diff --git a/src/main.cpp b/src/main.cpp index 2db10f9..06d5ac9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,10 +8,10 @@ * GND O O GND * 5V O O 5V * 3V3 O O 3V3 - * GND O < 36 + * GND O < 36 ROTARY_BTN * RX * * 17 - * TX * < 38 - * RST * BUTTON < 39 + * TX * < 38 ROTARY_A + * RST * BUTTON < 39 ROTARY_B * 0 * < 34 * 22 * < 35 * BME280_SDO 19 * LoRa_MISO * 32 @@ -44,6 +44,7 @@ #include "mp3.h" #include "BME280.h" #include "image.h" +#include "rotary.h" U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16); @@ -56,6 +57,7 @@ char timeStr[20]; BME280 bme280; MP3 mp3; +Rotary rotary; uint32_t lastButtonPress = 0; @@ -64,6 +66,9 @@ static const int buttonPin = 0; static const int ResetPin = 17; static const int SensorPin = 32; static const int BatteryPin = 34; +static const int rotaryPinA = 38; +static const int rotaryPinB = 39; +static const int rotaryPinButton = 36; int sensorValue = 0; //The batteryLimit defines the point at which the battery is considered empty. int batteryLimit = 3300; @@ -169,6 +174,9 @@ void setup() { mp3.begin(); u8g2.setContrast(127); + + rotary.registerCallback(nullptr); + rotary.begin(rotaryPinA, rotaryPinB, rotaryPinButton); } diff --git a/src/mp3.cpp b/src/mp3.cpp index f1aa5a5..83e9fa4 100644 --- a/src/mp3.cpp +++ b/src/mp3.cpp @@ -69,7 +69,7 @@ void MP3::mp3_decoder_task(void *pvParameters) { decoder = new AudioGeneratorMP3(); decoder->RegisterStatusCB(StatusCallback, (void*)"mp3"); decoder->begin(buff, out); - out->SetGain(1.0); + out->SetGain(0.2); playing = true; while(decoder->isRunning()) { @@ -113,7 +113,7 @@ bool MP3::begin() { // First, preallocate all the memory needed for the buffering and codecs, never to be freed preallocateBuffer = malloc(preallocateBufferSize); - if (!preallocateBuffer || !preallocateCodec) { + if (!preallocateBuffer) { Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize); return false; } diff --git a/src/rotary.cpp b/src/rotary.cpp new file mode 100644 index 0000000..d6d3970 --- /dev/null +++ b/src/rotary.cpp @@ -0,0 +1,158 @@ + /* interrupt routine for Rotary Encoders + tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de + and a few others, seems pretty universal + + The average rotary encoder has three pins, seen from front: A C B + Clockwise rotation A(on)->B(on)->A(off)->B(off) + CounterCW rotation B(on)->A(on)->B(off)->A(off) + + and may be a push switch with another two pins, pulled low at pin 8 in this case + raf@synapps.de 20120107 + +*/ + +#include "rotary.h" + +//using namespace std; + +TaskHandle_t Rotary::xTaskToNotify = NULL; + +Rotary::Rotary() {} + +bool Rotary::begin(uint8_t pinA, uint8_t pinB, uint8_t pinButton) { + this->pinA = pinA; + this->pinB = pinB; + this->pinButton = pinButton; + pinMode(pinA, INPUT_PULLUP); + pinMode(pinB, INPUT_PULLUP); + pinMode(pinButton, INPUT_PULLUP); + + xTaskCreate( + &cTaskWrapper, /* Task function. */ + "buttonTask", /* String with name of task. */ + 2048, /* Stack size in words. */ + this, /* Parameter passed as input of the task */ + tskIDLE_PRIORITY+2, /* Priority of the task. */ + &buttonTaskHandle); /* Task handle. */ + return true; +} + +void Rotary::cTaskWrapper(void* parameters) { + static_cast(parameters)->button_task(NULL); +} + +void Rotary::button_task(void *pvParameters) { + xTaskToNotify = xTaskGetCurrentTaskHandle(); + + attachInterrupt(digitalPinToInterrupt(pinA), doEncoderA, CHANGE); + attachInterrupt(digitalPinToInterrupt(pinB), doEncoderB, CHANGE); +// attachInterrupt(digitalPinToInterrupt(pinButton), doButton, CHANGE); + + uint32_t ulNotificationValue; + + while(true) { + + ulNotificationValue = xTaskNotifyWait(0x00, ULONG_MAX, &ulNotificationValue, portMAX_DELAY ); + Serial.printf("interrupt %d, rotating: %d\n", ulNotificationValue, rotating); + + if( ulNotificationValue == 1 ) { + // debounce + if ( rotating ) delay (1); // wait a little until the bouncing is done + + // Test transition, did things really change? + if ( digitalRead(pinA) != A_set ) { // debounce once more + A_set = !A_set; + + // adjust counter + if A leads B + if ( A_set && !B_set ) + encoderPos += 1; + + rotating = false; // no more debouncing until loop() hits again + changed(); + } + + } else if ( ulNotificationValue == 2 ) { + if ( rotating ) delay (1); + if ( digitalRead(pinB) != B_set ) { + B_set = !B_set; + // adjust counter - 1 if B leads A + if ( B_set && !A_set ) + encoderPos -= 1; + + rotating = false; + changed(); + } + } else if ( ulNotificationValue == 4 ) { + delay(1); + if ( digitalRead(pinButton) != Button_set ) { + Button_set = !Button_set; + + if (! Button_set ) { + encoderPos = 0; + } + } + } + + } +} + +/* +// main loop, work is done by interrupt service routines, this one only prints stuff +void loop() { + rotating = true; // reset the debouncer + + if (lastReportedPos != encoderPos) { + Serial.print("Index:"); + Serial.println(encoderPos, DEC); + lastReportedPos = encoderPos; + } + if (digitalRead(clearButton) == LOW ) { + encoderPos = 0; + } +} +*/ + +void Rotary::changed() { + if (lastReportedPos != encoderPos) { + Serial.print("encoder: "); + Serial.println(encoderPos); +// if (callback) callback(encoderPos); + lastReportedPos = encoderPos; + } + rotating = true; // reset the debouncer +} + +bool Rotary::registerCallback(std::function callback) { + this->callback = callback; + return true; +} + +void Rotary::doButton() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + configASSERT( xTaskToNotify != NULL ); + uint32_t intrBits = 4; + xTaskNotifyFromISR( xTaskToNotify, intrBits, eSetValueWithOverwrite, &xHigherPriorityTaskWoken ); + portYIELD_FROM_ISR(); //portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); +} + +// Interrupt on A changing state +void Rotary::doEncoderA() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + configASSERT( xTaskToNotify != NULL ); + uint32_t intrBits = 1; + xTaskNotifyFromISR( xTaskToNotify, intrBits, eSetValueWithOverwrite, &xHigherPriorityTaskWoken ); + portYIELD_FROM_ISR(); //portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); +} + +// Interrupt on B changing state, same as A above +void Rotary::doEncoderB() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + configASSERT( xTaskToNotify != NULL ); + uint32_t intrBits = 2; + xTaskNotifyFromISR( xTaskToNotify, intrBits, eSetValueWithOverwrite, &xHigherPriorityTaskWoken ); + portYIELD_FROM_ISR(); //portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); +} + diff --git a/src/rotary.cpp.txt b/src/rotary.cpp.txt deleted file mode 100644 index d626e21..0000000 --- a/src/rotary.cpp.txt +++ /dev/null @@ -1,90 +0,0 @@ - /* interrupt routine for Rotary Encoders - tested with Noble RE0124PVB 17.7FINB-24 http://www.nobleusa.com/pdf/xre.pdf - available at pollin.de - and a few others, seems pretty universal - - The average rotary encoder has three pins, seen from front: A C B - Clockwise rotation A(on)->B(on)->A(off)->B(off) - CounterCW rotation B(on)->A(on)->B(off)->A(off) - - and may be a push switch with another two pins, pulled low at pin 8 in this case - raf@synapps.de 20120107 - -*/ - -// usually the rotary encoders three pins have the ground pin in the middle -enum PinAssignments { - encoderPinA = 2, // right - encoderPinB = 3, // left - clearButton = 8 // another two pins -}; - -volatile unsigned int encoderPos = 0; // a counter for the dial -unsigned int lastReportedPos = 1; // change management -static boolean rotating = false; // debounce management - -// interrupt service routine vars -boolean A_set = false; -boolean B_set = false; - - -void setup() { - pinMode(encoderPinA, INPUT); - pinMode(encoderPinB, INPUT); - pinMode(clearButton, INPUT); - // turn on pullup resistors - digitalWrite(encoderPinA, HIGH); - digitalWrite(encoderPinB, HIGH); - digitalWrite(clearButton, HIGH); - - // encoder pin on interrupt 0 (pin 2) - attachInterrupt(0, doEncoderA, CHANGE); - // encoder pin on interrupt 1 (pin 3) - attachInterrupt(1, doEncoderB, CHANGE); - - Serial.begin(9600); // output -} - -// main loop, work is done by interrupt service routines, this one only prints stuff -void loop() { - rotating = true; // reset the debouncer - - if (lastReportedPos != encoderPos) { - Serial.print("Index:"); - Serial.println(encoderPos, DEC); - lastReportedPos = encoderPos; - } - if (digitalRead(clearButton) == LOW ) { - encoderPos = 0; - } -} - -// Interrupt on A changing state -void doEncoderA() { - // debounce - if ( rotating ) delay (1); // wait a little until the bouncing is done - - // Test transition, did things really change? - if ( digitalRead(encoderPinA) != A_set ) { // debounce once more - A_set = !A_set; - - // adjust counter + if A leads B - if ( A_set && !B_set ) - encoderPos += 1; - - rotating = false; // no more debouncing until loop() hits again - } -} - -// Interrupt on B changing state, same as A above -void doEncoderB() { - if ( rotating ) delay (1); - if ( digitalRead(encoderPinB) != B_set ) { - B_set = !B_set; - // adjust counter - 1 if B leads A - if ( B_set && !A_set ) - encoderPos -= 1; - - rotating = false; - } -} - diff --git a/src/rotary.h b/src/rotary.h new file mode 100644 index 0000000..242d97e --- /dev/null +++ b/src/rotary.h @@ -0,0 +1,41 @@ +#ifndef _ROTARY_H +#define _ROTARY_H + +#include +#include + +// usually the rotary encoders three pins have the ground pin in the middle +enum PinAssignments { + encoderPinA = 2, // right + encoderPinB = 3, // left + clearButton = 8 // another two pins +}; + +class Rotary { + public: + Rotary(); + bool begin(uint8_t pinA, uint8_t pinB, uint8_t pinButton); + bool registerCallback(std::function callback); + static void IRAM_ATTR doEncoderA(void); + static void IRAM_ATTR doEncoderB(void); + static void IRAM_ATTR doButton(void); + static TaskHandle_t xTaskToNotify; + private: + volatile unsigned int encoderPos = 0; // a counter for the dial + unsigned int lastReportedPos = 1; // change management + boolean rotating = false; // debounce management + // interrupt service routine vars + boolean A_set = false; + boolean B_set = false; + boolean Button_set = false; + uint8_t pinA; + uint8_t pinB; + uint8_t pinButton; + void changed(void); + std::function callback = nullptr; + TaskHandle_t buttonTaskHandle; + void button_task(void*); + static void cTaskWrapper(void*); +}; + +#endif /* _ROTARY_H */