1. Introduction
- Arduino ESP32 core is built over FreeRTOS. If you look into the cores/esp32 folder you will see a file called "main.cpp" with code:
- Here you can see our Arduino program is just a task of FreeRTOS. When the task is invoked it will invoke “setup()” function and then invoke the “loop()” in an infinite loop.
- We only have one task so we can only execute one job. Suppose that we implement an application that have 2 jobs:
+ job A to scan the input from keyboard.
+ job B to send a message and wait for response.
Our implementation:
If job B takes long time to finish, it will block job A. So job A may miss some keyboard pressing event. It is not a good design.
2. How to apply Finite State Machine (FSM)
- In order to overcome this we will use a method called Finite State Machine. The purpose of this method is to divide a machine into a collection of states. At a specific time, only one state is active and in each state the machine just executes a specific action. After finishing that action, it may transit to another state to execute another action. For example: we have a lighting system. It can have 3 states: ON, OFF, IDLE. Its normal state is IDLE and waiting for trigger to change state ON or OFF and then back to IDLE.
Note: people often implement Finite State Machine using switch/case structure.
- Back to the example above we can re-implement the application like below:
- Here jobB is divided into 3 states: SEND_MESSAGE, WAIT_FOR_RESPONSE and IDLE. jobB executes a specific action according to its state in every cycle and do not block jobA anymore. Each job has a chance to execute itself in every cycle, look like multitasking.
- I applied this method in Demo 15: How to build a system to update Price Tag automatically using Arduino ESP32
You can see in the code to display the received message on OLED, I used the switch/case structure, it is a style of FSM and it has states 1,2,3,4,5,6 but I did not mention the detail name of states since I just apply it to avoid blocking. Certainly we can apply it to make the program well-organized and easy to maintain. I will take one more example:
In Demo 14: How to use MQTT and Arduino ESP32 to build a simple Smart home system .
The loop() function have 2 jobs: MQTT handler and DHT temperature measure. In the old code we use counter:
Applying Finite State Machine, we can implement like below:
- Arduino ESP32 core is built over FreeRTOS. If you look into the cores/esp32 folder you will see a file called "main.cpp" with code:
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 | #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "Arduino.h" #if CONFIG_AUTOSTART_ARDUINO #if CONFIG_FREERTOS_UNICORE #define ARDUINO_RUNNING_CORE 0 #else #define ARDUINO_RUNNING_CORE 1 #endif void loopTask(void *pvParameters) { setup(); for(;;) { micros(); //update overflow loop(); } } extern "C" void app_main() { initArduino(); xTaskCreatePinnedToCore(loopTask, "loopTask", 4096, NULL, 1, NULL, ARDUINO_RUNNING_CORE); } #endif |
- We only have one task so we can only execute one job. Suppose that we implement an application that have 2 jobs:
+ job A to scan the input from keyboard.
+ job B to send a message and wait for response.
Our implementation:
1 2 3 4 5 6 7 8 9 | void loop(){ jobA_scan_the_keyboard(); jobB_send_message(); while(1){ if(jobB_get_response()){ break; } } } |
2. How to apply Finite State Machine (FSM)
- In order to overcome this we will use a method called Finite State Machine. The purpose of this method is to divide a machine into a collection of states. At a specific time, only one state is active and in each state the machine just executes a specific action. After finishing that action, it may transit to another state to execute another action. For example: we have a lighting system. It can have 3 states: ON, OFF, IDLE. Its normal state is IDLE and waiting for trigger to change state ON or OFF and then back to IDLE.
Note: people often implement Finite State Machine using switch/case structure.
- Back to the example above we can re-implement the application like below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void loop(){ jobA_scan_the_keyboard(); switch(jobB_state){ case SEND_MESSAGE: jobB_send_message(); jobB_state = WAIT_FOR_RESPONSE; break; case WAIT_FOR_RESPONSE: if(jobB_get_response()){ jobB_state = IDLE; break; } case IDLE: if(need_send_message){ jobB_state = SEND_MESSAGE; break; } case default: break; } } |
- I applied this method in Demo 15: How to build a system to update Price Tag automatically using Arduino ESP32
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 | void loop() { /* if client was disconnected then try to reconnect again */ if (!client.connected()) { mqttconnect(); } /* this function will listen for incomming subscribed topic-process-invoke receivedCallback */ client.loop(); /* because OLED waste time so we use state machine to display step by step every loop */ switch(dispState) { case 1: display.clearDisplay(); dispState = 2; break; case 2: display.setCursor(2,2); dispState = 3; break; case 3: display.println((char *)rec); dispState = 4; break; case 4: display.setCursor(2,22); dispState = 5; break; case 5: display.println((char *)&rec[separate]); dispState = 6; break; case 6: display.display(); dispState = 0; memset(rec, 0, BUF_SIZE); break; default: break; } } |
In Demo 14: How to use MQTT and Arduino ESP32 to build a simple Smart home system .
The loop() function have 2 jobs: MQTT handler and DHT temperature measure. In the old code we use counter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void loop() { /* if client was disconnected then try to reconnect again */ if (!client.connected()) { mqttconnect(); } /* this function will listen for incomming subscribed topic-process-invoke receivedCallback */ client.loop(); /* we measure temperature every 3 secs we count until 3 secs reached to avoid blocking program if using delay()*/ long now = millis(); if (now - lastMsg > 3000) { lastMsg = now; /* read DHT11/DHT22 sensor and convert to string */ temperature = dht.readTemperature(); if (!isnan(temperature)) { snprintf (msg, 20, "%lf", temperature); /* publish the message */ client.publish(TEMP_TOPIC, msg); } } } |
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 | /* these are available states of DHT */ typedef enum { /* wait until 3 seconds */ WAIT_FOR_TIMEOUT = 1, /* read DHT sensor for temperature */ START_MEASURE, /* publish MQTT topic temperature */ PUBLISH_MEASURE } DHT_State; /* create a variable to hold current state of DHT */ DHT_State state = WAIT_FOR_TIMEOUT; void loop() { /* if client was disconnected then try to reconnect again */ if (!client.connected()) { mqttconnect(); } /* this function will listen for incomming subscribed topic-process-invoke receivedCallback */ client.loop(); /* we measure temperature every 3 secs this code is to avoid blocking program and easy to read maintain when using FinitSate Machine */ switch(state){ case WAIT_FOR_TIMEOUT: long now = millis(); if (now - lastMsg > 3000) { state = START_MEASURE; } break; case START_MEASURE: temperature = dht.readTemperature(); if (!isnan(temperature)) { snprintf (msg, 20, "%lf", temperature); state = PUBLISH_MEASURE; } break; case PUBLISH_MEASURE: /* publish the message */ client.publish(TEMP_TOPIC, msg); lastMsg = now; state = WAIT_FOR_TIMEOUT; break; } } |
0 Comments