commit 8e44a9be9642a55aba30d3d38ab64c75bf611d54 Author: George Butskivsky Date: Wed Nov 15 18:07:30 2023 +0300 new file: EncButton.h new file: buildTime.h new file: feeder_v2.1.ino new file: microDS3231.cpp new file: microDS3231.h diff --git a/EncButton.h b/EncButton.h new file mode 100644 index 0000000..d945120 --- /dev/null +++ b/EncButton.h @@ -0,0 +1,463 @@ +/* + Ультра лёгкая и быстрая библиотека для энкодера, энкодера с кнопкой или просто кнопки + Документация: + GitHub: https://github.com/GyverLibs/EncButton + Возможности: + - Максимально быстрое чтение пинов для AVR (ATmega328/ATmega168, ATtiny85/ATtiny13) + - Оптимизированный вес + - Быстрые и лёгкие алгоритмы кнопки и энкодера + - Энкодер: поворот, нажатый поворот, быстрый поворот, счётчик + - Кнопка: антидребезг, клик, несколько кликов, счётчик кликов, удержание, режим step + - Подключение - только HIGH PULL! + - Опциональный режим callback (+22б SRAM на каждый экземпляр) + + AlexGyver, alex@alexgyver.ru + https://alexgyver.ru/ + MIT License + Опционально используется алгоритм из библиотеки // https://github.com/mathertel/RotaryEncoder + + Версии: + v1.1 - пуллап отдельныи методом + v1.2 - можно передать конструктору параметр INPUT_PULLUP / INPUT(умолч) + v1.3 - виртуальное зажатие кнопки энкодера вынесено в отдельную функцию + мелкие улучшения + v1.4 - обработка нажатия и отпускания кнопки + v1.5 - добавлен виртуальный режим + v1.6 - оптимизация работы в прерывании + v1.6.1 - PULLUP по умолчанию + v1.7 - большая оптимизация памяти, переделан FastIO + v1.8 - индивидуальная настройка таймаута удержания кнопки (была общая на всех) + v1.8.1 - убран FastIO + v1.9 - добавлена отдельная отработка нажатого поворота и запрос направления + v1.10 - улучшил обработку released, облегчил вес в режиме callback и исправил баги + v1.11 - ещё больше всякой оптимизации + настройка уровня кнопки + v1.11.1 - совместимость Digispark + v1.12 - добавил более точный алгоритм энкодера EB_BETTER_ENC + v1.13 - добавлен экспериментальный EncButton2 + v1.14 - добавлена releaseStep(). Отпускание кнопки внесено в дебаунс + v1.15 - добавлен setPins() для EncButton2 + v1.16 - добавлен режим EB_HALFSTEP_ENC для полушаговых энкодеров +*/ + +#ifndef _EncButton_h +#define _EncButton_h + +// ========= НАСТРОЙКИ (можно передефайнить из скетча) ========== +#define _EB_FAST 30 // таймаут быстрого поворота +#define _EB_DEB 50 // дебаунс кнопки +#define _EB_HOLD 1000 // таймаут удержания кнопки +#define _EB_STEP 500 // период срабатывания степ +#define _EB_CLICK 400 // таймаут накликивания +//#define EB_BETTER_ENC // точный алгоритм отработки энкодера (можно задефайнить в скетче) + +// =========== НЕ ТРОГАЙ ============ +#include + +#ifndef nullptr +#define nullptr NULL +#endif + +// флаг макро +#define _EB_setFlag(x) (flags |= 1 << x) +#define _EB_clrFlag(x) (flags &= ~(1 << x)) +#define _EB_readFlag(x) ((flags >> x) & 1) + +#ifndef EB_FAST +#define EB_FAST _EB_FAST +#endif +#ifndef EB_DEB +#define EB_DEB _EB_DEB +#endif +#ifndef EB_HOLD +#define EB_HOLD _EB_HOLD +#endif +#ifndef EB_STEP +#define EB_STEP _EB_STEP +#endif +#ifndef EB_CLICK +#define EB_CLICK _EB_CLICK +#endif + +enum eb_callback { + TURN_HANDLER, // 0 + LEFT_HANDLER, // 1 + RIGHT_HANDLER, // 2 + LEFT_H_HANDLER, // 3 + RIGHT_H_HANDLER, // 4 + CLICK_HANDLER, // 5 + HOLDED_HANDLER, // 6 + STEP_HANDLER, // 7 + PRESS_HANDLER, // 8 + CLICKS_HANDLER, // 9 + RELEASE_HANDLER, // 10 + HOLD_HANDLER, // 11 + TURN_H_HANDLER, // 12 + // clicks amount 13 +}; + +// константы +#define EB_TICK 0 +#define EB_CALLBACK 1 + +#define EB_NO_PIN 255 + +#define VIRT_ENC 254 +#define VIRT_ENCBTN 253 +#define VIRT_BTN 252 + +#ifdef EB_BETTER_ENC +static const int8_t _EB_DIR[] = { + 0, -1, 1, 0, + 1, 0, 0, -1, + -1, 0, 0, 1, + 0, 1, -1, 0 +}; +#endif + +// ===================================== CLASS ===================================== +template < uint8_t _EB_MODE, uint8_t _S1 = EB_NO_PIN, uint8_t _S2 = EB_NO_PIN, uint8_t _KEY = EB_NO_PIN > +class EncButton { +public: + // можно указать режим работы пина + EncButton(const uint8_t mode = INPUT_PULLUP) { + if (_S1 < 252 && mode == INPUT_PULLUP) pullUp(); + setButtonLevel(LOW); + } + + // подтянуть пины внутренней подтяжкой + void pullUp() { + if (_S1 < 252) { // реальное устройство + if (_S2 == EB_NO_PIN) { // обычная кнопка + pinMode(_S1, INPUT_PULLUP); + } else if (_KEY == EB_NO_PIN) { // энк без кнопки + pinMode(_S1, INPUT_PULLUP); + pinMode(_S2, INPUT_PULLUP); + } else { // энк с кнопкой + pinMode(_S1, INPUT_PULLUP); + pinMode(_S2, INPUT_PULLUP); + pinMode(_KEY, INPUT_PULLUP); + } + } + } + + // установить таймаут удержания кнопки для isHold(), мс (до 30 000) + void setHoldTimeout(int tout) { + _holdT = tout >> 7; + } + + // виртуально зажать кнопку энкодера + void holdEncButton(bool state) { + if (state) _EB_setFlag(8); + else _EB_clrFlag(8); + } + + // уровень кнопки: LOW - кнопка подключает GND (умолч.), HIGH - кнопка подключает VCC + void setButtonLevel(bool level) { + if (level) _EB_clrFlag(11); + else _EB_setFlag(11); + } + + // ===================================== TICK ===================================== + // тикер, вызывать как можно чаще + // вернёт отличное от нуля значение, если произошло какое то событие + uint8_t tick(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) { + tickISR(s1, s2, key); + checkCallback(); + return EBState; + } + + // тикер специально для прерывания, не проверяет коллбэки + uint8_t tickISR(uint8_t s1 = 0, uint8_t s2 = 0, uint8_t key = 0) { + if (!_isrFlag) { + _isrFlag = 1; + + // обработка энка (компилятор вырежет блок если не используется) + // если объявлены два пина или выбран вирт. энкодер или энкодер с кнопкой + if ((_S1 < 252 && _S2 < 252) || _S1 == VIRT_ENC || _S1 == VIRT_ENCBTN) { + uint8_t state; + if (_S1 >= 252) state = s1 | (s2 << 1); // получаем код + else state = fastRead(_S1) | (fastRead(_S2) << 1); // получаем код + poolEnc(state); + } + + // обработка кнопки (компилятор вырежет блок если не используется) + // если S2 не указан (кнопка) или указан KEY или выбран вирт. энкодер с кнопкой или кнопка + if ((_S1 < 252 && _S2 == EB_NO_PIN) || _KEY != EB_NO_PIN || _S1 == VIRT_BTN || _S1 == VIRT_ENCBTN) { + if (_S1 < 252 && _S2 == EB_NO_PIN) _btnState = fastRead(_S1); // обычная кнопка + if (_KEY != EB_NO_PIN) _btnState = fastRead(_KEY); // энк с кнопкой + if (_S1 == VIRT_BTN) _btnState = s1; // вирт кнопка + if (_S1 == VIRT_ENCBTN) _btnState = key; // вирт энк с кнопкой + _btnState ^= _EB_readFlag(11); // инверсия кнопки + poolBtn(); + } + } + _isrFlag = 0; + return EBState; + } + + // ===================================== CALLBACK ===================================== + // проверить callback, чтобы не дёргать в прерывании + void checkCallback() { + if (_EB_MODE) { + if (turn()) exec(0); + if (turnH()) exec(12); + if (EBState > 0 && EBState <= 8) exec(EBState); + if (release()) exec(10); + if (hold()) exec(11); + if (checkFlag(6)) { + exec(9); + if (clicks == _amount) exec(13); + } + EBState = 0; + } + } + + // подключить обработчик + void attach(eb_callback type, void (*handler)()) { + _callback[type] = *handler; + } + + // отключить обработчик + void detach(eb_callback type) { + _callback[type] = nullptr; + } + + // подключить обработчик на количество кликов (может быть только один!) + void attachClicks(uint8_t amount, void (*handler)()) { + _amount = amount; + _callback[13] = *handler; + } + + // отключить обработчик на количество кликов + void detachClicks() { + _callback[13] = nullptr; + } + + // ===================================== STATUS ===================================== + uint8_t getState() { return EBState; } // получить статус + void resetState() { EBState = 0; } // сбросить статус + + // ======================================= ENC ======================================= + bool left() { return checkState(1); } // поворот влево + bool right() { return checkState(2); } // поворот вправо + bool leftH() { return checkState(3); } // поворот влево нажатый + bool rightH() { return checkState(4); } // поворот вправо нажатый + + bool fast() { return _EB_readFlag(1); } // быстрый поворот + bool turn() { return checkFlag(0); } // энкодер повёрнут + bool turnH() { return checkFlag(9); } // энкодер повёрнут нажато + int8_t getDir() { return _dir; } // направление последнего поворота, 1 или -1 + int16_t counter = 0; // счётчик энкодера + + // ======================================= BTN ======================================= + bool press() { return checkState(8); } // кнопка нажата + bool release() { return checkFlag(10); } // кнопка отпущена + bool click() { return checkState(5); } // клик по кнопке + bool held() { return checkState(6); } // кнопка удержана + bool hold() { return _EB_readFlag(4); } // кнопка удерживается + bool step() { return checkState(7); } // режим импульсного удержания + bool state() { return _btnState; } // статус кнопки + bool releaseStep() {return checkFlag(12);} // кнопка отпущена после импульсного удержания + + uint8_t clicks = 0; // счётчик кликов + bool hasClicks(uint8_t num) { return (clicks == num && checkFlag(7)) ? 1 : 0; } // имеются клики + uint8_t hasClicks() { return checkFlag(6) ? clicks : 0; } // имеются клики + + // =================================================================================== + // =================================== DEPRECATED ==================================== + bool isStep() { return step(); } + bool isHold() { return hold(); } + bool isHolded() { return held(); } + bool isHeld() { return held(); } + bool isClick() { return click(); } + bool isRelease() { return release(); } + bool isPress() { return press(); } + bool isTurnH() { return turnH(); } + bool isTurn() { return turn(); } + bool isFast() { return fast(); } + bool isLeftH() { return leftH(); } + bool isRightH() { return rightH(); } + bool isLeft() { return left(); } + bool isRight() { return right(); } + + // ===================================== PRIVATE ===================================== +private: + bool fastRead(const uint8_t pin) { +#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) + if (pin < 8) return bitRead(PIND, pin); + else if (pin < 14) return bitRead(PINB, pin - 8); + else if (pin < 20) return bitRead(PINC, pin - 14); +#elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny13__) + return bitRead(PINB, pin); +#else + return digitalRead(pin); +#endif + return 0; + } + + // ===================================== POOL ENC ===================================== + void poolEnc(uint8_t state) { + #ifdef EB_BETTER_ENC + if (_prev != state) { + _ecount += _EB_DIR[state | (_prev << 2)]; // сдвиг внутреннего счётчика + _prev = state; + #ifdef EB_HALFSTEP_ENC // полушаговый энкодер + // спасибо https://github.com/GyverLibs/EncButton/issues/10#issue-1092009489 + if ((state == 0x3 || state == 0x0) && _ecount != 0) { + #else // полношаговый + if (state == 0x3 && _ecount != 0) { // защёлкнули позицию + #endif + EBState = (_ecount < 0) ? 1 : 2; + _ecount = 0; + if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой + if (!_EB_readFlag(4) && (_btnState || _EB_readFlag(8))) EBState += 2; // если кнопка не "удерживается" + } + _dir = (EBState & 1) ? -1 : 1; // направление + counter += _dir; // счётчик + if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера + else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера + if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот + else _EB_clrFlag(1); // обычный поворот + _debTimer = millis(); + } + } + #else + if (_encRST && state == 0b11) { // ресет и энк защёлкнул позицию + if (_S2 == EB_NO_PIN || _KEY != EB_NO_PIN) { // энкодер с кнопкой + if ((_prev == 1 || _prev == 2) && !_EB_readFlag(4)) { // если кнопка не "удерживается" и энкодер в позиции 1 или 2 + EBState = _prev; + if (_btnState || _EB_readFlag(8)) EBState += 2; + } + } else { // просто энкодер + if (_prev == 1 || _prev == 2) EBState = _prev; + } + + if (EBState > 0) { // был поворот + _dir = (EBState & 1) ? -1 : 1; // направление + counter += _dir; // счётчик + if (EBState <= 2) _EB_setFlag(0); // флаг поворота для юзера + else if (EBState <= 4) _EB_setFlag(9); // флаг нажатого поворота для юзера + if (millis() - _debTimer < EB_FAST) _EB_setFlag(1); // быстрый поворот + else _EB_clrFlag(1); // обычный поворот + } + + _encRST = 0; + _debTimer = millis(); + } + if (state == 0b00) _encRST = 1; + _prev = state; + #endif + } + + // ===================================== POOL BTN ===================================== + void poolBtn() { + uint32_t thisMls = millis(); + uint32_t debounce = thisMls - _debTimer; + if (_btnState) { // кнопка нажата + if (!_EB_readFlag(3)) { // и не была нажата ранее + if (debounce > EB_DEB) { // и прошел дебаунс + _EB_setFlag(3); // флаг кнопка была нажата + _debTimer = thisMls; // сброс таймаутов + EBState = 8; // кнопка нажата + } + if (debounce > EB_CLICK) { // кнопка нажата после EB_CLICK + clicks = 0; // сбросить счётчик и флаг кликов + flags &= ~0b0011000011100000; // clear 5 6 7 12 13 (клики) + } + } else { // кнопка уже была нажата + if (!_EB_readFlag(4)) { // и удержание ещё не зафиксировано + if (debounce < (_holdT << 7)) { // прошло меньше удержания + if (EBState != 0 && EBState != 8) _EB_setFlag(2); // но энкодер повёрнут! Запомнили + } else { // прошло больше времени удержания + if (!_EB_readFlag(2)) { // и энкодер не повёрнут + EBState = 6; // значит это удержание (сигнал) + _EB_setFlag(4); // запомнили что удерживается + _debTimer = thisMls; // сброс таймаута + } + } + } else { // удержание зафиксировано + if (debounce > EB_STEP) { // таймер степа + EBState = 7; // сигналим + _EB_setFlag(13); // зафиксирован режим step + _debTimer = thisMls; // сброс таймаута + } + } + } + } else { // кнопка не нажата + if (_EB_readFlag(3)) { // но была нажата + if (debounce > EB_DEB) { + if (!_EB_readFlag(4) && !_EB_readFlag(2)) { // энкодер не трогали и не удерживали - это клик + EBState = 5; + clicks++; + } + flags &= ~0b00011100; // clear 2 3 4 + _debTimer = thisMls; // сброс таймаута + _EB_setFlag(10); // кнопка отпущена + if (checkFlag(13)) _EB_setFlag(12); // кнопка отпущена после step + } + } else if (clicks > 0 && debounce > EB_CLICK && !_EB_readFlag(5)) flags |= 0b11100000; // set 5 6 7 (клики) + } + } + + // ===================================== MISC ===================================== + bool checkState(uint8_t val) { + if (EBState == val) { + EBState = 0; + return 1; + } return 0; + } + bool checkFlag(uint8_t val) { + if (_EB_readFlag(val)) { + _EB_clrFlag(val); + return 1; + } return 0; + } + void exec(uint8_t num) { + if (*_callback[num]) _callback[num](); + } + + uint8_t _prev : 2; + uint8_t EBState : 4; + bool _btnState : 1; + bool _encRST : 1; + bool _isrFlag : 1; + uint16_t flags = 0; + + #ifdef EB_BETTER_ENC + int8_t _ecount = 0; + #endif + + uint32_t _debTimer = 0; + uint8_t _holdT = (EB_HOLD >> 7); + int8_t _dir = 0; + void (*_callback[_EB_MODE ? 14 : 0])() = {}; + uint8_t _amount = 0; + + // flags + // 0 - enc turn + // 1 - enc fast + // 2 - enc был поворот + // 3 - флаг кнопки + // 4 - hold + // 5 - clicks flag + // 6 - clicks get + // 7 - clicks get num + // 8 - enc button hold + // 9 - enc turn holded + // 10 - btn released + // 11 - btn level + // 12 - btn released after step + // 13 - step flag + + // EBState + // 0 - idle + // 1 - left + // 2 - right + // 3 - leftH + // 4 - rightH + // 5 - click + // 6 - held + // 7 - step + // 8 - press +}; + +#endif \ No newline at end of file diff --git a/buildTime.h b/buildTime.h new file mode 100644 index 0000000..9405b22 --- /dev/null +++ b/buildTime.h @@ -0,0 +1,83 @@ +/* + Парсинг и получение даты и времени компиляции из __DATE__ и __TIME__ + Документация: + GitHub: https://github.com/GyverLibs/buildTime + Константы времени компиляции: + BUILD_YEAR - год + BUILD_MONTH - месяц + BUILD_DAY - день + BUILD_HOUR - час + BUILD_MIN - минута + BUILD_SEC - секунда + + Исходник http://qaru.site/questions/186859/how-to-use-date-and-time-predefined-macros-in-as-two-integers-then-stringify + AlexGyver, alex@alexgyver.ru + https://alexgyver.ru/ + MIT License + + Версии: + v1.0 - релиз +*/ + + +#ifndef buildTime_h +#define buildTime_h +// Example of __DATE__ string: "Jul 27 2012" +// 01234567890 + +#define BUILD_YEAR_CH0 (__DATE__[7]-'0') +#define BUILD_YEAR_CH1 (__DATE__[8]-'0') +#define BUILD_YEAR_CH2 (__DATE__[9]-'0') +#define BUILD_YEAR_CH3 (__DATE__[10]-'0') +#define BUILD_YEAR (BUILD_YEAR_CH0*1000+BUILD_YEAR_CH1*100 + BUILD_YEAR_CH2*10+BUILD_YEAR_CH3) + +#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n') +#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F') +#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r') +#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p') +#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y') +#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n') +#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l') +#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u') +#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S') +#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O') +#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N') +#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D') + +#define BUILD_MONTH \ + ( \ + (BUILD_MONTH_IS_JAN) ? 1 : \ + (BUILD_MONTH_IS_FEB) ? 2 : \ + (BUILD_MONTH_IS_MAR) ? 3 : \ + (BUILD_MONTH_IS_APR) ? 4 : \ + (BUILD_MONTH_IS_MAY) ? 5 : \ + (BUILD_MONTH_IS_JUN) ? 6 : \ + (BUILD_MONTH_IS_JUL) ? 7 : \ + (BUILD_MONTH_IS_AUG) ? 8 : \ + (BUILD_MONTH_IS_SEP) ? 9 : \ + (BUILD_MONTH_IS_OCT) ? 10 : \ + (BUILD_MONTH_IS_NOV) ? 11 : \ + (BUILD_MONTH_IS_DEC) ? 12 : \ + /* error default */ '?' \ + ) + +#define BUILD_DAY_CH0 (((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')-'0') +#define BUILD_DAY_CH1 (__DATE__[5]-'0') +#define BUILD_DAY (BUILD_DAY_CH0*10+BUILD_DAY_CH1) + +// Example of __TIME__ string: "21:06:19" +// 01234567 + +#define BUILD_HOUR_CH0 (__TIME__[0]-'0') +#define BUILD_HOUR_CH1 (__TIME__[1]-'0') +#define BUILD_HOUR (BUILD_HOUR_CH0*10+BUILD_HOUR_CH1) + +#define BUILD_MIN_CH0 (__TIME__[3]-'0') +#define BUILD_MIN_CH1 (__TIME__[4]-'0') +#define BUILD_MIN (BUILD_MIN_CH0*10+BUILD_MIN_CH1) + +#define BUILD_SEC_CH0 (__TIME__[6]-'0') +#define BUILD_SEC_CH1 (__TIME__[7]-'0') +#define BUILD_SEC (BUILD_SEC_CH0*10+BUILD_SEC_CH1) + +#endif \ No newline at end of file diff --git a/feeder_v2.1.ino b/feeder_v2.1.ino new file mode 100644 index 0000000..ea27f39 --- /dev/null +++ b/feeder_v2.1.ino @@ -0,0 +1,255 @@ +/* + Скетч к проекту "Спутниковая Роботорговля" +bgp, 2023 +*/ + + +#define EE_RESET 15 // любое число 0-255. Измени, чтобы сбросить настройки и обновить время +#define FEED_SPEED 1500 // задержка между шагами мотора (мкс) +#define DIRPIN 2 // d2 направление +#define STEPPIN 3 // d3 движение +#define BTN_PIN 4 // кнопка выдачи +#define BUNKERPIN 5 // d5 датчик наличия корма +#define HANDPIN 6 // d6 датчик наличия руки +#define TERM_INHIB 8 // d8 посылать "отказ от обслуживания" -upd +#define TERM_COIN 7 // d7 считаем монетки - upd +#define SLEEP 9 // d9 пока не работаем - двигатель спит +#define RELAY 10 // d10 ключ для отключения терминала и двигателя в ночное время +#define BTN_PIN2 11 // кнопка настройки + + +#define STEPS_FRW 19 // шаги вперёд +#define STEPS_BKW 12 // шаги назад + + +// ========================================================= +#include +#include "microDS3231.h" +MicroDS3231 rtc; +#include "EncButton.h" +#include //подключение библиотеки для режимов сна +#include +// #include + +EncButton btn; +EncButton btn2; +EncButton term_inhib; +// EncButton term_coin_put; +int feedAmount = 100; + +// https://robotchip.ru/podklyuchenie-kupyuropriemnika-cashcode-sm-k-arduino/ + +int valuePulse = 10; // Стоимость одного импульса +int minWidthPulse = 80; // Минимальная ширина одного импульса +int maxWidthPulse = 120; // Максимальная ширина одного импульса +int debounce = 4; // Защита от помех +int pulseCount = 0; // Сколько импульсов получено +int receivedRUB = 0; // Сумма +unsigned long pulseDuration; // Как давно был последний импульс +unsigned long pulseBegin = 0; // Начало импульса +unsigned long pulseEnd = 0; // Конец импульса +unsigned long curtime; // Время +int postPulsePause = 300; // Время ожидания, для завершения подсчета импульсов +int pulseState; // Состояние входа "0" или "1" +int lastState = 1; // Последние состояние входа "0" или "1" + + +int paymentsCount = 0; + +int onTime = 6; // время включения +int offTime = 20; // время выключения + +Sleep sleep; //объект для работы с режимами сна +unsigned long sleepTime; //переменная для задания времени сна + + +// GND -- [ R2 ] -- A0 -- [ R1 ] -- VIN +#define VREF 4.25 // точное напряжение на пине 5V (в данном случае зависит от стабилизатора на плате Arduino) +#define DIV_R1 10025 // точное значение 10 кОм резистора +#define DIV_R2 3285 // точное значение 3.3 кОм резистора + + +void setup() { + rtc.begin(); + if (EEPROM.read(0) != EE_RESET) { // первый запуск + EEPROM.write(0, EE_RESET); + EEPROM.put(1, feedAmount); + rtc.setTime(BUILD_SEC, BUILD_MIN, BUILD_HOUR, BUILD_DAY, BUILD_MONTH, BUILD_YEAR); + } + EEPROM.get(1, feedAmount); + pinMode(STEPPIN, OUTPUT); // пин движения + pinMode(DIRPIN, OUTPUT); // пин направления + pinMode(HANDPIN, INPUT); // пин ИК датчик руки + pinMode(BUNKERPIN, INPUT); // пин ИК датчик бункер + pinMode(LED_BUILTIN, OUTPUT); // встроенный LED + pinMode(SLEEP, OUTPUT); // пин для отключения двигателя + pinMode(TERM_COIN, INPUT); // прием сигнала от терминала + pinMode(RELAY, OUTPUT); + + + sleepTime = 60 * 60 * 1000; + //Debug + // Serial.begin(9600); // debug в serial +} + +void loop() { + static uint32_t tmr = 0; + float voltage = (float)analogRead(0) * VREF * ((DIV_R1 + DIV_R2) / DIV_R2) / 1024; + if (millis() - tmr > 500) { // два раза в секунду + static byte prevMin = 0; + tmr = millis(); + DateTime now = rtc.getTime(); + // получаем время + // Serial.print(voltage); // debug + // Serial.println(" V"); // debug + // Serial.print(now.hour); // debug + // Serial.println(" h"); // debug + // Serial.print(rtc.getHours()); + // Serial.print(":"); + // Serial.print(rtc.getMinutes()); + // Serial.print(":"); + // Serial.print(rtc.getSeconds()); + // Serial.print(" "); + // Serial.print(rtc.getDay()); + // Serial.print(" "); + // Serial.print(rtc.getDate()); + // Serial.print("/"); + // Serial.print(rtc.getMonth()); + // Serial.print("/"); + // Serial.println(rtc.getYear()); + if (voltage < 13.2) { + digitalWrite(RELAY, LOW); + sleep.pwrDownMode(); //установка режима сна PWR_DOWN + sleep.sleepDelay(sleepTime); //заснуть на указанное время + } + if ((now.hour >= onTime && now.hour < offTime && voltage > 13.2)) { //&& voltage > 10.2 + digitalWrite(RELAY, HIGH); // работаем если время рабочее И напряжение выше минимума + // digitalWrite(TERM_INHIB, LOW); + } else { + digitalWrite(RELAY, LOW); + } + + } + + // состояние датчика рук + int hand = digitalRead(HANDPIN); //читать инпут с "датчика рук" + digitalWrite(TERM_INHIB, term_inhib.isHold() == 0); + + + btn.tick(); + btn2.tick(); + term_inhib.tick(); + + pulseState = digitalRead(TERM_COIN); // Считываем значение с входа TERM_COIN + curtime = millis(); // Записываем значение миллисекунд с момента начала выполнения программы + + if ((pulseState == 0) && (lastState == 1)) // Ждем начало импульса, логический "0" + { + pulseBegin = curtime; // Записываем значение милисикунд + lastState = 0; // Записываем значение "0" в переменную lastState + } else if ((pulseState == 1) && (lastState == 0)) // Ждем окончания импульса, логический "1" + { + pulseDuration = curtime - pulseBegin; // Расчет длительности импульса в миллисекунд + if (pulseDuration > debounce) // Защита от помех, если импульс был небольшой + { + lastState = 1; // Записываем значение "1" в переменную lastState + } + if ((pulseDuration > minWidthPulse) && (pulseDuration < maxWidthPulse)) // Проверяем ширину импульса + { + pulseEnd = curtime; // Сохранить значение милисикунд, окончания импульса + pulseCount++; // Инкремент счетчика импульсов + } + } + if ((pulseEnd > 0) && (curtime - pulseEnd > postPulsePause)) // Проверяем, поступают ли еще импульсы + { + receivedRUB += pulseCount * valuePulse; // Расчет суммы + // if (hand == LOW) + // { + if (receivedRUB > 0) { + paymentsCount++; // Надо описать логику, "принимаем платежи сколько угодно раз и кормим сстолько же, как только в кадре появятся руки" + Serial.print("Credit: "); // Вывести текст на последовательный порт //Debug + Serial.print(receivedRUB); // Вывести значение переменной на пслеовательный порт //Debug + Serial.println(" RUB"); // Вывести текст на последовательный порт //Debug + Serial.print("Payed: "); + Serial.print(paymentsCount); + Serial.println(" times"); + pulseEnd = 0; // Обнуляем + pulseCount = 0; // Обнуляем + receivedRUB = 0; // Обнуляем + } + // } + } + + if (paymentsCount > 0) // Debug подставляем руки, жмем кнопку - получаем внеочередную кормежку + { + feed(); + paymentsCount--; + Serial.print("Payed: "); + Serial.print(paymentsCount); + Serial.println(" times"); + } + + // if (hand == LOW) // Debug подставляем руки, жмем кнопку - получаем внеочередную кормежку + // { + // feedByButton(); + // } + + if (btn.click()) { + feed(); + } + + if (btn2.hold()) // Задаем размер порции: + { + int newAmount = 0; + while (btn2.isHold()) // пока кнопка зажата + { + btn2.tick(); + oneRev(); // крутим мотор + newAmount++; // считаем шаги + } + feedAmount = newAmount; + EEPROM.put(1, feedAmount); // записываем количество шагов в постоянную память + } + + +} // конец основного цикла + +// выдача корма +void feed() { + for (int i = 0; i < feedAmount; i++) oneRev(); + disableMotor(); +} + + +// кормежка по кнопке +void feedByButton() { + if (btn.click()) { + feed(); + } +} + +// выключаем ток на мотор +void disableMotor() { + digitalWrite(SLEEP, LOW); +} + +// вращение +void runMotor() { + digitalWrite(SLEEP, HIGH); //Включаем мотор + digitalWrite(STEPPIN, HIGH); // по часовой + delayMicroseconds(FEED_SPEED); + digitalWrite(STEPPIN, LOW); // против часовой + delayMicroseconds(FEED_SPEED); +} + +// вращение двигателя в виброрежиме, для непопущения заклинивания +void oneRev() { + for (int i = 0; i < STEPS_BKW; i++) { + digitalWrite(DIRPIN, LOW); + runMotor(); + } + for (int i = 0; i < STEPS_FRW; i++) { + digitalWrite(DIRPIN, HIGH); + runMotor(); + } +} diff --git a/microDS3231.cpp b/microDS3231.cpp new file mode 100644 index 0000000..cff389f --- /dev/null +++ b/microDS3231.cpp @@ -0,0 +1,229 @@ +#include "microDS3231.h" + +//static const uint8_t _ds_daysInMonth[] PROGMEM = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +static uint8_t _ds_DIM(uint8_t i) { + return (i < 7) ? ((i == 1) ? 28 : ((i & 1) ? 30 : 31)) : ((i & 1) ? 31 : 30); +} + +static uint16_t getWeekDay(uint16_t y, uint8_t m, uint8_t d) { + if (y >= 2000) + y -= 2000; + uint16_t days = d; + for (uint8_t i = 1; i < m; ++i) + //days += pgm_read_byte(_ds_daysInMonth + i - 1); + days += _ds_DIM(i - 1); + if (m > 2 && y % 4 == 0) + ++days; + return (days + 365 * y + (y + 3) / 4 + 4) % 7 + 1; +} + +// поiхали +MicroDS3231::MicroDS3231(uint8_t addr) : _addr(addr) { + Wire.begin(); +} + +bool MicroDS3231::begin(void){ + Wire.begin(); // Инит шины + Wire.beginTransmission(_addr); // Зовем DS3231 по адресу + return (!Wire.endTransmission()); // если никто не откликнулся - возвращаем false +} + +void MicroDS3231::setTime(int8_t seconds, int8_t minutes, int8_t hours, int8_t date, int8_t month, int16_t year) { + // защиты от дурака + month = constrain(month, 1, 12); + //date = constrain(date, 0, pgm_read_byte(_ds_daysInMonth + month - 1)); + date = constrain(date, 0, _ds_DIM(month - 1)); + seconds = constrain(seconds, 0, 59); + minutes = constrain(minutes, 0, 59); + hours = constrain(hours, 0, 23); + + // отправляем + uint8_t day = getWeekDay(year, month, date); + year -= 2000; + Wire.beginTransmission(_addr); + Wire.write(0x00); + Wire.write(encodeRegister(seconds)); + Wire.write(encodeRegister(minutes)); + if (hours > 19) Wire.write((0x2 << 4) | (hours % 20)); + else if (hours > 9) Wire.write((0x1 << 4) | (hours % 10)); + else Wire.write(hours); + Wire.write(day); + Wire.write(encodeRegister(date)); + Wire.write(encodeRegister(month)); + Wire.write(encodeRegister(year)); + Wire.endTransmission(); +} + +void MicroDS3231::setHMSDMY(int8_t hours, int8_t minutes, int8_t seconds, int8_t date, int8_t month, int16_t year) { + setTime(seconds, minutes, hours, date, month, year); +} + +void MicroDS3231::setTime(DateTime time) { + setTime(time.second, time.minute, time.hour, time.date, time.month, time.year); +} + +static int charToDec(const char* p) { + return (10 * (*p - '0') + (*++p - '0')); +} + +void MicroDS3231::setTime(const __FlashStringHelper* stamp) { + char buff[25]; + memcpy_P(buff, stamp, 25); + + // Wed Jul 14 22:00:24 2021 + // 4 8 11 14 17 22 + // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + int h, m, s, d, mo, y; + h = charToDec(buff + 11); + m = charToDec(buff + 14); + s = charToDec(buff + 17); + d = charToDec(buff + 8); + switch (buff[4]) { + case 'J': mo = (buff[5] == 'a') ? 1 : (mo = (buff[6] == 'n') ? 6 : 7); break; + case 'F': mo = 2; break; + case 'A': mo = (buff[6] == 'r') ? 4 : 8; break; + case 'M': mo = (buff[6] == 'r') ? 3 : 5; break; + case 'S': mo = 9; break; + case 'O': mo = 10; break; + case 'N': mo = 11; break; + case 'D': mo = 12; break; + } + y = 2000 + charToDec(buff + 22); + setTime(s, m, h, d, mo, y); +} + +DateTime MicroDS3231::getTime() { + DateTime now; + Wire.beginTransmission(_addr); + Wire.write(0x0); + if (Wire.endTransmission() != 0) return now; + Wire.requestFrom(_addr, (uint8_t)7); + now.second = unpackRegister(Wire.read()); + now.minute = unpackRegister(Wire.read()); + now.hour = unpackHours(Wire.read()); + now.day = Wire.read(); + now.date = unpackRegister(Wire.read()); + now.month = unpackRegister(Wire.read()); + now.year = unpackRegister(Wire.read()) + 2000; + return now; +} +String MicroDS3231::getTimeString() { + DateTime now = getTime(); + String str = ""; + if (now.hour < 10) str += '0'; + str += now.hour; + str += ':'; + if (now.minute < 10) str += '0'; + str += now.minute; + str += ':'; + if (now.second < 10) str += '0'; + str += now.second; + return str; +} +String MicroDS3231::getDateString() { + DateTime now = getTime(); + String str = ""; + if (now.date < 10) str += '0'; + str += now.date; + str += '.'; + if (now.month < 10) str += '0'; + str += now.month; + str += '.'; + str += now.year; + return str; +} +void MicroDS3231::getTimeChar(char* array) { + DateTime now = getTime(); + array[0] = now.hour / 10 + '0'; + array[1] = now.hour % 10 + '0'; + array[2] = ':'; + array[3] = now.minute / 10 + '0'; + array[4] = now.minute % 10 + '0'; + array[5] = ':'; + array[6] = now.second / 10 + '0'; + array[7] = now.second % 10 + '0'; + array[8] = '\0'; +} +void MicroDS3231::getDateChar(char* array) { + DateTime now = getTime(); + array[0] = now.date / 10 + '0'; + array[1] = now.date % 10 + '0'; + array[2] = '.'; + array[3] = now.month / 10 + '0'; + array[4] = now.month % 10 + '0'; + array[5] = '.'; + itoa(now.year, array + 6, DEC); + array[10] = '\0'; +} +bool MicroDS3231::lostPower(void) { // возвращает true, если 1 января 2000 + if (getYear() == 2000 && getMonth() == 1 && getDate() == 1) return true; + else return false; +} + +uint8_t MicroDS3231::getSeconds(void) { + return (unpackRegister(readRegister(0x00))); +} + +uint8_t MicroDS3231::getMinutes(void) { + return (unpackRegister(readRegister(0x01))); +} + +uint8_t MicroDS3231::getHours(void) { + return (unpackHours(readRegister(0x02))); +} + +uint8_t MicroDS3231::getDay(void) { + return readRegister(0x03); +} + +uint8_t MicroDS3231::getDate(void) { + return (unpackRegister(readRegister(0x04))); +} + +uint8_t MicroDS3231::getMonth(void) { + return (unpackRegister(readRegister(0x05))); +} + +uint16_t MicroDS3231::getYear(void) { + return (unpackRegister(readRegister(0x06)) + 2000); +} + +// сервис +uint8_t MicroDS3231::readRegister(uint8_t addr) { + Wire.beginTransmission(_addr); + Wire.write(addr); + if (Wire.endTransmission() != 0) return 0; + Wire.requestFrom(_addr, (uint8_t)1); + uint8_t data = Wire.read(); + return data; +} + +uint8_t MicroDS3231::unpackRegister(uint8_t data) { + return ((data >> 4) * 10 + (data & 0xF)); +} + +uint8_t MicroDS3231::encodeRegister(int8_t data) { + return (((data / 10) << 4) | (data % 10)); +} + +uint8_t MicroDS3231::unpackHours(uint8_t data) { + if (data & 0x20) return ((data & 0xF) + 20); + else if (data & 0x10) return ((data & 0xF) + 10); + else return (data & 0xF); +} + +float MicroDS3231::getTemperatureFloat(void) { + return (getTemperatureRaw() * 0.25f); +} + +int MicroDS3231::getTemperature(void) { + return (getTemperatureRaw() >> 2); +} + +int MicroDS3231::getTemperatureRaw(void) { + Wire.beginTransmission(_addr); + Wire.write(0x11); + Wire.endTransmission(); + Wire.requestFrom(_addr, (uint8_t)2); + return ((int8_t)Wire.read() << 2) + (Wire.read() >> 6); +} \ No newline at end of file diff --git a/microDS3231.h b/microDS3231.h new file mode 100644 index 0000000..b93f79c --- /dev/null +++ b/microDS3231.h @@ -0,0 +1,82 @@ +/* + Лёгкая библиотека для работы с RTC DS3231 для Arduino + Документация: + GitHub: https://github.com/GyverLibs/microDS3231 + Возможности: + - Чтение и запись времени + - Вывод в char* и String + - Чтение температуры датчика + + Egor 'Nich1con' Zakharov & AlexGyver, alex@alexgyver.ru + https://alexgyver.ru/ + MIT License + + Версии: + v1.2 - добавлены ограничения на вводимые в setTime числа. Также нельзя ввести 29 февраля увы =) + v1.3 - пофикшено зависание, когда модуль отключен но опрашивается + v1.4 - незначительный фикс + v2.0 - новые возможности, оптимизация и облегчение + v2.1 - добавил вывод температуры, вывод в String и char + v2.2 - исправлены дни недели (пн-вс 1-7) + v2.3 - небольшие исправления, оптимизация, изменён порядок вывода даты + v2.4 - исправлена установка времени компиляции + v2.5 - добавлен begin для проверки наличия модуля на линии + v2.6 - исправлены отрицательные температуры +*/ + +#ifndef microDS3231_h +#define microDS3231_h +//#include // выбор между библиотеками Wire и microWire +#include +#include "buildTime.h" + +#include +#define COMPILE_TIME F(__TIMESTAMP__) + +struct DateTime { + uint8_t second; + uint8_t minute; + uint8_t hour; + uint8_t day; + uint8_t date; + uint8_t month; + uint16_t year; +}; + +class MicroDS3231 { +public: + MicroDS3231(uint8_t addr = 0x68); // конструктор. Можно передать адрес + bool begin(void); // инициализация, вернет true, если RTC найден + void setTime(const __FlashStringHelper* stamp); // установка времени == времени компиляции + void setTime(DateTime time); // установить из структуры DateTime + void setTime(int8_t seconds, int8_t minutes, int8_t hours, int8_t date, int8_t month, int16_t year); // установка времени + void setHMSDMY(int8_t hours, int8_t minutes, int8_t seconds, int8_t date, int8_t month, int16_t year); // установка времени тип 2 + + DateTime getTime(void); // получить в структуру DateTime + uint8_t getSeconds(void); // получить секунды + uint8_t getMinutes(void); // получить минуты + uint8_t getHours(void); // получить часы + uint8_t getDay(void); // получить день недели + uint8_t getDate(void); // получить число + uint16_t getYear(void); // получить год + uint8_t getMonth(void); // получить месяц + + String getTimeString(); // получить время как строку вида HH:MM:SS + String getDateString(); // получить дату как строку вида DD.MM.YYYY + void getTimeChar(char* array); // получить время как char array [8] вида HH:MM:SS + void getDateChar(char* array); // получить дату как char array [10] вида DD.MM.YYYY + + bool lostPower(void); // проверка на сброс питания + float getTemperatureFloat(void);// получить температуру float + int getTemperature(void); // получить температуру int + +private: + uint8_t encodeRegister(int8_t data); + int getTemperatureRaw(void); + uint8_t readRegister(uint8_t addr); + uint8_t unpackRegister(uint8_t data); + uint8_t unpackHours(uint8_t data); + const uint8_t _addr; +}; + +#endif \ No newline at end of file