Skip to content
SPONSORS
Picture of Corsi
Corsi
Picture of Arnaldo Alves Viana Junior
Arnaldo Alves Viana Junior

Métricas RTOS ​

Trabalhar com sistemas embarcados de tempo real envolve não apenas saber projetar o sistema, mas também avaliá-lo de forma quantitativa. Essa avaliação é importante para verificar se os requisitos temporais do projeto estão sendo respeitados. Somente assim podemos afirmar que o sistema realmente funciona dentro das restrições temporais especificadas.

Existem diversas métricas que podem ser utilizadas. Aqui, vamos detalhar apenas algumas das mais importantes para o nosso curso. Outras métricas e abordagens mais avançadas podem ser encontradas em literatura específica:

Worst Case Execution Time (WCET) ​

No caso de um sistema com RTOS, o WCET mede o pior tempo que uma tarefa (no nosso caso, uma task) leva para executar um ciclo completo da sua lógica. Esse valor pode ser obtido de duas formas:

  • Analítica: utilizando técnicas formais da teoria da computação e análise estática de código.
  • Empírica (exploratória): executando o firmware sob diferentes estímulos e medindo o pior tempo observado.

Embora a task seja configurada para executar com um determinado período (ex.: a cada 10 ms), o período real pode variar devido a:

  • interferência de outras tasks
  • tempo gasto em ISR
  • contenção de recursos
  • uso de recursos externos (LCD, printf)

Exemplo ​

Considere uma task configurada para executar a cada 10 ms.

ExecuçãoTempo medido da task
16.8 ms
27.1 ms
36.5 ms
47.9 ms
57.3 ms
67.6 ms

O maior valor observado foi:

WCET=7.9 ms

Como o período da task é 10 ms, ainda existe uma margem de: 10−7.9=2.1 ms. Isso indica que, nas condições testadas, a task consegue cumprir seu deadline dentro do escopo especificado. Caso o WCET observado fosse superior a 10 ms, o sistema apresentaria deadline misses.

Como medir? ​

A forma mais confiável de medir é instrumentalizar a task usando um GPIO:

  1. Configurar um pino do microcontrolador como saída.
  2. No início da task, colocar o pino em nível lógico 1.
  3. No final da task, colocar o pino em nível lógico 0.
  4. Conectar o pino a um osciloscópio.

A largura do pulso observado corresponde ao tempo de execução da task.

Medindo vários pulsos ao longo do tempo e sob diferentes estímulos, o maior valor observado corresponde ao WCET.

Exemplo:

c
void mpu6050_task(void *p) {
    gpio_init(TASK_PIN);
    gpio_set_dir(TASK_PIN, GPIO_OUT);
    mpu6050_init();

    while (1) {
        gpio_put(TASK_PIN, 1);
        int16_t acceleration[3], gyro[3], temp;

        mpu6050_read_raw(acceleration, gyro, &temp);
        printf("Acc. X = %d, Y = %d, Z = %d\n", acceleration[0], acceleration[1], acceleration[2]);
        printf("Gyro. X = %d, Y = %d, Z = %d\n", gyro[0], gyro[1], gyro[2]);
        printf("Temp. = %f\n", (temp / 340.0) + 36.53);

        gpio_put(TASK_PIN, 0);
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

TIP

Com essa instrumentalização, você será capaz de medir outras métricas também.

Deadline Miss Rate ​

O Deadline Miss Rate mede com que frequência uma tarefa não consegue terminar sua execução antes da próxima ativação. Em uma task periódica, existe um deadline implícito: ela deve concluir seu processamento dentro do próprio período. Quando isso não ocorre, dizemos que houve um deadline miss.

Suponha uma task configurada para rodar a cada 10 ms.

Se, em alguns momentos, ela leva mais de 10 ms para terminar, a próxima ativação ocorrerá enquanto a anterior ainda está executando. Isso caracteriza um deadline miss.

Se uma task executa N vezes e, em M dessas execuções, ela não termina a tempo, então:

Deadline Miss Rate=MN

Interpretação:

  • 0% → o sistema atende completamente aos requisitos temporais
  • > 0% → o sistema falha em cumprir seus deadlines e precisa ser reavaliado

Exemplo ​

Considere uma task configurada para executar a cada 10 ms.

ExecuçãoTempo que a task levouCumpriu o deadline?
18.5 ms✅ Sim
29.2 ms✅ Sim
310.8 ms❌ Não
411.3 ms❌ Não
59.7 ms✅ Sim

Nessa sequência:

  • Total de execuções: 5
  • Deadlines perdidos: 2

Deadline Miss Rate=25=40

Isso indica que, em 40% das vezes, a task não conseguiu terminar antes da próxima ativação, mostrando que o sistema não está conseguindo atender aos requisitos temporais.

Jitter ​

O Jitter é a variação temporal indesejada no instante em que uma tarefa periódica executa. Mesmo que uma task esteja configurada para rodar, por exemplo, a cada 10 ms, na prática ela não executa exatamente nesses instantes. Essa variação é o jitter.

Em sistemas de tempo real, não basta cumprir o período médio — é fundamental que o instante de execução seja previsível. O alto jitter em aplicações reais (áudio, controle, comunicação) pode causar:

  • distorção de sinal
  • instabilidade de controle
  • perda de amostras

Considere:

  • Ti = instante real da i-ésima execução da task
  • P = período configurado

O período real observado entre execuções é:

Pi=Ti−Ti−1

O jitter é definido como:

Jitter=Pmax−Pmin

Onde:

  • Pmax = maior período observado
  • Pmin = menor período observado

Exemplo ​

Se a task deveria executar a cada 10 ms e você mede:

ExecuçãoPeríodo medido
110.1 ms
29.8 ms
310.4 ms
49.7 ms

Então:

Jitter=10.4−9.7=0.7 ms

Isso significa que o sistema tem uma incerteza temporal de 700 µs a cada ativação.

Métricas de Stack no FreeRTOS ​

Em sistemas embarcados com RTOS, cada task possui sua própria stack. Essa stack é a região de memória utilizada durante a execução da task para:

  • variáveis locais
  • parâmetros de função
  • endereços de retorno de chamadas
  • contexto salvo pelo RTOS durante trocas de tarefa (context switch)

Diferente de programas simples (bare-metal), onde existe apenas uma stack global, no FreeRTOS cada task precisa ter sua stack dimensionada corretamente.

Não existe uma forma correta de definir o tamanho da stack. Normalmente começamos com um valor e vamos ajustando.

Regra de ouro

Quando eu trabalhava com desenvolvimento de firmwares para satélite, o requisito do projeto era que a task deveria ocupar no seu pior caso 80% da stack que foi alocada para ela. Vamos utilizar essa regra de ouro.

Sem essa medição, é comum ocorrer:

  • estouro de stack intermitente
  • comportamento imprevisível
  • travamentos difíceis de diagnosticar

Criação da task e o tamanho da stack ​

Ao criar uma task, o tamanho da stack é definido no momento da chamada da função:

c
xTaskCreate(
    vAudioTask,          // Função da task
    "AudioTask",         // Nome
    stack_size,          // Tamanho da stack (palavras (words))
    NULL,                // Parâmetros
    prioridade,          // Prioridade
    NULL                 // Handle
);

O terceiro parâmetro define quantas palavras(words) a stack terá.

Obs.: o valor é em palavras (words), não em bytes.

Escolher esse valor “no chute” é um erro comum e pode causar:

  • Stack overflow (valor pequeno demais)
  • Desperdício de memória (valor grande demais)

Como medir o uso real da stack ​

https://www.freertos.org/Documentation/02-Kernel/04-API-references/03-Task-utilities/04-uxTaskGetStackHighWaterMark

O FreeRTOS fornece a função uxTaskGetStackHighWaterMark() que retorna a menor quantidade de stack livre já observada desde que a task começou a rodar. Aí podemos chamar na task:

c
UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL);
printf("Stack livre minima: %u palavras\n", watermark);

Essa função retorna o menor espaço livre já observado na stack da task desde que ela começou a executar.

Com esse valor, é possível calcular o uso real:

Stack Usage=1−HighWaterMarkStackSize

Exemplo ​

Suponha que a task foi criada com:

  • stack_size = 1024 palavras

E a função retornou:

  • HighWaterMark = 300 palavras livres no pior caso depois de um bom tempo executando.

Então:

Stack Usage=1−3001024≈70% que é <80%

Isso indica um dimensionamento adequado (com base na regra dos 80%).

Task stack monitor ​

Para ajudar vocês eu criei uma task que faz o monitoramento do tamanho máximo da stack utilizada, vocês podem utilizar ela para realizarem as medições.

Código:

c
void stack_monitor_task(void* p) {
    static TaskStatus_t tasks[16];
    while (true) {
        vTaskDelay(pdMS_TO_TICKS(2000));
        UBaseType_t n = uxTaskGetSystemState(tasks, 16, NULL);
        printf("+------------------+-------+\n");
        printf("| %-16s | %5s |\n", "task", "free");
        printf("+------------------+-------+\n");
        for (UBaseType_t i = 0; i < n; i++) {
            printf("| %-16s | %5u |\n",
                   tasks[i].pcTaskName,
                   (unsigned)tasks[i].usStackHighWaterMark);
        }
        printf("+------------------+-------+\n");
        printf("| heap livre min   | %5u |\n",
               (unsigned)xPortGetMinimumEverFreeHeapSize());
        printf("+------------------+-------+\n\n");
    }
}

Exemplo de output:

txt
+------------------+-------+
| task             |  free |
+------------------+-------+
| monitor          |   394 |
| IDLE0            |   476 |
| IDLE1            |   484 |
| pwm              |   216 |
| fusion           |   876 |
| mpu              |  8140 |
| Tmr Svc          |   986 |
+------------------+-------+
| heap livre min   | 80888 |
+------------------+-------+