Die Entwicklung eines einfachen Internetradios ist mit Hilfe eines ESP32 Audiokit Boards sehr einfach. Das Board enthält bereits sämtliche Komponenten, die für das Verbinden ins Internet (ESP32 WiFi Modul) und das Ausgeben der Audio-Signale benötigt werden (DAC, Audiobuchsen bzw. Verstärker für direkte Lautsprecherausgabe). Somit ist nur noch die Software notwendig. Hier werden bereits sämtliche SW-Komponenten durch Libraries abgedeckt. Somit beschränkt sich die Programmierung nur auf die generelle Ablaufsteuerung (LED ansteuern, Taster auswerten, WiFi Verbindung starten, Audiostream abspielen ...). Die Liste unterhalb enthält eine Schritt-für-Schritt Anleitung für das Projekt. Weiter unterhalb befindet sich der Source Code.
/* USED LIBRARIES ESP32-audioI2S library - https://github.com/schreibfaul1/ESP32-audioI2S ES8388 library - https://github.com/maditnerd/es8388 */ /* IDE BOARD SETTINGS Arduino Board Settings: ESP32 WROVER Module Port: COMx Core Debug Level: None Erase All Flash Before Sketch Upload: Disabled Flash Frequency: 80MHz Flash Mode: QIO Partition Scheme: Huge APP (3MB No OTA/1MB SPIFFS) Upload Speed: 921600 Programmer: Esptool */ /* BOARD SETTINGS Settings for ESP32-A1S v2.2 (ES8388) Switch 2, 3 ON 1, 4, 5 OFF */ // INCLUDES #include "Arduino.h" #include "WiFi.h" #include "Audio.h" #include "ES8388.h" #include "SPI.h" #include "SD.h" #include "FS.h" #include "CSV_Parser.h" // DEFINES // SD card SPI pins #define SD_CS 13 #define SPI_MOSI 15 #define SPI_MISO 2 #define SPI_SCK 14 // Power amplifier pins #define PA_EN 21 // low = enabled, high = disabled // DAC pins #define I2S_DSIN 26 #define I2S_BCLK 27 #define I2S_LRC 25 #define I2S_MCLK 0 #define I2S_DOUT 35 #define IIC_CLK 32 #define IIC_DATA 33 // Button pins #define BTN_VOLUME_DOWN_PIN 19 // key 3 #define BTN_VOLUME_UP_PIN 23 // key 4 #define BTN_STATION_PREV_PIN 18 // key 5 #define BTN_STATION_NEXT_PIN 5 // key 6 #define BTN_DEBOUNCE_CNT 10 // LED pins #define LED_WIFI_PIN 22 // LED 4 #define LED_RADIO_PIN 22 // LED 4 // Station list size #define STATIONS_DEFAULT 5 // size of default station list #define STATIONS_MAX 100 // max. stored stations // Volume step count #define VOLUME_STEPS 21 // WiFi connection tries #define WIFI_CONNECTION_TRIES 10 // GLOBAL VARIABLES static ES8388 dac; Audio audio; const char* ssid_file = "/ssid.txt"; // Configuration files const char* pwd_file = "/password.txt"; const char* ssid2_file = "/ssid2.txt"; const char* pwd2_file = "/password2.txt"; const char* stations_file = "/stations.csv"; String ssid = "wifi"; // Default WIFI settings String password = "123456789"; String ssid2 = "wlan"; String password2 = "password"; uint8_t btn_volume_down_cnt = 0; // Button debouncing counters uint8_t btn_volume_up_cnt = 0; uint8_t btn_station_prev_cnt = 0; uint8_t btn_station_next_cnt = 0; uint8_t connectionCnt = 0; // Connection trys counter uint8_t stationCnt = 0; // How many stations are available uint8_t stationNumber = 0; // Which station is currently used String stationUrls[STATIONS_MAX]; // Available radio stations String stationNames[STATIONS_MAX]; //const char* stationUrls[STATIONS] = { // Default radio station list const String stationUrls_default[STATIONS_DEFAULT] = { "http://ors-sn05.ors-shoutcast.at/oe3-q1a", "http://ors-sn05.ors-shoutcast.at/oe1-q1a", "http://ors-sn05.ors-shoutcast.at/fm4-q1a", "http://ors-sn05.ors-shoutcast.at/noe-q1a", "http://ors-sn05.ors-shoutcast.at/wie-q1a" }; const String stationNames_default[STATIONS_DEFAULT] = { "OE3", "OE1", "FM4", "Radio NOE", "Radio Wien" }; // FUNCTIONS // Read complete content of a file into a string bool readFile(fs::FS &fs, const char * path, String &content) { File file = fs.open(path); if(!file) { Serial.printf("\n"); Serial.printf("File %s not found.", path); return false; } content = ""; while(file.available()) { content += char(file.read()); } file.close(); return true; } // Read comma separated values from a file with 2 columns bool readCSV(const char * path, String column1[], String column2[], uint8_t &rows, uint8_t rows_max) { uint8_t row = 0; CSV_Parser cp("ss", false, ','); // "string,string" with no header if(!cp.readSDfile(path)) { Serial.printf("\n"); Serial.printf("File %s not found.", path); return false; } // Debug output //cp.print(); rows = cp.getRowsCount(); if (rows > rows_max) { rows = rows_max; } for(row = 0; row < rows; row++) { column1[row] = ((char **)(cp[0]))[row]; column2[row] = ((char **)(cp[1]))[row]; } return true; } void setup() { // Initialize debug terminal interface Serial.begin(115200); Serial.printf("\n\n"); Serial.printf("ESP32 WIFI radio started."); // Initialize pins digitalWrite(SD_CS, LOW); // Deselect SD card pinMode(SD_CS, OUTPUT); digitalWrite(PA_EN, HIGH); // Disable power amplifier pinMode(PA_EN, OUTPUT); digitalWrite(LED_WIFI_PIN, HIGH); // Disable LEDs digitalWrite(LED_RADIO_PIN, HIGH); pinMode(LED_WIFI_PIN, OUTPUT); pinMode(LED_RADIO_PIN, OUTPUT); pinMode(BTN_VOLUME_DOWN_PIN, INPUT_PULLUP); // Button pins used as input pins pinMode(BTN_VOLUME_UP_PIN, INPUT_PULLUP); pinMode(BTN_STATION_PREV_PIN, INPUT_PULLUP); pinMode(BTN_STATION_NEXT_PIN, INPUT_PULLUP); // Initialize SD card and read configuration digitalWrite(SD_CS, HIGH); SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); if(SD.begin(SD_CS)) { String tmp_ssid; String tmp_pwd; if((readFile(SD, ssid_file, tmp_ssid)) && (readFile(SD, pwd_file, tmp_pwd))) { ssid = tmp_ssid; password = tmp_pwd; Serial.printf("\n"); Serial.printf("Primary WIFI configuration loaded."); } else { Serial.printf("\n"); Serial.printf("Use default primary WIFI configuration."); } if((readFile(SD, ssid2_file, tmp_ssid)) && (readFile(SD, pwd2_file, tmp_pwd))) { ssid2 = tmp_ssid; password2 = tmp_pwd; Serial.printf("\n"); Serial.printf("Backup WIFI configuration loaded."); } else { Serial.printf("\n"); Serial.printf("Use default backup WIFI configuration."); } if(readCSV(stations_file, stationNames, stationUrls, stationCnt, STATIONS_MAX)) { Serial.printf("\n"); Serial.printf("Radio station list loaded."); Serial.printf("\n"); Serial.printf("Station count: %d", stationCnt); } else { for (uint8_t i=0; i<STATIONS_DEFAULT; i++) { stationUrls[i] = stationUrls_default[i]; stationNames[i] = stationNames_default[i]; stationCnt++; } } } else { for (uint8_t i=0; i<STATIONS_DEFAULT; i++) { stationUrls[i] = stationUrls_default[i]; stationNames[i] = stationNames_default[i]; stationCnt++; } Serial.printf("\n"); Serial.printf("Default WIFI and radio stations used."); } // Initialize WIFI // Set station mode WiFi.disconnect(); WiFi.mode(WIFI_STA); // Connect to first WIFI network WiFi.begin(ssid.c_str(), password.c_str()); Serial.printf("\n"); Serial.print("Connecting "); do { connectionCnt++; Serial.print("."); delay(500); } while ((WiFi.status() != WL_CONNECTED) && (connectionCnt < WIFI_CONNECTION_TRIES)); if (WiFi.status() == WL_CONNECTED) { Serial.printf("\n"); Serial.printf("Connected WiFi: %s\n", ssid); digitalWrite(LED_WIFI_PIN, LOW); } // In case of connection failures, connect to second WIFI else { connectionCnt = 0; WiFi.disconnect(); WiFi.begin(ssid2.c_str(), password2.c_str()); do { connectionCnt++; Serial.print("."); delay(500); } while ((WiFi.status() != WL_CONNECTED) && (connectionCnt < WIFI_CONNECTION_TRIES)); if (WiFi.status() == WL_CONNECTED) { Serial.printf("\n"); Serial.printf("Connected WiFi: %s\n", ssid2); digitalWrite(LED_WIFI_PIN, LOW); } else { ESP.restart(); } } Serial.print("IP-Address: "); Serial.println(WiFi.localIP()); delay(2000); digitalWrite(LED_RADIO_PIN, HIGH); while (!dac.begin(IIC_DATA, IIC_CLK)) { Serial.println("DAC connection failure!"); delay(1000); } audio.i2s_mclk_pin_select(I2S_MCLK); audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DSIN); audio.setVolumeSteps(VOLUME_STEPS); audio.setVolume((VOLUME_STEPS>>1) + (VOLUME_STEPS>>2)); Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, audio.getVolume()); //audio.setTone(low, middle, high); audio.connecttohost(stationUrls[stationNumber].c_str()); Serial.printf("Active station: %s\n", stationNames[stationNumber]); digitalWrite(LED_RADIO_PIN, LOW); } void loop() { // Run audio player audio.loop(); // UI handling if (LOW == digitalRead(BTN_VOLUME_DOWN_PIN)) { btn_volume_down_cnt++; if (btn_volume_down_cnt >= BTN_DEBOUNCE_CNT) { btn_volume_down_cnt = 0; uint8_t vol = audio.getVolume(); if (vol > 0) { vol--; } audio.setVolume(vol); Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, vol); } } else { btn_volume_down_cnt = 0; } if (LOW == digitalRead(BTN_VOLUME_UP_PIN)) { btn_volume_up_cnt++; if (btn_volume_up_cnt >= BTN_DEBOUNCE_CNT) { btn_volume_up_cnt = 0; uint8_t vol = audio.getVolume(); if (vol < (VOLUME_STEPS-1)) { vol++; } audio.setVolume(vol); Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, vol); } } else { btn_volume_up_cnt = 0; } if (LOW == digitalRead(BTN_STATION_PREV_PIN)) { btn_station_prev_cnt++; if (btn_station_prev_cnt >= BTN_DEBOUNCE_CNT) { btn_station_prev_cnt = 0; delay(500); if (stationNumber > 0) { stationNumber--; } else { stationNumber = stationCnt-1; } audio.connecttohost(stationUrls[stationNumber].c_str()); Serial.printf("Active station: %s (nr. %d from %d)\n", stationNames[stationNumber].c_str(), stationNumber+1, stationCnt); } } else { btn_station_prev_cnt = 0; } if (LOW == digitalRead(BTN_STATION_NEXT_PIN)) { btn_station_next_cnt++; if (btn_station_next_cnt >= BTN_DEBOUNCE_CNT) { btn_station_next_cnt = 0; delay(500); if (stationNumber < (stationCnt-1)) { stationNumber++; } else { stationNumber = 0; } audio.connecttohost(stationUrls[stationNumber].c_str()); Serial.printf("Active station: %s (nr. %d from %d)\n", stationNames[stationNumber].c_str(), stationNumber+1, stationCnt); } } else { btn_station_next_cnt = 0; } }