├── .gitattributes ├── schemes └── scheme.jpg ├── 3Dprint ├── Screw v11.f3d └── Screw v11.stl ├── README.md └── firmware ├── GyverFeed_v2.2 ├── GyverFeed_v2.2.ino ├── buildTime.h ├── microDS3231.h ├── microDS3231.cpp └── EncButton.h ├── GyverFeed_v2.0 ├── GyverFeed_v2.0.ino └── EncButton.h └── GyverFeed_v2.1 ├── GyverFeed_v2.1.ino └── EncButton.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /schemes/scheme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexGyver/GyverFeed2/HEAD/schemes/scheme.jpg -------------------------------------------------------------------------------- /3Dprint/Screw v11.f3d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexGyver/GyverFeed2/HEAD/3Dprint/Screw v11.f3d -------------------------------------------------------------------------------- /3Dprint/Screw v11.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexGyver/GyverFeed2/HEAD/3Dprint/Screw v11.stl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GyverFeed2 2 | ### Автоматическая кормушка на Arduino 3 | v2.2 - перевёл на актуальные версии библиотек EncButton и microDS3231 -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.2/GyverFeed_v2.2.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Скетч к проекту "Автокормушка 2" 3 | - Страница проекта (схемы, описания): https://alexgyver.ru/gyverfeed2/ 4 | - Исходники на GitHub: https://github.com/AlexGyver/GyverFeed2/ 5 | Проблемы с загрузкой? Читай гайд для новичков: https://alexgyver.ru/arduino-first/ 6 | AlexGyver, AlexGyver Technologies, 2020 7 | */ 8 | 9 | // v2.1 - исправлен баг с невыключением мотора 10 | 11 | // Клик - внеочередная кормёжка 12 | // Удержание - задаём размер порции 13 | const byte feedTime[][2] = { 14 | {7, 0}, // часы, минуты. НЕ НАЧИНАТЬ ЧИСЛО С НУЛЯ 15 | {12, 0}, 16 | {17, 0}, 17 | {21, 0}, 18 | }; 19 | 20 | #define EE_RESET 12 // любое число 0-255. Измени, чтобы сбросить настройки и обновить время 21 | #define FEED_SPEED 3000 // задержка между шагами мотора (мкс) 22 | #define BTN_PIN 2 // кнопка 23 | #define STEPS_FRW 19 // шаги вперёд 24 | #define STEPS_BKW 12 // шаги назад 25 | const byte drvPins[] = {3, 4, 5, 6}; // драйвер (фазаА1, фазаА2, фазаВ1, фазаВ2) 26 | 27 | // ========================================================= 28 | #include 29 | #include "microDS3231.h" 30 | MicroDS3231 rtc; 31 | 32 | #include "EncButton.h" 33 | EncButton btn; 34 | int feedAmount = 100; 35 | 36 | void setup() { 37 | rtc.begin(); 38 | if (EEPROM.read(0) != EE_RESET) { // первый запуск 39 | EEPROM.write(0, EE_RESET); 40 | EEPROM.put(1, feedAmount); 41 | rtc.setTime(BUILD_SEC, BUILD_MIN, BUILD_HOUR, BUILD_DAY, BUILD_MONTH, BUILD_YEAR); 42 | } 43 | EEPROM.get(1, feedAmount); 44 | for (byte i = 0; i < 4; i++) pinMode(drvPins[i], OUTPUT); // пины выходы 45 | } 46 | 47 | void loop() { 48 | static uint32_t tmr = 0; 49 | if (millis() - tmr > 500) { // два раза в секунду 50 | static byte prevMin = 0; 51 | tmr = millis(); 52 | DateTime now = rtc.getTime(); 53 | if (prevMin != now.minute) { 54 | prevMin = now.minute; 55 | for (byte i = 0; i < sizeof(feedTime) / 2; i++) // для всего расписания 56 | if (feedTime[i][0] == now.hour && feedTime[i][1] == now.minute) feed(); 57 | } 58 | } 59 | 60 | btn.tick(); 61 | if (btn.click()) feed(); 62 | 63 | if (btn.hold()) { 64 | int newAmount = 0; 65 | while (btn.isHold()) { 66 | btn.tick(); 67 | oneRev(); 68 | newAmount++; 69 | } 70 | disableMotor(); 71 | feedAmount = newAmount; 72 | EEPROM.put(1, feedAmount); 73 | } 74 | } 75 | 76 | void feed() { 77 | for (int i = 0; i < feedAmount; i++) oneRev(); 78 | disableMotor(); 79 | } 80 | 81 | // выключаем ток на мотор 82 | void disableMotor() { 83 | for (byte i = 0; i < 4; i++) digitalWrite(drvPins[i], 0); 84 | } 85 | 86 | void oneRev() { 87 | for (int i = 0; i < STEPS_BKW; i++) runMotor(-1); 88 | for (int i = 0; i < STEPS_FRW; i++) runMotor(1); 89 | } 90 | 91 | const byte steps[] = {0b1010, 0b0110, 0b0101, 0b1001}; 92 | void runMotor(int8_t dir) { 93 | static byte step = 0; 94 | for (byte i = 0; i < 4; i++) digitalWrite(drvPins[i], bitRead(steps[step & 0b11], i)); 95 | delayMicroseconds(FEED_SPEED); 96 | step += dir; 97 | } 98 | -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.2/buildTime.h: -------------------------------------------------------------------------------- 1 | /* 2 | Парсинг и получение даты и времени компиляции из __DATE__ и __TIME__ 3 | Документация: 4 | GitHub: https://github.com/GyverLibs/buildTime 5 | Константы времени компиляции: 6 | BUILD_YEAR - год 7 | BUILD_MONTH - месяц 8 | BUILD_DAY - день 9 | BUILD_HOUR - час 10 | BUILD_MIN - минута 11 | BUILD_SEC - секунда 12 | 13 | Исходник http://qaru.site/questions/186859/how-to-use-date-and-time-predefined-macros-in-as-two-integers-then-stringify 14 | AlexGyver, alex@alexgyver.ru 15 | https://alexgyver.ru/ 16 | MIT License 17 | 18 | Версии: 19 | v1.0 - релиз 20 | */ 21 | 22 | 23 | #ifndef buildTime_h 24 | #define buildTime_h 25 | // Example of __DATE__ string: "Jul 27 2012" 26 | // 01234567890 27 | 28 | #define BUILD_YEAR_CH0 (__DATE__[7]-'0') 29 | #define BUILD_YEAR_CH1 (__DATE__[8]-'0') 30 | #define BUILD_YEAR_CH2 (__DATE__[9]-'0') 31 | #define BUILD_YEAR_CH3 (__DATE__[10]-'0') 32 | #define BUILD_YEAR (BUILD_YEAR_CH0*1000+BUILD_YEAR_CH1*100 + BUILD_YEAR_CH2*10+BUILD_YEAR_CH3) 33 | 34 | #define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n') 35 | #define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F') 36 | #define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r') 37 | #define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p') 38 | #define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y') 39 | #define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n') 40 | #define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l') 41 | #define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u') 42 | #define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S') 43 | #define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O') 44 | #define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N') 45 | #define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D') 46 | 47 | #define BUILD_MONTH \ 48 | ( \ 49 | (BUILD_MONTH_IS_JAN) ? 1 : \ 50 | (BUILD_MONTH_IS_FEB) ? 2 : \ 51 | (BUILD_MONTH_IS_MAR) ? 3 : \ 52 | (BUILD_MONTH_IS_APR) ? 4 : \ 53 | (BUILD_MONTH_IS_MAY) ? 5 : \ 54 | (BUILD_MONTH_IS_JUN) ? 6 : \ 55 | (BUILD_MONTH_IS_JUL) ? 7 : \ 56 | (BUILD_MONTH_IS_AUG) ? 8 : \ 57 | (BUILD_MONTH_IS_SEP) ? 9 : \ 58 | (BUILD_MONTH_IS_OCT) ? 10 : \ 59 | (BUILD_MONTH_IS_NOV) ? 11 : \ 60 | (BUILD_MONTH_IS_DEC) ? 12 : \ 61 | /* error default */ '?' \ 62 | ) 63 | 64 | #define BUILD_DAY_CH0 (((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')-'0') 65 | #define BUILD_DAY_CH1 (__DATE__[5]-'0') 66 | #define BUILD_DAY (BUILD_DAY_CH0*10+BUILD_DAY_CH1) 67 | 68 | // Example of __TIME__ string: "21:06:19" 69 | // 01234567 70 | 71 | #define BUILD_HOUR_CH0 (__TIME__[0]-'0') 72 | #define BUILD_HOUR_CH1 (__TIME__[1]-'0') 73 | #define BUILD_HOUR (BUILD_HOUR_CH0*10+BUILD_HOUR_CH1) 74 | 75 | #define BUILD_MIN_CH0 (__TIME__[3]-'0') 76 | #define BUILD_MIN_CH1 (__TIME__[4]-'0') 77 | #define BUILD_MIN (BUILD_MIN_CH0*10+BUILD_MIN_CH1) 78 | 79 | #define BUILD_SEC_CH0 (__TIME__[6]-'0') 80 | #define BUILD_SEC_CH1 (__TIME__[7]-'0') 81 | #define BUILD_SEC (BUILD_SEC_CH0*10+BUILD_SEC_CH1) 82 | 83 | #endif -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.0/GyverFeed_v2.0.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Скетч к проекту "Автокормушка 2" 3 | - Страница проекта (схемы, описания): https://alexgyver.ru/gyverfeed2/ 4 | - Исходники на GitHub: https://github.com/AlexGyver/GyverFeed2/ 5 | Проблемы с загрузкой? Читай гайд для новичков: https://alexgyver.ru/arduino-first/ 6 | AlexGyver, AlexGyver Technologies, 2020 7 | */ 8 | 9 | // Клик - внеочередная кормёжка 10 | // Удержание - задаём размер порции 11 | const byte feedTime[][2] = { 12 | {7, 0}, // часы, минуты. НЕ НАЧИНАТЬ ЧИСЛО С НУЛЯ 13 | {12, 0}, 14 | {17, 0}, 15 | {21, 0}, 16 | }; 17 | 18 | #define EE_RESET 12 // любое число 0-255. Измени, чтобы сбросить настройки и обновить время 19 | #define FEED_SPEED 3000 // задержка между шагами мотора (мкс) 20 | #define BTN_PIN 2 // кнопка 21 | #define STEPS_FRW 18 // шаги вперёд 22 | #define STEPS_BKW 10 // шаги назад 23 | const byte drvPins[] = {3, 4, 5, 6}; // драйвер (фазаА1, фазаА2, фазаВ1, фазаВ2) 24 | 25 | // ========================================================= 26 | #include "EncButton.h" 27 | #include 28 | #include 29 | RTC_DS3231 rtc; 30 | EncButton btn; 31 | int feedAmount = 100; 32 | 33 | void setup() { 34 | rtc.begin(); 35 | if (EEPROM.read(0) != EE_RESET) { // первый запуск 36 | EEPROM.write(0, EE_RESET); 37 | EEPROM.put(1, feedAmount); 38 | rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 39 | } 40 | EEPROM.get(1, feedAmount); 41 | for (byte i = 0; i < 4; i++) pinMode(drvPins[i], OUTPUT); // пины выходы 42 | } 43 | 44 | void loop() { 45 | static uint32_t tmr = 0; 46 | if (millis() - tmr > 500) { // два раза в секунду 47 | static byte prevMin = 0; 48 | tmr = millis(); 49 | DateTime now = rtc.now(); 50 | if (prevMin != now.minute()) { 51 | prevMin = now.minute(); 52 | for (byte i = 0; i < sizeof(feedTime) / 2; i++) // для всего расписания 53 | if (feedTime[i][0] == now.hour() && feedTime[i][1] == now.minute()) // пора кормить 54 | feed(); 55 | } 56 | } 57 | 58 | btn.tick(); 59 | if (btn.isClick()) { 60 | feed(); 61 | } 62 | if (btn.isHold()) { 63 | int newAmount = 0; 64 | while (btn.isHold()) { 65 | btn.tick(); 66 | oneRev(); 67 | newAmount++; 68 | } 69 | feedAmount = newAmount; 70 | EEPROM.put(1, feedAmount); 71 | } 72 | } 73 | 74 | void feed() { 75 | for (int i = 0; i < feedAmount; i++) oneRev(); // крутим на количество feedAmount 76 | for (byte i = 0; i < 4; i++) digitalWrite(drvPins[i], 0); // выключаем ток на мотор 77 | } 78 | 79 | void oneRev() { 80 | static uint16_t val = 0; 81 | for (byte i = 0; i < STEPS_FRW; i++) runMotor(val++); 82 | for (byte i = 0; i < STEPS_BKW; i++) runMotor(val--); 83 | } 84 | 85 | void runMotor(int thisStep) { 86 | /*static const byte steps[] = {0b1000, 0b1010, 0b0010, 0b0110, 0b0100, 0b0101, 0b0001, 0b1001}; 87 | for (byte i = 0; i < 4; i++) 88 | digitalWrite(drvPins[i], bitRead(steps[thisStep & 0b111], i)); 89 | */ 90 | static const byte steps[] = {0b1010, 0b0110, 0b0101, 0b1001}; 91 | for (byte i = 0; i < 4; i++) 92 | digitalWrite(drvPins[i], bitRead(steps[thisStep & 0b11], i)); 93 | delayMicroseconds(FEED_SPEED); 94 | } 95 | -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.2/microDS3231.h: -------------------------------------------------------------------------------- 1 | /* 2 | Лёгкая библиотека для работы с RTC DS3231 для Arduino 3 | Документация: 4 | GitHub: https://github.com/GyverLibs/microDS3231 5 | Возможности: 6 | - Чтение и запись времени 7 | - Вывод в char* и String 8 | - Чтение температуры датчика 9 | 10 | Egor 'Nich1con' Zakharov & AlexGyver, alex@alexgyver.ru 11 | https://alexgyver.ru/ 12 | MIT License 13 | 14 | Версии: 15 | v1.2 - добавлены ограничения на вводимые в setTime числа. Также нельзя ввести 29 февраля увы =) 16 | v1.3 - пофикшено зависание, когда модуль отключен но опрашивается 17 | v1.4 - незначительный фикс 18 | v2.0 - новые возможности, оптимизация и облегчение 19 | v2.1 - добавил вывод температуры, вывод в String и char 20 | v2.2 - исправлены дни недели (пн-вс 1-7) 21 | v2.3 - небольшие исправления, оптимизация, изменён порядок вывода даты 22 | v2.4 - исправлена установка времени компиляции 23 | v2.5 - добавлен begin для проверки наличия модуля на линии 24 | v2.6 - исправлены отрицательные температуры 25 | */ 26 | 27 | #ifndef microDS3231_h 28 | #define microDS3231_h 29 | //#include // выбор между библиотеками Wire и microWire 30 | #include 31 | #include "buildTime.h" 32 | 33 | #include 34 | #define COMPILE_TIME F(__TIMESTAMP__) 35 | 36 | struct DateTime { 37 | uint8_t second; 38 | uint8_t minute; 39 | uint8_t hour; 40 | uint8_t day; 41 | uint8_t date; 42 | uint8_t month; 43 | uint16_t year; 44 | }; 45 | 46 | class MicroDS3231 { 47 | public: 48 | MicroDS3231(uint8_t addr = 0x68); // конструктор. Можно передать адрес 49 | bool begin(void); // инициализация, вернет true, если RTC найден 50 | void setTime(const __FlashStringHelper* stamp); // установка времени == времени компиляции 51 | void setTime(DateTime time); // установить из структуры DateTime 52 | void setTime(int8_t seconds, int8_t minutes, int8_t hours, int8_t date, int8_t month, int16_t year); // установка времени 53 | void setHMSDMY(int8_t hours, int8_t minutes, int8_t seconds, int8_t date, int8_t month, int16_t year); // установка времени тип 2 54 | 55 | DateTime getTime(void); // получить в структуру DateTime 56 | uint8_t getSeconds(void); // получить секунды 57 | uint8_t getMinutes(void); // получить минуты 58 | uint8_t getHours(void); // получить часы 59 | uint8_t getDay(void); // получить день недели 60 | uint8_t getDate(void); // получить число 61 | uint16_t getYear(void); // получить год 62 | uint8_t getMonth(void); // получить месяц 63 | 64 | String getTimeString(); // получить время как строку вида HH:MM:SS 65 | String getDateString(); // получить дату как строку вида DD.MM.YYYY 66 | void getTimeChar(char* array); // получить время как char array [8] вида HH:MM:SS 67 | void getDateChar(char* array); // получить дату как char array [10] вида DD.MM.YYYY 68 | 69 | bool lostPower(void); // проверка на сброс питания 70 | float getTemperatureFloat(void);// получить температуру float 71 | int getTemperature(void); // получить температуру int 72 | 73 | private: 74 | uint8_t encodeRegister(int8_t data); 75 | int getTemperatureRaw(void); 76 | uint8_t readRegister(uint8_t addr); 77 | uint8_t unpackRegister(uint8_t data); 78 | uint8_t unpackHours(uint8_t data); 79 | const uint8_t _addr; 80 | }; 81 | 82 | #endif -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.1/GyverFeed_v2.1.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Скетч к проекту "Автокормушка 2" 3 | - Страница проекта (схемы, описания): https://alexgyver.ru/gyverfeed2/ 4 | - Исходники на GitHub: https://github.com/AlexGyver/GyverFeed2/ 5 | Проблемы с загрузкой? Читай гайд для новичков: https://alexgyver.ru/arduino-first/ 6 | AlexGyver, AlexGyver Technologies, 2020 7 | */ 8 | 9 | // v2.1 - исправлен баг с невыключением мотора 10 | 11 | // Клик - внеочередная кормёжка 12 | // Удержание - задаём размер порции 13 | const byte feedTime[][2] = { 14 | {7, 0}, // часы, минуты. НЕ НАЧИНАТЬ ЧИСЛО С НУЛЯ 15 | {12, 0}, 16 | {17, 0}, 17 | {21, 0}, 18 | }; 19 | 20 | #define EE_RESET 12 // любое число 0-255. Измени, чтобы сбросить настройки и обновить время 21 | #define FEED_SPEED 3000 // задержка между шагами мотора (мкс) 22 | #define BTN_PIN 2 // кнопка 23 | #define STEPS_FRW 18 // шаги вперёд 24 | #define STEPS_BKW 10 // шаги назад 25 | const byte drvPins[] = {3, 4, 5, 6}; // драйвер (фазаА1, фазаА2, фазаВ1, фазаВ2) 26 | 27 | // ========================================================= 28 | #include "EncButton.h" 29 | #include 30 | #include 31 | RTC_DS3231 rtc; 32 | EncButton btn; 33 | int feedAmount = 100; 34 | 35 | void setup() { 36 | rtc.begin(); 37 | if (EEPROM.read(0) != EE_RESET) { // первый запуск 38 | EEPROM.write(0, EE_RESET); 39 | EEPROM.put(1, feedAmount); 40 | rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 41 | } 42 | EEPROM.get(1, feedAmount); 43 | for (byte i = 0; i < 4; i++) pinMode(drvPins[i], OUTPUT); // пины выходы 44 | } 45 | 46 | void loop() { 47 | static uint32_t tmr = 0; 48 | if (millis() - tmr > 500) { // два раза в секунду 49 | static byte prevMin = 0; 50 | tmr = millis(); 51 | DateTime now = rtc.now(); 52 | if (prevMin != now.minute()) { 53 | prevMin = now.minute(); 54 | for (byte i = 0; i < sizeof(feedTime) / 2; i++) // для всего расписания 55 | if (feedTime[i][0] == now.hour() && feedTime[i][1] == now.minute()) // пора кормить 56 | feed(); 57 | } 58 | } 59 | 60 | btn.tick(); 61 | if (btn.isClick()) { 62 | feed(); 63 | } 64 | if (btn.isHold()) { 65 | int newAmount = 0; 66 | while (btn.isHold()) { 67 | btn.tick(); 68 | oneRev(); 69 | newAmount++; 70 | } 71 | disableMotor(); 72 | feedAmount = newAmount; 73 | EEPROM.put(1, feedAmount); 74 | } 75 | } 76 | 77 | void feed() { 78 | for (int i = 0; i < feedAmount; i++) oneRev(); // крутим на количество feedAmount 79 | disableMotor(); 80 | } 81 | 82 | void disableMotor() { 83 | for (byte i = 0; i < 4; i++) digitalWrite(drvPins[i], 0); // выключаем ток на мотор 84 | } 85 | 86 | void oneRev() { 87 | static byte val = 0; 88 | for (byte i = 0; i < STEPS_BKW; i++) runMotor(val--); 89 | for (byte i = 0; i < STEPS_FRW; i++) runMotor(val++); 90 | } 91 | 92 | void runMotor(byte thisStep) { 93 | /*static const byte steps[] = {0b1000, 0b1010, 0b0010, 0b0110, 0b0100, 0b0101, 0b0001, 0b1001}; 94 | for (byte i = 0; i < 4; i++) 95 | digitalWrite(drvPins[i], bitRead(steps[thisStep & 0b111], i)); 96 | */ 97 | static const byte steps[] = {0b1010, 0b0110, 0b0101, 0b1001}; 98 | for (byte i = 0; i < 4; i++) 99 | digitalWrite(drvPins[i], bitRead(steps[thisStep & 0b11], i)); 100 | delayMicroseconds(FEED_SPEED); 101 | } 102 | -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.2/microDS3231.cpp: -------------------------------------------------------------------------------- 1 | #include "microDS3231.h" 2 | 3 | //static const uint8_t _ds_daysInMonth[] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 4 | static uint8_t _ds_DIM(uint8_t i) { 5 | return (i < 7) ? ((i == 1) ? 28 : ((i & 1) ? 30 : 31)) : ((i & 1) ? 31 : 30); 6 | } 7 | 8 | static uint16_t getWeekDay(uint16_t y, uint8_t m, uint8_t d) { 9 | if (y >= 2000) 10 | y -= 2000; 11 | uint16_t days = d; 12 | for (uint8_t i = 1; i < m; ++i) 13 | //days += pgm_read_byte(_ds_daysInMonth + i - 1); 14 | days += _ds_DIM(i - 1); 15 | if (m > 2 && y % 4 == 0) 16 | ++days; 17 | return (days + 365 * y + (y + 3) / 4 + 4) % 7 + 1; 18 | } 19 | 20 | // поiхали 21 | MicroDS3231::MicroDS3231(uint8_t addr) : _addr(addr) { 22 | Wire.begin(); 23 | } 24 | 25 | bool MicroDS3231::begin(void){ 26 | Wire.begin(); // Инит шины 27 | Wire.beginTransmission(_addr); // Зовем DS3231 по адресу 28 | return (!Wire.endTransmission()); // если никто не откликнулся - возвращаем false 29 | } 30 | 31 | void MicroDS3231::setTime(int8_t seconds, int8_t minutes, int8_t hours, int8_t date, int8_t month, int16_t year) { 32 | // защиты от дурака 33 | month = constrain(month, 1, 12); 34 | //date = constrain(date, 0, pgm_read_byte(_ds_daysInMonth + month - 1)); 35 | date = constrain(date, 0, _ds_DIM(month - 1)); 36 | seconds = constrain(seconds, 0, 59); 37 | minutes = constrain(minutes, 0, 59); 38 | hours = constrain(hours, 0, 23); 39 | 40 | // отправляем 41 | uint8_t day = getWeekDay(year, month, date); 42 | year -= 2000; 43 | Wire.beginTransmission(_addr); 44 | Wire.write(0x00); 45 | Wire.write(encodeRegister(seconds)); 46 | Wire.write(encodeRegister(minutes)); 47 | if (hours > 19) Wire.write((0x2 << 4) | (hours % 20)); 48 | else if (hours > 9) Wire.write((0x1 << 4) | (hours % 10)); 49 | else Wire.write(hours); 50 | Wire.write(day); 51 | Wire.write(encodeRegister(date)); 52 | Wire.write(encodeRegister(month)); 53 | Wire.write(encodeRegister(year)); 54 | Wire.endTransmission(); 55 | } 56 | 57 | void MicroDS3231::setHMSDMY(int8_t hours, int8_t minutes, int8_t seconds, int8_t date, int8_t month, int16_t year) { 58 | setTime(seconds, minutes, hours, date, month, year); 59 | } 60 | 61 | void MicroDS3231::setTime(DateTime time) { 62 | setTime(time.second, time.minute, time.hour, time.date, time.month, time.year); 63 | } 64 | 65 | static int charToDec(const char* p) { 66 | return (10 * (*p - '0') + (*++p - '0')); 67 | } 68 | 69 | void MicroDS3231::setTime(const __FlashStringHelper* stamp) { 70 | char buff[25]; 71 | memcpy_P(buff, stamp, 25); 72 | 73 | // Wed Jul 14 22:00:24 2021 74 | // 4 8 11 14 17 22 75 | // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec 76 | int h, m, s, d, mo, y; 77 | h = charToDec(buff + 11); 78 | m = charToDec(buff + 14); 79 | s = charToDec(buff + 17); 80 | d = charToDec(buff + 8); 81 | switch (buff[4]) { 82 | case 'J': mo = (buff[5] == 'a') ? 1 : (mo = (buff[6] == 'n') ? 6 : 7); break; 83 | case 'F': mo = 2; break; 84 | case 'A': mo = (buff[6] == 'r') ? 4 : 8; break; 85 | case 'M': mo = (buff[6] == 'r') ? 3 : 5; break; 86 | case 'S': mo = 9; break; 87 | case 'O': mo = 10; break; 88 | case 'N': mo = 11; break; 89 | case 'D': mo = 12; break; 90 | } 91 | y = 2000 + charToDec(buff + 22); 92 | setTime(s, m, h, d, mo, y); 93 | } 94 | 95 | DateTime MicroDS3231::getTime() { 96 | DateTime now; 97 | Wire.beginTransmission(_addr); 98 | Wire.write(0x0); 99 | if (Wire.endTransmission() != 0) return now; 100 | Wire.requestFrom(_addr, (uint8_t)7); 101 | now.second = unpackRegister(Wire.read()); 102 | now.minute = unpackRegister(Wire.read()); 103 | now.hour = unpackHours(Wire.read()); 104 | now.day = Wire.read(); 105 | now.date = unpackRegister(Wire.read()); 106 | now.month = unpackRegister(Wire.read()); 107 | now.year = unpackRegister(Wire.read()) + 2000; 108 | return now; 109 | } 110 | String MicroDS3231::getTimeString() { 111 | DateTime now = getTime(); 112 | String str = ""; 113 | if (now.hour < 10) str += '0'; 114 | str += now.hour; 115 | str += ':'; 116 | if (now.minute < 10) str += '0'; 117 | str += now.minute; 118 | str += ':'; 119 | if (now.second < 10) str += '0'; 120 | str += now.second; 121 | return str; 122 | } 123 | String MicroDS3231::getDateString() { 124 | DateTime now = getTime(); 125 | String str = ""; 126 | if (now.date < 10) str += '0'; 127 | str += now.date; 128 | str += '.'; 129 | if (now.month < 10) str += '0'; 130 | str += now.month; 131 | str += '.'; 132 | str += now.year; 133 | return str; 134 | } 135 | void MicroDS3231::getTimeChar(char* array) { 136 | DateTime now = getTime(); 137 | array[0] = now.hour / 10 + '0'; 138 | array[1] = now.hour % 10 + '0'; 139 | array[2] = ':'; 140 | array[3] = now.minute / 10 + '0'; 141 | array[4] = now.minute % 10 + '0'; 142 | array[5] = ':'; 143 | array[6] = now.second / 10 + '0'; 144 | array[7] = now.second % 10 + '0'; 145 | array[8] = '\0'; 146 | } 147 | void MicroDS3231::getDateChar(char* array) { 148 | DateTime now = getTime(); 149 | array[0] = now.date / 10 + '0'; 150 | array[1] = now.date % 10 + '0'; 151 | array[2] = '.'; 152 | array[3] = now.month / 10 + '0'; 153 | array[4] = now.month % 10 + '0'; 154 | array[5] = '.'; 155 | itoa(now.year, array + 6, DEC); 156 | array[10] = '\0'; 157 | } 158 | bool MicroDS3231::lostPower(void) { // возвращает true, если 1 января 2000 159 | if (getYear() == 2000 && getMonth() == 1 && getDate() == 1) return true; 160 | else return false; 161 | } 162 | 163 | uint8_t MicroDS3231::getSeconds(void) { 164 | return (unpackRegister(readRegister(0x00))); 165 | } 166 | 167 | uint8_t MicroDS3231::getMinutes(void) { 168 | return (unpackRegister(readRegister(0x01))); 169 | } 170 | 171 | uint8_t MicroDS3231::getHours(void) { 172 | return (unpackHours(readRegister(0x02))); 173 | } 174 | 175 | uint8_t MicroDS3231::getDay(void) { 176 | return readRegister(0x03); 177 | } 178 | 179 | uint8_t MicroDS3231::getDate(void) { 180 | return (unpackRegister(readRegister(0x04))); 181 | } 182 | 183 | uint8_t MicroDS3231::getMonth(void) { 184 | return (unpackRegister(readRegister(0x05))); 185 | } 186 | 187 | uint16_t MicroDS3231::getYear(void) { 188 | return (unpackRegister(readRegister(0x06)) + 2000); 189 | } 190 | 191 | // сервис 192 | uint8_t MicroDS3231::readRegister(uint8_t addr) { 193 | Wire.beginTransmission(_addr); 194 | Wire.write(addr); 195 | if (Wire.endTransmission() != 0) return 0; 196 | Wire.requestFrom(_addr, (uint8_t)1); 197 | uint8_t data = Wire.read(); 198 | return data; 199 | } 200 | 201 | uint8_t MicroDS3231::unpackRegister(uint8_t data) { 202 | return ((data >> 4) * 10 + (data & 0xF)); 203 | } 204 | 205 | uint8_t MicroDS3231::encodeRegister(int8_t data) { 206 | return (((data / 10) << 4) | (data % 10)); 207 | } 208 | 209 | uint8_t MicroDS3231::unpackHours(uint8_t data) { 210 | if (data & 0x20) return ((data & 0xF) + 20); 211 | else if (data & 0x10) return ((data & 0xF) + 10); 212 | else return (data & 0xF); 213 | } 214 | 215 | float MicroDS3231::getTemperatureFloat(void) { 216 | return (getTemperatureRaw() * 0.25f); 217 | } 218 | 219 | int MicroDS3231::getTemperature(void) { 220 | return (getTemperatureRaw() >> 2); 221 | } 222 | 223 | int MicroDS3231::getTemperatureRaw(void) { 224 | Wire.beginTransmission(_addr); 225 | Wire.write(0x11); 226 | Wire.endTransmission(); 227 | Wire.requestFrom(_addr, (uint8_t)2); 228 | return ((int8_t)Wire.read() << 2) + (Wire.read() >> 6); 229 | } -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.0/EncButton.h: -------------------------------------------------------------------------------- 1 | #ifndef EncButton_h 2 | #define EncButton_h 3 | /* 4 | Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки 5 | - Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13) 6 | - Быстрые и лёгкие алгоритмы кнопки и энкодера 7 | - Оптимизированный вес 8 | - Гашение дребезга кнопки 9 | - Клик, несколько кликов, удержание, режим step 10 | - Встроенный счётчик энкодера 11 | - Подключение - high pull 12 | */ 13 | 14 | // =========== НАСТРОЙКИ ============ 15 | #define EB_FAST 30 // таймаут быстрого поворота 16 | #define EB_DEB 80 // дебаунс кнопки 17 | #define EB_HOLD 1000 // таймаут удержания кнопки 18 | #define EB_STEP 500 // период срабатывания степ 19 | #define EB_CLICK 400 // таймаут накликивания 20 | 21 | // =========== НЕ ТРОГАЙ ============ 22 | #include 23 | // флаг макро 24 | #define _setFlag(x) (flags |= 1 << x) 25 | #define _clrFlag(x) (flags &= ~(1 << x)) 26 | #define _readFlag(x) ((flags >> x) & 1) 27 | 28 | // быстрое чтение пина 29 | bool fastRead(const uint8_t pin) { 30 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 31 | if (pin < 8) return bitRead(PIND, pin); 32 | else if (pin < 14) return bitRead(PINB, pin - 8); 33 | else if (pin < 20) return bitRead(PINC, pin - 14); 34 | 35 | #elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__) 36 | return bitRead(PINB, pin); 37 | 38 | #elif defined(AVR) 39 | uint8_t *_pin_reg = portInputRegister(digitalPinToPort(pin)); 40 | uint8_t _bit_mask = digitalPinToBitMask(pin); 41 | return bool(*_pin_reg & _bit_mask); 42 | 43 | #else 44 | return digitalRead(pin); 45 | 46 | #endif 47 | return 0; 48 | } 49 | 50 | // класс 51 | template < uint8_t S1, uint8_t S2 = 255, uint8_t KEY = 255 > 52 | class EncButton { 53 | public: 54 | EncButton() { 55 | if (S2 == 255) { // обычная кнопка 56 | pinMode(S1, INPUT_PULLUP); 57 | } else if (KEY == 255) { // энк без кнопки 58 | pinMode(S1, INPUT_PULLUP); 59 | pinMode(S2, INPUT_PULLUP); 60 | } else { // энк с кнопкой 61 | pinMode(S1, INPUT_PULLUP); 62 | pinMode(S2, INPUT_PULLUP); 63 | pinMode(KEY, INPUT_PULLUP); 64 | } 65 | } 66 | 67 | void tick(bool hold = 0) { 68 | uint32_t thisMls = millis(); 69 | uint32_t debounce = thisMls - _debTimer; 70 | 71 | // обработка энка (компилятор вырежет блок если не используется) 72 | if (S1 != 255 && S2 != 255) { 73 | byte state = fastRead(S1) | (fastRead(S2) << 1); // получаем код 74 | if (_readFlag(0) && state == 0b11) { // ресет и энк защёлкнул позицию 75 | if (S2 == 255 || KEY != 255) { // энкодер с кнопкой 76 | if (!_readFlag(4)) { // если кнопка не "удерживается" 77 | if (_lastState == 0b10) EBState = (_btnState || hold) ? 3 : 1, counter++; 78 | else if (_lastState == 0b01) EBState = (_btnState || hold) ? 4 : 2, counter--; 79 | } 80 | } else { // просто энкодер 81 | if (_lastState == 0b10) EBState = 1, counter++; 82 | else if (_lastState == 0b01) EBState = 2, counter--; 83 | } 84 | if (EBState != 0 && debounce < EB_FAST) _setFlag(1); // режим быстрого поворота 85 | else _clrFlag(1); 86 | _clrFlag(0); 87 | _debTimer = thisMls; 88 | } 89 | if (state == 0b00) _setFlag(0); 90 | _lastState = state; 91 | } 92 | 93 | // обработка кнопки (компилятор вырежет блок если не используется) 94 | if (S2 == 255 || KEY != 255) { 95 | if (S2 == 255) _btnState = !fastRead(S1); // обычная кнопка 96 | if (KEY != 255) _btnState = !fastRead(KEY); // энк с кнопкой 97 | 98 | if (_btnState) { // кнопка нажата 99 | if (!_readFlag(3)) { // и не была нажата ранее 100 | if (debounce > EB_DEB) { // и прошел дебаунс 101 | _setFlag(3); // флаг кнопка была нажата 102 | _debTimer = thisMls; // сброс таймаутов 103 | EBState = 0; // сброс состояния 104 | } 105 | if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK 106 | clicks = 0; // сбросить счётчик и флаг кликов 107 | flags &= ~0b01100000; 108 | } 109 | } else { // кнопка уже была нажата 110 | if (!_readFlag(4)) { // и удержание ещё не зафиксировано 111 | if (debounce < EB_HOLD) { // прошло меньше удержания 112 | if (EBState != 0) _setFlag(2); // но энкодер повёрнут! Запомнили 113 | } else { // прошло больше времени удержания 114 | if (!_readFlag(2)) { // и энкодер не повёрнут 115 | EBState = 6; // значит это удержание (сигнал) 116 | _setFlag(4); // запомнили что удерживается 117 | _debTimer = thisMls; // сброс таймаута 118 | } 119 | } 120 | } else { // удержание зафиксировано 121 | if (debounce > EB_STEP) { // таймер степа 122 | EBState = 7; // сигналим 123 | _debTimer = thisMls; // сброс таймаута 124 | } 125 | } 126 | } 127 | } else { // кнопка не нажата 128 | if (_readFlag(3)) { // но была нажата 129 | if (debounce > EB_DEB && !_readFlag(4) && !_readFlag(2)) { // энкодер не трогали и не удерживали - это клик 130 | EBState = 5; 131 | clicks++; 132 | } 133 | flags &= ~0b00011100; // clear 2 3 4 134 | _debTimer = thisMls; // сброс таймаута 135 | } else if (clicks > 0 && debounce > EB_CLICK && !_readFlag(5)) flags |= 0b01100000; // флаг на клики 136 | } 137 | } 138 | } 139 | 140 | byte getState() { return EBState; } 141 | void resetState() { EBState = 0; } 142 | bool isFast() { return _readFlag(1); } 143 | bool isTurn() { return (EBState > 0 && EBState < 5); } 144 | bool isRight() { return checkState(1); } 145 | bool isLeft() { return checkState(2); } 146 | bool isRightH() { return checkState(3); } 147 | bool isLeftH() { return checkState(4); } 148 | bool isClick() { return checkState(5); } 149 | bool isHolded() { return checkState(6); } 150 | bool isHold() { return _readFlag(4); } 151 | bool isStep() { return checkState(7); } 152 | bool state() { return !fastRead(S1); } 153 | bool hasClicks(byte numClicks) { 154 | if (clicks == numClicks && _readFlag(6)) { 155 | _clrFlag(6); 156 | return 1; 157 | } 158 | return 0; 159 | } 160 | byte hasClicks() { 161 | if (_readFlag(6)) { 162 | _clrFlag(6); 163 | return clicks; 164 | } return 0; 165 | } 166 | 167 | int counter = 0; 168 | byte clicks = 0; 169 | 170 | private: 171 | bool checkState(byte val) { 172 | if (EBState == val) { 173 | EBState = 0; 174 | return 1; 175 | } return 0; 176 | } 177 | uint32_t _debTimer = 0; 178 | byte _lastState = 0, EBState = 0; 179 | bool _btnState = 0; 180 | byte flags = 0; 181 | 182 | // flags 183 | // 0 - enc reset 184 | // 1 - enc fast 185 | // 2 - enc был поворот 186 | // 3 - флаг кнопки 187 | // 4 - hold 188 | // 5 - clicks flag 189 | // 6 - clicks get 190 | // 7 - reserved 191 | 192 | // EBState 193 | // 0 - idle 194 | // 1 - right 195 | // 2 - left 196 | // 3 - rightH 197 | // 4 - leftH 198 | // 5 - click 199 | // 6 - holded 200 | // 7 - step 201 | }; 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.1/EncButton.h: -------------------------------------------------------------------------------- 1 | #ifndef EncButton_h 2 | #define EncButton_h 3 | /* 4 | Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки 5 | - Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13) 6 | - Быстрые и лёгкие алгоритмы кнопки и энкодера 7 | - Оптимизированный вес 8 | - Гашение дребезга кнопки 9 | - Клик, несколько кликов, удержание, режим step 10 | - Встроенный счётчик энкодера 11 | - Подключение - high pull 12 | */ 13 | 14 | // =========== НАСТРОЙКИ ============ 15 | #define EB_FAST 30 // таймаут быстрого поворота 16 | #define EB_DEB 80 // дебаунс кнопки 17 | #define EB_HOLD 1000 // таймаут удержания кнопки 18 | #define EB_STEP 500 // период срабатывания степ 19 | #define EB_CLICK 400 // таймаут накликивания 20 | 21 | // =========== НЕ ТРОГАЙ ============ 22 | #include 23 | // флаг макро 24 | #define _setFlag(x) (flags |= 1 << x) 25 | #define _clrFlag(x) (flags &= ~(1 << x)) 26 | #define _readFlag(x) ((flags >> x) & 1) 27 | 28 | // быстрое чтение пина 29 | bool fastRead(const uint8_t pin) { 30 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 31 | if (pin < 8) return bitRead(PIND, pin); 32 | else if (pin < 14) return bitRead(PINB, pin - 8); 33 | else if (pin < 20) return bitRead(PINC, pin - 14); 34 | 35 | #elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__) 36 | return bitRead(PINB, pin); 37 | 38 | #elif defined(AVR) 39 | uint8_t *_pin_reg = portInputRegister(digitalPinToPort(pin)); 40 | uint8_t _bit_mask = digitalPinToBitMask(pin); 41 | return bool(*_pin_reg & _bit_mask); 42 | 43 | #else 44 | return digitalRead(pin); 45 | 46 | #endif 47 | return 0; 48 | } 49 | 50 | // класс 51 | template < uint8_t S1, uint8_t S2 = 255, uint8_t KEY = 255 > 52 | class EncButton { 53 | public: 54 | EncButton() { 55 | if (S2 == 255) { // обычная кнопка 56 | pinMode(S1, INPUT_PULLUP); 57 | } else if (KEY == 255) { // энк без кнопки 58 | pinMode(S1, INPUT_PULLUP); 59 | pinMode(S2, INPUT_PULLUP); 60 | } else { // энк с кнопкой 61 | pinMode(S1, INPUT_PULLUP); 62 | pinMode(S2, INPUT_PULLUP); 63 | pinMode(KEY, INPUT_PULLUP); 64 | } 65 | } 66 | 67 | void tick(bool hold = 0) { 68 | uint32_t thisMls = millis(); 69 | uint32_t debounce = thisMls - _debTimer; 70 | 71 | // обработка энка (компилятор вырежет блок если не используется) 72 | if (S1 != 255 && S2 != 255) { 73 | byte state = fastRead(S1) | (fastRead(S2) << 1); // получаем код 74 | if (_readFlag(0) && state == 0b11) { // ресет и энк защёлкнул позицию 75 | if (S2 == 255 || KEY != 255) { // энкодер с кнопкой 76 | if (!_readFlag(4)) { // если кнопка не "удерживается" 77 | if (_lastState == 0b10) EBState = (_btnState || hold) ? 3 : 1, counter++; 78 | else if (_lastState == 0b01) EBState = (_btnState || hold) ? 4 : 2, counter--; 79 | } 80 | } else { // просто энкодер 81 | if (_lastState == 0b10) EBState = 1, counter++; 82 | else if (_lastState == 0b01) EBState = 2, counter--; 83 | } 84 | if (EBState != 0 && debounce < EB_FAST) _setFlag(1); // режим быстрого поворота 85 | else _clrFlag(1); 86 | _clrFlag(0); 87 | _debTimer = thisMls; 88 | } 89 | if (state == 0b00) _setFlag(0); 90 | _lastState = state; 91 | } 92 | 93 | // обработка кнопки (компилятор вырежет блок если не используется) 94 | if (S2 == 255 || KEY != 255) { 95 | if (S2 == 255) _btnState = !fastRead(S1); // обычная кнопка 96 | if (KEY != 255) _btnState = !fastRead(KEY); // энк с кнопкой 97 | 98 | if (_btnState) { // кнопка нажата 99 | if (!_readFlag(3)) { // и не была нажата ранее 100 | if (debounce > EB_DEB) { // и прошел дебаунс 101 | _setFlag(3); // флаг кнопка была нажата 102 | _debTimer = thisMls; // сброс таймаутов 103 | EBState = 0; // сброс состояния 104 | } 105 | if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK 106 | clicks = 0; // сбросить счётчик и флаг кликов 107 | flags &= ~0b01100000; 108 | } 109 | } else { // кнопка уже была нажата 110 | if (!_readFlag(4)) { // и удержание ещё не зафиксировано 111 | if (debounce < EB_HOLD) { // прошло меньше удержания 112 | if (EBState != 0) _setFlag(2); // но энкодер повёрнут! Запомнили 113 | } else { // прошло больше времени удержания 114 | if (!_readFlag(2)) { // и энкодер не повёрнут 115 | EBState = 6; // значит это удержание (сигнал) 116 | _setFlag(4); // запомнили что удерживается 117 | _debTimer = thisMls; // сброс таймаута 118 | } 119 | } 120 | } else { // удержание зафиксировано 121 | if (debounce > EB_STEP) { // таймер степа 122 | EBState = 7; // сигналим 123 | _debTimer = thisMls; // сброс таймаута 124 | } 125 | } 126 | } 127 | } else { // кнопка не нажата 128 | if (_readFlag(3)) { // но была нажата 129 | if (debounce > EB_DEB && !_readFlag(4) && !_readFlag(2)) { // энкодер не трогали и не удерживали - это клик 130 | EBState = 5; 131 | clicks++; 132 | } 133 | flags &= ~0b00011100; // clear 2 3 4 134 | _debTimer = thisMls; // сброс таймаута 135 | } else if (clicks > 0 && debounce > EB_CLICK && !_readFlag(5)) flags |= 0b01100000; // флаг на клики 136 | } 137 | } 138 | } 139 | 140 | byte getState() { return EBState; } 141 | void resetState() { EBState = 0; } 142 | bool isFast() { return _readFlag(1); } 143 | bool isTurn() { return (EBState > 0 && EBState < 5); } 144 | bool isRight() { return checkState(1); } 145 | bool isLeft() { return checkState(2); } 146 | bool isRightH() { return checkState(3); } 147 | bool isLeftH() { return checkState(4); } 148 | bool isClick() { return checkState(5); } 149 | bool isHolded() { return checkState(6); } 150 | bool isHold() { return _readFlag(4); } 151 | bool isStep() { return checkState(7); } 152 | bool state() { return !fastRead(S1); } 153 | bool hasClicks(byte numClicks) { 154 | if (clicks == numClicks && _readFlag(6)) { 155 | _clrFlag(6); 156 | return 1; 157 | } 158 | return 0; 159 | } 160 | byte hasClicks() { 161 | if (_readFlag(6)) { 162 | _clrFlag(6); 163 | return clicks; 164 | } return 0; 165 | } 166 | 167 | int counter = 0; 168 | byte clicks = 0; 169 | 170 | private: 171 | bool checkState(byte val) { 172 | if (EBState == val) { 173 | EBState = 0; 174 | return 1; 175 | } return 0; 176 | } 177 | uint32_t _debTimer = 0; 178 | byte _lastState = 0, EBState = 0; 179 | bool _btnState = 0; 180 | byte flags = 0; 181 | 182 | // flags 183 | // 0 - enc reset 184 | // 1 - enc fast 185 | // 2 - enc был поворот 186 | // 3 - флаг кнопки 187 | // 4 - hold 188 | // 5 - clicks flag 189 | // 6 - clicks get 190 | // 7 - reserved 191 | 192 | // EBState 193 | // 0 - idle 194 | // 1 - right 195 | // 2 - left 196 | // 3 - rightH 197 | // 4 - leftH 198 | // 5 - click 199 | // 6 - holded 200 | // 7 - step 201 | }; 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /firmware/GyverFeed_v2.2/EncButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки 3 | Документация: 4 | GitHub: https://github.com/GyverLibs/EncButton 5 | Возможности: 6 | - Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13) 7 | - Оптимизированный вес 8 | - Быстрые и лёгкие алгоритмы кнопки и энкодера 9 | - Энкодер: поворот, нажатый поворот, быстрый поворот, счётчик 10 | - Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим step 11 | - Подключение - только HIGH PULL! 12 | - Опциональный режим callback (+22б SRAM на каждый экземпляр) 13 | 14 | AlexGyver, alex@alexgyver.ru 15 | https://alexgyver.ru/ 16 | MIT License 17 | Опционально используется алгоритм из библиотеки // https://github.com/mathertel/RotaryEncoder 18 | 19 | Версии: 20 | v1.1 - пуллап отдельныи методом 21 | v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч) 22 | v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения 23 | v1.4 - обработка нажатия и отпускания кнопки 24 | v1.5 - добавлен виртуальный режим 25 | v1.6 - оптимизация работы в прерывании 26 | v1.6.1 - PULLUP по умолчанию 27 | v1.7 - большая оптимизация памяти, переделан FastIO 28 | v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех) 29 | v1.8.1 - убран FastIO 30 | v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления 31 | v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги 32 | v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки 33 | v1.11.1 - совместимость Digispark 34 | v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC 35 | v1.13 - добавлен экспериментальный EncButton2 36 | v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс 37 | v1.15 - добавлен setPins() для EncButton2 38 | v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров 39 | */ 40 | 41 | #ifndef _EncButton_h 42 | #define _EncButton_h 43 | 44 | // ========= НАСТРОЙКИ (можно передефайнить из скетча) ========== 45 | #define _EB_FAST 30 // таймаут быстрого поворота 46 | #define _EB_DEB 50 // дебаунс кнопки 47 | #define _EB_HOLD 1000 // таймаут удержания кнопки 48 | #define _EB_STEP 500 // период срабатывания степ 49 | #define _EB_CLICK 400 // таймаут накликивания 50 | //#define EB_BETTER_ENC // точный алгоритм отработки энкодера (можно задефайнить в скетче) 51 | 52 | // =========== НЕ ТРОГАЙ ============ 53 | #include 54 | 55 | #ifndef nullptr 56 | #define nullptr NULL 57 | #endif 58 | 59 | // флаг макро 60 | #define _EB_setFlag(x) (flags |= 1 << x) 61 | #define _EB_clrFlag(x) (flags &= ~(1 << x)) 62 | #define _EB_readFlag(x) ((flags >> x) & 1) 63 | 64 | #ifndef EB_FAST 65 | #define EB_FAST _EB_FAST 66 | #endif 67 | #ifndef EB_DEB 68 | #define EB_DEB _EB_DEB 69 | #endif 70 | #ifndef EB_HOLD 71 | #define EB_HOLD _EB_HOLD 72 | #endif 73 | #ifndef EB_STEP 74 | #define EB_STEP _EB_STEP 75 | #endif 76 | #ifndef EB_CLICK 77 | #define EB_CLICK _EB_CLICK 78 | #endif 79 | 80 | enum eb_callback { 81 | TURN_HANDLER, // 0 82 | LEFT_HANDLER, // 1 83 | RIGHT_HANDLER, // 2 84 | LEFT_H_HANDLER, // 3 85 | RIGHT_H_HANDLER, // 4 86 | CLICK_HANDLER, // 5 87 | HOLDED_HANDLER, // 6 88 | STEP_HANDLER, // 7 89 | PRESS_HANDLER, // 8 90 | CLICKS_HANDLER, // 9 91 | RELEASE_HANDLER, // 10 92 | HOLD_HANDLER, // 11 93 | TURN_H_HANDLER, // 12 94 | // clicks amount 13 95 | }; 96 | 97 | // константы 98 | #define EB_TICK 0 99 | #define EB_CALLBACK 1 100 | 101 | #define EB_NO_PIN 255 102 | 103 | #define VIRT_ENC 254 104 | #define VIRT_ENCBTN 253 105 | #define VIRT_BTN 252 106 | 107 | #ifdef EB_BETTER_ENC 108 | static const int8_t _EB_DIR[] = { 109 | 0, -1, 1, 0, 110 | 1, 0, 0, -1, 111 | -1, 0, 0, 1, 112 | 0, 1, -1, 0 113 | }; 114 | #endif 115 | 116 | // ===================================== CLASS ===================================== 117 | template < uint8_t _EB_MODE, uint8_t _S1 = EB_NO_PIN, uint8_t _S2 = EB_NO_PIN, uint8_t _KEY = EB_NO_PIN > 118 | class EncButton { 119 | public: 120 | // можно указать режим работы пина 121 | EncButton(const uint8_t mode = INPUT_PULLUP) { 122 | if (_S1 < 252 && mode == INPUT_PULLUP) pullUp(); 123 | setButtonLevel(LOW); 124 | } 125 | 126 | // подтянуть пины внутренней подтяжкой 127 | void pullUp() { 128 | if (_S1 < 252) { // реальное устройство 129 | if (_S2 == EB_NO_PIN) { // обычная кнопка 130 | pinMode(_S1, INPUT_PULLUP); 131 | } else if (_KEY == EB_NO_PIN) { // энк без кнопки 132 | pinMode(_S1, INPUT_PULLUP); 133 | pinMode(_S2, INPUT_PULLUP); 134 | } else { // энк с кнопкой 135 | pinMode(_S1, INPUT_PULLUP); 136 | pinMode(_S2, INPUT_PULLUP); 137 | pinMode(_KEY, INPUT_PULLUP); 138 | } 139 | } 140 | } 141 | 142 | // установить таймаут удержания кнопки для isHold(), мс (до 30 000) 143 | void setHoldTimeout(int tout) { 144 | _holdT = tout >> 7; 145 | } 146 | 147 | // виртуально зажать кнопку энкодера 148 | void holdEncButton(bool state) { 149 | if (state) _EB_setFlag(8); 150 | else _EB_clrFlag(8); 151 | } 152 | 153 | // уровень кнопки: LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC 154 | void setButtonLevel(bool level) { 155 | if (level) _EB_clrFlag(11); 156 | else _EB_setFlag(11); 157 | } 158 | 159 | // ===================================== TICK ===================================== 160 | // тикер, вызывать как можно чаще 161 | // вернёт отличное от нуля значение, если произошло какое то событие 162 | uint8_t tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) { 163 | tickISR(s1, s2, key); 164 | checkCallback(); 165 | return EBState; 166 | } 167 | 168 | // тикер специально для прерывания, не проверяет коллбэки 169 | uint8_t tickISR(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) { 170 | if (!_isrFlag) { 171 | _isrFlag = 1; 172 | 173 | // обработка энка (компилятор вырежет блок если не используется) 174 | // если объявлены два пина или выбран вирт. энкодер или энкодер с кнопкой 175 | if ((_S1 < 252 && _S2 < 252) || _S1 == VIRT_ENC || _S1 == VIRT_ENCBTN) { 176 | uint8_t state; 177 | if (_S1 >= 252) state = s1 | (s2 << 1); // получаем код 178 | else state = fastRead(_S1) | (fastRead(_S2) << 1); // получаем код 179 | poolEnc(state); 180 | } 181 | 182 | // обработка кнопки (компилятор вырежет блок если не используется) 183 | // если S2 не указан (кнопка) или указан KEY или выбран вирт. энкодер с кнопкой или кнопка 184 | if ((_S1 < 252 && _S2 == EB_NO_PIN) || _KEY != EB_NO_PIN || _S1 == VIRT_BTN || _S1 == VIRT_ENCBTN) { 185 | if (_S1 < 252 && _S2 == EB_NO_PIN) _btnState = fastRead(_S1); // обычная кнопка 186 | if (_KEY != EB_NO_PIN) _btnState = fastRead(_KEY); // энк с кнопкой 187 | if (_S1 == VIRT_BTN) _btnState = s1; // вирт кнопка 188 | if (_S1 == VIRT_ENCBTN) _btnState = key; // вирт энк с кнопкой 189 | _btnState ^= _EB_readFlag(11); // инверсия кнопки 190 | poolBtn(); 191 | } 192 | } 193 | _isrFlag = 0; 194 | return EBState; 195 | } 196 | 197 | // ===================================== CALLBACK ===================================== 198 | // проверить callback, чтобы не дёргать в прерывании 199 | void checkCallback() { 200 | if (_EB_MODE) { 201 | if (turn()) exec(0); 202 | if (turnH()) exec(12); 203 | if (EBState > 0 && EBState <= 8) exec(EBState); 204 | if (release()) exec(10); 205 | if (hold()) exec(11); 206 | if (checkFlag(6)) { 207 | exec(9); 208 | if (clicks == _amount) exec(13); 209 | } 210 | EBState = 0; 211 | } 212 | } 213 | 214 | // подключить обработчик 215 | void attach(eb_callback type, void (*handler)()) { 216 | _callback[type] = *handler; 217 | } 218 | 219 | // отключить обработчик 220 | void detach(eb_callback type) { 221 | _callback[type] = nullptr; 222 | } 223 | 224 | // подключить обработчик на количество кликов (может быть только один!) 225 | void attachClicks(uint8_t amount, void (*handler)()) { 226 | _amount = amount; 227 | _callback[13] = *handler; 228 | } 229 | 230 | // отключить обработчик на количество кликов 231 | void detachClicks() { 232 | _callback[13] = nullptr; 233 | } 234 | 235 | // ===================================== STATUS ===================================== 236 | uint8_t getState() { return EBState; } // получить статус 237 | void resetState() { EBState = 0; } // сбросить статус 238 | 239 | // ======================================= ENC ======================================= 240 | bool left() { return checkState(1); } // поворот влево 241 | bool right() { return checkState(2); } // поворот вправо 242 | bool leftH() { return checkState(3); } // поворот влево нажатый 243 | bool rightH() { return checkState(4); } // поворот вправо нажатый 244 | 245 | bool fast() { return _EB_readFlag(1); } // быстрый поворот 246 | bool turn() { return checkFlag(0); } // энкодер повёрнут 247 | bool turnH() { return checkFlag(9); } // энкодер повёрнут нажато 248 | int8_t getDir() { return _dir; } // направление последнего поворота, 1 или -1 249 | int16_t counter = 0; // счётчик энкодера 250 | 251 | // ======================================= BTN ======================================= 252 | bool press() { return checkState(8); } // кнопка нажата 253 | bool release() { return checkFlag(10); } // кнопка отпущена 254 | bool click() { return checkState(5); } // клик по кнопке 255 | bool held() { return checkState(6); } // кнопка удержана 256 | bool hold() { return _EB_readFlag(4); } // кнопка удерживается 257 | bool step() { return checkState(7); } // режим импульсного удержания 258 | bool state() { return _btnState; } // статус кнопки 259 | bool releaseStep() {return checkFlag(12);} // кнопка отпущена после импульсного удержания 260 | 261 | uint8_t clicks = 0; // счётчик кликов 262 | bool hasClicks(uint8_t num) { return (clicks == num && checkFlag(7)) ? 1 : 0; } // имеются клики 263 | uint8_t hasClicks() { return checkFlag(6) ? clicks : 0; } // имеются клики 264 | 265 | // =================================================================================== 266 | // =================================== DEPRECATED ==================================== 267 | bool isStep() { return step(); } 268 | bool isHold() { return hold(); } 269 | bool isHolded() { return held(); } 270 | bool isHeld() { return held(); } 271 | bool isClick() { return click(); } 272 | bool isRelease() { return release(); } 273 | bool isPress() { return press(); } 274 | bool isTurnH() { return turnH(); } 275 | bool isTurn() { return turn(); } 276 | bool isFast() { return fast(); } 277 | bool isLeftH() { return leftH(); } 278 | bool isRightH() { return rightH(); } 279 | bool isLeft() { return left(); } 280 | bool isRight() { return right(); } 281 | 282 | // ===================================== PRIVATE ===================================== 283 | private: 284 | bool fastRead(const uint8_t pin) { 285 | #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) 286 | if (pin < 8) return bitRead(PIND, pin); 287 | else if (pin < 14) return bitRead(PINB, pin - 8); 288 | else if (pin < 20) return bitRead(PINC, pin - 14); 289 | #elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__) 290 | return bitRead(PINB, pin); 291 | #else 292 | return digitalRead(pin); 293 | #endif 294 | return 0; 295 | } 296 | 297 | // ===================================== POOL ENC ===================================== 298 | void poolEnc(uint8_t state) { 299 | #ifdef EB_BETTER_ENC 300 | if (_prev != state) { 301 | _ecount += _EB_DIR[state | (_prev << 2)]; // сдвиг внутреннего счётчика 302 | _prev = state; 303 | #ifdef EB_HALFSTEP_ENC // полушаговый энкодер 304 | // спасибо https://github.com/GyverLibs/EncButton/issues/10#issue-1092009489 305 | if ((state == 0x3 || state == 0x0) && _ecount != 0) { 306 | #else // полношаговый 307 | if (state == 0x3 && _ecount != 0) { // защёлкнули позицию 308 | #endif 309 | EBState = (_ecount < 0) ? 1 : 2; 310 | _ecount = 0; 311 | if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой 312 | if (!_EB_readFlag(4) && (_btnState || _EB_readFlag(8))) EBState += 2; // если кнопка не "удерживается" 313 | } 314 | _dir = (EBState & 1) ? -1 : 1; // направление 315 | counter += _dir; // счётчик 316 | if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера 317 | else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера 318 | if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот 319 | else _EB_clrFlag(1); // обычный поворот 320 | _debTimer = millis(); 321 | } 322 | } 323 | #else 324 | if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию 325 | if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой 326 | if ((_prev == 1 || _prev == 2) && !_EB_readFlag(4)) { // если кнопка не "удерживается" и энкодер в позиции 1 или 2 327 | EBState = _prev; 328 | if (_btnState || _EB_readFlag(8)) EBState += 2; 329 | } 330 | } else { // просто энкодер 331 | if (_prev == 1 || _prev == 2) EBState = _prev; 332 | } 333 | 334 | if (EBState > 0) { // был поворот 335 | _dir = (EBState & 1) ? -1 : 1; // направление 336 | counter += _dir; // счётчик 337 | if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера 338 | else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера 339 | if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот 340 | else _EB_clrFlag(1); // обычный поворот 341 | } 342 | 343 | _encRST = 0; 344 | _debTimer = millis(); 345 | } 346 | if (state == 0b00) _encRST = 1; 347 | _prev = state; 348 | #endif 349 | } 350 | 351 | // ===================================== POOL BTN ===================================== 352 | void poolBtn() { 353 | uint32_t thisMls = millis(); 354 | uint32_t debounce = thisMls - _debTimer; 355 | if (_btnState) { // кнопка нажата 356 | if (!_EB_readFlag(3)) { // и не была нажата ранее 357 | if (debounce > EB_DEB) { // и прошел дебаунс 358 | _EB_setFlag(3); // флаг кнопка была нажата 359 | _debTimer = thisMls; // сброс таймаутов 360 | EBState = 8; // кнопка нажата 361 | } 362 | if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK 363 | clicks = 0; // сбросить счётчик и флаг кликов 364 | flags &= ~0b0011000011100000; // clear 5 6 7 12 13 (клики) 365 | } 366 | } else { // кнопка уже была нажата 367 | if (!_EB_readFlag(4)) { // и удержание ещё не зафиксировано 368 | if (debounce < (_holdT << 7)) { // прошло меньше удержания 369 | if (EBState != 0 && EBState != 8) _EB_setFlag(2); // но энкодер повёрнут! Запомнили 370 | } else { // прошло больше времени удержания 371 | if (!_EB_readFlag(2)) { // и энкодер не повёрнут 372 | EBState = 6; // значит это удержание (сигнал) 373 | _EB_setFlag(4); // запомнили что удерживается 374 | _debTimer = thisMls; // сброс таймаута 375 | } 376 | } 377 | } else { // удержание зафиксировано 378 | if (debounce > EB_STEP) { // таймер степа 379 | EBState = 7; // сигналим 380 | _EB_setFlag(13); // зафиксирован режим step 381 | _debTimer = thisMls; // сброс таймаута 382 | } 383 | } 384 | } 385 | } else { // кнопка не нажата 386 | if (_EB_readFlag(3)) { // но была нажата 387 | if (debounce > EB_DEB) { 388 | if (!_EB_readFlag(4) && !_EB_readFlag(2)) { // энкодер не трогали и не удерживали - это клик 389 | EBState = 5; 390 | clicks++; 391 | } 392 | flags &= ~0b00011100; // clear 2 3 4 393 | _debTimer = thisMls; // сброс таймаута 394 | _EB_setFlag(10); // кнопка отпущена 395 | if (checkFlag(13)) _EB_setFlag(12); // кнопка отпущена после step 396 | } 397 | } else if (clicks > 0 && debounce > EB_CLICK && !_EB_readFlag(5)) flags |= 0b11100000; // set 5 6 7 (клики) 398 | } 399 | } 400 | 401 | // ===================================== MISC ===================================== 402 | bool checkState(uint8_t val) { 403 | if (EBState == val) { 404 | EBState = 0; 405 | return 1; 406 | } return 0; 407 | } 408 | bool checkFlag(uint8_t val) { 409 | if (_EB_readFlag(val)) { 410 | _EB_clrFlag(val); 411 | return 1; 412 | } return 0; 413 | } 414 | void exec(uint8_t num) { 415 | if (*_callback[num]) _callback[num](); 416 | } 417 | 418 | uint8_t _prev : 2; 419 | uint8_t EBState : 4; 420 | bool _btnState : 1; 421 | bool _encRST : 1; 422 | bool _isrFlag : 1; 423 | uint16_t flags = 0; 424 | 425 | #ifdef EB_BETTER_ENC 426 | int8_t _ecount = 0; 427 | #endif 428 | 429 | uint32_t _debTimer = 0; 430 | uint8_t _holdT = (EB_HOLD >> 7); 431 | int8_t _dir = 0; 432 | void (*_callback[_EB_MODE ? 14 : 0])() = {}; 433 | uint8_t _amount = 0; 434 | 435 | // flags 436 | // 0 - enc turn 437 | // 1 - enc fast 438 | // 2 - enc был поворот 439 | // 3 - флаг кнопки 440 | // 4 - hold 441 | // 5 - clicks flag 442 | // 6 - clicks get 443 | // 7 - clicks get num 444 | // 8 - enc button hold 445 | // 9 - enc turn holded 446 | // 10 - btn released 447 | // 11 - btn level 448 | // 12 - btn released after step 449 | // 13 - step flag 450 | 451 | // EBState 452 | // 0 - idle 453 | // 1 - left 454 | // 2 - right 455 | // 3 - leftH 456 | // 4 - rightH 457 | // 5 - click 458 | // 6 - held 459 | // 7 - step 460 | // 8 - press 461 | }; 462 | 463 | #endif --------------------------------------------------------------------------------