Введение
Аналогично UART-связи Arduino с Qt-приложением из основного Arduino-цикла (статьи 280-290), STM32 поддерживает UART для последовательной связи с компьютером — в статье разберём настройку UART на стороне STM32 через HAL и приём данных в Qt-приложении на ПК через QSerialPort (основной цикл статей).
Концепция
STM32 HAL предоставляет функции HAL_UART_Transmit()/HAL_UART_Receive() для блокирующей передачи/приёма данных через UART, а также неблокирующие варианты с прерываниями (HAL_UART_Transmit_IT()) или DMA (статья 357) для более эффективной передачи без блокировки основного цикла выполнения программы микроконтроллера. На стороне Qt-приложения используется тот же QSerialPort, что и для связи с Arduino — с точки зрения ПК UART-устройство STM32 (через встроенный USB-UART конвертер на отладочной плате, или внешний переходник USB-UART) выглядит так же, как и аналогичное Arduino-устройство.
Пример кода: UART на STM32
// stm32 main.c — настройка UART2 и передача данных датчика
#include "stm32f4xx_hal.h"
#include <stdio.h>
UART_HandleTypeDef huart2;
void UART2_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
HAL_UART_Init(&huart2);
}
int main(void)
{
HAL_Init();
SystemClock_Config();
UART2_Init();
while (1) {
// Аналогично Arduino-формату из основного цикла — простой текстовый протокол
char message[64];
int length = snprintf(message, sizeof(message), "TEMP:23.5,HUMID:45.0\n");
HAL_UART_Transmit(&huart2, (uint8_t *)message, length, HAL_MAX_DELAY);
HAL_Delay(1000);
}
}
// Неблокирующий приём через прерывание — для случая, когда STM32 ожидает команды от Qt-приложения
#define RX_BUFFER_SIZE 64
uint8_t rxBuffer[RX_BUFFER_SIZE];
volatile uint8_t rxComplete = 0;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2) {
rxComplete = 1; // обработка фактического содержимого — в основном цикле, не в самом прерывании
}
}
int main(void)
{
// ... инициализация, как выше ...
HAL_UART_Receive_IT(&huart2, rxBuffer, RX_BUFFER_SIZE); // запуск приёма в фоне, не блокируя
while (1) {
if (rxComplete) {
// обработка rxBuffer
rxComplete = 0;
HAL_UART_Receive_IT(&huart2, rxBuffer, RX_BUFFER_SIZE); // повторный запуск приёма
}
// основной цикл продолжает работать, не блокируясь ожиданием данных
}
}
// Qt-приложение на ПК — приём данных от STM32, идентично по подходу основному Arduino-циклу
#include <QSerialPort>
void setupStm32Connection(QSerialPort &port)
{
port.setPortName("COM5"); // или /dev/ttyUSB0 на Linux
port.setBaudRate(115200);
port.open(QIODevice::ReadOnly);
QObject::connect(&port, &QSerialPort::readyRead, [&port]() {
QByteArray data = port.readAll();
qDebug() << "Получено от STM32:" << data;
// Парсинг строки "TEMP:23.5,HUMID:45.0" аналогично подходу из статьи 280
});
}
Пояснения к коду
HAL_UART_Transmit() с параметром HAL_MAX_DELAY — простейший, полностью блокирующий вариант передачи, приемлемый для редкой, не критичной по времени отправки данных (как периодическая отправка показаний датчика раз в секунду в примере), но неприемлемый для кода, которому нужно продолжать выполнять другую работу во время самой передачи. HAL_UART_Receive_IT() запускает приём в фоновом режиме через прерывание — основной цикл while(1) продолжает работать без блокировки, а когда приём заданного количества байт завершается, автоматически вызывается HAL_UART_RxCpltCallback(), где устанавливается простой флаг (rxComplete), проверяемый в основном цикле — именно такой паттерн (минимальная работа непосредственно в обработчике прерывания, основная обработка вне его) рекомендован для написания корректного, отзывчивого кода обработки прерываний, поскольку обработчики прерываний должны выполняться максимально быстро, не блокируя обработку других, возможно более приоритетных прерываний.
Подводные камни
- Выполнение длительной, медленной обработки данных непосредственно внутри
HAL_UART_RxCpltCallback(), а не простая установка флага с переносом реальной обработки в основной цикл (как в примере) — обработчики прерываний должны завершаться максимально быстро, и любая задержка внутри них (включая, например, вызовHAL_UART_Transmit()с блокирующим ожиданием) может пропустить или задержать обработку других важных прерываний системы, что особенно критично для приложений с жёсткими требованиями к реальному времени. - Несовпадение настроек UART (скорость передачи, биты данных, чётность, стоп-биты) между STM32 и Qt-приложением на ПК — аналогично проблеме из основного Arduino-цикла, малейшее несовпадение этих параметров приводит к получению полностью искажённых, нечитаемых данных на одной из сторон, и при первой настройке связи стоит явно сверить эти параметры на обеих сторонах соединения.
- Отсутствие перезапуска неблокирующего приёма (
HAL_UART_Receive_IT()) после обработки полученных данных — в примере это явно показано (повторный вызов внутриif (rxComplete)), но забытый повторный запуск приёма после обработки первой порции данных оставит UART неспособным принять следующее сообщение, что выглядит как «зависшая», не отвечающая больше после первого сообщения связь. - Использование фиксированного размера буфера приёма (
RX_BUFFER_SIZE) без проверки, что реальные сообщения действительно умещаются в этот размер, и без обработки случая прихода сообщения переменной, не заранее известной длины — для протоколов с сообщениями переменной длины (в отличие от примера с фиксированным форматом) стоит использовать более сложный подход с определением конца сообщения по специальному символу-разделителю (аналогично парсингу строк, заканчивающихся\n, в Qt-приложении) и аккуратной обработкой накопления данных по частям, а не предполагать, что одно прерывание о завершении приёма соответствует ровно одному полному, законченному сообщению.