In this tutorial you will make a simple clock. Doing this you will learn how to replace a tangled set of if
's with state machine. If you don't know how state machine works, please read first my Control actions with state machine tutorial.
Table of contents:
In this iteration you:
- make initial cicruit consisting of microcontroller, LCD with I2C adapter and one tact switch (button);
- measure the passage of time using
millis()
function; - reset time after pressing the button.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
#include <Wire.h> #include <LiquidCrystal_I2C.h> #define PIN_RESET 7 #define UPDATE_INTERVAL 1000 LiquidCrystal_I2C lcd(0x27,16,2); // BEGIN: For my virtual clock unsigned long vcCurrentTime; unsigned long vcDeltaTime; unsigned long vcLastUpdate; // END: For my virtual clock char hours, minutes, seconds; unsigned long deltaTime; unsigned long currentTime; // Current time in milli seconds unsigned long ct; // Current time in seconds unsigned long lastUpdate; char buff[64]; void setup() { vcLastUpdate = 0; lastUpdate = 0; Serial.begin(9600); pinMode(PIN_RESET, INPUT); lcd.init(); lcd.backlight(); } void getTime() { ct = currentTime/1000; hours = ct / 3600; minutes = (ct - hours * 3600) / 60; seconds = ct - hours * 3600 - minutes * 60; if (hours == 24) { hours = 0; } } // Create my virtual clock to control it independently // of the actual passage of time. unsigned long updateVirtualClock() { vcCurrentTime = millis(); if (vcCurrentTime >= vcLastUpdate) { vcDeltaTime = vcCurrentTime - vcLastUpdate; currentTime += vcDeltaTime; } else { vcDeltaTime = vcLastUpdate - vcCurrentTime; currentTime = vcDeltaTime; } vcLastUpdate = vcCurrentTime; } void updateDisplay() { deltaTime = currentTime - lastUpdate; // Non-blocking time print once every second if ( deltaTime > UPDATE_INTERVAL || deltaTime < 0) { lastUpdate = currentTime; getTime(); sprintf(buff, "%02d:%02d:%02d", hours, minutes, seconds); Serial.println(buff); lcd.setCursor(0,0); lcd.print(buff); } } void loop() { updateVirtualClock(); updateDisplay(); if(digitalRead(PIN_RESET) == HIGH){ delay(20); currentTime = 0; } } |
In this iteration you:
- add all buttons required to control your clock;
- make a diagram showing actions and how they reflect state changes;
- implement initial code for small subset of actions and states.
Below you can find inital code taking control on the small subset of states and actions:
This should give you an overwiew how you can continue this approach to make fully functional clock. Just after the code you will find an animation how it works.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
#include <Wire.h> #include <LiquidCrystal_I2C.h> #define PIN_MENU 2 #define PIN_ENTER 3 #define PIN_NEXT 4 #define PIN_PREV 5 #define UPDATE_INTERVAL 1000 #define STATE_UNDEFINED 0 #define STATE_WAIT 1 #define STATE_SET_TIME 2 #define STATE_SET_ALARM 3 #define STATE_ALARM_ON_OFF 4 #define ACTION_NONE 0 #define ACTION_MENU 1 #define ACTION_ENTER 2 #define ACTION_NEXT 3 #define ACTION_PREV 4 LiquidCrystal_I2C lcd(0x27,16,2); // BEGIN: For my virtual clock unsigned long vcCurrentTime; unsigned long vcDeltaTime; unsigned long vcLastUpdate; // END: For my virtual clock char hours, minutes, seconds; unsigned long deltaTime; unsigned long currentTime; // Current time in milli seconds unsigned long ct; // Current time in seconds unsigned long lastUpdate; char buff[64]; int currentState = STATE_WAIT; int lastState; int action = ACTION_NONE; void setup() { vcLastUpdate = 0; lastUpdate = 0; Serial.begin(9600); pinMode(PIN_MENU, INPUT); pinMode(PIN_ENTER, INPUT); pinMode(PIN_NEXT, INPUT); pinMode(PIN_PREV, INPUT); lcd.init(); lcd.backlight(); } void getTime() { ct = currentTime/1000; hours = ct / 3600; minutes = (ct - hours * 3600) / 60; seconds = ct - hours * 3600 - minutes * 60; if (hours == 24) { hours = 0; } } // Create my virtual clock to control it independently // of the actual passage of time. unsigned long updateVirtualClock() { vcCurrentTime = millis(); if (vcCurrentTime >= vcLastUpdate) { vcDeltaTime = vcCurrentTime - vcLastUpdate; currentTime += vcDeltaTime; } else { vcDeltaTime = vcLastUpdate - vcCurrentTime; currentTime = vcDeltaTime; } vcLastUpdate = vcCurrentTime; } void displayCurrentTime() { deltaTime = currentTime - lastUpdate; // Non-blocking time print once every second if ( deltaTime > UPDATE_INTERVAL || deltaTime < 0) { lastUpdate = currentTime; getTime(); sprintf(buff, "%02d:%02d:%02d ", hours, minutes, seconds); //Serial.println(buff); lcd.setCursor(0,0); lcd.print(buff); } } void menuLevel1(int option) { if (option == 1) { lcd.clear(); lcd.print("1. Set time"); } else if (option == 2) { lcd.clear(); lcd.print("2. Set alarm"); } else if (option == 3) { lcd.clear(); lcd.print("3. Alarm on/off"); } } void makeAnAction(int newAction) { action = newAction; } void changeState(int newState) { currentState = newState; } void testButtons() { if(digitalRead(PIN_MENU) == HIGH){ delay(20); while(digitalRead(PIN_MENU) == HIGH); delay(20); makeAnAction(ACTION_MENU); } else if(digitalRead(PIN_ENTER) == HIGH){ delay(20); while(digitalRead(PIN_ENTER) == HIGH); delay(20); makeAnAction(ACTION_ENTER); } else if(digitalRead(PIN_NEXT) == HIGH){ delay(20); while(digitalRead(PIN_NEXT) == HIGH); delay(20); makeAnAction(ACTION_NEXT); } else if(digitalRead(PIN_PREV) == HIGH){ delay(20); while(digitalRead(PIN_PREV) == HIGH); delay(20); makeAnAction(ACTION_PREV); } } void loop() { updateVirtualClock(); testButtons(); if (currentState == STATE_WAIT) { displayCurrentTime(); if (action == ACTION_MENU) { changeState(STATE_SET_TIME); } makeAnAction(ACTION_NONE); lastState = STATE_WAIT; } else if (currentState == STATE_SET_TIME) { if (lastState != currentState) { menuLevel1(1); } if (action == ACTION_MENU) { changeState(STATE_WAIT); } else if (action == ACTION_NEXT) { changeState(STATE_SET_ALARM); } else if (action == ACTION_PREV) { changeState(STATE_ALARM_ON_OFF); } makeAnAction(ACTION_NONE); lastState = STATE_SET_TIME; } else if (currentState == STATE_SET_ALARM) { if (lastState != currentState) { menuLevel1(2); } if (action == ACTION_MENU) { changeState(STATE_WAIT); } else if (action == ACTION_NEXT) { changeState(STATE_ALARM_ON_OFF); } else if (action == ACTION_PREV) { changeState(STATE_SET_TIME); } makeAnAction(ACTION_NONE); lastState = STATE_SET_ALARM; } else if (currentState == STATE_ALARM_ON_OFF) { if (lastState != currentState) { menuLevel1(3); } if (action == ACTION_MENU) { changeState(STATE_WAIT); } else if (action == ACTION_NEXT) { changeState(STATE_SET_TIME); } else if (action == ACTION_PREV) { changeState(STATE_SET_ALARM); } makeAnAction(ACTION_NONE); lastState = STATE_ALARM_ON_OFF; } } |
In this iteration you:
- turn a set of
if
s into state machine which simplifies code development.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
#include <Wire.h> #include <LiquidCrystal_I2C.h> #define PIN_MENU 2 #define PIN_ENTER 3 #define PIN_NEXT 4 #define PIN_PREV 5 #define UPDATE_INTERVAL 1000 #define STATE_UNDEFINED 0 #define STATE_WAIT 1 #define STATE_SET_TIME 2 #define STATE_SET_ALARM 3 #define STATE_ALARM_ON_OFF 4 #define ACTION_NONE 0 #define ACTION_TICK 1 #define ACTION_MENU 2 #define ACTION_ENTER 3 #define ACTION_NEXT 4 #define ACTION_PREV 5 #define INDEX_STATE_CURRENT 0 #define INDEX_ACTION 1 #define INDEX_STATE_NEW 2 #define NUMBER_OF_ROUTES 11 // Warning!!! // Remember to update NUMBER_OF_ROUTES when changing routing table byte routes[][3] = {{STATE_WAIT, ACTION_TICK, STATE_WAIT}, {STATE_WAIT, ACTION_MENU, STATE_SET_TIME}, {STATE_SET_TIME, ACTION_NEXT, STATE_SET_ALARM}, {STATE_SET_TIME, ACTION_PREV, STATE_ALARM_ON_OFF}, {STATE_SET_TIME, ACTION_MENU, STATE_WAIT}, {STATE_SET_ALARM, ACTION_NEXT, STATE_ALARM_ON_OFF}, {STATE_SET_ALARM, ACTION_PREV, STATE_SET_TIME}, {STATE_SET_ALARM, ACTION_MENU, STATE_WAIT}, {STATE_ALARM_ON_OFF, ACTION_NEXT, STATE_SET_TIME}, {STATE_ALARM_ON_OFF, ACTION_PREV, STATE_SET_ALARM}, {STATE_ALARM_ON_OFF, ACTION_MENU, STATE_WAIT} }; LiquidCrystal_I2C lcd(0x27,16,2); // BEGIN: For my virtual clock unsigned long vcCurrentTime; unsigned long vcDeltaTime; unsigned long vcLastUpdate; // END: For my virtual clock char hours, minutes, seconds; unsigned long deltaTime; unsigned long currentTime; // Current time in milli seconds unsigned long ct; // Current time in seconds unsigned long lastUpdate; char buff[64]; int currentState = STATE_WAIT; int lastState; int action = ACTION_TICK; int lastAction; void setup() { vcLastUpdate = 0; lastUpdate = 0; Serial.begin(9600); pinMode(PIN_MENU, INPUT); pinMode(PIN_ENTER, INPUT); pinMode(PIN_NEXT, INPUT); pinMode(PIN_PREV, INPUT); lcd.init(); lcd.backlight(); } void getTime() { ct = currentTime/1000; hours = ct / 3600; minutes = (ct - hours * 3600) / 60; seconds = ct - hours * 3600 - minutes * 60; if (hours == 24) { hours = 0; } } // Create my virtual clock to control it independently // of the actual passage of time. unsigned long updateVirtualClock() { vcCurrentTime = millis(); if (vcCurrentTime >= vcLastUpdate) { vcDeltaTime = vcCurrentTime - vcLastUpdate; currentTime += vcDeltaTime; } else { vcDeltaTime = vcLastUpdate - vcCurrentTime; currentTime = vcDeltaTime; } vcLastUpdate = vcCurrentTime; } void displayCurrentTime() { deltaTime = currentTime - lastUpdate; // Non-blocking time print once every second if ( deltaTime > UPDATE_INTERVAL || deltaTime < 0) { lastUpdate = currentTime; getTime(); sprintf(buff, "%02d:%02d:%02d ", hours, minutes, seconds); //Serial.println(buff); lcd.setCursor(0,0); lcd.print(buff); } } void menuLevel1(int option) { if (option == 1) { lcd.clear(); lcd.print("1. Set time"); } else if (option == 2) { lcd.clear(); lcd.print("2. Set alarm"); } else if (option == 3) { lcd.clear(); lcd.print("3. Alarm on/off"); } } void makeAnAction(int newAction) { action = newAction; } void changeState(int newState) { currentState = newState; } void testButtons() { if(digitalRead(PIN_MENU) == HIGH){ delay(20); while(digitalRead(PIN_MENU) == HIGH); delay(20); makeAnAction(ACTION_MENU); } else if(digitalRead(PIN_ENTER) == HIGH){ delay(20); while(digitalRead(PIN_ENTER) == HIGH); delay(20); makeAnAction(ACTION_ENTER); } else if(digitalRead(PIN_NEXT) == HIGH){ delay(20); while(digitalRead(PIN_NEXT) == HIGH); delay(20); makeAnAction(ACTION_NEXT); } else if(digitalRead(PIN_PREV) == HIGH){ delay(20); while(digitalRead(PIN_PREV) == HIGH); delay(20); makeAnAction(ACTION_PREV); } } void loop() { updateVirtualClock(); testButtons(); stateMachine(); } void stateWait() { displayCurrentTime(); makeAnAction(ACTION_TICK); } void stateSetTime() { menuLevel1(1); } void stateSetAlarm() { menuLevel1(2); } void stateAlarmOnOff() { menuLevel1(3); } void executeStateAction(){ switch(currentState) { case STATE_WAIT: stateWait(); break; case STATE_SET_TIME: stateSetTime(); break; case STATE_SET_ALARM: stateSetAlarm(); break; case STATE_ALARM_ON_OFF: stateAlarmOnOff(); break; } } void stateMachine(){ byte i; //while(true) { if(action != ACTION_NONE){ lastAction = action; makeAnAction(ACTION_NONE); for(i=0; i<NUMBER_OF_ROUTES; ++i){ if(routes[i][INDEX_STATE_CURRENT] == currentState && routes[i][INDEX_ACTION] == lastAction){ changeState(routes[i][INDEX_STATE_NEW]); break; } } executeStateAction(); lastState = currentState; } //} } |
In this iteration you:
- add LED to signal alarm activation;
- implement all required functionality according to description from iteration 2.
Code from this iteration is much longer than from previous. It's not more complicated but is longer simply because it implements all functionality. I also added to the circuit LED and buzzer. Because it's long I divided it into parts:
- clock.ino - main file
- set_time.ino
- set_alarm.ino
- alarm_on_off.ino
- state_machine.ino
- clock.zip - complete project
This code works - see the video (approx. 128MiB).