Keil代码移植_轮询串口2
127.4.17Keil代码移植_轮询串口2
/*
* 整体目的:把上一节基于 USART1 的轮询收发工程迁移到 USART2,并通过外置 USB 转 TTL 模块验证通信。
* 整体设计:main.c 轮询接收 4 字节命令,收到 "LED1"/"LED2" 后翻转对应 LED;
* 随后用 USART2 周期发送格式化的温湿度示例和 printf 测试文本。
* 硬件依据:STM32F103ZET6 默认映射下,USART2_TX 位于 PA2,USART2_RX 位于 PA3;
* 板载 CH340C 接的是 USART1,因此 USART2 必须通过 PA2、PA3 和 GND 外接 USB 转 TTL 模块。
* 代码范围:以下列出本节需要理解和修改的项目层代码,来源于配套工程 KEIL/03UART2。
* CubeMX 模板版权头、空 USER CODE 区和未改动的中断文件从笔记中省略;
* 官方 stm32f1xx_hal_uart.c 仍须加入 Keil 工程,但它是 HAL 库依赖,不属于本节逐行改写的业务源码。
*/
/* ==================== Core/Inc/main.h:本节用到的 LED 引脚别名 ==================== */
#define LED2_Pin GPIO_PIN_5 /* 把 LED2 的引脚编号定义为 5,需与下方 GPIOE 端口组合成 PE5。 */
#define LED2_GPIO_Port GPIOE /* 指定 LED2 接在 GPIOE;调用 HAL GPIO 接口时与 LED2_Pin 配套使用。 */
#define LED1_Pin GPIO_PIN_5 /* 把 LED1 的引脚编号定义为 5,需与下方 GPIOB 端口组合成 PB5。 */
#define LED1_GPIO_Port GPIOB /* 指定 LED1 接在 GPIOB;串口命令最终会翻转这个物理引脚。 */
/* ==================== Core/Inc/uart.h:USART2 对外接口 ==================== */
#ifndef __UART_H__ /* 若尚未包含 uart.h,则进入头文件正文,防止重复定义。 */
#define __UART_H__ /* 记录 uart.h 已被包含,后续重复 include 时将跳过正文。 */
#include "main.h" /* 引入 HAL 类型、GPIO 定义及 Error_Handler() 等工程公共声明。 */
#include <stdio.h> /* 引入 sprintf() 声明,printf 宏需要先把格式化文本写入缓冲区。 */
extern UART_HandleTypeDef huart2; /* 声明 USART2 的 HAL 句柄;实体在 uart.c 中定义,其他文件可共享它。 */
extern uint8_t u_buf[256]; /* 声明 printf 重定向使用的 256 字节发送缓冲区,存储空间也在 uart.c。 */
/*
* 先用 sprintf() 把可变参数格式化到 u_buf,再按实际字符数阻塞发送。
* 这是课程使用的简化宏:它没有长度保护,格式化结果超过 255 字节会越界;
* 生产代码宜改用 snprintf(),并避免在中断或多任务并发环境中共享同一缓冲区。
*/
#define printf(...) HAL_UART_Transmit( /* 用同名宏替代普通 printf,使调用结果从 USART2 发出。 */ \
&huart2, /* 指向 USART2 句柄,HAL 据此访问 USART2 寄存器和状态。 */ \
(uint8_t *)u_buf, /* 把格式化后的字节缓冲区交给 HAL 发送接口。 */ \
sprintf((char *)u_buf, __VA_ARGS__), /* 写入格式化文本,并把返回的字符数作为待发送长度。 */ \
0xFFFFU) /* 最长等待 65535 ms;在此期间发送函数可能阻塞 CPU。 */
void MDK_USART2_UART_Init(void); /* 声明本工程封装的 USART2 初始化函数,供 main.c 调用。 */
#endif /* __UART_H__ */ /* 结束 uart.h 的头文件保护范围。 */
/* ==================== Core/Src/main.c:主流程和轮询收发 ==================== */
#include "main.h" /* 引入芯片 HAL 公共声明、引脚别名和 Error_Handler() 原型。 */
#include "gpio.h" /* 引入 MDK_GPIO_Init() 声明,用来初始化两路 LED。 */
#include "uart.h" /* 引入 huart2、printf 宏和 MDK_USART2_UART_Init() 声明。 */
#include <string.h> /* 引入 strcmp()、strlen() 和 memset() 的标准声明。 */
void SystemClock_Config(void); /* 提前声明系统时钟配置函数,函数实体位于本文件后部。 */
uint8_t UART_Recv[5]; /* 分配 5 字节接收区:4 字节命令后保留 1 字节全局初值 0 作为字符串结尾。 */
uint8_t DHT11_Buf[30]; /* 分配 30 字节发送区,保存本节写死的温湿度格式化示例。 */
int main(void) /* 程序入口;启动文件完成栈和数据段初始化后调用这里。 */
{ /* main() 函数体开始。 */
HAL_Init(); /* 复位 HAL 外设状态并建立 1 ms SysTick,为超时计时等 HAL 功能打基础。 */
SystemClock_Config(); /* 把系统时钟配置为 72 MHz,并设置 APB1/APB2 分频。 */
MDK_GPIO_Init(); /* 开启相关 GPIO 时钟,将 PB5、PE5 初始化为 LED 输出。 */
MDK_USART2_UART_Init(); /* 初始化 USART2 及 PA2/PA3,后续收发必须使用同一个 huart2。 */
while (1) /* 裸机程序持续轮询;循环不会主动退出。 */
{ /* 一次“接收命令并发送状态文本”的循环开始。 */
HAL_UART_Receive( /* 以阻塞轮询方式等待串口数据,返回值未保存。 */
&huart2, /* 指定从 USART2 接收,而不是上一工程的 USART1。 */
UART_Recv, /* 把收到的字节依次写入 5 字节接收数组的前 4 字节。 */
4U, /* 每次期望接收恰好 4 字节,正好容纳 "LED1" 或 "LED2"。 */
1000U); /* 最多等待 1000 ms;超时后仍继续执行后面的比较和发送。 */
if (strcmp("LED1", /* 比较接收字符串是否与命令 "LED1" 完全相同。 */
(char *)UART_Recv) == 0) /* strcmp() 返回 0 表示相等;强制转换用于匹配 char 指针参数。 */
{ /* “LED1”命令处理分支开始。 */
HAL_GPIO_TogglePin( /* 翻转 LED1 当前输出:高变低、低变高。 */
LED1_GPIO_Port, /* 选择 LED1 所在的 GPIOB 端口。 */
LED1_Pin); /* 选择端口中的 5 号引脚,即 PB5。 */
memset(UART_Recv, /* 命令处理后清空已接收的 4 个字符,避免下一轮重复匹配。 */
'\0', /* 用数值 0 填充,它也是 C 字符串的结束标志。 */
4U); /* 只清前 4 字节;第 5 字节作为结尾本来就是 0。 */
} /* “LED1”命令处理分支结束。 */
if (!strcmp("LED2", /* strcmp 相等时返回 0,逻辑非运算后变成真。 */
(char *)UART_Recv)) /* 将接收缓冲区按以 0 结尾的字符串与 "LED2" 比较。 */
{ /* “LED2”命令处理分支开始。 */
HAL_GPIO_TogglePin( /* 翻转 LED2 当前输出电平。 */
LED2_GPIO_Port, /* 选择 LED2 所在的 GPIOE 端口。 */
LED2_Pin); /* 选择端口中的 5 号引脚,即 PE5。 */
memset(UART_Recv, /* 清除刚刚处理的命令,避免残留内容影响下一次判断。 */
'\0', /* 以空字符覆盖原有字节。 */
4U); /* 清除本轮实际接收的 4 字节。 */
} /* “LED2”命令处理分支结束。 */
sprintf((char *)DHT11_Buf, /* 把浮点示例转换为可由串口发送的 ASCII 文本。 */
"H2:%.1f T2:%.2f\n", /* 湿度保留 1 位小数、温度保留 2 位小数,并在末尾换行。 */
30.1, /* 对应第一个 %.1f,输出为 30.1;这里是写死的演示值。 */
36.5); /* 对应第二个 %.2f,输出为 36.50;这里同样不是真实传感器数据。 */
HAL_UART_Transmit( /* 以阻塞轮询方式发送格式化后的温湿度文本。 */
&huart2, /* 使用 USART2 句柄,数据会从 PA2/USART2_TX 输出。 */
DHT11_Buf, /* 指向刚由 sprintf() 写好的发送缓冲区。 */
strlen((char *)DHT11_Buf), /* 计算字符串长度,不把末尾的 '\0' 发送出去。 */
1000U); /* 最多等待 1000 ms 完成发送。 */
printf("Hellow world wfeng %d!\n", /* 通过 uart.h 的宏再发送一行测试文本;Hellow 是配套源码原文。 */
123); /* 将整数 123 代入 %d。 */
} /* 本轮结束,立即返回 while 顶部再次等待 4 字节命令。 */
} /* main() 语法边界;正常运行不会从无限循环返回。 */
/* ==================== Core/Src/main.c:72 MHz 系统时钟 ==================== */
void SystemClock_Config(void) /* 配置振荡器、PLL、系统时钟及总线分频。 */
{ /* SystemClock_Config() 函数体开始。 */
RCC_OscInitTypeDef RCC_OscInitStruct = {0}; /* 清零振荡器配置结构体,避免未赋值字段含随机数据。 */
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /* 清零时钟树配置结构体。 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 本工程选择外部高速晶振 HSE。 */
RCC_OscInitStruct.HSEState = RCC_HSE_ON; /* 打开 HSE 振荡器。 */
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; /* HSE 送入 PLL 前不分频。 */
RCC_OscInitStruct.HSIState = RCC_HSI_ON; /* 同时保持内部高速振荡器开启。 */
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; /* 开启 PLL 倍频器。 */
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* 选择 HSE 作为 PLL 输入源。 */
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; /* 8 MHz HSE 乘 9 得到 72 MHz PLL 时钟。 */
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) /* 应用振荡器配置,并检查 HAL 返回状态。 */
{ /* 振荡器启动或 PLL 配置失败时进入异常分支。 */
Error_Handler(); /* 停止正常业务,避免在错误时钟下继续运行。 */
} /* 振荡器错误判断结束。 */
RCC_ClkInitStruct.ClockType = /* 指定下面要同时配置四类时钟。 */
RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | /* 包括 AHB/HCLK 与系统时钟 SYSCLK。 */
RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; /* 还包括 APB1/PCLK1 与 APB2/PCLK2。 */
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; /* 选择 72 MHz PLL 输出作为系统时钟。 */
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; /* AHB 不分频,因此 HCLK 为 72 MHz。 */
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; /* APB1 分频为 36 MHz,USART2 位于这条总线。 */
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; /* APB2 不分频,PCLK2 为 72 MHz。 */
if (HAL_RCC_ClockConfig( /* 应用系统与总线时钟配置。 */
&RCC_ClkInitStruct, /* 传入上面填写的时钟树结构体。 */
FLASH_LATENCY_2) != HAL_OK) /* 72 MHz 时使用 2 个 Flash 等待周期,并检查结果。 */
{ /* 时钟切换失败时进入异常分支。 */
Error_Handler(); /* 禁止程序带着未知时钟状态继续运行。 */
} /* 总线时钟错误判断结束。 */
} /* 系统时钟配置函数结束。 */
/* ==================== Core/Src/gpio.c:LED 输出初始化 ==================== */
#include "gpio.h" /* 引入 GPIO 初始化声明及 main.h 中的 LED 引脚别名。 */
void MDK_GPIO_Init(void) /* 配置本实验需要的 PB5、PE5 两路 LED 输出。 */
{ /* GPIO 初始化函数体开始。 */
GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 清零 GPIO 配置结构体,随后重复填写并用于两只 LED。 */
__HAL_RCC_GPIOE_CLK_ENABLE(); /* 开启 GPIOE 外设时钟,否则无法配置和驱动 PE5。 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启 GPIOA 时钟;USART2 的 PA2、PA3 也需要它。 */
__HAL_RCC_GPIOB_CLK_ENABLE(); /* 开启 GPIOB 外设时钟,否则无法配置和驱动 PB5。 */
HAL_GPIO_WritePin( /* 在切换为输出模式前先写入 LED2 的初始锁存电平。 */
LED2_GPIO_Port, /* 选择 GPIOE。 */
LED2_Pin, /* 选择 PE5。 */
GPIO_PIN_SET); /* 输出高电平;本板 LED 低电平点亮,因此上电先熄灭。 */
HAL_GPIO_WritePin( /* 同样预置 LED1 的初始输出电平。 */
LED1_GPIO_Port, /* 选择 GPIOB。 */
LED1_Pin, /* 选择 PB5。 */
GPIO_PIN_SET); /* 输出高电平,使 LED1 初始保持熄灭。 */
GPIO_InitStruct.Pin = LED2_Pin; /* 本轮准备配置 PE5。 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 设为通用推挽输出,可主动输出高、低电平。 */
GPIO_InitStruct.Pull = GPIO_PULLUP; /* 请求上拉;F1 输出模式主要由 ODR 驱动,初值仍应显式设置。 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* LED 无需高速翻转,使用低速输出可减小边沿干扰。 */
HAL_GPIO_Init( /* 把上述模式真正写入 PE5 的 GPIO 配置寄存器。 */
LED2_GPIO_Port, /* 选择 GPIOE 端口。 */
&GPIO_InitStruct); /* 传入 GPIO 配置结构体地址。 */
GPIO_InitStruct.Pin = LED1_Pin; /* 复用同一结构体,只把目标引脚改为 PB5。 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 将 PB5 配置为通用推挽输出。 */
GPIO_InitStruct.Pull = GPIO_PULLUP; /* 保留与 LED2 相同的上拉配置。 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 同样使用低速输出。 */
HAL_GPIO_Init( /* 把配置写入 PB5 对应寄存器。 */
LED1_GPIO_Port, /* 选择 GPIOB 端口。 */
&GPIO_InitStruct); /* 传入已更新的配置结构体地址。 */
} /* GPIO 初始化函数结束。 */
/* ==================== Core/Src/uart.c:USART2 参数、时钟和引脚 ==================== */
#include "uart.h" /* 引入 UART HAL 类型、huart2 对外声明和初始化函数原型。 */
uint8_t u_buf[256]; /* 定义 printf 宏使用的全局缓冲区,静态存储期使其初始值全为 0。 */
UART_HandleTypeDef huart2; /* 定义 USART2 HAL 句柄,保存实例、参数、状态及错误码。 */
void MDK_USART2_UART_Init(void) /* 填写 USART2 通信参数并让 HAL 完成外设初始化。 */
{ /* USART2 初始化函数体开始。 */
huart2.Instance = USART2; /* 绑定真实 USART2 寄存器基地址,这是从 USART1 迁移的关键项。 */
huart2.Init.BaudRate = 115200; /* 设置波特率为 115200 bit/s,串口助手必须使用相同数值。 */
huart2.Init.WordLength = UART_WORDLENGTH_8B; /* 每帧包含 8 个数据位。 */
huart2.Init.StopBits = UART_STOPBITS_1; /* 每帧使用 1 个停止位。 */
huart2.Init.Parity = UART_PARITY_NONE; /* 不使用奇偶校验位。 */
huart2.Init.Mode = UART_MODE_TX_RX; /* 同时开启发送器和接收器。 */
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 不使用 RTS/CTS 硬件流控,只接 TX、RX、GND。 */
huart2.Init.OverSampling = UART_OVERSAMPLING_16; /* 每位按 16 倍过采样,供接收判决和波特率生成使用。 */
if (HAL_UART_Init(&huart2) != HAL_OK) /* 初始化 UART;HAL 内部还会回调下方的 HAL_UART_MspInit()。 */
{ /* UART 参数无效或初始化失败时进入错误分支。 */
Error_Handler(); /* 停止正常流程,便于定位初始化错误。 */
} /* UART 初始化结果判断结束。 */
} /* USART2 参数初始化函数结束。 */
void HAL_UART_MspInit( /* HAL_UART_Init() 调用的底层硬件支持初始化回调。 */
UART_HandleTypeDef *uartHandle) /* 接收当前待初始化的 UART 句柄,可能对应不同 USART 实例。 */
{ /* MSP 初始化函数体开始。 */
GPIO_InitTypeDef GPIO_InitStruct = {0}; /* 清零临时 GPIO 配置结构体。 */
if (uartHandle->Instance == USART2) /* 只在本次句柄确实绑定 USART2 时执行以下配置。 */
{ /* USART2 专属底层配置开始。 */
__HAL_RCC_USART2_CLK_ENABLE(); /* 开启 APB1 上的 USART2 外设时钟;漏掉它时串口无法工作。 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启 GPIOA 时钟,以便配置 PA2 和 PA3。 */
GPIO_InitStruct.Pin = GPIO_PIN_2; /* 选择 PA2,它是默认映射下的 USART2_TX。 */
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; /* 将 PA2 设为复用推挽输出,由 USART2 而非普通 GPIO 驱动。 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; /* 使用高速输出能力以保证串口边沿质量。 */
HAL_GPIO_Init( /* 应用 PA2 的 TX 配置。 */
GPIOA, /* 选择 GPIOA 端口。 */
&GPIO_InitStruct); /* 传入 PA2 配置结构体地址。 */
GPIO_InitStruct.Pin = GPIO_PIN_3; /* 选择 PA3,它是默认映射下的 USART2_RX。 */
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; /* 将 PA3 配置为输入,让 USART2 接收外部模块的 TXD。 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /* 不启用内部上下拉,空闲高电平由对端串口发送器维持。 */
HAL_GPIO_Init( /* 应用 PA3 的 RX 配置。 */
GPIOA, /* PA3 同样属于 GPIOA。 */
&GPIO_InitStruct); /* 传入更新后的配置结构体地址。 */
} /* USART2 专属底层配置结束。 */
} /* MSP 初始化回调结束。 */
一、回顾上节内容
1.1 添加urt.c文件
手工 Keil 工程需要把 stm32f1xx_hal_uart.c 加入编译,并把业务层 uart.c 加入 Core/Src 分组。两者作用不同:
- HAL 驱动实现
HAL_UART_Init/Receive/Transmit; - 业务层文件定义
huart1/huart2、初始化参数和 GPIO MSP。
仅有头文件声明不能生成函数实体,链接时仍会报告 undefined symbol。
1.2 修改配置文件
stm32f1xx_hal_conf.h 使用条件编译控制外设模块:
#define HAL_UART_MODULE_ENABLED
#ifdef HAL_UART_MODULE_ENABLED
#include "stm32f1xx_hal_uart.h"
#endif
宏被注释时,相关声明和代码不参与编译,编辑器可能显示为灰色。启用实际使用的模块、关闭完全不用的模块,可以减少固件和编译负担,但必须保证依赖关系完整。
二、任务
本节将 USART1 轮询工程迁移到 USART2。软件和硬件都要同步改变:句柄、实例、初始化函数、外设时钟、引脚以及电脑连接通路。
2.1 关闭上一个工程
2.1.1 复制工程改名
复制 03UART1 并命名为 03UART2。打开工程后先确认 Keil 实际加载的是副本中的工程文件,避免在两个相似目录之间编辑错目标。
2.1.2 找到main函数
从 main.c 的初始化和所有 HAL 调用开始搜索 huart1、USART1、MDK_USART1_UART_Init,再检查 uart.h 和 uart.c。不能只批量替换数字,还要根据 STM32F103 的真实硬件映射逐项确认。
2.1.2.1 修改头文件
uart.h 中应声明:
extern UART_HandleTypeDef huart2;
void MDK_USART2_UART_Init(void);
若 printf 宏或 _write() 也通过 USART2 输出,其内部句柄必须使用 huart2。声明、定义和调用三处名称必须一致。
2.1.2.2 修改函数声明
主函数初始化调用改成 MDK_USART2_UART_Init(),所有轮询收发调用使用 &huart2。SystemClock_Config() 无需仅因切换串口而改名,但要确保 APB1 时钟满足 USART2 波特率计算。
2.1.2.3 修改urt.c文件
需要核对的标识符:
| USART1工程 | USART2工程 |
|---|---|
huart1 | huart2 |
USART1 | USART2 |
MDK_USART1_UART_Init | MDK_USART2_UART_Init |
__HAL_RCC_USART1_CLK_ENABLE | __HAL_RCC_USART2_CLK_ENABLE |
| PA9 / PA10 | PA2 / PA3 |
批量替换后仍要人工检查注释和 GPIO。配套源码的注释把 PA3 写成 USART3_RX,但实际配置和芯片复用功能均为 PA3/USART2_RX。
2.1.2.4 修改函数实体
USART2 初始化核心:
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
MSP 中开启 USART2 和 GPIOA 时钟,PA2 配置为复用推挽 TX,PA3 配置为浮空/无上下拉输入 RX。USART2 位于 APB1,HAL 使用 PCLK1 计算 BRR;USART1 位于 APB2,使用 PCLK2。
2.2 编译并查看效果
程序通过编译只说明符号和类型基本正确,不能证明实际线路已经连接。板载 Type-C/CH340C 仍连接 USART1 的 PA9/PA10,切换到 USART2 后,原 USB 口不会自动转接到 PA2/PA3。
2.3 问题所在
开发板原理图中的固定连接:
板载CH340C TXD ──> PA10 / USART1_RX
板载CH340C RXD <── PA9 / USART1_TX
PA2/PA3 只引到芯片和排针,没有接入板载 CH340C。因此需要一个外置 USB 转 TTL 模块。
2.4 硬件改变
USART2 使用 3.3 V TTL/CMOS 电平。外置模块应工作在兼容的 IO 电压,不能把 RS-232 正负电压直接接到 MCU。
如果开发板已经单独供电,通常只接 GND、TXD、RXD;不要再把模块 5 V 与板上电源盲目相连,避免两个电源互相反灌。
三、串口模块的连接
正确交叉连接:
USB转TTL模块 STM32F103
TXD ────────────────────> PA3 / USART2_RX
RXD <──────────────────── PA2 / USART2_TX
GND ───────────────────── GND
为什么 TX 要接 RX:发送端输出的串行位流必须进入接收端输入。为什么必须共地:TTL 是单端信号,接收器通过信号线相对 GND 的电压判断 0/1;没有共同参考电位,逻辑阈值就不可靠。
flowchart LR
PC[电脑USB] <-->|USB协议| CH340[外置CH340]
CH340 -- TXD --> PA3[PA3 USART2_RX]
PA2[PA2 USART2_TX] -- TXD --> CH340
CH340 --- GND[共同GND]
GND --- MCU[STM32地]
四、串口助手设置
插入外置模块后,系统会增加一个 COM 口。关闭旧串口连接,选择新 COM 口并设置 115200、8 数据位、无校验、1 停止位、无流控。
若能打开但没有数据,依次检查:
- TX/RX 是否交叉;
- GND 是否共地;
- 模块逻辑电压是否兼容;
- 代码是否真正调用
MDK_USART2_UART_Init(); - USART2 时钟和 GPIOA 时钟是否开启;
- 串口助手是否选中了外置模块对应的 COM 口。
五、修改时钟问题
USART2 必须执行 __HAL_RCC_USART2_CLK_ENABLE()。PA2、PA3 属于 GPIOA,因此还需 __HAL_RCC_GPIOA_CLK_ENABLE()。
若使用 USART3 的默认 PB10/PB11,则要开启 USART3 和 GPIOB 时钟;若使用重映射引脚,还需配置 AFIO 重映射。不能只根据“串口编号”机械更换 GPIO 端口。
六、投屏控制灯
外接线路正确后,串口助手发送 LED1 或 LED2,USART2 接收 4 字节,主循环比较命令并翻转 PB5/PE5 LED。发送端使用 huart2 后,格式化温湿度和 printf 输出也应从外置模块的 COM 口出现。
LED 电路仍是低电平有效;切换 USART 不会改变 LED 的 GPIO 或电气逻辑。
七、串口助手问题
浮点格式:
| 格式符 | 示例值 30.1 | 结果 |
|---|---|---|
%f | 30.1 | 30.100000 |
%.1f | 30.1 | 30.1 |
%.2f | 30.1 | 30.10 |
小数位数控制只影响文本格式,不影响 UART 波特率。Keil 使用的 C 库也可能默认裁剪浮点 printf;若 %f 无法输出,应检查所用运行库和链接选项,或用定点整数格式减少 Flash 占用。