Si4703 FM Radio Receiver Driver in Rust

Si4703 found a channel after seeking
 

Today I present you a platform-agnostic Rust driver for the Si4703 and Si4702 FM radio receivers (turners).

The devices

The Si4702/03-C19 extends Silicon Laboratories Si4700/Si4701 FM tuner family, and further increases the ease and attractiveness of adding FM radio reception to mobile devices through small size and board area, minimum component count, flexible programmability, and superior, proven performance.

The device offers significant programmability, and caters to the subjective nature of FM listeners and variable FM broadcast environments world-wide through a simplified programming interface and mature functionality.

The Si4703-C incorporates a digital processor for the European Radio Data System (RDS) and the US Radio Broadcast Data System (RBDS) including all required symbol decoding, block synchronization, error detection, and error correction functions.

RDS enables data such as station identification and song name to be displayed to the user. The Si4703-C offers a detailed RDS view and a standard view, allowing adopters to selectively choose granularity of RDS status, data, and block errors.

Documentation:

Using the driver

To use the device from Rust, you have to add the si4703 crate to your project as well as a concrete implementation of the embedded-hal traits. For example if you are using the Raspberry Pi running Linux (see driver-examples for bare-metal hardware):

1
2
3
4
5
6
# Cargo.toml
...
[dependencies]
si4703 = "0.1"
linux-embedded-hal = "0.3"
nb = "0.1"

Here is an example program which will seek to the first FM channel available and print it (source):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use embedded_hal::blocking::delay::DelayMs;
use linux_embedded_hal::{Delay, I2cdev, Pin};
use nb::block;
use si4703::{
reset_and_select_i2c_method1, ChannelSpacing, DeEmphasis, SeekDirection, SeekMode, Si4703,
Volume,
};

fn main() {
let mut delay = Delay {};
{
// Reset and communication protocol selection must be done beforehand
let mut sda = Pin::new(2);
let mut rst = Pin::new(17);
reset_and_select_i2c_method1(&mut rst, &mut sda, &mut delay).unwrap();
}
let dev = I2cdev::new("/dev/i2c-1").unwrap();
let mut radio = Si4703::new(dev);
radio.enable_oscillator().unwrap();
// Wait for the oscillator to stabilize
delay.delay_ms(500_u16);
radio.enable().unwrap();
// Wait for powerup
delay.delay_ms(110_u16);

radio.set_volume(Volume::Dbfsm28).unwrap();
radio.set_deemphasis(DeEmphasis::Us50).unwrap();
radio.set_channel_spacing(ChannelSpacing::Khz100).unwrap();
radio.unmute().unwrap();

let stc_int = Pin::new(27);
// Seek using STC interrupt pin
block!(radio.seek_with_stc_int_pin(SeekMode::Wrap, SeekDirection::Up, &stc_int)).unwrap();
let channel = radio.channel().unwrap_or(-1.0);
println!("Found channel at {:1} MHz", channel);
}

I also created an example program that runs on the STM32F1 “BluePill” board which seeks an FM channel forward or backwards when pressing a button and prints it on an OLED display (the one in the title picture). You can find the source code here.

In the driver-examples repository you can find bare-metal examples which you can adapt to do other things with this device.

Where to go from here?

There is much more information and example programs in the crate documentation.
If you encounter any issues, please report them in the issue tracker.
Feedback, suggestions and improvements are gladly welcome.

What’s next?

I have been writing many other platform-agnostic Rust drivers although I am slow to announce them here. If you want to know what I am currently working on you can follow me on github.

Thanks for reading and stay tuned!

Links: Source code - Crate - Documentation

Share