一、实验目的
- 掌握STM32开发板串口(USART1)的初始化与配置方法,理解串口通信的基本原理。
- 学会使用FlyMCU工具向STM32开发板烧录程序,掌握开发板下载模式与运行模式的切换方法。
- 实现Qt自制串口助手与STM32开发板的双向通信,验证串口收发功能的正确性。
- 通过Qt发送指令控制STM32板载LED灯(D0=PB5、D1=PE5)的亮灭,完成软硬件结合的功能测试。
- 排查串口通信中的常见问题(如端口占用、乱码、指令无响应等),提升问题解决能力。
二、实验原理
2.1串口通信原理
串口通信是一种异步串行通信方式,通过TX(发送端)和RX(接收端)引脚实现数据双向传输,需约定统一的通信参数(波特率、数据位、校验位、停止位),否则会出现通信失败或乱码。本实验采用USART1(STM32)与USB-TTL模块通信,波特率设为115200,数据位8位,无校验位,停止位1位(115200 8N1)。
2.2 STM32串口与LED控制原理
STM32F103ZET6开发板的USART1对应引脚为PA9(TX)和PA10(RX),通过配置GPIO和USART寄存器,实现串口数据的发送与接收;板载LED灯D0对应PB5引脚、D1对应PE5引脚,通过配置GPIO为推挽输出模式,控制引脚电平高低(低电平点亮、高电平熄灭),实现LED灯的亮灭控制。
STM32通过串口中断接收Qt发送的指令(0、1、2),根据指令执行对应的LED控制逻辑,并将执行结果回显给Qt串口助手,实现双向交互。
2.3工具与环境原理
FlyMCU用于向STM32烧录程序,需通过手动切换开发板至下载模式(BOOT0=1)才能完成程序烧录;Qt串口助手用于与STM32通信,需使用64位MinGW编译器编译,避免串口识别失败,同时需关闭其他占用串口的工具(如FlyMCU),防止端口冲突。
三、实验环境
3.1硬件环境
- STM32F103ZET6开发板(板载LED灯D0=PB5、D1=PE5)
- USB-TTL模块(带CH340芯片)
- 串口交叉线(或杜邦线)
- USB数据线(用于供电和通信)
- 电脑一台(Windows 11 64位系统)
3.2软件环境
- Keil uVision5(用于编写、编译STM32程序,生成.hex文件)
- FlyMCU(用于向STM32开发板烧录程序)
- Qt Creator 5.14.2(带64位MinGW编译器,用于运行自制串口助手)
- CH340串口驱动(CH34ISER.EXE,与电脑系统版本匹配)
四、实验步骤
4.1实验环境搭建
- 安装CH340串口驱动:运行CH34ISER.EXE,根据电脑操作系统版本选择对应驱动进行安装,安装完成后,将USB-TTL模块插入电脑,在设备管理器中查看串口(COMx),确认驱动安装成功(无黄色感叹号)。
- 硬件接线:使用杜邦线连接USB-TTL模块与STM32开发板,接线原则为“TX接RX、RX接TX、GND共地”,具体接线如下:
注意:严禁将USB-TTL的5V引脚与STM32连接,避免烧毁开发板。
- USB-TTL TX → STM32 PA10(USART1 RX)
- USB-TTL RX → STM32 PA9(USART1 TX)
- USB-TTL GND → STM32 GND
- USB-TTL 3.3V → STM32 3.3V(为开发板供电)
- Qt串口助手准备:确保Qt Creator使用64位MinGW编译器编译串口助手项目,修改串口接收(readData)和发送(on_btn_send_clicked)函数,实现控制字符过滤、自动换行和指令发送功能,确保串口参数可设置为115200 8N1。
4.2 STM32程序编写与编译(这里注明一下,虽然我的keil5代码是中文注释,但是会被识别成乱码)
- 打开Keil uVision5,创建STM32F103ZET6项目,添加main.c、usart.h、usart.c三个文件,复制以下正确代码(适配D0=PB5、D1=PE5):
- main.c:实现LED初始化、串口初始化,开机发送欢迎信息和心跳包。
#include "stm32f10x.h" #include "usart.h" // 初始化D0(PB5)、D1(PE5)两个板载灯 void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 开启GPIOB和GPIOE的时钟(必须开!) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); // ========== 初始化D0:PB5 ========== GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // ========== 初始化D1:PE5(修正了之前的错误!) ========== GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOE, &GPIO_InitStructure); // 这里必须是GPIOE! // 默认全部熄灭(高电平熄灭,低电平点亮) GPIO_SetBits(GPIOB, GPIO_Pin_5); GPIO_SetBits(GPIOE, GPIO_Pin_5); } int main(void) { LED_Init(); USART1_Init(); // 函数名正确,无波浪线 // 开机欢迎信息(中文正常显示,无乱码) USART1_SendString("==================================================\r\n"); USART1_SendString(" STM32 软硬件结合测试就绪 (D0=PB5 D1=PE5)\r\n"); USART1_SendString(" 发送 1 → D0亮 \r\n"); USART1_SendString(" 发送 2 → D1亮 \r\n"); USART1_SendString(" 发送 0 → 全部灭\r\n"); USART1_SendString("==================================================\r\n"); while(1) { // 每秒发送心跳包 USART1_SendString("STM32 运行中...\r\n"); Delay_ms(10000); } }- usart.h:声明串口初始化、字符串发送、延时函数。
#ifndef __USART_H #define __USART_H #include "stm32f10x.h" void USART1_Init(void); void USART1_SendString(char *str); void Delay_ms(uint32_t ms); #endif- usart.c:实现串口初始化、中断服务函数(接收指令、控制LED、回显)、字符串发送和延时功能。
#include "usart.h" // 串口中断服务函数,必须正确声明 void USART1_IRQHandler(void); void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 开启GPIOA和USART1的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // ========== 串口引脚初始化 ========== // PA9: USART1_TX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // PA10: USART1_RX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // ========== 串口参数配置(115200 8N1,和Qt完全一致) ========== USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // ========== 串口中断配置(必须开!否则收不到指令) ========== NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); // 使能接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 使能串口 USART_Cmd(USART1, ENABLE); } // ========== 串口中断:接收指令+控制灯+回显 ========== void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { uint8_t ch = USART_ReceiveData(USART1); // 1. 回显给Qt,让Qt收到发送的内容 USART_SendData(USART1, ch); // 2. 指令控制灯 if(ch == '0') { GPIO_SetBits(GPIOB, GPIO_Pin_5); // D0灭 GPIO_SetBits(GPIOE, GPIO_Pin_5); // D1灭 USART1_SendString(" → 全部灯已灭\r\n"); } else if(ch == '1') { GPIO_ResetBits(GPIOB, GPIO_Pin_5); // D0亮 GPIO_SetBits(GPIOE, GPIO_Pin_5); // D1灭 USART1_SendString(" → D0(PB5) 已亮\r\n"); } else if(ch == '2') { GPIO_ResetBits(GPIOE, GPIO_Pin_5); // D1亮 GPIO_SetBits(GPIOB, GPIO_Pin_5); // D0灭 USART1_SendString(" → D1(PE5) 已亮\r\n"); } } } // ========== 串口发送字符串函数 ========== void USART1_SendString(char *str) { while(*str) { USART_SendData(USART1, *str++); // 等待发送完成 while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); } } // ========== 毫秒延时函数(72MHz系统时钟下,1ms延时) ========== void Delay_ms(uint32_t ms) { uint32_t i, j; for(i = 0; i < ms; i++) for(j = 0; j < 7200; j++); }
- 编译项目:点击Keil工具栏“魔法棒”,在Output选项中勾选“Create HEX File”,点击“Build”编译项目,确保编译结果显示“0 errors, 0 warnings”,生成.hex文件(位于Objects文件夹中)。
4.3 STM32程序烧录
- 关闭所有占用串口的工具(Qt、其他串口助手等),打开FlyMCU软件。
- FlyMCU参数设置:
- 串口号:选择设备管理器中CH340对应的COM口(非虚拟串口COM1/COM2)。
- 波特率:设置为115200。
- 芯片型号:选择“STM32F103ZET6”。
- HEX文件:点击“打开”,选择Keil生成的.hex文件。
- 其他参数:数据位8位、无校验位、停止位1位,勾选“DTR/RTS自动复位”(辅助复位开发板)。
- 开发板进入下载模式:
- 先断开开发板电源(拔掉USB线)。
- 按住开发板上的BOOT0按键不放,再按住RESET按键不放。
- 先松开RESET按键,等待1-2秒后,再松开BOOT0按键,此时开发板进入下载模式。
- 烧录程序:点击FlyMCU中的“开始编程”,等待烧录完成,烧录日志显示“烧录完成!校验成功!”即为烧录成功。
- 烧录完成后,关闭FlyMCU(释放串口),将开发板切回正常运行模式:按住RESET按键,松开BOOT0按键,再松开RESET按键,开发板重启并运行烧录的程序。
4.4软硬件结合功能测试
- 打开Qt串口助手,进行串口设置:
点击“打开串口”,确认串口打开成功(状态显示“串口已打开”)。
- 串口号:选择CH340对应的COM口(与FlyMCU中选择的一致)。
- 波特率:115200,数据位8位,校验位无,停止位1位。
- 验证开发板自动发送功能:Qt接收区应每秒收到开发板发送的心跳包“STM32 运行中...”,同时收到开机欢迎信息,说明串口通信正常。
- 验证LED灯控制功能:
- 在Qt发送框输入“1”,点击发送,观察开发板D0灯(PB5)是否点亮,Qt接收区是否收到回显“1 → D0(PB5) 已亮”。
- 在Qt发送框输入“2”,点击发送,观察开发板D1灯(PE5)是否点亮,Qt接收区是否收到回显“2 → D1(PE5) 已亮”。
- 在Qt发送框输入“0”,点击发送,观察开发板两个LED灯是否全部熄灭,Qt接收区是否收到回显“0 → 全部灯已灭”。
- 异常测试:验证串口断开、端口占用等异常情况的处理,确保Qt能正确提示“串口已断开”,端口被占用时能提示“无法打开串口”。
五、实验结果与分析
5.1实验结果
- 环境搭建结果:CH340驱动安装成功,设备管理器中能正常识别串口;硬件接线正确,开发板供电正常;Qt串口助手能正常打开,64位编译器编译无错误。
- 程序烧录结果:通过FlyMCU手动下载模式,成功将程序烧录至STM32开发板,烧录日志显示“校验成功”,开发板能正常重启运行。
- 串口通信结果:Qt串口助手能正常接收开发板发送的欢迎信息和心跳包,无乱码、不刷屏;开发板能正常接收Qt发送的指令,无丢失。
- LED控制结果:发送“1”时D0灯亮、D1灯灭;发送“2”时D1灯亮、D0灯灭;发送“0”时两灯全部熄灭,回显信息正确,符合实验预期。
5.2结果分析
- 串口通信成功的关键:通信参数(波特率、数据位等)完全一致,接线正确(TX与RX交叉连接),无端口占用,Qt使用64位编译器编译。
- LED控制无响应的排查:本次实验中初期LED无响应,原因是PE5引脚初始化错误(误配置为GPIOB),修正代码后,LED控制功能正常,说明GPIO初始化的正确性直接影响硬件控制效果。
- 烧录失败的排查:初期烧录卡壳,原因是开发板未手动进入下载模式(BOOT0未接高电平),切换至下载模式后,烧录成功,说明开发板模式切换是烧录的关键步骤。
- 乱码问题的解决:Qt接收区出现箭头乱码,是因为未过滤串口通信中的控制字符(\r、\n),修改readData函数过滤控制字符后,乱码问题解决。
六、实验注意事项
- 硬件接线时,严格遵循“TX接RX、RX接TX、GND共地”原则,严禁将5V电压接入STM32开发板,避免烧毁芯片。
- 烧录程序时,必须先关闭所有占用串口的工具(如Qt、其他串口助手),否则会出现“端口被占用”错误;开发板必须手动进入下载模式,否则烧录失败。
- Qt串口助手必须使用64位MinGW编译器编译,Win11系统下32位编译器无法正常识别串口,导致串口打不开。
- STM32程序编译时,需确保函数名拼写正确、GPIO初始化正确(对应LED灯引脚),否则会出现串口通信失败或LED控制无响应。
- 串口通信时,Qt与STM32的通信参数必须完全一致(115200 8N1),参数不匹配会导致乱码或无法接收数据。
- 烧录完成后,需将开发板切回正常运行模式,否则开发板会一直处于下载模式,无法运行程序。
- 实验过程中,若出现串口无法打开、数据乱码等问题,优先排查端口占用、接线、通信参数和代码错误。
七、实验总结
本次实验完成了STM32与Qt串口助手的软硬件结合测试,成功实现了串口双向通信和LED灯控制功能。通过实验,掌握了STM32串口初始化、程序烧录的方法,理解了串口通信的原理,学会了排查串口通信中的常见问题(端口占用、乱码、指令无响应等)。
实验过程中,遇到了LED引脚初始化错误、烧录模式切换不当、串口乱码等问题,通过修正代码、严格按步骤操作,均成功解决。本次实验验证了Qt自制串口助手的功能正确性,实现了教材要求的软硬件结合测试目标,为后续STM32串口通信的实际应用奠定了基础。同时,也认识到实验操作的严谨性至关重要,任何一个细节错误(如接线错误、参数不匹配)都会导致实验失败,需耐心排查、逐步验证。