目录
- 1.屏蔽870警告
- [2. 串口调试工具设置](#2. 串口调试工具设置)
- 3.STM32串口对应引脚
- 4.debug代码理解
-
- [4.1 debug_gpio配置](#4.1 debug_gpio配置)
-
- [4.1.1 TX引脚GPIO模式配置理解](#4.1.1 TX引脚GPIO模式配置理解)
- [4.1.2 RX引脚GPIO模式配置理解](#4.1.2 RX引脚GPIO模式配置理解)
- [4.2 波特率配置](#4.2 波特率配置)
- 5.发送数据代码理解
1.屏蔽870警告

--diag_suppress=870 直白讲解(Keil5 专用)
1、870 警告是什么?
#870-D:invalid multibyte character sequence(无效多字节字符)
出现原因:代码里写了中文注释、中文字符串(UTF-8 汉字),Keil 编译器不认中文编码,编译就疯狂弹窗警告 870。
只是警告(Warning),不是报错,不影响程序编译、下载运行。
2、加--diag_suppress=870的作用
放到工程 Options→C/C++→Misc Controls 框里:
全局屏蔽整个工程所有 870 号警告,编译不再弹出中文相关的冗余警告。
不加这个的坏处:
代码里只要有中文注释,编译框疯狂刷屏一堆#870-D警告,一堆无用信息,容易把真正有用的报错、重要警告淹没,不好排查 BUG。
3、两种屏蔽写法区别
全局(你截图这种):--diag_suppress=870
工程所有文件全部屏蔽 870 警告,一劳永逸,整项目生效。
单文件屏蔽:代码开头写 #pragma diag_suppress 870
只对当前.c 文件屏蔽 870,别的文件照常提示警告。
4、一句话总结
加它 = 关掉「中文注释 / 中文字符串」带来的烦人 870 警告,编译窗口变干净,方便找真正错误。
补充:不想屏蔽的替代方案
把文件编码改成 GB2312,Keil 就能正常识别中文,自然不会报 870 警告,但中文跨设备打开容易乱码,所以大部分工程师直接加这条屏蔽指令。
2. 串口调试工具设置

勾选HEX,日志模式,清除接收区,打开串口。
3.STM32串口对应引脚
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
USART引脚在STM32F103ZET6芯片具体分布见表STM32F103ZET6芯片的
USART引脚。
| 引脚 | APB2总线 USART1 | APB1总线 USART2 | APB1总线 USART3 | APB1总线 UART4 | APB1总线 UART5 |
|---|---|---|---|---|---|
| TX | PA9 | PA2 | PB10 | PC10 | PC12 |
| RX | PA10 | PA3 | PB11 | PC11 | PD2 |
| SCLK | PA8 | PA4 | PB12 | ||
| nCTS | PA11 | PA0 | PB13 | ||
| nRTS | PA12 | PA1 | PB14 |
STM32F103ZET6系统控制器有三个USART和两个UART,其中USART1和时钟来源于APB2总线时钟,其最大频率为72MHz,其他四个的时钟来源于APB1总线时钟,其最大频率为36MHz,UART只是异步传输功能,所以没有SCLK、nCTS和nRTS功能引脚。
4.debug代码理解
4.1 debug_gpio配置
c
void DEBUG_USART_PinConfig(void)
{
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(DEBUG_TX_GPIO_CLK_PORT, ENABLE); //开启GPIOA 端口时钟
// 配置引脚:选择GPIOA的Pin9
GPIO_InitStruct.GPIO_Pin = DEBUG_TX_GPIO_PIN;
// 配置输出速度:50MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
// 配置工作模式:推挽复用
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(DEBUG_TX_GPIO_PORT, &GPIO_InitStruct);
}

4.1.1 TX引脚GPIO模式配置理解
对串口发送的GPIO引脚模式配置:推挽复用输出
推挽复用输出模式(GPIO)通俗理解
一、一句话说清它是干嘛的
推挽复用输出模式 = 让 GPIO 引脚,交给片上外设(比如串口、SPI、I2C、定时器)来控制,而不是普通的高低电平输出。
它的核心是 "复用":这个引脚不再受普通 GPIO 控制,而是被指定给某个外设专用,用来输出外设信号(比如串口的 TX 发送脚、定时器的 PWM 输出脚)。
二、什么时候设置这个模式?
当你需要用引脚的 外设功能(复用功能)时,就必须设置成推挽复用输出模式。
最常见的场景:
串口发送(TX 引脚)
比如你之前用的 USART1 的 PA9(TX)、USART2 的 PA2(TX),这些引脚要发送串口数据,就必须配置成推挽复用输出模式,让串口外设来控制引脚输出高低电平时序。
定时器 PWM 输出
比如定时器的通道引脚,要输出 PWM 波控制电机、LED 呼吸灯,必须配置成推挽复用输出模式,让定时器外设来控制引脚电平。
SPI、I2S、CAN 等通信的输出引脚
比如 SPI 的 SCK、MOSI 引脚,需要由 SPI 外设控制输出时钟和数据,就用这个模式。
三、和普通推挽输出的区别(关键)
| 模式 | 控制权 | 用途 |
|---|---|---|
| 普通推挽输出(GPIO_Mode_Out_PP) | CPU 直接控制 | 输出普通高低电平,比如点亮 LED、控制继电器 |
| 推挽复用输出(GPIO_Mode_AF_PP) | 片上外设控制 | 输出外设信号,比如串口 TX、PWM、SPI 时钟等 |
四、举个你熟悉的例子(串口 TX 引脚配置)
以你之前用的 USART1 的 PA9(TX)为例,配置步骤就是:
打开 GPIOA 和 USART1 的时钟
把 PA9 设置为 推挽复用输出模式(GPIO_Mode_AF_PP)
配置 PA9 的复用功能为 USART1_TX
初始化 USART1
此时,PA9 引脚就不再受 GPIO 控制,而是由 USART1 外设来控制,自动输出串口数据的高低电平时序,你只需要往 USART_DR 写数据,引脚就会自动把数据发出去。
五、补充:为什么是 "推挽"?
推挽模式的优势是:
高低电平驱动能力强,能直接驱动负载(或外设)
输出电平稳定,不会被外部电路拉偏
非常适合串口、PWM 这种需要稳定输出信号的场景。
六、什么时候不要用它?
当你只是想用引脚做普通高低电平控制(比如按键输入、LED 点亮)时,不要用这个模式,直接用普通推挽输出或输入模式即可。
4.1.2 RX引脚GPIO模式配置理解
先给结论:串口 RX 必须是输入模式;选浮空或上拉,都是为了让 "空闲时电平稳定、不乱跳、不误触发起始位"。
一、RX 为什么必须是 "输入模式"
TX 是发送:STM32 主动往外发数据 → 配置成推挽复用输出。
RX 是接收:STM32 被动听别人发来的数据 → 只能配置成输入模式(浮空 / 上拉 / 下拉),不能是输出。
二、为什么常用 "浮空输入" 或 "上拉输入"
串口是异步通信:
空闲状态:RX 应该是高电平(逻辑 1)。
起始位:一个下降沿(1→0),表示一帧开始。
1)浮空输入(GPIO_Mode_IN_FLOATING)
内部不上拉、不下拉,电平完全由外部电路决定。
优点:外部发送端(TX)是推挽输出时,驱动能力强、电平干净,浮空最标准。
风险:如果外部断开 / 悬空,引脚像 "天线",噪声会让电平乱跳,误判起始位。
典型:两个 STM32 互发(TX→RX 直连),常用浮空。
2)上拉输入(GPIO_Mode_IPU)
内部有约 40kΩ 上拉电阻到 VDD;空闲 / 悬空时自动为高电平。
优点:不怕悬空、抗干扰强,不会乱跳,保证空闲是高,不容易误触发起始位。
小缺点:外部拉低时要多克服一个上拉电流,但串口电平足够,不影响。
典型:布线长、噪声大、外部可能断开,优先上拉。
3)为什么不推荐 "下拉输入"
下拉空闲是低电平,而串口空闲必须是高 → 一直误判 "在发起始位",完全不能用。
三、浮空 vs 上拉
| 模式 | 空闲电平 | 抗干扰 | 适用场景 |
|---|---|---|---|
| 浮空输入(IN_FLOATING) | 由外部决定(理想高) | 较弱 | 短距离、直连、外部TX是推挽输出 |
| 上拉输入(IPU) | 固定高电平 | 强 | 长距离、噪声大、外部可能悬空/断开 |
四、一句话总结
RX 配置为浮空或上拉输入,核心是保证串口空闲时 RX 稳定在高电平,防止噪声误触发起始位,确保接收可靠;下拉输入因空闲为低,不符合串口协议,不能用。
4.2 波特率配置

c
/**
* @brief 配置 DEBUG 串口模式
* @param 无
* @retval 无
*/
void DEBUG_USART_MODEConfig(void)
{
USART_InitTypeDef usart_InitStruct = { 0 };
/* 开启 DEBUG 相关的GPIO外设/端口时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启GPIOA 端口时钟
/* 配置串口的工作参数 */
//波特率配置
usart_InitStruct.USART_BaudRate = ;
//硬件流控开关配置
usart_InitStruct.USART_HardwareFlowControl = ;
//配置工作模式:
usart_InitStruct.USART_Mode = ;
//校验
usart_InitStruct.USART_Parity = ;
//停止位
usart_InitStruct.USART_StopBits = ;
//数据帧长度
usart_InitStruct.USART_WordLength = ;
USART_Init(USART1, &usart_InitStruct);
}
c
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启GPIOA 端口时钟
因为我设置PA9是TX在总线APB2并且在串口1USART1上,所以需要开启串口1时钟总线
串口结构体成员定义:
c
typedef struct
{
uint32_t USART_BaudRate; /*!< This member configures the USART communication baud rate.
The baud rate is computed using the following formula:
- IntegerDivider = ((PCLKx) / (16 * (USART_InitStruct->USART_BaudRate)))
- FractionalDivider = ((IntegerDivider - ((u32) IntegerDivider)) * 16) + 0.5 */
uint16_t USART_WordLength; /*!< Specifies the number of data bits transmitted or received in a frame.
This parameter can be a value of @ref USART_Word_Length */
uint16_t USART_StopBits; /*!< Specifies the number of stop bits transmitted.
This parameter can be a value of @ref USART_Stop_Bits */
uint16_t USART_Parity; /*!< Specifies the parity mode.
This parameter can be a value of @ref USART_Parity
@note When parity is enabled, the computed parity is inserted
at the MSB position of the transmitted data (9th bit when
the word length is set to 9 data bits; 8th bit when the
word length is set to 8 data bits). */
uint16_t USART_Mode; /*!< Specifies wether the Receive or Transmit mode is enabled or disabled.
This parameter can be a value of @ref USART_Mode */
uint16_t USART_HardwareFlowControl; /*!< Specifies wether the hardware flow control mode is enabled
or disabled.
This parameter can be a value of @ref USART_Hardware_Flow_Control */
} USART_InitTypeDef;
成员1 uint32_t USART_BaudRate 对波特率设置,可以看到上位机上面的波特率是115200,所以这里设置115200
成员2 uint16_t USART_WordLength 数据帧长度设置
在注释里面标记这句
@ref USART_Word_Length,告诉我们在 USART_Word_Length去找

这里定义的有两个数据长度,8和9,这里设置8
成员6 uint16_t USART_HardwareFlowControl 流控设置
在注释里面标记这句
@ref USART_Hardware_Flow_Control 告诉我们在USART_Hardware_Flow_Control 去找

这里设置USART_HardwareFlowControl_None,无流控
成员5 uint16_t USART_Mode 模式设置
在注释里面标记这句
@ref USART_Mode 告诉我们在 USART_Mode 去找

这里设置 USART_Mode_Tx 发送模式
成员4 uint16_t USART_Parity 校验设置
在注释里面标记这句
@ref USART_Parity 告诉我们在USART_Parity 去找

这里设置USART_Parity_No 无校验
成员3 uint16_t USART_StopBits 停止位设置
在注释里面标记这句
@ref USART_Stop_Bits 告诉我们在USART_Stop_Bits 去找

这里设置USART_StopBits_1 1个停止位
5.发送数据代码理解
c
/**
* @brief 发送一字节函数
* @param pUSARTx:USARTx(X=1,2,3)/UARTx(x=4,5)
* @param ch:要发送的数据
* @note
* @retval 无
*/
void USART_SenByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
/*发送一字节数据到pUSARTx*/
USART_SendData(pUSARTx, ch);
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/**
* @brief 发送8位的数组函数
* @param pUSARTx:USARTx(X=1,2,3)/UARTx(x=4,5)
* @param arrary:要发送的数组
* @param num: 数组大小
* @note
* @retval 无
*/
void USART_SenArray(USART_TypeDef* pUSARTx, uint8_t* array, uint32_t num)
{
/* 等待发送完成 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TC) == RESET);
for(uint32_t i = 0; i < num; i++)
{
/*发送一字节数据到pUSARTx*/
USART_SendData(pUSARTx, array[i]);
/* 等待发送数据寄存器为空 */
while(USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
}
USART_GetFlagStatus 函数大白话详解
这个函数是 STM32 标准库串口的 "状态标志读取工具",作用是帮你查询串口的各种状态(比如发完没、收到没、有没有错误)。
一、一句话搞懂它
USART_GetFlagStatus(USARTx, USART_FLAG_xxx)
= 查询串口 USARTx 的某个状态标志位是否为 1(SET)
返回值是:SET(标志位为 1)或 RESET(标志位为 0)。
如果返回值是 RESET 则发送数据未完成
如果返回值是 SET 则发送数据完成
二、函数参数拆解
c
FlagStatus USART_GetFlagStatus(
USART_TypeDef* USARTx, // 参数1:指定要查哪个串口(USART1/2/3等)
uint16_t USART_FLAG // 参数2:指定要查哪个标志位
);
参数 1:USARTx
就是你之前问的串口指针,用来指定你要操作的串口外设,比如:
USART1:查询串口 1 的状态
USART2:查询串口 2 的状态
参数 2:USART_FLAG
| 标志位 | 含义(大白话) | 常用场景 |
|---|---|---|
USART_FLAG_TXE |
发送数据寄存器空 | 检查"数据能不能写入发送寄存器" |
USART_FLAG_TC |
一帧数据发送完成 | 检查"数据是不是真的发完了" |
USART_FLAG_RXNE |
接收数据寄存器非空 | 检查"有没有收到数据" |
USART_FLAG_IDLE |
总线空闲 | 检查"一整包数据是不是收完了" |
USART_FLAG_ORE |
接收溢出错误 | 检查"有没有数据没读被覆盖" |
三、最常用的 3 个例子(直接复制就能用)
- 等待发送寄存器空(写数据前)
c
// 等TXE=1,确保上一个数据已经进移位寄存器了
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART1->DR = 'A'; // 现在可以安全写数据了
- 等待一帧数据完全发送完成
c
USART1->DR = 'A';
// 等TC=1,确保数据已经全部发出去了
while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
- 检查有没有收到数据
c
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
{
uint8_t data = USART1->DR; // 读走收到的数据
}
四、关键细节(你一定要记)
USART_FLAG_TC 必须手动清零
它不会自动清零,发完数据后需要手动调用:
c
USART_ClearFlag(USART1, USART_FLAG_TC);
不然下次查询永远是 SET。
USART_FLAG_IDLE 清零方式特殊
必须先读 SR 再读 DR,才能清除标志位,函数也无法直接清除它。
和中断的区别
USART_GetFlagStatus:查询标志位(轮询方式),不触发中断
对应的中断(如TXEIE):标志位为 1 时自动触发中断,适合高实时性场景