Implementação de Drivers de UART para Telas Seriais TJC com STM32 HAL

Este documento detalha a implementação de drivers para telas seriais TJC (Touch Job Control) utilizando o framework HAL (Hardware Abstraction Layer) do STM32. A solução emprega técnicas avançadas como buffers circulares, DMA e a funcionalidade de interrupção Idle para otimizar a comunicação serial.

  1. Arquitetura Geral

A arquitetura foi projetada para oferecer um manuseio eficiente tanto da recepção quanto da transmissão de dados, priorizando a não-bloqueio e a minimização do uso da CPU.

Fluxo de Recepção

A recepção de dados utiliza o modo DMA Circular em conjunto com a interrupção Idle do UART. Quando o barramento UART fica ocioso (indicando o fim de uma trama), um callback é acionado. Este callback apenas sinaliza a presença de novos dados para a aplicação de camada superior, que então os processa em sua própria lógica de loop.

Fluxo de Transmissão

A transmissão é realizada através de uma função de impressão formatada (similar a printf) que primeiro formata a string e anexa os terminadores específicos do protocolo TJC (0xFF 0xFF 0xFF). A transmissão em si é realizada de forma blouqeante usando HAL_UART_Transmit, o que é adequado para comandos de tela que geralmente são curtos e exigem confirmação.

Bufferização

Dois buffers circulares independentes são mantidos para cada interface UART (um para TJC e outro para BLE, por exemplo). Essa separação garante que a comunicação entre as diferentes portas seriais não interfira uma na outra.

  1. Estrutura de Dados do Buffer Circular

Para gerenciar os dados recebidos de forma eficiente, um buffer circular é empregado. A estrutura básica é definida da seguinte forma:


typedef struct {
   volatile uint16_t read_ptr;       // Ponteiro de leitura do software (head)
   uint8_t buffer_data[BUFFER_SIZE]; // Área de dados real do buffer
} CircularBuffer_t;

// Instâncias dos buffers, alocadas em memória DMA para acesso otimizado
DMA_BUFFER CircularBuffer_t uart_rx_buffer;
DMA_BUFFER uint8_t tx_temp_buffer[TX_BUFFER_SIZE];
   

O campo read_ptr é marcado como volatile para assegurar que o compilador não aplique otimizações que possam levar a leituras desatualizadas por parte da aplicação.

  1. Funções Principais e Análise Detalhada

3.1. Inicialização (HMI_Init)

A função de inicialização configura os buffers e inicia a recepção DMA.


void HMI_Init(UART_HandleTypeDef* huart, CircularBuffer_t* rx_buffer) {
   rx_buffer->read_ptr = 0; // Zera o ponteiro de leitura

   // Inicia a recepção DMA no modo Idle, usando o buffer circular
   HAL_UARTEx_ReceiveToIdle_DMA(huart, rx_buffer->buffer_data, BUFFER_SIZE);

   // Desabilita interrupções de Half Transfer (HT) do DMA, pois são desnecessárias
   // no modo circular e consomem recursos de CPU indevidamente.
   __HAL_DMA_DISABLE_IT(huart->hdmarx, DMA_IT_HT);
}
   

Pontos Chave:

  • ReceiveToIdle_DMA: Preferível ao Receive_DMA padrão, pois só gera um evento ao final de uma trama de dados, adaptando-se a tamanhos de pacote variáveis.
  • Desabilitação de DMA_IT_HT: A interrupção de transferência pela metade (Half Transfer) não traz benefícios no contexto de buffers circulares e é desativada para reduzir a sobrecarga do sistema.
  • Inicialização do buffer: Apenas o ponteiro de leitura é zerado. Os dados do buffer são continuamente sobrescritos pelo DMA, dispensando a necessidade de limpar a área de memória.

3.2. Contagem de Dados Disponíveis (HMI_Get_DataCount)

Calcula quantos bytes estão prontos para serem lidos no buffer circular.


uint16_t HMI_Get_DataCount(CircularBuffer_t* rx_buffer, UART_HandleTypeDef* huart) {
   // O contador DMA (NDTR) representa os bytes *restantes* a serem transmitidos.
   // Portanto, o ponteiro de escrita (write_ptr) é o tamanho total menos o contador.
   uint16_t write_ptr = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(huart->hdmarx);

   // Cálculo do número de bytes disponíveis, tratando o caso de 'wrap-around' do buffer.
   return (write_ptr - rx_buffer->read_ptr + BUFFER_SIZE) % BUFFER_SIZE;
}
   

A fórmula (write_ptr - read_ptr + BUFFER_SIZE) % BUFFER_SIZE é um padrão para calcular o espaço utilizável em buffers circulares, garantindo a correção mesmo quando write_ptr é menor que read_ptr (indicando que o buffer "deu a volta").

3.3. Leitura e Consumo de Dados (HMI_ConsumeData e HMI_PeekData)

Estas funções permitem que a aplicação acesse os dados no buffer sem a necessidade de cópias intermediárias.

  • HMI_ConsumeData(size): Simplesmente avança o read_ptr pelo número de bytes consumidos. Não envolve cópia de dados.
  • HMI_PeekData(index): Retorna o byte na posição index a partir do read_ptr atual, utilizando a lógica de endereçamento circular (read_ptr + index) % BUFFER_SIZE. Isso permite que a aplicação implemente máquinas de estado para analisar os pacotes diretamente no buffer DMA.

3.4. Envio de Dados (HMI_SendFormatted)

Responsável por formatar e enviar dados para a tela.


void HMI_SendFormatted(UART_HandleTypeDef* huart, const char* format_string, ...) {
   // Seleciona o buffer de transmissão temporário baseado na instância do UART
   uint8_t* tx_buffer_ptr = (huart->Instance == TJC_UART_INSTANCE) ? tx_buffer_tjc : tx_buffer_ble;

   // Verifica se o UART já está transmitindo para evitar sobreposição de comandos
   if (huart->gState == HAL_UART_STATE_BUSY_TX) {
       return;
   }

   va_list arguments;
   va_start(arguments, format_string);
   // Formata a string, reservando espaço para os terminadores 0xFF 0xFF 0xFF
   int bytes_written = vsnprintf((char*)tx_buffer_ptr, TX_BUFFER_SIZE - 3, format_string, arguments);
   va_end(arguments);

   // Verifica se a formatação foi bem-sucedida e se não excedeu o buffer
   if (bytes_written <= 0) {
       return;
   }
   if (bytes_written > TX_BUFFER_SIZE - 3) {
       bytes_written = TX_BUFFER_SIZE - 3; // Trunca se for muito grande
   }

   // Anexa os terminadores do protocolo TJC
   tx_buffer_ptr[bytes_written++] = 0xFF;
   tx_buffer_ptr[bytes_written++] = 0xFF;
   tx_buffer_ptr[bytes_written++] = 0xFF;

   // Realiza a transmissão bloqueante
   HAL_UART_Transmit(huart, tx_buffer_ptr, bytes_written, HAL_MAX_DELAY);
}
   
  • Espaço para Terminadores: O buffer temporário de transmissão tem espaço reservado para garantir que os 0xFF finais sempre caibam.
  • Proteção contra Reentrada: A verificação HAL_UART_STATE_BUSY_TX previne que comandos sejam enviados em sequência rápida demais, o que poderia corromper a comunicação.
  • Suporte a Ponto Flutuante: Para utilizar formatação de ponto flutuante (e.g., %f), pode ser necessário habilitar a opção -u _printf_float nas configurações do compilador.
  1. Mecanismo de Callback de Recepção

O callback HAL_UARTEx_RxEventCallback é acionado quando a interrupção Idle ocorre.


void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t received_size) {
   // Apenas define um flag para indicar que novos dados estão disponíveis.
   // O processamento real ocorre no loop principal da aplicação.
   if (huart->Instance == TJC_UART_INSTANCE) {
       tjc_data_ready_flag = 1;
   } else if (huart->Instance == BLE_UART_INSTANCE) {
       ble_data_ready_flag = 1;
   }
   // Reativa a recepção DMA Idle para continuar monitorando o barramento
   HAL_UARTEx_ReceiveToIdle_DMA(huart, /* buffer, size */);
}
   

Este callback é deliberadamente simples, apenas atualizando um flag. A lógica de processamento dos dados é delegada ao loop principal da aplicação, promovendo um design mais limpo e desacoplado.

  1. Considerações Importantes

  1. Configuração CubeMX: No STM32CubeMX, configure o modo DMA do canal de recepção do UART para Circular. Caso contrário, será necessário gerenciar manualmente o início e o fim das transferências DMA.
  2. Tamanho do Buffer: Para desempenho ótimo, o BUFFER_SIZE do buffer circular deve ser uma potência de dois (e.g., 256, 512, 1024).
  3. Gerenciamento do Ponteiro de Leitura: É crucial que a aplicação chame HMI_ConsumeData após processar os dados recebidos. Falhar em fazer isso fará com que o buffer circular pareça "cheio" (read_ptr não avança), resultando na perda de novos dados.

Tags: STM32 HAL UART DMA Buffer Circular

Publicado em 10-25 13:42