ESP32 Internet Radio + Display

ESP32 Internet Radio + Display

In my last blog I showed an internet radio with three stations hard coded into the program (Arduino sketch). This allowed the station to be cycled between via a button press. To improve upon this I will make use of the OLED 0.96-inch Display – Amazon Link and another button on pin23.

Showing the station and playing song.

On the left is the right top most pins of my ESP32 Wemos Lolin. The right side shows the OLED pins. RED 3v3 – Power, I2C SCL (Clock), I2C SDA (Data) match up to the same marked pins on OLED display.

To begin with I need to find example code for the OLED from the Adafruit_SSD1306 library. I run the ssd1306_128x64_i2c example (Ardunio IDE File Menu). The display didn’t work with the example code as is. The address needed changing in the code 0x3D needed to be changed to 0x3C as shown in bold below. I have take out unneeded code to leave the sample for the text I want to see. This is to experiment with layout. The below program shows the text then switches the back the the normal app after 10 seconds. This is long enough to test the positioning using over the air updates. The station text size is set to 1, position top left and the song info/name is set to size 2 12 pixels below that.

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include "esp_partition.h"
#include "esp_ota_ops.h"

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {

  Serial.begin(115200);
  delay(1000);
  Serial.print("\n");
  Serial.setDebugOutput(true);

  Serial.println("OTA Tool Example");
  const esp_partition_t *running = esp_ota_get_running_partition();
  // Display the running partition
  Serial.printf("Running partition: %s", running->label);
  Serial.println();
  Serial.println("Example end");
    
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Clear the buffer
  display.clearDisplay();
  display.display();

  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);

  display.setTextSize(1);
  display.println(F("Station Name"));

  display.setTextSize(2);
  display.setCursor(0,12);
  display.println(F("Song Info"));

  display.display();

  delay(10000);

  //boot to other app
  const esp_partition_t *next_update_partition = esp_ota_get_next_update_partition(NULL);
  if(esp_ota_set_boot_partition(next_update_partition) == ESP_OK) esp_restart();  
}

void loop() {
}

I needed to create a array to store the text from the functions built-in to output the information in the serial.

//My array with three lines of empty space
char *infoln[3] = 
{
  (char*) "        ",
  (char*) "        ",
  (char*) "        "
};

//this reusable function prints the stored info to the screen.
void updateRadioDisp(void) {
  display.clearDisplay();
  for(int i=0; i<=2; i++) {
    //two different text sizes dependent on i (row number)
    if (i == 0) {display.setTextSize(1);}
    if (i == 1) {display.setTextSize(2);}
    //the cursor position relates i (row number)
    display.setCursor(0,i*12);
    display.setTextColor(SSD1306_WHITE);
    display.println(F(infoln[i]));
    //below line needed to make changes visible
    display.display();
  }
}

//these two update the lines then call the above function to update the display
void audio_showstation(const char *info){
    Serial.print("station     ");Serial.println(info);
    infoln[0] = (char*)info;
    infoln[2] = (char*)"";
    updateRadioDisp();    
}

void audio_showstreamtitle(const char *info){
    Serial.print("streamtitle ");Serial.println(info);
    infoln[1] = (char*)info;
    updateRadioDisp();    
}

Making the menu

I needed to store an array of channel names and a numeric counter integer. It’s so the program knows which station to stream from. These are in the same order as links for the streams.

char *menuln[3] = 
{
(char*) "Gold ",
(char*) "Heart ",
(char*) "Smooth "
};

int staNumber = 0;

char *staList[3] = {
(char*) "http://icecast.thisisdax.com/Gold",
(char*) "http://icecast.thisisdax.com/HeartKent",
(char*) "http://icecast.thisisdax.com/SmoothKent"
};

To get the items to cycle and select I need to code reaction for the buttons. To detect the button press we need to measure the time it was held down for. A debounce time and state are needed so we can do this.

unsigned long lastResetDebounceTime = 0;
unsigned const long debounceResetDelay = 5000;
unsigned int lastButtonState = 0;
unsigned int lastButton2State = 0;
unsigned int buttonState = 0;
unsigned int button2State = 0;

unsigned int lastButton2DebounceTime = 0;
unsigned const int debounceButton2Delay = 100;
unsigned int lastPlayDebounceTime = 0;
unsigned int debouncePlayDelay = 100;

This is in the main loop.

Play Button

 //watch for button press
  int buttonRead = digitalRead(buttonPin);
  if (buttonRead != lastButtonState){
    lastButtonState = currentMillis;
  }

  if ((currentMillis - lastPlayDebounceTime) > debouncePlayDelay)
  {
    lastPlayDebounceTime = currentMillis;
    
    if (buttonRead != buttonState) {
      buttonState = buttonRead;
      
      if (buttonRead == LOW) {        
        // only play if the button state is HIGH
        audio.stopSong();


        audio.connecttohost(staList[infolnc]);

        //clear 2nd line of display
        infoln[1] = (char*) "";

        staNumber = infolnc;

        
        //play the sound flag
        p = true;
  
        Serial.println(infolnc);
      }
    }

  }

Cycle menu Select Button

  //watch for button2 press
  int button2Read = digitalRead(button2Pin);
  if (button2Read != lastButton2State){
    lastButton2State = currentMillis;
  }
  
  if ((currentMillis - lastButton2DebounceTime) > debounceButton2Delay)
  {
    lastButton2DebounceTime = currentMillis;
    if (button2Read != button2State) {
      button2State = button2Read;
      // only toggle menu if the button2 state is HIGH
      if (button2State == LOW) {
        Serial.println("Button2 Pressed!");

        if(infolnc>=2)
          {
            infolnc = 0;
          }
            else
          {
            infolnc++;
          }
        
        updateDisp();
        
      }
    }
  }  

After these if statements the below variables save the sate so the change can be monitored. This creates a one short button. The cod

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = buttonRead;
  lastButton2State = button2Read;