基于HTTPS长连接的ESP32+VS1053网络电台收音机Arduino代码
- 硬件搭建
- 本文使用的控制板两块:ESP32最小系统板,带USB转串行接口,电源模块,可向外部供电。VS1053+SD播放模块,板载耳机功放、耳机插头、线路输入插头及麦克风。如下图:
- 引脚连接如下:
* ESP32 VS1053_SD 功能说明
*
* D5 ———————- CS SD卡片选
* D18 ———————– SCK SPI总线时钟
* D19 ———————– MISO SPI总线输出
* D23 ———————– MOSI SPI总线输入
* D22 ———————– X_CS VS1053控制总线片选
* D21 ———————– X_DCS VS1053数据总线片选
* D15 ———————– DREQ VS1053总线忙信号
* EN ———————– X_RESET VS1053复位(实际EN脚IO号为34,即D34)
* VIN ———————– 5V 5V电源
* GND ——————— GND 接地线
*
- 软件环境
代码编辑使用的是Arduino,并配置好ESP32编译环境。具体软件下载和配置方法网上很多,不再赘述。
- 程序说明
程序总体思路是:
void setup() { uint32_t TimerOut; Serial.begin(115200); Player.begin();//Player.SetVolume(90); WiFi.mode(WIFI_STA); WiFi.begin(SSIDNAME,PASSWORD); Serial.print("Waiting for WiFi to connect..."); TimerOut=0;while ((WiFi.status()!=WL_CONNECTED) && TimerOut++<60){delay(500);Serial.print(".");} if(TimerOut>=60){Serial.println("Connection failed");return;}else{Serial.println(" connected");} setClock();//同步NTP服务器时间,加密传输运算需要。 httpsconnection();//连接网站以获取电台播放数据。 }
1.先连接WIFI(注意SSIDNAME和PASSWORD须与自己使用的WIFI信息一致),
2.系统时间与NTP服务器时间同步(HTTPS连接须要用到)。void setClock()
3.连接电台。void httpsconnection(void)
过程中client -> setCACert(rootCACertificate);语句设置使用网站根证书验证,必须将根证书信息保存在rootCACertificate变量中。本程序中保存的是蜻蜓网络电台的网站根证书,如果连接其他网络电台,则必须进行相应修改。如果不想使用网站根证书进行验证,则可以将此句注释掉,然后使用client -> setInsecure();通知服务器不使用安全验证,明码传输虽然不安全,但对于网络电台收音机来说,这个并不重要。
需要注意的是,如果使用根证书验证,则此过程必须在setClock()过程更新时间后进行,否则连接会不成功。
4.连接成功后不断接收服务器下发的数据块,进行相应处理后存入缓存中,并通过SPI串行总线将数据发送给VS1053解码播放。此过程在loop()主循环中执行。
- 完整代码如下:编译前请先修改SSIDNAME和PASSWORD。其中class VS_1053是一个VS1053操作对象的完整封装,里面有很多用于MP3播放的函数,如果用不上,可以删去多余的函数。完整代码可以到以下地址下载:https://download..net/download/liyong_sbcel/78272687
void setClock() { //此过程更新ESP32内部时钟。 //configTime(0, 0, "pool.ntp.org", "time.nist.gov"); configTime(0, 0, "ntp1.aliyun.com", "ntp2.aliyun.com","ntp3.aliyun.com"); Serial.print(F("Waiting for NTP time sync: ")); time_t nowSecs = time(nullptr); while (nowSecs < 8 * 3600 * 2) { delay(500); Serial.print(F(".")); yield(); nowSecs = time(nullptr); } Serial.println(); struct tm timeinfo; gmtime_r(&nowSecs, &timeinfo); Serial.print(F("Current time: ")); Serial.println(&timeinfo, "%F %T %A"); // 格式化输出:2021-10-24 23:00:44 Sunday } void httpsconnection(void){ uint16_t Index; String writeString; uint8_t writeBuffer[500]; delete client;client = new WiFiClientSecure; client -> setCACert(rootCACertificate);//根证书验证。 client -> setTimeout(15000);//设置超时。 //client -> setInsecure();//不进行身份验证(不使用加密传输)。 Serial.printf("connect https://%s:%d%s...",host,httpsPort,path); client -> connect(host,httpsPort,15000);//开始连接。 Index=100;while(!client -> connected() && Index>0){delay(100);Index--;}if(!client -> connected()){Serial.println("Connection failed");return;}else{Serial.println(" connected");}//超过10秒未建立连接则显示连接失败并退出。 writeString=String("GET ")+path+" HTTP/1.1 Host: "+host+" "; for(Index=0;Index<writeString.length();Index++){ writeBuffer[Index]=writeString[Index]; } client -> write(writeBuffer,Index); Index=100;BufferIndex=0;writeString=""; while(Index){ delay(100);Index--; while(client->available()){ writeString=writeString+char(client->read());BufferIndex++; if(BufferIndex>30){ //等待服务器HTTP回应信息。 if(writeString[BufferIndex-1]== && writeString[BufferIndex-2]== && writeString[BufferIndex-3]== && writeString[BufferIndex-4]== ){ Serial.print(writeString); Index=0;break; } } if(BufferIndex>BufferSize){Index=0;break;}//非HTTP响应,放弃并跳出。 } } //因为第一帧数据表示后续块的大小,为配合数据处理算法,加上一个结束标志. Buffer[0]= ;Buffer[1]= ; BufferIndex=2;PlayIndex=0; } void setup() { uint32_t TimerOut; Serial.begin(115200); Player.begin();//Player.SetVolume(90); WiFi.mode(WIFI_STA); WiFi.begin(SSIDNAME,PASSWORD); Serial.print("Waiting for WiFi to connect..."); TimerOut=0;while ((WiFi.status()!=WL_CONNECTED) && TimerOut++<60){delay(500);Serial.print(".");} if(TimerOut>=60){Serial.println("Connection failed");return;}else{Serial.println(" connected");} setClock();//同步NTP服务器时间,加密传输运算需要。 httpsconnection();//连接网站以获取电台播放数据。 } void loop() { int index; if(!client->connected()){Serial.printf("重新连接: ");httpsconnection();}//如连接断开则自动重连。 //不断读取电台数据,处理后存入缓存中。 while(client->available()){ Buffer[BufferIndex]=client->read();BufferIndex++; if(Buffer[(BufferIndex+BufferSize-1)%BufferSize]== && Buffer[(BufferIndex+BufferSize-2)%BufferSize]== && Buffer[(BufferIndex+BufferSize-6)%BufferSize]== && Buffer[(BufferIndex+BufferSize-7)%BufferSize]== ){ //int DataSize=(Buffer[(BufferIndex+BufferSize-3)%BufferSize]-48)+(Buffer[(BufferIndex+BufferSize-4)%BufferSize]-48)*16+(Buffer[(BufferIndex+BufferSize-5)%BufferSize]-48)*16*16; //Serial.printf("块大小:%d ",DataSize); BufferIndex=(BufferIndex+BufferSize-7)%BufferSize;//去除无用的块大小指示帧及块结束标志字节(间隔字符7字节"前一帧数据 xxx 后一帧数据")(将各帧播放数据连接起来) }else{ BufferIndex=BufferIndex%BufferSize; } //保证缓存足量数据用于播放模块。(缓存将满时将数据提供给播放模块) if(PlayIndex>BufferIndex && PlayIndex-BufferIndex<1024 || BufferIndex>PlayIndex && BufferIndex-PlayIndex>BufferSize-1024){break;} yield();//交出系统控制权。 } //接收数据为空或缓存将满时都会播放缓存数据。 if(PlayIndex>BufferIndex && PlayIndex-BufferIndex<BufferSize-128 || BufferIndex>PlayIndex && BufferIndex>PlayIndex+128){ Player.playChunk(Buffer,BufferSize,PlayIndex,64); PlayIndex=(PlayIndex+64)%BufferSize; } yield(); }
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/291101.html