---
# System prepended metadata

title: RTOS with STM32
tags: [MCU]

---

# RTOS with STM32

> Hands-On RTOS with Microcontrollers: Building real-time embedded systems using FreeRTOS, STM32 MCUs, and SEGGER debug tools
> https://www.amazon.com/Hands-RTOS-Microcontrollers-Building-real-time/dp/1838826734
>
> STM32 基礎入門教學
> https://ithelp.ithome.com.tw/users/20141525/ironman/4839
> 
> 嵌入式那些事
> https://www.zhihu.com/column/automationshen

*This note is base on the above book which is based on STM32 and I will include note taken when reviewing a bachelor degree class course slides*

## Types of RTOS

* **Hard Real Time OS** : The deadline of all the tasks should be met.
* **Firm Real Time OS** : Accept a few tasks to complete late.
* **Soft Real Time OS** : Offer only *best-effort*

## FreeRTOS

![](https://hackmd.io/_uploads/r1Pm6Xajn.png =600x)

:::info
:bulb: **CMSIS** 

**CMSIS** (Common Microcontroller Software Interface Standard) is provided by ARM and provide a universal interface for **M series and A series ARM CPU**. Additionally, **CMSIS provide a DSP and NN library** which can help building TinyML or write accelerated hardware and these libraries can use SIMD automatically provided the choosen hardware have SIMD support.
:::

## Super Loop v.s. RTOS

### Super Loop

![](https://hackmd.io/_uploads/ry8z0STs2.png)

:::success
:information_source: **Interrupts**

The time to finish an interrupt should be kept as **short** as possible since it will be served and possible causing longer function responds time.

**ISR(Interrupt Service Routine)** : A set of functions to handle an interrupt event. 

**NVIC (Nest Vector Interrupt Controller)** : A nest interrupt means that an interrupt can be interrupted by other one with higher priority.

**Interrupt Tail-Chaining** : If a processor detect that there are multiple interrupts waiting to be served, the processor will not restore its state fully back to before the interruption start. This helps further reduce latency.

**DMA (Direct Memory Access Controller)** : A processor *configure the DMA* for a peripheral for data transfer. *DMA controller handles all the data transfer* and *notify CPU when the transfer is complete*. Setting up DMA has a overhead thus, for small data transfer, simply use poll or interrupt might have better performance.

:::

### RTOS Tasks

* Each task will have its own stack which all the other task will not use. 
* A priority will be assigned to a task which help MCU make better decision of which task to run.

With the above two feature, we can implement tasks as if it's the only thing that run on the MCU. A preemptive scheduler will automatically migrate the CPU state to work on the most important task.

:::success
:bulb: **Super loop in RTOS tasks**

*In super loop, there is only one infinite loop* which iterate and perform functions as the code order. On the other hand, *a program use RTOS will have infinite loops in all the tasks.* Since the scheduler will run these tasks in round-robin faction or in preemptive faction, there should be no task that will run forever. (Watch dogs probably will get triggered)
:::

## Scheduling

### Round-robin Scheduling
![](https://hackmd.io/_uploads/rJasgQSnn.png)

Round-robin scheduling assign equal time slices to all the tasks. However, tasks can have different execution time for a single iteration and by assigning equal time slices to all the tasks, tasks will have finish different number of iterations.

> **Book P.43**
> *So, with this scheduling scheme, if a task has a shorter loop, it will execute more often than a task with a longer loop.*

The contain switch has overhead thus, round-robin scheduler will continue to spend CPU cycle doing contain switch.

### Preemptive-based Scheduling

By assign priorities to all the tasks, the scheduler will try to work on task that has the highest priority.

> **Book P.43**
> *Preemptive scheduling provides a mechanism for ensuring that the system is always performing its most important task.*

Preemptive scheduler will not be able to do much if all the tasks ara using 100% of the CPU time. However, if a task cannot get CPU time because there are task with higher, the task is consider ***starved***.

![](https://hackmd.io/_uploads/HkyUxwB22.png)

### Task Signaling and Communication Mechanisms

## RTOS queue

![](https://hackmd.io/_uploads/BkryGPBn2.png)

## RTOS semaphores

### Counting Semaphores

Setting a number according to the resource that the system have and counting the number of the taken resource to block and put tasks to wait.

## Binary Semaphores

Special case of counting semaphores which can only count to one.

## RTOS mutexs

Mutex stands for mutual exclusion which is very similar to binary semaphore.

:::warning
:warning: **Caveat**

***1. Priority Inversion***

![](https://hackmd.io/_uploads/S19YNDB3h.png)

In the above example, we can see that in step 3, task B preempts task C and work until finished and return back to task C. Therefore, task A has a implicit dependancy on task B which should have lower priority.

![](https://hackmd.io/_uploads/SkIKSwBnh.png)

By using the priority inheritance, task C can inherits task A's priority thus, task B will not be able to preempt task C.

:::

:::info
:information_source: **MCU Spec Considerations**

* **ROM \(Read-Only Memory\)** : This effects the code size.
* **RAM \(Random Access Memory\)** : Interpreted languages runs virtual machines will use up large amounts of RAM \(MicroPython\). Additionally, each task in FreeRTOS will have its own stack. An external RAM could be used but with higher cost, complicated design and possible EMI.
* **CPU Clock Rate** : 
> **Book P.72**
> 
> Another thing to be mindful of is the absolute maximum clock rate of the device versus the practical clock rate for an application. For example, some MCUs' maximum clock frequency is incompatible with generating an internal 48 MHz clock required for a USB peripheral, so it can't be used at maximum speed if the USB peripheral is also used.

* **Hardware Peripherals**
* **Connectivity**
* **Memory Protection Units**
* **FPU** : If DSP or Machine Learning is required, this can have great deal of performance differences.
* **DSP** : SIMD hardware
* **DMA**
* **Communication Interfaces**
* **External Memory Support**
* **Power Consumption**
* **Low-Power Modes**
* **Wake-Up Time**

:::success
:information_source: **雜訊特性(EMI和EMS)**
https://www.rohm.com.tw/electronics-basics/opamps/op_what13
:::

## ESP32 FreeRTOS

:::success
:information_source: **ESP32 with DHT22 Webpage Temperature and Humidity Report**

*Please refer to Home Server Setup Note*
:::

**Basic Structure of FreeRTOS in Arduino IDE**

* A Task should have a *void pointer*
* No return value
* Usually contains a infinite loop

```cpp
// ESP32 Dev Module Core ID
// Protocol CPU core : 0
// Application CPU core : 1

void taskA(void* pvParam)
{
  while(true) {
    // Task A work body
  }
}

void taskB(void* pvParam)
{
  while(true) {
    // Task B work body
  }
}

void setup() {
  xTaskCreate(taskA, "taskA"); // add task to scheduler
  xTaskCreate(taskB, "taskB");
}

void loop() {
  
}
```

:::info

:information_source: **FreeRTOS Arduino Syntax**

```cpp
xTaskCreate(
    taskA, // pointer to the function or the function name
    "task A about", // for debugging purposes
    1000, // memory Size for this task
    NULL, // parameter to the function
    1, // priority of the task (The higher the number the higher the priority)
    TaskHandle_t // task handler for control this task
)
```

For ESP32 the size of the memory is measured in **Byte**, usually it will be set to 1000. On other platform, its measure by **word size** which depand on the processors bit size.

```cpp
vTaskDelay() // portTICK_PERIOD_MS = 1 Arduino Platform is 1ms per tick.
```

:information_source: **FreeRTOS Data Types and Naming Convention Prefix**

* **BaseType_t** : Similar to int size depands on processor's bit size.
* **UBaseType_t** : Unsigned
* **TickType_t** : Store system tick time same size as BaseType_t
* **c** : char
* **s** : short
* **l** : long
* **u** : unsigned
* **p** : point
* **e** : enum
* **x** : others
:::

### Watchdog

Watchdog will envoke when tasks are starving or any possible hanging and reset the microcontroller. Watchdog based on a Timer counting.

* *yield()* or *taskYIELD()*
* *vTaskDelay()*
* *esp_task_wdt_reset()* : reset watchdog counter
* *disableCore0WDT()* or *disableCore1WDT()* : disable watchdog on specific core.

### Handle Tasks

With task handle, we can adjust priority, delete or suspend a task.

* *uxTaskPriorityGet()*:
* *vTaskPrioritySet()*:
* *vTaskDelete()*:
* *vTaskSuspend()*:
* *vTaskResume()*:

:::success
:bulb: **ESP32 Run Task on Specified Core**

By default, ESP32 `setup()` and `loop()` run on Core 1 and `xTaskCreate()` tasks run on Core 0. 

Set a task to run on specified core
`xTaskCreatePinnedToCore(task, "task about", 1000, NULL, 1, NULL, 1);`
:::

### Communication between tasks with Queue

Using global variable to communicate between tasks will require locking the data. On the other hand, queue can be used without explicitly handling the data coherence. 

* *QueueHandle_t queue = xQueueCreate(numberOfElement, elementSize)*
* *xQueueSend(queue, &Data, portMAX_DELAY)*
* *xQueueReceive(queue, &Data, portMAX_DELAY)*

### Synchronization

Tasks using same resource should take turns which require locking mechanism.

***Semaphore***

* *xSemaphoreCreateBinary()*
* *xSemaphoreTake()*
* *xSemaphoreGive*

***Mutex***

* *xSemaphoreCreateMutex()*

## RTOS-aware Debugging \(TODO : Prospone since other project running and this involve in updating firmware\)

> **Reference :** *Book Page 124*

* Knowing the current operational state of each task
* Knowing which task & function the **program counter \(PC\)** was
* Knowing which interrupts are active
* Looking at the value of key global variables
* Observing / unwinding the stack of each task

There are hardware-centric method and software-centric one. The book focusing on software-centric method. However, this method has some downside. This method relies on MCU to record the event which might not be correct when serving a interrupt is significantly delayed. Additionally, the availability of RAM and CPU cycles might not sufficient on heavily loaded systems.

***ST-Link***

ST-Link is located on the programming hardware ***\(a sub circuit\)***

![](https://hackmd.io/_uploads/r18v8PMQ6.jpg)

## CMSIS RTOS

As long as the RTOS support CMSIS RTOS, the interface will be the same cross all arm cortex and other processors. There are CMSIS RTOSv1 and v2. The v2 version will support not only Cortex M series but also ArmV8 A series.

### In STM32CubeIDE

![](https://hackmd.io/_uploads/HJBz-_m7p.png =x500)

In the FreeRTOS Configuration section, we can add new tasks and configure them.

![](https://hackmd.io/_uploads/BySMVdmmp.png =x400)

**MSP \(Main Stack Pointer\)**
There is a main stack in Cortex-M which is used by ISRs and the FreeRTOS kernel.
**PSP \(Process Stack Pointer\)**
User tasks that execute on the process stack

:::info
:bulb: **Memory Management Scheme**

![](https://hackmd.io/_uploads/HJt98_QQT.png =x400)

> The following article provide detailed explainatation of each schemes.
> https://zhuanlan.zhihu.com/p/115276865
:::

## FreeRTOS Queue Management

In FreeRTOS, we can use Queue for communication between tasks.

* **Passing by Value** : Usally is safer but require space and copy.
* **Passing by Reference** : If the passed pointer is point to a data that no longer exist, *\(destroyed after function finished\)* invalid access will invoke.

```cpp
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);

// uxQueueLength : The maximum number of elements the queue can hold.
// uxItemSize : The byte size of a element
// Return a handle of a queue or NULL if failed to create queue.

BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType xTicksToWait);

// xQueue : queue handle
// pvItemQueue : Copied element will be pushed in the queue
// xTicksToWait : block time (if the queue is full) pdMS_TO_TICKS() can be used.
// Return a pdPass or errQUEUE_FULL

BaseType_t xQueueReceive(QueueHandle_t xQueue, const void * pvBuffer, TickType_t xTicksToWait);

// xQueue : queue handle
// pvBuffer : Element will be copied to this buffer
// xTicksToWait : as xQueueSend
// Return a pdPass or errQUEUE_EMPTY
```


