This commit is contained in:
TheK4n 2021-08-15 21:28:09 +03:00
parent eb1a420979
commit 59a740b947
16 changed files with 1111 additions and 0 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Alex
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.

View File

@ -0,0 +1,149 @@
![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)
![author](https://img.shields.io/badge/author-AlexGyver-informational.svg)
# GyverEncoder
Библиотека для расширенной работы с энкодером
**ВНИМАНИЕ, БИБЛИОТЕКА УСТАРЕЛА! ИСПОЛЬЗУЙ БИБЛИОТЕКУ [EncButton](https://github.com/GyverLibs/EncButton)**
- Отработка поворота энкодера
- Отработка "нажатого поворота"
- Отработка "быстрого поворота"
- Несколько алгоритмов опроса энкодера
- Выбор подтяжки подключения энкодера
- Работа с двумя типами экнодеров
- Работа с внешним энкодером (через расширитель пинов и т.п.)
- Отработка нажатия/удержания кнопки с антидребезгом
### Совместимость
Совместима со всеми Arduino платформами (используются Arduino-функции)
### Документация
К библиотеке есть [расширенная документация](https://alexgyver.ru/encoder/)
## Содержание
- [Установка](#install)
- [Инициализация](#init)
- [Использование](#usage)
- [Пример](#example)
- [Версии](#versions)
- [Баги и обратная связь](#feedback)
<a id="install"></a>
## Установка
- Библиотеку можно найти по названию **GyverEncoder** и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- [Скачать библиотеку](https://github.com/GyverLibs/GyverEncoder/archive/refs/heads/main.zip) .zip архивом для ручной установки:
- Распаковать и положить в *C:\Program Files (x86)\Arduino\libraries* (Windows x64)
- Распаковать и положить в *C:\Program Files\Arduino\libraries* (Windows x32)
- Распаковать и положить в *Документы/Arduino/libraries/*
- (Arduino IDE) автоматическая установка из .zip: *Скетч/Подключить библиотеку/Добавить .ZIP библиотеку…* и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек [здесь](https://alexgyver.ru/arduino-first/#%D0%A3%D1%81%D1%82%D0%B0%D0%BD%D0%BE%D0%B2%D0%BA%D0%B0_%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA)
<a id="init"></a>
## Инициализация
```cpp
Encoder enc; // не привязан к пину
Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос)
Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой
Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа
Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопкой и с указанием типа
```
<a id="usage"></a>
## Использование
```cpp
void tick(); // опрос энкодера, нужно вызывать постоянно или в прерывании
void setType(boolean type); // TYPE1 / TYPE2 - тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
void setTickMode(boolean tickMode); // MANUAL / AUTO - ручной или автоматический опрос энкодера функцией tick(). (по умолчанию ручной)
void setDirection(boolean direction); // NORM / REVERSE - направление вращения энкодера
void setFastTimeout(int timeout); // установка таймаута быстрого поворота
void setPinMode(bool mode); // тип подключения энкодера, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND)
void setBtnPinMode(bool mode); // тип подключения кнопки, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND)
boolean isTurn(); // возвращает true при любом повороте, сама сбрасывается в false
boolean isRight(); // возвращает true при повороте направо, сама сбрасывается в false
boolean isLeft(); // возвращает true при повороте налево, сама сбрасывается в false
boolean isRightH(); // возвращает true при удержании кнопки и повороте направо, сама сбрасывается в false
boolean isLeftH(); // возвращает true при удержании кнопки и повороте налево, сама сбрасывается в false
boolean isFastR(); // возвращает true при быстром повороте
boolean isFastL(); // возвращает true при быстром повороте
boolean isPress(); // возвращает true при нажатии кнопки, сама сбрасывается в false
boolean isRelease(); // возвращает true при отпускании кнопки, сама сбрасывается в false
boolean isClick(); // возвращает true при нажатии и отпускании кнопки, сама сбрасывается в false
boolean isHolded(); // возвращает true при удержании кнопки, сама сбрасывается в false
boolean isHold(); // возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯ
boolean isSingle(); // возвращает true при одиночном клике (после таймаута), сама сбрасывается в false
boolean isDouble(); // возвращает true при двойном клике, сама сбрасывается в false
void resetStates(); // сбрасывает все is-флаги
```
<a id="example"></a>
## Пример
Остальные примеры смотри в **examples**!
```cpp
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW); // для работы c кнопкой
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
//if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс)
//if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick
if (enc1.isClick()) Serial.println("Click"); // одиночный клик
if (enc1.isSingle()) Serial.println("Single"); // одиночный клик (с таймаутом для двойного)
if (enc1.isDouble()) Serial.println("Double"); // двойной клик
if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки
}
```
<a id="versions"></a>
## Версии
- v3.6 от 16.09.2019 - Возвращены дефайны настроек
- v4.0 от 13.11.2019
- Оптимизирован код
- Исправлены баги
- Добавлены другие алгоритмы опроса
- Добавлена возможность полностью убрать кнопку (экономия памяти)
- Добавлена возможность подключения внешнего энкодера
- Добавлена настройка подтяжки пинов
- v4.1: Исправлено изменение подтяжек
- v4.2
- Добавлена поддержка TYPE1 для алгоритма PRECISE_ALGORITHM
- Добавлена отработка двойного клика: isSingle / isDouble
- v4.3: Исправлено ложное isSingle
- v4.4: Добавлен метод resetStates, сбрасывает все is-флаги и счётчики
- v4.5: Улучшен алгоритм BINARY_ALGORITHM (спасибо Ярославу Курусу)
- v4.6: BINARY_ALGORITHM пофикшен для TYPE1, добавлена isReleaseHold
- v4.7: Исправлен случайный нажатый поворот в BINARY_ALGORITHM
- v4.8: увеличена производительность для AVR Arduino
- v4.9: быстрый поворот отключен если кнопка удерживается
<a id="feedback"></a>
## Баги и обратная связь
При нахождении багов создавайте **Issue**, а лучше сразу пишите на почту [alex@alexgyver.ru](mailto:alex@alexgyver.ru)
Библиотека открыта для доработки и ваших **Pull Request**'ов!

View File

@ -0,0 +1,61 @@
// пример с прерываниями pinChangeInterrupt (прерывания на любом пине)
// только для ATmega328 (UNO, Nano, Pro Mini)
#define SW 0
#define DT 2
#define CLK 3
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
// настроить PCINT
attachPCINT(CLK);
attachPCINT(DT);
}
void loop() {
enc1.tick(); // оставляем тут для работы "временных" функций и антидребезга
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}
// функция для настройки PCINT для ATmega328 (UNO, Nano, Pro Mini)
uint8_t attachPCINT(uint8_t pin) {
if (pin < 8) { // D0-D7 // PCINT2
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << pin);
return 2;
}
else if (pin > 13) { //A0-A5 // PCINT1
PCICR |= (1 << PCIE1);
PCMSK1 |= (1 << pin - 14);
return 1;
}
else { // D8-D13 // PCINT0
PCICR |= (1 << PCIE0);
PCMSK0 |= (1 << pin - 8);
return 0;
}
}
// Векторы PCINT, нужно кинуть сюда тики
// не обязательно в каждый вектор, достаточно в тот, который задействован
// пины 0-7: PCINT2
// пины 8-13: PCINT0
// пины A0-A5: PCINT1
ISR(PCINT0_vect) {
//enc1.tick();
}
ISR(PCINT1_vect) {
//enc1.tick();
}
ISR(PCINT2_vect) {
enc1.tick();
}

View File

@ -0,0 +1,28 @@
/*
В последнее время китайцы стали делать одинаковые модули (ку 40)
с разными типами энкодеров - полный период и полпериода.
Если ваш энкодер ведёт себя странно (один тик считает за два поворота),
то смените тип энкодера
*/
#define CLK 4
#define DT 3
#define SW 2
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}

View File

@ -0,0 +1,30 @@
#define CLK 6
#define DT 5
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setTickMode(AUTO);
}
void loop() {
// enc1.tick(); // не нужна, в этом режиме (AUTO) она входит в каждую функцию!
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс)
if (enc1.isRelease()) Serial.println("Release"); // отпускание кнопки (+ дебаунс)
if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки
}

View File

@ -0,0 +1,47 @@
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
//Encoder enc1(CLK, DT); // для работы без кнопки
Encoder enc1(CLK, DT, SW); // для работы c кнопкой
//Encoder enc1(CLK, DT, SW, TYPE2); // для работы c кнопкой и сразу выбираем тип
//Encoder enc1(CLK, DT, ENC_NO_BUTTON, TYPE2); // для работы без кнопки и сразу выбираем тип
// Варианты инициализации:
// Encoder enc; // не привязан к пину
// Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос)
// Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой
// Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа
// Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопкой и с указанием типа
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
//if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс)
//if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick
if (enc1.isClick()) Serial.println("Click"); // одиночный клик
if (enc1.isSingle()) Serial.println("Single"); // одиночный клик (с таймаутом для двойного)
if (enc1.isDouble()) Serial.println("Double"); // двойной клик
if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки
}

View File

@ -0,0 +1,32 @@
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
int value = 0;
void setup() {
Serial.begin(9600);
enc1.setType(TYPE1); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип\=
enc1.setFastTimeout(40); // таймаут на скорость isFastR. По умолч. 50
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) value++; // если был поворот направо, увеличиваем на 1
if (enc1.isLeft()) value--; // если был поворот налево, уменьшаем на 1
if (enc1.isRightH()) value += 5; // если было удержание + поворот направо, увеличиваем на 5
if (enc1.isLeftH()) value -= 5; // если было удержание + поворот налево, уменьшаем на 5
if (enc1.isFastR()) value += 10; // если был быстрый поворот направо, увеличиваем на 10
if (enc1.isFastL()) value -= 10; // если был быстрый поворот налево, уменьшаем на 10
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
Serial.println(value); // выводим значение при повороте
}
}

View File

@ -0,0 +1,33 @@
/*
Пример работы с энкодером с прерыванием. Максимальная чёткость работы
в любом быдлокоде!
*/
#define CLK 2
#define DT 3
#define SW 4
#include "GyverEncoder.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
attachInterrupt(0, isrCLK, CHANGE); // прерывание на 2 пине! CLK у энка
attachInterrupt(1, isrDT, CHANGE); // прерывание на 3 пине! DT у энка
}
void isrCLK() {
enc1.tick(); // отработка в прерывании
}
void isrDT() {
enc1.tick(); // отработка в прерывании
}
void loop() {
enc1.tick();
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}

View File

@ -0,0 +1,40 @@
// подключаем "внешний" энкодер, для работы с расширителями пинов например
#define SW 0
#define DT 2
#define CLK 3
#include "GyverEncoder.h"
Encoder enc1;
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2);
pinMode(SW, INPUT_PULLUP);
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
}
void loop() {
// подаём значения напрямую в tick()
// можно подавать лог. величины с любых расширителей пинов!!!
// Здесь в качестве примера digitalRead
enc1.tick(digitalRead(CLK), digitalRead(DT), !digitalRead(SW));
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
// ваш код
}
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
if (enc1.isPress()) Serial.println("Press"); // нажатие на кнопку (+ дебаунс)
if (enc1.isClick()) Serial.println("Click"); // отпускание кнопки (+ дебаунс)
//if (enc1.isRelease()) Serial.println("Release"); // то же самое, что isClick
if (enc1.isHolded()) Serial.println("Holded"); // если была удержана и энк не поворачивался
//if (enc1.isHold()) Serial.println("Hold"); // возвращает состояние кнопки
}

View File

@ -0,0 +1,31 @@
/*
* Отработка по прерыванию таймера
*/
#define CLK 7
#define DT 8
#define SW 9
#include "GyverEncoder.h"
#include "TimerOne.h"
Encoder enc1(CLK, DT, SW);
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
Timer1.initialize(1000); // установка таймера на каждые 1000 микросекунд (= 1 мс)
Timer1.attachInterrupt(timerIsr); // запуск таймера
}
void timerIsr() { // прерывание таймера
enc1.tick(); // отработка теперь находится здесь
}
void loop() {
if (enc1.isRight()) Serial.println("Right"); // если был поворот
if (enc1.isLeft()) Serial.println("Left");
if (enc1.isRightH()) Serial.println("Right holded"); // если было удержание + поворот
if (enc1.isLeftH()) Serial.println("Left holded");
}

View File

@ -0,0 +1,20 @@
// два энкодера
#include "GyverEncoder.h"
Encoder enc1(4, 3, 2);
Encoder enc2(7, 6, 5);
void setup() {
Serial.begin(9600);
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
enc2.tick();
if (enc1.isLeft()) Serial.println("enc 1 left");
if (enc1.isRight()) Serial.println("enc 1 right");
if (enc2.isLeft()) Serial.println("enc 2 left");
if (enc2.isRight()) Serial.println("enc 2 right");
}

View File

@ -0,0 +1,31 @@
#define CLK 7
#define DT 8
#define SW 9
#include "GyverEncoder.h"
//Encoder enc1(CLK, DT); // для работы без кнопки
Encoder enc1(CLK, DT, SW); // для работы c кнопкой
//Encoder enc1(CLK, DT, SW, TYPE2); // для работы c кнопкой и сразу выбираем тип
//Encoder enc1(CLK, DT, ENC_NO_BUTTON, TYPE2); // для работы без кнопки и сразу выбираем тип
int value = 0;
void setup() {
Serial.begin(9600);
enc1.setType(TYPE2); // тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
}
void loop() {
// обязательная функция отработки. Должна постоянно опрашиваться
enc1.tick();
if (enc1.isRight()) value++; // если был поворот направо, увеличиваем на 1
if (enc1.isLeft()) value--; // если был поворот налево, уменьшаем на 1
if (enc1.isRightH()) value += 5; // если было удержание + поворот направо, увеличиваем на 5
if (enc1.isLeftH()) value -= 5; // если было удержание + поворот налево, уменьшаем на 5
if (enc1.isTurn()) { // если был совершён поворот (индикатор поворота в любую сторону)
Serial.println(value); // выводим значение при повороте
}
}

View File

@ -0,0 +1,52 @@
#######################################
# Syntax Coloring Map For GyverEncoder
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
Encoder KEYWORD1
GyverEncoder KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
tick KEYWORD2
setType KEYWORD2
setDirection KEYWORD2
setFastTimeout KEYWORD2
setPinMode KEYWORD2
setBtnPinMode KEYWORD2
isTurn KEYWORD2
isRight KEYWORD2
isLeft KEYWORD2
isRightH KEYWORD2
isLeftH KEYWORD2
isFastR KEYWORD2
isFastL KEYWORD2
isPress KEYWORD2
isRelease KEYWORD2
isReleaseHold KEYWORD2
isClick KEYWORD2
isSingle KEYWORD2
isDouble KEYWORD2
isHolded KEYWORD2
isHold KEYWORD2
resetStates KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
TYPE1 LITERAL1
TYPE2 LITERAL1
MANUAL LITERAL1
AUTO LITERAL1
NORM LITERAL1
REVERSE LITERAL1
HIGH_PULL LITERAL1
LOW_PULL LITERAL1
ENC_NO_BUTTON LITERAL1

View File

@ -0,0 +1,9 @@
name=GyverEncoder
version=4.9
author=AlexGyver <alex@alexgyver.ru>
maintainer=AlexGyver <alex@alexgyver.ru>
sentence=Advanced encoder control library
paragraph=Advanced encoder control library
category=Sensors
url=https://github.com/GyverLibs/GyverEncoder
architectures=*

View File

@ -0,0 +1,352 @@
#include "GyverEncoder.h"
#if defined(PRECISE_ALGORITHM)
const int8_t KNOBDIR[] = {
0, -1, 1, 0,
1, 0, 0, -1,
-1, 0, 0, 1,
0, 1, -1, 0,
};
int8_t encPos = 0;
#endif
// ================= CONSTRUCTOR =================
Encoder::Encoder() {
flags.use_button = true;
}
Encoder::Encoder(uint8_t clk, uint8_t dt, int8_t sw, bool type) {
_CLK = clk;
_DT = dt;
if (sw != -1) {
_SW = sw;
flags.use_button = true;
} else {
flags.use_button = false;
}
flags.enc_type = type;
#if defined(__AVR__)
_pin_reg_CLK = portInputRegister(digitalPinToPort(_CLK));
_bit_mask_CLK = digitalPinToBitMask(_CLK);
_pin_reg_DT = portInputRegister(digitalPinToPort(_DT));
_bit_mask_DT = digitalPinToBitMask(_DT);
if (sw != -1) {
_pin_reg_SW = portInputRegister(digitalPinToPort(_SW));
_bit_mask_SW = digitalPinToBitMask(_SW);
}
#endif
pinMode(_CLK, (DEFAULT_ENC_PULL ? INPUT : INPUT_PULLUP));
pinMode(_DT, (DEFAULT_ENC_PULL ? INPUT : INPUT_PULLUP));
if (flags.use_button) pinMode(_SW, (DEFAULT_BTN_PULL ? INPUT : INPUT_PULLUP));
flags.invBtn = (DEFAULT_BTN_PULL == HIGH_PULL) ? true : false;
#if defined(FAST_ALGORITHM)
prevState = _readCLK();
#else
prevState = _readCLK() | (_readDT() << 1);
#endif
}
// ================= SET =================
void Encoder::setDirection(bool direction) {
if (direction) {
uint8_t buf = _CLK;
_CLK = _DT;
_DT = buf;
}
}
void Encoder::setPinMode(bool mode) {
pinMode(_CLK, (mode) ? INPUT : INPUT_PULLUP);
pinMode(_DT, (mode) ? INPUT : INPUT_PULLUP);
}
void Encoder::setBtnPinMode(bool mode) {
pinMode(_SW, (mode) ? INPUT : INPUT_PULLUP);
flags.invBtn = (mode) ? 0 : 1;
}
void Encoder::setType(bool type) {
flags.enc_type = type;
}
void Encoder::setTickMode(bool tickMode) {
flags.enc_tick_mode = tickMode;
}
void Encoder::setFastTimeout(uint16_t timeout) {
_fast_timeout = timeout;
}
// ================= IS =================
// повороты
boolean Encoder::isTurn() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isTurn_f) {
flags.isTurn_f = false;
return true;
} else return false;
}
boolean Encoder::isRight() {
if (flags.enc_tick_mode) Encoder::tick();
if (encState == 2) {
encState = 0;
return true;
} else return false;
}
boolean Encoder::isLeft() {
if (flags.enc_tick_mode) Encoder::tick();
if (encState == 1) {
encState = 0;
return true;
} else return false;
}
boolean Encoder::isRightH() {
if (flags.enc_tick_mode) Encoder::tick();
if (encState == 4) {
encState = 0;
return true;
} else return false;
}
boolean Encoder::isLeftH() {
if (flags.enc_tick_mode) Encoder::tick();
if (encState == 3) {
encState = 0;
return true;
} else return false;
}
boolean Encoder::isFastR() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isFastR_f) {
flags.isFastR_f = false;
return true;
} else return false;
}
boolean Encoder::isFastL() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isFastL_f) {
flags.isFastL_f = false;
return true;
} else return false;
}
// кнопка
boolean Encoder::isPress() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isPress_f) {
flags.isPress_f = false;
return true;
} else return false;
}
boolean Encoder::isRelease() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isRelease_f) {
flags.isRelease_f = false;
return true;
} else return false;
}
boolean Encoder::isReleaseHold() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isReleaseHold_f) {
flags.isReleaseHold_f = false;
return true;
} else return false;
}
boolean Encoder::isClick() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isRelease_f) {
flags.isRelease_f = false;
return true;
} else return false;
}
boolean Encoder::isHolded() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.hold_flag && flags.isHolded_f) {
flags.isHolded_f = false;
return true;
} else return false;
}
boolean Encoder::isSingle() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isSingle_f) {
flags.isSingle_f = false;
flags.isDouble_f = false;
return true;
} else return false;
}
boolean Encoder::isDouble() {
if (flags.enc_tick_mode) Encoder::tick();
if (flags.isDouble_f) {
flags.isDouble_f = false;
flags.isSingle_f = false;
return true;
} else return false;
}
boolean Encoder::isHold() {
if (flags.enc_tick_mode) Encoder::tick();
return (SW_state);
}
void Encoder::resetStates() {
encState = 0;
flags.isTurn_f = false;
flags.isFastR_f = false;
flags.isFastL_f = false;
flags.isPress_f = false;
flags.isRelease_f = false;
flags.isReleaseHold_f = false;
flags.isHolded_f = false;
flags.isSingle_f = false;
flags.isDouble_f = false;
}
// ================= TICK =================
void Encoder::tick(bool clk, bool dt, bool sw) {
extTick = true;
flags.extCLK = clk;
flags.extDT = dt;
flags.extSW = sw;
Encoder::tick();
extTick = false;
}
void Encoder::tick() {
uint32_t thisMls = millis();
uint32_t debounceDelta = thisMls - debounce_timer;
#ifdef ENC_WITH_BUTTON
if (flags.use_button) {
if (!extTick) SW_state = _readSW() ^ flags.invBtn; // читаем состояние кнопки SW
else SW_state = flags.extSW;
if (SW_state && !flags.butt_flag && (debounceDelta > ENC_DEBOUNCE_BUTTON)) {
flags.butt_flag = true;
flags.turn_flag = false;
debounce_timer = thisMls;
debounceDelta = 0;
flags.isPress_f = true;
flags.isHolded_f = true;
flags.doubleAllow = true;
}
if (!SW_state && flags.butt_flag && (debounceDelta > ENC_DEBOUNCE_BUTTON)) {
if (!flags.turn_flag && !flags.hold_flag) { // если кнопка отпущена и ручка не поворачивалась
flags.turn_flag = false;
flags.isRelease_f = true;
}
if (debounceDelta > ENC_HOLD_TIMEOUT) flags.isReleaseHold_f = true;
flags.butt_flag = false;
debounce_timer = thisMls;
debounceDelta = 0;
flags.hold_flag = false;
if (flags.doubleAllow && !flags.doubleFlag) {
flags.doubleFlag = true;
flags.countFlag = false;
} else {
flags.countFlag = true;
}
}
if (flags.doubleFlag && debounceDelta > ENC_DOUBLE_TIMEOUT) {
if (!flags.turn_flag) {
if (!flags.countFlag) flags.isSingle_f = true;
else flags.isDouble_f = true;
}
flags.doubleFlag = false;
}
if (flags.butt_flag && debounceDelta > ENC_HOLD_TIMEOUT && !flags.turn_flag) {
if (SW_state) {
flags.hold_flag = true;
flags.doubleAllow = false;
} else {
flags.butt_flag = false;
flags.hold_flag = false;
debounce_timer = thisMls;
debounceDelta = 0;
}
}
}
#endif
#if defined(FAST_ALGORITHM)
uint8_t curState = (extTick) ? (flags.extCLK) : (_readCLK());
if (curState != prevState
#if (ENC_DEBOUNCE_TURN > 0)
&& (debounceDelta > ENC_DEBOUNCE_TURN)
#endif
) {
encState = 0;
turnFlag = !turnFlag;
if (turnFlag || !flags.enc_type) {
if (( (extTick) ? (flags.extDT) : _readDT() ) != prevState) {
encState = 1;
} else {
encState = 2;
}
}
#elif defined(BINARY_ALGORITHM)
uint8_t curState = (extTick) ? (flags.extCLK | (flags.extDT << 1)) : (_readCLK() | (_readDT() << 1));
if (curState != prevState
#if (ENC_DEBOUNCE_TURN > 0)
&& (debounceDelta > ENC_DEBOUNCE_TURN)
#endif
) {
encState = 0;
if (flags.rst_flag) {
if (curState == 0b11) {
flags.rst_flag = 0;
//encState = 3-prevState;
switch (prevState) {
case 0b10: encState = 1; break; // 2 - 1
case 0b01: encState = 2; break; // 1 - 2
}
} else if (!flags.enc_type && (curState == 0b00)) {
flags.rst_flag = 0;
//encState = prevState;
switch (prevState) {
case 0b01: encState = 1; break;
case 0b10: encState = 2; break;
}
}
}
if (curState == 0b00 || (!flags.enc_type && curState == 0b11)) flags.rst_flag = 1;
#elif defined(PRECISE_ALGORITHM)
uint8_t curState = (extTick) ? (flags.extCLK | (flags.extDT << 1)) : (_readCLK() | (_readDT() << 1));
if (curState != prevState
#if (ENC_DEBOUNCE_TURN > 0)
&& (debounceDelta > ENC_DEBOUNCE_TURN)
#endif
) {
encState = 0;
encPos += KNOBDIR[curState | (prevState << 2)];
if (flags.enc_type) {
if (curState == 0x3 && encPos != 0) {
encState = (encPos == 4) ? 1 : 2;
encPos = 0;
}
} else {
if ((curState == 0x3 || !curState) && encPos != 0) {
encState = (encPos == 2) ? 1 : 2;
encPos = 0;
}
}
#endif
if (encState != 0) {
flags.isTurn_f = true;
if (!SW_state && thisMls - fast_timer < _fast_timeout) {
if (encState == 1) flags.isFastL_f = true;
else if (encState == 2) flags.isFastR_f = true;
fast_timer = thisMls;
} else fast_timer = thisMls;
#ifdef ENC_WITH_BUTTON
if (flags.use_button && SW_state) encState += 2;
#endif
}
prevState = curState;
flags.turn_flag = true;
debounce_timer = thisMls;
debounceDelta = 0;
}
}

View File

@ -0,0 +1,175 @@
/*
GyverEncoder - библиотека для расширенной работы с энкодером
ВНИМАНИЕ, БИБЛИОТЕКА УСТАРЕЛА! ИСПОЛЬЗУЙ БИБЛИОТЕКУ EncButton https://github.com/GyverLibs/EncButton
Документация: https://alexgyver.ru/encoder/
GitHub: https://github.com/GyverLibs/GyverEncoder
Возможности:
- Отработка поворота энкодера
- Отработка "нажатого поворота"
- Отработка "быстрого поворота"
- Несколько алгоритмов опроса энкодера
- Выбор подтяжки подключения энкодера
- Работа с двумя типами экнодеров
- Работа с внешним энкодером (через расширитель пинов и т.п.)
- Отработка нажатия/удержания кнопки с антидребезгом
Версии:
v3.6 от 16.09.2019 - Возвращены дефайны настроек
v4.0 от 13.11.2019
- Оптимизирован код
- Исправлены баги
- Добавлены другие алгоритмы опроса
- Добавлена возможность полностью убрать кнопку (экономия памяти)
- Добавлена возможность подключения внешнего энкодера
- Добавлена настройка подтяжки пинов
v4.1: Исправлено изменение подтяжек
v4.2
- Добавлена поддержка TYPE1 для алгоритма PRECISE_ALGORITHM
- Добавлена отработка двойного клика: isSingle / isDouble
v4.3: Исправлено ложное isSingle
v4.4: Добавлен метод resetStates, сбрасывает все is-флаги и счётчики
v4.5: Улучшен алгоритм BINARY_ALGORITHM (спасибо Ярославу Курусу)
v4.6: BINARY_ALGORITHM пофикшен для TYPE1, добавлена isReleaseHold
v4.7: Исправлен случайный нажатый поворот в BINARY_ALGORITHM
v4.8: увеличена производительность для AVR Arduino
v4.9: быстрый поворот отключен если кнопка удерживается
*/
#ifndef GyverEncoder_h
#define GyverEncoder_h
#include <Arduino.h>
// ========= КОНСТАНТЫ ==========
#define ENC_NO_BUTTON -1 // константа для работы без пина
#define TYPE1 0 // полушаговый энкодер
#define TYPE2 1 // полношаговый
#define NORM 0 // направление вращения обычное
#define REVERSE 1 // обратное
#define MANUAL 0 // нужно вызывать функцию tick() вручную
#define AUTO 1 // tick() входит во все остальные функции и опрашивается сама!
#define HIGH_PULL 0 // внутренняя подтяжка к питанию (pinMode INPUT_PULLUP)
#define LOW_PULL 1 // внешняя подтяжка к GND (pinMode INPUT)
// =========== НАСТРОЙКИ ===========
// закомментируй строку, чтобы полностью убрать отработку кнопки из кода
#define ENC_WITH_BUTTON
// тип подключения энкодера по умолчанию (LOW_PULL или HIGH_PULL)
//#define DEFAULT_ENC_PULL LOW_PULL
#define DEFAULT_ENC_PULL HIGH_PULL
// тип подключения кнопки энкодера по умолчанию (LOW_PULL или HIGH_PULL)
//#define DEFAULT_BTN_PULL LOW_PULL
#define DEFAULT_BTN_PULL HIGH_PULL
// алгоритмы опроса энкодера (раскомментировать нужный)
//#define FAST_ALGORITHM // тик 10 мкс, быстрый, не справляется с люфтами
#define BINARY_ALGORITHM // тик 14 мкс, лучше справляется с люфтами
//#define PRECISE_ALGORITHM // тик 16 мкс, работает даже с убитым энкодером (по мотивам https://github.com/mathertel/RotaryEncoder)
// настройка антидребезга энкодера, кнопки, таймаута удержания и таймаута двойного клика
#define ENC_DEBOUNCE_TURN 0
#define ENC_DEBOUNCE_BUTTON 80
#define ENC_HOLD_TIMEOUT 700
#define ENC_DOUBLE_TIMEOUT 300
#if defined(__AVR__)
#define _readCLK() bool(*_pin_reg_CLK & _bit_mask_CLK)
#define _readDT() bool(*_pin_reg_DT & _bit_mask_DT)
#define _readSW() bool(*_pin_reg_SW & _bit_mask_SW)
#else
#define _readCLK() digitalRead(_CLK)
#define _readDT() digitalRead(_DT)
#define _readSW() digitalRead(_SW)
#endif
#pragma pack(push,1)
typedef struct
{
bool hold_flag: 1;
bool butt_flag: 1;
bool turn_flag: 1;
bool isTurn_f: 1;
bool isPress_f: 1;
bool isRelease_f: 1;
bool isReleaseHold_f: 1;
bool isHolded_f: 1;
bool isFastR_f: 1;
bool isFastL_f: 1;
bool enc_tick_mode: 1;
bool enc_type: 1;
bool use_button : 1;
bool extCLK : 1;
bool extDT : 1;
bool extSW : 1;
bool invBtn : 1;
bool isSingle_f : 1;
bool isDouble_f : 1;
bool countFlag : 1;
bool doubleFlag : 1;
bool doubleAllow : 1;
bool rst_flag : 1;
} GyverEncoderFlags;
#pragma pack(pop)
// Варианты инициализации:
// Encoder enc; // не привязан к пину
// Encoder enc(пин CLK, пин DT); // энкодер без кнопки (ускоренный опрос)
// Encoder enc(пин CLK, пин DT, пин SW); // энкодер с кнопкой
// Encoder enc(пин CLK, пин DT, пин SW, тип); // энкодер с кнопкой и указанием типа
// Encoder enc(пин CLK, пин DT, ENC_NO_BUTTON, тип); // энкодер без кнопкой и с указанием типа
class Encoder {
public:
Encoder(); // для непривязанного к пинам энкодера
Encoder(uint8_t clk, uint8_t dt, int8_t sw = -1, bool type = false); // CLK, DT, SW, тип (TYPE1 / TYPE2) TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
void tick(); // опрос энкодера, нужно вызывать постоянно или в прерывании
void tick(bool clk, bool dt, bool sw = 0); // опрос "внешнего" энкодера
void setType(bool type); // TYPE1 / TYPE2 - тип энкодера TYPE1 одношаговый, TYPE2 двухшаговый. Если ваш энкодер работает странно, смените тип
void setPinMode(bool mode); // тип подключения энкодера, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND)
void setBtnPinMode(bool mode); // тип подключения кнопки, подтяжка HIGH_PULL (внутренняя) или LOW_PULL (внешняя на GND)
void setTickMode(bool tickMode); // MANUAL / AUTO - ручной или автоматический опрос энкодера функцией tick(). (по умолчанию ручной)
void setDirection(bool direction); // NORM / REVERSE - направление вращения энкодера
void setFastTimeout(uint16_t timeout); // установка таймаута быстрого поворота
boolean isTurn(); // возвращает true при любом повороте, сама сбрасывается в false
boolean isRight(); // возвращает true при повороте направо, сама сбрасывается в false
boolean isLeft(); // возвращает true при повороте налево, сама сбрасывается в false
boolean isRightH(); // возвращает true при удержании кнопки и повороте направо, сама сбрасывается в false
boolean isLeftH(); // возвращает true при удержании кнопки и повороте налево, сама сбрасывается в false
boolean isFastR(); // возвращает true при быстром повороте
boolean isFastL(); // возвращает true при быстром повороте
boolean isPress(); // возвращает true при нажатии кнопки, сама сбрасывается в false
boolean isRelease(); // возвращает true при отпускании кнопки, сама сбрасывается в false
boolean isReleaseHold(); // возвращает true при отпускании кнопки после удержания, сама сбрасывается в false
boolean isClick(); // возвращает true при нажатии и отпускании кнопки, сама сбрасывается в false
boolean isHolded(); // возвращает true при удержании кнопки, сама сбрасывается в false
boolean isHold(); // возвращает true при удержании кнопки, НЕ СБРАСЫВАЕТСЯ
boolean isSingle(); // возвращает true при одиночном клике (после таймаута), сама сбрасывается в false
boolean isDouble(); // возвращает true при двойном клике, сама сбрасывается в false
void resetStates(); // сбрасывает все is-флаги и счётчики
private:
GyverEncoderFlags flags;
uint8_t _fast_timeout = 50; // таймаут быстрого поворота
uint8_t prevState = 0;
uint8_t encState = 0; // 0 не крутился, 1 лево, 2 право, 3 лево нажат, 4 право нажат
uint32_t debounce_timer = 0, fast_timer = 0;
uint8_t _CLK = 0, _DT = 0, _SW = 0;
bool turnFlag = false, extTick = false, SW_state = false;
#if defined(__AVR__)
volatile uint8_t *_pin_reg_CLK;
volatile uint8_t _bit_mask_CLK;
volatile uint8_t *_pin_reg_DT;
volatile uint8_t _bit_mask_DT;
volatile uint8_t *_pin_reg_SW;
volatile uint8_t _bit_mask_SW;
#endif
};
#endif