ESP8266 + PN532 + Rotary encoder interrupt

Hello

Beginning on arduino, I’m looking to develop an rfid music player.

The principle is as follows: I scan an rfid card on which is encoded a URI (spotify / itunes …) and the music launches via an API rest (http sonos api on a rapsberry pi) on my sonos speaker.
a rotary encoder will be used to change the volume and the switch for playpause / next / previous.

I have to realize this project a wemos D1 mini + PN532 ( rfid ) and a rotary encoder (KY-040).

At the beginning I had some problems with the rotary encoder, so I added to the “checkknob” function the parameter “ICACHE_RAM_ATTR” and it almost works.
The SetvolumeUp and SetvolumeDown functions are correctly called but when I query for the volume it fails .httpcode = req.GET(); returns -1code

Second problem, I can’t get the pn 532 to work at the same time as the rotary.
The rfid reader is waiting for a card and reads it but if I turn the button the wemos restarts.

I wired the wemos like this

3V3 (WeMos) → 3V3 (PN532)
D8 (WeMos) → SSEL (PN532)
D7 (WeMos) → MOSI (PN532)
D6 (WeMos) → MISO (PN532)
D5 (WeMos) → SCK (PN532)
GND (WeMos) → GND (PN532)

3V3 (WeMos) → 3V3 (rotary)
D1(WeMos) → CLK(rotary)
D2 (WeMos) → DT (rotary)
D3(WeMos) → SW (rotary)
GND (WeMos) → GND (rotary)

What’s not good? I’ll take any advice to improve and make this project work.

if I made a mistake in the forum, you can tell me so that I can move the post.

Thank you very much

#include <Arduino.h>
#include <ESP8266HTTPClient.h>
#include <OneButton.h>
#include <ESP8266WiFi.h>
#include <SPI.h>
#include "PN532_SPI.h"
#include "PN532.h"
#include "NfcAdapter.h"


#define pinA D1 // clk
#define pinB D2 // dt

/////////////////////////////// User Settings /////////////////////////////////////////

const char* ssid     = "SFR-38d0";
const char* password = "QM2CZ6YFAALY";

// Variable API Sonos
String SPEAKER_SONOS = "Salon";
String URL_API_SONOS = "http://192.168.0.13:5005";
String URL_BASE =  URL_API_SONOS + "/" + SPEAKER_SONOS;
String URL_PLAYPAUSE = URL_BASE + "/playpause";
String URL_PLAYMUSIC = URL_BASE + "/spotify/now";
String URL_NEXT = URL_BASE + "/next";
String URL_PREVIOUS = URL_BASE + "/previous";
String URL_VOLUME_UP = URL_BASE + "/volume/+";
String URL_VOLUME_DOWN = URL_BASE + "/volume/-";

/////////////////////////////// Hardware Configuration ////////////////////////////////

//attach a button on pin D3 to the library
OneButton button(D3, true);
PN532_SPI interface(SPI, D8);
NfcAdapter nfc = NfcAdapter(interface);

String lastuid = "";
String RecordAsString = "";

// variable knob
int compteur = 0 ;
bool etatA ;
bool dernierEtatA ;
long unsigned tempsA ;

// initialisation httpclient
HTTPClient req;
WiFiClient client;
/////////////////////////////// Fonction ////////////////////////////////

void requeteApi(String requete)
{
  int httpcode;
  // affichage de la requete dans la console
  Serial.println(requete);
  // creation de la requete
  req.begin(client,requete);
  Serial.println("req.begin passé");
  // recuperation du code http de retour
  httpcode = req.GET();
  Serial.println("error code : " + httpcode);
  //Check the returning code
  if (httpcode > 0)
  {
    String payload = req.getString();
    Serial.println(payload);
      if (payload != "{\"status\":\"success\"}")
      {
        Serial.println("Erreur pour joindre l'API");
      }
      //Close connection
      
      req.end();
  }
  else
  {
    Serial.println("HTTP SONOS API ne repond pas");
  }
  
}

void SetvolumeUp(int vol)
{
  String volrequp =  URL_VOLUME_DOWN + 5;
  requeteApi(volrequp);
}

void SetvolumeDown(int vol)
{
  String volreqdw =  URL_VOLUME_DOWN + 5;
  requeteApi(volreqdw);
}

ICACHE_RAM_ATTR void checkknob()
{
  // on mesure A
  etatA = digitalRead(pinA);

  // controle du temps pour eviter des erreurs
  if ( abs(millis() - tempsA) > 50 )
  {
    // Si B different de l'ancien état de A alors
    if (digitalRead(pinB) != dernierEtatA) {
      compteur--;
      SetvolumeDown(compteur);
    }
    else {
      compteur++;
      SetvolumeUp(compteur);
    }
    // memorisation du temps pour A
    tempsA = millis();
  }
  dernierEtatA = etatA ;
}

void next() {
  requeteApi(URL_NEXT);
}

void playpause() {
  requeteApi(URL_PLAYPAUSE);
}

void previous() {
  requeteApi(URL_PREVIOUS);
}

//////////////////////// MAIN //////////////////////////////////////////////////////

void setup() 
{

    //init console
    Serial.begin(115200);
    delay(10);

    // init wifi
    Serial.print("Connecting to ");
    Serial.println(ssid);

    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED)
    {
      delay(500);
      Serial.print(".");
    }

    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    // link fonction bouton
    button.attachDoubleClick(next);         
    button.attachClick(playpause);                 
    button.attachLongPressStop(previous);  

    // knob 
    pinMode(pinA, INPUT);
    pinMode(pinB, INPUT);

    //pn532
     Serial.println("NDEF Reader");
    nfc.begin(); // begin NFC comm
    
}

void loop() {
  
   //button interrupt
     button.tick();
   //Knob interrupt
     attachInterrupt(digitalPinToInterrupt(pinA), checkknob, CHANGE);
    // rfid function
    ReadCard();

    //delay(500);
}

void ReadCard()
{

  Serial.println("\nScan an NFC tag\n");

  if (nfc.tagPresent())
  {

    NfcTag tag = nfc.read();

    if (tag.hasNdefMessage())
    {

      NdefMessage message = tag.getNdefMessage();
      for (int i = 0; i < message.getRecordCount(); i++)
      {

        NdefRecord record = message.getRecord(i);
        int payloadLength = record.getPayloadLength();
        byte payload[payloadLength];
        record.getPayload(payload);


        for (int c = 0; c < payloadLength; c++)
        {
          RecordAsString += (char)payload[c];
        }


      }
    }

    if (tag.getUidString() != lastuid)
    {
      lastuid = tag.getUidString();
      playmusic(RecordAsString);
      Serial.println("lets music play");
    }
    else {
      Serial.println("Same Card");
    }

  }
}

void playmusic(String tag)
{
  //test
  Serial.println(tag);
}

nobody has an idea :slightly_frowning_face: ?

I believe D1 and D2 are the default I2C pins. So make sure you don't have a conflict there.

I'm not able to study your code in detail, but I would suggest you look into the "state machine" method of processing decoder input. It works quite well. A good explanation can be found here:

https://chome.nerpa.tech/mcu/rotary-encoder-interrupt-service-routine-for-avr-micros/

Of course it's for AVRs, but it should be similar for the D1 Mini.

I went ahead and adapted my standard encoder code to the D1 Mini. I hope it will be useful:

/*
ESP8266
Rotary encoder - Standard lookup table method - both pin-change interrupts enabled.

Interrupts are enabled on both pins.  When either pin triggers an interrupt, the
pins are read, and the change from the previous state is interpreted through a
lookup table.  So all the bouncing on both pins triggers interrupts.

With the appropriate "encoderType" definition, this will work for encoders with
the same number of pulses as detents per revolution (Type 0), as well as for those
with half as many pulses as detents (Type 1).  In either case, the code produces
one tick per detent.  For Type 0, that is only when both switches are open.
For Type 1 encoders, switches can be either both open or both closed at a detent.

The encoder pins are connected to D1 and D2.  The code increments or decrements
the variable "currentValue" when the encoder knob is moved to the next detent,
and the value is sent to the serial monitor whenever it changes.
*/

const int aPIN = D1;                    // encoder pins D1 and D2
const int bPIN = D2;

const byte encoderType = 0;             // encoder with equal # of detents & pulses per rev
//const byte encoderType = 1;             // encoder with  pulses = detents/2. pick one, commment out the other

const int THRESH =(4-(2*encoderType));  // transitions needed to recognize a tick - type 0 = 4, type 1 = 2

const byte ZEERO = 0x80;                // byte data type doesn't do negative

volatile int currentValue = 0;
int oldValue = 0;
byte CURRENT;                           // the current state of the switches
byte INDEX = 15;                        // Index into lookup state table
byte TOTAL = 0;

// Encoder state table - there are 16 possible transitions between interrupts

int ENCTABLE[]  = {0,1,-1,0,-1,0,0,1,1,0,0,-1,0,-1,1,0};

void setup() {

  Serial.begin(57600);

  pinMode(aPIN, INPUT_PULLUP);          // set up encoder pins as INPUT-PULLUP
  pinMode(bPIN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(aPIN), Encoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(bPIN), Encoder, CHANGE);
}

void loop() {

  if(currentValue != oldValue) {        // serial print current value when it changes
    Serial.println(currentValue);
    oldValue = currentValue;
  }
}

ICACHE_RAM_ATTR void Encoder() {        // pin change interrupts service routine. interrupts
                                        //     automatically disabled during execution

  INDEX     = INDEX << 2;               // Shift previous state left 2 bits (0 in)
  if(digitalRead (aPIN)) bitSet(INDEX,0); // If aPIN is high, set INDEX bit 0
  if(digitalRead (bPIN)) bitSet(INDEX,1); // If bPIN is high, set INDEX bit 1
  CURRENT = INDEX & 3;                  // CURRENT is the two low-order bits of INDEX
  INDEX &= 15;                          // Mask out all but prev and current

// INDEX is now a four-bit index into the 16-byte ENCTABLE state table

  TOTAL += ENCTABLE[INDEX];             //Accumulate transitions

  if((CURRENT == 3) || ((CURRENT == 0) && encoderType)) {  //A valid tick can occur only at a detent

    if(TOTAL == (ZEERO + THRESH)) {
      currentValue++;
    }

    else if(TOTAL == (ZEERO - THRESH)) {
      currentValue--;
    }
    TOTAL = ZEERO;                      //Always reset TOTAL to 0x80 at detent
  }
}