一、库函数
USART_ClockInit()
把配置好的时钟参数写入硬件寄存器
USART_ClockStructInit()
给时钟配置结构体赋默认值
这两个函数只在 USART 同步模式下有用(比如模拟 SPI 通信),异步模式(普通串口)无需调用,调用了也无效;
USART_SendData()
把数据写入 USART 发送寄存器(TDR)
USART_ReceiveData()
从 USART 接收寄存器(RDR)读取已接收的数据
USART_GetFlagStatus()
读取 USART 状态寄存器(SR)的某一位,返回 "置 1 / 置 0"阻塞式收发、轮询判断硬件
USART_ClearFlag()
手动清除状态寄存器中的指定标志位清除非中断触发的标志(如空闲帧、溢出错误)
USART_GetITStatus()
检查 "某中断是否触发"(需同时满足:标志位置 1 + 中断使能)中断服务函数中判断中断来源
USART_ClearITPendingBit()
清除中断挂起位,结束本次中断
二、串口发送
接线图

选用串口1,串口1TX对应的是PA9

打开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
配置GPIO口
PA9由外设串口控制,配置为复用推挽输出。
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
配置串口
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructure);
波特率:9600 流控:关闭 USART模式:仅配置为发送模式 校验位:无
停止位:1位 字长:8位
打开串口模块
USART_Cmd(USART1,ENABLE);
传送单个数据
发送数据,等待数据发送完成。
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1,Byte);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
Serial_SendByte(0x41);

发送字节数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
uint16_t i;
for(i = 0; i < Length; i ++)
{
Serial_SendByte(Array[i]);
}
}
uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};
Serial_SendArray(MyArray,4);

发送字符串
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
Serial_SendString("\r\nNum1=");

发送数字
uint32_t Setial_Pow(uint32_t X,uint32_t Y)
{
uint32_t Result = 1;
while(Y --)
{
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
uint8_t i;
for(i = 0; i < Length; i++)
{
Serial_SendByte(Number / Setial_Pow(10, Length - i - 1) % 10 + '0');
}
}
Serial_SendNumber(111,3);

三、串口接收
串口1RX映射的是PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
配置串口模式
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
打开串口中断
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
配置串口中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
中断函数
串口触发中断 --> 检查标志位 --> 将串口接收的数据放在RxData --> 将串口接收标志位置1 --> 手动清除中断标志位。
uint8_t Serial_RxData;
uint8_t Serial_RxFlag;
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);
Serial_RxFlag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
读取接收标志位
uint8_t Serial_GetRxFlag(void)
{
if(Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
读取接收的数据
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;
}
调用
while(1)
{
if(Serial_GetRxFlag() == 1)
{
RxData = Serial_GetRxData();
Serial_SendByte(RxData);
OLED_ShowHexNum(1,8,RxData,2);
}
}


四、printf重定向
C 标准库的printf并非直接输出数据,而是通过fputc(file put character)这个 "中间接口" 逐字符输出:
- PC 端默认逻辑 :
printf → 解析格式化字符串 → 调用fputc → 写入控制台; - STM32 重定向逻辑 :
printf → 解析格式化字符串 → 调用我们重写的fputc → 写入串口。
简单说:fputc是 printf 的 "输出管道",我们把管道的出口从 "控制台" 换成 "串口",就实现了 printf 的串口重定向。
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
标准printf依赖微库,且代码体积稍大;自定义Serial_Printf更轻量、可控性更高,核心是借助va_list处理可变参数,vsprintf格式化字符串。
-
定义缓冲区
string[100]存储格式化后的字符串。 -
使用
va_list、va_start和va_end处理可变参数列表。 -
vsprintf将格式化结果写入缓冲区,Serial_SendString发送完整字符串。void Serial_Printf(char *format, ...)
{
// 静态缓冲区:避免栈溢出,大小可按需调整(如200/512)
static char string[200];
va_list arg; // 可变参数列表// 1. 初始化可变参数列表 va_start(arg, format); // 2. 安全格式化字符串(vsnprintf防止缓冲区溢出) vsnprintf(string, sizeof(string), format, arg); // 3. 结束可变参数列表 va_end(arg); // 4. 发送格式化后的字符串 Serial_SendString(string);}
注
重定向后直接调用printf会卡死?因为标准库需要 "微库(MicroLIB)" 支持:
- 打开 MDK 工程,点击魔法棒(Target);
- 切换到
Tar get选项卡,勾选Use MicroLIB(使用精简版 C 库); - 切换到
c/c++选项卡,在MiscControls填写--no-multibyte-chars - 保存并重新编译。
头文件不能少
#include <stdio.h>:支持FILE/fputc/printf;#include <stdarg.h>:支持va_list/va_start/vsnprintf;- 缺少会报 "未定义标识符" 错误。
五、串口收发HEX数据包
接线图

发送数据包
定义发送数组
uint8_t Serial_Txpacket[4];
在.h中声明
extern uint8_t Serial_Txpacket[];
发送
void Serial_sendpacket(void)
{
Serial_SendByte(0xFF);
Serial_SendArray(Serial_Txpacket,4);
Serial_SendByte(0xFE);
}
接收数据包
定义接收数据包的缓存区和标志位
uint8_t Serial_Rxpacket[4];
在.h中声明
extern uint8_t Serial_Rxpacket[];
用状态机执行接收逻辑
用 3 个状态解析数据包,避免数据错乱:
- 状态 0:空闲状态,等待接收包头(0xFF);
- 状态 1:接收数据状态,连续接收 3 字节有效数据;
- 状态 2:校验包尾状态,等待接收包尾(0xFE),校验通过则标记 "数据包接收完成"。
流程图

void USART1_IRQHandler(void)
{
static uint8_t RxState = 0;
static uint8_t pRxpacket = 0;
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if(RxState == 0)
{
if(RxData == 0xFF)
{
RxState = 1;
pRxpacket = 0;
}
}
else if(RxState == 1)
{
Serial_Rxpacket[pRxpacket] = RxData;
pRxpacket++;
if(pRxpacket >= 4)
{
RxState = 2;
}
}
else if(RxState == 2)
{
if(RxData == 0xFE)
{
RxState = 0;
Serial_RxFlag = 1;
}
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
调用
int main(void)
{
OLED_Init();
key_Init();
Serial_Init();
OLED_ShowString(1,1,"Txpacket");
OLED_ShowString(3,1,"Rxpacket");
Serial_Txpacket[0] = 0x01;
Serial_Txpacket[1] = 0x02;
Serial_Txpacket[2] = 0x03;
Serial_Txpacket[3] = 0x04;
while(1)
{
keyNum = key_GetNum();
if(keyNum == 1)
{
Serial_Txpacket[0] ++;
Serial_Txpacket[1] ++;
Serial_Txpacket[2] ++;
Serial_Txpacket[3] ++;
Serial_sendpacket();
OLED_ShowHexNum(2,1,Serial_Txpacket[0],2);
OLED_ShowHexNum(2,4,Serial_Txpacket[1],2);
OLED_ShowHexNum(2,7,Serial_Txpacket[2],2);
OLED_ShowHexNum(2,10,Serial_Txpacket[3],2);
}
if(Serial_GetRxFlag() == 1)
{
OLED_ShowHexNum(4,1,Serial_Rxpacket[0],2);
OLED_ShowHexNum(4,4,Serial_Rxpacket[1],2);
OLED_ShowHexNum(4,7,Serial_Rxpacket[2],2);
OLED_ShowHexNum(4,10,Serial_Rxpacket[3],2);
}
}
}
六、串口收发文本数据包
void USART1_IRQHandler(void)
{
// static修饰:中断多次调用时保留状态和指针值
static uint8_t RxState = 0;
static uint8_t pRxPacket = 0;
// 检测USART1的接收非空中断(RXNE)
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
// 读取接收到的1字节数据
uint8_t RxData = USART_ReceiveData(USART1);
// 状态机解析:@开头 + 内容 + \r\n结尾
if (RxState == 0)
{
// 初始状态:收到@且上一包已处理(Serial_RxFlag=0),进入接收状态
if (RxData == '@' && Serial_RxFlag == 0)
{
RxState = 1;
pRxPacket = 0; // 重置数据包指针
}
}
else if (RxState == 1)
{
// 接收内容状态:收到\r则进入校验换行状态,否则存储数据
if (RxData == '\r')
{
RxState = 2;
}
else
{
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
else if (RxState == 2)
{
// 校验换行状态:收到\n则完成接收,置位标志
if (RxData == '\n')
{
RxState = 0;
Serial_RxPacket[pRxPacket] = '\0'; // 字符串结束符
Serial_RxFlag = 1; // 接收完成标志置1
}
}
// 清除中断挂起位,避免中断反复触发
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}

int main(void)
{
OLED_Init();
LED_Init();
Serial_Init();
OLED_ShowString(1, 1, "TxPacket");
OLED_ShowString(3, 1, "RxPacket");
while (1)
{
if (Serial_RxFlag == 1)
{
OLED_ShowString(4, 1, " ");
OLED_ShowString(4, 1, Serial_RxPacket);
if (strcmp(Serial_RxPacket, "LED_ON") == 0)
{
LED1_ON();
Serial_SendString("LED_ON_OK\r\n");
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "LED_ON_OK");
}
else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
{
LED1_OFF();
Serial_SendString("LED_OFF_OK\r\n");
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "LED_OFF_OK");
}
else
{
Serial_SendString("ERROR_COMMAND\r\n");
OLED_ShowString(2, 1, " ");
OLED_ShowString(2, 1, "ERROR_COMMAND");
}
Serial_RxFlag = 0;
}
}
}