Под катом пример цифрового автомата и пояснениями.
Вряд ли кому-то этот код пригодится целиком, но в качестве понимания работы конечного автомата, очень даже подойдет.
Код
#include <Arduino.h> #define RELAY_UP_PIN 2 #define RELAY_DOWN_PIN 3 #define LED_PIN 4 #define SENSOR_TOP_PIN 5 #define SENSOR_CALIB_PIN 6 #define SENSOR_COUNTER_PIN 7 #define BUTTON_START_PIN 8 #define BUTTON_STOP_PIN 9 #define BEEPER_PIN 10 #define TOP_SENSOR_ACTIVE 0 #define CALIB_SENSOR_ACTIVE 1 #define BUTTON_ACTIVE 0 #define QUICK_BLINK_DELAY 100 #define SLOW_BLINK_DELAY 500 #define BUTTON_DEBOUNCE_DELAY 100 #define LONG_PRESS_DELAY 1000 #define MOTOR_STOP_DELAY 800 #define MEASURING_DELAY 30000 #define BEEPER_TONE 2000 #define BRAKE_COUNT_DOWN 33 #define BRAKE_COUNT_UP 32 #define STATE_UNDEFINED 0 #define STATE_STOP 1 #define STATE_MOVE_DOWN 2 #define STATE_STOPPING_DOWN 3 #define STATE_MOVE_UP 4 #define STATE_STOPPING_UP 5 #define STATE_CALIBRATED 6 #define STATE_TOP 7 #define STATE_WAITING 8 #define STATE_FINISH 9 //#define STATE_POSITION 10 #define COMMAND_NO_COMMAND 0 #define COMMAND_STOP 1 #define COMMAND_START 2 #define LED_STATE_OFF 0 #define LED_STATE_ON 1 #define LED_STATE_QUICK_BLINK 2 #define LED_STATE_SLOW_BLINK 3 #define POSITION_FREE 2332 // 53.0mm #define POSITION_40 1320 // 30.0mm #define POSITION_70 550 // 12.5mm #define POSITION_CALIBRATION 440 // 10.0mm height in pulse count #define COUNTER_DEVIDER 44 // pulse per millimeter byte lastCState; byte lastFState; byte cState; byte fState; byte ledState; byte command; bool ledON; bool startButtonState; bool startButtonLastState; bool startButtonStateChanged; bool stopButtonState; bool stopButtonLastState; bool stopButtonStateChanged; bool counterSensorLastState; unsigned long startButtonPressTime; unsigned long startButtonTime; unsigned long stopButtonPressTime; unsigned long stopButtonTime; unsigned long currentTime; unsigned long ledSwitchTime; unsigned long stopTime; unsigned long finishTime; byte pressCounter; byte topSensorCounter; int height; int position; void setup() { pinMode(RELAY_UP_PIN, OUTPUT); pinMode(RELAY_DOWN_PIN, OUTPUT); digitalWrite(RELAY_UP_PIN, LOW); digitalWrite(RELAY_DOWN_PIN, LOW); pinMode(RELAY_DOWN_PIN, OUTPUT); pinMode(BEEPER_PIN, OUTPUT); pinMode(SENSOR_TOP_PIN, INPUT); pinMode(SENSOR_CALIB_PIN, INPUT); pinMode(SENSOR_COUNTER_PIN, INPUT); pinMode(BUTTON_START_PIN, INPUT); pinMode(BUTTON_STOP_PIN, INPUT); cState = STATE_UNDEFINED; fState = STATE_STOP; ledState = LED_STATE_OFF; ledON = false; ledSwitchTime = 0; startButtonState = false; startButtonLastState = false; startButtonStateChanged = false; startButtonTime = 0; startButtonPressTime = 0; stopButtonState = false; stopButtonLastState = false; stopButtonStateChanged = false; stopButtonTime = 0; stopButtonPressTime = 0; counterSensorLastState = false; pressCounter = 0; height = -1; position = POSITION_FREE; command = COMMAND_NO_COMMAND; topSensorCounter = 0; lastCState = 255; lastFState = 255; tone(BEEPER_PIN, BEEPER_TONE, 1000); } void loop() { currentTime = millis(); automataRun(); // Transitions control buttonsProcessing(); // Buttons processing sensorsProcessing(); // Sensors processing ledControl(); // LED control } inline void startUp(){ digitalWrite(RELAY_UP_PIN, 1); cState = STATE_MOVE_UP; } inline void startDown(){ digitalWrite(RELAY_DOWN_PIN, 1); cState = STATE_MOVE_DOWN; } inline void stopUp(){ if(cState != STATE_STOPPING_UP){ digitalWrite(RELAY_UP_PIN, 0); stopTime = currentTime; cState = STATE_STOPPING_UP; } } inline void stopDown(){ if(cState != STATE_STOPPING_DOWN){ digitalWrite(RELAY_DOWN_PIN, 0); stopTime = currentTime; cState = STATE_STOPPING_DOWN; } } void automataRun(){ if(cState == fState){ return; } if(fState == STATE_FINISH){ if(height == -1){ fState = STATE_STOP; } switch(cState){ case STATE_TOP:{ pressCounter = 0; position = POSITION_70; startDown(); break; } case STATE_MOVE_DOWN:{ if(height - BRAKE_COUNT_DOWN < position){ stopDown(); } break; } case STATE_MOVE_UP:{ if(height + BRAKE_COUNT_UP > position ){ stopUp(); } break; } case STATE_STOPPING_DOWN:{ if(currentTime - stopTime > MOTOR_STOP_DELAY){ if(digitalRead(SENSOR_CALIB_PIN) == CALIB_SENSOR_ACTIVE){ height = POSITION_CALIBRATION; cState = STATE_CALIBRATED; } else{ cState = STATE_STOP; } } return; } case STATE_STOPPING_UP:{ if(currentTime - stopTime > MOTOR_STOP_DELAY){ if(digitalRead(SENSOR_TOP_PIN) == TOP_SENSOR_ACTIVE){ cState = STATE_TOP; } else{ cState = STATE_STOP; } } return; } case STATE_STOP:{ if(position == POSITION_70){ startUp(); pressCounter++; position = POSITION_FREE; } else if(position == POSITION_40){ cState = STATE_WAITING; finishTime = currentTime; } else if(position == POSITION_FREE){ startDown(); if(pressCounter < 3){ position = POSITION_70; } else{ position = POSITION_40; } } break; } case STATE_WAITING:{ if(currentTime - finishTime > MEASURING_DELAY){ cState = STATE_FINISH; tone(BEEPER_PIN, BEEPER_TONE, 2000); ledState = LED_STATE_ON; } } } return; } switch(cState){ case STATE_UNDEFINED:{ stopUp(); stopDown(); cState = STATE_STOP; fState = STATE_STOP; ledState = LED_STATE_OFF; return; } case STATE_TOP:{ switch(fState){ case STATE_MOVE_DOWN:{ startDown(); return; } case STATE_CALIBRATED:{ startDown(); return; } case STATE_FINISH:{ startDown(); pressCounter = -1; return; } } return; } case STATE_MOVE_DOWN:{ switch(fState){ case STATE_TOP:{ stopDown(); return; } case STATE_MOVE_UP:{ stopDown(); return; } case STATE_STOP:{ stopDown(); return; } } return; } case STATE_MOVE_UP:{ switch(fState){ case STATE_MOVE_DOWN:{ stopUp(); return; } case STATE_CALIBRATED:{ stopUp(); return; } case STATE_STOP:{ stopUp(); return; } } return; } case STATE_STOPPING_DOWN:{ if(currentTime - stopTime > MOTOR_STOP_DELAY){ if(digitalRead(SENSOR_CALIB_PIN) == CALIB_SENSOR_ACTIVE){ height = POSITION_CALIBRATION; cState = STATE_CALIBRATED; } else{ cState = STATE_STOP; } ledState = LED_STATE_OFF; } return; } case STATE_STOPPING_UP:{ if(currentTime - stopTime > MOTOR_STOP_DELAY){ if(digitalRead(SENSOR_TOP_PIN) == TOP_SENSOR_ACTIVE){ cState = STATE_TOP; } else{ cState = STATE_STOP; } ledState = LED_STATE_OFF; } return; } case STATE_CALIBRATED:{ switch(fState){ case STATE_TOP:{ startUp(); return; } case STATE_MOVE_UP:{ startUp(); return; } } return; } case STATE_FINISH:{ switch(fState){ case STATE_TOP:{ startUp(); return; } case STATE_MOVE_DOWN:{ startDown(); return; } case STATE_MOVE_UP:{ startUp(); return; } case STATE_CALIBRATED:{ startDown(); return; } } return; } case STATE_STOP:{ switch(fState){ case STATE_TOP:{ startUp(); return; } case STATE_MOVE_DOWN:{ startDown(); return; } case STATE_MOVE_UP:{ startUp(); return; } case STATE_CALIBRATED:{ startDown(); return; } } return; } } } void sensorsProcessing(){ if(digitalRead(SENSOR_TOP_PIN) == TOP_SENSOR_ACTIVE && cState == STATE_MOVE_UP){ if(topSensorCounter > 3){ stopUp(); ledState = LED_STATE_ON; } else{ topSensorCounter++; } } else{ topSensorCounter = 0; } if(digitalRead(SENSOR_CALIB_PIN) == CALIB_SENSOR_ACTIVE && cState == STATE_MOVE_DOWN){ stopDown(); ledState = LED_STATE_ON; } bool counterSensorState = digitalRead(SENSOR_COUNTER_PIN) == 0; if(height != -1){ if(counterSensorLastState != counterSensorState){ if(cState == STATE_MOVE_DOWN || cState == STATE_STOPPING_DOWN){ height--; } else if(cState == STATE_MOVE_UP || cState == STATE_STOPPING_UP){ height++; } counterSensorLastState = counterSensorState; } } } void buttonsProcessing(){ buttonsDebounce(); if(startButtonState){ if(currentTime - startButtonPressTime >= LONG_PRESS_DELAY){ // Start button long press ledState = LED_STATE_SLOW_BLINK; fState = STATE_CALIBRATED; } } if(startButtonStateChanged){ if(startButtonState == false){ if(currentTime - startButtonPressTime < LONG_PRESS_DELAY){ // Start button quick press ledState = LED_STATE_QUICK_BLINK; fState = STATE_FINISH; } } } if(stopButtonState){ if(currentTime - stopButtonPressTime >= LONG_PRESS_DELAY){ // Stop button long press ledState = LED_STATE_SLOW_BLINK; fState = STATE_TOP; } } if(stopButtonStateChanged){ if(stopButtonState == false){ if(currentTime - stopButtonPressTime < LONG_PRESS_DELAY){ // Stop button quick press ledState = LED_STATE_OFF; fState = STATE_STOP; } } } } void ledControl(){ switch(ledState){ case LED_STATE_OFF:{ digitalWrite(LED_PIN, 0); ledON = false; break; } case LED_STATE_ON:{ digitalWrite(LED_PIN, 1); ledON = true; break; } case LED_STATE_QUICK_BLINK:{ if(currentTime - ledSwitchTime > QUICK_BLINK_DELAY){ ledON = !ledON; digitalWrite(LED_PIN, ledON); ledSwitchTime = currentTime; } break; } case LED_STATE_SLOW_BLINK:{ if(currentTime - ledSwitchTime > SLOW_BLINK_DELAY){ ledON = !ledON; digitalWrite(LED_PIN, ledON); ledSwitchTime = currentTime; } break; } } } void buttonsDebounce(){ bool currentButtonState = digitalRead(BUTTON_START_PIN) == BUTTON_ACTIVE; startButtonStateChanged = false; if(startButtonLastState != currentButtonState){ startButtonTime = currentTime; startButtonLastState = currentButtonState; } else{ if(startButtonState != currentButtonState && (currentTime - startButtonTime) > BUTTON_DEBOUNCE_DELAY){ startButtonState = currentButtonState; startButtonStateChanged = true; if(startButtonState == true){ startButtonPressTime = currentTime; } } } currentButtonState = digitalRead(BUTTON_STOP_PIN) == BUTTON_ACTIVE; stopButtonStateChanged = false; if(stopButtonLastState != currentButtonState){ stopButtonTime = currentTime; stopButtonLastState = currentButtonState; } else{ if(stopButtonState != currentButtonState && (currentTime - stopButtonTime > BUTTON_DEBOUNCE_DELAY)){ stopButtonState = currentButtonState; stopButtonStateChanged = true; if(stopButtonState == true){ stopButtonPressTime = currentTime; } } } }
Собственно сам цикл.
Все просто до безобразия
void loop() { currentTime = millis(); automataRun(); // Transitions control buttonsProcessing(); // Buttons processing sensorsProcessing(); // Sensors processing ledControl(); // LED control }
Переходы конечного автомата
void automataRun(){ if(cState == fState){ return; Если автомат находится в конечном состоянии, то ничего не делаем } if(fState == STATE_FINISH){ ... Здесь обрабатывается логика автомата запущенного по программе return; } ...Здесь расположен код работы в ручном режиме. Удерживание более 1 сек кнопки СТАРТ движение до датчика вниз (калибровка) Удерживание более 1 сек кнопки СТОП движение вверх до ограничительного датчика Быстрое нажатие кнопки СТАРТ - запуск программы Быстрое нажатие кнопки СТОП - остановить любое движение (вверх.вниз.программу)
Обработка датчиков
Мне не удалось полностью избавиться от помех на линиях датчиков, поэтому пришлось сделать программную обработку всплесков (помех)
if(digitalRead(SENSOR_TOP_PIN) == TOP_SENSOR_ACTIVE && cState == STATE_MOVE_UP){ if(topSensorCounter > 3){ stopUp(); ledState = LED_STATE_ON; } else{ topSensorCounter++; } } else{ topSensorCounter = 0; }
Проверка верхнего сенсора проводится только при движении вверх, и только после того как счетчик topSensorCounter увеличится до значения 3 сигнал датчика считается активным, если же состояние датчика за три прохода основного цикла поменялось назад, то счетчик сбрасывается и это считается помехой.
Управление светодиодом простое, пояснений не требует 🙂
Антидребезг
void buttonsDebounce(){ bool currentButtonState = digitalRead(BUTTON_START_PIN) == BUTTON_ACTIVE; startButtonStateChanged = false; // Сбрасываем признак, что кнопка только что была нажата if(startButtonLastState != currentButtonState){ // Если состояние кнопки изменилось, то запоминаем время нажатия и запоминаем последнее состояние кнопки startButtonTime = currentTime; startButtonLastState = currentButtonState; } else{ // Если состояние кнопки не изменилось, но оно не равно предыдущему подтвержденному состоянию и // время после изменения состояние прошло больше чем BUTTON_DEBOUNCE_DELAY, то считаем, // что состояние кнопки точно поменялось. if(startButtonState != currentButtonState && (currentTime - startButtonTime) > BUTTON_DEBOUNCE_DELAY){ // Изменяем подтвержденное состояние кнопки, устанавливаем признак КНОПКА НАЖАТА и если кнопка // перешла в активное состояние, то запоминаем время этого нажатия. startButtonState = currentButtonState; startButtonStateChanged = true; if(startButtonState == true){ startButtonPressTime = currentTime; } } }
Переменная startButtonStateChanged нужна для того, чтобы в том месте, где обрабатываются нажатия кнопок можно было узнать сменилось ли ее состояние или нет. На следующий цикл это состояние будет сброшено. Это нужно, например, для определения долгого нажатия кнопки.
Константа BUTTON_DEBOUNCE_DELAY определяет время антидребезга
#define BUTTON_DEBOUNCE_DELAY 100
Для лучшего понимания кода прибор выполняет следующие действия после запуска программы.
1. Платформа опускается до значения #define POSITION_70 550
2. Платформа поднимается до значения #define POSITION_FREE 2332
(Эти действия повторяются три раза.
3. Платформа опускается до значения #define POSITION_40 1320
4. Производится задержка в течение #define MEASURING_DELAY 30000
5. Пищим динамиком — программа окончена.
Положение платформы считается счетчиком
Один оборот
#define COUNTER_DEVIDER 44 // pulse per millimeter
Положение калибровки платформы
#define POSITION_CALIBRATION 440 // 10.0mm height in pulse count
Если после включения устройства не была произведена калибровка, то программа исполняться не будет.
Платформа при движении имеет определенную инерцию, поэтому двигатель необходимо выключать немного раньше, чем достигнутое значение счетчика импульсов.
Константы
#define BRAKE_COUNT_DOWN 33
#define BRAKE_COUNT_UP 32
определяют насколько раньше нужно начать остановку платформы.
Вроде бы больше премудростей в коде нет.
Если будут вопросы по коду буду отвечать в комментариях.
Собственно эта статья написана для того, чтобы объяснить, что код с задержками — это зло (это мое личное мнение, об этом можно спорить, как и об GOTO в языках программирования, так что на эту тему я отвечать не буду).
Иногда они нужно, но очень редко.
Вывод такой: конечные автоматы — это просто!
С коде есть пара скользких моментов, но для понимания работы автомата они не помешают.