/* ---------------------------- 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;