Open DocLeoCC opened 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.
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_ */
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();
}
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.
Can you reproduce this behavior with the basic polling or interrupt examples here?
Your code with buffering is working fine
/ 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())
String(" buffered events: ") + String(
std::accumulate(bufferedEvents.begin(),bufferedEvents.end(), 0,
[&](int b, std::tuple<int, std::vector
for(std::tuple<int, std::vector
//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); }
--- 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
working buttons
/*
*/
/ Describes new objects based on the Rotary and Adafruit MCP23017 library /
/ 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
/ 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,pinC), 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, 1); // enable pullup on this pin mcp->setupInterruptPin(pinA, CHANGE); mcp->pinMode(pinB, INPUT); mcp->pullUp(pinB, 1); // disable pullup on this pin mcp->setupInterruptPin(pinB, CHANGE); // attempting to insert switch pin mcp->pinMode(pinC, INPUT); mcp->pullUp(pinC, 1); 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, pinValC); 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 */ };
code #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);
}
see where u can buy it , thank you
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.