作为一名嵌入式开发工程师,我深知串口通信在STM32项目开发中的重要性。USART(通用同步异步收发器)作为最基础也最常用的外设之一,几乎出现在每一个嵌入式项目中。本文将基于STM32 HAL库,详细讲解串口发送功能的实现方法和实际运用技巧。
在实际项目中,串口常用于:
掌握HAL库的串口操作,能极大提升开发效率。相比标准外设库,HAL库提供了更简洁的API接口,但同时也隐藏了一些底层细节,这正是我们需要重点解析的部分。
以常见的STM32F103C8T6最小系统板为例,USART1的默认引脚为:
连接示意图:
code复制STM32 USB转串口模块
PA9(TX) ----- RX
PA10(RX) ----- TX
GND ----- GND
注意:务必确保TX-RX交叉连接,且共地。我曾见过不少初学者直接将TX-TX相连导致通信失败的情况。
打开STM32CubeMX,选择对应型号
在Pinout视图中启用USART1:
参数配置(常用设置):
NVIC Settings中启用中断(如需中断方式):
生成代码前,确保Project Manager中:
最基本的发送函数是HAL_UART_Transmit():
c复制HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
典型使用示例:
c复制uint8_t msg[] = "Hello STM32!\r\n";
HAL_UART_Transmit(&huart1, msg, sizeof(msg)-1, HAL_MAX_DELAY);
参数说明:
实测发现:当发送大量数据时,轮询方式会阻塞主程序运行。在实时性要求高的场景需谨慎使用。
更高效的发送方式是使用中断:
c复制HAL_UART_Transmit_IT(&huart1, msg, sizeof(msg));
需要实现发送完成回调函数:
c复制void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART1) {
// 发送完成处理
}
}
中断方式的优势:
对于大数据量传输,DMA是最佳选择:
c复制HAL_UART_Transmit_DMA(&huart1, msg, sizeof(msg));
DMA配置要点:
DMA发送完成回调:
c复制void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart) {
// 半传输完成(大数据量时有用)
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
// 全部发送完成
}
方便调试的重定向方法:
c复制#include <stdio.h>
int _write(int file, char *ptr, int len) {
HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, HAL_MAX_DELAY);
return len;
}
使用示例:
c复制printf("System Clock: %ld Hz\r\n", HAL_RCC_GetSysClockFreq());
注意:使用此方法需在工程选项中勾选"Use MicroLIB"(Keil环境)
自定义格式化发送函数:
c复制void UART_Printf(UART_HandleTypeDef *huart, const char *fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
HAL_UART_Transmit(huart, (uint8_t*)buf, strlen(buf), HAL_MAX_DELAY);
}
实际项目中建议添加超时检测:
c复制if(HAL_UART_Transmit(&huart1, data, size, 100) != HAL_OK) {
// 错误处理
Error_Handler();
}
可能原因:
优化方案:
排查步骤:
实现思路:
c复制uint8_t tx_buf1[256], tx_buf2[256];
uint8_t *active_buf = tx_buf1;
// 填充active_buf数据
// ...
// 发送并切换缓冲区
HAL_UART_Transmit_DMA(&huart1, active_buf, len);
active_buf = (active_buf == tx_buf1) ? tx_buf2 : tx_buf1;
高效发送方法:
c复制void UART_Send_Direct(const uint8_t *data, uint16_t len) {
while(len--) {
while(!(huart1.Instance->SR & USART_SR_TXE));
huart1.Instance->DR = *data++;
}
}
实用技巧代码:
c复制void Auto_BaudRate_Detection(void) {
// 通过测量起始位时间计算波特率
// 具体实现取决于硬件支持
}
在最近的一个工业控制器项目中,我们使用USART与多个传感器通信。总结几点关键经验:
抗干扰设计:
协议设计:
调试技巧:
我曾遇到一个棘手问题:DMA发送偶尔丢失最后几个字节。最终发现是DMA传输完成中断过早触发,解决方案是在回调函数中添加微小延迟:
c复制void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
HAL_Delay(1); // 等待最后字节真正发送完成
// 后续处理...
}
当需要管理多个UART接口时,建议采用面向对象方式封装:
c复制typedef struct {
UART_HandleTypeDef *huart;
uint8_t tx_buf[256];
uint16_t tx_len;
} UART_Device;
void UART_Send_Device(UART_Device *dev, uint8_t *data, uint16_t len) {
// 实现带缓冲区的发送
}
在FreeRTOS中的典型用法:
c复制void vUARTTask(void *pvParameters) {
while(1) {
xQueueReceive(uart_tx_queue, &msg, portMAX_DELAY);
HAL_UART_Transmit(&huart1, msg.data, msg.len, 100);
}
}
通过HC-05等蓝牙模块扩展无线功能:
c复制#define BT_KEY_PIN GPIO_PIN_1
#define BT_KEY_PORT GPIOA
void BT_Enter_AT_Mode(void) {
HAL_GPIO_WritePin(BT_KEY_PORT, BT_KEY_PIN, GPIO_PIN_SET);
HAL_Delay(100);
// 发送AT指令...
}