CubeMX部分
摘要:本文以 STM32F407ZGT6 为例,完整记录了使用 STM32CubeMX 配置 USART1 串口通信的全过程。内容涵盖原理图引脚确认(PA9 为 TX、PA10 为 RX)、CubeMX 中使能 USART1 并设置波特率 115200、8 位数据位、1 位停止位、无校验等参数,以及过采样率 16 倍采样的作用与原理。随后给出库函数层面的 usart.c 配置流程(开启时钟、GPIO 复用推挽输出、连接串口、写入寄存器),并在 main.c 中通过 HAL_UART_Transmit / HAL_UART_Receive 实现串口收发,配合 if 与 switch 两种写法解析上位机指令控制 LED 亮灭,适合 STM32 串口入门参考。
先声明一下:我使用了别人的图片,如果介意看到了,联系我后会删除与修改。
STM32F407ZGT6 原理图
由上面两图可以看出:
USART1 接在 PA9 和 PA10 这两个引脚上。
在图形化界面中可以清晰地看到:
PA10 — RX(接收)
PA9 — TX(发送)
这里可以看到,USART1 已经被标上了 √。
先看到上面,对应着:
第一个参数对应着波特率(常见的有 115200、9600)。
数据位长度(Word Length)对应着数据位的位数。
由于一个字节要 8 个 bit 位,所以这里用 8 bits。
常见的还有 9 bits,这个多一位一般是给奇偶校验位的(只能一定程度上减少出错的风险)。
同时起始位和停止位各自都有一位(一般情况)。
起始位:低电平。
停止位:高电平。
其中低位先行与小端字节序很像。
校验位(Parity):这里选择无校验。
停止位(Stop Bits):给一位。**********
其他的目前不需要(这里默认会配置LED部分哈)
然后打开vscode
库函数代码部分
找到 usart.c 文件
从中间一段看起
先设置外设 - USART1
配置通信速率 - 115200 bits/s
设置数据位长度(Word Length)- 8 位
停止位(Stop Bits)- 一位
校验位(Parity)- 无
模式(Mode)- 发送和接收
硬件流控制(Hardware Flow Control)- 不需要
过采样率(OverSampling)- 16 倍
判断是否配置成功 - 否则进入错误中断部分,防止出现未知错误。
过采样率的作用
在一个数据位之间连续采样 16 次,其中第 8、9、10 次如果全是高电平,则认为是 1;如果全是低电平,则认为是 0。
特别地,如果第 8、9、10 次采样中出现不一致的电平,则采用「少数服从多数」的原则:
- 1、1、0 → 1
- 0、0、1 → 0
其他组合同理。不过一旦出现这种采样不一致的情况,通常说明链路存在问题,例如波特率不匹配、地线没有共地、走线太长或周围有强电磁干扰。
这里只解释重要的参数,其他的自行了解。
模式(Mode)- 复用推挽输出模式。
上、下拉(Pull-up/Pull-down)- 无。
速度(Speed)- 非常高(减少信号的上升、下降沿时间,保证通信质量)。
配置引脚的复用功能编号(Alternate Function)- 将 PA9、PA10 连接到 USART1 外设。
配置流程可以概括为:
开启时钟(GPIOA、USART1)→ PA9、PA10 初始化为复用推挽输出 → 连接串口外设 → 写入寄存器完成配置。
核心配置(main.c)
下面先用一张流程图把 main.c 里串口收发的完整判断逻辑串起来,再逐段看代码:
receive_data是一个长度为 2 的uint8_t数组,两个字节分工明确:receive_data[0]用来选择要操作哪一颗 LED('0'对应 LED1,'1'对应 LED2),receive_data[1]用来决定这颗 LED 是亮还是灭('0'表示关,'1'表示开)。所以上位机只要发两个字符,例如"01",就表示「选中 LED1 并点亮」,"10"则表示「选中 LED2 并熄灭」。
接下来补充代码部分,在使用串口部分时要先验证一下哦~
方法一代码:
// char message[] = "Hello world";staticuint8_treceive_data[2];/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while(1){/* *message需要强制转换为uint*_t类型的指针,否则会报错 */// HAL_UART_Transmit(&huart1, (uint8_t*)message, strlen(message), 100);HAL_UART_Transmit(&huart1,receive_data,2,HAL_MAX_DELAY);HAL_UART_Receive(&huart1,receive_data,2,HAL_MAX_DELAY);GPIO_PinState state=GPIO_PIN_SET;if(receive_data[1]=='0'){state=GPIO_PIN_RESET;//PF9 -- PF10}if(receive_data[0]=='0'){HAL_GPIO_WritePin(GPIOF,LED1_Pin,state);}elseif(receive_data[0]=='1'){HAL_GPIO_WritePin(GPIOF,LED2_Pin,state);}/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}方法二代码:用 if 语句实现
方法一直接在while(1)里用HAL_GPIO_WritePin写引脚电平,方法二则把「选 LED + 定亮灭」拆成LED1_ON()、LED1_OFF()这类宏或函数,可读性更好。下面先给出 if 嵌套的写法:
/* *方法二 *0一般是关 *1一般是开 */if(receive_data[1]=='0'){if(receive_data[0]=='0'){LED1_OFF();}elseif(receive_data[0]=='1'){LED2_OFF();}}elseif(receive_data[1]=='1'){if(receive_data[0]=='0'){LED1_ON();}elseif(receive_data[0]=='1'){LED2_ON();}}switch 语句实现(更简洁)
/* *方法二 *switch语句实现 *0一般是关 *1一般是开 */switch(receive_data[1]){case'0':{if(receive_data[0]=='0'){LED1_OFF();}elseif(receive_data[0]=='1'){LED2_OFF();}break;}case'1':{if(receive_data[0]=='0'){LED1_ON();}elseif(receive_data[0]=='1'){LED2_ON();}break;}default:break;}这里也可以把receive_data[0]的判断再拆成一层 switch,代码会更整齐。
结语
本文从 CubeMX 的引脚与参数配置讲起,一路走到main.c里的串口收发与 LED 控制,核心其实就是三件事:引脚复用配对(PA9/PA10)、串口参数一致(115200-8-N-1)、收发数据按约定解析。
如果后续想继续扩展,可以尝试:
- 用串口中断或 DMA 替代阻塞式
HAL_UART_Receive,避免主循环被卡住; - 增加帧头帧尾或校验位,让指令解析更可靠;
- 把 LED 控制封装成独立模块,方便移植到其他工程。
本人水平有限,只是把了解到的一点知识整理出来,欢迎各位大佬在评论区指正。下一篇见!