MCU.TEC Algoritimos,C,STM32 Como Redirecionar o printf para a UART no STM32F411: Um Guia Completo para Debug Serial

Como Redirecionar o printf para a UART no STM32F411: Um Guia Completo para Debug Serial


Por que redirecionar o printf para a UART?

Durante o desenvolvimento de firmware para STM32, uma das formas mais simples e eficazes de entender o comportamento do código é imprimir mensagens de log: valores de variáveis, estados de máquina de estados, mensagens de erro, etc. Em ambientes embarcados, não temos um “console” como no PC, então precisamos escolher um canal físico para transportar essas mensagens. A forma mais comum, simples e robusta é usar uma UART (porta serial) ligada ao computador do desenvolvedor.

Na placa de prototipação Nucleo STM32F411 (por exemplo, NUCLEO-F411RE), o próprio ST-LINK já oferece uma porta serial virtual (Virtual COM Port – VCP). Quando você conecta a placa via USB ao PC, além da interface de debug SWD, é criada uma porta serial (como COMx no Windows ou /dev/ttyACMx no Linux). Essa porta está conectada internamente a um dos periféricos UART do microcontrolador (tipicamente USART2, pinos PA2/PA3), permitindo que tudo o que for enviado por essa UART apareça em um terminal serial no computador.

O artigo da ST sobre redirecionar o printf para uma UART segue justamente essa ideia: em vez de mandar o printf para o “stdout” abstrato, fazemos com que a função de baixo nível que o printf usa internamente envie cada caractere pela UART, por meio da função HAL_UART_Transmit(). Em muitos exemplos oficiais da ST, isso é feito definindo um macro do tipo:

#ifdef __GNUC__
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

e depois implementando essa função para transmitir o caractere pela UART configurada. (Gist)

No contexto da Nucleo STM32F411, isso significa que, depois de configurarmos corretamente a UART no STM32CubeIDE (por exemplo, USART2 a 115200 bps, 8N1), qualquer chamada a printf("Hello, world!\r\n"); no código C passará a sair pela porta USB da placa, aparecendo no seu terminal serial. Isso elimina a necessidade de ficar alternando LEDs, pausando com breakpoints, ou usando ferramentas mais complexas de trace apenas para ver valores simples.

Neste tutorial, vamos seguir uma abordagem estruturada, inspirada no procedimento descrito pela ST e em exemplos de comunidade:

  1. Entender a UART e o VCP da Nucleo STM32F411 – quais pinos estão envolvidos, como a UART é ligada ao ST-LINK e à porta USB no PC.
  2. Criar e configurar o projeto no STM32CubeIDE – habilitando a UART correta e os clocks necessários.
  3. Implementar o redirecionamento do printf – adicionando o macro PUTCHAR_PROTOTYPE e a função que chama HAL_UART_Transmit().
  4. Ajustar o buffer e o comportamento do printf – explicando questões como setvbuf(), uso de \n/\r e possíveis travamentos.
  5. Testar com um terminal serial no PC – configurando o terminal, verificando a comunicação e mostrando um exemplo de sessão de debug.
  6. Boas práticas e limitações – impacto em tempo de execução, alternativas (ITM/SWO, semihosting, logs condicionais, etc.).

Ao longo das próximas seções, vamos sempre assumir como placa-alvo a Nucleo STM32F411 e o ambiente de desenvolvimento STM32CubeIDE, usando a HAL da ST. A ideia é que, ao final do tutorial, você tenha um “esqueleto” de projeto no qual basta chamar printf() em qualquer parte do código para enviar mensagens de debug pela UART/USB para o seu PC.


2. Entendendo a UART e a Virtual COM Port (VCP) na Nucleo STM32F411

A plataforma Nucleo STM32F411 possui um recurso extremamente útil para debug: o ST-LINK/V2-1, que integra no mesmo cabo USB tanto a interface de programação/debug quanto uma porta serial virtual que aparece no computador como um dispositivo COM (Windows) ou /dev/ttyACMx (Linux). Essa porta é automaticamente conectada, via jumpers na placa, ao periférico USART2 do microcontrolador STM32F411.

Isso significa que não é necessário nenhum conversor USB-Serial adicional: basta conectar a placa ao PC e abrir um terminal serial. Do ponto de vista elétrico e lógico, o ST-LINK faz a ponte entre os pinos da UART do MCU e o USB do computador, simplificando enormemente o processo de debug.

2.1. Mapeamento de pinos na Nucleo F411RE

Na maioria das placas Nucleo baseadas no F4, o mapeamento é:

  • USART2_TX → PA2
  • USART2_RX → PA3

Esses sinais passam pelo circuito do ST-LINK através de jumpers normalmente identificados como:

  • JP5 (ou equivalente) — controla a conexão da UART ao ST-LINK.

Se os jumpers estiverem na posição padrão (factory default), a UART estará conectada ao ST-LINK, e o printf será exibido no terminal do computador.

2.2. Configuração típica da UART para debug

A configuração mais comum, e recomendada pela ST, é:

  • Baudrate: 115200 bps
  • Bits de dados: 8
  • Paridade: None
  • Stop bits: 1
  • Modo: TX (opcionalmente RX se quiser receber dados)
  • Hardware Flow Control: None

Essas configurações funcionam com praticamente todos os terminais como PuTTY, screen, Minicom, CoolTerm, Serial Studio e o monitor serial da própria ST.

2.3. Clocks envolvidos

A UART só funcionará corretamente se:

  1. O clock do barramento APB1 estiver configurado.
  2. O clock da UART seja habilitado (feito automaticamente pelo CubeIDE ao ativar a USART2).
  3. Os pinos PA2 e PA3 estejam configurados como Alternate Function 7 (AF7).

Todos esses passos serão automatizados quando configurarmos o projeto no STM32CubeIDE, mas é importante compreender que a comunicação serial depende diretamente da estabilidade do clock do sistema. Uma configuração inadequada do PLL ou do HSE/HSI pode fazer a UART transmitir dados corrompidos.

2.4. Como o fluxo de debug é estabelecido

O caminho do printf até o terminal serial pode ser resumido assim:

printf() 
função de baixo nível __io_putchar() 
HAL_UART_Transmit()
USART2
ST-LINK (Virtual COM Port)
Cabo USB
PC (Terminal Serial)

Esse pipeline deixa claro que:

  • O printf não sabe nada sobre UART; ele apenas escreve caracteres.
  • Quem realmente envia cada caractere é nossa implementação de _write() ou __io_putchar().
  • Se a UART não estiver inicializada, nada será transmitido.
  • Se a UART bloquear (modo polling), o printf também bloqueará.

Por isso, utilizaremos uma implementação simples usando HAL_UART_Transmit(), que é suficiente para debug e fácil de manter.


3. Configurando o Projeto no STM32CubeIDE para Habilitar a UART

Nesta etapa vamos preparar o ambiente no STM32CubeIDE para que a placa Nucleo STM32F411 use corretamente a USART2 como canal de debug. O CubeIDE facilita muito o processo automatizando a configuração de pinos, clocks e inicialização do HAL.


3.1. Criando um novo projeto

  1. Abra o STM32CubeIDE.
  2. Vá em File > New > STM32 Project.
  3. Pesquise por Nucleo-F411RE.
  4. Selecione a placa e avance.
  5. Escolha o nome do projeto (por exemplo: UART_Debug_Printf).

O CubeIDE automaticamente carregará a configuração de pinos padrão da placa.


3.2. Habilitando a USART2 como canal de debug

  1. No Device Configuration Tool (a tela .ioc), clique no pino PA2 e selecione USART2_TX.
  2. Clique em PA3 e selecione USART2_RX.
  3. No menu lateral, selecione Connectivity → USART2.
  4. Configure como:
  • Mode: Asynchronous
  • Baudrate: 115200
  • Word Length: 8 Bits
  • Parity: None
  • Stop Bits: 1
  • Hardware Flow Control: None

O CubeIDE automaticamente adicionará a chamada:

MX_USART2_UART_Init();

no arquivo main.c.


3.3. Verificando os clocks

Entre em Clock Configuration e confirme:

  • SYSCLK: 84 MHz (valor típico no F411 configurado pelo CubeIDE)
  • APB1: ≤ 42 MHz
  • USART2 Clock: habilitado automaticamente pelo CubeIDE

A UART depende diretamente desses clocks. Se você modificar manualmente o PLL, ajuste os divisores para manter APB1 em valores seguros.


3.4. Interação entre HAL e UART para debug

Ao habilitar a UART no .ioc, o CubeIDE:

  • Gera o handle global UART_HandleTypeDef huart2;
  • Cria a função MX_USART2_UART_Init()
  • Configura os pinos PA2 e PA3 como Alternate Function 7
  • Habilita o clock da USART2

Com isso, HAL_UART_Transmit() já pode ser usado imediatamente na aplicação.

Exemplo simples:

uint8_t msg[] = "UART funcionando\r\n";
HAL_UART_Transmit(&huart2, msg, sizeof(msg)-1, HAL_MAX_DELAY);

Esse é o passo fundamental antes de redirecionar o printf. Sem uma UART funcional, o redirecionamento não terá efeito.


3.5. Compilar e validar

Clique em:

  • Project > Build Project
  • Depois Run ou Debug

Ao conectar a placa ao PC, confirme no sistema operacional que a porta serial virtual foi criada.

Exemplos:

  • Windows: COM5
  • Linux: /dev/ttyACM0

4. Implementando o redirecionamento do printf para a UART

Agora que a USART2 da Nucleo STM32F411 está configurada e testada com um HAL_UART_Transmit() simples, podemos dar o passo mais importante: fazer com que o printf() use essa UART como “saída padrão”.

Em microcontroladores ARM com newlib (caso do STM32CubeIDE, que usa arm-none-eabi-gcc), o printf não escreve diretamente no hardware. Ele chama funções de baixo nível, como _write() ou, em alguns exemplos da ST, uma função auxiliar chamada __io_putchar(). A ideia do redirecionamento é simples:

  • Implementar _write() (ou __io_putchar()) para enviar os bytes pela UART;
  • Compilar e linkar essa implementação junto ao projeto;
  • A partir daí, qualquer printf() encaminha os dados para a UART.

A seguir vou mostrar um modelo que funciona muito bem com o STM32CubeIDE/GCC e a Nucleo F411.


4.1. Incluindo os headers necessários

Primeiro, em um arquivo C do seu projeto (pode ser o próprio main.c ou, melhor ainda, um arquivo dedicado como retarget.c), inclua:

#include "stm32f4xx_hal.h"
#include <stdio.h>
#include <unistd.h>

Depois, declare o handle da UART como extern, pois ele é criado em outro arquivo (gerado pelo CubeIDE):

extern UART_HandleTypeDef huart2;

4.2. Implementando _write() para o GCC/newlib

A abordagem mais robusta, em ambiente GCC + newlib, é sobrescrever a função _write(). O printf acaba chamando _write() internamente para escrever os dados:

int _write(int file, char *ptr, int len)
{
    // Envia o buffer "ptr" com "len" bytes pela UART2
    HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
}

Alguns detalhes importantes:

  • O parâmetro file representa o descritor de arquivo (stdout, stderr, etc.). Para debug simples, você pode ignorá-lo.
  • ptr é o endereço do buffer com os caracteres já montados pelo printf.
  • len é a quantidade de bytes a transmitir.
  • HAL_MAX_DELAY faz a função bloquear até a transmissão terminar. É simples e suficiente para debug (não é ideal em aplicações com requisitos de tempo real estritos).

Com essa implementação, qualquer printf() que escreva em stdout já será redirecionado para a UART2.


4.3. Implementando __io_putchar() (opcional, mas comum em exemplos ST)

Em muitos exemplos da ST, você verá o uso de um macro PUTCHAR_PROTOTYPE que mapeia para __io_putchar() (no GCC) ou fputc() (em outros toolchains). Você pode combinar as duas ideias:

#ifdef __GNUC__
/* GCC/STM32CubeIDE usa __io_putchar() internamente em printf em alguns exemplos da ST */
int __io_putchar(int ch)
{
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}
#else
/* Para outros compiladores, como Keil ou IAR */
int fputc(int ch, FILE *f)
{
    HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}
#endif

Se você implementar apenas _write(), o printf já funciona no GCC/newlib. Se quiser compatibilidade máxima com exemplos da ST e outros toolchains, mantenha também __io_putchar()/fputc().


4.4. Onde colocar esse código no projeto

Existem duas abordagens:

  1. No próprio main.c
    • Mais simples para começar, ideal para experimentos.
    • Basta inserir a implementação de _write() e/ou __io_putchar() abaixo das includes e da declaração extern UART_HandleTypeDef huart2;.
  2. Em um arquivo separado, por exemplo retarget.c
    • Organização melhor, especialmente em projetos grandes.
    • Crie o arquivo via New > Source File, inclua as cabeçalhos e implemente _write() ali.
    • Não esqueça de adicionar retarget.c ao build (o CubeIDE já faz isso automaticamente ao criar dentro do projeto).

O ponto fundamental é: o arquivo que contém _write()/__io_putchar() precisa ser compilado junto com o projeto para que o linker substitua a implementação fraca (weak) padrão de _write() pela sua.


4.5. Pequeno exemplo de uso no main.c

Depois de configurada a UART e implementado _write(), o main.c pode conter algo como:

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_USART2_UART_Init();   // Importante: inicializa a UART2 antes do primeiro printf

    printf("Sistema iniciado!\r\n");
    printf("Clock do sistema: %lu Hz\r\n", HAL_RCC_GetHCLKFreq());

    while (1)
    {
        printf("Loop principal, tick = %lu\r\n", HAL_GetTick());
        HAL_Delay(1000);
    }
}

Se tudo estiver correto, essas mensagens aparecerão no terminal serial do seu PC, na porta correspondente à Virtual COM Port, a 115200 bps.


5. Cuidados com printf: buffer, quebra de linha e impacto em tempo real

Com o printf já saindo pela UART2, é hora de falar dos detalhes práticos que mais costumam pegar de surpresa: comportamento de buffer, diferença entre \n e \r\n, e o impacto de sair “espalhando printf pelo código” em um sistema que tem requisitos de tempo real.


5.1. \n vs \r\n – por que às vezes o texto “sobe” no terminal?

Em muitos terminais seriais (PuTTY, Minicom, screen, etc.), o caractere \n (line feed) apenas move o cursor para a linha de baixo, sem retornar para a coluna 0. Já \r (carriage return) volta o cursor para o início da linha, mas não desce. O comportamento mais comum em sistemas embarcados é usar a combinação:

printf("Mensagem de debug\r\n");

Se você usar somente \n, é comum ver efeito de “escada” ou textos desalinhados no terminal. O padrão “universal” para logs em interface serial é \r\n. Se quiser, pode criar um helper:

void logln(const char *msg)
{
    printf("%s\r\n", msg);
}

E usar logln("Sistema iniciado"); ao invés de lembrar sempre do \r\n.


5.2. Bufferização do printf (stdout) e “trava” aparente

Por padrão, a biblioteca C pode bufferizar a saída (stdout). Em ambiente embarcado, isso varia conforme a toolchain, mas alguns sintomas são:

  • Você chama printf("Teste\r\n"); e nada aparece no terminal.
  • Ao chamar outro printf maior ou ao encerrar o programa (em PC), tudo aparece “de uma vez”.

Para evitar esse tipo de surpresa, você pode:

  1. Desabilitar o buffer de stdout (quando suportado), logo após iniciar o sistema: setvbuf(stdout, NULL, _IONBF, 0); // stdout sem buffer Em muitos ambientes embarcados com newlib-nano, isso já é o comportamento, mas não custa colocar.
  2. Sempre finalizar as linhas com \r\n, o que, em algumas implementações, força flush.

Se perceber comportamentos estranhos, experimente remover otimizações extremas de compilação e garantir que _write() está sendo realmente linkado (às vezes uma implementação fraca default pode estar sendo usada por engano).


5.3. Cuidado com o tempo de bloqueio: HAL_UART_Transmit() é bloqueante

Na implementação que fizemos:

int _write(int file, char *ptr, int len)
{
    HAL_UART_Transmit(&huart2, (uint8_t *)ptr, len, HAL_MAX_DELAY);
    return len;
}

o HAL_UART_Transmit() é bloqueante: a CPU fica parada até todos os bytes serem transmitidos. Em termos práticos:

  • Uma linha como
    printf("Valor = %d, estado = %d\r\n", x, estado);
    pode gerar 30–60 bytes.
  • A 115200 bps, você transmite cerca de 11.520 bytes/s (aprox. 10 bits por byte, contando start/stop).
  • Então 50 bytes ≈ 4,3 ms de transmissão não-interrompida.

Se você fizer isso dentro de uma ISR (rotina de interrupção) ou em uma tarefa crítica de tempo real, esses milissegundos a mais podem causar problemas:

  • Perda de deadlines.
  • Atraso na leitura de sensores ou controle de motor.
  • Estouro de watchdog, se o sistema for mais rígido.

Boas práticas:

  • Nunca usar printf em ISR.
    Se precisar debugar uma interrupção, registre flags ou copie dados para um buffer e imprima em contexto de thread/tarefa.
  • Usar printf com parcimônia em laços que rodam com alta frequência.
  • Em sistemas com RTOS, considerar uma tarefa de log dedicada.

5.4. Estratégia com RTOS: fila de logs (opcional, mas muito recomendável)

Se você estiver usando FreeRTOS (ou outro RTOS), uma abordagem mais limpa é:

  1. Criar uma fila (queue) para mensagens de log.
  2. Criar uma tarefa “Logger” que:
    • Bloqueia esperando mensagens na fila.
    • Ao receber, chama HAL_UART_Transmit() para mandar pela UART.
  3. Substituir printf por algo como log_printf(), que formata a string e envia para a fila.

Esboço de ideia (bem simplificado):

// Tamanho máximo de uma mensagem de log
#define LOG_MSG_MAX 128

QueueHandle_t xLogQueue;

void LoggerTask(void *argument)
{
    char msg[LOG_MSG_MAX];

    for (;;)
    {
        if (xQueueReceive(xLogQueue, &msg, portMAX_DELAY) == pdTRUE)
        {
            HAL_UART_Transmit(&huart2, (uint8_t *)msg, strlen(msg), HAL_MAX_DELAY);
        }
    }
}

// Exemplo simples de função de log (sem vsnprintf, apenas ilustrativo)
void log_printf(const char *fmt, ...)
{
    char buffer[LOG_MSG_MAX];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buffer, LOG_MSG_MAX, fmt, args);
    va_end(args);

    xQueueSend(xLogQueue, &buffer, 0);
}

Essa abordagem tira a carga do contexto onde a mensagem é gerada e concentra o custo de transmissão em uma única tarefa de menor prioridade.


5.5. printf grande demais e estouro de stack/heap

Outra armadilha comum: strings muito grandes em printf, especialmente em sistemas com pouco RAM:

  • Cada printf complexa pode usar vsnprintf internamente, exigindo buffers temporários.
  • Se o stack ou heap forem pequenos, isso pode levar a stack overflow ou corromper memória silenciosamente.

Recomendações:

  • Evite prints gigantescos; prefira várias mensagens menores e objetivas.
  • Ajuste o tamanho de stack das tarefas em RTOS que usam printf.
  • Se possível, use a newlib-nano com suporte a printf reduzido (sem float), a menos que precise imprimir float/double.

6. Testando o redirecionamento do printf no PC com terminal serial

Com toda a infraestrutura montada — UART configurada, _write() implementado e printf() funcionando — é hora de validar tudo com um terminal serial no computador. Esta etapa confirma que a Virtual COM Port (VCP) do ST-LINK está operando corretamente e que os dados enviados pelo microcontrolador estão realmente chegando até o PC.


6.1. Identificando a porta serial (VCP) no sistema operacional

Quando você conecta a Nucleo STM32F411 ao computador usando o cabo USB no conector do ST-LINK (não no USB OTG do microcontrolador), o ST-LINK cria automaticamente uma porta serial virtual.

Para verificar:

No Windows

Abra o Gerenciador de DispositivosPortas (COM & LPT)
Aparecerá algo como:

  • STMicroelectronics Virtual COM Port (COM5)

O número da porta (COM5, COM7 etc.) varia de máquina para máquina.

No Linux

Use no terminal:

ls /dev/ttyACM*

ou observe mensagens do dmesg:

dmesg | tail

Normalmente será:

  • /dev/ttyACM0
  • /dev/ttyACM1

No macOS

Em geral:

ls /dev/tty.usbmodem*

[Espaço para Imagem 9 – Captura mostrando a porta STMicroelectronics Virtual COM Port no Windows /dev/ttyACM0 no Linux]


6.2. Configurando o terminal serial

Você pode usar ferramentas como:

  • PuTTY
  • screen (Linux/macOS)
  • Minicom
  • CoolTerm
  • Serial Studio
  • TeraTerm

As configurações fundamentais:

  • Baud rate: 115200
  • Data bits: 8
  • Parity: None
  • Stop bits: 1
  • Flow control: None

Essas são as mesmas configurações definidas no CubeIDE para a UART2.

Exemplos:

No PuTTY (Windows):

  • Connection type: Serial
  • Serial line: COM5
  • Speed: 115200
  • Clique em Open.

No Linux (screen):

screen /dev/ttyACM0 115200

Para sair:

  • Pressione: Ctrl + A, depois K.

6.3. Primeiro teste: envio de mensagem simples

Carregue o firmware na placa e inicialize. Logo no início, se o seu main.c tiver:

printf("Sistema iniciado!\r\n");

Você deverá ver no terminal:

Sistema iniciado!

Se após alguns segundos o loop do seu exemplo estiver imprimindo:

printf("Loop principal, tick = %lu\r\n", HAL_GetTick());

Você verá:

Loop principal, tick = 1001
Loop principal, tick = 2002
Loop principal, tick = 3003
...

Se nada aparecer:

  • Verifique a porta correta.
  • Confirme a velocidade (115200).
  • Garanta que MX_USART2_UART_Init() é chamado antes do primeiro printf.
  • Confira se _write() está realmente sendo compilado (abra a pasta /Debug e verifique se retarget.o está lá).
  • Veja se PA2/PA3 não foram sobrescritos no .ioc.

6.4. Segundo teste: envio de valores variáveis

Para testar formatação e estabilidade, experimente inserir:

int contador = 0;
while (1)
{
    printf("Contador = %d\r\n", contador++);
    HAL_Delay(500);
}

Após rodar, o terminal deve exibir algo como:

Contador = 0
Contador = 1
Contador = 2
Contador = 3
...

Esse teste simples valida:

  • Integração com printf
  • Transmissão contínua
  • Quebras de linha \r\n
  • Buffer do terminal
  • Confiabilidade da UART

7. Boas práticas, alternativas ao printf e resumo final

Agora que o redirecionamento do printf para a UART está funcionando e validado no terminal, é importante fechar o tutorial consolidando boas práticas e apresentando métodos alternativos de debug que podem ser superiores dependendo do contexto do projeto.


7.1. Boas práticas ao usar printf em sistemas embarcados

O printf é muito útil para desenvolvimento, testes e prototipagem. Porém, em firmware profissional, seu uso deve ser criterioso. Aqui estão as principais recomendações:

1. Evite usar printf dentro de interrupções (ISR)

Como vimos, o HAL_UART_Transmit() é bloqueante. Se for chamado dentro de uma interrupção:

  • A interrupção ficará presa transmitindo os bytes,
  • O sistema pode perder deadlines,
  • Outras interrupções podem ser atrasadas,
  • Pode ocorrer estouro do watchdog.

Sempre registre dados críticos em buffers e imprima em contexto de thread.

2. Não abuse do printf em laços rápidos

Se o loop roda a cada 1 ms e imprime texto extenso, a UART não acompanhará o ritmo. Isso pode causar:

  • Travamento aparente,
  • Perda de cadência no controle,
  • Saturação de buffer,
  • Queda de desempenho.

3. Evite printf com floats

O suporte a float aumenta muito o tamanho do binário e o custo computacional.
Prefira converter para inteiro multiplicando por 100 ou 1000:

printf("Temperatura = %d.%02d C\r\n", (int)temp, (int)(temp * 100) % 100);

4. Desative logs em builds finais

Em builds de produção, é comum:

  • Compilar sem logs,
  • Usar #define DEBUG para habilitar/desabilitar os prints,
  • Criar macros como:
#ifdef DEBUG
    #define debug_printf(...) printf(__VA_ARGS__)
#else
    #define debug_printf(...)
#endif

Assim você elimina overhead quando não precisa de logs.


7.2. Alternativas avançadas ao printf

Embora a UART seja excelente para debug, ela não é a única nem a melhor solução em todos os casos.

1. ITM/SWO (Single Wire Output)

Disponível na maioria dos STM32F4, incluindo o F411.
Oferece:

  • Alta velocidade,
  • Impressão quase imediata,
  • Baixíssimo overhead,
  • Não bloqueia o firmware,
  • Funciona dentro de ISR.

Requer que seu ST-LINK suporte SWO (quase todos os V2-1 suportam).

A função usada no HAL é:

ITM_SendChar(ch);

Muitos desenvolvedores substituem __io_putchar() para cair aqui.

2. RTT (Real-Time Transfer) – SEGGER J-Link

Se você usa J-Link:

  • RTT é extremamente rápido,
  • Zero bloqueio,
  • Permite canais bidirecionais,
  • Ideal para sistemas de alto desempenho.

Funciona mesmo com microcontroladores sem UART sobrando.

3. Semihosting

O printf é exibido no console do IDE, sem precisar de UART.

Porém:

  • Extremamente lento,
  • Bloqueante,
  • Inadequado para quase tudo exceto testes acadêmicos.

4. Logs estruturados por DMA

Você pode configurar a UART com DMA circular para transmitir logs sem bloquear a CPU.
Exige controle mais sofisticado, mas reduz o custo de tempo real.


7.3. Resumo final do processo

Aqui está um resumo objetivo de tudo o que você implementou:

  1. Configure a USART2 no CubeIDE usando PA2 (TX) e PA3 (RX), 115200 bps.
  2. Confirme que a VCP do ST-LINK está ativa (porta COM/ttyACM).
  3. Compile o projeto e inicialize a UART com MX_USART2_UART_Init().
  4. Implemente a função _write() ou __io_putchar() para enviar caracteres via HAL_UART_Transmit().
  5. Teste com um terminal serial como PuTTY, Minicom ou screen.
  6. Use \r\n para evitar quebras de linha incorretas.
  7. Evite printf em ISR e em laços críticos.
  8. Em aplicações mais avançadas, considere alternativas como ITM/SWO ou RTT.

Com isso, você agora possui um ambiente completo, simples e confiável para fazer debug via UART no STM32F411 — exatamente como demonstrado no guia da ST, mas adaptado para o contexto da Nucleo.


0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x