项目描述
1.1项目介绍
此项目是以基于ESP32-S2模块的物联网/音频处理平台,搭载Arduino开发环境,协同Python socket库,实现了网络收音机和FM收音机的以下基本功能:
1可以通过WiFi接收网络上的电台,也可以通过FM模块接收空中的电台,并可以通过按键进行切换、选台
2在OLED显示屏上显示网络电台的IP地址、节目名字等相关信息或FM信号的频段
3系统能够自动校时,开机后自动调节到准确的时间(年、月、日、时、分、秒)
4可以通过FM接收电台信号,并播放出来
5通过OLED显示电台的频率
1.2项目设计思路
项目设计思路是从实现的目标出发,首先考虑FM收音机部分,对ESP32-S2学习板上的RDA5807模块相应的技术规格进行了解,包括实现原理和音频信号处理的过程,再将其在OLED显示屏上显示,其中需要考虑校时部分,因此需要解决网络连接部分;其次是网络收音机部分,参考多种案例,选择以音频网络服务器作为音频源来源,还是以本地服务器作为音频源。通过预想所要实现的目标,评估ESP32-S2的配置,选用Arduino作为开发环境与烧写软件。
硬件介绍
1.2.1核心控制器介绍
本平台使用了乐鑫公司的ESP32-S2-Mini-1模块,ESP32-S2-MINI-1是一颗通用型Wi-Fi MCU模组,采用PCB板载天线,模组配置了4MB SPI flash,采用的是 ESP32-S2FN4 芯片。
1.2.2收音机模块介绍
此项目采用的RDA5807收音机模块,RDA5807是一颗调频广播单芯片接收调谐芯片。只需要外部非常少的元器件,便可以组成一个完整的调频广播接收机。这款芯片工作电压范围2.7~3.3V。
1.4功能介绍
1.4.1网络连接
无论是FM功能实现方面,还是网络收音机方面,还是校时功能,都需要网络环境,于是项目首先解决网络连接问题。本项目采用Arduino中的WIFI库实现WiFi的连接。
代码如下:
WiFiClient client;//声明一个与客户端对象,用于与服务器连接
const char *admin = "chhzqx";//指定WIFI用户名
const char *password = "821542zc";//指定WIFI密码
String Initial_WiFi(){
int times = 0;
WiFi.mode(WIFI_STA);//将wifi模块调成STA模式,(可能默认是它)
WiFi.begin(admin, password);
while(WiFi.status() != WL_CONNECTED){ //未连接上则一直循环
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tf);//设置字体信息
u8g2.setCursor(0,32);
u8g2.print("Connecting...");
u8g2.sendBuffer();
delay(400);
times++;
if(times >=20) break;//连接超时则跳出循环开机
}
return WiFi.localIP().toString(); //连接上
1.4.2时间校准
校时部分采用了一个通用的时间同步服务,选择连接时间服务器IP :cn.pool.ntp.org,再通过代码获取时间。
代码如下:
const char *ntpServer = "pool.ntp.org";//一个公共获取时间的服务器
const long gmtOffset_sec = 3600*8; //时区
const int daylightOffset_sec = 3600; //夏令时
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);//初始化时间获取信息
1.4.3 FM收音机部分
通过查询并学习多种案例,选择在arduino中采用RDA5807收音机模块库<RDA5807.h>来完成FM收音部分,
首先也是最容易忽视的一步,通过原理图可知:FM和ESP32板输入的选择由RS2101XC6的模拟开关决定,IN输入信号为low则输出的是No引脚上的信号;IN输入信
号为High,则输出是NC引脚上的信号。因此若要使用FM模块,则需将1号对应AUDIO_SEL的电平调高。其次,也是观察电路图,和音频放大器的技术规格说明,只有当1号对应引脚SHUTDOWN为高电平时才能将输入的音频信号处理输出给扬声器输出。
在调好高低电平后就可以使用<RDA5807.h>来进行编程了。
我国可收电台频率范围为87.5MHZ-108MHZ,于是选定8750作为FM初始频率,在进行遍历,到10800不停的搜索电台信号,如果有信号且Rssi大于35,则将频率计入数组中作为可用频道,设定最多10个频道,利用自定义freq_update函数将搜到的频道再进行信号排序,信号高的往前排。
代码如下:
int freq_update(int freq, int rssi){
while(radio.isStereo()){
radio.setFrequencyUp();
delay(100);
if(rssi< radio.getRssi()){
freq= radio.getFrequency();
rssi= radio.getRssi();//如果这个台的频道信号有更高的则替换
}
}
return freq;//返回此台最好的频率值
} int freq_update(int freq, int rssi){
while(radio.isStereo()){
radio.setFrequencyUp();
delay(100);
if(rssi< radio.getRssi()){
freq= radio.getFrequency();
rssi= radio.getRssi();//如果这个台的频道信号有更高的则替换
}
}
return freq;//返回此台最好的频率值
}
int i= 1;//步长变量
int nation=0;//台数
int freq;
radio.setup();
radio.setVolume(6);
radio.setFrequency(8750);
radio.setRDS(true);//初始化收音机
for(i; i<=210; i++){
if(radio.isStereo() && radio.getRssi()>35) nations[nation++]= freq_update(radio.getFrequency(), radio.getRssi());//如果搜索到的频率有台且信号大于40,则将此台对应频率优化后记入数组
radio.setFrequencyUp();//提高频率进行搜索
delay(100);
if(nation>9) break;//如果台数超过10,则搜索结束*/
Serial.println(radio.getFrequency());
1.4.4 网络收音机部分
在已经实现了网络正常连接的情况下,我是有直接访问一些含音频的服务器,然后通过解码库解码播放的思路,但是通过案例以及自己的实际试验,确实在Arduino环境下调用I2S解码库会报错,因此也是选择直接降低ESP32任务量,建立服务器,将本地已经解码好的wav格式进行数据传输,通过DAC输出的方式输出给扬声器,实现播放音乐。整个过程设计有:
1.服务器方面,采用TCP协议利用socket服务的方式进行数据传输。
2.音频处理方面,采用QQ音乐上的音频转码功能实现MP3转wav;再通过python处理降低音频文件的采样率,同时由于开发板仅有一个声道,所以也需要截取源声音的一个声道;最重要的一点在于量化位数的改变。由于DAC只是一个8位的输出,每次采样都是使用16位的数来存取声音,所以需要将16位的声音映射到8位的声音上,这样不可避免的影响了音质。
3.使用方面,将多个音乐转换后的音频文件放入python列表,通过编程实现播放歌曲结束自动播放下一曲,和可手动播放下一曲的功能。
1.4.5 按键功能部分
FM收音机部分,设置“切换模式”,“提高音量”,“降低音量”,“换台”四个功能。
相关代码如下:
if(digitalRead(button1_pin)==LOW){
delay(80);//防抖
if(digitalRead(button1_pin)==LOW){
if(Mode==1){
client.write('q');
client.stop();//与服务器断开连接
write_num=0;//重新赋值为零
read_num=0;//为下一次在进入网络声音机模式做准备
Index=0;
isFinish=0;
music=0;
timer.detach();}//若由网络模式切回其他模式则关掉客户端和定时器
Mode = (Mode+1)%3;//利用取余实现0-1-2-0的模式切换
if(Mode==1){
isconnect = connectWebserver();//连接服务器
}
}
}//按键1为切换模式
if(Mode == 0){
if(digitalRead(button2_pin)==LOW){
delay(100);//防抖
radio.setVolumeUp();//按键2为调高音量
}
if(digitalRead(button3_pin)==LOW){
delay(100);//防抖
radio.setVolumeDown();//按键3为减小音量
}
if(digitalRead(button4_pin)==LOW){
delay(100);//防抖
num++;
if(num>9)num=0;//如果台数达最大则重新到1
radio.setFrequency(nations[num]);//设定指定的频率收听广播
}
}
网络收音机部分,设置“切换模式”,“下一曲“”两个功能。
相关代码如下:
if(Mode == 1){
if(digitalRead(button4_pin)==LOW || isEnd==1){//按第四个按钮或者播放完一首则下一曲
delay(100);//防抖
client.write('n');//向服务器发送下一曲的指令
write_num=0;//重新赋值为零
read_num=0;
Index=0;
isFinish=0;
music = (music+1)% musics;//下一首
}
1.4.6 屏幕显示部分
原本我采用的是典型的OLED驱动<Adafruit_SSD1306.h>,使用它的几个实例后都没有问题,但是一旦将其用于与收音机的信息显示就出现黑屏的问题,多次尝试无果,于是采用u8g2库来驱动OLED屏。
1开机界面 开机的时候即启动搜台函数,并显示已搜到的台数;搜寻完毕后开始连接WIFI,最后进行一个欢迎界面。
效果如图:
2FM收音机界面 屏幕依次显示时间,模式状态,频率,网络IP,音量与台数信息。
效果如图:
3网络收音机界面 屏幕依次显示时间,模式状态,当前歌曲数和服务器端口。
效果如图:
4信息界面 屏幕依次显示时间,模式状态,WiFi连接信息。
效果如图:
部分代码如下:
{u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB14_tf);//设置字体信息
u8g2.setFontDirection(0);
u8g2.setCursor(20, 32);//定位打字位置
u8g2.print("Welcome");
u8g2.setFont(u8g2_font_ncenB08_tf);//设置字体信息
u8g2.setCursor(40, 50);
u8g2.print("By Congo Red");
u8g2.sendBuffer();}
{u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tf);//设置字体信息
u8g2.setFontDirection(0);
u8g2.setCursor(0, 10);
u8g2.print(&timeinfo, "%Y/%m/%d ");//年月日
u8g2.print(&timeinfo, " %H:%M:%S");//时分秒
u8g2.setCursor(0, 25);
u8g2.print("MODE : FM");
u8g2.setCursor(0, 40);
sprintf(str,"Freq: %.1fMHZ", Message_FM/100.0);
u8g2.print(str);
u8g2.setCursor(0, 55);
sprintf(str, "Vol: %02d/%d Num: %02d/%d", Message_vol, 15, num+1, 10);
u8g2.print(str);
u8g2.sendBuffer();
delay(10);
}
{
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tf);//设置字体信息
u8g2.setFontDirection(0);
u8g2.setCursor(0, 10);
u8g2.print(&timeinfo, "%Y/%m/%d ");//年月日
u8g2.print(&timeinfo, " %H:%M:%S");//时分秒
u8g2.setCursor(0, 25);
u8g2.print("MODE : FM");
u8g2.setCursor(0, 40);
sprintf(str,"Freq: %.1fMHZ", Message_FM/100.0);
u8g2.print(str);
u8g2.setCursor(0, 55);
sprintf(str, "Vol: %02d/%d Num: %02d/%d", Message_vol, 15, num+1, 10);
u8g2.print(str);
u8g2.sendBuffer();
delay(10);
}
- 项目过程中遇到的难题及解决办法
1.作为一个有一些编程基础的单片机小白,过程中遇到的最大难题是思路不清,不知从何下手,拿到一个板子怎么阅读参考资料,电路图怎么分析。
解决办法:在b站搜寻观看利用Arduino实现的项目基础视频,学习有关开发板的基础知识;进而结合电子森林和乐鑫官网提供的案例,程序等逐渐深入学习,启发思路。
2.屏幕显示这块算是开始学习时的一个较大的绊脚石,尽管通过视频案例等搞清楚驱动原理后,一直采用<Adafruit_SSD1306.h>驱动库尝试设计项目。然而发现在与网络相关代码一起运行时,屏幕老是出现黑屏情况。
解决办法:再仔细检查代码无问题然而仍然黑屏后,我还是选择了采用u8g2来实现OLED的驱动。
3.网络收音机这部分一个大的问题就是通信相关知识的匮乏,在连接到WIFI的情况下不知道怎么从远程获取音频数据,无从下手。
解决办法:用过网络简单学习通信知识,包括服务器,客户端,网络协议等,此外也是结合案例选择本地服务器的方向,通过自建服务器,利用TCP协议下的socket实现数据传输。
4.在实现多首音乐的自动循环功能屡屡遇挫。在服务器将一首音乐的数据传输到数据缓冲区后,当客户端发出请求数据,服务器并发送1024bytes后,这里就短暂出现客户端检测不到数据,而发生“跳曲”的问题。
解决办法:通过编写代码,引入判定变量,在未检测到数据前不进行下一曲指令。代码如下:
if(num_>0 && num_ <1024){
isEnd = 1;//如果读取的是最后一段,不为1024整,则表示一首播放完毕
num_ = 1024;
}
else{
isEnd = 0;//解决播放完后再读取数据时available第一次未识别问题
}
}
- 未来的计划或建议
作为一个单片机的小白,此次寒假一起练的项目带我真正的入门了单片机,同时学习了硬件和软件的知识,尤其是通信方面的知识,未来我计划用此开发板尝试其他的几个项目,充分利用的单片机模块,继续提升自己。