No desenvolvimento de sistemas embarcados com FreeRTOS, o uso de variáveis globais do tipo float
e double
exige atenção especial devido à natureza não atômica dessas operações e ao impacto do chaveamento de contexto. Operações matemáticas como divisão e multiplicação podem ser interrompidas, resultando em valores corrompidos.
Neste artigo, abordaremos:
- A questão da atomicidade em operações matemáticas.
- O impacto do chaveamento de contexto.
- O papel de variáveis
volatile
. - Uso do modificador
_Atomic
. - Proteções adequadas e boas práticas.
- Exemplos práticos em código.
Atomicidade e Operações Matemáticas
O que é Atomicidade?
Uma operação é atômica se for executada do início ao fim sem interrupções. Em sistemas multitarefa, operações não atômicas podem ser interrompidas por uma interrupção ou por outro contexto de tarefa, resultando em valores inconsistentes.
Por exemplo, ao realizar uma multiplicação em uma variável global:
global_var = global_var * factor;
Se essa operação for interrompida, a variável pode conter um valor intermediário inválido.
O Impacto do Chaveamento de Contexto
O escalonador do FreeRTOS pode trocar entre tarefas a qualquer momento, inclusive no meio de uma operação não atômica. Isso pode levar à corrupção de dados, especialmente ao lidar com variáveis globais compartilhadas entre tarefas e interrupções.
O Papel das Variáveis volatile
O que é volatile
?
A palavra-chave volatile
instrui o compilador a não otimizar o acesso a uma variável, garantindo que ela seja sempre lida/escrita diretamente na memória.
Limitações de volatile
Embora útil para variáveis acessadas por interrupções ou tarefas diferentes, volatile
não garante atomicidade. Ele não protege operações compostas, como global_var = global_var * factor;
, contra interrupções.
Uso do Modificador _Atomic
O que é _Atomic
?
O modificador _Atomic
é parte do padrão C11 e fornece uma maneira de declarar variáveis atômicas. Variáveis marcadas com _Atomic
garantem que as operações sobre elas sejam realizadas de maneira atômica, evitando interrupções no meio da operação.
Benefícios de _Atomic
- Elimina a necessidade de desabilitar interrupções para operações simples.
- Pode ser combinado com funções atômicas como
atomic_fetch_add
eatomic_store
para realizar operações seguras.
Exemplo de declaração:
#include <stdatomic.h>
_Atomic float global_var = 0.0f;
Operações Atômicas em Variáveis _Atomic
É possível realizar operações matemáticas seguras usando funções atômicas disponíveis no cabeçalho <stdatomic.h>
:
#include <stdatomic.h>
void TaskA(void *pvParameters) {
float factor = 2.0f;
float local_copy;
while (1) {
local_copy = atomic_load(&global_var); // Carregar o valor de forma atômica
local_copy = local_copy * factor; // Operação local
atomic_store(&global_var, local_copy); // Atualizar o valor de forma atômica
vTaskDelay(pdMS_TO_TICKS(100));
}
}
Compatibilidade
Embora _Atomic
seja padrão em C11, nem todos os compiladores para sistemas embarcados o suportam completamente. É importante verificar a documentação do compilador.
Boas Práticas e Soluções
1. Uso de Mutex
Um Mutex pode proteger operações mais complexas envolvendo variáveis globais.
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xMutex;
volatile float global_var;
void TaskA(void *pvParameters) {
float factor = 1.5f;
while (1) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
global_var = global_var * factor;
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
2. Desabilitar Interrupções Temporariamente
Para operações breves:
taskENTER_CRITICAL();
global_var = global_var / factor;
taskEXIT_CRITICAL();
3. Combinação com _Atomic
Ao usar _Atomic
, muitas vezes não é necessário desabilitar interrupções ou usar mutexes para operações simples.
Exemplo Completo com _Atomic
Abaixo, um exemplo que combina _Atomic
e FreeRTOS para garantir a manipulação segura de uma variável float
global:
#include <stdatomic.h>
#include "FreeRTOS.h"
#include "task.h"
_Atomic float global_var = 0.0f;
void TaskA(void *pvParameters) {
float factor = 2.5f;
float local_var;
while (1) {
local_var = atomic_load(&global_var); // Carregar valor
local_var = local_var * factor; // Operação local
atomic_store(&global_var, local_var); // Atualizar valor
vTaskDelay(pdMS_TO_TICKS(100)); // Aguardar 100ms
}
}
void TaskB(void *pvParameters) {
float divisor = 1.2f;
float local_var;
while (1) {
local_var = atomic_load(&global_var); // Carregar valor
local_var = local_var / divisor; // Operação local
atomic_store(&global_var, local_var); // Atualizar valor
vTaskDelay(pdMS_TO_TICKS(200)); // Aguardar 200ms
}
}
int main(void) {
xTaskCreate(TaskA, "TaskA", 1000, NULL, 1, NULL);
xTaskCreate(TaskB, "TaskB", 1000, NULL, 1, NULL);
vTaskStartScheduler();
for (;;);
return 0;
}
Conclusão
Usar _Atomic
é uma solução moderna e eficiente para manipular variáveis globais em sistemas multitarefa com FreeRTOS. Ele elimina a necessidade de técnicas manuais mais complexas para garantir atomicidade em operações simples. No entanto, para operações mais complexas, o uso de Mutex ou a combinação com desabilitação de interrupções ainda pode ser necessário.
Sempre verifique o suporte ao padrão C11 no compilador utilizado e teste rigorosamente para garantir a confiabilidade em sistemas críticos.