1.

串口通信基础:USART与UART的异同
如果你刚开始接触STM32,可能会被USART和UART这两个词搞糊涂。
我刚开始学的时候也这样,总觉得它们差不多,但实际用起来才发现,细节上的差异还真不少。
简单来说,USART是“通用同步/异步收发器”,而UART是“通用异步收发器”。
名字里多了一个“S”(同步),这就是最核心的区别。
你可以把UART想象成一条单行线,数据只能一个接一个地按顺序通过,它不需要额外的时钟线来同步,完全依靠双方事先约定好的速度(波特率)来收发数据。
我们平时用串口调试助手和单片机通信,99%的情况都是这种异步模式。
而USART呢,它更像一条带有时钟信号的双车道,除了数据线,还可以多出一根时钟线(SCLK)。
发送方会通过这根线提供一个时钟节拍,告诉接收方“数据来了,准备采样”。
这种同步模式在需要高速、可靠传输的场景下很有用,比如连接某些特定的存储器或智能卡。
在STM32F103这颗经典的“国民MCU”上,它一共给了我们5个串口,其中USART1、USART2、USART3是功能齐全的同步/异步串口,而UART4和UART5是精简版的,只支持异步通信。
所以,当你看到原理图上标着UART4/5时,就别想着去用它的同步功能了,硬件上就没支持。
我刚开始画板子的时候没注意这点,想把一个需要同步时钟的模块接到UART4上,调了半天没反应,最后查数据手册才发现踩了坑。
那在实际项目中我们该怎么选呢?我的经验是,如果你只是进行普通的调试打印、连接GPS/蓝牙模块、或者跟传感器进行单向通信,用哪个都行,优先选择引脚布局方便的那个。
但如果你要驱动一个需要时钟信号的TFT屏幕控制器,或者进行高速的FPGA通信,那务必选择USART1/2/3,并配置为同步模式。
别小看这个选择,它直接决定了你项目的通信上限和稳定性。
2.
硬件连接与时钟配置:5个串口的差异详解
给STM32的串口接线,听起来就是接个TX和RX,但实际动手时,每个串口的引脚位置和时钟来源都不一样,一不留神就会出错。
我们先从最根本的时钟说起,这是很多新手会忽略的关键点。
STM32F103的时钟树有点复杂,但针对串口你只需要记住两句话:USART1挂在高速的APB2总线上,最高时钟频率72MHz;而USART2、USART3、UART4、UART5都挂在APB1总线上,最高频率36MHz。
这意味着,在计算相同波特率时,这两个总线上的分频系数会不同。
如果你用库函数,它帮你算好了,但如果你是自己写寄存器配置,这点必须搞清楚。
我见过有朋友把USART2的波特率计算参数直接套用到USART1上,结果通信速率差了一倍,数据全乱了。
具体到每个串口的引脚,虽然STM32有复用功能,但也不是随便哪个脚都能用的。
我整理了一个表格,你可以把它存下来,画原理图或者飞线的时候特别有用:
style="text-align:left">串口 | (默认) | style="text-align:left">时钟总线 | style="text-align:left">使能时钟的函数 | |
|---|---|---|---|---|
style="text-align:left">USART1 | style="text-align:left">PA9 | style="text-align:left">PA10 | style="text-align:left">APB2 | style="text-align:left"> |
style="text-align:left">USART2 | style="text-align:left">PA2 | style="text-align:left">PA3 | style="text-align:left">APB1 | style="text-align:left"> |
style="text-align:left">USART3 | style="text-align:left">PB10 | style="text-align:left">PB11 | style="text-align:left">APB1 | style="text-align:left"> |
style="text-align:left">UART4 | style="text-align:left">PC10 | style="text-align:left">PC11 | style="text-align:left">APB1 | style="text-align:left"> |
style="text-align:left">UART5 | style="text-align:left">PC12 | style="text-align:left">PD2 | style="text-align:left">APB1 | style="text-align:left"> |
注意看UART5,它的TX和RX分别在不同的端口(PC12和PD2),这在配置GPIO时钟时需要同时使能GPIOC和GPIOD的时钟,是个容易遗漏的点。
硬件连接上,除了TX接RX、RX接TX这个基本规则,GND共地是必须的,否则电平参考点不同,很可能收到一堆乱码。
对于长距离通信(超过1米),建议使用RS-485电平标准,这时需要在单片机引脚和外部线路之间加一个MAX3485这类转换芯片,而不是直接连接。
3.
核心配置三步走:GPIO、串口参数与中断
配置一个串口,无论是哪个编号,都逃不开下面这三个步骤。
我把它叫做“串口配置三板斧”,掌握了这个套路,任何一个串口你都能在5分钟内调通。
第一步:配置GPIO模式。
这里千万不能配错。
TX引脚是单片机输出数据给外部设备的,所以要设置为复用推挽输出(GPIO_Mode_AF_PP)。
而RX引脚是接收外部设备数据的,要配置为浮空输入(GPIO_Mode_IN_FLOATING)或者上拉输入。
我习惯用浮空输入,然后在外部电路上加一个上拉电阻,这样抗干扰能力更好一些。
速度一般选50MHz就行。
第二步:配置串口本体参数。
这部分是通过一个叫USART_InitTypeDef的结构体来完成的。
你需要设置几个关键参数:
- USART_BaudRate(波特率):比如9600,115200。
要和通信对方严格一致。
- USART_WordLength(字长):通常选8位数据位。
如果用了奇偶校验,则需要选9位。
- USART_StopBits(停止位):最常用的是1位停止位。
- USART_Parity(校验位):根据需求选择奇校验、偶校验或者无校验。
工业通信中为了可靠,常用偶校验。
- USART_Mode(模式):收
USART_Mode_Rx和发USART_Mode_Tx,通常用“或”运算同时开启。 - USART_HardwareFlowControl(硬件流控):除非通信双方都支持并连接了RTS/CTS线,否则一般禁用。
第三步:配置中断(如果需要)。
我们当然可以用查询方式不断去读状态标志位,但那太浪费CPU资源了。
中断才是高效的方式。
你需要做两件事:1.
配置NVIC(嵌套向量中断控制器),设置好这个串口中断的优先级;2.
使能串口特定的中断源,比如接收中断USART_IT_RXNE(接收寄存器非空)。
下面这段代码是配置USART1的完整示例,我加上了详细的注释。
USART2/3和UART4/5的代码结构完全一样,只需要把USART1换成USART2、UART4等,同时把时钟使能函数和引脚定义改对就行。
void。USART1_Config(void)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA
ENABLE);
配置串口工作参数:115200波特率,8位数据,无校验,1位停止位
USART_InitStructure.USART_BaudRate
=
USART_InitStructure.USART_WordLength
=
USART_InitStructure.USART_StopBits
=
USART_InitStructure.USART_Parity
=
USART_InitStructure.USART_HardwareFlowControl
=
USART_HardwareFlowControl_None;
=
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
设置优先级分组
NVIC_InitStructure.NVIC_IRQChannel
=
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority
=
NVIC_InitStructure.NVIC_IRQChannelSubPriority
=
NVIC_InitStructure.NVIC_IRQChannelCmd
=
NVIC_Init(&NVIC_InitStructure);
USART_IT_RXNE,
多场景应用实战:从调试打印到多机通信
光会配置还不够,关键得知道怎么用。
STM32的5个串口就像你手下的5个兵,用好了能同时处理好几摊子事。
场景一:调试打印(USART1)。
这是最经典的用法。
通过重写
fputc函数,把printf绑定到串口上,就能像在电脑上编程一样方便地打印变量值、调试信息。我强烈建议你把USART1留给调试用,因为很多开发板的USB转串口默认就接在USART1上,方便。
代码片段如下:
//重定向printf到USART1
while(USART_GetFlagStatus(USART1,
USART_FLAG_TXE)
printf("系统启动成功,当前电压:%d
mV\r\n",
voltage);
场景二:连接无线模块(USART2/3)。
比如接一个蓝牙模块(HC-05)或者Wi-Fi模块(ESP8266)。
这类模块通常也是AT指令控制,你可以用另一个串口专门和它通信。
例如,用USART2以9600波特率连接蓝牙模块,实现手机APP控制。
这时候,中断服务函数里就不是简单回显了,而是需要解析特定的指令帧。
场景三:工业传感器数据采集(带校验的UART)。
工业环境噪声大,通信需要校验。
比如用一个UART4以4800波特率、偶校验方式,连接一个温湿度传感器。
在配置时,需要把
USART_WordLength设为USART_WordLength_9b(8位数据+1位校验位),USART_Parity设为USART_Parity_Even。在中断接收时,如果校验出错,硬件会置位标志位,你可以丢弃这包错误数据,并请求重发。
场景四:多机主从通信(RS-485总线)。
这是USART一个非常重要的应用。
通过一个USART(比如USART3)连接一个MAX3485芯片,就可以挂接多个485设备。
你需要控制MAX3485的收发使能引脚(DE/RE),在发送前拉高,发送完成后拉低,切换回接收状态。
软件上要注意增加帧头帧尾、地址识别和CRC校验,确保总线上的多个设备互不干扰。
我曾经用这个方式做过一个楼宇灯光控制系统,一个主机带30多个从机,非常稳定。
5.
避坑指南与高级技巧
最后,分享几个我踩过坑才总结出来的经验,能帮你节省大量调试时间。
第一个坑:中断服务函数名写错。
这是最诡异的问题,代码编译没问题,但中断就是进不去。
记住,中断服务函数的名字是固定的,必须和启动文件里定义的一模一样。
USART1的是
USART1_IRQHandler,UART4的是UART4_IRQHandler。写错一个字母,函数就变成了一个普通函数,中断发生时永远不会被调用。
第二个坑:波特率误差导致数据错误。
特别是当你使用非标准的波特率时。
STM32的波特率发生器是通过一个16倍或8倍的分频系数来算的,有时算出来不是整数,就会有误差。
公式是:
波特率=
USARTDIV)
误差最好控制在2%以内。
如果你发现低波特率(如9600)正常,高波特率(如115200)就丢数据,很可能是时钟源(HSE/HSI)精度不够或者波特率误差太大。
用示波器量一下实际波形最直观。
第三个技巧:使用DMA+串口解放CPU。
当你需要高速、连续地传输大量数据(比如通过串口发送摄像头图像)时,一定要用DMA。
配置好后,数据搬运完全由DMA控制器完成,CPU只需要处理打包好的数据块,效率极高。
配置步骤是:1.
初始化串口;2.
初始化DMA通道,设置源地址(内存)、目标地址(串口数据寄存器)、数据长度;3.
使能串口的DMA发送请求。
第四个技巧:利用空闲中断接收不定长数据。
我们常用的
USART_IT_RXNE中断是每收到一个字节就触发一次。如果对方发送的是一串不定长的数据,处理起来很麻烦。
这时可以同时使能空闲中断(USART_IT_IDLE)。
当一帧数据发送完毕,总线出现一个字节的空闲高电平时间时,就会触发此中断。
在空闲中断里,你可以知道一包数据已经收完了,然后统一处理。
这是处理Modbus、自定义协议帧的利器。
//USART_IT_RXNE
USART_ClearITPendingBit(USART1,
USART_IT_RXNE);
USART_ClearITPendingBit(USART1,
USART_IT_IDLE);
}
把上面这些内容吃透,STM32F103的串口对你来说就再也没有秘密了。
关键就是多动手,从一个简单的回显实验开始,逐步增加复杂度,最终你会发现自己能轻松驾驭这5个串口,让它们在你的项目中各司其职,协同工作。


