平台介绍
硬件介绍:本平台使用了乐鑫公司的ESP32-S2-Mini-1模块,ESP32-S2-MINI-1是一颗通用型Wi-Fi MCU模组,功能强大,具有丰富的外设接口,可用于可穿戴电子设备、智能家居等场景。ESP32-S2-MINI-1采用PCB板载天线,模组配置了4MB SPI flash,采用的是 ESP32-S2FN4 芯片。该芯片搭载了Xtensa® 32 位LX7 单核处理器,工作频率高达 240 MHz。用户可以关闭 CPU 的电源,利用低功耗协处理器监测外设的状态变化或某些模拟量是否超出阈值。ESP32-S2-FH4 还集成了丰富的外设接口。2.4 GHz WiFi (802.11 b/g/n) 模组,可叠封2 MB PSRAM,37 个GPIO,丰富的外设,板载PCB 天线或外部天线连接器。
音频处理平台的电路板3D效果图
平台功能介绍
核心功能介绍
- 基于ESP32-S2 WiFi核心模块
- 128*64 OLED显示,SPI接口,显示信息、参数、波形
- 4个按键,用于参数控制、菜单选择
- 1路Mic音频输入 - 模拟电路,通过电位计可以调节增益0-40dB调节范围,并有带通滤波器
- 1路耳机插座音频输入 - 模拟电路,通过电位计可以调节增益 0-40dB调节范围,并有带通滤波器
- 2路音频输出,并有功率放大,可以驱动喇叭和耳机插座
- 一个FM接收模块,ESP32通过I2C接口对其进行参数设置,调节FM电台以及设置音量大小
- 一个模拟开关切换来自ESP32产生的音频还是FM输出的音频,模块开关的输出送到喇叭或耳机输出
系统框图
片上模块的功能使用
- 通过I2C接口操作FM模块
- 片上DAC模块将音频的数字信号转换为模拟信号
- 通过GPIO识别按键输入,控制多路选择器开关以及扬声器开关
- 通过SPI模块控制OLED显示
项目功能——实现网络收音机/FM收音机的功能
- 可以通过WiFi接收网络上的电台,也可以通过FM模块接收空中的电台,并可以通过按键进行切换、选台
- 在OLED显示屏上显示网络电台的IP地址、节目名字等相关信息或FM信号的频段
- 系统能够自动校时,开机后自动调节到准确的时间(年、月、日、时、分、秒)
功能结构
各个功能模块的应用实现
连接WiFi
初始化tcp/ip适配层→初始化事件调度器→初始化WiFi驱动→设置wifi模式(sta)→配置WiFi接口的参数→启动WiFi→WiFi状态机开始运转
ipaddress = wifi_conn(); //连接WiFi
Serial.println(ipaddress.c_str());
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // 从网络时间服务器上获取时间
//联网信息
const char* ssid = "PULULU";
const char* password = "zxttxzhhh";
//时间服务器
const char *ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 8 * 3600;
const int daylightOffset_sec = 0;
wificonnect部分
String wifi_conn(){
//连接WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(400);
//Serial.print("-");
}
return WiFi.localIP().toString();
}
FMRADIO模块
使用了RDA5807
//FMRADIO
#define ESP32_I2C_SDA 5
#define ESP32_I2C_SCL 4
#define MAX_DELAY_RDS 40 // 40ms - polling method
#define MAX_STATIONS 10 //最多允许的电台数量
long rds_elapsed = millis();
uint8_t fmvol = 0; //初始化音量
RDA5807 rx;
//选择输出设备 0 不输出 1 收音机 2 esp
uint8_t curr_sour = 1; //默认为无输出
void selectDevice(uint8_t dev) {
if (dev == 1) {
digitalWrite(41, LOW);
digitalWrite(42, LOW);
}
if (dev == 1) {
digitalWrite(41, HIGH);
digitalWrite(42, LOW);
}
if (dev == 2) {
digitalWrite(41, HIGH);
digitalWrite(42, HIGH);
}
}
uint8_t stations = 0; //当前选中的电台
uint16_t fmstation[MAX_STATIONS] = {0}; //最多允许MAX_STATIONS个台
//搜索收音机的台 从8700~10800 MZH
void searchFM() {
uint16_t i = 0;
uint8_t num = 0;
rx.setFrequency(8000); //从8000MHZ开始扫描
while (i < 210) {
delay(100);
if (rx.isStereo() && rx.getRssi() > 30) { //如果搜到台,且信号值大于35
fmstation[num++] = findMaxSing(rx.getFrequency(), rx.getRssi());
//rx.setFrequency(rx.getFrequency()+50);
if (num >= MAX_STATIONS) return; //只找7个台
}
rx.setFrequencyUp();
i++;
}
for (i = 0; i < MAX_STATIONS; i++) {
Serial.print(fmstation[i]);
Serial.print(" ");
}
Serial.println("Search over!");
}
//找到台后寻找信号最好的频段
uint16_t findMaxSing(uint16_t freq, uint8_t rssi) {
while (rx.isStereo()) {
rx.setFrequencyUp();
delay(100);
if (rx.isStereo() && rx.getRssi() > rssi) {
freq = rx.getFrequency();
rssi = rx.getRssi();
}
}
return freq;
}
//收音机换台
void changeStation() {
while (1) {
if (stations == MAX_STATIONS - 1) {
stations = 0;
rx.setFrequency(fmstation[stations]);
//Serial.println(fmstation[stations]);
return;
}
if (fmstation[stations + 1] != 0) {
rx.setFrequency(fmstation[stations + 1]);
stations++;
Serial.println(fmstation[stations]);
return;
}
stations++;
}
}
//计算当前一共有多少个可用的电台
uint8_t countStation() {
uint8_t i;
for (i = 0; i < MAX_STATIONS; i++) {
if (fmstation[i] == 0) return i;
}
return i;
}
WEBRADIO
学习了一个案例,大概原理是:整个webradio的实现过程可以划分为三部分
- 获取音频流。程序使用的 http 协议从一个服务器上面获取的音频数据,并将整个数据存放到一个 buffer 中。
- 对音频流进行解码。当 buffer 中有一定的数据后(可以通过宏进行调整),开启解码线程。解码线程会从这个 buffer 中取出数据,然后调用解码库,将音频流解码为可直接输出的数字信号。
- 将解码后的数据通过 DAC 输出。解码线程每解完一帧数据后,将它通过 I2S 驱动程序直接送给 DAC。
这里面存在一个同步的问题,即从服务器上面获取音频流与对音频流进行解码的同步。如果获取的音频流过快,超过了解码的速度,则它可能将 buffer 撑爆,因此会丢失部分数据;如果解码的速度过快,超过了获取音频的速度,则它可能将 buffer 消耗的干干净净,从而也会出现声音卡顿问题。这需要进行权衡。
但是在S2中无法实现在线音频流的解码,所以利用本机作为服务器用socketTCP传输搭建了了独立的网络音频库
//WEBRADIO部分
uint8_t netbuf[3][1024]; //网络数据缓冲区
uint16_t writep = 0; //写入数量
uint16_t readp = 0; //读取数量
WiFiClient client; //声明一个客户端对象,用于与服务器进行连接
bool connstat = false; //连接状态
bool iswaitecho = false; //是否等待服务器回应
Ticker flipper; //时间中断
uint16_t m_offset = 0;
#define WEBSERVERIP "172.20.10.4"
#define WEBSERVERPORT 3999
void onTimer(void) {
if(readp<=writep) dacWrite(17, netbuf[readp % 3][m_offset++]); //播放一次声音
if (m_offset >= 1024) {
m_offset = 0;
readp++; //读取完成一个缓冲区
}
}
//连接webradio服务器
bool connNetMusic() {
uint8_t i = 0;
while (i < 5) { //控制访问次数,超时则确定为数据传输失败
if (client.connect(WEBSERVERIP, WEBSERVERPORT)) {
connstat = true;
Serial.println("success");
return true;
} else {
Serial.println("failure");
client.stop(); //关闭客户端
}
i++;
delay(100);
}
return false;
}
遇到的问题
第一次输入的服务器IP是本地局域网,结果显示的webradio没有连接上,经过更改后开发板、接入的设备必须连入同一个网络,方可显示webradio连接成功,但是在pycharm的运行信息中在有限的尝试次数后由于由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。
FM连接效果不好的问题,在FM模块下如果初次扫描频段没有得到质量较好的频道,FM收音机将会播放音质很差的广播节目,且不会显示频率、频道数等信息。也无法通过调频,来重新扫描频段来高质量接受空中广播。因此可能需要多次烧录程序至开发板来获取较高质量的频道,和后续的调频工作。
关于IDF, idf感觉相对于arduino对于ESP32S2的开发更为专业,也有更多封装度更高的库来实现特定的功能,但是IDF环境的搭建着实困难,对于新手很不友好。
收获
在本次暑假在家一起练活动中,作为小白,我接触到了从前在书本上没有接触过的实际开发的过程。虽然到最后没有实现全部的功能,但是在学习相关模块功能的代码和思路中我第一次接触到了嵌入式编程,以及将之前学过的通信网,数字信号处理的知识又有了新的理解。