|
@ -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 <paul@pjrc.com> |
|
|
|
|
|
* |
|
|
|
|
|
* 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; |