暑假一起练——基于ESP32-S2-Mini-1模块制作的收音机
ESP32-S2 Arduino C 调频收音机 wav 音频解码 采样率 DAC U8G2 OLED
标签
嵌入式系统
测试
aramy
更新2021-09-05
3057

    硬件介绍:ESP32-S2是乐鑫2019年推出的一款新芯片。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。

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

    任务实现:有了任务目标、选好了编程语言和环境,接下来就是动手啦!
    首先是实现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收音机是可以收到电视伴音的。Fmdl4HFXXP--nvnn9KrLk0V_0ltV

#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);
}

    接下来开始遇到巨大的困难了。wifi,校时都很easy搞定,毕竟esp家族的东西,天生就擅长连wifi。但是播放网络音乐却让人一筹莫展。阅读了几个网上的ESP32做的网络收音机项目,基本都是通过网络访问网络上的音频资源,然后通过I2S解码、播放。顺着这条思路向下走,遇到了第一个无法跨越的鸿沟。FgRaphTfvVnTmV0hhbI-ITTbGrAH
在Arduino下,只要调用I2S的包,便会报错。多方寻找原因,终于明白了,在Arduino下应该是不支持I2S的库。虽然ESP32S2有I2S的支持,但是Arduino下的I2S的库又在esp32下验证过,但是很明显在S2下无法通过。最后网友给了个截图,彻底死了心。FrQCbOuPM2krAbdff6PP64HdWfUt
    既然通过I2S播放音乐走不通,那么就要想其它方式方法。扬声器是接在IO17管脚上的,是个DAC的输出,在Arduino下DAC的输出可以通过函数dacWrite直接操作。通过测试可以控制DAC输出电平。那么换个想法,那就是把播放音乐的过程,变成在指定的时间上给DAC管脚输出指定的电平,这样也能播放出音乐。接下来面临几个问题:
    问题1:如何控制指定的时间?音乐播放在时间上对应的是采样率。CD级别声音的采样率是44100,也就是每秒钟有44100个样本。如何在开发板上将每秒切分成44100份?上网搜索,找到一个使用定时器方式来获取时间中断的方法。使用timer,但是很可惜,程序编译、运行都没问题,就是进不了中断。百思不得其解,无奈下找来一块ESP32的开发板测试,发现程序没有问题,那么只能判断S2在Arduino下不支持这个方法。Ftr_rtvEEDFmTmK-OGhXgtlSSAIg
    退而求其次,还有个时间中断的方法: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."),连接上后显示服务器信息。FpacYHAwHq9F2UeoLfg4DooyQni_Fpz7KaGl-BH-Y3f7v2HT_iJMXBFWFpacYHAwHq9F2UeoLfg4DooyQni_

    心得体会非常感谢硬禾学堂和电子森林,ESP32-S2算是很新的芯片了,通过这次活动能够很好地掌握这款芯片的使用。结识了许多朋友。

 

   后记:CircuitPython支持:这里记录一下CircuitPython支持的过程,毕竟也是折腾了好久。
    1、烧写固件。通过板子上的TypecUSB口,与电脑usb相连接,使用flash_download_tool 或者用esptool.py来烧写固件(adafruit-circuitpython-muselab_nanoesp32_s2-en_US-20210426-7cf8f79)。我这里使用的是flash_download_tool来烧写的。
FnMhai6eCKuSD2on_ogig8GbphCR
    2、烧写完成后,就不用再通过typecUSB口与电脑相连接了。需要通过板子上的USB口与电脑连接,因为板子上是个USB母头,所以需要用两头都是公头的USB线与电脑相连接。连接后电脑会识别到一个新的盘符。就可以通过相关软件进行编程啦!我这里使用的是Thonny。FtmACmUTVSJNGjl2pdhDmXtfFoNOFqkdakNFbCwuz2p-W8cRjZAO7xfs

附件下载
adafruit-circuitpython-muselab_nanoesp32_s2-en_US-20210426-7cf8f79.bin
python_socket_server.zip
esp32s2.zip
Arduino程序
团队介绍
团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号