一、实验功能
本实验实现 STM32 串口 三大核心功能:
- printf 重定向:单片机向上位机发送字符串
- 串口中断接收 :电脑发送数据 → 单片机自动触发中断读取数据
- 接收回显:单片机收到什么数据,立刻通过 printf 发回电脑显示
1、什么是串口中断接收?(通俗解释)
- 普通接收(轮询) :主循环里一直
while读取,CPU 累死,效率低 - 中断接收 :CPU 正常干自己的事,数据来了自动喊 CPU ,CPU 停下手里的活,立刻读取数据不占 CPU 资源、响应最快、串口开发标准用法
2、串口中断接收完整工作流程
- 电脑发送一个字符 → 单片机 RX 引脚(PA10)收到
- 硬件自动把数据存入接收寄存器
- 触发 RXNE 接收中断(RX Non Empty)
- CPU 暂停主循环
while(1) - 自动跳转到
USART1_IRQHandler()执行 - 读取数据 → printf 回显
- 清除中断标志 → 回到主循环继续执行
二、硬件连接
- USART1_TX (PA9) :串口发送
- USART1_RX (PA10) :串口接收
- 波特率:115200
- 数据位:8,停止位:1,校验位:无
三、完整代码
1.main.c 主程序文件
// 包含STM32标准库底层驱动
#include "stm32f10x.h"
// 主头文件
#include "main.h"
// 串口驱动头文件
#include "usart.h"
// 标准输入输出头文件(使用printf必须包含)
#include "stdio.h"
/**
* @brief 毫秒级软件延时函数
* @param time: 延时时间,单位 1ms
* @retval 无
*/
void delay(uint16_t time)
{
uint16_t i = 0;
while(time --)
{
i = 12000;
while(i --);
}
}
/**
* @brief 主函数
* @retval int
*/
int main()
{
// 1. 配置中断分组(整个工程只配置1次)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 2. 初始化串口(包含:GPIO、波特率、中断、NVIC)
my_usart_init();
// 3. 上电后发送一条欢迎信息
printf("hello\r\n");
// 4. 主循环死等
// 所有接收逻辑交给【中断】自动处理,主循环无需任何代码!
while(1)
{
}
}
2.usart.c 串口驱动文件(核心重点)
// 包含STM32标准库
#include "stm32f10x.h"
// 串口驱动头文件
#include "usart.h"
// 标准输入输出头文件
#include "stdio.h"
/**
* @brief 串口初始化函数(发送+接收+中断)
* @param 无
* @retval 无
*/
void my_usart_init(void)
{
// 定义配置结构体
GPIO_InitTypeDef GPIOInitStruct;
USART_InitTypeDef USART_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// ===================== 1. 开启时钟 =====================
// 同时开启 GPIOA 时钟 和 USART1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// ===================== 2. 配置 TX 引脚 PA9 =====================
// 发送引脚必须配置为:复用推挽输出
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_9;
GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIOInitStruct);
// ===================== 3. 配置 RX 引脚 PA10 =====================
// 接收引脚配置为:上拉输入
GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIOInitStruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIOInitStruct);
// ===================== 4. 配置串口通信参数 =====================
USART_InitStruct.USART_BaudRate = 115200; // 波特率115200
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无流控
USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 同时开启发送和接收
USART_InitStruct.USART_Parity = USART_Parity_No; // 无校验位
USART_InitStruct.USART_StopBits = USART_StopBits_1; // 1位停止位
USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 8位数据位
// 初始化串口寄存器
USART_Init(USART1, &USART_InitStruct);
// ===================== 5. 使能串口外设 =====================
USART_Cmd(USART1, ENABLE);
// ===================== 6. 开启【串口接收中断】 =====================
// 开启:RXNE 接收非空中断(当RX寄存器收到数据时,触发中断)
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// ===================== 7. 配置 NVIC 中断控制器 =====================
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; // 选择USART1中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级0
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级0
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能该中断通道
// 初始化NVIC
NVIC_Init(&NVIC_InitStruct);
}
/**
* @brief 串口发送一个字节
*/
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data)
{
// 写入数据到发送寄存器
USART_SendData(USARTx, Data);
// 等待发送完成(TXE=1表示发送完成)
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
}
/**
* @brief 串口发送字符串
*/
void My_Usart_Send_String(USART_TypeDef* USARTx, char * str)
{
uint16_t i = 0;
do
{
My_Usart_Send_Byte(USARTx,*(str+i));
i++;
}while(*(str+i) != '\0');
// 等待整个字符串发送完成
while (USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
}
// ===================== printf 重定向函数 =====================
/**
* @brief 重写fputc函数,将printf输出到串口1
*/
int fputc(int ch, FILE * p)
{
// 通过串口发送一个字符
USART_SendData(USART1, (u8)ch);
// 等待发送完成
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
// 返回字符
return ch;
}
// ===================== 【重点】串口中断服务函数 =====================
/**
* @brief USART1中断服务函数
* @note 函数名固定!!!不能修改
*/
void USART1_IRQHandler()
{
// 定义变量存储接收到的数据
char str;
// 1. 判断中断标志:是否是【接收中断】触发的
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
// 2. 读取串口接收寄存器的数据
str = USART_ReceiveData(USART1);
// 3. 把收到的数据通过printf回显到电脑
printf("receive data: %c \r\n",str);
// 4. 清除中断标志位(硬件自动清除,也可手动清除)
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
①. 开启接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_IT_RXNE:接收寄存器非空中断- 作用:只要 RX 收到数据,就立刻触发中断
- 不开启这行 → 中断永远不触发
②. NVIC 配置(中断管家)
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
- NVIC 是所有中断的大管家
- 必须开启对应中断通道,中断才能生效
③. 【最重要】串口中断服务函数
void USART1_IRQHandler()
✅ 规则:
- 函数名固定,不能自定义!
- 中断触发后,硬件自动调用这个函数
- 接收逻辑必须写在这里面
函数内部逻辑:
// 判断是不是接收中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
// 读取收到的数据
str = USART_ReceiveData(USART1);
// 回显数据
printf("receive data: %c \r\n",str);
// 清除中断标志
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
四、运行效果

五、必记 5 大规则
- 中断服务函数名固定,不能写错
- 必须开启
USART_IT_RXNE接收中断 - 必须配置 NVIC 使能中断通道
- 中断里尽量少写代码,快进快出
printf必须重定向fputc+ 勾选MicroLIB