文章目录
前言
本文将介绍如何在 Arduino 平台上使用 SI4703 FM 收音机模块,并结合完整的驱动代码进行讲解。通过串口监视器我们可以控制模块并查看调试信息。本文内容包括模块简介、引脚定义、通讯时序、主要寄存器和驱动代码以及总结介绍,适合对 FM 收音机模块感兴趣的开发者参考。
一、SI4703 模块简介
SI4703 是一款低功耗 FM 收音机芯片,支持 RDS广播数据,具有简单的 I2C 接口,主要用于便携式无线音频接收。主要特性如下:
- 支持全球 FM 频段 (76–108MHz)
- 集成 VCO 的频率合成器
- 搜索调谐
- 自动频率控制 (AFC)
- 自动增益控制 (AGC)
- 音量控制
- 2 线和 3 线控制接口
- 支持RDS广播
二、引脚定义
3.3V / GND | 模块供电 |
---|---|
SDIO | 数据输入输出引脚 |
SCLK | 时钟输入引脚 |
SET | 串行使能输入,用于切换2/3线制模式,低电平则为3线制,模块默认2线制I2C |
RST | 复位引脚,低电平有效 |
GPIO1 / GPIO2 | 通用IO口,可用于产生中断 |
三、通讯时序
2线制通讯时序
3线制通讯时序
四、主要寄存器说明
POWERCFG (0x02):控制模块电源、寻台和调谐操作。
SYSCONFIG1 (0x04) 和 SYSCONFIG2 (0x05):用于配置RDS功能、音频调节、频道间隔以及音量等。
STATUSRSSI (0x0A):包含状态信息,如调谐完成标志(STC)和寻台失败标志(SFBL)。
READCHAN (0x0B):用于读取当前调谐的频道信息。
五、SI4703关键驱动函数说明
si4703_init()
对SI4703模块进行复位和初始化。该函数先控制复位引脚和SDIO引脚(使模块进入2线模式),然后通过I2C读取寄存器,设置内部振荡器(寄存器0x07设置为0x8100)、启用芯片(POWERCFG寄存器设置为0x4001)、开启RDS功能和设置频道间隔,并设置初始音量为最低。中间使用了延时确保时序满足要求。
readRegisters()
通过I2C请求并读取32个字节数据(16个16位寄存器),存入全局数组 si4703_registers[]。
updateRegisters()
将修改后的寄存器值写回模块,对寄存器0x02到0x07进行写操作,保证新的配置生效。
setChannel(channel)
根据传入的频道值(例如973表示97.3MHz),进行必要的转换后写入频道寄存器,并启动调谐过程。该函数先清除原有频道位,写入新频道值,然后设置TUNE位启动调谐,轮询STC位(调谐完成标志),再清除TUNE位,最终等待STC位清除后完成调谐。
setVolume(volume)
读取当前寄存器,清除并写入音量值到SYSCONFIG2寄存器中,然后更新寄存器。
seek()
seekUp() 和 seekDown() 分别调用 seek() 函数,实现向上或向下自动搜台功能。内部会设置寻台相关位,并轮询STC位判断寻台是否完成,再返回当前频道值。
readRDS()
轮询读取RDS数据,将收集到的字符对存入传入的buffer中,超时后返回空字符串。
getChannel()
从READCHAN寄存器中读取当前频道值,并做简单转换后返回(例如返回973表示97.3MHz)。
六、Arduino驱动SI4703
引脚接线
Arduino | SI4703 |
---|---|
3.3V / GND | 3.3V / GND |
A4 | SDIO |
A5 | SCLK |
D2 | RST |
命令控制
+、- | 音量加减 |
---|---|
a、b | 指定电台 |
u、d | 向上、向下搜频 |
r | 打印RDS数据 |
代码示例
#include "Arduino.h"
#include "Si4703_Breakout.h"
#include "Wire.h"
Si4703_Breakout::Si4703_Breakout(int resetPin, int sdioPin, int sclkPin)
{
_resetPin = resetPin;
_sdioPin = sdioPin;
_sclkPin = sclkPin;
}
void Si4703_Breakout::powerOn()
{
si4703_init();
}
void Si4703_Breakout::setChannel(int channel)
{
//Freq(MHz) = 0.200(in USA) * Channel + 87.5MHz
//97.3 = 0.2 * Chan + 87.5
//9.8 / 0.2 = 49
int newChannel = channel * 10; //973 * 10 = 9730
newChannel -= 8750; //9730 - 8750 = 980
newChannel /= 10; //980 / 10 = 98
//These steps come from AN230 page 20 rev 0.5
readRegisters();
si4703_registers[CHANNEL] &= 0xFE00; //Clear out the channel bits
si4703_registers[CHANNEL] |= newChannel; //Mask in the new channel
si4703_registers[CHANNEL] |= (1<<TUNE); //Set the TUNE bit to start
updateRegisters();
//delay(60); //Wait 60ms - you can use or skip this delay
//Poll to see if STC is set
// while(1) {
// readRegisters();
// if( (si4703_registers[STATUSRSSI] & (1<<STC)) != 0) break; //Tuning complete!
// }
unsigned long tuneStart = millis();
while(1) {
readRegisters(); // 也要加超时判断
if( (si4703_registers[STATUSRSSI] & (1<<STC)) != 0) break;
if(millis() - tuneStart > 1000) { // 超时1秒
Serial.println("Tuning setChannel() timed out waiting for STC");
break;
}
}
readRegisters();
si4703_registers[CHANNEL] &= ~(1<<TUNE); //Clear the tune after a tune has completed
updateRegisters();
//Wait for the si4703 to clear the STC as well
while(1) {
readRegisters();
if( (si4703_registers[STATUSRSSI] & (1<<STC)) == 0) break; //Tuning complete!
}
}
int Si4703_Breakout::seekUp()
{
return seek(SEEK_UP);
}
int Si4703_Breakout::seekDown()
{
return seek(SEEK_DOWN);
}
void Si4703_Breakout::setVolume(int volume)
{
readRegisters(); //Read the current register set
if(volume < 0) volume = 0;
if (volume > 15) volume = 15;
si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
si4703_registers[SYSCONFIG2] |= volume; //Set new volume
updateRegisters(); //Update
}
void Si4703_Breakout::readRDS(char* buffer, long timeout)
{
long endTime = millis() + timeout;
boolean completed[] = {false, false, false, false};
int completedCount = 0;
while(completedCount < 4 && millis() < endTime) {
readRegisters();
if(si4703_registers[STATUSRSSI] & (1<<RDSR)){
// ls 2 bits of B determine the 4 letter pairs
// once we have a full set return
// if you get nothing after 20 readings return with empty string
uint16_t b = si4703_registers[RDSB];
int index = b & 0x03;
if (! completed[index] && b < 500)
{
completed[index] = true;
completedCount ++;
char Dh = (si4703_registers[RDSD] & 0xFF00) >> 8;
char Dl = (si4703_registers[RDSD] & 0x00FF);
buffer[index * 2] = Dh;
buffer[index * 2 +1] = Dl;
// Serial.print(si4703_registers[RDSD]); Serial.print(" ");
// Serial.print(index);Serial.print(" ");
// Serial.write(Dh);
// Serial.write(Dl);
// Serial.println();
}
delay(40); //Wait for the RDS bit to clear
}
else {
delay(30); //From AN230, using the polling method 40ms should be sufficient amount of time between checks
}
}
if (millis() >= endTime) {
buffer[0] ='\0';
return;
}
buffer[8] = '\0';
}
//To get the Si4703 inito 2-wire mode, SEN needs to be high and SDIO needs to be low after a reset
//The breakout board has SEN pulled high, but also has SDIO pulled high. Therefore, after a normal power up
//The Si4703 will be in an unknown state. RST must be controlled
void Si4703_Breakout::si4703_init()
{
pinMode(_resetPin, OUTPUT);
pinMode(_sdioPin, OUTPUT); //SDIO is connected to A4 for I2C
digitalWrite(_sdioPin, LOW); //A low SDIO indicates a 2-wire interface
digitalWrite(_resetPin, LOW); //Put Si4703 into reset
delay(1); //Some delays while we allow pins to settle
digitalWrite(_resetPin, HIGH); //Bring Si4703 out of reset with SDIO set to low and SEN pulled high with on-board resistor
delay(1); //Allow Si4703 to come out of reset
Wire.begin(); //Now that the unit is reset and I2C inteface mode, we need to begin I2C
readRegisters(); //Read the current register set
//si4703_registers[0x07] = 0xBC04; //Enable the oscillator, from AN230 page 9, rev 0.5 (DOES NOT WORK, wtf Silicon Labs datasheet?)
si4703_registers[0x07] = 0x8100; //Enable the oscillator, from AN230 page 9, rev 0.61 (works)
updateRegisters(); //Update
delay(500); //Wait for clock to settle - from AN230 page 9
readRegisters(); //Read the current register set
si4703_registers[POWERCFG] = 0x4001; //Enable the IC
// si4703_registers[POWERCFG] |= (1<<SMUTE) | (1<<DMUTE); //Disable Mute, disable softmute
si4703_registers[SYSCONFIG1] |= (1<<RDS); //Enable RDS
si4703_registers[SYSCONFIG1] |= (1<<DE); //50kHz Europe setup
si4703_registers[SYSCONFIG2] |= (1<<SPACE0); //100kHz channel spacing for Europe
si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
si4703_registers[SYSCONFIG2] |= 0x0001; //Set volume to lowest
updateRegisters(); //Update
delay(110); //Max powerup time, from datasheet page 13
}
void Si4703_Breakout::readRegisters()
{
Wire.requestFrom(SI4703, 32);
// 加超时
unsigned long start = millis();
while(Wire.available() < 32) {
if(millis() - start > 1000) {
Serial.println("readRegisters timed out!");
return; // 直接return,表示读取失败
}
}
for(int x = 0x0A ; ; x++) {
if(x == 0x10) x = 0;
si4703_registers[x] = Wire.read() << 8;
si4703_registers[x] |= Wire.read();
if(x == 0x09) break;
}
}
//Write the current 9 control registers (0x02 to 0x07) to the Si4703
//It's a little weird, you don't write an I2C addres
//The Si4703 assumes you are writing to 0x02 first, then increments
byte Si4703_Breakout::updateRegisters() {
Wire.beginTransmission(SI4703);
//A write command automatically begins with register 0x02 so no need to send a write-to address
//First we send the 0x02 to 0x07 control registers
//In general, we should not write to registers 0x08 and 0x09
for(int regSpot = 0x02 ; regSpot < 0x08 ; regSpot++) {
byte high_byte = si4703_registers[regSpot] >> 8;
byte low_byte = si4703_registers[regSpot] & 0x00FF;
Wire.write(high_byte); //Upper 8 bits
Wire.write(low_byte); //Lower 8 bits
}
//End this transmission
byte ack = Wire.endTransmission();
if(ack != 0) { //We have a problem!
return(FAIL);
}
return(SUCCESS);
}
//Seeks out the next available station
//Returns the freq if it made it
//Returns zero if failed
int Si4703_Breakout::seek(byte seekDirection){
readRegisters();
//Set seek mode wrap bit
si4703_registers[POWERCFG] |= (1<<SKMODE); //Allow wrap
//si4703_registers[POWERCFG] &= ~(1<<SKMODE); //Disallow wrap - if you disallow wrap, you may want to tune to 87.5 first
if(seekDirection == SEEK_DOWN) si4703_registers[POWERCFG] &= ~(1<<SEEKUP); //Seek down is the default upon reset
else si4703_registers[POWERCFG] |= 1<<SEEKUP; //Set the bit to seek up
si4703_registers[POWERCFG] |= (1<<SEEK); //Start seek
updateRegisters(); //Seeking will now start
//Poll to see if STC is set
while(1) {
readRegisters();
if((si4703_registers[STATUSRSSI] & (1<<STC)) != 0) break; //Tuning complete!
}
readRegisters();
int valueSFBL = si4703_registers[STATUSRSSI] & (1<<SFBL); //Store the value of SFBL
si4703_registers[POWERCFG] &= ~(1<<SEEK); //Clear the seek bit after seek has completed
updateRegisters();
//Wait for the si4703 to clear the STC as well
while(1) {
readRegisters();
if( (si4703_registers[STATUSRSSI] & (1<<STC)) == 0) break; //Tuning complete!
}
if(valueSFBL) { //The bit was set indicating we hit a band limit or failed to find a station
return(0);
}
return getChannel();
}
//Reads the current channel from READCHAN
//Returns a number like 973 for 97.3MHz
int Si4703_Breakout::getChannel() {
readRegisters();
int channel = si4703_registers[READCHAN] & 0x03FF; //Mask out everything but the lower 10 bits
//Freq(MHz) = 0.100(in Europe) * Channel + 87.5MHz
//X = 0.1 * Chan + 87.5
channel += 875; //98 + 875 = 973
return(channel);
}
#include "Si4703_Breakout.h"
#include <Wire.h>
int resetPin = 2;
int SDIO = A4;
int SCLK = A5;
Si4703_Breakout radio(resetPin, SDIO, SCLK);
int channel;
int volume = 5;
char rdsBuffer[10];
void setup()
{
Serial.begin(9600);
Serial.println("\n\nSi4703_Breakout Test Sketch");
Serial.println("===========================");
Serial.println("a b Favourite stations");
Serial.println("+ - Volume (max 15)");
Serial.println("u d Seek up / down");
Serial.println("r Listen for RDS Data (15 sec timeout)");
Serial.println("Send me a command letter.");
radio.powerOn();
radio.setVolume(volume);
}
void loop()
{
if (Serial.available())
{
char ch = Serial.read();
if (ch == 'u')
{
channel = radio.seekUp();
displayInfo();
}
else if (ch == 'd')
{
channel = radio.seekDown();
displayInfo();
}
else if (ch == '+')
{
volume ++;
if (volume == 16) volume = 15;
radio.setVolume(volume);
displayInfo();
}
else if (ch == '-')
{
volume --;
if (volume < 0) volume = 0;
radio.setVolume(volume);
displayInfo();
}
else if (ch == 'a')
{
channel = 930; // Rock FM
radio.setChannel(channel);
displayInfo();
}
else if (ch == 'b')
{
channel = 974; // BBC R4
radio.setChannel(channel);
displayInfo();
}
else if (ch == 'r')
{
//不断轮询寄存器,直到收到完整的RDS数据或超时,需要一定时间
Serial.println("RDS listening");
radio.readRDS(rdsBuffer, 15000);
Serial.print("RDS heard:");
Serial.println(rdsBuffer);
}
}
}
void displayInfo()
{
Serial.print("Channel:"); Serial.print(channel);
Serial.print(" Volume:"); Serial.println(volume);
}
效果展示
总结
本文简单介绍了如何在 Arduino 上使用 SI4703 FM 收音机模块。希望本文能帮助你深入理解 SI4703 模块的工作原理以及驱动代码的实现过程。如果大家有好的优化改进建议,欢迎在评论区讨论交流。
如需源码可点赞加收藏在评论留下邮箱获取!!!