基于Arduino驱动 SI4703 FM 收音机模块接收无线电台


前言

  本文将介绍如何在 Arduino 平台上使用 SI4703 FM 收音机模块,并结合完整的驱动代码进行讲解。通过串口监视器我们可以控制模块并查看调试信息。本文内容包括模块简介、引脚定义、通讯时序、主要寄存器和驱动代码以及总结介绍,适合对 FM 收音机模块感兴趣的开发者参考。


一、SI4703 模块简介

  SI4703 是一款低功耗 FM 收音机芯片,支持 RDS广播数据,具有简单的 I2C 接口,主要用于便携式无线音频接收。主要特性如下:

  1. 支持全球 FM 频段 (76–108MHz)
  2. 集成 VCO 的频率合成器
  3. 搜索调谐
  4. 自动频率控制 (AFC)
  5. 自动增益控制 (AGC)
  6. 音量控制
  7. 2 线和 3 线控制接口
  8. 支持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

引脚接线

ArduinoSI4703
3.3V / GND3.3V / GND
A4SDIO
A5SCLK
D2RST

命令控制

+、-音量加减
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 模块的工作原理以及驱动代码的实现过程。如果大家有好的优化改进建议,欢迎在评论区讨论交流。
  如需源码可点赞加收藏在评论留下邮箱获取!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值