基于ESP32S2音频扩展版的FM电台/网络电台
基于ESP32S2音频扩展版,实现了连接WiFi,矫正时间,收听FM电台,连接网络电台和选台的功能~
标签
嵌入式系统
网络与通信
2022寒假在家练
SYQ
更新2022-03-01
哈尔滨工业大学
2091

1 项目介绍

  • 系统能够自动连接WiFi,获取准确的当前时间(年、月、日、时、分、秒)
  • 可以通过WiFi接收和播放网络上的电台,并通过按键进行选台
  • 也可以通过FM模块接收和播放空中的电台,并通过按键进行调频
  • 在OLED显示屏上显示网络电台的节目名字和FM信号的频率、音频质量等信息

2 硬件介绍

  • 核心控制器采用乐鑫公司的ESP32-S2-Mini-1模块:通用型Wi-Fi MCU模组,4MB SPI flash,ESP32-S2-FH4芯片,工作频率高达 240 MHz
  • FM模块:RDA5807M

3 软件环境

  • Arduino
  • arduino-esp32 v2.0.0: Arduino平台的ESP32开发库
  • u8g2: Arduino使用I2C或SPI控制屏幕的库
  • RDA5807: Arduino使用I2C控制收音机模块的库
  • VS code
  • esp-idf v4.3:乐鑫ESP系列的基本开发环境
  • esp-adf v2.3:乐鑫的音频开发框架

4 实现思路

      上电后进行WiFi连接和显示的初始化,在连接WiFi时加载一个Audio Loading…界面,然后,进行时间校准,连接网络电台、FM电台的任务。首先会自动播放第一个网络电台,先获取m3u8文件后,对文件中的aac音频地址进行下载、解码、播放;使用RDA5807库函数来接收FM电台、改变频率、获取音频质量。通过按键1可以在网络电台和FM电台之间切换, 通过按键2、3实现FM电台调频和网络电台的换台。

      用LVGL创建2个屏幕,一个用来显示网络电台的信息、另一个显示FM电台的信息。网络电台显示的有:IP地址、节目名称、当前时间。FM电台显示的有:频率、RSSI、当前时间,通过按键进行切换。

Fin3-x4wZ1FXIpKj2v0imXbAV82U

5 主要代码

  • 上电: 进行屏幕, FM, WIFI等功能的初始化
/* ssd1306 LVGL初始化 */
    SSD1306_init();

    /* 加载动画显示 */
    my_lvgl_load_anim();

    wifi_connect();

    // xTaskCreate(state_task, "state_task", 2048, NULL, 15, NULL);
    xTaskCreate(time_task, "time_task", 1024*2, NULL, 10, NULL);
    /* HLS初始化 */
    play_living_stream_start();
    
    /* 独立按键初始化 */
    board_button_init();
    
    /* FM模块rda5807初始化 */
    rda5807m_app_init();

    /* APP_GUI绘制 */
    my_lvgl_app();
  • 时间校对并显示,时间信息储存在timeinfo中
 time(&now);     // 更新当前时间
        localtime_r(&now, &timeinfo);

        setenv("TZ", "CST-8", 1);
        tzset();
        localtime_r(&now, &timeinfo);
        strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d      %H:%M:%S", &timeinfo);
        if (data_time_label1 != NULL && data_time_label2 != NULL && timeinfo.tm_year > (2022-1900)) // 大于2022-1900是因为有时ntp更新时间太慢,tm_year是与1900时间差
        {
            lv_label_set_text(data_time_label1, strftime_buf);//显示年月日
            lv_label_set_text(data_time_label2, strftime_buf);//显示时分秒
        }
        ESP_LOGI(TAG, "The current date/time in Harbin is: %s", strftime_buf);
  • WiFi连接
void wifi_connect()
{
    ESP_LOGI(TAG, "[ * ] Start and wait for Wi-Fi network");
    esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
    set = esp_periph_set_init(&periph_cfg);
    periph_wifi_cfg_t wifi_cfg = {
        .ssid = CONFIG_WIFI_SSID,
        .password = CONFIG_WIFI_PASSWORD,
    };
    esp_periph_handle_t wifi_handle = periph_wifi_init(&wifi_cfg);
    esp_periph_start(set, wifi_handle);
    periph_wifi_wait_for_connected(wifi_handle, portMAX_DELAY);
}
  • 设置FM电台的频率,频率值储存在rda5807m_current_fre中
rda5807m_set_frequency_khz(&rda5807m_dev, rda5807m_current_fre);
  • 连接网络电台,通过url地址可以获取m3u8文件,对文件中的aac音频地址进行下载、解码、播放
static int _http_stream_event_handle(http_stream_event_msg_t *msg)
{
    if (msg->event_id == HTTP_STREAM_RESOLVE_ALL_TRACKS) {
        return ESP_OK;
    }

    if (msg->event_id == HTTP_STREAM_FINISH_TRACK) {
        return http_stream_next_track(msg->el);
    }
    if (msg->event_id == HTTP_STREAM_FINISH_PLAYLIST) {
        return http_stream_fetch_again(msg->el);
    }
    return ESP_OK;
}

static void living_stream_task()
{
    while (1) {
        audio_event_iface_msg_t msg;
        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
            continue;
        }

        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
            && msg.source == (void *) aac_decoder
            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
            audio_element_info_t music_info = {0};
            audio_element_getinfo(aac_decoder, &music_info);

            ESP_LOGI(TAG, "[ * ] Receive music info from aac decoder, sample_rates=%d, bits=%d, ch=%d",
                     music_info.sample_rates, music_info.bits, music_info.channels);
            updata_radio_info_label(music_info);
            audio_element_setinfo(output_stream_writer, &music_info);
            // i2s_stream_set_clk(output_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            pwm_stream_set_clk(output_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            continue;
        }

        /* restart stream when the first pipeline element (http_stream_reader in this case) receives stop event (caused by reading errors) */
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
            && msg.source == (void *) http_stream_reader
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_ERROR_OPEN) {
            ESP_LOGW(TAG, "[ * ] Restart stream");
            audio_pipeline_stop(pipeline);
            audio_pipeline_wait_for_stop(pipeline);
            audio_element_reset_state(aac_decoder);
            audio_element_reset_state(output_stream_writer);
            audio_pipeline_reset_ringbuffer(pipeline);
            audio_pipeline_reset_items_state(pipeline);
            audio_pipeline_run(pipeline);
            continue;
        }
    }
  • 电台信息储存在HLS_list数组中
HLS_INFO_t HLS_list[MAX_HLS_URL_NUM] = {
    {.hls_url = "http://open.ls.qingting.fm/live/20210755/64k.m3u8?format=aac", .program_name = "星河音乐"}, // 星河音乐
    {.hls_url = "http://open.ls.qingting.fm/live/20210756/64k.m3u8?format=aac", .program_name = "天籁古典"}, // 天籁古典
    {.hls_url = "http://open.ls.qingting.fm/live/1007/64k.m3u8?format=aac", .program_name = "CRI HIT FM"}, // CRI HIT FM
    {.hls_url = "http://open.ls.qingting.fm/live/386/64k.m3u8?format=aac", .program_name = "中国之声"}, // 中国之声
    {.hls_url = "http://open.ls.qingting.fm/live/4985/64k.m3u8?format=aac", .program_name = "CNR中国交通广播"},  // CNR中国交通广播
    {.hls_url = "http://open.ls.qingting.fm/live/1005/64k.m3u8?forma t=aac", .program_name = "CRI环球资讯"}, // CRI环球资讯
    {.hls_url = "http://open.ls.qingting.fm/live/387/64k.m3u8?format=aac", .program_name = "CNR经济之声"},  // CNR经济之声
};
  • 最后,设计FM电台、网络电台界面,将信息显示出来。要显示一段文字,首先要通过lv_label_create()函数创建对象,通过lv_obj_align()函数排列文本,然后通过lv_label_set_text()函数打印文本,lv_obj_set_pos()可以用来设置文本位置,lv_obj_set_style_text_font()可以设置字体。
static void internet_radio_scr_create()
{
    my_scr1 = lv_obj_create(NULL);    //创建屏幕

    /* 设置布局 */
    //lv_obj_set_layout(my_scr1, LV_LAYOUT_FLEX);
    lv_obj_set_flex_flow(my_scr1, LV_FLEX_FLOW_COLUMN);

    static lv_style_t row_container_style;
    lv_style_init(&row_container_style);
    lv_style_set_pad_row(&row_container_style, 0);  // 改变元素间距
    lv_obj_add_style(my_scr1, &row_container_style, 0);

    /*
     * 创建label
     */
    
    /* 节目名称 */
    radio_label = lv_label_create(my_scr1);
    //lv_obj_set_pos(radio_label, 0, 9);
    lv_obj_set_width(radio_label, 128);
    lv_label_set_long_mode(radio_label, LV_LABEL_LONG_SCROLL);       // 来回滑动显示
    lv_obj_set_style_text_font(radio_label, &lv_font_myfont_8, 0);      // 设置支持中文字体
    lv_label_set_text_fmt(radio_label, "%s", HLS_list[0].program_name);
    /* IP地址 */
    lv_obj_t* ip_address_label = lv_label_create(my_scr1); // 从当前活动屏幕创建
    lv_obj_set_pos(ip_address_label, 0, 0);
    lv_obj_set_size(ip_address_label, 128, 8);
    lv_label_set_text(ip_address_label, "IP:172.20.10.2");
    /* 地址 */
    radio_info_label = lv_label_create(my_scr1);
    lv_obj_set_width(radio_info_label, 128);
    
    // lv_label_set_long_mode(radio_info_label, LV_LABEL_LONG_SCROLL);     // 来回滑动显示
    /* 当前日期时间 */
    data_time_label1 = lv_label_create(my_scr1);
    lv_obj_set_width(data_time_label1, 128);
    // lv_label_set_long_mode(data_time_label1, LV_LABEL_LONG_SCROLL);
    // get_current_time(current_time, sizeof(current_time));
    lv_label_set_text(data_time_label1, "updating time...");

    lv_group_add_obj(g, radio_label);   // 控制的对象添加到组中
    lv_obj_add_event_cb(radio_label, radio_label_event_cb, LV_EVENT_KEY, NULL);     // 添加事件

}

static void FM_radio_scr_create()
{
    my_scr2 = lv_obj_create(NULL);    //创建屏幕

    /* 设置布局 */
    //lv_obj_set_layout(lv_scr_act(), LV_LAYOUT_FLEX);
    lv_obj_set_flex_flow(my_scr2, LV_FLEX_FLOW_COLUMN);

    static lv_style_t row_container_style;
    lv_style_init(&row_container_style);
    lv_style_set_pad_row(&row_container_style, 0);  // 改变元素间距
    lv_obj_add_style(my_scr2, &row_container_style, 0);

    /* 当前频段 */
    fre_label = lv_label_create(my_scr2);
    lv_obj_set_style_text_font(fre_label, &lv_font_montserrat_20, 0);
    lv_label_set_text_fmt(fre_label, "%.1fMHz", ((float)rda5807m_current_fre/1000));
    lv_group_add_obj(g, fre_label);     // 添加到组中
    lv_obj_add_event_cb(fre_label, fre_label_event_cb, LV_EVENT_KEY, NULL);     // 配置事件

    /* 当前信号RSSI */
    rssi_label = lv_label_create(my_scr2);
    lv_obj_set_style_text_font(rssi_label, &lv_font_montserrat_20, 0);
    lv_label_set_text(rssi_label, "RSSI:0");
    /* 当前日期时间 */
    data_time_label2 = lv_label_create(my_scr2);
    lv_obj_set_width(data_time_label2, 128);
    //lv_obj_set_pos(data_time_label2, 0, 50);
    lv_label_set_text(data_time_label2, "updating time...");

}

5 功能展示

  • 加载界面:

Fq3901NDKZhNwv4i3NXVCDGeLkzo

  • FM电台界面

FohZ8hx4T-FG4FpuIDZRmKOGcMi0

  • 网络电台界面

FjIWR1SybGS0Fk1eYWXoE7o1S_fI

6 遇到的问题及解决方法

  • 刚开始选择了Arduino开发环境,配置了ESP32S2器件,添加了u8g2、RDA5807等库,实现了连接WiFi、校准时间、收听FM电台的功能,但是实现网络电台时遇到了瓶颈,于是转战VsCode。
  • 用了很多时间配置VsCode环境,需要安装esp-idf、idf tools、esp-adf等,借鉴了例程,最后成功编译,烧录。
软硬件
元器件
ESP32-S2-MINI-1
2.4GHz Wi­Fi (802.11 b/g/n) 模组, 内置ESP32­S2系列芯片,Xtensa® 单核32位LX7微处理器, 内置芯片叠封4MB flash,可叠封2MB PSRAM, 37个GPIO,丰富的外设, 板载PCB天线或外部天线连接器
RDA5807M
FM收音机模块
附件下载
附件-百度网盘地址.txt
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号