4、UART串口通信基础:UART协议原理、波特率设置、STM32串口配置、串口助手调试

说到嵌入式开发,串口绝对是咱们打交道最多的外设之一。我刚开始做项目那会儿,第一个调试手段就是串口打印。说白了,UART就是设备之间对话的"电话线",简单、可靠、到处都能用。

这一章咱们就把UART串口通信彻底讲透。从协议原理到STM32的配置,再到实际调试,一条龙搞定。

4.1 UART协议原理

UART的全称是Universal Asynchronous Receiver/Transmitter,通用异步收发器。注意"异步"这两个字——收发双方不需要共享时钟信号,全靠约定的波特率来同步。

我当年刚学的时候有个疑问:没有时钟怎么对齐数据?其实答案就在起始位和停止位上。

一个标准的UART数据帧长这样:

起始位(1位) + 数据位(5~8位) + 校验位(可选) + 停止位(1~2位)

具体来说:

  • 空闲状态:总线保持高电平
  • 起始位:拉低电平,告诉接收方"我要发数据了"
  • 数据位:从低位到高位逐位发送,通常是8位
  • 校验位:奇校验或偶校验,用来简单检错
  • 停止位:拉回高电平,表示一帧结束

我在项目中遇到过一个问题:两个设备明明波特率一样,但通信就是乱码。查了半天发现,一个用的是8位数据+1位停止,另一个用的是7位数据+2位停止。你想想看,帧格式不匹配,数据肯定对不上。

核心要点:UART通信双方必须约定完全相同的帧格式——波特率、数据位、校验位、停止位,一个都不能错。

4.2 波特率设置

波特率就是每秒传输的比特数。常见的波特率有9600、19200、38400、115200等。115200是目前最常用的,速度够快,稳定性也不错。

波特率怎么算?对于STM32来说,公式是这样的:

波特率 = 时钟频率 / (16 × USARTDIV)

其中USARTDIV是一个分频系数,写在USART_BRR寄存器里。嗯,这里要注意:USARTDIV可以带小数,小数部分用4位二进制表示。

举个例子,如果时钟是72MHz,想要115200的波特率:

USARTDIV = 72000000 / (16 × 115200) ≈ 39.0625
整数部分 = 39 = 0x27
小数部分 = 0.0625 × 16 = 1 = 0x1
BRR值 = (39 << 4) | 1 = 0x271

我个人习惯直接用STM32CubeMX生成配置,它会自动算好。但如果你在做底层驱动或者Bootloader,手动算波特率是基本功。

避坑指南:我曾经在项目里直接用115200的波特率,结果通信距离超过2米后频繁丢包。后来降到38400,问题就解决了。波特率越高,对线路质量的要求也越高。长距离通信时,适当降低波特率是明智的选择。

4.3 STM32串口配置

STM32的USART外设功能很强大,支持同步模式、智能卡、IrDA等。但咱们做支付模块,最常用的就是异步模式,也就是标准的UART。

配置步骤其实就这几步:

  1. 使能时钟:GPIO时钟和USART时钟都要打开
  2. 配置GPIO:TX设为复用推挽输出,RX设为浮空输入
  3. 配置USART参数:波特率、数据位、停止位、校验位
  4. 使能USART:先使能发送器,再使能接收器
  5. 配置中断(可选):如果需要中断接收,就配置NVIC

下面是一个标准的初始化代码,我用的是HAL库:

void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

你看,代码其实很简洁。但有个细节我提醒一下:OverSampling这个参数,默认是16倍过采样。如果时钟频率不够高,可以改成8倍,这样能支持更高的波特率。

发送数据用这个:

HAL_UART_Transmit(&huart1, (uint8_t *)"Hello\r\n", 7, 1000);

接收数据用这个:

HAL_UART_Receive(&huart1, rx_buffer, 1, HAL_MAX_DELAY);

个人经验:我建议在实际项目中用中断方式接收,而不是轮询。轮询会阻塞CPU,浪费资源。中断接收配合环形缓冲区,是串口通信的黄金搭档。

4.4 串口助手调试

硬件配好了,代码写完了,怎么验证对不对?这时候就需要串口助手了。

常用的串口助手有:

  • SSCOM:经典款,轻量好用
  • Putty:支持串口和SSH,功能全面
  • SecureCRT:商业软件,功能强大
  • 串口调试助手:网上各种版本,选一个顺手的就行

调试步骤很简单:

  1. 用USB转串口模块连接STM32的TX和RX
  2. 打开串口助手,设置好波特率、数据位、停止位、校验位
  3. 打开串口,发送数据看接收区有没有回显
  4. 如果乱码,先检查波特率是否一致
  5. 如果没反应,检查TX和RX有没有接反

我遇到过最坑的一次:串口助手显示正常,但设备就是没反应。折腾了半天才发现,USB转串口模块的TX接了STM32的TX,RX接了RX——这是典型的"直连"错误。UART是交叉连接的,TX接RX,RX接TX。

调试口诀:TX接RX,RX接TX,GND接GND。波特率一致,帧格式一致,通信就通了。

还有一个实用技巧:用串口助手发送十六进制数据,可以精确控制每个字节。比如发送0xAA 0x55来测试通信链路,这两个字节的二进制是10101010和01010101,交替的0和1能暴露很多时序问题。

好了,UART串口通信的基础就这些。下一章咱们会讲怎么用串口和支付模块的读卡器通信,到时候会用到中断和DMA,那才是真正的实战。