Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
The original Internet radio is based on Wi-Fi networks, but an article ESP8266 and ESP32 WiFi hacked on www.Hackady.io webpage specifically addresses the dangers of hacker attacks on ESP32 and ESP8266 modules through Wi-Fi. I therefore come up to an idea of building an ESP32 radio with a much safer wired Ethernet connection to Internet. I started with the original source code and modified it to connect to the Internet through LAN8720A Network module. The module connects to ESP32 through EMAC interface, so VS1053 had to be moved to HSPI bus. A number of GPIOs had been used to connect LAN8720A network module, but there are still pins connect peripherals such as display, rotary encoder and IR receiver. The project also includes a micro SD card. However, I have only tested a bare version of the radio without display, encoder and IR receiver…
Many ESP32 Radio builders report hearing white noise while listening to it and some even suggest using battery power for VS1053 module or even audio transformers. But I have to thank my bad luck to have managed to build an internet radio that needs none of these. AMS1117B 1.8 V fixed voltage regulator had broken in an accident, but I replaced it with Texas Instruments TPS7A4701-EP based high precision psu. I also added 2200 µF, 1000 µF and two 3300 µF capacitors to all components (ESP32, LAN8720A Network module V1053 and micro SD card).
The radio uses 3.3 V and 5 V power from Microchip Starter Kit I/O Expansion board that was also at hand, but the board power supply is provided from a relatively 12 V 2 A plug power supply. I guess it could be done with a smaller number of capacitors as well, but I have no intention to test it now, when the radio works perfectly.
Of course, I had to put the hardware somewhere, but I had no suitable plastic case, so I built one from LEGO blocks. Though the case seemed awkward at first, it proved handy when adding openings for additional peripheral components like IR receiver.
Adding peripherals
A display and a IR receiver are important additions to an internet radio that require just a little bit more soldering. Schematic version 1.2 adds a 5 V Sharp IR receiver to GPIO35 input and adapts it output to a 0 V to +3.4 V range with a 10 k ohm and a 15 k ohm resistors. It also adds an OLED display with SSD1106 controller on a 5 MHz SPI bus. A rotary encoder with a button and an extra ON/OFF button were also added, but they only required firmware configuration changes to define a new GPIOs settings.
There are no spare GPIOs left on ESP32, now! if you want to connect more preripherals, you may opt for an additional ESP module that connects through programming RS232 port or adding an RS232 port extender. RS232 GPIOs may also be used as digital input and outputs, but this prevents displaying debugging information during the ratio operation. There is also an option of adding more devices in SPI or I2C buses that share the same GPIOs, but this may impair the radio operation.
However, there was much more work that had to be done in the firmware. Sharp GP1UX511QS IR receiver significantly differ from the one used in the original Ed Smallenburg’s Internet radio project. I added irtest command that displays raw data from IR sensor and makes adding a new IR receiver type much easier. SHARP IR receiver has different timings and also seems to be less powerful, but it preforms good enough to control the radio with a number of remotes, anyway.
The original SSD1306 programing library uses I2C bus, but I opted for a faster SPI bus. Quite some changes also had to be made to adapt the library to correctly display the data on OLED with an older SSD1106 controller, as well.
NOTE: THIS IS A MODIFIED ESP32 Radio VERSION THAT SUPPORTS A WIRED ETHERNET CONNECTION by PC USB PROJECTS. Firmware updates and ArduinOTA are disabled to prevent updating with a unsuitable firmware from the Internet. Alter SPI.cpp library to comment or remove the last program line, like this: "// SPIClass SPI(HSPI);" to be enable VSPI bus to be replaced by HSPI bus to support LAN8720A Network module (sample path: C:\Users\Administrator\AppData\Local\Arduino15\packages\esp32\
hardware\esp32\1.0.4\libraries\SPI\src).
You will find a modified *.ino (ESP32-radio_srv.ino) file on https://sites.google.com/site/pcusbprojects, the rest of the source code and programming libraries are available from https://github.com/Edzelf/ESP32-radio.
ESP32 Internet Radio with wired Ethernet connection
C/C++//*******************************************************************************************************************
//* ESP32_Radio_SRV -- Webradio receiver for ESP32, VS1053 MP3 module and optional display. *
//* THIS IS A MODIFIED VERSION THAT SUPPORTS A WIRED ETHERNET CONNECTION by PC USB PROJECTS *
//* https://sites.google.com/site/pcusbprojects
//* Firmware updates and ArduinOTA are disabled to prevent updating with a unsuitable firmware from *
//* the Internet *
//* NOTE: Alter SPI.cpp library to comment or remove the last program line, like this: "// SPIClass SPI(HSPI);" * *
//* to be enable VSPI bus to be replaced by HSPI bus to support LAN8720A Network module *
//* (ex.path:C:\Users\Administrator\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries\SPI\src) *
//* *
//*******************************************************************************************************************
// ESP32 libraries used:
// - WiFiMulti
// - nvs
// - Adafruit_ST7735
// - ArduinoOTA
// - PubSubClientenc_dt_pin
// - SD
// - FS
// - update
// A library for the VS1053 (for ESP32) is not available (or not easy to find). Therefore
// a class for this module is derived from the maniacbug library and integrated in this sketch.
//
// See http://www.internet-radio.com for suitable stations. Add the stations of your choice
// to the preferences in either Esp32_radio_init.ino sketch or through the webinterface.
//
// Brief description of the program:
// First a suitable WiFi network is found and a connection is made.
// Then a connection will be made to a shoutcast server. The server starts with some
// info in the header in readable ascii, ending with a double CRLF, like:
// icy-name:Classic Rock Florida - SHE Radio
// icy-genre:Classic Rock 60s 70s 80s Oldies Miami South Florida
// icy-url:http://www.ClassicRockFLorida.com
// content-type:audio/mpeg
// icy-pub:1
// icy-metaint:32768 - Metadata after 32768 bytes of MP3-data
// icy-br:128 - in kb/sec (for Ogg this is like "icy-br=Quality 2"
//
// After de double CRLF is received, the server starts sending mp3- or Ogg-data. For mp3, this
// data may contain metadata (non mp3) after every "metaint" mp3 bytes.
// The metadata is empty in most cases, but if any is available the content will be
// presented on the TFT.
// Pushing an input button causes the player to execute a programmable command.
//
// The display used is a Chinese 1.8 color TFT module 128 x 160 pixels.
// Now there is room for 26 characters per line and 16 lines.
// Software will work without installing the display.
// Other displays are also supported. See documentation.
// The SD card interface of the module may be used to play mp3-tracks on the SD card.
//
// For configuration of the WiFi network(s): see the global data section further on.
//
// The VSPI interface is used for VS1053, TFT and SD.
//
// Wiring. Note that this is just an example. Pins (except 18,19 and 23 of the SPI interface)
// can be configured in the config page of the web interface.
// ESP32dev Signal Wired to LCD LAN8720A Network Module Wired to VS1053 SDCARD Wired to the rest
// -------- ------ -------------- ----------------------- ------------------- ------ -------------------
// GPIO32 XDCS - - pin 1 (XDCS) - -
// GPIO15 XCS - - pin 2 (XCS) - -
// GPIO33 DREQ - - pin 4 (DREQ) - -
// GPIO2 AD pin 3 (D/C or A0) - - - -
// GPIO23 SD_CS - - - pin 1 (CS) -
// GPIO16 EMAC_MDIO - MDIO - - -
// GPIO17 EMAC_MCD - MDC - - -
// GPIO19 EMAC_TXD0 - TX0 - - -
// GPIO21 EMAC_TX_EN - TX-EN - - -
// GPIO22 EMAC_TXD1 - TX1 - - -
// GPIO25 EMAC_RXD0 - RX0 - - -
// GPIO26 EMAC_RXD1 - RX1 - - -
// GPIO27 EMAC_CRS_DV - CRS - - -
// GPIO12 MISO - - pin 7 (MISO) pin 7 (MISO) -
// GPIO13 MOSI pin 4 (DIN or SDA) - pin 6 (MOSI) pin 2 (MOSI) -
// GPIO14 SCK pin 5 CLK or SCK - pin 5 (SCK) pin 5 (CLK) -
// GPIO4 OSC-EN - OSC-EN - - -
// GPIO0 RETCLK - nINT/RETCLK - - -
// GPI03 RXD0 - - - - Reserved serial input for programming
// GPIO1 TXD0 - - - - Reserved serial output for programming
// GPIO18 - - - - - -
// GIO5 - - - - - -
// GPIO35 - - - - - Infrared receiver VS1838B
// GPIO34 - - - - - Rotary encoder CLK
// GPIO36 - - - - - Rotary encoder DT
// GPIO39 - - - - - Rotary encoder SW
// EN - pin 1 (RST) - pin 3 (XRST) - -
// ------- ------ --------------- ---------------------- ------------------- ------ ----------------
// GND - pin 8 (GND) GND pin 8 (GND) pins 3,6 GND Power supply GND
// VDD 3.3 V - VCC - pin 4 3.3 V Power supply
// VCC 5 V pin 7 (BL) - pin 9 (5 V) - 5 V Power supply
// VCC 5 V pin 6 (VCC) pin 9 (5 V) - - 5 V Power supply
//
// 22-06-2020, SV: A modified version to support fixed ethernet connection. Firmware updates and ArduinOTA are disabled to prevent updating with a unsuitable firmware from the Internet
// 26-04-2017, ES: First set-up, derived from ESP8266 version.
// 08-05-2017, ES: Handling of preferences.
// 20-05-2017, ES: Handling input buttons and MQTT.
// 22-05-2017, ES: Save preset, volume and tone settings.
// 23-05-2017, ES: No more calls of non-iram functions on interrupts.
// 24-05-2017, ES: Support for featherboard.
// 26-05-2017, ES: Correction playing from .m3u playlist. Allow single hidden SSID.
// 30-05-2017, ES: Add SD card support (FAT format), volume indicator.
// 26-06-2017, ES: Correction: start in AP-mode if no WiFi networks configured.
// 28-06-2017, ES: Added IR interface.
// 30-06-2017, ES: Improved functions for SD card play.
// 03-07-2017, ES: Webinterface control page shows current settings.
// 04-07-2017, ES: Correction MQTT subscription. Keep playing during long operations.
// 08-07-2017, ES: More space for streamtitle on TFT.
// 18-07-2017, ES: Time Of Day on TFT.
// 19-07-2017, ES: Minor corrections.
// 26-07-2017, ES: Flexible pin assignment. Add rotary encoder switch.
// 27-07-2017, ES: Removed tinyXML library.
// 18-08-2017, Es: Minor corrections
// 28-08-2017, ES: Preferences for pins used for SPI bus,
// Corrected bug in handling programmable pins,
// Introduced touch pins.
// 30-08-2017, ES: Limit number of retries for MQTT connection.
// Added MDNS responder.
// 11-11-2017, ES: Increased ringbuffer. Measure bit rate.
// 13-11-2017, ES: Forward declarations.
// 16-11-2017, ES: Replaced ringbuffer by FreeRTOS queue, play function on second CPU,
// Included improved rotary switch routines supplied by fenyvesi,
// Better IR sensitivity.
// 30-11-2017, ES: Hide passwords in config page.
// 01-12-2017, ES: Better handling of playlist.
// 07-12-2017, ES: Faster handling of config screen.
// 08-12-2017, ES: More MQTT items to publish, added pin_shutdown.
// 13-12-2017, ES: Correction clear LCD.
// 15-12-2017, ES: Correction defaultprefs.h.
// 18-12-2017, ES: Stop playing during config.
// 02-01-2018, ES: Stop/resume is same command.
// 22-01-2018, ES: Read ADC (GPIO36) and display as a battery capacity percentage.
// 13-02-2018, ES: Stop timer during NVS write.
// 15-02-2018, ES: Correction writing wifi credentials in NVS.
// 03-03-2018, ES: Correction bug IR pinnumber.
// 05-03-2018, ES: Improved rotary encoder interface.
// 10-03-2018, ES: Minor corrections.
// 13-04-2018, ES: Guard against empty string send to TFT, thanks to Andreas Spiess.
// 16-04-2018, ES: ID3 tags handling while playing from SD.
// 25-04-2018, ES: Choice of several display boards.
// 30-04-2018, ES: Bugfix: crash when no IR is configured, no display without VS1063.
// 08-05-2018, ES: 1602 LCD display support (limited).
// 11-05-2018, ES: Bugfix: incidental crash in isr_enc_turn().
// 30-05-2018, ES: Bugfix: Assigned DRAM to global variables used in timer ISR.
// 31-05-2018, ES: Bugfix: Crashed if I2C is used, but pins not defined.
// 01-06-2018, ES: Run Playtask on CPU 0.
// 04-06-2018, ES: Made handling of playlistdata more tolerant (NDR).
// 09-06-2018, ES: Typo in defaultprefs.h.
// 10-06-2018, ES: Rotary encoder, interrupts on all 3 signals.
// 25-06-2018, ES: Timing of mp3loop. Limit read from stream to free queue space.
// 16-07-2018, ES: Correction tftset().
// 25-07-2018, ES: Correction touch pins.
// 30-07-2018, ES: Added GPIO39 and inversed shutdown pin. Thanks to fletsche.
// 31-07-2018, ES: Added TFT backlight control.
// 01-08-2018, ES: Debug info for IR. Shutdown amplifier if volume is 0.
// 02-08-2018, ES: Added support for ILI9341 display.
// 03-08-2018, ES: Added playlistposition for MQTT.
// 06-08-2018, ES: Correction negative time offset, OTA through remote host.
// 16-08-2018, ES: Added Nextion support.
// 18-09-2018, ES: "uppreset" and "downpreset" for MP3 player.
// 04-10-2018, ES: Fixed compile error OLED 64x128 display.
// 09-10-2018, ES: Bug fix xSemaphoreTake.
// 05-01-2019, ES: Fine tune datarate.
// 05-01-2019, ES: Basic http authentication. (just one user)
// 11-02-2019, ES: MQTT topic and subtopic size enlarged.
// 24-04-2019, ES: Do not lock SPI during gettime(). Calling gettime may take a long time.
// 15-05-2019, ES: MAX number of presets as a defined constant.
// 16-12-2019, ES: Modify of claimSPI() function for debugability.
// 21-12-2019, ES: Check chip version.
// 23-03-2020, ES: Allow playlists on SD card.
// 25-03-2020, ES: End of playlist: start over.
//
//
// Define the version number, also used for webserver as Last-Modified header and to
// check version for update. The format must be exactly as specified by the HTTP standard!
#define VERSION "V1.0 23.6.2020 04:43 CET"
//
//
#define UPDATEHOST ""
#define BINFILE ""
#define TFTFILE ""
//
// Define (just one) type of display. See documentation.
//#define BLUETFT // Works also for RED TFT 128x160
#define OLED // 64x128 I2C OLED
//#define DUMMYTFT // Dummy display
//#define LCD1602I2C // LCD 1602 display with I2C backpack
//#define ILI9341 // ILI9341 240*320
//#define NEXTION // Nextion display. Uses UART 2 (pin 16 and 17)
//
#include <ETH.h>
#include <nvs.h>
#include <PubSubClient.h>
#include <WiFiMulti.h>
#include <ESPmDNS.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
// #include <ArduinoOTA.h>
#include <freertos/queue.h>
#include <freertos/task.h>
#include <esp_task_wdt.h>
#include <esp_partition.h>
#include <driver/adc.h>
#include <Update.h>
#include <base64.h>
// Number of entries in the queue
#define QSIZ 400
// Debug buffer size
#define DEBUG_BUFFER_SIZE 150
#define NVSBUFSIZE 150
// Access point name if connection to WiFi network fails. Also the hostname for WiFi and OTA.
// Note that the password of an AP must be at least as long as 8 characters.
// Also used for other naming.
#define NAME "ESP32Radio"
// Max number of presets in preferences
#define MAXPRESETS 200
// Maximum number of MQTT reconnects before give-up
#define MAXMQTTCONNECTS 5
// Adjust size of buffer to the longest expected string for nvsgetstr
#define NVSBUFSIZE 150
// Position (column) of time in topline relative to end
#define TIMEPOS -52
// SPI speed for SD card
#define SDSPEED 1000000
// Size of metaline buffer
#define METASIZ 1024
// Max. number of NVS keys in table
#define MAXKEYS 200
// Time-out [sec] for blanking TFT display (BL pin)
#define BL_TIME 45
//
// Subscription topics for MQTT. The topic will be pefixed by "PREFIX/", where PREFIX is replaced
// by the the mqttprefix in the preferences. The next definition will yield the topic
// "ESP32Radio/command" if mqttprefix is "ESP32Radio".
#define MQTT_SUBTOPIC "command" // Command to receive from MQTT
//
#define otaclient mp3client // OTA uses mp3client for connection to host
//**************************************************************************************************
// Forward declaration and prototypes of various functions. *
//**************************************************************************************************
void displaytime ( const char* str, uint16_t color = 0xFFFF ) ;
void showstreamtitle ( const char* ml, bool full = false ) ;
void handlebyte_ch ( uint8_t b ) ;
void handleFSf ( const String& pagename ) ;
void handleCmd() ;
char* dbgprint( const char* format, ... ) ;
const char* analyzeCmd ( const char* str ) ;
const char* analyzeCmd ( const char* par, const char* val ) ;
void chomp ( String &str ) ;
String httpheader ( String contentstype ) ;
bool nvssearch ( const char* key ) ;
void mp3loop() ;
void tftlog ( const char *str ) ;
void playtask ( void * parameter ) ; // Task to play the stream
void spftask ( void * parameter ) ; // Task for special functions
void gettime() ;
void reservepin ( int8_t rpinnr ) ;
IPAddress ip(10,0,0,3);
//IPAddress ip(192, 168, 137, 7);
IPAddress gw(10,0,0,1);
//IPAddress gw(0, 0, 0, 0);
IPAddress msk(255,255,255, 252);
IPAddress dns_1(193,189,160,13);
IPAddress dns_2(192,189,160,23);
//**************************************************************************************************
// Several structs. *
//**************************************************************************************************
//
SPIClass SPI(HSPI);
struct scrseg_struct // For screen segments
{
bool update_req ; // Request update of screen
uint16_t color ; // Textcolor
uint16_t y ; // Begin of segment row
uint16_t height ; // Height of segment
String str ; // String to be displayed
} ;
enum qdata_type { QDATA, QSTARTSONG, QSTOPSONG } ; // datatyp in qdata_struct
struct qdata_struct
{
int datatyp ; // Identifier
__attribute__((aligned(4))) uint8_t buf[32] ; // Buffer for chunk
} ;
struct ini_struct
{
String mqttbroker ; // The name of the MQTT broker server
String mqttprefix ; // Prefix to use for topics
uint16_t mqttport ; // Port, default 1883
String mqttuser ; // User for MQTT authentication
String mqttpasswd ; // Password for MQTT authentication
uint8_t reqvol ; // Requested volume
uint8_t rtone[4] ; // Requested bass/treble settings
int16_t newpreset ; // Requested preset
String clk_server ; // Server to be used for time of day clock
int8_t clk_offset ; // Offset in hours with respect to UTC
int8_t clk_dst ; // Number of hours shift during DST
int8_t ir_pin ; // GPIO connected to output of IR decoder
int8_t enc_clk_pin ; // GPIO connected to CLK of rotary encoder
int8_t enc_dt_pin ; // GPIO connected to DT of rotary encoder
int8_t enc_sw_pin ; // GPIO connected to SW of rotary encoder
int8_t tft_cs_pin ; // GPIO connected to CS of TFT screen
int8_t tft_dc_pin ; // GPIO connected to D/C or A0 of TFT screen
int8_t tft_scl_pin ; // GPIO connected to SCL of i2c TFT screen
int8_t tft_sda_pin ; // GPIO connected to SDA of I2C TFT screen
int8_t tft_bl_pin ; // GPIO to activate BL of display
int8_t tft_blx_pin ; // GPIO to activate BL of display (inversed logic)
int8_t sd_cs_pin ; // GPIO connected to CS of SD card
int8_t vs_cs_pin ; // GPIO connected to CS of VS1053
int8_t vs_dcs_pin ; // GPIO connected to DCS of VS1053
int8_t vs_dreq_pin ; // GPIO connected to DREQ of VS1053
int8_t vs_shutdown_pin ; // GPIO to shut down the amplifier
int8_t vs_shutdownx_pin ; // GPIO to shut down the amplifier (inversed logic)
int8_t spi_sck_pin ; // GPIO connected to SPI SCK pin
int8_t spi_miso_pin ; // GPIO connected to SPI MISO pin
int8_t spi_mosi_pin ; // GPIO connected to SPI MOSI pin
uint16_t bat0 ; // ADC value for 0 percent battery charge
uint16_t bat100 ; // ADC value for 100 percent battery charge
} ;
struct WifiInfo_t // For list with WiFi info
{
uint8_t inx ; // Index as in "wifi_00"
char * ssid ; // SSID for an entry
char * passphrase ; // Passphrase for an entry
} ;
struct nvs_entry
{
uint8_t Ns ; // Namespace ID
uint8_t Type ; // Type of value
uint8_t Span ; // Number of entries used for this item
uint8_t Rvs ; // Reserved, should be 0xFF
uint32_t CRC ; // CRC
char Key[16] ; // Key in Ascii
uint64_t Data ; // Data in entry
} ;
struct nvs_page // For nvs entries
{ // 1 page is 4096 bytes
uint32_t State ;
uint32_t Seqnr ;
uint32_t Unused[5] ;
uint32_t CRC ;
uint8_t Bitmap[32] ;
nvs_entry Entry[126] ;
} ;
struct keyname_t // For keys in NVS
{
char Key[16] ; // Max length is 15 plus delimeter
} ;
//**************************************************************************************************
// Global data section. *
//**************************************************************************************************
// There is a block ini-data that contains some configuration. Configuration data is *
// saved in the preferences by the webinterface. On restart the new data will *
// de read from these preferences. *
// Items in ini_block can be changed by commands from webserver/MQTT/Serial. *
//**************************************************************************************************
enum display_t { T_UNDEFINED, T_BLUETFT, T_OLED, // Various types of display
T_DUMMYTFT, T_LCD1602I2C, T_ILI9341,
T_NEXTION } ;
enum datamode_t { INIT = 1, HEADER = 2, DATA = 4, // State for datastream
METADATA = 8, PLAYLISTINIT = 16,
PLAYLISTHEADER = 32, PLAYLISTDATA = 64,
STOPREQD = 128, STOPPED = 256
} ;
// Global variables
int DEBUG = 1 ; // Debug on/off
int numSsid ; // Number of available WiFi networks
WiFiMulti wifiMulti ; // Possible WiFi networks
ini_struct ini_block ; // Holds configurable data
WiFiServer cmdserver ( 80 ) ; // Instance of embedded webserver, port 80
WiFiClient mp3client ; // An instance of the mp3 client, also used for OTA
WiFiClient cmdclient ; // An instance of the client for commands
WiFiClient wmqttclient ; // An instance for mqtt
PubSubClient mqttclient ( wmqttclient ) ; // Client for MQTT subscriber
HardwareSerial* nxtserial = NULL ; // Serial port for NEXTION (if defined)
TaskHandle_t maintask ; // Taskhandle for main task
TaskHandle_t xplaytask ; // Task handle for playtask
TaskHandle_t xspftask ; // Task handle for special functions
SemaphoreHandle_t SPIsem = NULL ; // For exclusive SPI usage
hw_timer_t* timer = NULL ; // For timer
char timetxt[9] ; // Converted timeinfo
char cmd[130] ; // Command from MQTT or Serial
uint8_t tmpbuff[6000] ; // Input buffer for mp3 or data stream
QueueHandle_t dataqueue ; // Queue for mp3 datastream
QueueHandle_t spfqueue ; // Queue for special functions
qdata_struct outchunk ; // Data to queue
qdata_struct inchunk ; // Data from queue
uint8_t* outqp = outchunk.buf ; // Pointer to buffer in outchunk
uint32_t totalcount = 0 ; // Counter mp3 data
datamode_t datamode ; // State of datastream
int metacount ; // Number of bytes in metadata
int datacount ; // Counter databytes before metadata
char metalinebf[METASIZ + 1] ; // Buffer for metaline/ID3 tags
int16_t metalinebfx ; // Index for metalinebf
String icystreamtitle ; // Streamtitle from metadata
String icyname ; // Icecast station name
String ipaddress ; // Own IP-address
int bitrate ; // Bitrate in kb/sec
int mbitrate ; // Measured bitrate
int metaint = 0 ; // Number of databytes between metadata
int16_t currentpreset = -1 ; // Preset station playing
String host ; // The URL to connect to or file to play
String playlist ; // The URL of the specified playlist
bool hostreq = false ; // Request for new host
bool reqtone = false ; // New tone setting requested
bool muteflag = false ; // Mute output
bool resetreq = false ; // Request to reset the ESP32
bool updatereq = false ; // Request to update software from remote host
bool NetworkFound = false ; // True if WiFi network connected
bool mqtt_on = false ; // MQTT in use
String networks ; // Found networks in the surrounding
uint16_t mqttcount = 0 ; // Counter MAXMQTTCONNECTS
int8_t playingstat = 0 ; // 1 if radio is playing (for MQTT)
int16_t playlist_num = 0 ; // Nonzero for selection from playlist
File mp3file ; // File containing mp3 on SD card
uint32_t mp3filelength ; // File length
bool localfile = false ; // Play from local mp3-file or not
bool chunked = false ; // Station provides chunked transfer
int chunkcount = 0 ; // Counter for chunked transfer
String http_getcmd ; // Contents of last GET command
String http_rqfile ; // Requested file
bool http_response_flag = false ; // Response required
uint16_t ir_value = 0 ; // IR code
uint32_t ir_0 = 550 ; // Average duration of an IR short pulse
uint32_t ir_1 = 1650 ; // Average duration of an IR long pulse
struct tm timeinfo ; // Will be filled by NTP server
bool time_req = false ; // Set time requested
bool SD_okay = false ; // True if SD card in place and readable
String SD_nodelist ; // Nodes of mp3-files on SD
int SD_nodecount = 0 ; // Number of nodes in SD_nodelist
String SD_currentnode = "" ; // Node ID of song playing ("0" if random)
uint16_t adcval ; // ADC value (battery voltage)
uint32_t clength ; // Content length found in http header
uint32_t max_mp3loop_time = 0 ; // To check max handling time in mp3loop (msec)
int16_t scanios ; // TEST*TEST*TEST
int16_t scaniocount ; // TEST*TEST*TEST
uint16_t bltimer = 0 ; // Backlight time-out counter
display_t displaytype = T_UNDEFINED ; // Display type
std::vector<WifiInfo_t> wifilist ; // List with wifi_xx info
// nvs stuff
nvs_page nvsbuf ; // Space for 1 page of NVS info
const esp_partition_t* nvs ; // Pointer to partition struct
esp_err_t nvserr ; // Error code from nvs functions
uint32_t nvshandle = 0 ; // Handle for nvs access
uint8_t namespace_ID ; // Namespace ID found
char nvskeys[MAXKEYS][16] ; // Space for NVS keys
std::vector<keyname_t> keynames ; // Keynames in NVS
// Rotary encoder stuff
#define sv DRAM_ATTR static volatile
sv uint16_t clickcount = 0 ; // Incremented per encoder click
sv int16_t rotationcount = 0 ; // Current position of rotary switch
sv uint16_t enc_inactivity = 0 ; // Time inactive
sv bool singleclick = false ; // True if single click detected
sv bool doubleclick = false ; // True if double click detected
sv bool tripleclick = false ; // True if triple click detected
sv bool longclick = false ; // True if longclick detected
enum enc_menu_t { VOLUME, PRESET, TRACK } ; // State for rotary encoder menu
enc_menu_t enc_menu_mode = VOLUME ; // Default is VOLUME mode
//
struct progpin_struct // For programmable input pins
{
int8_t gpio ; // Pin number
bool reserved ; // Reserved for connected devices
bool avail ; // Pin is available for a command
String command ; // Command to execute when activated
// Example: "uppreset=1"
bool cur ; // Current state, true = HIGH, false = LOW
} ;
progpin_struct progpin[] = // Input pins and programmed function
{
{ 0, false, false, "", false },
//{ 1, true, false, "", false }, // Reserved for TX Serial output
{ 2, false, false, "", false },
//{ 3, true, false, "", false }, // Reserved for RX Serial input
{ 4, false, false, "", false },
{ 5, false, false, "", false },
//{ 6, true, false, "", false }, // Reserved for FLASH SCK
//{ 7, true, false, "", false }, // Reserved for FLASH D0
//{ 8, true, false, "", false }, // Reserved for FLASH D1
//{ 9, true, false, "", false }, // Reserved for FLASH D2
//{ 10, true, false, "", false }, // Reserved for FLASH D3
//{ 11, true, false, "", false }, // Reserved for FLASH CMD
{ 12, false, false, "", false },
{ 13, false, false, "", false },
{ 14, false, false, "", false },
{ 15, false, false, "", false },
{ 16, false, false, "", false }, // May be UART 2 RX for Nextion
{ 17, false, false, "", false }, // May be UART 2 TX for Nextion
{ 18, false, false, "", false }, // Default for SPI CLK
{ 19, false, false, "", false }, // Default for SPI MISO
//{ 20, true, false, "", false }, // Not exposed on DEV board
{ 21, false, false, "", false }, // Also Wire SDA
{ 22, false, false, "", false }, // Also Wire SCL
{ 23, false, false, "", false }, // Default for SPI MOSI
//{ 24, true, false, "", false }, // Not exposed on DEV board
{ 25, false, false, "", false },
{ 26, false, false, "", false },
{ 27, false, false, "", false },
//{ 28, true, false, "", false }, // Not exposed on DEV board
//{ 29, true, false, "", false }, // Not exposed on DEV board
//{ 30, true, false, "", false }, // Not exposed on DEV board
//{ 31, true, false, "", false }, // Not exposed on DEV board
{ 32, false, false, "", false },
{ 33, false, false, "", false },
{ 34, false, false, "", false }, // Note, no internal pull-up
{ 35, false, false, "", false }, // Note, no internal pull-up
{ 36, true, false, "", false }, // Reserved for ADC battery level
{ 39, false, false, "", false }, // Note, no internal pull-up
{ -1, false, false, "", false } // End of list
} ;
struct touchpin_struct // For programmable input pins
{
int8_t gpio ; // Pin number GPIO
bool reserved ; // Reserved for connected devices
bool avail ; // Pin is available for a command
String command ; // Command to execute when activated
// Example: "uppreset=1"
bool cur ; // Current state, true = HIGH, false = LOW
int16_t count ; // Counter number of times low level
} ;
touchpin_struct touchpin[] = // Touch pins and programmed function
{
{ 4, false, false, "", false, 0 }, // TOUCH0
{ 0, true, false, "", false, 0 }, // TOUCH1, reserved for BOOT button
{ 2, true, false, "", false, 0 }, // TOUCH2, reserver for xrst for vs1053
{ 15, true, false, "", false, 0 }, // TOUCH3, reserved for hspi for vs1053
{ 13, true, false, "", false, 0 }, // TOUCH4, reserved for hspi for vs1053
{ 12, true, false, "", false, 0 }, // TOUCH5, reserved for hspi for vs1053
{ 14, true, false, "", false, 0 }, // TOUCH6, reserved for hspi for vs1053
{ 27, false, false, "", false, 0 }, // TOUCH7
{ 33, false, false, "", false, 0 }, // TOUCH8
{ 32, true, false, "", false, 0 }, // TOUCH9, reserved for XDCS for vs1053
{ -1, false, false, "", false, 0 } // End of list
// End of table
} ;
//**************************************************************************************************
// Pages, CSS and data for the webinterface. *
//**************************************************************************************************
#include "about_html.h"
#include "config_html.h"
#include "index_html.h"
#include "mp3play_html.h"
#include "radio_css.h"
#include "favicon_ico.h"
#include "defaultprefs.h"
//**************************************************************************************************
// End of global data section. *
//**************************************************************************************************
//**************************************************************************************************
// M Q T T P U B _ C L A S S *
//**************************************************************************************************
// ID's for the items to publish to MQTT. Is index in amqttpub[]
enum { MQTT_IP, MQTT_ICYNAME, MQTT_STREAMTITLE, MQTT_NOWPLAYING,
MQTT_PRESET, MQTT_VOLUME, MQTT_PLAYING, MQTT_PLAYLISTPOS
} ;
enum { MQSTRING, MQINT8, MQINT16 } ; // Type of variable to publish
class mqttpubc // For MQTT publishing
{
struct mqttpub_struct
{
const char* topic ; // Topic as partial string (without prefix)
uint8_t type ; // Type of payload
void* payload ; // Payload for this topic
bool topictrigger ; // Set to true to trigger MQTT publish
} ;
// Publication topics for MQTT. The topic will be pefixed by "PREFIX/", where PREFIX is replaced
// by the the mqttprefix in the preferences.
protected:
mqttpub_struct amqttpub[9] = // Definitions of various MQTT topic to publish
{ // Index is equal to enum above
{ "ip", MQSTRING, &ipaddress, false }, // Definition for MQTT_IP
{ "icy/name", MQSTRING, &icyname, false }, // Definition for MQTT_ICYNAME
{ "icy/streamtitle", MQSTRING, &icystreamtitle, false }, // Definition for MQTT_STREAMTITLE
{ "nowplaying", MQSTRING, &ipaddress, false }, // Definition for MQTT_NOWPLAYING
{ "preset" , MQINT8, ¤tpreset, false }, // Definition for MQTT_PRESET
{ "volume" , MQINT8, &ini_block.reqvol, false }, // Definition for MQTT_VOLUME
{ "playing", MQINT8, &playingstat, false }, // Definition for MQTT_PLAYING
{ "playlist/pos", MQINT16, &playlist_num, false }, // Definition for MQTT_PLAYLISTPOS
{ NULL, 0, NULL, false } // End of definitions
} ;
public:
void trigger ( uint8_t item ) ; // Trigger publishig for one item
void publishtopic() ; // Publish triggerer items
} ;
//**************************************************************************************************
// MQTTPUB class implementation. *
//**************************************************************************************************
//**************************************************************************************************
// T R I G G E R *
//**************************************************************************************************
// Set request for an item to publish to MQTT. *
//**************************************************************************************************
void mqttpubc::trigger ( uint8_t item ) // Trigger publishig for one item
{
amqttpub[item].topictrigger = true ; // Request re-publish for an item
}
//**************************************************************************************************
// P U B L I S H T O P I C *
//**************************************************************************************************
// Publish a topic to MQTT broker. *
//**************************************************************************************************
void mqttpubc::publishtopic()
{
int i = 0 ; // Loop control
char topic[80] ; // Topic to send
const char* payload ; // Points to payload
char intvar[10] ; // Space for integer parameter
while ( amqttpub[i].topic )
{
if ( amqttpub[i].topictrigger ) // Topic ready to send?
{
amqttpub[i].topictrigger = false ; // Success or not: clear trigger
sprintf ( topic, "%s/%s", ini_block.mqttprefix.c_str(),
amqttpub[i].topic ) ; // Add prefix to topic
switch ( amqttpub[i].type ) // Select conversion method
{
case MQSTRING :
payload = ((String*)amqttpub[i].payload)->c_str() ;
//payload = pstr->c_str() ; // Get pointer to payload
break ;
case MQINT8 :
sprintf ( intvar, "%d",
*(int8_t*)amqttpub[i].payload ) ; // Convert to array of char
payload = intvar ; // Point to this array
break ;
case MQINT16 :
sprintf ( intvar, "%d",
*(int16_t*)amqttpub[i].payload ) ; // Convert to array of char
payload = intvar ; // Point to this array
break ;
default :
continue ; // Unknown data type
}
dbgprint ( "Publish to topic %s : %s", // Show for debug
topic, payload ) ;
if ( !mqttclient.publish ( topic, payload ) ) // Publish!
{
dbgprint ( "MQTT publish failed!" ) ; // Failed
}
return ; // Do the rest later
}
i++ ; // Next entry
}
}
mqttpubc mqttpub ; // Instance for mqttpubc
//
//**************************************************************************************************
// VS1053 stuff. Based on maniacbug library. *
//**************************************************************************************************
// VS1053 class definition. *
//**************************************************************************************************
class VS1053
{
private:
int8_t cs_pin ; // Pin where CS line is connected
int8_t dcs_pin ; // Pin where DCS line is connected
int8_t dreq_pin ; // Pin where DREQ line is connected
int8_t shutdown_pin ; // Pin where the shutdown line is connected
int8_t shutdownx_pin ; // Pin where the shutdown (inversed) line is connected
uint8_t curvol ; // Current volume setting 0..100%
const uint8_t vs1053_chunk_size = 32 ;
// SCI Register
const uint8_t SCI_MODE = 0x0 ;
const uint8_t SCI_STATUS = 0x1 ;
const uint8_t SCI_BASS = 0x2 ;
const uint8_t SCI_CLOCKF = 0x3 ;
const uint8_t SCI_AUDATA = 0x5 ;
const uint8_t SCI_WRAM = 0x6 ;
const uint8_t SCI_WRAMADDR = 0x7 ;
const uint8_t SCI_AIADDR = 0xA ;
const uint8_t SCI_VOL = 0xB ;
const uint8_t SCI_AICTRL0 = 0xC ;
const uint8_t SCI_AICTRL1 = 0xD ;
const uint8_t SCI_num_registers = 0xF ;
// SCI_MODE bits
const uint8_t SM_SDINEW = 11 ; // Bitnumber in SCI_MODE always on
const uint8_t SM_RESET = 2 ; // Bitnumber in SCI_MODE soft reset
const uint8_t SM_CANCEL = 3 ; // Bitnumber in SCI_MODE cancel song
const uint8_t SM_TESTS = 5 ; // Bitnumber in SCI_MODE for tests
const uint8_t SM_LINE1 = 14 ; // Bitnumber in SCI_MODE for Line input
SPISettings VS1053_SPI ; // SPI settings for this slave
uint8_t endFillByte ; // Byte to send when stopping song
bool okay = true ; // VS1053 is working
protected:
inline void await_data_request() const
{
while ( ( dreq_pin >= 0 ) &&
( !digitalRead ( dreq_pin ) ) )
{
NOP() ; // Very short delay
}
}
inline void control_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
digitalWrite ( cs_pin, LOW ) ;
}
inline void control_mode_off() const
{
digitalWrite ( cs_pin, HIGH ) ; // End control mode
SPI.endTransaction() ; // Allow other SPI users
}
inline void data_mode_on() const
{
SPI.beginTransaction ( VS1053_SPI ) ; // Prevent other SPI users
//digitalWrite ( cs_pin, HIGH ) ; // Bring slave in data mode
digitalWrite ( dcs_pin, LOW ) ;
}
inline void data_mode_off() const
{
digitalWrite ( dcs_pin, HIGH ) ; // End data mode
SPI.endTransaction() ; // Allow other SPI users
}
uint16_t read_register ( uint8_t _reg ) const ;
void write_register ( uint8_t _reg, uint16_t _value ) const ;
inline bool sdi_send_buffer ( uint8_t* data, size_t len ) ;
void sdi_send_fillers ( size_t length ) ;
void wram_write ( uint16_t address, uint16_t data ) ;
uint16_t wram_read ( uint16_t address ) ;
void output_enable ( bool ena ) ; // Enable amplifier through shutdown pin(s)
public:
// Constructor. Only sets pin values. Doesn't touch the chip. Be sure to call begin()!
VS1053 ( int8_t _cs_pin, int8_t _dcs_pin, int8_t _dreq_pin,
int8_t _shutdown_pin, int8_t _shutdownx_pin) ;
void begin() ; // Begin operation. Sets pins correctly,
// and prepares SPI bus.
void startSong() ; // Prepare to start playing. Call this each
// time a new song starts.
inline bool playChunk ( uint8_t* data, // Play a chunk of data. Copies the data to
size_t len ) ; // the chip. Blocks until complete.
// Returns true if more data can be added
// to fifo
void stopSong() ; // Finish playing a song. Call this after
// the last playChunk call.
void setVolume ( uint8_t vol ) ; // Set the player volume.Level from 0-100,
// higher is louder.
void setTone ( uint8_t* rtone ) ; // Set the player baas/treble, 4 nibbles for
// treble gain/freq and bass gain/freq
inline uint8_t getVolume() const // Get the current volume setting.
{ // higher is louder.
return curvol ;
}
void printDetails ( const char *header ) ; // Print config details to serial output
void softReset() ; // Do a soft reset
bool testComm ( const char *header ) ; // Test communication with module
inline bool data_request() const
{
return ( digitalRead ( dreq_pin ) == HIGH ) ;
}
void AdjustRate ( long ppm2 ) ; // Fine tune the datarate
} ;
//**************************************************************************************************
// VS1053 class implementation. *
//**************************************************************************************************
VS1053::VS1053 ( int8_t _cs_pin, int8_t _dcs_pin, int8_t _dreq_pin,
int8_t _shutdown_pin, int8_t _shutdownx_pin) :
cs_pin(_cs_pin), dcs_pin(_dcs_pin), dreq_pin(_dreq_pin), shutdown_pin(_shutdown_pin),
shutdownx_pin(_shutdownx_pin)
{
}
uint16_t VS1053::read_register ( uint8_t _reg ) const
{
uint16_t result ;
control_mode_on() ;
SPI.write ( 3 ) ; // Read operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
// Note: transfer16 does not seem to work
result = ( SPI.transfer ( 0xFF ) << 8 ) | // Read 16 bits data
( SPI.transfer ( 0xFF ) ) ;
await_data_request() ; // Wait for DREQ to be HIGH again
control_mode_off() ;
return result ;
}
void VS1053::write_register ( uint8_t _reg, uint16_t _value ) const
{
control_mode_on( );
SPI.write ( 2 ) ; // Write operation
SPI.write ( _reg ) ; // Register to write (0..0xF)
SPI.write16 ( _value ) ; // Send 16 bits data
await_data_request() ;
control_mode_off() ;
}
bool VS1053::sdi_send_buffer ( uint8_t* data, size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter
data_mode_on() ;
while ( len ) // More to do?
{
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
await_data_request() ; // Wait for space available
SPI.writeBytes ( data, chunk_length ) ;
data += chunk_length ;
}
data_mode_off() ;
return data_request() ; // True if more data can de stored in fifo
}
void VS1053::sdi_send_fillers ( size_t len )
{
size_t chunk_length ; // Length of chunk 32 byte or shorter
data_mode_on() ;
while ( len ) // More to do?
{
await_data_request() ; // Wait for space available
chunk_length = len ;
if ( len > vs1053_chunk_size )
{
chunk_length = vs1053_chunk_size ;
}
len -= chunk_length ;
while ( chunk_length-- )
{
SPI.write ( endFillByte ) ;
}
}
data_mode_off();
}
void VS1053::wram_write ( uint16_t address, uint16_t data )
{
write_register ( SCI_WRAMADDR, address ) ;
write_register ( SCI_WRAM, data ) ;
}
uint16_t VS1053::wram_read ( uint16_t address )
{
write_register ( SCI_WRAMADDR, address ) ; // Start reading from WRAM
return read_register ( SCI_WRAM ) ; // Read back result
}
bool VS1053::testComm ( const char *header )
{
// Test the communication with the VS1053 module. The result wille be returned.
// If DREQ is low, there is problably no VS1053 connected. Pull the line HIGH
// in order to prevent an endless loop waiting for this signal. The rest of the
// software will still work, but readbacks from VS1053 will fail.
int i ; // Loop control
uint16_t r1, r2, cnt = 0 ;
uint16_t delta = 300 ; // 3 for fast SPI
const uint16_t vstype[] = { 1001, 1011, 1002, 1003, // Possible chip versions
1053, 1033, 0000, 1103 } ;
dbgprint ( header ) ; // Show a header
if ( !digitalRead ( dreq_pin ) )
{
dbgprint ( "VS1053 not properly installed!" ) ;
// Allow testing without the VS1053 module
pinMode ( dreq_pin, INPUT_PULLUP ) ; // DREQ is now input with pull-up
return false ; // Return bad result
}
// Further TESTING. Check if SCI bus can write and read without errors.
// We will use the volume setting for this.
// Will give warnings on serial output if DEBUG is active.
// A maximum of 20 errors will be reported.
if ( strstr ( header, "Fast" ) )
{
delta = 3 ; // Fast SPI, more loops
}
for ( i = 0 ; ( i < 0xFFFF ) && ( cnt < 20 ) ; i += delta )
{
write_register ( SCI_VOL, i ) ; // Write data to SCI_VOL
r1 = read_register ( SCI_VOL ) ; // Read back for the first time
r2 = read_register ( SCI_VOL ) ; // Read back a second time
if ( r1 != r2 || i != r1 || i != r2 ) // Check for 2 equal reads
{
dbgprint ( "VS1053 SPI error. SB:%04X R1:%04X R2:%04X", i, r1, r2 ) ;
cnt++ ;
delay ( 10 ) ;
}
}
okay = ( cnt == 0 ) ; // True if working correctly
// Further testing: is it the right chip?
r1 = ( read_register ( SCI_STATUS ) >> 4 ) & 0x7 ; // Read status to get the version
if ( r1 != 4 ) // Version 4 is a genuine VS1053
{
dbgprint ( "This is not a VS1053, " // Report the wrong chip
"but a VS%d instead!",
vstype[r1] ) ;
okay = false ;
}
return ( okay ) ; // Return the result
}
void VS1053::begin()
{
pinMode ( dreq_pin, INPUT ) ; // DREQ is an input
pinMode ( cs_pin, OUTPUT ) ; // The SCI and SDI signals
pinMode ( dcs_pin, OUTPUT ) ;
delay(10); // Delay for 10 ms for VS1053 to start
digitalWrite ( dcs_pin, HIGH ) ; // Start HIGH for SCI en SDI
digitalWrite ( cs_pin, HIGH ) ;
if ( shutdown_pin >= 0 ) // Shutdown in use?
{
pinMode ( shutdown_pin, OUTPUT ) ;
}
if ( shutdownx_pin >= 0 ) // Shutdown (inversed logic) in use?
{
pinMode ( shutdownx_pin, OUTPUT ) ;
}
output_enable ( false ) ; // Disable amplifier through shutdown pin(s)
delay ( 100 ) ;
// Init SPI in slow mode ( 0.2 MHz )
VS1053_SPI = SPISettings ( 200000, MSBFIRST, SPI_MODE0 ) ;
SPI.setDataMode ( SPI_MODE0 ) ;
SPI.setBitOrder ( MSBFIRST ) ;
//printDetails ( "Right after reset/startup" ) ;
delay ( 20 ) ;
//printDetails ( "20 msec after reset" ) ;
if ( testComm ( "Slow SPI, Testing VS1053 read/write registers..." ) )
{
// Most VS1053 modules will start up in midi mode. The result is that there is no audio
// when playing MP3. You can modify the board, but there is a more elegant way:
wram_write ( 0xC017, 3 ) ; // GPIO DDR = 3
wram_write ( 0xC019, 0 ) ; // GPIO ODATA = 0
delay ( 100 ) ;
//printDetails ( "After test loop" ) ;
softReset() ; // Do a soft reset
// Switch on the analog parts
write_register ( SCI_AUDATA, 44100 + 1 ) ; // 44.1kHz + stereo
// The next clocksetting allows SPI clocking at 5 MHz, 4 MHz is safe then.
write_register ( SCI_CLOCKF, 6 << 12 ) ; // Normal clock settings
// multiplyer 3.0 = 12.2 MHz
//SPI Clock to 4 MHz. Now you can set high speed SPI clock.
VS1053_SPI = SPISettings ( 5000000, MSBFIRST, SPI_MODE0 ) ;
write_register ( SCI_MODE, _BV ( SM_SDINEW ) | _BV ( SM_LINE1 ) ) ;
testComm ( "Fast SPI, Testing VS1053 read/write registers again..." ) ;
delay ( 10 ) ;
await_data_request() ;
endFillByte = wram_read ( 0x1E06 ) & 0xFF ;
dbgprint ( "endFillByte is %X", endFillByte ) ;
//printDetails ( "After last clocksetting" ) ;
delay ( 100 ) ;
}
}
void VS1053::setVolume ( uint8_t vol )
{
// Set volume. Both left and right.
// Input value is 0..100. 100 is the loudest.
...
This file has been truncated, please download it to see its full contents.
Comments