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.
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}