用M5StickC Plus制作网络收音机
基于M5StickC Plus平台制作的网络电台收音机,通过网络获取HLS数据,使用内置DAC输出音频信号。
标签
Arduino
网络与通信
ESP32
2022暑假在家练
M5StickC Plus
网络收音机
2x3j
更新2022-09-07
中国海洋大学
931

项目介绍

设计一款能够播放网络电台的收音机,具体要求如下:

  • 使用M5StickC Plus通过WiFi模块连接网络
  • 在M5StickC Plus上进行解码,并通过提供的扬声器模块播放音乐
  • 在M5StickC Plus上能够切换电台,并将电台的信息显示在LCD屏幕上

所使用的器件如下:

  • M5StickC Plus一套

  • 扬声器板一块

开发平台介绍

M5StickC PLUS 主控采用ESP32-PICO-D4模组,具备蓝牙4.2与WIFI功能,机身内部集成了丰富的硬件资源,如红外、RTC、麦克风、LED、IMU、按键、蜂鸣器、PMU等,包含1.14寸、135*240分辨率的TFT屏幕,电池容量达到120mAh,接口同样支持HAT与Unit系列产品。

FjtlMoE-9t3kn2zuT3GvQNajTbAT

产品特性

  • 基于 ESP32开发,支持WiFi、蓝牙

  • 内置3轴加速计与3轴陀螺仪

  • 内置Red LED

  • 集成红外发射管

  • 内置RTC

  • 集成麦克风

  • 用户按键, LCD(1.14 寸), 电源/复位按键

  • 120 mAh 锂电池

  • 拓展接口

  • 集成无源蜂鸣器

  • 可穿戴 & 可固定

  • 开发平台 UIFlow, MicroPython, Arduino

 

设计思路

本应用设计思路非常清晰,将设备连接网络后,访问网络广播地址,解析数据后进行播放即可。同时需要绘制界面显示网络电台信息和系统状态,并设置按键用于功能控制。由于M5StickC Plus平台内置加速度传感器,在进行项目实现时增加了通过旋转设备实现电台切换的功能。整体流程如下图所示:

FqqED_8VCW9A_NVT6aa8vflz7CHx

最终显示效果图如下,左侧为M5StickC Plus开发平台,右侧为扬声器模块。可以直观看到屏幕中显示了当前的电台信息和播放状态。平台的G26引脚用于输出音频信号,可以与扬声器模块进行直接连接,不需要额外接线。

Fh7rYdjoa3HNa4DEE9kx8HDsbEZC

FhNoc1iukWKY1WP0yUZj1Re0ZVcV

设计要点

M5官方为Arduino平台提供了常用API,能够快速实现联网、屏幕显示、按键控制等功能,本应用设计的主要难点是如何播放网络电台信息。

扬声器模块没有搭配DAC,需要使用芯片内部的DAC输出音频信号。由于平台的处理器是ESP32芯片,电子森林平台已经有比较多的案例给出了使用ESP ADF开发框架播放在线音频的具体方案。但ESP ADF的使用存在一定门槛,且较难进行快速产出,被留为了备选方案。

通过搜索,本人在Github平台上找到了专门为M5平台封装的网络电台播放器,将其加载到Arduino后,便可进行高效开发。播放器支持播放M3U8格式的网络广播地址,创建好播放器对象后设置地址即可自动播放,并且支持换台功能。默认情况下采用G26引脚直接输出模拟音频。下面是使用该库播放网络广播的简单例子:

#include <M3U8Player.h>
​
M3U8Player *player;
String stationUrl = "http://xxxx/xxxx/playlist.m3u8";
player = new M3U8Player(stationUrl);
player->start();

在进行简单的界面设计时,发现M5官方的LCD显示库默认状态下不能显示汉字信息,经过一番查找在官方的Github仓库找到了汉字显示的代码。需要使用M5.Lcd.loadHzk16()加载汉字库后,再使用M5.Lcd.writeHzk(text)绘制待显示的内容。另外,中文字符需要采用GB2312编码保存,与arduino默认编码不同。本项目工程中的stationinfo.h文件包含了将要播放的站点信息,其采用GB2312格式进行保存,修改时需要留意。

为支持姿态控制功能,本人使用了M5.IMU.getAccelData(&accx, &accy, &accz)获取了三个方向的加速度,随后通过阈值判断对不同旋转方向进行识别。姿态识别后,会更新当前播放的电台序号并重新绘制屏幕内容。为避免连续时间内进行多次识别,这里简单增加了一秒钟的延时。

项目主要代码如下:

#include <M5StickCPlus.h>
#include <WiFi.h>
#include <M3U8Player.h>
#include <SD.h>
#include "stationinfo.h"
​
// Write your SSID and Password here
#define SSID "esptest"
#define PASSWD "123456789"
​
M3U8Player *player;
​
uint8_t cur_station_id = 0;
float accx, accy, accz;
bool isMuted = false;
​
enum RadioStatus { STOP, PLAY } cur_status;
​
void print(const char *text) {
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(5, 0);
    M5.Lcd.printf("%s", text);
}
​
void drawScreen() {
    M5.Lcd.fillScreen(BLACK);
    if (cur_status == PLAY) {
        M5.Lcd.setCursor(10, 10);
        M5.Lcd.print("Playing");
    } else {
        M5.Lcd.setCursor(10, 10);
        M5.Lcd.print("Stop");
    }
​
    M5.Lcd.drawLine(0, 40, 205, 40, ORANGE);
​
    M5.Lcd.setCursor(10, 45);
    M5.Lcd.writeHzk(stationName[cur_station_id]);
}
​
void setup() {
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.loadHzk16();
  M5.Lcd.setTextSize(2);
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWD);
  M5.IMU.Init();
  print("WiFi Connecting...");
  while (!WiFi.isConnected()){ delay(10); }
  cur_status = PLAY;
  player = new M3U8Player(stations[cur_station_id], 100.0, true);
  drawScreen();
}
​
// 判断姿态
void judgePitch() {
    if (accx < -0.9) {
        cur_station_id = cur_station_id - 1 + stationNumber;
        cur_station_id %= stationNumber;
        player->changeStationURL(stations[cur_station_id]);
        drawScreen();
        delay(1000);
    } else if (accx > 0.9) {
        cur_station_id = cur_station_id + 1 + stationNumber;
        cur_station_id %= stationNumber;
        player->changeStationURL(stations[cur_station_id]);
        drawScreen();
        delay(1000);
    }
}
​
void loop() {
  M5.IMU.getAccelData(&accx, &accy, &accz);
  judgePitch();
​
  M5.update();  // 按键读取
  if (M5.BtnA.isPressed()){
    cur_station_id = cur_station_id + 1;
    cur_station_id %= stationNumber;
    player->changeStationURL(stations[cur_station_id]);
    drawScreen();
    delay(1000);
  }
  if (M5.BtnB.isPressed()){
    float nextVolume = isMuted ? 100.0 : 0.0;
    player->setVolume(nextVolume);
    isMuted = !isMuted;
​
​
    if (cur_status == PLAY) {
        cur_status = STOP;
    } else {
        cur_status = PLAY;
    }
    drawScreen();
    delay(1000);
  }
  delay(100);
}

总结

M3U8Player库大大简化了项目代码,但该库仅支持m3u8格式的网络电台,不能处理如 http://ngcdn002.cnr.cn/live/jjzs/index.m3u8 结构的复杂地址。如果想要实现更好的效果,则必须要对其内部的代码进行修改。

目前的项目实现中,网络电台的地址是直接写在代码里的,如果想要增删电台则必须重新编译下载程序。为实现更好的效果,可以构建服务器,对电台内容进行在线调整,或接入蜻蜓FM等平台获取在线电台地址。

目前的的项目实现中,屏幕的显示内容相对简单,可以参照优秀项目,重新设计显示界面。并且可以增加时间显示或动态LOGO,丰富整个屏幕的内容。

参考资料

附件下载
stickcwebradio.zip
项目源码
团队介绍
个人团队
团队成员
2x3j
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号