This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| en:iot-open:introductiontoembeddedprogramming2:cppfundamentals:programmingpatterns [2023/11/17 16:36] – pczekalski | en:iot-open:introductiontoembeddedprogramming2:cppfundamentals:programmingpatterns [2024/05/27 13:53] (current) – [Finite State Machine] ktokarz | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Programming patterns ====== | ||
| + | {{: | ||
| + | This chapter presents some programming templates and fragments of the code that are common in embedded systems. Some patterns, such as non-blocking algorithms, do not use '' | ||
| + | [[en: | ||
| + | |||
| + | ==== Tracing vs Debugging - Serial Ports ==== | ||
| + | Almost any MCU has a hardware debugging capability. This complex technique requires an external debugger using an interface such as JTAG. Setting up hardware and software for simple projects may not be worth a penny; thus, the most frequent case is tracing over debugging. Tracing uses a technique where the Developer explicitly sends some data to the external device (usually a terminal, over a serial port, and eventually a display) that visualises it. The Developer then knows the variables' | ||
| + | Note to use a '' | ||
| + | <code c> | ||
| + | void setup(){ | ||
| + | delay(100); | ||
| + | Serial.begin(115200); | ||
| + | Serial.println(); | ||
| + | ... | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | |||
| + | <note tip>A rule of thumb is that after programming and during a boot, every MCU drops some garbage to the serial buffer. That is visualised as several random characters in the terminal. To easily distinguish the tracing from the garbage, it is advised to put some '' | ||
| + | |||
| + | The '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ==== Interfacing with the Device - Serial Port ==== | ||
| + | The serial port and a class '' | ||
| + | <note important> | ||
| + | Data in the serial port are sent as bytes; thus, it is up to the developer to handle the correct data conversion. Reading a single byte of the data using '' | ||
| + | |||
| + | ==== Hardware buttons ==== | ||
| + | Hardware buttons tend to vibrate when switching. This physical effect causes bouncing of the state forth and back, generating, in fact, many pulses instead of a single edge during switching. Getting rid of this is called debouncing. In most cases, switches (buttons) short to 0 (GND) and use pull-up resistors, as in the figure {{ref> | ||
| + | <figure pullupsample> | ||
| + | {{ : | ||
| + | < | ||
| + | </ | ||
| + | The switch, when open, results in VCC through R1 driving the GPIO2 (referenced as HIGH), and when short, 0 is connected to it, so it becomes LOW: | ||
| + | * button short -> GPIO2=LOW, | ||
| + | * button released -> GPIO2=HIGH. | ||
| + | Some MCUs offer internal pull-ups and pull-downs, configurable from the software level. | ||
| + | The transition state between HIGH and LOW causes bouncing. | ||
| + | |||
| + | A dummy debouncing mechanism only checks periodically for a press/ | ||
| + | <code c> | ||
| + | #define BUTTON_GPIO 2 | ||
| + | |||
| + | bool bButtonPressed=false; | ||
| + | |||
| + | void setup() { | ||
| + | Serial.begin(9600); | ||
| + | pinMode(BUTTON_GPIO, | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | if (digitalRead(BUTTON_GPIO)==LOW && !bButtonPressed) | ||
| + | { | ||
| + | Serial.println(" | ||
| + | delay(200); | ||
| + | bButtonPressed=true; | ||
| + | } | ||
| + | if (bButtonPressed && digitalRead(BUTTON_GPIO)==HIGH) | ||
| + | { | ||
| + | Serial.println(" | ||
| + | bButtonPressed=false; | ||
| + | delay(200); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | A more advanced technique for complex handling of the buttons is presented below in the context of the State Machines. | ||
| + | |||
| + | ==== Finite State Machine ==== | ||
| + | A Finite State Machine (FSM) idea represents states and flow conditions between the states that reflect how the software is built for the selected system or its component. An example of button handling using the FSM is present here. The FSM reflects the physical state of the device, sensor or system on the software level, becoming a digital twin of a real device. | ||
| + | |||
| + | For the simple case (without detecting double-click or long press), 3 different button states can be distinguished: | ||
| + | <code c> | ||
| + | typedef enum { | ||
| + | RELEASED = 0, | ||
| + | DEBOUNCING, | ||
| + | PRESSED | ||
| + | } tButtonState; | ||
| + | </ | ||
| + | A flow between the states can be then described in the following diagram (figure {{ref> | ||
| + | <figure statemachine> | ||
| + | {{ : | ||
| + | < | ||
| + | </ | ||
| + | * In the '' | ||
| + | * In the '' | ||
| + | * In the '' | ||
| + | The state machine is implemented as a simple class and has 2 additional fields that store handlers for functions that are called when the state machine enters '' | ||
| + | <code c> | ||
| + | class PullUpButtonHandler{ | ||
| + | private: | ||
| + | tButtonState buttonState=RELEASED; | ||
| + | uint8_t ButtonPin; | ||
| + | unsigned long tDebounceTime; | ||
| + | unsigned long DTmr; | ||
| + | void(*ButtonPressed)(void); | ||
| + | void(*ButtonReleased)(void); | ||
| + | void btReleasedAction() { //Action to be done | ||
| + | // | ||
| + | if(digitalRead(ButtonPin)==LOW) { | ||
| + | buttonState = DEBOUNCING; | ||
| + | DTmr = millis(); | ||
| + | } | ||
| + | } | ||
| + | void btDebouncingAction() { //Action to be done | ||
| + | // | ||
| + | if(millis()-DTmr > tDebounceTime) | ||
| + | if(digitalRead(ButtonPin)==LOW) { | ||
| + | buttonState = PRESSED; | ||
| + | if(ButtonPressed!=NULL) ButtonPressed(); | ||
| + | } | ||
| + | else | ||
| + | buttonState=RELEASED; | ||
| + | } | ||
| + | void btPressedAction() { // | ||
| + | // | ||
| + | if(digitalRead(ButtonPin)==HIGH) { | ||
| + | buttonState=RELEASED; | ||
| + | if(ButtonReleased!=NULL) ButtonReleased(); | ||
| + | } | ||
| + | } | ||
| + | public: | ||
| + | PullUpButtonHandler(uint8_t pButtonPin, unsigned long pDebounceTime) { | ||
| + | // | ||
| + | ButtonPin = pButtonPin; | ||
| + | tDebounceTime = pDebounceTime; | ||
| + | } | ||
| + | void fRegisterBtPressCalback(void (*Callback)()) { | ||
| + | // | ||
| + | //a button PRESSED callback | ||
| + | ButtonPressed = Callback; | ||
| + | } | ||
| + | void fRegisterBtReleaseCalback(void (*Callback)()) { | ||
| + | // | ||
| + | //a button RELEASED callback | ||
| + | ButtonReleased = Callback; | ||
| + | } | ||
| + | void fButtonAction() | ||
| + | // | ||
| + | { //along with private functions above | ||
| + | switch(buttonState) { | ||
| + | case RELEASED: btReleasedAction(); | ||
| + | break; | ||
| + | case DEBOUNCING: btDebouncingAction(); | ||
| + | break; | ||
| + | case PRESSED: btPressedAction(); | ||
| + | break; | ||
| + | default: | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | }; | ||
| + | </ | ||
| + | Sample use looks as follows: | ||
| + | <code c> | ||
| + | #define BUTTON_GPIO 2 | ||
| + | |||
| + | PullUpButtonHandler bh = PullUpButtonHandler(BUTTON_GPIO, | ||
| + | void onButtonPressed() { | ||
| + | Serial.println(" | ||
| + | } | ||
| + | void onButtonReleased() { | ||
| + | Serial.println(" | ||
| + | } | ||
| + | void setup() { | ||
| + | Serial.begin(9600); | ||
| + | pinMode(BUTTON_GPIO, | ||
| + | bh.fRegisterBtPressCalback(onButtonPressed); | ||
| + | bh.fRegisterBtReleaseCalback(onButtonReleased); | ||
| + | } | ||
| + | |||
| + | void loop() { | ||
| + | bh.fButtonAction(); | ||
| + | } | ||
| + | </ | ||
| + | <note important> | ||
| + | The great feature of this FSM is that it can be easily extended with new functions, such as detecting the double click or long button press. | ||