Hey there, thanks for taking the time to read and respond. Here is the complete code in its' current form:
/* ============================================================================================
* To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.
*
* The pins for I2S on ESP8266 using the ESP8266Audio lib are:
* LRC - D4 (GPIO2)
* BCLK - D8 (GPIO15)
* DIN - RX (GPIO3, RXD0)
*
* The extra pins on the MAX98357A board are:
* SD - taken care of by adafruit breakout board HW; mode set to stereo average with 5V VCC
* Gain - Tie to 100k resistor to 5V VCC for 3dB gain... default is 9dB
* Vin - tie to 5V VCC
* GND - have a guess
*
*
* // Impolement eeprom to save last channel and volume???
* // add tft stuff for rolling display of station?
// animated icon to indicate playing?
// metadata(ICY) contains stream information
metadata not always populated! just hard code the names and whatnot and use metadata when avail
add indicator LEDs
// grab time from internet...
connecting to two webpages at once or quickly retriving data from a page?
//In the past I have used a circuit to detect that power is dropping out (have to do that before the regulator) and do your write only then...
//rtry same for rst
save data to eeprom in a stuct!!!!
Switch to a lighter display lib???
sometimes the volume wont go down to zero
All the stations that I used in the url list is 64kbps rate for light stream buffer.
The esp8266 have very small buffer space so for my experience its not able to stream flowently above 96kbps and its also depends on your Internet bandwidth and traffic And the server that's transmit the stream some of them weeker and some of them stronger.
The internal DAC of the esp8266 is a 10 bit DAC so don't expect great sound from this DAC.
/*
*
*
* Important Notes about ISR
Inside the attached function, delay() won’t work and the value returned by millis() will not increment. Serial data received while in the function may be lost. You should declare as volatile any variables that you modify within the attached function.
Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as volatile.
*
* ============================================================================================ */
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include "AudioFileSourceHTTPStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"
#include <Wire.h> // For I2C on GPIO
#include <Adafruit_GFX.h> // Core graphics library (!not being used currently)
#include <Adafruit_SSD1306.h> // I2C OLED display driver library (Hardware-specific library)
// WiFi setup
// To include multiple, define an enum type?
#ifndef STASSID
#define STASSID "SSID"
#define STAPSK "PASSWORD"
#endif
#define NUM_STATIONS 11
#define VOL_UP_PIN D3
#define VOL_DN_PIN D6
#define VOL_MAX 2.00
#define VOL_MIN 0.05
#define VOL_INCR 0.05
#define INTERVAL 100
#define CHANGE_STATION_PIN D5
#define STATUS_LED D7
#define SCL_PIN D1
#define SDA_PIN D2
// Declaration for SSD1306 OLED display. The pins for I2C are defined by the Wire-library.
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
double volume = VOL_MIN + VOL_INCR;
double fuzz = 0.000001;
bool VOL_UP_FLAG = false;
bool VOL_DN_FLAG = false;
bool CHANGE_STATION_FLAG = false;
const char* ssid = STASSID;
const char* password = STAPSK;
unsigned int station = 0;
unsigned int cnt = 0;
// These are initialized with radioDisplayInit(), cuz im lazy and couldnt bother finding the exact pixel nums
unsigned int sigPosX;
unsigned int sigPosY;
unsigned int stationNameUpdatePosX;
unsigned int stationNameUpdatePosY;
unsigned int stationURLUpdatePosX;
unsigned int stationURLUpdatePosY;
unsigned int volUpdatePosX;
unsigned int volUpdatePosY;
unsigned int volIndicatorPosX;
unsigned int volIndicatorPosY;
//URL'S
const char* URL_ARRAY[NUM_STATIONS] = {"http://a1rj.streams.com.br:7801/sm",
"http://jazz.streamr.ru/jazz-64.mp3",
"http://www.golden-apple.com:680/;",
"http://stm14.mfmedios.info:8048/;",
"http://cast2.servcast.net:3020/;",
"http://live02.rfi.fr/rfimonde-64.mp3",
"http://live.wbcb1490.com:88/broadwavehigh.mp3",
"http://14543.live.streamtheworld.com:3690/XHFO_FM_SC",
"http://14523.live.streamtheworld.com:3690/KNBAFM_SC",
"http://sa.mp3.icecast.magma.edge-access.net:7200/sc_rad31",
"http://stream.lt8.com.ar:8080/delsiglo995.mp3"
};
//URL'S Names
const char* stationName[NUM_STATIONS] = {"ALJ",
"Jazz RU",
"Golden Apple",
"Mfmedios",
"Servcast",
"RFI - Monde",
"WBCB UK",
"XHFO FM",
"KNBA FM",
"Radio Nacional",
"Del Siglo"
};
AudioGeneratorMP3 *mp3;
AudioFileSourceHTTPStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2S *out;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Construct a display object
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string);
void StatusCallback(void *cbData, int code, const char *string);
void ICACHE_RAM_ATTR volUp();
void ICACHE_RAM_ATTR volDown();
void ICACHE_RAM_ATTR changeStation();
void newStation(const char* URL);
void radioDisplayInit(); // pass eeprom read values in the future
void updateDisplayStationInfo();
void updateDisplayVolume();
void setup() {
Serial.begin(115200);
delay(INTERVAL);
pinMode(STATUS_LED, OUTPUT);
pinMode(VOL_UP_PIN, INPUT);
pinMode(VOL_DN_PIN, INPUT);
pinMode(CHANGE_STATION_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(VOL_UP_PIN), volUp, FALLING);
attachInterrupt(digitalPinToInterrupt(VOL_DN_PIN), volDown, RISING);
attachInterrupt(digitalPinToInterrupt(CHANGE_STATION_PIN), changeStation, RISING);
GPOC = (1 << 13);
// Init display for displaying
Serial.printf("Starting I2C OLED display with addr %x\n", SCREEN_ADDRESS);
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
display.clearDisplay();
radioDisplayInit();
Serial.println("Connecting to WiFi");
WiFi.disconnect();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Try forever
while (WiFi.status() != WL_CONNECTED) {
cnt++;
if (cnt % 2 == 0) GPOS = (1 << 13);
else GPOC = (1 << 13);
Serial.println("...Connecting to WiFi");
delay(100);
}
Serial.println("Connected");
GPOC = (1 << 13);
audioLogger = &Serial;
newStation(URL_ARRAY[station], false);
}
void loop() {
static int lastms = 0;
// MP3 Stream is running...
if (mp3->isRunning()) {
if (VOL_UP_FLAG || VOL_DN_FLAG) {
Serial.print("Volume: ");
Serial.println(volume);
out->SetGain(volume);
// update dipaly
updateDisplayVolume();
VOL_UP_FLAG = false;
VOL_DN_FLAG = VOL_UP_FLAG;
}
if (CHANGE_STATION_FLAG) {
Serial.println("Changing station URL...");
if (station < NUM_STATIONS) station++;
else station = 0;
newStation(URL_ARRAY[station], true);
Serial.println("Done.");
//Update display
updateDisplayStationInfo();
CHANGE_STATION_FLAG = false;
}
// Implement heartbeat using ESP8266 timer interrupt library
if (millis()-lastms > 5*INTERVAL) {
cnt++;
lastms = millis();
if (cnt % 2 == 0) GPOS = (1 << 13);
else GPOC = (1 << 13);
Serial.printf("Running for %d ms...\n", lastms);
Serial.flush();
}
if (!mp3->loop()) {
mp3->stop();
GPOC = (1 << 13);
}
}
else {
GPOC = (1 << 13);
Serial.printf("MP3 done\n");
Serial.println("Restarting...");
newStation(URL_ARRAY[station], true);
//delay(10*INTERVAL);
}
if (!WiFi.isConnected()) {
Serial.printf("Reconnecting...\n.");
WiFi.reconnect();
while (WiFi.status() != WL_CONNECTED) {
cnt++;
if (cnt % 2 == 0) GPOS = (1 << 13);
else GPOC = (1 << 13);
if (cnt % 15 == 0) Serial.print("\n");
Serial.print(".");
delay(100);
}
Serial.print("Connected.");
// begin stream again
newStation(URL_ARRAY[station], true);
}
}
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
char s1[32], s2[64];
strncpy_P(s1, type, sizeof(s1));
s1[sizeof(s1)-1]=0;
strncpy_P(s2, string, sizeof(s2));
s2[sizeof(s2)-1]=0;
Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2);
Serial.flush();
}
// Called when there's a warning or error (like a buffer underflow or decode hiccup)
void StatusCallback(void *cbData, int code, const char *string) {
const char *ptr = reinterpret_cast<const char *>(cbData);
// Note that the string may be in PROGMEM, so copy it to RAM for printf
char s1[64];
strncpy_P(s1, string, sizeof(s1));
s1[sizeof(s1)-1]=0;
//if (cnt % 2 == 0) GPOS = (1 << 13);
//else GPOC = (1 << 13);
//GPOC = (1 << 13);
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1);
Serial.flush();
}
// ISR for volume up button
void volUp() {
if (volume < VOL_MAX + fuzz || volume < VOL_MAX - fuzz) {
volume = volume + VOL_INCR;
VOL_UP_FLAG = true;
}
else VOL_UP_FLAG = false;
}
// ISR for volume down button
void volDown() {
if (volume > VOL_MIN + fuzz || volume > VOL_MIN - fuzz) {
volume = volume - VOL_INCR;
while (volume < 0) {
volume += fuzz;
}
VOL_DN_FLAG = true;
}
else VOL_DN_FLAG = false;
}
void newStation(const char* URL, bool switchBit) {
// Predeclarations for file, buff, out, and mp3 pointers
/*AudioGeneratorMP3 *mp3;
AudioFileSourceHTTPStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2S *out;*/
// My attempt at managing the dynamic mem
if (switchBit) {
delete file;
delete buff;
delete out;
delete mp3;
}
file = new AudioFileSourceHTTPStream(URL);
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, 2048);
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
out = new AudioOutputI2S();
mp3 = new AudioGeneratorMP3();
mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out);
// Added to properly initialize the vol
out->SetGain(volume);
}
void changeStation() {
CHANGE_STATION_FLAG = true;
}
void radioDisplayInit() {
//DISPLAY INIT STUFF
//display.clearDisplay();
display.setTextSize(2); // Prepare font for header text
display.setTextColor(WHITE);
//The colors are fixed. The top rows are yellow and the rest white.
//You can put whatever text you want at the top but if the font is too big the colors will be split.
display.setCursor(0, 0);
display.print(F("ESP.RADIO"));
display.setTextSize(1);
sigPosX = display.getCursorX();
sigPosY = display.getCursorY() + 9;
display.print(F(" By"));
display.setCursor(sigPosX, sigPosY);
display.print(F(" KH"));
display.drawLine(0, 17, 128, 17, SSD1306_WHITE);
display.setCursor(0, 22);
display.print(F("Ch: "));
// Write read-in vals from EEPROM
stationNameUpdatePosX = display.getCursorX();
stationNameUpdatePosY = display.getCursorY();
display.print(stationName[station]);
display.println();
display.println();
display.print(F("@: "));
stationURLUpdatePosX = display.getCursorX();
stationURLUpdatePosY = display.getCursorY();
//Implement string length limit and accompanying rolling text
//display.print(URL_ARRAY[station]);
display.println();
display.println();
display.print(F("Vol: "));
volUpdatePosX = display.getCursorX();
volUpdatePosY = display.getCursorY();
display.print(volume / 2.0);
display.print(F(" "));
volIndicatorPosX = display.getCursorX();
volIndicatorPosY = display.getCursorY();
display.display();
}
void updateDisplayStationInfo() {
display.setTextColor(WHITE, BLACK);
display.setCursor(stationNameUpdatePosX, stationNameUpdatePosY);
display.print(F(" "));
display.setCursor(stationNameUpdatePosX, stationNameUpdatePosY);
display.print(stationName[station]);
display.display();
//Not yet implemented
//display.setCursor(stationURLUpdatePosX, stationURLUpdatePosY);
}
void updateDisplayVolume() {
display.setTextColor(WHITE, BLACK);
/*
Not yet implemented
display.setCursor(volIndicatorPosX, volIndicatorPosY);
display.print(" ");*/
display.setCursor(volUpdatePosX, volUpdatePosY);
display.print(F(" "));
display.setCursor(volUpdatePosX, volUpdatePosY);
display.print(volume);
display.display();
}
/*
Running for 289680 ms...
15:30:36.982 -> Running for 290681 ms...
15:30:37.949 -> Running for 291682 ms...
15:30:38.272 -> Changing station URL...
15:30:38.474 -> MP3 source file not open
15:30:38.474 -> Done.
15:30:38.474 ->
15:30:38.474 -> User exception (panic/abort/assert)
15:30:38.514 -> --------------- CUT HERE FOR EXCEPTION DECODER ---------------
15:30:38.514 ->
15:30:38.514 -> Abort called
15:30:38.514 ->
15:30:38.514 -> >>>stack>>>
15:30:38.514 ->
15:30:38.514 -> ctx: cont
15:30:38.514 -> sp: 3ffefe60 end: 3ffeff10 offset: 0000
15:30:38.514 -> 3ffefe60: 00000001 3fff223c 3fff1974 40209b54
15:30:38.514 -> 3ffefe70: 000000fe 00000000 00000000 00000000
15:30:38.514 -> 3ffefe80: 00000000 00000000 00000000 3ffeff78
15:30:38.514 -> 3ffefe90: 3ffeef10 000000fe 04300860 00000000
15:30:38.514 -> 3ffefea0: 402094d8 4021c44c 3ffeec54 3ffeff78
15:30:38.514 -> 3ffefeb0: 3fffdad0 00000020 04300860 40213ac2
15:30:38.514 -> 3ffefec0: 3ffeed48 00000005 3ffeed48 40213ad4
15:30:38.554 -> 3ffefed0: 3ffeed48 3ffeec50 3ffeec60 40100efb
15:30:38.554 -> 3ffefee0: 3fffdad0 00000000 3fff1974 40209aa6
15:30:38.554 -> 3ffefef0: 3fffdad0 00000000 3ffeff4c 40213400
15:30:38.554 -> 3ffeff00: feefeffe feefeffe 3fffdab0 401011b9
15:30:38.554 -> <<<stack<<<
15:30:38.554 ->
15:30:38.554 -> --------------- CUT HERE FOR EXCEPTION DECODER ---------------
15:30:38.554 ->
15:30:38.554 -> ets Jan 8 2013,rst cause:2, boot mode:(3,6)
15:30:38.594 ->
15:30:38.594 -> load 0x4010f000, len 3424, room 16
15:30:38.594 -> tail 0
15:30:38.594 -> chksum 0x2e
15:30:38.594 -> load 0x3fff20b8, len 40, room 8
15:30:38.594 -> tail 0
15:30:38.594 -> chksum 0x2b
15:30:38.594 -> csum 0x2b
15:30:38.594 -> v00073160
15:30:38.594 -> ~ld
15:30:39.641 -> Connecting to WiFi
15:30:39.763 -> ...Connecting to WiFi
15:30:40.772 -> ...Connecting to WiFi
15:30:41.749 -> ...Connecting to WiFi
15:30:43.561 -> ...Connecting to WiFi
15:30:44.566 -> Connected
*/
There's a lot I intend to remove, and of course it will eventually look a bit prettier. Since beginning this program I've also learned more about using ISR's especially with the Arduino IDE, so I think I'm missing some volatile/static keywords.