Zápisník experimentátora
When you create a project using the Arduino and use buttons and LEDs, you usually create some type of state machine. The state machine responds to the buttons and outputs signals outward via the output pins. You may not even realize that you are creating the state machine that has to adhere to a lot of rules. Typically, you have a limited number of buttons and one button performs various tasks. The state machine makes it easy to define the states and definition of actions that are associated with these states. And with these exact rules, your program will respond as you need it. Let's learn how to make state machine comfortably.
We will use the ready-made solution to create state machines. You install the arduino-fsm library using a library manager. In three examples, we will show its possibilities. In the demos, I used the examples that are part of the library. However, this library also suffers from bad documentation, and examples contain mistakes. I have corrected these bugs and added one more example to better illustrate the possibilities of the library.
The first example mimics the typical Blink program, with which begins every Arduino user. The program is longer than the original Blink, as it demonstrates callback capabilities at any point in time. In this case, most of the features do nothing; they merely list the serial port information. This is a good introduction to understanding the state machine.
Each state (state_light_on
and state_light_off
) is defined by three callback functions.
In order to switch between states, it is necessary to define the permitted paths between states. This is done using the function add_transition
. Its parameters are
If the path between states is defined in the system and the event is signaled, the state machine switches to a new state. Switching is accompanied by calling all callback functions in the correct order. This order can be found in the source code of the function make_transition
. The event is send to the state machine using the function trigger
.
#include "Fsm.h"
// Used pins
#define LED_PIN LED_BUILTIN
// State machine variables
#define FLIP_LIGHT_SWITCH 1
State state_light_on(on_light_on_enter, NULL, &on_light_on_exit);
State state_light_off(on_light_off_enter, NULL, &on_light_off_exit);
Fsm fsm(&state_light_off);
// Transition callback functions
void on_light_on_enter() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, HIGH);
}
void on_light_on_exit() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, LOW);
}
void on_light_off_enter() {
Serial.println(__FUNCTION__);
}
void on_light_off_exit() {
Serial.println(__FUNCTION__);
}
void on_trans_light_on_light_off() {
Serial.println(__FUNCTION__);
Serial.println(" Transitioning from LIGHT_ON to LIGHT_OFF");
}
void on_trans_light_off_light_on() {
Serial.println(__FUNCTION__);
Serial.println(" Transitioning from LIGHT_OFF to LIGHT_ON");
}
// standard arduino functions
void setup() {
Serial.begin(9600);
Serial.println("FSM Light Switch");
pinMode(LED_PIN, OUTPUT);
fsm.add_transition(&state_light_on, &state_light_off,
FLIP_LIGHT_SWITCH,
&on_trans_light_on_light_off);
fsm.add_transition(&state_light_off, &state_light_on,
FLIP_LIGHT_SWITCH,
&on_trans_light_off_light_on);
fsm.run_machine();
}
void loop() {
Serial.println("-- loop --");
delay(2000);
fsm.trigger(FLIP_LIGHT_SWITCH);
}
The second example uses a state machine to switch the status using a button. The first press switches state_led_on. The second press switches state_led_off. You can also press the button to handle the button debounce in the function check_button
. Pushbuttons often generate spurious open/close transitions when pressed, due to mechanical and physical issues: these transitions may be read as multiple presses in a very short time fooling the program.
The demo contains another interesting transition between the states. This is a timed transition, which is called automatically when state_led_on is turned on and 5 seconds elapsed. This will be done by the function add_timed_transition
. This is a very good aid in the world of microcontrollers, where such timing has a lot of use. You do not have to complicate programming, just add a timed transition. Note that the loop function calls only one function run_machine
. This function calls the function check_button
, which takes care of triggering the event at the moment the button push is detected.
#include "Fsm.h"
// Used pins
#define LED_PIN LED_BUILTIN
#define BUTTON_PIN 10
// Events
#define BUTTON_EVENT 0
// Button states and debounce
int buttonState = 0;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
// States
State state_led_off(&led_off, &check_button, NULL);
State state_led_on(&led_on, &check_button, NULL);
Fsm fsm(&state_led_off);
void led_off() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, LOW);
}
void led_on() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, HIGH);
}
// Check button and debounce
void check_button() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState)
lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > 50) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
Serial.println("button_pressed");
fsm.trigger(BUTTON_EVENT);
}
}
}
lastButtonState = reading;
}
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
fsm.add_transition(&state_led_off, &state_led_on,
BUTTON_EVENT, NULL);
fsm.add_timed_transition(&state_led_on, &state_led_off, 5000, NULL);
fsm.add_transition(&state_led_on, &state_led_off, BUTTON_EVENT, NULL);
}
void loop() {
fsm.run_machine();
}
In the last example, I decided to test the library's options on a larger state machine. From the previous example, I left the power on and off and added other states. These are used to control eight LEDs. I have them connected as shown in the picture. I use a module with 8 LEDs along with resistors. This is one of my own module and I use it on a breadboard.
8 LEDs make up a fancy strip of LED lights that turn on gradually. Press the buttons repeatedly to turn on all the LEDs. If you continue to press the button, the LEDs will turn off and the entire cycle will be repeated. Note how I used the function led_leave
in the last state. Here I was given the opportunity to perform the action when I left the state and reset the LED counter.
To make the example more complicated, I also added timed transitions. The length of the LED strip remaining in the lighted state is directly proportional to the number of LEDs on. If I do not press the button again at the specified time, the whole state machine switches to the initial state.
#include "Fsm.h"
// Used pins
#define LED_PIN LED_BUILTIN
#define BUTTON_PIN 10
const int LED_BAR[] = {2, 3, 4, 5, 6, 7, 8, 9};
const int LED_BAR_LENGTH = sizeof(LED_BAR) / sizeof(int);
int bar_position = 0;
// Events
#define BUTTON_EVENT 0
// Button states and debounce
int buttonState = 0;
int lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
// States
State state_led_off(&led_off, &check_button, NULL);
State state_led_on(&led_on, &check_button, NULL);
State state_led1(&led_enter, &check_button, NULL);
State state_led2(&led_enter, &check_button, NULL);
State state_led3(&led_enter, &check_button, NULL);
State state_led4(&led_enter, &check_button, NULL);
State state_led5(&led_enter, &check_button, NULL);
State state_led6(&led_enter, &check_button, NULL);
State state_led7(&led_enter, &check_button, NULL);
State state_led8(&led_enter, &check_button, &led_leave);
Fsm fsm(&state_led_off);
void led_off() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, LOW);
for (int i = 0; i < LED_BAR_LENGTH; i++)
digitalWrite(LED_BAR[i], LOW);
bar_position = 0;
}
void led_on() {
Serial.println(__FUNCTION__);
digitalWrite(LED_PIN, HIGH);
}
void led_enter() {
Serial.println(__FUNCTION__);
if (bar_position < LED_BAR_LENGTH)
bar_position++;
for (int i = 0; i < bar_position; i++)
digitalWrite(LED_BAR[i], HIGH);
for (int i = bar_position; i < LED_BAR_LENGTH; i++)
digitalWrite(LED_BAR[i], LOW);
}
void led_leave() {
Serial.println(__FUNCTION__);
bar_position = 0;
}
// Check button and debounce
void check_button() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState)
lastDebounceTime = millis();
if ((millis() - lastDebounceTime) > 50) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == LOW) {
Serial.println("button_pressed");
fsm.trigger(BUTTON_EVENT);
}
}
}
lastButtonState = reading;
}
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
for (int i = 0; i < LED_BAR_LENGTH; i++)
pinMode(LED_BAR[i], OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// on/off
fsm.add_transition( &state_led_off, &state_led_on, BUTTON_EVENT, NULL);
fsm.add_timed_transition(&state_led_on, &state_led_off, 5000, NULL);
// LED bar button
fsm.add_transition( &state_led_on, &state_led1, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led1, &state_led2, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led2, &state_led3, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led3, &state_led4, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led4, &state_led5, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led5, &state_led6, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led6, &state_led7, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led7, &state_led8, BUTTON_EVENT, NULL);
fsm.add_transition( &state_led8, &state_led1, BUTTON_EVENT, NULL);
// timed transitions
fsm.add_timed_transition(&state_led1, &state_led_off, 1000, NULL);
fsm.add_timed_transition(&state_led2, &state_led_off, 2000, NULL);
fsm.add_timed_transition(&state_led3, &state_led_off, 3000, NULL);
fsm.add_timed_transition(&state_led4, &state_led_off, 4000, NULL);
fsm.add_timed_transition(&state_led5, &state_led_off, 5000, NULL);
fsm.add_timed_transition(&state_led6, &state_led_off, 6000, NULL);
fsm.add_timed_transition(&state_led7, &state_led_off, 7000, NULL);
fsm.add_timed_transition(&state_led8, &state_led_off, 8000, NULL);
}
void loop() {
fsm.run_machine();
}
The source code is located on the GitHub server.
14.02.2018