STM32——串口中断接收

一、实验功能

本实验实现 STM32 串口 三大核心功能:

  1. printf 重定向:单片机向上位机发送字符串
  2. 串口中断接收 :电脑发送数据 → 单片机自动触发中断读取数据
  3. 接收回显:单片机收到什么数据,立刻通过 printf 发回电脑显示

1、什么是串口中断接收?(通俗解释)

  • 普通接收(轮询) :主循环里一直while读取,CPU 累死,效率低
  • 中断接收 :CPU 正常干自己的事,数据来了自动喊 CPU ,CPU 停下手里的活,立刻读取数据不占 CPU 资源、响应最快、串口开发标准用法

2、串口中断接收完整工作流程

  1. 电脑发送一个字符 → 单片机 RX 引脚(PA10)收到
  2. 硬件自动把数据存入接收寄存器
  3. 触发 RXNE 接收中断(RX Non Empty)
  4. CPU 暂停主循环while(1)
  5. 自动跳转到 USART1_IRQHandler() 执行
  6. 读取数据 → printf 回显
  7. 清除中断标志 → 回到主循环继续执行

二、硬件连接

  • 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()

✅ 规则:

  1. 函数名固定,不能自定义!
  2. 中断触发后,硬件自动调用这个函数
  3. 接收逻辑必须写在这里面

函数内部逻辑:

复制代码
// 判断是不是接收中断
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 大规则

  1. 中断服务函数名固定,不能写错
  2. 必须开启 USART_IT_RXNE 接收中断
  3. 必须配置 NVIC 使能中断通道
  4. 中断里尽量少写代码,快进快出
  5. printf 必须重定向 fputc + 勾选 MicroLIB
相关推荐
charlie1145141913 小时前
嵌入式C++实践开发第21篇(单片机实践):按钮输入 —— 硬件原理、消抖与HAL API
开发语言·c++·单片机
一起搞IT吧3 小时前
Android性能系列专题理论之十一:block IO问题分析思路
android·嵌入式硬件·智能手机·性能优化
余生皆假期-3 小时前
YuanHub 源码分析【一】FlashDB 初始化与项目应用
笔记·单片机·嵌入式硬件
Deitymoon3 小时前
STM32——串口通信发送数据
stm32·单片机·嵌入式硬件
玩转单片机与嵌入式3 小时前
嵌入式AI场景:哪些应用场景不适合将AI模型部署到单片机(MCU)中?
人工智能·单片机·嵌入式硬件
czwxkn4 小时前
8STM32(stdl)低功耗模式
stm32·单片机·嵌入式硬件
czwxkn4 小时前
9STM32(stdl)看门狗
stm32·单片机·嵌入式硬件
coward915 小时前
Linux 内核 KGDB 以及内核驱动单串口调试笔记:telnet + agent-proxy + gdb-multiarch 实践
linux·单片机·嵌入式硬件
刻BITTER5 小时前
VirtualBox 安装Armbian x86 虚拟机
linux·嵌入式硬件