广播发烧网

从 FM 到 WiFi:在 ESP32-S2 上做网络收音机

广播发烧网 发布于 电路及维修

你是否也曾在小时候,抱着收音机听着音乐入眠?那种悠扬的电台声音,伴随着我们的成长。今天,我们要带你回到那个时代,但这一次,我们用最新的技术重塑这段回忆!通过ESP32-S2开发板,我们不仅实现了传统的FM收音机功能,还能通过WiFi连接到网络电台,让你随时随地享受最爱的电台节目。

 

从基本的硬件配置到完整的功能实现,这个项目将带你走进ESP32-S2的世界,了解如何用这款小巧强大的开发板,创建一个支持FM收音机与网络电台的智能收音机。无论是通过FM接收电台,还是通过WiFi播放网络音乐,我们的方案都能为你提供完美的音质体验。

01

硬件介绍

ESP32-S2-MINI-1采用PCB板载天线,模组配置了4MB SPI flash,32 位LX7 单核处理器,工作频率高达 240 MHz。43 个 GPIO 口,14 个电容式传感 IO,支持 SPI、I2C、I2S、UART、ADC/DAC 和 PWM 等各种标准外设,支持 LCD 接口(8-bit 并口 RGB、8080、6800 接口),支持 8-/16-bit DVP 图像传感器接口,最高时钟频率支持到 40 MHz ,支持全速 USB OTG。

硬禾学堂在ESP32-S2-MINI-1的基础上,扩展了麦克风输入、按键输入、红外输入、FM收音机模块、12864OLED屏幕输出、扬声器(耳机接口)输出。这里尤其让我兴奋的就是RDA5807收音机模块,总是念念不忘以前抱着睡一觉入睡的日子。
程序编写支持官网提供的esp-idf、Arduino,CicruitPython。这里我选择的是Arduino。

02

任务选择

实现网络收音机/FM收音机的功能。可以通过WiFi接收网络上的电台,也可以通过FM模块接收空中的电台,并可以通过按键进行切换、选台。在OLED显示屏上显示网络电台的IP地址、节目名字等相关信息或FM信号的频段。系统能够自动校时,开机后自动调节到准确的时间(年、月、日、时、分、秒)。毕竟少年时期可是听着卡朋特的那首歌“When I was young,I’d listen to the radio”长大的,满满的都是回忆。

03

任务实现

有了任务目标、选好了编程语言和环境,接下来就是动手啦!

首先是实现OLED屏幕的显示,这个OLED屏幕是标准的1306驱动的12864的OLED。这里选用U8G2的库来驱动屏幕,这个屏幕使用的是SPI驱动的,使用U8G2的spi驱动函数,很容易就驱动起来。这里稍微留意一下板子上oled没有使用CS这个管脚,这里找一个闲置的管脚给初始化函数即可。
  •  
U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 36, /* data=*/ 35, /* cs=*/ 46, /* dc=*/ 33, /* reset=*/ 34);

然后是收音机模块,这个板子的收音机模块使用的是RDA5807,使用iic与esp32S2通讯,用了一个MUX来控制音源的选择,使用NCP2890作为扬声器驱动,不过可惜只有一个扬声器,所以只输出了左声道的音频。在Arduino中库管理中搜索RDA5807找到对应的库进行安装。然后再示例例中打开serialRadio的例子,就可以测试收音机模块啦!这里的FM频率从87-108MHz,但是不外接天线啥都收不到,必须外接天线才行。接收频率也可以扩展到65MHz,但是在那些范围,接了天线也啥都收不到。更奇怪的是始终没有收到电视伴音,记得小时候,FM收音机是可以收到电视伴音的。


04

相关代码

 


#include <RDA5807.h>
// I2C bus pin on ESP32
#define ESP32_I2C_SDA 5
#define ESP32_I2C_SCL 4
#define MAX_DELAY_RDS 40   // 40ms - polling method
long rds_elapsed = millis();
RDA5807 rx;
void showHelp()
{
  Serial.println("Type U to increase and D to decrease the frequency");
  Serial.println("Type S or s to seek station Up or Down");
  Serial.println("Type + or - to volume Up or Down");
  Serial.println("Type 0 to show current status");
  Serial.println("Type ? to this help.");
  Serial.println("==================================================");
  delay(1000);
}
// Show current frequency
void showStatus()
{
  char aux[80];
  sprintf(aux,"\nYou are tuned on %u MHz | RSSI: %3.3u dbUv | Vol: %2.2u | %s ",rx.getFrequency(), rx.getRssi(), rx.getVolume(), (rx.isStereo()) ? "Yes" : "No" );
  Serial.print(aux);
}
void setup()
{
    Serial.begin(115200);
    while (!Serial) ;
    pinMode(41, OUTPUT);
    pinMode(42, OUTPUT);
    digitalWrite(41, HIGH);
    digitalWrite(42, LOW);
    // The line below may be necessary to setup I2C pins on ESP32
    Wire.begin(ESP32_I2C_SDA, ESP32_I2C_SCL);

    rx.setup();
    rx.setVolume(6);
    delay(500);
    // Select a station with RDS service in your place
    Serial.print("\nEstacao 106.5MHz");
    rx.setFrequency(10650); // It is the frequency you want to select in MHz multiplied by 100.
    // Enables SDR
    rx.setRDS(true);
    showHelp();
    showStatus();

}
void loop()
{
  if (Serial.available() > 0)
  {
    char key = Serial.read();
    switch (key)
    {
    case '+':
      rx.setVolumeUp();
      break;
    case '-':
      rx.setVolumeDown();
      break;
    case 'U':
    case 'u':
      rx.setFrequencyUp();
      break;
    case 'D':
    case 'd':
      rx.setFrequencyDown();
      break;
    case 'S':
      rx.seek(RDA_SEEK_WRAP, RDA_SEEK_UP);
      break;
    case 's':
      rx.seek(RDA_SEEK_WRAP, RDA_SEEK_DOWN);
      break;
    case '0':
      showStatus();
      break;
    case '?':
      showHelp();
      break;
    default:
      break;
    }
    delay(200);
    showStatus();
  } 
  delay(5);
}
 

05

遇到的问题

接下来开始遇到巨大的困难了。wifi,校时都很easy搞定,毕竟esp家族的东西,天生就擅长连wifi。但是播放网络音乐却让人一筹莫展。阅读了几个网上的ESP32做的网络收音机项目,基本都是通过网络访问网络上的音频资源,然后通过I2S解码、播放。顺着这条思路向下走,遇到了第一个无法跨越的鸿沟。


在Arduino下,只要调用I2S的包,便会报错。多方寻找原因,终于明白了,在Arduino下应该是不支持I2S的库。虽然ESP32S2有I2S的支持,但是Arduino下的I2S的库又在esp32下验证过,但是很明显在S2下无法通过。最后网友给了个截图,彻底死了心。

既然通过I2S播放音乐走不通,那么就要想其它方式方法。扬声器是接在IO17管脚上的,是个DAC的输出,在Arduino下DAC的输出可以通过函数dacWrite直接操作。通过测试可以控制DAC输出电平。那么换个想法,那就是把播放音乐的过程,变成在指定的时间上给DAC管脚输出指定的电平,这样也能播放出音乐。接下来面临几个问题:


问题1:如何控制指定的时间?

音乐播放在时间上对应的是采样率。CD级别声音的采样率是44100,也就是每秒钟有44100个样本。如何在开发板上将每秒切分成44100份?上网搜索,找到一个使用定时器方式来获取时间中断的方法。使用timer,但是很可惜,程序编译、运行都没问题,就是进不了中断。百思不得其解,无奈下找来一块ESP32的开发板测试,发现程序没有问题,那么只能判断S2在Arduino下不支持这个方法。


退而求其次,还有个时间中断的方法:Ticker。但是这个方法最小的时间中断,测试了到1ms都是ok的,再小就不行了。难道要让我播放1000采样率的声音,那将会惨不忍睹啊!毕竟比较差音质的电话传递的声音,采样率都到了8000。几经周折,发现Ticker方法,在设置小于1ms的时间中断时,中断依然存在,而且都始终是一个值,仔细实验测量,发现只要在attach设置中断时长时,给的参数小于0.0001秒,那么中断时长就是个定值,这个定值大概是1/200000秒。这样一来,提供个0参数给Ticker,就能获得每秒约20000次的中断,就可以实现20000的DAC的输出!虽然不是正常的调用函数,但是也能实现需求啦。也算峰回路转了吧!


问题2:声音的解码

网络上大多数音频格式都是MP3或者其他压缩格式。这些格式的数据以流媒体方式在网上传输。而开发板播放声音只能是对应的无压缩的格式,并且开发板每秒钟要做20000次的中断,再处理解码,感觉力不从心。如果先获取完音频文件,解码后再播放,内存容量又不允许。最后采取折中方法,自建网络电台。使用电脑强大的解码功能,将mp3解码成无压缩格式wav的文件,再通过搭建socket服务,开发板使用socket通过网络连接到对应的服务,获得声音数据,然后播放。这样也就完成了网络收音机的功能。这里mp3转wav格式我使用的是ffmpeg来做转换的。

  •  
ffmpeg -i  "王菲 - 传奇.mp3" -acodec pcm_s16le -ac 2 -ar 44100 chuanqi.wav

问题3:音质问题

一般我们听的音乐,为了保证音质,都会选用采样率高的声音文件。这里我选的源文件都是44100采样率的,为了适应开发板的播放,会降到20000的采样率,这个损失倒是不大。源文件一般都是双声道,开发板只有一个声道,所以截取源声音的左声道,这里影响也不大。影响最大的是“量化位数”。也就是每次采样都是使用16位的数来存取声音,这样声音的变化就有65536种变化了,而DAC这里只是一个8位的输出,所以需要将16位的声音映射到8位的声音上。也就是每次采样中只有256种变化,严重影响了声音的品质。这里功放芯片没有提供放大缩小声音的功能,如果想放大缩小声音,只能改变DAC的输出电平,考虑声音的放大缩小,将更进一步损害音质,所以在播放网络音乐时不再允许调节音量。而且在最后测试中,发现OLED的刷新也会影响到声音的播放,解决方案就是 在播放音乐时,降低OLED的刷新,降低到5~7秒才刷新一次OLED屏幕。

解决完以上问题,剩下的就是做功能的组合,实现任务。在系统启动后,OLED显示"wellcom”,程序就开始扫描FM的波段,从87MHz,每0.1MHz做步近值,扫描到108MHz,过滤掉Rssi低于30的信号,对超过30的信号,再寻找信号最好的频率记录下来,最多纪录10个台。搜索完FM波段后,连接wifi,然后通过互联网"pool.ntp.org"获得当前时间。由于开机初始化需要做的事比较多,所以启动速度有点慢。


然后是三种状态。最左边按键负责切换状态。1、静音状态:不播放声音,仅仅显示时间和当前IP。2、收音机播放状态。从左开始第二个键负责切换电台,右侧两个按键控制音量。音量从0~15可以调节。不过到了15声音也不大。3、网络音乐播放。自动连接服务器(如果连不上,显示"Connection failed."),连接上后显示服务器信息。


06

后记

CircuitPython支持:这里记录一下CircuitPython支持的过程,毕竟也是折腾了好久。

1.烧写固件

通过板子上的TypecUSB口,与电脑usb相连接,使用flash_download_tool 或者用esptool.py来烧写固件(adafruit-circuitpython-muselab_nanoesp32_s2-en_US-20210426-7cf8f79)。我这里使用的是flash_download_tool来烧写的。


2.编程

烧写完成后,就不用再通过typecUSB口与电脑相连接了。需要通过板子上的USB口与电脑连接,因为板子上是个USB母头,所以需要用两头都是公头的USB线与电脑相连接。连接后电脑会识别到一个新的盘符。就可以通过相关软件进行编程啦!我这里使用的是Thonny。

我们提供各种基于ESP32-S2的开发板和扩展模块,帮助你轻松实现更多有趣的项目。我们的开发板兼容多种编程环境,支持Arduino、CircuitPython等,让你从入门到进阶,快速搭建你的硬件项目。
文章目录