Zum Hauptinhalt springen

ESP32

ESP32 Webradio

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.

  • Die ESP Softwareentwicklung startet man mit dem Download der Arduino IDE (bei mir war Version 2.1.0 aktuell). Danach muss der Board Manager um die ESP32 Boards erweitert werden. Unter “File > Preferences > Additional boards manager URLs” muss folgende URL eingetragen werden https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json. Danach kann man die EPS32 im Boards Manager installieren: “Tools > Board > Boards Manager > esp32 by Espressif > Install”.
  • Für die serielle Verbindung zum Board wird noch der Silab USB UART Chip Treiber (CP210x Universal Windows Driver) benötigt. Beim ESP32 Audiokit Board wird zuerst die USB Schnittstelle für die Versorgung und danach erst die USB Schnittstelle der seriellen Schnittstelle angeschlossen.
  • Innerhalb der Arduino IDE kann man das Board und die COM-Verbindung auswählen: “Tools > Board > esp > ESP32 Wrover Module, COM3”.
  • Danach kann ein Projekt angelegt oder geöffnet werden (Projektordner und “Main”-Datei müssen gleich lauten, z.B. ./webradio/webradio.ino).
  • Damit die benötigten Libraries gefunden werden werden sie mittels Library Manager installiert: “Sketch > Include Library > Library Manager”. Heruntergeladene ZIP Dateien können mittels separaten Menüpunkt installiert werden: “Sketch > Include Library > Add .ZIP Library”. Für das Webradio Projekt wurden folgende Libraries benötigt: ESP32-audioI2S-master.zip und es8388-main.zip.
  • Nach dem Programmieren wird das Projekt einfach Compiliert “Sketch > Verify/Compile” und auf das Board gespielt “Sketch > Upload”. Die Debug Ausgabe kann im “Serial Monitor” Fenster angesehen werden.

Board
WiFi Module
UART Chip
Speaker Amplifier Chip
Mounting
Final Setup

  1/* USED LIBRARIES
  2  ESP32-audioI2S library - https://github.com/schreibfaul1/ESP32-audioI2S
  3  ES8388 library         - https://github.com/maditnerd/es8388
  4*/
  5 
  6/* IDE BOARD SETTINGS
  7  Arduino Board Settings:               ESP32 WROVER Module
  8  Port:                                 COMx
  9  Core Debug Level:                     None
 10  Erase All Flash Before Sketch Upload: Disabled
 11  Flash Frequency:                      80MHz
 12  Flash Mode:                           QIO
 13  Partition Scheme:                     Huge APP (3MB No OTA/1MB SPIFFS)
 14  Upload Speed:                         921600
 15  Programmer:                           Esptool
 16*/
 17 
 18/* BOARD SETTINGS
 19  Settings for ESP32-A1S v2.2 (ES8388)
 20  Switch 2, 3    ON
 21         1, 4, 5 OFF
 22*/
 23 
 24// INCLUDES
 25#include "Arduino.h"
 26#include "WiFi.h"
 27#include "Audio.h"
 28#include "ES8388.h"
 29#include "SPI.h"
 30#include "SD.h"
 31#include "FS.h"
 32#include "CSV_Parser.h"
 33 
 34 
 35// DEFINES
 36// SD card SPI pins
 37#define SD_CS                 13
 38#define SPI_MOSI              15
 39#define SPI_MISO              2
 40#define SPI_SCK               14
 41 
 42// Power amplifier pins
 43#define PA_EN                 21                      // low = enabled, high = disabled
 44 
 45// DAC pins
 46#define I2S_DSIN              26
 47#define I2S_BCLK              27
 48#define I2S_LRC               25
 49#define I2S_MCLK              0
 50#define I2S_DOUT              35
 51 
 52#define IIC_CLK               32
 53#define IIC_DATA              33
 54 
 55// Button pins
 56#define BTN_VOLUME_DOWN_PIN   19                      // key 3
 57#define BTN_VOLUME_UP_PIN     23                      // key 4
 58#define BTN_STATION_PREV_PIN  18                      // key 5
 59#define BTN_STATION_NEXT_PIN  5                       // key 6
 60 
 61#define BTN_DEBOUNCE_CNT      10
 62 
 63// LED pins
 64#define LED_WIFI_PIN          22                      // LED 4
 65#define LED_RADIO_PIN         22                      // LED 4
 66 
 67// Station list size
 68#define STATIONS_DEFAULT      5                       // size of default station list
 69#define STATIONS_MAX          100                     // max. stored stations
 70 
 71// Volume step count
 72#define VOLUME_STEPS          21
 73 
 74// WiFi connection tries
 75#define WIFI_CONNECTION_TRIES 10
 76 
 77 
 78// GLOBAL VARIABLES
 79static ES8388 dac;
 80Audio audio;
 81 
 82const char* ssid_file     = "/ssid.txt";              // Configuration files
 83const char* pwd_file      = "/password.txt";
 84const char* ssid2_file    = "/ssid2.txt";
 85const char* pwd2_file     = "/password2.txt";
 86const char* stations_file = "/stations.csv";
 87 
 88String ssid      = "wifi";                            // Default WIFI settings
 89String password  = "123456789";
 90String ssid2     = "wlan";
 91String password2 = "password";
 92 
 93uint8_t btn_volume_down_cnt  = 0;                     // Button debouncing counters
 94uint8_t btn_volume_up_cnt    = 0;
 95uint8_t btn_station_prev_cnt = 0;
 96uint8_t btn_station_next_cnt = 0;
 97 
 98uint8_t connectionCnt = 0;                            // Connection trys counter
 99uint8_t stationCnt    = 0;                            // How many stations are available
100uint8_t stationNumber = 0;                            // Which station is currently used
101 
102String stationUrls[STATIONS_MAX];                     // Available radio stations
103String stationNames[STATIONS_MAX];
104 
105//const char* stationUrls[STATIONS] = {
106// Default radio station list
107const String stationUrls_default[STATIONS_DEFAULT] = {
108  "http://ors-sn05.ors-shoutcast.at/oe3-q1a",
109  "http://ors-sn05.ors-shoutcast.at/oe1-q1a",
110  "http://ors-sn05.ors-shoutcast.at/fm4-q1a",
111  "http://ors-sn05.ors-shoutcast.at/noe-q1a",
112  "http://ors-sn05.ors-shoutcast.at/wie-q1a"
113};
114 
115const String stationNames_default[STATIONS_DEFAULT] = {
116  "OE3",
117  "OE1",
118  "FM4",
119  "Radio NOE",
120  "Radio Wien"
121};
122 
123 
124// FUNCTIONS
125// Read complete content of a file into a string
126bool readFile(fs::FS &fs, const char * path, String &content) {
127  File file = fs.open(path);
128  if(!file) {
129    Serial.printf("\n");
130    Serial.printf("File %s not found.", path);
131    return false;
132  }
133 
134  content = "";
135  while(file.available()) {
136    content += char(file.read());
137  }
138 
139  file.close();
140  return true;
141}
142 
143// Read comma separated values from a file with 2 columns
144bool readCSV(const char * path, String column1[], String column2[], uint8_t &rows, uint8_t rows_max) {
145  uint8_t row  = 0;
146 
147 
148  CSV_Parser cp("ss", false, ',');                    // "string,string" with no header
149  if(!cp.readSDfile(path)) {
150    Serial.printf("\n");
151    Serial.printf("File %s not found.", path);
152    return false;
153  }
154 
155  // Debug output
156  //cp.print();
157 
158  rows = cp.getRowsCount();
159  if (rows > rows_max) {
160    rows = rows_max;
161  }
162 
163  for(row = 0; row < rows; row++) {
164    column1[row] = ((char **)(cp[0]))[row];
165    column2[row] = ((char **)(cp[1]))[row];
166  }
167 
168  return true;
169}
170 
171 
172void setup() {
173  // Initialize debug terminal interface
174  Serial.begin(115200);
175  Serial.printf("\n\n");
176  Serial.printf("ESP32 WIFI radio started.");
177 
178 
179  // Initialize pins
180  digitalWrite(SD_CS, LOW);                           // Deselect SD card
181  pinMode(SD_CS, OUTPUT);
182 
183  digitalWrite(PA_EN, HIGH);                          // Disable power amplifier
184  pinMode(PA_EN, OUTPUT);
185 
186  digitalWrite(LED_WIFI_PIN, HIGH);                   // Disable LEDs
187  digitalWrite(LED_RADIO_PIN, HIGH);
188  pinMode(LED_WIFI_PIN, OUTPUT);
189  pinMode(LED_RADIO_PIN, OUTPUT);
190 
191  pinMode(BTN_VOLUME_DOWN_PIN, INPUT_PULLUP);         // Button pins used as input pins
192  pinMode(BTN_VOLUME_UP_PIN, INPUT_PULLUP);
193  pinMode(BTN_STATION_PREV_PIN, INPUT_PULLUP);
194  pinMode(BTN_STATION_NEXT_PIN, INPUT_PULLUP);
195 
196 
197  // Initialize SD card and read configuration
198  digitalWrite(SD_CS, HIGH);
199  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
200  if(SD.begin(SD_CS)) {
201    String tmp_ssid;
202    String tmp_pwd;
203 
204    if((readFile(SD, ssid_file, tmp_ssid)) &&
205       (readFile(SD, pwd_file, tmp_pwd))) {
206      ssid     = tmp_ssid;
207      password = tmp_pwd;
208 
209      Serial.printf("\n");
210      Serial.printf("Primary WIFI configuration loaded.");
211    } else {
212      Serial.printf("\n");
213      Serial.printf("Use default primary WIFI configuration.");
214    }
215 
216    if((readFile(SD, ssid2_file, tmp_ssid)) &&
217       (readFile(SD, pwd2_file, tmp_pwd))) {
218      ssid2     = tmp_ssid;
219      password2 = tmp_pwd;
220 
221      Serial.printf("\n");
222      Serial.printf("Backup WIFI configuration loaded.");
223    } else {
224      Serial.printf("\n");
225      Serial.printf("Use default backup WIFI configuration.");
226    }
227 
228    if(readCSV(stations_file, stationNames, stationUrls, stationCnt, STATIONS_MAX)) {
229      Serial.printf("\n");
230      Serial.printf("Radio station list loaded.");
231      Serial.printf("\n");
232      Serial.printf("Station count: %d", stationCnt);
233    } else {
234      for (uint8_t i=0; i<STATIONS_DEFAULT; i++) {
235        stationUrls[i]  = stationUrls_default[i];
236        stationNames[i] = stationNames_default[i];
237        stationCnt++;
238      }
239    }
240  }
241  else {
242    for (uint8_t i=0; i<STATIONS_DEFAULT; i++) {
243      stationUrls[i]  = stationUrls_default[i];
244      stationNames[i] = stationNames_default[i];
245      stationCnt++;
246    }
247 
248    Serial.printf("\n");
249    Serial.printf("Default WIFI and radio stations used.");
250  }
251 
252 
253  // Initialize WIFI
254  // Set station mode
255  WiFi.disconnect();
256  WiFi.mode(WIFI_STA);
257 
258  // Connect to first WIFI network
259  WiFi.begin(ssid.c_str(), password.c_str());
260  Serial.printf("\n");
261  Serial.print("Connecting ");
262  do {
263    connectionCnt++;
264    Serial.print(".");
265    delay(500);
266  }
267  while ((WiFi.status() != WL_CONNECTED) &&
268         (connectionCnt < WIFI_CONNECTION_TRIES));
269 
270  if (WiFi.status() == WL_CONNECTED) {
271    Serial.printf("\n");
272    Serial.printf("Connected WiFi: %s\n", ssid);
273 
274    digitalWrite(LED_WIFI_PIN, LOW);
275  }
276  // In case of connection failures, connect to second WIFI
277  else {
278    connectionCnt = 0;
279    WiFi.disconnect();
280    WiFi.begin(ssid2.c_str(), password2.c_str());
281 
282    do {
283      connectionCnt++;
284      Serial.print(".");
285      delay(500);
286    }
287    while ((WiFi.status() != WL_CONNECTED) &&
288           (connectionCnt < WIFI_CONNECTION_TRIES));
289 
290    if (WiFi.status() == WL_CONNECTED) {
291      Serial.printf("\n");
292      Serial.printf("Connected WiFi: %s\n", ssid2);
293      digitalWrite(LED_WIFI_PIN, LOW);
294    }
295    else {
296      ESP.restart();
297    }
298  }
299 
300  Serial.print("IP-Address: ");
301  Serial.println(WiFi.localIP());
302 
303  delay(2000);
304  digitalWrite(LED_RADIO_PIN, HIGH);
305  while (!dac.begin(IIC_DATA, IIC_CLK)) {
306    Serial.println("DAC connection failure!");
307    delay(1000);
308  }
309  audio.i2s_mclk_pin_select(I2S_MCLK);
310  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DSIN);
311  audio.setVolumeSteps(VOLUME_STEPS);
312  audio.setVolume((VOLUME_STEPS>>1) + (VOLUME_STEPS>>2));
313  Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, audio.getVolume());
314  //audio.setTone(low, middle, high);
315  audio.connecttohost(stationUrls[stationNumber].c_str());
316  Serial.printf("Active station: %s\n", stationNames[stationNumber]);
317  digitalWrite(LED_RADIO_PIN, LOW);
318}
319 
320void loop()
321{
322  // Run audio player
323  audio.loop();
324 
325  // UI handling
326  if (LOW == digitalRead(BTN_VOLUME_DOWN_PIN)) {
327    btn_volume_down_cnt++;
328 
329    if (btn_volume_down_cnt >= BTN_DEBOUNCE_CNT) {
330      btn_volume_down_cnt = 0;
331 
332      uint8_t vol = audio.getVolume();
333      if (vol > 0) {
334        vol--;
335      }
336      audio.setVolume(vol);
337      Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, vol);
338    }
339  }
340  else {
341    btn_volume_down_cnt = 0;
342  }
343 
344 
345  if (LOW == digitalRead(BTN_VOLUME_UP_PIN)) {
346    btn_volume_up_cnt++;
347 
348    if (btn_volume_up_cnt >= BTN_DEBOUNCE_CNT) {
349      btn_volume_up_cnt = 0;
350 
351      uint8_t vol = audio.getVolume();
352      if (vol < (VOLUME_STEPS-1)) {
353        vol++;
354      }
355      audio.setVolume(vol);
356      Serial.printf("Volume [0-%d]: %d\n", VOLUME_STEPS-1, vol);
357    }
358  }
359  else {
360    btn_volume_up_cnt = 0;
361  }
362 
363 
364  if (LOW == digitalRead(BTN_STATION_PREV_PIN)) {
365    btn_station_prev_cnt++;
366 
367    if (btn_station_prev_cnt >= BTN_DEBOUNCE_CNT) {
368      btn_station_prev_cnt = 0;
369      delay(500);
370 
371      if (stationNumber > 0) {
372        stationNumber--;
373      }
374      else {
375        stationNumber = stationCnt-1;
376      }
377      audio.connecttohost(stationUrls[stationNumber].c_str());
378      Serial.printf("Active station: %s (nr. %d from %d)\n",
379                    stationNames[stationNumber].c_str(),
380                    stationNumber+1, stationCnt);
381    }
382  }
383  else {
384    btn_station_prev_cnt = 0;
385  }
386 
387 
388  if (LOW == digitalRead(BTN_STATION_NEXT_PIN)) {
389    btn_station_next_cnt++;
390 
391    if (btn_station_next_cnt >= BTN_DEBOUNCE_CNT) {
392      btn_station_next_cnt = 0;
393      delay(500);
394 
395      if (stationNumber < (stationCnt-1)) {
396        stationNumber++;
397      }
398      else {
399        stationNumber = 0;
400      }
401      audio.connecttohost(stationUrls[stationNumber].c_str());
402      Serial.printf("Active station: %s (nr. %d from %d)\n",
403                    stationNames[stationNumber].c_str(),
404                    stationNumber+1, stationCnt);
405    }
406  }
407  else {
408    btn_station_next_cnt = 0;
409  }
410}