2021暑假一起练-用ESP32S2做了网络收音机/FM收音机
电子森林暑假一起练项目。ESP32S2实现网络收音机/FM收音机的功能,WIFI接收网络电台或者RDA5807M接收空中电台。以及在这两种模式间切换。基于ESP-IDF,ESP-ADF,ESP-IOT-SOLUTION,LVGL等库
标签
嵌入式系统
ESP32
开源
esp-idf
学习
DamingBear
更新2021-09-08
3535

【电子森林项目】网络收音机/FM收音机

这个项目是报名《硬禾“暑期一起练”第3个平台 - 基于ESP32-S2模块的网络收音机和音频信号处理》所做的。

项目Github地址:https://github.com/DaMiBear/ESP32S2_Network-Radio_FM-Radio

FozWSaRUge7303m-0siHNifuhZ2f

基本功能

  • 可以连接WiFi收听HLS协议的网络电台节目

  • 收听空中的FM电台88MHz~108MHz

  • OLED0.96寸显示

  • 四个独立按键控制两种模式,切换节目,静音。

项目环境

  • ESP-IDF v4.2:乐鑫ESP系列的基本开发环境,借鉴了sntp例程。

  • ESP-ADF v2.3:乐鑫的音频开发框架,主要借鉴了pipeline_living_streampipeline_play_mp3_with_dac_or_pwm这两个例程。

  • ESP-IOT-SOLUTION:Iot设备驱动和解决方案。使用SSD1306库,里面的7.5版本的LVGL已经被我用8.1的替换,并且修改一处SSD1306驱动程序的错误(LVGL仓库中的:ssd1306水平终止范围错误 #103)以及使用iot_button库,因为这个驱动可以识别按钮单击、双击、长按等功能。

  • LVGL v8.1:嵌入式GUI库,替换ESP-IOT-SOLUTION中的v7.5的LVGL。

  • ESP-IDF-LIB v0.8.1:基于IDF开发的一些常用芯片的库(项目中使用RDA5807M的库)。

  • MCU_Font_Release:用于生成LVGL使用的自定义字体来显示中文。

硬件

ESP32-S2-MINI-1:ESP32-S2-FH4的芯片,320K的DRAM,无PSRAM。

FM模块:RDA5807M

这个项目仅仅是学习目的,IDF也只是第二次接触,其他库第一次接触,本人编程功底也很烂,项目中肯定有不合适的地方,请大家多多指教。

1 如何使用

关于环境配置,需要自己按照官方教程配置IDF和ADF环境,其他的iot-solution以及LVGL以及在项目里。下面是用的VS Code的esp-idf插件作为开发环境。

1.1 克隆项目

git clone https://github.com/DaMiBear/ESP32S2_Network-Radio_FM-Radio.git

1.2 选择设备

VS Code打开项目文件夹后,F1输入如下命令,选择根据你的设备选择,项目中使用的是ESP32S2。

FgDOYhh9Xg1L1m3wNRFtMlDRDYyn

1.3 配置

点击如下图标,进行设置

FlgidtKT5BFAITYldRsPZFabS_Y_

Serial flasher config

Flash size改为4MB

Partition Table

更改为如下设置,默认的factory大小只有1M是不够的,要改为3M

FpLBOj0sR8hDNh94vvJ0BhvYObZC

Audio HAL

选择ESP32-S2-Kaluga-1,这个选项是属于ADF里的,ADF里大多数程序都是按照官方提供的那些开发板来的,官方开发板中的S2是有2MB的PSRAM的,这里主要是为了产生一些宏定义防止编译出错,具体初始化的程序是我们自己写的。

FotrFAqBLWSsGc2cve4V0nTO0n8M

play_living_stream Configuration

输入自己WiFi的名称和密码。如果有中文,就在sdkconfig这个文件中修改,注意在sdkconfig文件中修改只限于WiFi名称和密码,其他的值大部分会在编译的时候重新覆盖。

Compiler options

为了节省内存,这里选择.

因为ADF的play_living_stream程序非常占内存,再加上FreeRTOS和LVGL,如果是默认设置,320K的内存是肯定不够的,所以要更改一些配置来节省内存。

FoHiP7Q3CITwbHnoOAZ7YGNHyHkR

ESP32S2-specific

选择240MHz

FrT3JlWpq43QPcWqOjN3s38bhO7l

Wi-Fi

与IRAM有关的都去掉,要不然代码会直接放入内存中,虽然可以加快速度,但是内存是真的不够用。

FiR6u8MgviPk3cKYIrTP7JWmR7AF

LCD Drivers

勾选SSD1306

LVGL configuration

勾选LVGL minimal configuration

Color settings

选择1:1

Memory settings

设置为6,默认的大小为32,就是32K,这样就会产生一个大小为32K的全局变量,这个才是内存占用的罪魁祸首。一开始没发现这一点,导致加入LVGL程序后,内存根本不够用。这里设置为6可以不报错,试过5或者更小,但在运行过程中会出错。

Fp0a8gImIyyrVHvE4udRtf_EMrwk

Feature configuration - Others

勾选Enable float in built-in (v)snprintf functions来支持浮点数显示

Font usage - Enable built-in fonts

选用Enable Montserrat 20Enable UNSCII 8 (Perfect monospace font),因为项目中使用的LVGL内置字体就这两个,还有一个为了支持中文显示的自定义字体。

下面的Select theme default title font默认格式选择UNSCII 8。这里LVGL里面的Kconfig文件中,比如我们选择的是UNSCII 8那就会产生一个CONFIG_LV_FONT_DEFAULT_UNSCII_8的宏定义,之后根据lv_conf_kconfig.h文件的#elif defined会定义

/* file:lv_conf_kconfig.h */
#elif defined CONFIG_LV_FONT_DEFAULT_UNSCII_8
#define CONFIG_LV_THEME_DEFAULT_FONT         &lv_font_unscii_8

随后会根据lv_conf_internal.h文件中下面部分代码,但是代码中使用的是CONFIG_LV_FONT_DEFAULT,而不是CONFIG_LV_THEME_DEFAULT_FONT,因为CONFIG_LV_FONT_DEFAULT没有定义,那这样设置默认字体是没效果的。所以我把CONFIG_LV_FONT_DEFAULT改为了CONFIG_LV_THEME_DEFAULT_FONT,这样就可以改变默认字体了,当然这肯定不是最好的办法。

/* file:lv_conf_internal.h */
/*Always set a default font*/
#ifndef LV_FONT_DEFAULT
# ifdef CONFIG_LV_FONT_DEFAULT // 更改为了CONFIG_LV_THEME_DEFAULT_FONT
#   define LV_FONT_DEFAULT CONFIG_LV_FONT_DEFAULT // 更改为了CONFIG_LV_THEME_DEFAULT_FONT
# else
#   define LV_FONT_DEFAULT &lv_font_montserrat_8
# endif
#endif

后面的LVGL选项自己根据情况勾选就行。

1.4 编译下载

点击左下角图小,分辨是编译、下载、监视,第四个是三个功能合为一个按键。

Fu_tFILy2CE1VZQ0p91JuxI9UCM9

2 运行现象

从左到右四个按键功能依次是:网络电台/FM电台模式切换、切换至下一个节目/频段、切换至上一个节目/频段、禁用功放(静音)。

默认开机运行网络电台模式,该模式下,显示屏显示一些电台名称、时间等信息。FM电台模式下显示频段、RSSI、时间信息。

日志输出

WiFi的输出省略

I (439) cpu_start: Starting scheduler on PRO CPU.
I (441) gpio: GPIO[41]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
I (441) gpio: GPIO[42]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
I (461) spi_bus: SPI2 bus created
I (461) spi_bus: SPI2 bus device added, CS=38 Mode=0 Speed=20000000
I (661) lvgl_gui: GUI Run at esp32s2 Pinned to Core0
I (661) lvgl adapter: Alloc memory total size: 1024 Byte
I (661) lvgl_gui: Start to run LVGL
I (761) PLAY_LIVING_STREAM: [ * ] Start and wait for Wi-Fi network
I (771) wifi:wifi driver task: 3ffd68bc, prio:23, stack:6656, core=0
I (771) system_api: Base MAC address is not set
I (771) system_api: read default base MAC address from EFUSE
I (781) wifi:wifi firmware version: c7d0450
I (781) wifi:wifi certification version: v7.0
I (781) wifi:config NVS flash: enabled
I (781) wifi:config nano formating: disabled
··················································
W (1591) PERIPH_WIFI: WiFi Event cb, Unhandle event_base:WIFI_EVENT, event_id:4
I (1591) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (2461) esp_netif_handlers: sta ip: 192.168.123.100, mask: 255.255.255.0, gw: 192.168.123.1
I (2461) PERIPH_WIFI: Got ip:192.168.123.100
I (2461) GET_TIME: Initializing SNTP
I (2471) GET_TIME: Waiting for system time to be set... (1/10)
I (2471) PLAY_LIVING_STREAM: [1.0] Create audio pipeline for playback
I (2481) PLAY_LIVING_STREAM: [1.1] Create http stream to read data
I (2491) PLAY_LIVING_STREAM: [2.2] Create PWM stream to write data to codec chip
I (2501) PLAY_LIVING_STREAM: [2.3] Create aac decoder to decode aac file
I (2501) PLAY_LIVING_STREAM: [2.4] Register all elements to audio pipeline
I (2511) PLAY_LIVING_STREAM: [2.5] Link it together http_stream-->aac_decoder-->pwm_stream-->[codec_chip]
I (2521) AUDIO_PIPELINE: link el->rb, el:0x3ffdceac, tag:http, rb:0x3ffdfa98
I (2531) AUDIO_PIPELINE: link el->rb, el:0x3ffdf718, tag:aac, rb:0x3ffdfad4
I (2541) PLAY_LIVING_STREAM: [2.6] Set up  uri (http as http_stream, aac as aac decoder, and default output is i2s(changed to PWM))
I (2551) PLAY_LIVING_STREAM: [ 3 ] Set up  event listener
I (2551) PLAY_LIVING_STREAM: [3.1] Listening event from all elements of pipeline
I (2561) PLAY_LIVING_STREAM: [3.2] Listening event from peripherals
I (2571) PLAY_LIVING_STREAM: [ 4 ] Start audio_pipeline
I (2581) AUDIO_ELEMENT: [http-0x3ffdceac] Element task created
I (2581) AUDIO_ELEMENT: [aac-0x3ffdf718] Element task created
I (2591) AUDIO_ELEMENT: [output-0x3ffdf338] Element task created
I (2601) AUDIO_PIPELINE: Func:audio_pipeline_run, Line:359, MEM Total:87268 Bytes
​
I (2601) AUDIO_ELEMENT: [http] AEL_MSG_CMD_RESUME,state:1
I (2611) AUDIO_ELEMENT: [aac] AEL_MSG_CMD_RESUME,state:1
I (2621) AUDIO_ELEMENT: [output] AEL_MSG_CMD_RESUME,state:1
I (2621) AUDIO_PIPELINE: Pipeline started
I (2631) gpio: GPIO[1]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (2641) board_button: Button[0] created
I (2641) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (2651) board_button: Button[1] created
I (2651) gpio: GPIO[3]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (2661) board_button: Button[2] created
I (2671) gpio: GPIO[6]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (2681) board_button: Button[3] created
I (2691) rda5807m: Device initialized
I (2691) rda5807m: Frequency: 102200 kHz
I (2701) HTTP_STREAM: total_bytes=789
I (2711) HTTP_STREAM: Live stream URI. Need to be fetched again!
I (2741) rda5807m: rda5807m_wait_and_finish_tune
I (2781) HTTP_STREAM: total_bytes=57780
I (2781) CODEC_ELEMENT_HELPER: The element is 0x3ffdf718. The reserve data 2 is 0x0.
I (2781) AAC_DECODER: a new song playing
I (2791) AAC_DECODER: this audio is RAW AAC
I (2821) PLAY_LIVING_STREAM: [ * ] Receive music info from aac decoder, sample_rates=24000, bits=16, ch=2
I (4251) GET_TIME: Notification of a time synchronization event
I (5471) main: The current date/time in Shanghai is: 2021-09-04      19:27:38
I (6471) main: The current date/time in Shanghai is: 2021-09-04      19:27:39
W (6801) HTTP_STREAM: No more data,errno:0, total_bytes:57780, rlen = 0
I (6801) AUDIO_ELEMENT: IN-[http] AEL_IO_DONE,0
I (6831) HTTP_STREAM: total_bytes=57848

板子运行现象

FuH-BDjPtwTe95vcPZdBk3oOLofOFrAdRSjw_3O8VyHFYJZl3UJfkF1a

3 已知BUG

  • SNTP校时任务可能会出现较长时间无法校时的情况,但还是会校时,只是时间较长。

  • WiFi输出日志中会出现W (1591) PERIPH_WIFI: WiFi Event cb, Unhandle event_base:WIFI_EVENT, event_id:4警告

  • 按键无响应,只遇到过一次

  • 网络电台切台过快程序会报错,目前只加延时简单处理

因为中间RDA5807M芯片有故障花了很多时间,这些BUG还没有来得及处理。

4 笔记

4.1 项目前瞻

刚刚看到这个项目的时候,下面详情中提供的参考资料全部都是使用Arduino实现的,所以我一开始觉得Arduino应该有很多轮子可以用,所以去乐鑫的Arduino的环境等等,确实,SSD1306、RDA5807M是有很好用的库,但是有一点就是关于音频的播放,都是使用额外的解码IC来完成的,但是项目提供的开发板并没有解码IC,若是自己编写mp3格式解码对我来说确实要花很多很多时间。所以又乐鑫的仓库寻找,发现了ESP-ADF这个好东西,里面有我需要的例程,所以才决定使用ESP-IDF来实现。(后面也发现Arduino可以作为IDF的组件来使用,但当时Arduino没有非常需要的库,所以只是试了一下demo就没用了)。

4.2 环境搭建

环境搭建在使用ESP-IDF中还是很重要的,当然没有环境肯定不能开发这是废话。重要的是在环境搭建过程中关于ESP-IDF整个框架的认识。在之前的使用ESP32制作一个接入HomeKit的空调控制器的项目中,因为没有用到很多第三方的库,所以只认识到了IDF框架的一点点。但是在这个项目中,因为要添加很多其他的库,需要对组件CMakeListsKconfigKconfig.projbuild这些概念或文件有一定的了解。

如果需要添加其他组件到项目中,一方面可以在main下的Cmakelists文件中添加,或者直接拷贝库到components下,因为idf会自动搜索components文件夹下的组件。

而且每个组件必须包含Cmakelists文件来注册成为组件。一个库中包含很多组件,库的更目录中就会有一个component.cmake文件比如esp-iot-solution库中的components\esp-iot-solution\component.cmake,它会把库中的组件都包含进去。而且在每个组件中可以设置这个组件依赖于其他组件,所以很多时候如果set target失败了,八成是组件缺少了。

每个组件可以包含Kconfig文件来进行一定的设置,就像在1.3 配置中选项的那样,这些GUI的配置界面都是根据Kconfig文件里的内容来显示的,就像下面这样,选中某个选项就会生成一个以CONFIG_开头的宏定义,以此来控制条件编译,Kconfig.projbuild也是类似,一些例程进场会使用Kconfig.projbuild文件来配置WiFi名称密码这样的信息。这些事情都是因为遇到了上面LVGL默认字体不能改变的问题才学习到了这些东西。

Fj5XmYy_QjLNj5CWwTHn2g5oPx5U

4.3 程序思路

上电进行WiFi和显示的初始化,在连接WiFi的时候加载一个Loading界面来等待,启动时间校准任务,启动网络电台任务,初始化按键,RDA5807M,进行主要界面GUI绘制。

时间校准是直接套用了IDF中SNTP的例程,也可以注册校准成功后的回调函数,校准成功后应该会自动更新RTOS中的时间,RTOS中会自动以校准后的时间为基准进行累加,所以后面显示的时候,直接读取RTOS的时间。如果超出了预期校准时间,那么RTOS时间好像是1970年开始,其中struct tm结构体类型中的tm_year这个成员,不是直接表示的年份,而是与1900年的差值。

LVGL创建了2个屏幕,一个负责网络电台的信息显示、另一个负责FM电台的信息显示。网络电台显示的有:IP地址、节目名称、音频信息、当前时间。FM电台显示的有:频段、RSSI、当前时间。两个屏幕同时存在,通过按键控制进行切换,切换时有个简单的滑动动画,程序可以设置动画时间,切换到另一个屏幕的时候,并没有删除另一个屏幕,仍然占据内存(也就几K),相当于作为后台运行。还给LVGL注册了输入设备轮询读取按键事件。

在运行过程中,首先会自动播放第一个网络电台,先获取m3u8文件后,对文件中的aac音频地址进行下载、解码、播放,获取解码播放这些功能在ADF中被称作元素,这些元素都被登记到一个管道中。

按键使用了iot_button组件,可以检测单击,双击,多击次数,长按这些状态,提供两种方式进行检测,轮询和回调,一开始使用轮询发现要么漏检,要么重复检测,所以后面改用了回调的方式,可以给每个按键设置对应状态的回调函数,项目中目前只用了单机和长按两种。因为LVGL读取按键是轮询的,所以在回调函数中记录当前按键的状态,LVGL读取状态,执行对应的操作后清除按键状态。模式切换和静音是直接在LVGL的读取按键的回调函数中进行的,单击模式切换的按键后,如果当前是网络电台屏幕,则停止网络电台管道切换至FM屏幕,同时创建读取FM信息任务。如果当前是FM屏幕,则删除读取FM信息任务,开始网络电台管道。节目切换或者频段切换是在标签对象的回调函数中进行的,初始化的时候设置了标签按键事件的回调函数,但是任何键值都会调用回调函数,所以需要在函数中判断键值,再根据当前屏幕进行相应的操作:切换网络电台或者频段。

RDA5807M库是在IDF库的基础上,又把IIC的库针对一些RDA5807M寄存器进行了封装,比如连续读模式和寄存器读模式。在RDA5807库依赖的i2cdev库,在每次进行IIC读写的时候都会设置IIC的地址和检测IIC是否进行初始化,设置IIC的地址可以理解,因为有两种模式的读写地址,但是不知道为什么都要检测IIC初始化,其中在读写的时候也用了互斥锁,应该是的吧,其中API中有带锁的读写和不带的。切到FM模式时,默认频率为102.2MHz,因为我这边102.2是有一个电台的,记得接上天线,一根杜邦线足以。按键每次改变0.1MHz。库中也添加了显示所有寄存器的值的函数和连续写模式的函数,这俩函数是为了解决下面4.4问题中使用的。

4.4 遇到的问题

在看IDF中的代码的时候,真的能察觉到世界的参差,那些代码写的是那么标准规范,自己写的代码真的烂了又烂。IDF都是基于FreeRTOS的,对FreeRTOS的接触也是从这里开始的,多线程的使用认识的很少,队列,锁也没有去使用。至少会了一点xTaskCreatevTaskDelete.

LVGL

一:因为LVGL的ESP32库是针对IIC的SSD1306,却没有SPI的,esp-iot-solution中的LVGL有SPI的驱动却没有针对SSD1306的,所以我就把这两个例程缝合了起来,具体的细节都在LVGL_PORT_ESP32_SSD1306-SPI这个的README中说了。而且这两个的例程的Kconfig文件还是有差距的,所以也有上面默认字体设置了却没作用这样的问题;二:还有就是LVGL的默认32K的全局变量的内存占用了,上面已经说明;三:还有第三点中文字体的问题,LVGL的内置字体lv_font_simsun_16_cjk缺了很多中文的字,所以参考了MCU_Font_Release来自定义字体;四:LVGL组对象中的聚焦问题,目前我这种程度的理解,LVGL中如果要使用输入设备,就必须把控制对象放到组中,再把组分配给输入设备。之后给LVGL提供一个轮询检测键盘的接口,进行相关按键操作。本人的思路是:获得某个键值后,启动组中某个对象的回调函数对该对象进行进一步的操作,但是问题是:组中的对象只有被聚焦的情况下,才有可能调用回调函数,不被聚焦是不可能调用回调函数的。在两个屏幕之间进行切换后,新的活动屏幕并不会主动去聚焦组中的对象,所以需要使用lv_group_focus_obj(fre_label);手动设置聚焦对象,才能获取到相关事件调用回调函数。

ADF

网络电台基本例程是pipeline_living_stream音频的控制参考pipeline_sdcard_mp3_control,项目中使用切换,或者是停止,都是只停止了一部分,整个ADF占用的资源并没有完全释放,因为尝试过删除设备等等,结果并没有成功。使用只使用了最简单的方法。还有蜻蜓FM的接入,看了文档不太理解,所以就仅仅对着URL中那段数字改,在蜻蜓FM中查早对应的电台名称。

RDA5807M

这个问题最要命,我一开始在esp-idf-lib中RDA5807M的例程基础上瞎改,比如把IIC的两个引脚调换、连续读写方式的地址和寄存器读写方式的地址调换。然后再改回最初的程序后,就没有声音输出了,在搜台模式下,频段也是一直卡在118.9MHz,一开始以为是设置的问题,结果把数据手册看了好几遍,也设置了好几遍,还是没有作用,同样的程序在别人的板子上就正常工作,后来没有办法了只能买了新的RDA5807M模块换上,结果一换就好了。而且后面还有一个坑,在更换了硬件之后,使用Arduino库来测试是正常工作的,但在使用esp-idf-lib中提供的例程,不论什么频段RSSI都是1,后来发现有一行程序把寄存器0x05写入了0x880f,在数据手册上看着是没问题,都是默认值,其中7:6位数据手册上没有写,但如果查看RDA5807MP的数据手册,可以发现7:6位是有定义的,RDA5807M和RDA5807MP不是一个芯片,但在RDA5807M的程序中按照RDA5807MP的手册设置了7:6位后,确实正常工作了。我也下了其他地方的RDA5807M的数据手册,都没有7:6位的定义。也许是我运气不好下的一直是劣质的数据手册?

Fq3-fB7mRVPrfJPeCqazMKeUpbx4FkcP_WkGmMiy2DRA7qaRVfZTxCpD

附件下载
partition-table.bin
下载至0x8000(wifi名称:wifitest 密码:123456789)
bootloader.bin
下载至0x1000(wifi名称:wifitest 密码:123456789)源文件请至项目文档中的github下载
ESP32S2_Audio.bin
下载至0x10000(wifi名称:wifitest 密码:123456789)
团队介绍
普普通通江南大学物联网学院电子信息工程在读生。对嵌入式物联网感兴趣。
团队成员
DamingBear
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号