Esp32 WebRadio interruption non maîtrisée!

Bonjour à tous,
J'essaie de modifier un sketch de Web Radio à base d'ESP32 + Max 98357A en lui incorporant une Rotary encoder pour régler le volume et, à terme, le choix des stations.
Pour ce faire, j'ai utilisé ce sketch de radio tout simple : https://www.xtronical.com/i2sinternetradio/

auquel j'ai ajouté ce sketch de rotary encoder : GitHub - RalphBacon/226-Better-Rotary-Encoder---no-switch-bounce: An improved sketch to cater for switch bounce, courtesy of Marko Pinteric

Si les deux sketchs fonctionnent correctement indépendamment, ce n'est plus le cas après compilation des deux .
En effet, l'incrémentation ou décrémentation du volume n'intervient qu'après plusieurs rotations du Rotary encoder. C'est assez aléatoire comme le montre cet extrait de la console série :

Setup completed
digitalRead
digitalRead
lrsum = 0
digitalRead
lrsum = 0
digitalRead
lrsum = 0
digitalRead
lrsum =+4
Volume = 7
digitalRead
lrsum = 0
digitalRead
lrsum = 0
digitalRead
lrsum =+4
Volume = 8
digitalRead
lrsum = 0
digitalRead
lrsum = 0
digitalRead
digitalRead
lrsum = 0
digitalRead
lrsum = 0
digitalRead
lrsum = 0

Je butte sur ce problème depuis plusieurs jours et j'espère que vous pourrez m'aider.

Ci dessous mon code :


#include "Arduino.h"
#include "WiFi.h"
#include "Audio.h"

// Digital I/O used
#define I2S_DOUT      26  // DIN connection
#define I2S_BCLK      27  // Bit clock
#define I2S_LRC       25  // Left Right Clock

//uint8_t volume = 5;        // range is 0 to 21

Audio audio;

//Rotary_encoder

// Rotary encoder pins
#define PIN_A 34
#define PIN_B 35
#define PUSH_BTN 32

// A turn counter for the rotary encoder (negative = anti-clockwise)
int rotationCounter = 200;
int volume = 6;

// Flag from interrupt routine (moved=true)
volatile bool rotaryEncoder = false;

// Interrupt routine just sets a flag when rotation is detected
void IRAM_ATTR rotary()
{
  rotaryEncoder = true;
}

// Rotary encoder has moved (interrupt tells us) but what happened?
// See https://www.pinteric.com/rotary.html
int8_t checkRotaryEncoder()
{
  // Reset the flag that brought us here (from ISR)
  rotaryEncoder = false;

  static uint8_t lrmem = 3;
  static int lrsum = 0;
  static int8_t TRANS[] = {0, -1, 1, 14, 1, 0, 14, -1, -1, 14, 0, 1, 14, 1, -1, 0};

  // Read BOTH pin states to deterimine validity of rotation (ie not just switch bounce)
  int8_t l = digitalRead(PIN_A);
  int8_t r = digitalRead(PIN_B);
  Serial.println("digitalRead");

  // Move previous value 2 bits to the left and add in our new values
  lrmem = ((lrmem & 0x03) << 2) + 2 * l + r;

  // Convert the bit pattern to a movement indicator (14 = impossible, ie switch bounce)
  lrsum += TRANS[lrmem];

  /* encoder not in the neutral (detent) state */
  if (lrsum % 4 != 0)
  {
    Serial.println("lrsum = 0");
    return 0;
  }

  /* encoder in the neutral state - clockwise rotation*/
  if (lrsum == 4)
  {
    Serial.println("lrsum =+4");
    lrsum = 0;
    return 1;
  }

  /* encoder in the neutral state - anti-clockwise rotation*/
  if (lrsum == -4)
  {
    Serial.println("lrsum =-4");
    lrsum = 0;
    return -1;
  }
  Serial.println("fin digital read");
  // An impossible rotation has been detected - ignore the movement
  lrsum = 0;
  return 0;

}
//Fin Rotary_encoder




String ssid =     "*************";
String password = "**********";

void setup() {
  Serial.begin(115200);
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid.c_str(), password.c_str());
  while (WiFi.status() != WL_CONNECTED) delay(1500);
  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  //audio.setVolume(6); // 0...21
  audio.setVolume(volume);

  //***************Rotary_encoder********************
  // The module already has pullup resistors on board
  pinMode(PIN_A, INPUT);
  pinMode(PIN_B, INPUT);

  // But not for the push switch
  pinMode(PUSH_BTN, INPUT_PULLUP);

  // We need to monitor both pins, rising and falling for all states
  //attachInterrupt(digitalPinToInterrupt(PIN_A), rotary, CHANGE);
  //attachInterrupt(digitalPinToInterrupt(PIN_B), rotary, CHANGE);
  attachInterrupt (PIN_A, rotary, CHANGE);
  attachInterrupt (PIN_B, rotary, CHANGE);
  Serial.println("Setup completed");
  //***************Fin rotary_encoder*****************



  //    audio.connecttohost("http://www.wdr.de/wdrlive/media/einslive.m3u");
  //    audio.connecttohost("http://macslons-irish-pub-radio.com/media.asx");
  //    audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.aac"); //  128k aac
  //     audio.connecttohost("http://mp3.ffh.de/radioffh/hqlivestream.mp3"); //  128k mp3
  audio.connecttohost("http://vis.media-ice.musicradio.com/CapitalMP3"); //  128k mp3
  //    audio.connecttospeech("Wenn die Hunde schlafen, kann der Wolf gut Schafe stehlen.", "de");
  //    audio.connecttohost("http://media.ndr.de/download/podcasts/podcast4161/AU-20190404-0844-1700.mp3"); // podcast
}

void loop()
{
  // Has rotary encoder moved?
  if (rotaryEncoder)
  {
    // Get the movement (if valid)
    int8_t rotationValue = checkRotaryEncoder();

    // If valid movement, do something
    if (rotationValue != 0)
    {
      if (rotationValue < 1) (volume = volume - 1);
      else (volume = volume + 1);
      Serial.print("Volume = "), Serial.println (volume);
    }
  }

  if (digitalRead(PUSH_BTN) == LOW)
  {
    volume = 0;
    Serial.print("X");
    Serial.println(volume);

    // Wait until button released (demo only! Blocking call!)
    while (digitalRead(PUSH_BTN) == LOW)
    {
      delay(100);
    }
  }

  audio.loop();

}

/*
  // optional
  void audio_info(const char *info) {
  Serial.print("info        "); Serial.println(info);
  }
  void audio_id3data(const char *info) { //id3 metadata
  Serial.print("id3data     "); Serial.println(info);
  }
  void audio_eof_mp3(const char *info) { //end of file
  Serial.print("eof_mp3     "); Serial.println(info);
  }
  void audio_showstation(const char *info) {
  Serial.print("station     "); Serial.println(info);
  }
  void audio_showstreaminfo(const char *info) {
  Serial.print("streaminfo  "); Serial.println(info);
  }
  void audio_showstreamtitle(const char *info) {
  Serial.print("streamtitle "); Serial.println(info);
  }
  void audio_bitrate(const char *info) {
  Serial.print("bitrate     "); Serial.println(info);
  }
  void audio_commercial(const char *info) { //duration in sec
  Serial.print("commercial  "); Serial.println(info);
  }
  void audio_icyurl(const char *info) { //homepage
  Serial.print("icyurl      "); Serial.println(info);
  }
  void audio_lasthost(const char *info) { //stream URL played
  Serial.print("lasthost    "); Serial.println(info);
  }
  void audio_eof_speech(const char *info) {
  Serial.print("eof_speech  "); Serial.println(info);
  }
*/

J'ai mis des "mouchards" à plusieurs endroits pour voir où ça coince!
Merci à vous.

Bonjour

J'ai rencontré la même situation en voulant gérer le récepteur radio web avec une télécommande infra rouge.

Je n'ai pas de solution et en suis resté à l'idée qu'il y a conflit potentiel entre les interruptions externes par GPIO et celles qui sont à l'oeuvre pour le DMA (Accès direct mémoire) associé au fonctionnnement du module I2S

Il faudrait se plonger dans l'étude du driver I2S (fourni par Espressif) et de la librairie audio_I2S pour éclaircir les chose et peut être trouver un compromis.
ça me dépasse....et je me contente d'une ergonomie médiocre (appuis parfois répétés sur la télécommande) pour avoir un bon flux audio issu du web , décodé par l'ESP32 puis sorti au bon rythme sur le bus I2S

Pour moi c'est un souci de prise en charge de plusieurs sources d'interruptions, ton interruption externe pour encodeur a probablemnt du mal à passer dans une espace déjà chargé en interruptions internes cadencées dans un timing tendu.

l'ESP32 dispose il me semble d'un hardware permettant de gérer directement un compteur/décompteur , reste à voir si la librairie utilsée pour l'encodeur sait exploiter ce hardware spécifique ou le fait de manière logicielle comme pour un AVR

Bonsoir fitness04,
Vous avez une autre alternative pour votre WebRadio :
ESP32 + VS1053 + télécommande et recepteur IR + Ecran LCD 2004...

1/ connexions :

  • Broche 5V du VS1053 : Broche VIN (5V) de l'ESP32
  • Broche DGND du VS1053 : Broche GND de l'ESP32
  • Broche MISO du VS1053 : Broche D19 de l'ESP32
  • Broche MOSI du VS1053 : Broche D23 de l'ESP32
  • Broche SCK du VS1053 : Broche D18 de l'ESP32
  • Broche DREQ du VS1053 : Broche D4 de l'ESP32
  • Broche XRST du VS1053 : Broche EN de l'ESP32
  • Broche XCS du VS1053 : Broche D5 de l'ESP32
  • Broche XDCS du VS1053 : Broche RX2 - D16 de l'ESP32

Librairie :
https://github.com/schreibfaul1/ESP32-vs1053_ext

Code avec date et heure sur serveur NTP:

#include <LiquidCrystal_I2C.h>
#include "Arduino.h"
#include "WiFi.h"
#include "SPI.h"
#include "vs1053_ext.h"
#include "IRremote.h"
#include "time.h" //horloge

LiquidCrystal_I2C lcd(0x27,20,4);

//Telecommande - Recepteur IR
int receiverPin = 14;
IRrecv irrecv(receiverPin);     
decode_results results;

// Definition des pins du module VS1053 vers l'ESP32
#define SPI_MOSI      23
#define SPI_MISO      19
#define SPI_SCK       18
#define VS1053_CS     5
#define VS1053_DCS    16
#define VS1053_DREQ   4

// Listes des liens WebRadios
const char *radio[18] = {"scdn.nrjaudio.fm/adwz2/fr/30001/mp3_128.mp3?origine=fluxradios","icecast.radiofrance.fr/franceinfo-midfi.mp3","direct.francemusique.fr/live/francemusique-midfi.mp3","icecast.radiofrance.fr/franceinter-midfi.mp3",\ 
"streaming.radio.rtl.fr/rtl-1-44-128?listen=webCwsBCggNCQgLDQUGBAcGBg","icecast.radiofrance.fr/franceculture-midfi.mp3","stream.europe1.fr/europe1.mp3","icecast.radiofrance.fr/fip-midfi.mp3","jazzradio.ice.infomaniak.ch/jazzradio-high.mp3",\
"direct.francebleu.fr/live/fbpoitou-midfi.mp3","streamingp.shoutcast.com/NostalgiePremium-mp3","icecast.skyrock.net/s/natio_mp3_128k","edge.radiomontecarlo.net/RMC.mp3","radioclassique.ice.infomaniak.ch/radioclassique-high.mp3",\
"icecast.funradio.fr/fun-1-44-128?listen=webCwsBCggNCQgLDQUGBAcGBg","stream.virginradio.fr/virgin.mp3","icecast.rtl2.fr/rtl2-1-44-128?listen=webCwsBCggNCQgLDQUGBAcGBg","streamingp.shoutcast.com/Cherie-mp3"}; 

// Nom des WebRadios
const char *NomRadio[18] = {"NRJ","FRANCE INFO","FRANCE MUSIQUE","FRANCE INTER","RTL","FRANCE CULTURE","EUROPE 1","FIP","JAZZ RADIO","FRANCE BLEU POITOU","NOSTALGIE BELGIQUE","SKYROCK","RMC","RADIO CLASSIQUE",\
"FUN RADIO","VIRGIN RADIO","RTL 2","CHERIE"};

byte i=4; // gére les liens et le nom des WebRadios au travers des deux tableaux radio et NomRadio

String ssid =     "fff";
String password = "zzz";

//Volume
int volume=9; // volume a l initialisation
int DernierVolume=0;
boolean Mute = false;

VS1053 mp3(VS1053_CS, VS1053_DCS, VS1053_DREQ); // parametrage - initialisation  VS1053

// paremetres horloge
const int decalage = 2;  // la valeur dépend du fuseau horaire - 2 pour la France. 
const int delaiDemande = 60; // nombre de secondes entre deux demandes consécutives au serveur NTP
unsigned long derniereDemande = millis(); // moment de la plus récente demande au serveur NTP
unsigned long derniereMaJ = millis(); // moment de la plus récente mise à jour de l'affichage de l'heure
time_t maintenant;
struct tm * timeinfo;

void setup() {
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,HIGH); // parametre GPIO 33  pour sortir du sommeil ESP32
  
  lcd.begin();
  lcd.backlight();
  affiche(); // affiche nom et volume de la Web Radio

  
  irrecv.enableIRIn(); // initialise recepteur IR

  
  
    
    Serial.begin(115200);
    SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); //initialise le protocole SPI
    WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid.c_str(), password.c_str());
    while (WiFi.status() != WL_CONNECTED) delay(1500);
     
     //horloge
    configTime(decalage * 3600, 0, "fr.pool.ntp.org");
    while (time(nullptr) <= 100000) {
    Serial.print(".");
    delay(1000);}
    time(&maintenant); //horloge

    // initialise au demarrage la lecture du flux mp3 de RTL au volume 15
    mp3.begin();
    mp3.setVolume(volume); // Fixe le volume à 9
    mp3.connecttohost("streaming.radio.rtl.fr/rtl-1-44-128?listen=webCwsBCggNCQgLDQUGBAcGBg"); // connexion au flux RTL

     
}


void loop()
{
  //HORLOGE
  // est-ce le moment de demander l'heure NTP ?
  if ((millis() - derniereDemande) >=  delaiDemande * 1000 ) {
    time(&maintenant);
    derniereDemande = millis();

    Serial.println("Interrogation du serveur NTP");
  }

  // est-ce que millis() a débordé?
  if (millis() < derniereDemande ) {
    time(&maintenant);
    derniereDemande = millis();
  }

  // est-ce le moment de raffraichir la date indiquée?
  if ((millis() - derniereMaJ) >=   1000 ) {
    maintenant = maintenant + 1;
    afficheHeureDate();
    derniereMaJ = millis();
  }

  
  //WEBRADIO
    mp3.loop();
  // Telecommande
   if (irrecv.decode(&results)){ // Si on reçoit un signal IR
    convertIR(); // on utilise cette procedure pour utiliser le signal
    irrecv.resume(); // Reception d'une nouvelle valeur IR
  }  
  
   
}

void convertIR() // convert IR code
{
 
 Serial.print(results.value);
 Serial.print(F(" -> "));
 
  switch(results.value)
  {
  // moins  
  case 0xFFA25D: if (i !=0) i--; mp3.connecttohost(radio[i]); affiche(); break;
  
  // Mise en SOMMEIL ESP32
  case 0xFFE21D: lcd.clear(); lcd.setCursor(0,1); lcd.print("MISE EN VEILLE"); lcd.setCursor(0,2); lcd.print("VEUILLEZ PATIENTER"); esp_deep_sleep_start(); break;
  
  case 0xFF629D: //plus
  if (i==17) break;
  if (i <=17) i++; mp3.connecttohost(radio[i]); affiche(); break;
  
  case 0xFF22DD: if (Mute!=1) volume= 15; mp3.setVolume(volume); affiche(); break; //fixe le volume a 15
  case 0xFF02FD: if (Mute!=1) volume= 10; mp3.setVolume(volume); affiche(); break; //fixe le volume a 10
  
  //Gestion du volume
  case 0xFFC23D: if (Mute!=1) volume= 6; mp3.setVolume(volume); affiche(); break; //fixe le volume a 7
  case 0xFFE01F:  // diminue le volume
  if (Mute!=1) {
  volume--; 
  if (volume<0) volume=0  ; mp3.setVolume(volume); affiche(); }
  break; 
  case 0xFFA857: // augmente le volume
  if (Mute!=1) {
  volume++; 
  if (volume>21) volume=21; mp3.setVolume(volume); affiche();}
  break; 
  case 0xFF906F: // MUTE
  if (Mute == false) {
  DernierVolume = volume;
  volume=0; mp3.setVolume(volume); affiche();
  Mute = true; break;
  }
  else
  {
    volume = DernierVolume;
    mp3.setVolume(volume); affiche();
    Mute = false; break;
  }

  case 0xFF9867: Serial.println("Non utilise 1"); break; // Non utilise
  case 0xFFB04F: Serial.println("Non utilise 2"); break; // Non utilise
  
  // Gestion des touches 0 a 9 de la telecommande
  case 0xFF6897: i=0; mp3.connecttohost(radio[0]); affiche();  break; //0
  case 0xFF30CF: i=1; mp3.connecttohost(radio[1]); affiche();  break; //1
  case 0xFF18E7: i=2; mp3.connecttohost(radio[2]); affiche();  break; //2
  case 0xFF7A85: i=3; mp3.connecttohost(radio[3]); affiche();  break; //3
  case 0xFF10EF: i=4; mp3.connecttohost(radio[4]); affiche();  break; //4
  case 0xFF38C7: i=5; mp3.connecttohost(radio[5]); affiche();  break; //5
  case 0xFF5AA5: i=6; mp3.connecttohost(radio[6]); affiche();  break; //6
  case 0xFF42BD: i=7; mp3.connecttohost(radio[7]); affiche();  break; //7
  case 0xFF4AB5: i=8; mp3.connecttohost(radio[8]); affiche();  break; //8
  case 0xFF52AD: i=9; mp3.connecttohost(radio[9]); affiche();  break; //9
  
  case 0xFFFFFFFF: Serial.println("Non utilise 3"); break; // Non utilise
  
  default: // Hors portee par exemple
    Serial.println(F(" inconnu "));

  }
  delay(500);
}

void affiche(){ // Gere l'affichage des parametres de la radio sur les deux premieres lignes de l ecran LCD
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(NomRadio[i]);
  lcd.setCursor(0,1);
  lcd.print("VOLUME : ");
  lcd.print(volume);

}
void afficheHeureDate() { // ligne 3 et 4 du LCD
  timeinfo = localtime(&maintenant);
  
  lcd.setCursor(0,2);
  lcd.print("Date: ");

  if (timeinfo->tm_mday < 10) {
    lcd.print("0");
  }

  lcd.print(timeinfo->tm_mday);  // timeinfo->tm_mday: jour du mois (1 - 31)
  lcd.print("-");

  if ((timeinfo->tm_mon + 1) < 10) {
    lcd.print("0");
  }

  lcd.print(timeinfo->tm_mon + 1);    // timeinfo->tm_mon: mois (0 - 11, 0 correspond à janvier)
  lcd.print("-");
  lcd.print(timeinfo->tm_year + 1900);  // timeinfo->tm_year: tm_year nombre d'années écoulées depuis 1900
  
  lcd.setCursor(0,3);
  
  lcd.print("Heure: ");
  if ((timeinfo->tm_hour ) < 10) {
    lcd.print("0");
  }
  lcd.print(timeinfo->tm_hour );  // heure entre 0 et 23
  lcd.print(":");
  if (timeinfo->tm_min < 10) {
    lcd.print("0");
  }
  lcd.print(timeinfo->tm_min);   // timeinfo->tm_min: minutes (0 - 59)
  lcd.print(":");

  if (timeinfo->tm_sec < 10) {
    lcd.print("0");
  }
  lcd.print(timeinfo->tm_sec);   // timeinfo->tm_sec: secondes (0 - 60)

  

}

ça fonctionne parfaitement...
Bonne soirée.

Bonjour à tous les deux et merci pour vos réponses.

Al1fch : c'est un peu ce que je pensais. Une incompatibilité I2S avec l'interruption pour le rotaty encoder. Je vais chercher une autre solution.

Philippe86220 : J'ai déjà testé une solution avec ESP32 + VS1053, sans télécommande mais avec Rotary encoder qui fonctionne. Je m'étais tourné vers le Max 98357A parce qu'il est beaucoup plus compact que le VS1053 et pour apprendre en développant mon propre montage et sketch.

Merci encore.

Pour le 'rotary encoder' sur ESP32 il existe une possibilité , peut être encore non exploitée, de se passer des interruptions par action directe sur des compteurs /décompteurs hardware, l'application radioweb n'aurait qu'à interroger à son initiative la valeur en cours des compteurs.

Reste à trouver une librairie dédiée ESP32 gérant de cette façon les encodeurs en quadrature
(expoitation du module de compteurs PCNT : https://github.com/espressif/esp-idf/tree/3e370c4/examples/peripherals/pcnt/rotary_encoder , pas compteurs soft progressant sur interruptions externes.

Bonsoir

Selon ce qui est annoncé, cette librairie pour ESP32 fait gérer un ou plusiers encodeurs en quadrature par le bloc de comptage PCNT

https://github.com/madhephaestus/ESP32Encoder

Las seule interruption susceptible d'intervenir est celle provoqurée par un débordement de compteur, pas d'interruptions pour la progression de l'encodeur.

Peut être une solution pour une web radio ESP32 'tout en un' : réception flux radio web + décodage mp3 + sortie I2S + réglages par encodeurs en quadrature

Bonsoir al1fch,

L'exemple proposé est avec 2 encodeurs. Comment faire avec un seul?

Merci.

Bonsoir

Probablement en retirant du code d'exemple tout ce qui concerne encoder2