O que é RTOS?
RTOS é a sigla para “Sistema Operacional de Tempo Real” (Real Time Operating System). Um sistema operacional é um programa que gerencia as funções básicas de um computador e oferece serviços para outros programas que rodam nele. Em um sistema operacional, vários programas são executados ao mesmo tempo. Cada núcleo do processador executa uma única tarefa (ou thread) que é escolhida pelo planejador. O planejador decide qual programa será executado em cada momento, alternando rapidamente entre eles para dar a ilusão de que todos estão rodando simultaneamente. O RTOS é um tipo especial de sistema operacional projetado para gerenciar o tempo de resposta de tarefas em ambientes onde a precisão e a previsibilidade são cruciais, priorizando, assim, a execução de tarefas dentro de prazos definidos. Isso é importante em sistemas embarcados, como em dispositivos médicos, automóveis e equipamentos industriais, onde atrasos ou variações no tempo de resposta podem resultar em falhas críticas ou perigosas.
O que é Azure RTOS?
O Azure RTOS é uma classe de RTOS, sendo um pacote de middleware de nível profissional com um conjunto de componentes desenvolvidos pela Microsoft para aplicar em sistemas embarcados e dispositivos de IoT (Internet das Coisas). A Azure RTOS está integrada no ecossistema STM32Cube e pode ser facilmente implementada no firmware das placas da STM.
Os componentes principais do Azure RTOS são:
- ThreadX: núcleo principal do Azure RTOS.
- FileX: sistema de arquivos de alto desempenho que oferece suporte para várias estruturas de diretório e tipos de arquivo.
- GUIX: biblioteca de interface gráfica de usuário (GUI) que facilita a criação de interfaces gráficas sofisticadas para dispositivos embarcados.
- NetX Duo: protocolos de rede que fornecem suporte para conectividade de rede, incluindo TCP/IP, adequadas para dispositivos conectados à Internet.
- USBX: conjunto de componentes que oferece suporte para USB host, dispositivo e On-The-Go (OTG), permitindo a comunicação com periféricos USB.
Descrição Projeto
Neste artigo, vamos aprender a trabalhar com o ThreadX configurando o Azure RTOS usando a ferramenta CubeMX. Nosso objetivo é controlar o LED conectado ao pino PB6 ao pressionar o BOTÃO conectado ao pino PA8 e exibir o status do LED via UART. Para isso, criaremos duas tarefas e utilizaremos uma fila para a comunicação entre essas tarefas. Não será necessário nenhum hardware adicional além da Franzininho C0.
Configurando CubeMX
- Abra STM32Cube , crie um novo projeto e selecione o microcontrolador de destino “STM32C011F6P6”.
- Entre na página de configurações de relógio e ajuste HCLK para 48 MHz.
- Volte para a página de configuração dos pinos. Em “System Core” > “SYS”, habilite os pinos PA9 e PA10. Em seguida, selecione “TIM16” como a base de tempo (timebase source). Ao usar o Azure RTOS, a fonte de base de tempo não pode ser o clock interno do microcontrolador. Deve-se usar um temporizador (timer) específico para garantir precisão e estabilidade nas operações de tempo real.
- Depois, vamos configurar nossa UART. Selecione o pino PA10 como “USART1_RX” e PA9 como “USART1_TX”. Em “Connectivity” selecione a opção “USART1” e em Mode escolha o “Asynchronous”. As demais configurações deixe padrão.
- Em seguida, habilite o ThreadX do Azure RTOS. Para fazer isso, vá para “Middleware and Software,” procure por “THREADX,” e habilite a opção “Core.”
- Na sequência, configuraremos as entradas e saídas. Para configurar a entrada, selecione o pino “PA8”, defina seu modo como “Input Mode” e ative o resistor de Pull-up. Depois, selecione o pino “PB6”, defina seu modo como “Output Mode” e renomeie-o para “LED”.
- Por fim, gere o código em “Project” > “Generate Code”.
Implementação código
Ao gerar o código, serão incluídos dois arquivos específicos para trabalhar com o Azure RTOS.

Primeiro, acesse o arquivo main.c, e inclua MX_ThreadX_Init() na função principal int main:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
MX_ThreadX_Init();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
Depois, acesse o arquivo app_threadx.c e realize as seguintes modificações:
- Inclua stdio.h e main.h
#include <stdio.h>
#include "main.h"- Crie as definições a seguir:
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define BTN_STATUS_STACK_SIZE 512
#define LED_STATUS_STACK_SIZE 512
#define QUEUE_STACK_SIZE 16
/* USER CODE END PD */
- Crie as seguintes variáveis e protótipos das funções (tarefas):
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
extern UART_HandleTypeDef huart1;
uint8_t btn_status_stack[BTN_STATUS_STACK_SIZE];
uint8_t led_status_stack[LED_STATUS_STACK_SIZE];
uint8_t queue_stack[QUEUE_STACK_SIZE];
TX_THREAD btn_status_ptr;
TX_THREAD led_status_ptr;
TX_QUEUE queue_ptr;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
VOID btn_status(ULONG initial_input);
VOID led_status(ULONG initial_input);
/* USER CODE END PFP */
- Procure por App_ThreadX_Init e inclua a criação das tarefas e fila.
UINT App_ThreadX_Init(VOID *memory_ptr)
{
UINT ret = TX_SUCCESS;
/* USER CODE BEGIN App_ThreadX_MEM_POOL */
/* USER CODE END App_ThreadX_MEM_POOL */
/* USER CODE BEGIN App_ThreadX_Init */
tx_queue_create(&queue_ptr, "message_btn_status", TX_1_ULONG ,queue_stack,QUEUE_STACK_SIZE);
tx_thread_create(&btn_status_ptr,"btn_status",btn_status,0,btn_status_stack, BTN_STATUS_STACK_SIZE,15,15,1,TX_AUTO_START);
tx_thread_create(&led_status_ptr,"led_status",led_status,0,led_status_stack, LED_STATUS_STACK_SIZE,15,15,1,TX_AUTO_START);
/* USER CODE END App_ThreadX_Init */
return ret;
}
A função tx_thread_create cria e inicia uma nova thread (tarefa). Seus parâmetros são:
- &btn_status_ptr: Ponteiro para a estrutura da thread.
- “btn_status”: Nome da thread (tarefa).
- btn_status: Ponteiro para a função da thread.
- 0: Valor passado como argumento para a função da thread (não utilizado nesta implementação).
- btn_status_stack: Ponteiro para o stack da thread.
- BTN_STATUS_STACK_SIZE: Tamanho do stack da thread.
- 15: Prioridade da thread (quanto menor o valor, maior a prioridade).
- 15: Sub-prioridade da thread (em sistemas com preempção de múltiplos níveis, geralmente igual à prioridade principal).
- 1: Time-slice para a thread (quantidade de ticks que a thread pode rodar antes de outra thread com a mesma prioridade ser executada).
- TX_AUTO_START: Opção de auto-start para a thread (a thread começa a rodar automaticamente após ser criada).
A função tx_queue_create cria a fila que será usada para passar mensagens entre as tarefas btn_status e led_status. A criação da fila possui os seguintes parâmetros:
- queue_ptr: Ponteiro para a fila.
- “message_btn_status”: Nome da fila.
- TX_1_ULONG: Tipo de mensagem (um ULONG).
- queue_stack: Array utilizado como armazenamento da fila.
- QUEUE_STACK_SIZE: Tamanho do array queue_stack.
- Crie a função da thread btn_status (ULONG initial_input). Esta tarefa lê o estado de um botão conectado ao pino GPIOA, GPIO_PIN_8 e envia uma mensagem para a fila queue_ptr sempre que o estado do botão muda.
VOID btn_status (ULONG initial_input) {
ULONG message_pin_status = DISABLE;
ULONG message_pin_status_new = DISABLE;
while(1) {
tx_thread_sleep(10);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8) == GPIO_PIN_RESET) {
message_pin_status_new = ENABLE;
} else {
message_pin_status_new = DISABLE;
}
if (message_pin_status != message_pin_status_new) {
message_pin_status = message_pin_status_new;
tx_queue_send(&queue_ptr, &message_pin_status, TX_NO_WAIT);
}
}
}
- Crie a função da thread led_status(ULONG initial_input). Esta tarefa recebe mensagens da fila queue_ptr e aciona um LED conectado ao LED_GPIO_Port e LED_Pin com base nas mensagens recebidas. Também transmite mensagens pela UART para indicar o estado do LED.
VOID led_status(ULONG initial_input) {
ULONG message_status;
uint8_t msg_led_on[] = "The LED is ON\r\n";
uint8_t msg_led_off[] = "The LED is OFF\r\n";
while(1) {
tx_queue_receive(&queue_ptr, &message_status, TX_WAIT_FOREVER);
if(message_status == ENABLE) {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
HAL_UART_Transmit(&huart1, msg_led_on, sizeof(msg_led_on) - 1, 1000);
} else {
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
HAL_UART_Transmit(&huart1, msg_led_off, sizeof(msg_led_off) - 1, 1000);
}
}
}
Gravação
Ao finalizar o código, partiremos para gravação. Nessa etapa você pode utilizar o ST-Link seguindo as conexões da imagem abaixo e clicando em “run” no STM32CubeIde.
Ou você pode optar por utilizar um cabo usb e gravar conforme explicado no seguinte tutorial: gravar-franzininho-c0-via-stm32cubeprogrammer
Funcionamento

Conclusão
Neste artigo, demonstramos como configurar e utilizar o Azure RTOS para controlar um LED com um botão utilizando a placa Franzininho C0 com a ferramenta STM32CubeMX. Através da integração do ThreadX, parte do Azure RTOS, configuramos duas tarefas principais para monitorar o estado de um botão e controlar um LED com base nas leituras do botão.





