Funções de atraso — como busy_wait_ms(), HAL_Delay(), ets_delay_us() e outras — parecem, à primeira vista, tarefas simples: “esperar um determinado tempo”. Porém, dentro de um microcontrolador, essa ação envolve mecanismos profundamente diferentes, que variam conforme a arquitetura, o clock, o subsistema de temporização, a presença (ou não) de RTOS e até mesmo o estilo de compilação adotado pelo fabricante.
Para quem está começando, é comum imaginar que todas essas funções são equivalentes, bastando trocar o nome conforme a plataforma. Entretanto, na prática, cada família de microcontroladores implementa o conceito de delay com uma filosofia própria. Algumas usam busy-wait, queimando ciclos de CPU enquanto aguardam o tempo passar; outras dependem de timers como SysTick, mantendo interrupções ativas enquanto o core permanece ocupado; outras, ainda, delegam o atraso ao escalonador de um RTOS, permitindo que o sistema execute outras tasks durante o período de espera.
Essas diferenças afetam diretamente o desempenho, o consumo de energia, a responsividade e até mesmo a estabilidade do firmware. Em sistemas simples — como piscar um LED no AVR ou iniciar um periférico no RP2040 — um delay bloqueante pode ser suficiente. Já em aplicações complexas, como um ESP32 conectado via Wi-Fi ou um STM32 rodando múltiplas tasks com FreeRTOS, atrasos mal implementados podem gerar resets por watchdog, perda de pacotes, travamentos e comportamentos erráticos.
Por isso, compreender profundamente como cada família implementa suas funções de atraso é uma habilidade essencial para qualquer desenvolvedor de sistemas embarcados. Este artigo analisa, capítulo por capítulo, as abordagens adotadas pelos principais fabricantes — RP2040, ESP32, STM32, AVR, NXP e Renesas — explicando como suas funções de delay funcionam, quando usá-las e quais são as armadilhas mais comuns. Ao final, apresentamos um resumo comparativo que serve como guia rápido para projetos futuros.
RP2040: Entendendo busy_wait_ms() e sleep_ms()
O RP2040, utilizado no Raspberry Pi Pico, oferece duas categorias de funções de atraso: as que travam completamente a CPU (busy-wait) e as que permitem modos de espera eficiente, às vezes reduzindo consumo. A distinção é essencial para quem projeta firmware com temporizações precisas ou sistemas multitarefa simples.
A função mais conhecida é busy_wait_ms(), presente na Pico SDK. Ela implementa um mecanismo clássico de busy-wait, isto é, a CPU entra em um laço interno verificando continuamente o timer de hardware até que o período solicitado expire. Isso significa que, durante esse tempo, nenhuma outra instrução é executada, interrupções continuam funcionando, mas o núcleo não progride com mais nada. Esse comportamento é valioso em rotinas de inicialização, sequências de setup de sensores e em operações que exigem timing curto, como bit-banging.
O funcionamento pode ser compreendido com o pequeno exemplo abaixo:
#include "pico/stdlib.h"
int main() {
stdio_init_all();
while (true) {
gpio_put(25, 1); // LED ON
busy_wait_ms(250); // trava a CPU por 250 ms
gpio_put(25, 0); // LED OFF
busy_wait_ms(250);
}
}
Aqui, mesmo que existam interrupções habilitadas (por exemplo, UART RX ou SysTick), a aplicação não executa nenhuma lógica adicional entre os delays. Isso torna o comportamento extremamente previsível — essencial em timing de protocolos manuais — porém inviabiliza tarefas concorrentes.
Já a função sleep_ms() funciona de forma distinta. Ela pode colocar o núcleo em sleep via instrução WFI (Wait For Interrupt), permitindo que o consumo caia para níveis muito mais baixos. Durante esse intervalo, interrupções continuam despertando o core, mas a execução do firmware fica suspensa. Para quem projeta sistemas alimentados por bateria ou sensores remotos, essa diferença é crucial.
Exemplo comparativo:
sleep_ms(1000); // economiza energia, usa WFI quando possível
busy_wait_ms(1000); // consome CPU, não usa WFI
O desenvolvedor deve escolher com base na necessidade:
– precisão e bloqueio total → busy_wait_ms()
– eficiência energética e cooperação com interrupções → sleep_ms()
Nos casos em que o RP2040 opera com FreeRTOS, a recomendação muda: atrasos longos devem ser feitos via vTaskDelay(), enquanto busy_wait_ms() deve ser reservado a operações curtas e críticas. Essa abordagem evita que a task monopolize o processador e impede travamentos no escalonamento.
Capítulo 2 — ESP32: ets_delay_us() e vTaskDelay(): duas filosofias distintas
O ESP32 possui uma característica arquitetural que o diferencia das famílias tradicionais: ele é, por padrão, um sistema baseado em FreeRTOS, possuindo duas CPUs (no modelo clássico ESP32) e um conjunto de rotinas de ROM e SDK que se dividem entre atrasos busy-wait e atrasos cooperativos. Essa dualidade faz do ESP32 um excelente exemplo para entender quando se deve travar totalmente o core e quando se deve permitir que o sistema continue executando outras tarefas.
O método mais “baixo nível” disponível é ets_delay_us(), uma função residente na ROM. Ao contrário de busy_wait_ms() do RP2040, que depende da Pico SDK, ets_delay_us() existe mesmo antes de o FreeRTOS iniciar e funciona exclusivamente em modo de busy-wait. Isso significa que o core executa um laço apertado baseado em ciclos de CPU até completar a quantidade desejada de microssegundos. Como vantagem, ela é extremamente útil em inicializações, calibração de periféricos e trechos de código onde o timing é crítico.
Um exemplo típico de uso é em drivers de sensores que exigem pulsos finos:
#include "esp_rom_sys.h"
#include "driver/gpio.h"
void send_pulse() {
gpio_set_level(GPIO_NUM_2, 1);
ets_delay_us(10); // atraso preciso, busy-wait
gpio_set_level(GPIO_NUM_2, 0);
}
Aqui, o ESP32 fica totalmente dedicado à geração do pulso. Em drivers sensíveis, como controladores de LEDs one-wire, leituras de ultrassom ou protocolos temporizados manualmente, essa previsibilidade é essencial.
Porém, quando falamos em aplicações, especialmente as que usam Wi-Fi, BLE ou tarefas paralelas, travar o core é inaceitável. É nesse ponto que entra o mecanismo correto: vTaskDelay(), fornecido pelo FreeRTOS. Esse método faz o oposto de ets_delay_us(); em vez de bloquear o core, a task atual entra em estado de “dormência” cooperativa, permitindo que as demais continuem executando. Isso é fundamental para manter responsividade no sistema, garantir que a pilha TCP/IP rode adequadamente e que os drivers internos da Espressif mantenham o link com o roteador.
Um uso correto de vTaskDelay() é ilustrado abaixo:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void sensor_task(void *pvParameters) {
while (1) {
read_sensor();
vTaskDelay(pdMS_TO_TICKS(500)); // 500 ms sem bloquear o core
}
}
O comportamento aqui é completamente diferente do busy-wait: durante os 500 ms, o core executa outras tarefas — handling de Wi-Fi, Bluetooth, tarefas da aplicação, pilha TCP/IP e rotinas internas do sistema.
Como regra prática ao desenvolver no ESP32:
- Use
ets_delay_us()somente em:- drivers sensíveis a timing,
- inicializações onde o RTOS ainda não está ativo,
- protocolos finos (microsegundos),
- bit-banging de baixa camada.
- Use
vTaskDelay()para:- temporizações longas,
- lógica de aplicação,
- manter Wi-Fi e BLE funcionando estáveis,
- permitir multitarefa eficaz.
Essa separação é crítica para evitar travamentos como “WDT reset” (Watchdog Reset) característicos de código que monopoliza a CPU. Em ambientes baseados em Arduino-ESP32, o comportamento é semelhante: delay(ms) normalmente usa vTaskDelay, enquanto delayMicroseconds(us) mantém o busy-wait.
STM32: HAL_Delay(), LL_mDelay() e a dependência do SysTick
A família STM32 possui uma das maiores variações internas de timers e subsistemas de tempo entre todos os microcontroladores modernos. Embora a HAL (Hardware Abstraction Layer) ofereça funções de atraso prontas, como HAL_Delay() e LL_mDelay(), o funcionamento delas é muito diferente do que se vê no RP2040 ou no ESP32. Isso ocorre porque esses delays dependem explicitamente do SysTick, um temporizador gerado no núcleo ARM Cortex-M usado para medir o “tick” do sistema.
A função mais popular, HAL_Delay(), não é uma função de busy-wait puro. Ela faz um laço ocupado, mas sua contagem de tempo é baseada em uma variável global chamada uwTick, que é incrementada dentro da interrupção de SysTick. Logo, a precisão e o funcionamento correto de HAL_Delay() dependem de:
- SysTick estar corretamente configurado,
- interrupções globais estarem habilitadas,
SysTick_Handler()chamarHAL_IncTick().
Um exemplo simples ajuda a visualizar seu funcionamento:
#include "main.h"
int main(void) {
HAL_Init();
SystemClock_Config();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_5;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &gpio);
while (1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(500); // depende do SysTick; CPU ocupada, mas interrupções ativas
}
}
Apesar de o código parecer semelhante ao busy-wait do RP2040, a diferença crítica é que HAL_Delay só funciona porque o SysTick está gerando interrupções a cada 1 ms. Se o desenvolvedor desabilitar as interrupções globais para entrar em uma seção crítica, o SysTick não será executado e o programa pode ficar preso para sempre dentro de HAL_Delay().
Para quem deseja um comportamento mais próximo de um busy-wait puro, a biblioteca LL (“Low Layer”) oferece LL_mDelay(), mas ainda assim essa função utiliza o SysTick internamente — mantendo a mesma dependência estrutural.
Nos sistemas que utilizam FreeRTOS, a situação muda completamente. O SysTick passa a ser propriedade do RTOS, e atrasos baseados em HAL podem interferir no agendamento correto das tasks. Nesse contexto, a ST recomenda substituir chamadas a HAL_Delay() por osDelay(), função definida pelo CMSIS-RTOS e mapeada pelo FreeRTOS para vTaskDelay().
Um exemplo correto usando RTOS seria:
#include "cmsis_os.h"
void StartTask(void *argument) {
for(;;) {
process_data();
osDelay(1000); // libera a CPU, delay cooperativo
}
}
Diferentemente do caso anterior, esse delay não bloqueia a CPU. A task fica “adormecida” e o escalonador FreeRTOS pode executar outras tasks, drivers ou colocar o core em baixo consumo. Essa abordagem é essencial em sistemas com múltiplas responsabilidades, como controle de motores, protocolos de comunicação e operações de rede.
Como regra geral ao trabalhar com STM32:
- Use
HAL_Delay()apenas para:- inicialização,
- testes rápidos,
- ambientes sem RTOS,
- pequenos atrasos onde o SysTick está ativo e configurado.
- Use
osDelay()em:- aplicações com FreeRTOS,
- sistemas multitarefa,
- lógica de aplicação onde o consumo energético ou o tempo de resposta importa.
- Evite desabilitar interrupções longamente, porque isso afeta diretamente as funções de delay da HAL e LL.
Assim, no universo STM32, entender o SysTick é essencial para saber como suas funções de temporização se comportam — diferentemente de RP2040 e ESP32, aqui o delay depende de uma infraestrutura de tempo que está sempre ativa em background.
AVR: _delay_ms(), _delay_us() e o mecanismo de temporização baseado no compilador
Os microcontroladores AVR — como os clássicos ATmega328P, ATmega2560 e ATtiny — possuem uma abordagem completamente distinta para funções de atraso. Ao contrário das famílias anteriores, nas quais os delays são baseados em timers de hardware ou em um RTOS, os delays no AVR (via AVR-libc) são gerados pelo compilador, que transforma o valor do atraso em um bloco de instruções cuidadosamente ajustado para consumir exatamente o tempo requerido, considerando o clock do sistema.
As funções mais conhecidas são:
_delay_ms(double ms)_delay_us(double us)
Ambas são busy-wait, mas elas têm particularidades importantes:
- O atraso é calculado em tempo de compilação
O compilador expande_delay_ms(1)em um conjunto de instruções assembly que, somadas, produzem exatamente 1 ms de atraso — desde que o símboloF_CPUesteja configurado corretamente. - Os argumentos possuem limites
Como o compilador gera loops que consomem tempo proporcional ao atraso, valores grandes podem exceder o tamanho permitido do loop interno, e isso gera um erro. A solução é quebrar o atraso em pequenos fragmentos. - Se
F_CPUestiver errado, o tempo fica errado
Se o microcontrolador está realmente a 8 MHz, mas você compila comF_CPU=16MHz, o atraso será duas vezes maior do que deveria.
Isso torna os delays da AVR-libc extremamente precisos, mas sensíveis ao ambiente de compilação.
Um exemplo simples:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
int main(void) {
DDRB |= (1 << PB5);
while (1) {
PORTB ^= (1 << PB5);
_delay_ms(500); // precisão excelente, CPU 100% ocupada
}
}
Aqui, o LED pisca com precisão porque o compilador gera ciclos que equivalem a 500 ms. Em microcontroladores pequenos — ATtiny, por exemplo — essa abordagem evita o uso de timers quando não necessário.
Delays no ambiente Arduino (AVR)
No ecossistema Arduino, as funções de atraso são:
delay(ms)delayMicroseconds(us)
Mas elas não são equivalentes a _delay_ms().
delay(ms)depende da funçãomillis(), que por sua vez depende do Timer0 e sua interrupção.- Toda vez que o Timer0 interrompe (geralmente a cada 1 ms), uma variável global é incrementada.
delay(ms)simplesmente espera até que o valor da variável tenha avançado a quantidade necessária — o que significa que:- Ele não trava interrupções.
- Ele consome CPU rodando um laço, mas permite que outras interrupções ocorram.
- Se você desabilitar interrupções,
delay(ms)falha.
Já delayMicroseconds() é implementado como busy-wait baseado em instruções assembly específicas, parecido com _delay_us(), porém menos preciso para valores grandes.
Quando usar cada mecanismo?
- Use
_delay_us/_delay_ms(AVR-libc) quando:- você precisa de extrema precisão,
- o valor do clock é conhecido e fixo,
- está desenvolvendo código bare-metal.
- Use
delay()(Arduino) quando:- trabalha no ambiente Arduino,
- não deseja configurar timers,
- precisa de atrasos maiores que 1 ms,
- não está preocupado com precisão absoluta.
- Evite
_delay_ms()para valores grandes (ex.: > 50 ms), pois o código gerado pode crescer demais. Nesse caso, faça:
for (int i = 0; i < 50; i++)
_delay_ms(10);
NXP (Kinetis, LPC, i.MX RT): SDK_DelayAtLeastUs() e a filosofia do MCUXpresso
Os microcontroladores da família NXP — abarcando as séries Kinetis, LPC e i.MX RT — utilizam o SDK MCUXpresso, que apresenta uma estratégia de temporização mais explícita e direta que as famílias anteriores. A função principal, presente em praticamente todos os exemplos e drivers, é:
SDK_DelayAtLeastUs(uint32_t delay, uint32_t coreClockHz)
Essa função implementa um busy-wait cujo parâmetro de base é o clock do núcleo (CPU). Não há dependência de SysTick, de interrupções ou de RTOS — tudo é calculado a partir da frequência real de operação. Isso torna o mecanismo simples, previsível e altamente portátil dentro da própria família NXP, mas exige que o desenvolvedor passe o valor correto de coreClockHz.
Um exemplo de uso típico:
#include "fsl_common.h"
#include "fsl_gpio.h"
int main(void) {
BOARD_InitBootPins();
BOARD_BootClockRUN(); // configura o clock do MCU
GPIO_PinWrite(GPIO1, 5, 1);
SDK_DelayAtLeastUs(100, CLOCK_GetFreq(kCLOCK_CpuClk)); // atraso preciso
GPIO_PinWrite(GPIO1, 5, 0);
while(1);
}
Aqui, CLOCK_GetFreq(kCLOCK_CpuClk) retorna a frequência real do núcleo, garantindo que o atraso seja proporcional ao clock ativo — mesmo que o sistema tenha passado por clock scaling, PLL dynamic switching ou modos de baixo consumo.
A vantagem desse modelo é a previsibilidade: não importa se o SysTick está habilitado ou não, nem se interrupções estão desabilitadas. A função sempre executa um laço de instruções calibrado pelo clock fornecido. Por outro lado, isso significa que:
- atrasos longos consomem CPU,
- a precisão depende diretamente do clock informado,
- não há mecanismo automático de suspensão do core (sleep).
Por isso, em aplicações com FreeRTOS sobre NXP, a função correta é outra: utilizar vTaskDelay() ou vTaskDelayUntil(), que permitem operação cooperativa.
Um exemplo típico num sistema multitarefa:
void task_led(void *arg) {
while (1) {
toggle_led();
vTaskDelay(pdMS_TO_TICKS(200)); // ideal para sistemas com RTOS
}
}
Essa abordagem evita monopolizar o core e permite que drivers como USB, Ethernet e Wi-Fi (em placas i.MX RT) continuem funcionando adequadamente.
Delays baseados em SysTick nos exemplos da NXP
Além do SDK_DelayAtLeastUs(), vários exemplos didáticos da NXP utilizam funções como:
SysTick_DelayTicks()DELAY_Us(),DELAY_Ms()(dependendo do pacote)
Essas funções configuram o SysTick para uma frequência específica e mantêm contadores incrementados por interrupção — semelhante ao HAL_Delay() do STM32, mas sem a abstração HAL. A diferença é que elas costumam ser menos robustas e menos padronizadas que o delay cooperativo de RTOS ou o busy-wait explícito da SDK.
Quando usar cada método em projetos NXP
- Use
SDK_DDelayAtLeastUsquando:- desenvolvendo bare-metal,
- a precisão em microssegundos é fundamental,
- a CPU não deve depender de SysTick,
- está inicializando periféricos.
- Use
vTaskDelay()quando:- seu firmware possui FreeRTOS,
- é necessário liberar a CPU,
- a aplicação utiliza comunicação complexa (USB/Ethernet),
- você precisa de atrasos acima de alguns milissegundos.
O ponto central é: NXP separa muito bem os mundos bare-metal e RTOS. A escolha da função afeta diretamente o comportamento geral, especialmente em sistemas com alta carga de periféricos.
Renesas (RA, RX, Synergy): R_BSP_SoftwareDelay() e a convivência entre Busy-Wait e RTOS
A família Renesas — especialmente as linhas RA, RX e a antiga plataforma Synergy — fornece um ecossistema robusto baseado em duas camadas fundamentais: o BSP (Board Support Package) e os frameworks de aplicação. Nesses produtos, os atrasos podem ser implementados tanto via busy-wait puro, usando funções do BSP, quanto via mecanismos cooperativos de RTOS, como FreeRTOS ou ThreadX (embarcado nos kits Synergy e em várias placas RA).
A função mais comum no BSP das famílias RA e RX é:
R_BSP_SoftwareDelay(uint32_t delay, bsp_delay_units_t units)
Essa função opera em modo busy-wait, semelhante ao _delay_ms() do AVR-libc, porém bem mais flexível porque suporta múltiplas unidades de medida (BSP_DELAY_UNITS_MICROSECONDS, MILLISECONDS, NANOSECONDS, dependendo da família). Ela faz cálculos em função da frequência de clock configurada no BSP, garantindo que o atraso seja proporcional ao clock real do sistema.
Exemplo básico:
#include "bsp_api.h"
void blink_led(void) {
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_05, BSP_IO_LEVEL_HIGH);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
R_IOPORT_PinWrite(&g_ioport_ctrl, BSP_IO_PORT_01_PIN_05, BSP_IO_LEVEL_LOW);
R_BSP_SoftwareDelay(100, BSP_DELAY_UNITS_MILLISECONDS);
}
Aqui, o código faz um laço ocupado: a CPU permanece executando instruções até o tempo expirar. A precisão é boa, porém, como em todas as funções busy-wait, o core fica completamente dedicado ao atraso, o que não é ideal em aplicações com múltiplas responsabilidades.
Quando entra o RTOS: ThreadX ou FreeRTOS
Os kits Synergy e novos MCUs RA frequentemente utilizam ThreadX, enquanto alguns projetos RA e RX adotam FreeRTOS. Em ambos os casos, o uso de software delay deve ser evitado na lógica de aplicação, pois:
- Ele bloqueia 100% da CPU,
- impede escalonamento,
- causa lentidão em pilhas de comunicação,
- e pode gerar timing fault em sistemas críticos.
O atraso correto nos ambientes Renesas com RTOS é:
tx_thread_sleep(ticks)(ThreadX)vTaskDelay(milliseconds)(FreeRTOS)
Por exemplo, usando ThreadX:
void tx_task_led(ULONG initial_input) {
while (1) {
toggle_led();
tx_thread_sleep(50); // delay cooperativo, permite outras threads
}
}
Ou usando FreeRTOS nos RA mais recentes:
void led_task(void *pvParams) {
while (1) {
toggle_led();
vTaskDelay(pdMS_TO_TICKS(200)); // recomendação ideal
}
}
Esses mecanismos deixam o core livre para executar outras threads, manter stacks de rede, atualizar timers de segurança e executar lógica paralela sem travamentos.
Por que Renesas separa tão bem os dois mundos?
- Os MCUs desta família frequentemente combinam:
- controle em tempo real,
- pilhas de segurança,
- conectividade avançada (Ethernet/USB),
- e sistemas multitarefa via ThreadX.
- Em tais cenários, atrasos busy-wait de longo período podem:
- gerar inconsistências de temporização,
- derrubar a pilha de comunicação USB/Ethernet,
- causar watchdog reset,
- prejudicar a responsividade geral.
Por isso, a documentação Renesas é clara: busy-wait apenas para inicialização e delays extremamente curtos; RTOS para todo o resto.
Síntese da família Renesas
- Bare-metal →
R_BSP_SoftwareDelay() - ThreadX →
tx_thread_sleep() - FreeRTOS →
vTaskDelay() - Atrasos longos → sempre RTOS
- Atrasos curtos temporizados por clock → busy-wait
Capítulo Final — Comparativo Geral das Funções de Delay nas Principais Famílias de Microcontroladores
Após analisar RP2040, ESP32, STM32, AVR, NXP e Renesas, fica claro que as funções de atraso — apesar de todas terem o mesmo objetivo: esperar — são profundamente diferentes em filosofia, implementação e efeitos colaterais. Um engenheiro embarcado precisa compreender essas nuances para evitar travamentos, consumo excessivo de energia, perda de pacotes e problemas de temporização.
A seguir, apresento um comparativo consolidado e didático.
1. Busy-Wait Puro (Spin Delay)
(Trava completamente o core)
Famílias que usam busy-wait de forma explícita
- RP2040 →
busy_wait_ms(),busy_wait_us() - ESP32 →
ets_delay_us() - AVR (AVR-libc) →
_delay_ms(),_delay_us() - NXP MCUXpresso →
SDK_DelayAtLeastUs() - Renesas →
R_BSP_SoftwareDelay()
Características
- CPU não executa mais nada.
- Excelente para timing de precisão, como bit-banging e protocolos sensíveis.
- Não depende de interrupções.
- Ruim para:
- sistemas multitarefa,
- Wi-Fi/BLE (ESP32),
- aplicações com USB/Ethernet,
- economia de energia,
- processamento paralelo.
Resumo
Úteis e precisas, mas perigosas em sistemas complexos.
Devem ser usadas curtas e conscientemente.
2. Delay Baseado em Timer + Interrupção (SysTick/Timer0)
(CPU fica presa no laço, mas interrupções continuam)
Famílias que dependem fortemente de temporizador
- STM32 HAL/LL →
HAL_Delay(),LL_mDelay() - Arduino AVR →
delay() - Exemplos NXP antigos →
SysTick_DelayTicks() - Exemplos diversos Renesas → funções auxiliares baseadas em interrupção
Características
- Não é multitarefa: CPU presa no laço, mas interrupções continuam funcionando.
- Útil para:
- garantir timing razoável sem precisar de valores exatos,
- inicialização de periféricos,
- piscar LEDs, testes simples.
- Problemas típicos:
- desabilitar interrupções trava o delay,
- reduz responsividade,
- inadequado para aplicações de rede, USB e RTOS.
Resumo
Ocupam a CPU, mas permitem infraestrutura mínima rodando em background.
3. Delay Cooperativo (RTOS)
(A task dorme, o core continua trabalhando)
Famílias que adotam RTOS amplamente
- ESP32 (FreeRTOS) →
vTaskDelay(),vTaskDelayUntil() - STM32 com CMSIS-RTOS →
osDelay() - NXP com FreeRTOS →
vTaskDelay() - Renesas Synergy (ThreadX) →
tx_thread_sleep() - Renesas RA/RX com FreeRTOS →
vTaskDelay()
Características
- Não trava a CPU: o escalonador executa outras tasks.
- Preciso o suficiente para aplicações de alto nível.
- Essencial para:
- Wi-Fi, BLE, USB, TCP/IP,
- controle de motores com múltiplas tasks,
- sistemas IoT,
- aplicações que exigem baixo consumo.
- Granularidade definida pelo tick do RTOS (1 ms típico).
Resumo
É a forma “correta” em qualquer firmware multitarefa.
Evita resets por watchdog, travamentos e perda de pacotes.
4. Onde Cada Família se Destaca
RP2040
- excelente precisão em delays curtos,
- utiliza WFI em
sleep_ms()(ótimo para low-power), - ideal para bit-banging e temporizações finas,
- não possui RTOS por padrão — busy-wait é comum.
ESP32
- sistema naturalmente RTOS,
- busy-wait só para microsegundos críticos,
vTaskDelayé obrigatório em aplicações Wi-Fi/BLE.
STM32
HAL_Delay()depende do SysTick,- boa precisão ms, mas bloqueia CPU,
- compatível com RTOS via
osDelay().
AVR
- precisão absoluta (compilador gera loops),
- ótimo para sistemas muito simples,
- não indicado para tempos longos (gera código enorme).
NXP
- busy-wait configurado pelo clock (muito robusto),
- RTOS amplamente usado em i.MX RT,
SDK_DelayAtLeastUs()ideal para drivers.
Renesas
- abordagem híbrida clara,
- ThreadX e FreeRTOS dominam aplicações reais,
- delays busy-wait só na inicialização ou para tempos curtíssimos.
5. Tabela Comparativa Final
| Família/SDK | Busy-Wait | Delay Baseado em Timer | Delay Cooperativo | Observações |
|---|---|---|---|---|
| RP2040 | busy_wait_ms/us | sleep_ms (pode usar WFI) | via RTOS (não nativo) | excelente para bit-banging |
| ESP32 | ets_delay_us | raramente usado | vTaskDelay obrigatório | evita WDT resets |
| STM32 | — | HAL_Delay, LL_mDelay | osDelay | SysTick é crítico |
| AVR | _delay_ms/us | delay() via Timer0 | não nativo | muito preciso sem RTOS |
| NXP | SDK_DelayAtLeastUs | variantes SysTick | vTaskDelay | ideal para drivers |
| Renesas | R_BSP_SoftwareDelay | alguns timers auxiliares | tx_thread_sleep / vTaskDelay | RTOS muito presente |
6. Conclusão Geral
As funções de atraso não são intercambiáveis, e o uso correto depende do contexto:
- Precisa de precisão curta? → busy-wait
- Tem RTOS? → delays cooperativos
- Sistema está inicializando? → uso limitado de busy-wait
- Precisa economizar energia? → delays com WFI ou RTOS
- Periféricos de alta demanda (Wi-Fi/USB/Ethernet)? → nunca usar busy-wait prolongado
Saber escolher entre cada categoria diferencia um firmware amador de um firmware profissional e escalável. Em todas as famílias estudadas, a regra central permanece:
busy-wait é para microsegundos; RTOS é para milissegundos.