diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 2ec66f46..0b0e157a 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -335,6 +335,10 @@ #define ENCODER_SUPPORT 0 #endif +#ifndef ENCODER_MINIMUM_DELTA +#define ENCODER_MINIMUM_DELTA 1 +#endif + //------------------------------------------------------------------------------ // LED //------------------------------------------------------------------------------ diff --git a/code/espurna/config/hardware.h b/code/espurna/config/hardware.h index 6148c6f3..5008f428 100644 --- a/code/espurna/config/hardware.h +++ b/code/espurna/config/hardware.h @@ -3698,6 +3698,7 @@ #define LIGHT_CHANNELS 1 #define LIGHT_CH1_PIN 5 #define LIGHT_CH1_INVERSE 0 + #define ENCODER_SUPPORT 1 // A bit of HLW8012 - pins 6,7,8 #ifndef HLW8012_SUPPORT diff --git a/code/espurna/encoder.ino b/code/espurna/encoder.ino index 1836e91c..4d777524 100644 --- a/code/espurna/encoder.ino +++ b/code/espurna/encoder.ino @@ -8,7 +8,7 @@ Copyright (C) 2018-2019 by Xose PĂ©rez #if ENCODER_SUPPORT && (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) -#include +#include "libs/Encoder.h" #include typedef struct { @@ -22,6 +22,7 @@ typedef struct { } encoder_t; std::vector _encoders; +unsigned long _encoder_min_delta = 1; void _encoderConfigure() { @@ -85,24 +86,22 @@ void _encoderConfigure() { } } + _encoder_min_delta = getSetting("encMinDelta", ENCODER_MINIMUM_DELTA).toInt(); + if (!_encoder_min_delta) _encoder_min_delta = 1; + } void _encoderLoop() { - // for each encoder + // for each encoder, read delta (read()) and map button action for (unsigned char i=0; i<_encoders.size(); i++) { - // get encoder encoder_t encoder = _encoders[i]; - // read encoder long delta = encoder.encoder->read(); encoder.encoder->write(0); - if (0 == delta) continue; - - DEBUG_MSG_P(PSTR("[ENCODER] Delta: %d\n"), delta); + if ((0 == delta) || (_encoder_min_delta > abs(delta))) continue; - // action if (encoder.button_pin == GPIO_NONE) { // if there is no button, the encoder drives CHANNEL1 @@ -110,7 +109,7 @@ void _encoderLoop() { } else { - // check if button is pressed + // otherwise, use button based on encoder mode bool pressed = (digitalRead(encoder.button_pin) != encoder.button_logic); if (ENCODER_MODE_CHANNEL == encoder.mode) { diff --git a/code/espurna/libs/Encoder.h b/code/espurna/libs/Encoder.h new file mode 100644 index 00000000..7e24e1d5 --- /dev/null +++ b/code/espurna/libs/Encoder.h @@ -0,0 +1,228 @@ +/* ---------------------------- Original copyright ----------------------------- + * + * Encoder Library, for measuring quadrature encoded signals + * http://www.pjrc.com/teensy/td_libs_Encoder.html + * Copyright (c) 2011,2013 PJRC.COM, LLC - Paul Stoffregen + * + * Version 1.2 - fix -2 bug in C-only code + * Version 1.1 - expand to support boards with up to 60 interrupts + * Version 1.0 - initial release + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * ----------------------------------------------------------------------------- + * + * Encoder.h, updated for ESP8266 use in ESPurna. Other hardware is not supported. + * + * - Added ESP-specific attributes to ISR handlers to place them in IRAM. + * - Reduced per-encoder structure sizes - only 5 Encoders can be used on ESP8266, + * and we can directly reference pin number instead of storing both register and bitmask + * + */ + +#pragma once + +// _______ _______ +// Pin1 ______| |_______| |______ Pin1 +// negative <--- _______ _______ __ --> positive +// Pin2 __| |_______| |_______| Pin2 + +// new new old old +// pin2 pin1 pin2 pin1 Result +// ---- ---- ---- ---- ------ +// 0 0 0 0 no movement +// 0 0 0 1 +1 +// 0 0 1 0 -1 +// 0 0 1 1 +2 (assume pin1 edges only) +// 0 1 0 0 -1 +// 0 1 0 1 no movement +// 0 1 1 0 -2 (assume pin1 edges only) +// 0 1 1 1 +1 +// 1 0 0 0 +1 +// 1 0 0 1 -2 (assume pin1 edges only) +// 1 0 1 0 no movement +// 1 0 1 1 -1 +// 1 1 0 0 +2 (assume pin1 edges only) +// 1 1 0 1 -1 +// 1 1 1 0 +1 +// 1 1 1 1 no movement + +namespace EncoderLibrary { + + typedef struct { + uint8_t pin1; + uint8_t pin2; + uint8_t state; + int32_t position; + } encoder_values_t; + + constexpr const unsigned char ENCODERS_MAXIMUM {5u}; + + encoder_values_t * EncoderValues[ENCODERS_MAXIMUM] = {nullptr}; + + uint8_t _encoderFindStorage() { + for (uint8_t i = 0; i < ENCODERS_MAXIMUM; i++) { + if (EncoderValues[i] == nullptr) { + return i; + } + } + return ENCODERS_MAXIMUM; + } + + void _encoderCleanStorage(uint8_t pin1, uint8_t pin2) { + for (uint8_t i = 0; i < ENCODERS_MAXIMUM; i++) { + if (EncoderValues[i] == nullptr) continue; + if (((EncoderValues[i])->pin1 == pin1) && ((EncoderValues[i])->pin2 == pin2)) { + EncoderValues[i] = nullptr; + break; + } + } + } + + // update() is not meant to be called from outside Encoder, + // but it is public to allow static interrupt routines. + void ICACHE_RAM_ATTR update(encoder_values_t *target) { + uint8_t p1val = GPIP(target->pin1); + uint8_t p2val = GPIP(target->pin2); + uint8_t state = target->state & 3; + if (p1val) state |= 4; + if (p2val) state |= 8; + target->state = (state >> 2); + switch (state) { + case 1: case 7: case 8: case 14: + target->position++; + return; + case 2: case 4: case 11: case 13: + target->position--; + return; + case 3: case 12: + target->position += 2; + return; + case 6: case 9: + target->position -= 2; + return; + } + } + + // 2 pins per encoder, 1 isr per encoder + void ICACHE_RAM_ATTR isr0() { update(EncoderValues[0]); } + void ICACHE_RAM_ATTR isr1() { update(EncoderValues[1]); } + void ICACHE_RAM_ATTR isr2() { update(EncoderValues[2]); } + void ICACHE_RAM_ATTR isr3() { update(EncoderValues[3]); } + void ICACHE_RAM_ATTR isr4() { update(EncoderValues[4]); } + + constexpr void (*_isr_funcs[5])() = { + isr0, isr1, isr2, isr3, isr4 + }; + + class Encoder { + + private: + + encoder_values_t values; + + public: + + Encoder(uint8_t pin1, uint8_t pin2) { + + values.pin1 = pin1; + values.pin2 = pin2; + + pinMode(values.pin1, INPUT_PULLUP); + pinMode(values.pin2, INPUT_PULLUP); + + values.position = 0; + + // allow time for a passive R-C filter to charge + // through the pullup resistors, before reading + // the initial state + delayMicroseconds(2000); + + uint8_t current = 0; + if (GPIP(values.pin1)) { + current |= 1; + } + + if (GPIP(values.pin2)) { + current |= 2; + } + + values.state = current; + + attach(); + + } + + ~Encoder() { + detach(); + } + + uint8_t pin1() { + return values.pin1; + } + + uint8_t pin2() { + return values.pin2; + } + + int32_t read() { + noInterrupts(); + + update(&values); + int32_t ret = values.position; + + interrupts(); + return ret; + } + + void write(int32_t position) { + noInterrupts(); + values.position = position; + interrupts(); + } + + bool attach() { + uint8_t index = _encoderFindStorage(); + if (index >= ENCODERS_MAXIMUM) return false; + + EncoderValues[index] = &values; + + attachInterrupt(values.pin1, _isr_funcs[index], CHANGE); + attachInterrupt(values.pin2, _isr_funcs[index], CHANGE); + + return true; + } + + void detach() { + noInterrupts(); + + _encoderCleanStorage(values.pin1, values.pin2); + + detachInterrupt(values.pin1); + detachInterrupt(values.pin2); + + interrupts(); + } + + + }; + +} + +using EncoderLibrary::Encoder; diff --git a/code/platformio.ini b/code/platformio.ini index d03c49d4..f6280564 100644 --- a/code/platformio.ini +++ b/code/platformio.ini @@ -86,7 +86,6 @@ lib_deps = https://github.com/xoseperez/debounceevent.git#2.0.5 https://github.com/xoseperez/eeprom_rotate#0.9.2 Embedis - Encoder https://github.com/plerup/espsoftwareserial#3.4.1 https://github.com/me-no-dev/ESPAsyncTCP#55cd520 https://github.com/me-no-dev/ESPAsyncWebServer#05306e4