maxgerhardt / rotary-encoder-over-mcp23017

Library and example code with which one can controll multiple rotary encoders over the MCP23017 I2C GPIO expander.
GNU Lesser General Public License v3.0
110 stars 18 forks source link

Using the encoder button #9

Open DocLeoCC opened 2 years ago

DocLeoCC commented 2 years ago

From what I've seen in the .h file, it appears that only the CLK and DT pins are used in the library. I was wondering how would I modify the it to use the switch pin on the code.

maxgerhardt commented 2 years ago

There is example code at https://github.com/maxgerhardt/rotary-encoder-over-mcp23017/issues/1#issuecomment-464493307 -- maybe I'll add an example code or expand the library to handle it natively, right now it only handles the rotation of a rotary encoder.

DocLeoCC commented 2 years ago

So I've messed around with your original .h file, and managed to get the encoders' buttons to work (apparently). Added pinC and a rotaryButtonFunc to the main object.

/*
 * RotaryEncOverMCP.h      **MODIFIED**
 *
 *  Created on: 21.05.2018
 *      Author: Maxi
 * 
 */

#ifndef SRC_ROTARYENCOVERMCP_H_
#define SRC_ROTARYENCOVERMCP_H_

/* Describes new objects based on the Rotary and Adafruit MCP23017 library */
#include <Adafruit_MCP23017.h>
#include <Rotary.h>
/* function pointer definition */
typedef void (*rotaryActionFunc)(bool clockwise, int id);
/*new button def*/
typedef void (*rotaryButtonFunc)(bool pressed, int id);
/* We describe an object in which we instantiate a rotary encoder
 * over an I2C expander.
 * It holds the information:
 *  * to which MCP object it's connected
 *  * which pin it is connected to
 *  * what function to call when there is a change
 * */
class RotaryEncOverMCP {
public:
    RotaryEncOverMCP(Adafruit_MCP23017* mcp, byte pinA, byte pinB, byte pinC, rotaryActionFunc actionFunc = nullptr, 
                                rotaryButtonFunc buttonFunc = nullptr, int id = 0)
    : rot(pinA, pinB), mcp(mcp),
      pinA(pinA), pinB(pinB), pinC(pinC),
      actionFunc(actionFunc), buttonFunc(buttonFunc), id(id) {
    }

    /* Initialize object in the MCP */
    void init() {
        if(mcp != nullptr) {
            mcp->pinMode(pinA, INPUT);
            mcp->pullUp(pinA, 0); //disable pullup on this pin
            mcp->setupInterruptPin(pinA,CHANGE);
            mcp->pinMode(pinB, INPUT);
            mcp->pullUp(pinB, 0); //disable pullup on this pin
            mcp->setupInterruptPin(pinB,CHANGE);
            //attempting to insert switch pin
        mcp->pinMode(pinC, INPUT);
        mcp->pullUp(pinC, 0);
        mcp->setupInterruptPin(pinC, CHANGE);
        }
    }

    /* On an interrupt, can be called with the value of the GPIOAB register (or INTCAP) */
    void feedInput(uint16_t gpioAB) {
        uint8_t pinValA = bitRead(gpioAB, pinA);
        uint8_t pinValB = bitRead(gpioAB, pinB);
    uint8_t pinValC = bitRead(gpioAB, pinC); // new read
        uint8_t event = rot.process(pinValA, pinValB);
        if(event == DIR_CW || event == DIR_CCW) {
            //clock wise or counter-clock wise
            bool clockwise = event == DIR_CW;
            //Call into action function if registered
            if(actionFunc) {
                actionFunc(clockwise, id);
            }
        }
    if (!pinValC){
        bool pressed = true;
        if (buttonFunc)
                buttonFunc(pressed, id);
        }
    }

    /* Poll the encoder. Will cause an I2C transfer. */
    void poll() {
        if(mcp != nullptr) {
            feedInput(mcp->readGPIOAB());
        }
    }

    Adafruit_MCP23017* getMCP() {
        return mcp;
    }

    int getID() {
        return id;
    }

private:
    Rotary rot;                         /* the rotary object which will be created*/
    Adafruit_MCP23017* mcp = nullptr;   /* pointer the I2C GPIO expander it's connected to */
    uint8_t pinA = 0;
    uint8_t pinB = 0;           /* the pin numbers for output A and output B */
    uint8_t pinC = 0;
    rotaryActionFunc actionFunc = nullptr;  /* function pointer, will be called when there is an action happening */
    rotaryButtonFunc buttonFunc = nullptr;
    int id = 0;                             /* optional ID for identification */
};

#endif /* SRC_ROTARYENCOVERMCP_H_ */
DocLeoCC commented 2 years ago

The example file with interupts, using 2 encoders and 1 mcp I arrived at this (probably would have to change the const BUTTON_N depending on how many mcp and buttons I'd be using):

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Rotary.h>
#include <RotaryEncOverMCP.h>

#if defined(ESP32) || defined(ESP8266)
#define INTERRUPT_FUNC_ATTRIB IRAM_ATTR
#else
#define INTERRUPT_FUNC_ATTRIB  
#endif
const int BUTTON_N = 4;
/* Our I2C MCP23017 GPIO expanders */
Adafruit_MCP23017 mcp;

//Array of pointers of all MCPs if there is more than one
Adafruit_MCP23017* allMCPs[] = { &mcp };
constexpr int numMCPs = (int)(sizeof(allMCPs) / sizeof(*allMCPs));

/* the INT pin of the MCP can only be connected to
 * an interrupt capable pin on the Arduino, either
 * D3 or D2.
 * */
byte arduinoIntPin = 3;

int button_pins[BUTTON_N] = {12,13,14,15};

/* variable to indicate that an interrupt has occured */
volatile boolean awakenByInterrupt = false;

/* function prototypes */
void intCallBack();
void cleanInterrupts();
void handleInterrupt();
void RotaryEncoderChanged(bool clockwise, int id);
void RotaryButton (bool pressed, int id);

/* Array of all rotary encoders and their pins */
RotaryEncOverMCP rotaryEncoders[] = {
    // outputA,B on GPB7,GPB6, register with callback and ID=1 (GPB0=7 .. GPB7=15)
    RotaryEncOverMCP(&mcp, 3, 2, button_pins[1], &RotaryEncoderChanged, &RotaryButton, 2),
    // outputA,B on GPA0,GPA1, register with callback and ID=2
    RotaryEncOverMCP(&mcp, 1, 0, button_pins[0], &RotaryEncoderChanged, &RotaryButton, 1)
};
constexpr int numEncoders = (int)(sizeof(rotaryEncoders) / sizeof(*rotaryEncoders));

void RotaryEncoderChanged(bool clockwise, int id) {
    Serial.println("Encoder " + String(id) + " "
            + (clockwise ? String("clockwise") : String("counter-clockwise")));
}

void RotaryButton(bool pressed, int id){
    if(pressed)
      Serial.println("Button " + String(id+63) + " pressed");
      else return;
}

void setup(){
    Serial.begin(115200);
    Serial.println("MCP23017 Interrupt Test");
    Serial.setTimeout(1);
    pinMode(arduinoIntPin,INPUT);

    mcp.begin();      // use default address 0
    mcp.readINTCAPAB(); //read this so that the interrupt is cleared

    //initialize all rotary encoders

    //Setup interrupts, OR INTA, INTB together on both ports.
    //thus we will receive an interrupt if something happened on
    //port A or B with only a single INT connection.
    mcp.setupInterrupts(true,false,LOW);

    //Initialize input encoders (pin mode, interrupt)
    for(int i=0; i < numEncoders; i++) {
        rotaryEncoders[i].init();
    }

    attachInterrupt(digitalPinToInterrupt(arduinoIntPin), intCallBack, CHANGE);
}

// The int handler will just signal that the int has happened
// we will do the work from the main loop.
void INTERRUPT_FUNC_ATTRIB intCallBack() {
    awakenByInterrupt=true;
}

void checkInterrupt() {
    if(awakenByInterrupt) {
        // disable interrupts while handling them.
        detachInterrupt(digitalPinToInterrupt(arduinoIntPin));
        handleInterrupt();
        attachInterrupt(digitalPinToInterrupt(arduinoIntPin),intCallBack,FALLING);
    }
}

void handleInterrupt(){
    //Read the entire state when the interrupt occurred

    //An interrupt occurred on some MCP object.
    //since all of them are ORed together, we don't
    //know exactly which one has fired.
    //just read all of them, pre-emptively.

    for(int j = 0; j < numMCPs; j++) {
        uint16_t gpioAB = allMCPs[j]->readINTCAPAB();
        // we need to read GPIOAB to clear the interrupt actually.        
        volatile uint16_t dummy = allMCPs[j]->readGPIOAB();
        for (int i=0; i < numEncoders; i++) {
            //only feed this in the encoder if this
            //is coming from the correct MCP
            if(rotaryEncoders[i].getMCP() == allMCPs[j])
                rotaryEncoders[i].feedInput(gpioAB);
        }
    }
    cleanInterrupts();
}

// handy for interrupts triggered by buttons
// normally signal a few due to bouncing issues
void cleanInterrupts(){
#ifdef __AVR__
    EIFR=0x01;
#endif
    awakenByInterrupt=false;
}

void loop() {
    //Check if an interrupt has occurred and act on it
    checkInterrupt();
}
cnc4less commented 2 years ago

i used your modified code with https://github.com/MBilge/Rotary-Encoder-Development-Board-v1.2 yes works fine after i changed the pins to pull-up but when i turn them fast the esp32 will stop reading, works fine if i turn the encoders slowly.

maxgerhardt commented 2 years ago

Can you reproduce this behavior with the basic polling or interrupt examples here?

cnc4less commented 2 years ago

Your code with buffering is working fine

include

include

include

include

include

include

/ Our I2C MCP23017 GPIO expanders / Adafruit_MCP23017 mcp;

//Array of pointers of all MCPs if there is more than one Adafruit_MCP23017 allMCPs[] = { &mcp }; constexpr int numMCPs = (int)(sizeof(allMCPs) / sizeof(allMCPs));

enum class RotaryEvent : uint8_t { RotationClockwise = 0, RotationCounterClockwise = 1, ButtonOff = 0, ButtonOn = 1, // ButtonOn = 1;

};

/ can record up to 64 events / struct RotaryEncoderEventCollection { int id; / stores rotary encoders ID / uint64_t events; / bit mask stores event. 0 = clockwise, 1 = coutner-clock / size_t numEvents; / stores number of valid event bits / bool dirty; / true if the value has changed in regards to the last readout / bool overflow; / true if more than the maximum number of allowed events were reached / };

class RotaryEncodersEventBufferer { public: void registerEncoder(int encoderId) { rotaryIDToEvents[encoderId] = RotaryEncoderEventCollection{ encoderId }; }

void registerEncoders(RotaryEncOverMCP* encoders, int numEncoders) {
    for(int i=0; i < numEncoders; i++) {
        registerEncoder(encoders[i].getID());
    }
}

/* initializes a mutex to prevent data races */
void init() { dataLock = xSemaphoreCreateRecursiveMutex(); }
void lock() { xSemaphoreTakeRecursive(dataLock, portMAX_DELAY); }
void unlock() { xSemaphoreGiveRecursive(dataLock); }

void recordEvent(bool clockwise, int id) {
    lock();
    if(!rotaryIDToEvents.count(id)) {
        unlock();
        return;
    }
    auto record = rotaryIDToEvents[id];
    if(record.numEvents == 64) {
        //couldn't save it
        record.overflow = true;
    } else {
        if(!clockwise) 
            record.events |= (uint64_t) (1ull << record.numEvents);
        record.numEvents++;
        record.dirty = true;
    }
    //save back
    rotaryIDToEvents[id] = record;
    unlock();
    Serial.println("[Bufferer] Recorded event for ID = " + String(id) 
        + ": " + (((record.events & (1ull << (record.numEvents - 1))) == 0) ? String("clockwise") : String("counter-clock-wise")));
}

std::vector<RotaryEvent> getEvents(int id, bool& overflow) {
    lock();
    std::vector<RotaryEvent> events {};
    if(!rotaryIDToEvents.count(id)) {
        unlock();
        return events;
    }
    auto record = rotaryIDToEvents[id];
    if(record.dirty) {
        for(int i=0; i < record.numEvents; i++) {
            RotaryEvent evt = ((record.events & (1ull << i)) == 0) 
                ? RotaryEvent::RotationClockwise : RotaryEvent::RotationCounterClockwise;  
            events.push_back(evt);
        }
        //reset read info.
        record.dirty = false;
        overflow = record.overflow;
        record.overflow = false;
        record.numEvents = 0;
        record.events = 0ul;
        rotaryIDToEvents[id] = record;
    }
    unlock();
    return events;
}
//funciton without overflow info (shortcut)
std::vector<RotaryEvent> getEvents(int id) { bool dummy = false; return getEvents(id, dummy); }

std::vector<std::tuple<int, std::vector<RotaryEvent>>> getAllEvents() {
    std::vector<std::tuple<int, std::vector<RotaryEvent>>> events {};
    lock();
    for (const auto &p : rotaryIDToEvents)
    {
        auto new_events = getEvents(p.first);
        if(new_events.size() != 0)
            events.push_back(std::make_tuple(p.first, new_events));
    } 
    unlock();
    return events;
}

private: std::map<int, RotaryEncoderEventCollection> rotaryIDToEvents; SemaphoreHandle_t dataLock; };

/* the INT pin of the MCP can only be connected to

/ semaphore that will be used for reading of the rotary encoder / SemaphoreHandle_t rotaryISRSemaphore = nullptr;

/ function prototypes / void intCallBack(); void cleanInterrupts(); void handleInterrupt(); void RotaryEncoderChanged(bool clockwise, int id); void rotaryReaderTask(void* pArgs);

/ Array of all rotary encoders and their pins / RotaryEncOverMCP rotaryEncoders[] = { // outputA,B on GPA6,GPB1, register with callback and ID=1 RotaryEncOverMCP(&mcp, 6, 5, 7, &RotaryEncoderChanged, 1), // outputA,B on GPA0,GPA1, register with callback and ID=2 RotaryEncOverMCP(&mcp, 3, 2, 4, &RotaryEncoderChanged, 2), RotaryEncOverMCP(&mcp, 10, 9, 8, &RotaryEncoderChanged, 3), RotaryEncOverMCP(&mcp, 12, 13, 11, &RotaryEncoderChanged, 4), RotaryEncOverMCP(&mcp, 15, 1, 14, &RotaryEncoderChanged, 5)

}; constexpr int numEncoders = (int)(sizeof(rotaryEncoders) / sizeof(*rotaryEncoders));

RotaryEncodersEventBufferer rotaryEncodersEventBufferer;

void RotaryEncoderChanged(bool clockwise, int id) { //Serial.println("Encoder " + String(id) + ": " // + (clockwise ? String("clockwise") : String("counter-clock-wise"))); rotaryEncodersEventBufferer.recordEvent(clockwise, id); }

void setup(){

Serial.begin(9600);
Serial.println("MCP23007 Interrupt Test");

//initialize semaphore for reader task
rotaryISRSemaphore = xSemaphoreCreateBinary();

pinMode(arduinoIntPin,INPUT);

mcp.begin(0x27);      // use default address 0
mcp.readINTCAPAB(); //read this so that the interrupt is cleared

//initialize all rotary encoders

//Setup interrupts, OR INTA, INTB together on both ports.
//thus we will receive an interrupt if something happened on
//port A or B with only a single INT connection.
mcp.setupInterrupts(true,false,LOW);

//Initialize input encoders (pin mode, interrupt)
for(int i=0; i < numEncoders; i++) {
    rotaryEncoders[i].init();
}

rotaryEncodersEventBufferer.init();
rotaryEncodersEventBufferer.registerEncoders(rotaryEncoders, numEncoders);

xTaskCreatePinnedToCore(&rotaryReaderTask, "rotary reader", 2048, NULL, 20, NULL, 1);

attachInterrupt(arduinoIntPin, intCallBack, FALLING);

}

// The int handler will just signal that the int has happened // we will do the work from a task. void IRAM_ATTR intCallBack() { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xSemaphoreGiveFromISR(rotaryISRSemaphore, &xHigherPriorityTaskWoken); if(xHigherPriorityTaskWoken) { portYIELD_FROM_ISR (); } }

void rotaryReaderTask(void* pArgs) { (void)pArgs; Serial.println("Started rotary reader task."); while(true) { if(xSemaphoreTake(rotaryISRSemaphore, portMAX_DELAY) == pdPASS) { //printing slows processing down, detaching and re-attaching interrupts //doesn't seem to needed here because of the high speed of the ESP32. //Serial.println("Got Signal from interrupt"); //detachInterrupt(arduinoIntPin); handleInterrupt(); //attachInterrupt(arduinoIntPin, intCallBack, FALLING); } } }

void handleInterrupt(){ //Read the entire state when the interrupt occurred

//An interrupt occurred on some MCP object.
//since all of them are ORed together, we don't
//know exactly which one has fired.
//just read all of them, pre-emptively.

for(int j = 0; j < numMCPs; j++) {
    uint16_t gpioAB = allMCPs[j]->readINTCAPAB();
    // we need to read GPIOAB to clear the interrupt actually.
    volatile uint16_t dummy = allMCPs[j]->readGPIOAB();
    (void)dummy;
    for (int i=0; i < numEncoders; i++) {
        //only feed this in the encoder if this
        //is coming from the correct MCP
        if(rotaryEncoders[i].getMCP() == allMCPs[j])
            rotaryEncoders[i].feedInput(gpioAB);
    }
}

}

void loop() { //check on buffered events prepared by the other task auto bufferedEvents = rotaryEncodersEventBufferer.getAllEvents(); Serial.println("[Main Task] Checking number of changed encoders: " + String(bufferedEvents.size())

cnc4less commented 2 years ago

--- Miniterm on COM5 9600,8,N,1 --- --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- MCP23007 Interrupt Test Started rotary reader task. [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Bufferer] Recorded event for ID = 1: counter-clock-wise [Main Task] Checking number of changed encoders: 1 buffered events: 1 (Buffered) Encoder 1: counter-clock-wise[Bufferer] Recorded event for ID = 1: counter-clock-wise

[Main Task] Doing some work for 2 seconds...[Bufferer] Recorded event for ID = 1: counter-clock-wise

[Bufferer] Recorded event for ID = 1: counter-clock-wise [Bufferer] Recorded event for ID = 1: counter-clock-wise [Main Task] Checking number of changed encoders: 1 buffered events: 4 (Buffered) Encoder 1: counter-clock-wise (Buffered) Encoder 1: counter-clock-wise (Buffered) Encoder 1: counter-clock-wise (Buffered) Encoder 1: counter-clock-wise [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0 [Main Task] Doing some work for 2 seconds... [Main Task] Checking number of changed encoders: 0 buffered events: 0

cnc4less commented 2 years ago

working buttons

/*

ifndef H_NCOVERMCPH

define H_YENCOVERMCPH

/ Describes new objects based on the Rotary and Adafruit MCP23017 library /

include

include

/ function pointer definition / typedef void (rotaryActionFunc)(bool clockwise, int id); /new button def/ typedef void (rotaryButtonFunc)(bool pressed, int id); /* We describe an object in which we instantiate a rotary encoder

private: Rotary rot; / the rotary object which will be created/ Adafruit_MCP23017 mcp = nullptr; / pointer the I2C GPIO expander it's connected to / uint8_t pinA = 0; uint8_t pinB = 0; / the pin numbers for output A and output B / uint8_t pinC = 0; rotaryActionFunc actionFunc = nullptr; / function pointer, will be called when there is an action happening / rotaryButtonFunc buttonFunc = nullptr; int id = 0; / optional ID for identification */ };

endif / SRC_ENCOVERMCPH /

cnc4less commented 2 years ago

code #include

include

include

include

include

include

/ Our I2C MCP23017 GPIO expanders / Adafruit_MCP23017 mcp;

//Array of pointers of all MCPs if there is more than one Adafruit_MCP23017 allMCPs[] = { &mcp }; constexpr int numMCPs = (int)(sizeof(allMCPs) / sizeof(allMCPs));

enum class RotaryEvent : uint8_t { RotationClockwise = 0, RotationCounterClockwise = 1, ButtonOff = 0, ButtonOn = 1, // ButtonOn = 1;

};

/ can record up to 64 events / struct RotaryEncoderEventCollection { int id; / stores rotary encoders ID / uint64_t events; / bit mask stores event. 0 = clockwise, 1 = coutner-clock / size_t numEvents; / stores number of valid event bits / bool dirty; / true if the value has changed in regards to the last readout / bool overflow; / true if more than the maximum number of allowed events were reached / };

class RotaryEncodersEventBufferer { public: void registerEncoder(int encoderId) { rotaryIDToEvents[encoderId] = RotaryEncoderEventCollection{ encoderId }; }

void registerEncoders(RotaryEncOverMCP* encoders, int numEncoders) {
    for(int i=0; i < numEncoders; i++) {
        registerEncoder(encoders[i].getID());
    }
}

void RotaryButton(bool pressed, int id)
{
    if (pressed)
        Serial.println("Button " + String(id + 63) + " pressed");
    else
        return;
}
/* initializes a mutex to prevent data races */
void init() { dataLock = xSemaphoreCreateRecursiveMutex(); }
void lock() { xSemaphoreTakeRecursive(dataLock, portMAX_DELAY); }
void unlock() { xSemaphoreGiveRecursive(dataLock); }

void recordEvent(bool clockwise, int id) {
    lock();
    if(!rotaryIDToEvents.count(id)) {
        unlock();
        return;
    }
    auto record = rotaryIDToEvents[id];
    if(record.numEvents == 64) {
        //couldn't save it
        record.overflow = true;
    } else {
        if(!clockwise) 
            record.events |= (uint64_t) (1ull << record.numEvents);
        record.numEvents++;
        record.dirty = true;
    }
    //save back
    rotaryIDToEvents[id] = record;
    unlock();
    Serial.println("[Bufferer] Recorded event for ID = " + String(id) 
        + ": " + (((record.events & (1ull << (record.numEvents - 1))) == 0) ? String("clockwise") : String("counter-clock-wise")));
}

std::vector<RotaryEvent> getEvents(int id, bool& overflow) {
    lock();
    std::vector<RotaryEvent> events {};
    if(!rotaryIDToEvents.count(id)) {
        unlock();
        return events;
    }
    auto record = rotaryIDToEvents[id];
    if(record.dirty) {
        for(int i=0; i < record.numEvents; i++) {
            RotaryEvent evt = ((record.events & (1ull << i)) == 0) 
                ? RotaryEvent::RotationClockwise : RotaryEvent::RotationCounterClockwise;  
            events.push_back(evt);
        }
        //reset read info.
        record.dirty = false;
        overflow = record.overflow;
        record.overflow = false;
        record.numEvents = 0;
        record.events = 0ul;
        rotaryIDToEvents[id] = record;
    }
    unlock();
    return events;
}
//funciton without overflow info (shortcut)
std::vector<RotaryEvent> getEvents(int id) { bool dummy = false; return getEvents(id, dummy); }

std::vector<std::tuple<int, std::vector<RotaryEvent>>> getAllEvents() {
    std::vector<std::tuple<int, std::vector<RotaryEvent>>> events {};
    lock();
    for (const auto &p : rotaryIDToEvents)
    {
        auto new_events = getEvents(p.first);
        if(new_events.size() != 0)
            events.push_back(std::make_tuple(p.first, new_events));
    } 
    unlock();
    return events;
}

private: std::map<int, RotaryEncoderEventCollection> rotaryIDToEvents; SemaphoreHandle_t dataLock; };

/* the INT pin of the MCP can only be connected to

/ semaphore that will be used for reading of the rotary encoder / SemaphoreHandle_t rotaryISRSemaphore = nullptr;

/ function prototypes / void intCallBack(); void cleanInterrupts(); void handleInterrupt(); void RotaryEncoderChanged(bool clockwise, int id); void rotaryReaderTask(void* pArgs); void RotaryButton(bool pressed, int id);

/ Array of all rotary encoders and their pins / RotaryEncOverMCP rotaryEncoders[] = { // outputA,B on GPA6,GPB1, register with callback and ID=1 RotaryEncOverMCP(&mcp, 6, 5, 7, &RotaryEncoderChanged, &RotaryButton, 1), // outputA,B on GPA0,GPA1, register with callback and ID=2 RotaryEncOverMCP(&mcp, 3, 2, 4, &RotaryEncoderChanged, &RotaryButton, 2), RotaryEncOverMCP(&mcp, 10, 9, 8, &RotaryEncoderChanged, &RotaryButton, 3), RotaryEncOverMCP(&mcp, 12, 13, 11, &RotaryEncoderChanged, &RotaryButton, 4), RotaryEncOverMCP(&mcp, 15, 1, 14, &RotaryEncoderChanged, &RotaryButton,5)

}; constexpr int numEncoders = (int)(sizeof(rotaryEncoders) / sizeof(*rotaryEncoders));

RotaryEncodersEventBufferer rotaryEncodersEventBufferer;

void RotaryEncoderChanged(bool clockwise, int id) { //Serial.println("Encoder " + String(id) + ": " // + (clockwise ? String("clockwise") : String("counter-clock-wise"))); rotaryEncodersEventBufferer.recordEvent(clockwise, id); }

void RotaryButton(bool pressed, int id) { if (pressed) Serial.println("Button " + String(id + 63) + " pressed"); else return; }

void setup()
{

    Serial.begin(9600);
    Serial.println("MCP23007 Interrupt Test");

    // initialize semaphore for reader task
    rotaryISRSemaphore = xSemaphoreCreateBinary();

    pinMode(arduinoIntPin, INPUT);

    mcp.begin(0x27);    // use default address 0
    mcp.readINTCAPAB(); // read this so that the interrupt is cleared

    // initialize all rotary encoders

    // Setup interrupts, OR INTA, INTB together on both ports.
    // thus we will receive an interrupt if something happened on
    // port A or B with only a single INT connection.
    mcp.setupInterrupts(true, false, LOW);

    // Initialize input encoders (pin mode, interrupt)
    for (int i = 0; i < numEncoders; i++)
    {
        rotaryEncoders[i].init();
    }

    rotaryEncodersEventBufferer.init();
    rotaryEncodersEventBufferer.registerEncoders(rotaryEncoders, numEncoders);

    xTaskCreatePinnedToCore(&rotaryReaderTask, "rotary reader", 2048, NULL, 20, NULL, 1);

    attachInterrupt(arduinoIntPin, intCallBack, FALLING);
}

// The int handler will just signal that the int has happened
// we will do the work from a task.
void IRAM_ATTR intCallBack()
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(rotaryISRSemaphore, &xHigherPriorityTaskWoken);
    if (xHigherPriorityTaskWoken)
    {
        portYIELD_FROM_ISR();
    }
}

void rotaryReaderTask(void *pArgs)
{
    (void)pArgs;
    Serial.println("Started rotary reader task.");
    while (true)
    {
        if (xSemaphoreTake(rotaryISRSemaphore, portMAX_DELAY) == pdPASS)
        {
            // printing slows processing down, detaching and re-attaching interrupts
            // doesn't seem to needed here because of the high speed of the ESP32.
            // Serial.println("Got Signal from interrupt");
            // detachInterrupt(arduinoIntPin);
            handleInterrupt();
            // attachInterrupt(arduinoIntPin, intCallBack, FALLING);
        }
    }
}

void handleInterrupt()
{
    // Read the entire state when the interrupt occurred

    // An interrupt occurred on some MCP object.
    // since all of them are ORed together, we don't
    // know exactly which one has fired.
    // just read all of them, pre-emptively.

    for (int j = 0; j < numMCPs; j++)
    {
        uint16_t gpioAB = allMCPs[j]->readINTCAPAB();
        // we need to read GPIOAB to clear the interrupt actually.
        volatile uint16_t dummy = allMCPs[j]->readGPIOAB();
        (void)dummy;
        for (int i = 0; i < numEncoders; i++)
        {
            // only feed this in the encoder if this
            // is coming from the correct MCP
            if (rotaryEncoders[i].getMCP() == allMCPs[j])
                rotaryEncoders[i].feedInput(gpioAB);
        }
    }
}

void loop()
{
    // check on buffered events prepared by the other task
    auto bufferedEvents = rotaryEncodersEventBufferer.getAllEvents();
    Serial.println("[Main Task] Checking number of changed encoders: " + String(bufferedEvents.size()) + String(" buffered events: ") + String(std::accumulate(bufferedEvents.begin(), bufferedEvents.end(), 0, [&](int b, std::tuple<int, std::vector<RotaryEvent>> a)
                                                                                                                                                               { return std::get<1>(a).size() + b; })));

    for (std::tuple<int, std::vector<RotaryEvent>> &event : bufferedEvents)
    {
        int encoderId = std::get<0>(event);
        for (RotaryEvent turnEvent : std::get<1>(event))
        {
            Serial.println("(Buffered) Encoder " + String(encoderId) + ": " + (turnEvent == RotaryEvent::RotationClockwise ? String("clockwise") : String("counter-clock-wise")));
            // can do application logic here
        }
    }

    // we can do work here that is time-intensive and blocking, since all encoder events
    //  will be buffered by the managed rotary encoder class.
    Serial.println("[Main Task] Doing some work for 2 seconds...");
    delay(2000);
}
cnc4less commented 2 years ago

see where u can buy it , thank you

https://www.ebay.com/itm/174625985977