之前在使用 RDA5807FP制作收音机的时候【参考1】曾经注意到,芯片提供了 I2S输出。因此,这次同样使用这个芯片制作一个将收到的信号通过USB传输到电脑中的设备。
设计上使用DFRobot出品的FireBeetle 2 Board ESP32-S3-U作为主控。其中的核心芯片是 ESP32 S3 带有 USB Device的功能,我们通过代码将它模拟为USB麦克风,插入电脑后无需驱动即可直接使用。此外,芯片还模拟出一个 USB 串口,用于主机发送例如选台之类的命令。

硬件非常简单,但是细节很多(前后打了三版)。总结下来需要注意的有如下几点:
1. RDA5807FP 芯片有假货,我最开始买的装上之后只有沙沙声无法收到电台。最后咨询了B站网友“我是电视电视迷”,他推荐我在“深圳市义胜电子网店”购买了,工作正常;
2. RDA5807FP芯片有两个工作模式:直接工作模式和单片机模式。前者是意思是上电之后芯片会根据 Pin7 和 Pin8 调整频率,Pin15和Pin16 调整音量;后者的意思是上电后需要使用单片机从 I2C 通讯来控制。但是,这个模式切换引脚在 Datasheet上是标记为 GND 的隐藏起来的。
3. 供电部分使用了LP5907MFX-3.3,主要是为了降低电源噪音;
4. 对于接收信号影响最大的是天线部分,这次的设计将天线和耳机分开两个接口。二者是可以放在一起的,耳机的地线作为天线使用,效果也非常不错。当使用耳机作为天线的时候,耳机仍然能够正常工作。
5. PCB 布线需要特别注意:不要在芯片底部走线,会影响效果。
最终电路图如下,可以看到设计上非常简单,左侧是一个2.54mm座子,右侧是 RDA芯片:

PCB设计如下:

最终焊接后的成品如下:

拿到PCB 后,先进行简单的焊接,不要连接FireBeetle板子,将跳线跳到直接工作模式后,从外部引入5V 供电即可在耳机中听到沙沙声,如果没有声音请检查RDA5807FP焊接特别是供电部分。之后,将 SCL 或者SDA 引脚短暂接地,这样能够启动RDA5807FP的自动搜台功能,如果无法搜索到任何频道,请检查天线部分。
接下来进行代码设计。为了实现USB 麦克风,这里使用 CherryUSB 框架,CherryUSB 是一款国产的小而美的、可移植性高的、用于嵌入式系统的 USB 主从协议栈。
1. 创建一个 USB 麦克风,对于 CherryUSB 来说,已经准备好了大部分的代码,只需要下面设定描述符之类的动作即可
usbd_desc_register(busid, cdc_audio_v1_descriptor);
usbd_add_interface(busid, usbd_audio_init_intf(busid, &intf0, 0x0100, audio_entity_table, 1));
usbd_add_interface(busid, usbd_audio_init_intf(busid, &intf1, 0x0100, audio_entity_table, 1));
usbd_add_endpoint(busid, &audio_in_ep);
usbd_initialize(busid, reg_base, usbd_event_handler);
2. 创建一个USB 串口(USB CDC)
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf2));
usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf3));
usbd_add_endpoint(busid, &cdc_out_ep);
usbd_add_endpoint(busid, &cdc_in_ep);
3. 初始化RDA5807 (初始化使用 I2S 输出音频之后,仍然能够在耳机输出口听到音频输出,这对于调试帮助很大)。初始化使用 I2S 输出之后仍然能够在耳机插孔中听到接收到的声音,这样对于调试非常有帮助。代码涉及到的寄存器可以在 RDA5807 手册上查到。
void FMInit() {
i2c_master_init(&bus_handle, &dev_handle);
rda_reg02 rda2;
rda2.raw = 0;
rda2.refined.NEW_METHOD = 0;
rda2.refined.RDS_EN = 0;
rda2.refined.CLK_MODE = 0;
rda2.refined.RCLK_DIRECT_IN = 0;
rda2.refined.NON_CALIBRATE = 0;
rda2.refined.MONO = 0;
rda2.refined.DMUTE = 1;
rda2.refined.DHIZ = 1;
rda2.refined.ENABLE = 1;
rda2.refined.BASS = 1;
rda2.refined.SEEK = 0;
writeRegister(0x02, rda2.raw);
rda_reg05 rda5;
rda5.raw = 0x00;
rda5.refined.INT_MODE = 0;
rda5.refined.LNA_PORT_SEL = 2;
rda5.refined.LNA_ICSEL_BIT = 0;
rda5.refined.SEEKTH = 8;
rda5.refined.VOLUME = 0;
writeRegister(0x05, rda5.raw);
vTaskDelay(pdMS_TO_TICKS(500)); // 必须的
ESP_LOGI(TAG, "Reg04: %X",readRegister(0x04));
ESP_LOGI(TAG, "Reg06: %X",readRegister(0x06));
uint16_t reg04 = readRegister(0x04);
reg04 |= (1 << 6); //I2S_ENABLED
writeRegister(0x04, reg04);
uint16_t reg06 = readRegister(0x06);
reg06 |= (1 << 12)|(1<<9); //I2S_MODE == Slave Mode
writeRegister(0x06, reg06);
ESP_LOGI(TAG,"Reg06: %X\n", readRegister(0x06));
ESP_LOGI(TAG, "Reg04: %X\n",readRegister(0x04));
ESP_LOGI(TAG, "Reg06: %X\n",readRegister(0x06));
vTaskDelay(pdMS_TO_TICKS(500)); // 必须的
setFreq(10170); // 设置频率为101.7MHz
}
4.获得I2S数据的代码,获得数据之后会放置在一个 Ring Buffer 中,这样的设计可以尽量让代码并行处理
void task_func(void *arg)
{
i2c_mic_rx_data_t rx_data;
while (1)
{
{
size_t out_len = 0;
rx_data.buffer = malloc(AUDIO_IN_PACKET);
rx_data.size = AUDIO_IN_PACKET;
i2s_channel_read(rx_handle, rx_data.buffer, rx_data.size, &out_len, 10);
// 将数据放入队列
if (xQueueSend(s_receive_queue, &rx_data, portMAX_DELAY) != pdTRUE) {
USB_LOG_RAW("Send2 Err %x\n",rx_data.buffer);
}
}
}
vTaskDelete(NULL);
}
5. 处理USB 串口收到的数据代码。特别注意,收到的数据不可以立即处理,因为需要通过 I2C 对RDA5807FP 发送数据,耗时较长阻塞整个程序。
void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
USB_LOG_RAW(TAG,"actual out len:%d\r\n", nbytes);
if ((nbytes==0x05)&&(read_buffer[0]=='S')) {
write_buffer[0]='S';write_buffer[1]='t';write_buffer[2]=read_buffer[1];
if (read_buffer[1] == '1') {
i2ccmd=1;
} else if (read_buffer[1] == '2') {
i2ccmd=2;
} else if (read_buffer[1] == '3') {
i2ccmd=3;
} else if (read_buffer[1] == '4') {
i2ccmd=4;
} else if (read_buffer[1] == '5') {
i2ccmd=5;
} else if (read_buffer[1] == '6') {
i2ccmd=6;
} else if (read_buffer[1] == '7') {
i2ccmd=7;
} else if (read_buffer[1] == '8') {
i2ccmd=8;
} else if (read_buffer[1] == '9') {
i2ccmd=9;
} else if (read_buffer[1] == 'a') {
i2ccmd=0x0a;
} else {
write_buffer[0]='U';write_buffer[1]='N';write_buffer[2]='W';
}
cdc_ep_tx_busy_flag = true;
usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, 3);
} else {
// 将接收到的数据复制到发送缓冲区
memcpy(write_buffer, read_buffer, nbytes);
// 启动数据发送(回环)
cdc_ep_tx_busy_flag = true;
usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, nbytes);
}
/* setup next out ep read transfer */
usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
}
6. 从设备向主机发送音频数据
// 没有数据,提前接收,准备好数据
if (0 == rx_data.size)
{
if (pdPASS == xQueueReceive(s_receive_queue, &rx_data, 1))
{
if (rx_data.size != 64)
{
USB_LOG_INFO("Recv %x , size=%d\n", rx_data.buffer, rx_data.size);
}
//else USB_LOG_INFO("GD");
}
else
{
rx_data.size = 0;
}
}
// 空闲循环的时候也要等待,让出CPU跑别的任务
if (0 == tx_flag)
{
stop_count++;
xSemaphoreTake(sign_tx, 1);
}
// 等待发送
if (tx_flag && rx_data.size)
{
loop_count--;
// 如果当前没有发送
if (ep_tx_busy_flag != true)
{
ep_tx_busy_flag = true;
xSemaphoreTake(sign_tx, 0);
// 发送数据到 USB
usbd_ep_start_write(0, AUDIO_IN_EP, rx_data.buffer, rx_data.size);
}
xSemaphoreTake(sign_tx, 10);
while (ep_tx_busy_flag)
{
if (tx_flag == false)
{
break;
}
}
// 发送完成,释放缓冲区
rx_data.size = 0;
free(rx_data.buffer);
}
整体代码框架是天杀帮忙设计的,可以方便的迁移到其他使用 I2S 通讯的项目中,有兴趣的朋友可以深入研究。
RDA5807FP 是上海锐迪科推出的高集成度国产 FM 收音机调谐器芯片,个人感觉可玩度很高。只需要几个外围元件即可接收播放调频广播;同时还可以通过单片机进行控制。这次更将玩法深入一步,通过USB 和计算机连接在一起。有兴趣的朋友不妨试试。
参考:
1. https://oshwhub.com/zoologist/rda5807fp-shou-yin-ji RDA5807FP收音机

返回首页
回到顶部
评论