You are here

Projekt 32 - ESP32

Themen:

 


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) von https://www.arduino.cc/en/software.
  • 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: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads. 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

/* 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;
  }
}