硬件:STM32F103,对接JDY‑31蓝牙模块
引脚:PA9(TX),PA10(RX);波特率可设置;全局变量 RxData 保存收到的1字节数据

一、代码整体功能

初始化串口1,开启接收中断。蓝牙发来1个字节,不用主循环不停查询,单片机会自动进中断把数据读到RxDatawhile(1)主循环直接读取RxData使用。

二、USART1_Init()初始化函数逐块理解

1. 开启时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

类比:给GPIOA引脚、串口1模块“通电”,不通电外设不工作。

2. 配置TX PA9

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);

PA9作为串口发送脚,复用推挽输出,不归普通GPIO管,交给串口外设控制输出电平。

3. 配置RX PA10

GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA, &GPIO_Initstructure);

PA10接收外部发来的数据,浮空输入,读取引脚上的电平信号。

4. 串口参数配置

USART1_InitStructure.USART_BaudRate = bound;
USART1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART1_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //既接收也发送
USART1_InitStructure.USART_Parity = USART_Parity_No;
USART1_InitStructure.USART_StopBits = USART_StopBits_1;
USART1_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART1_InitStructure);
USART_Cmd(USART1, ENABLE); //使能串口1,串口开始工作

设置:波特率、无硬件流控、收发模式、无校验、1停止位、8位数据,最后使能串口硬件。

5. NVIC中断控制器配置

NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  • USART1_IRQn:串口1对应的中断通道
  • NVIC_IRQChannelCmd = ENABLE:打开串口1总中断开关
  • 抢占优先级1,子优先级0:多个中断同时到来,决定谁优先执行

⚠️重点:NVIC_PriorityGroupConfig() 不能写在此函数内部,要放到main函数最开头,否则优先级设置无效。

6. 外设中断开关 + 清标志

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
USART_ClearFlag(USART1, USART_FLAG_TC);
  1. USART_IT_RXNE接收事件中断开关。收到字节产生RXNE标志后,才会向NVIC发起中断请求。

两层开关:NVIC总开关 + USART_ITConfig外设中断开关,两个都打开,中断才会触发。

  1. USART_FLAG_TC:发送完成标志。这行只是初始化清除发送的残留标志,和接收中断无关,可以删掉

三、中断服务函数

void USART1_IRQHandler(void)
{
	if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
	{
			USART_ClearFlag(USART1, USART_FLAG_RXNE);
			RxData = USART_ReceiveData(USART1);
//    USART_SendData(USART1, RxData);
//    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) ;
	}
}

函数名字固定,不能修改,启动文件靠这个名字找到中断入口。

完整事件举例(蓝牙发送字符 '1' 给单片机)

  1. JDY‑31蓝牙模块,通过PA10引脚,把字符'1'逐位发给STM32。
  2. 串口硬件收完整一个字节,硬件自动置位RXNE = SET(接收寄存器非空)。
  3. 因为打开了USART_IT_RXNE,串口向NVIC发出中断申请。
  4. NVIC判断允许响应中断,CPU暂停while(1)里面正在跑的代码,保存现场,跳转到USART1_IRQHandler
  5. if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET) 判断确认:是收到数据这件事触发的中断。
  6. RxData = USART_ReceiveData(USART1); 读取寄存器拿到'1'存入全局变量。

读取USART_ReceiveData()硬件会自动清除RXNE标志;代码又手动调用ClearFlag,属于冗余,不报错。

  1. 中断函数运行结束,CPU恢复现场,回到while(1)继续运行;主循环读取RxData就拿到蓝牙的数据。

注释掉的回显代码:打开后收到什么就原路发回去;中断里面不建议写while阻塞等待,会拖慢系统。

四、3个关键标志通俗区分

  1. RXNE:接收寄存器非空。收到1字节硬件置1;读接收寄存器硬件自动清零,用于接收中断。
  2. TXE:发送寄存器空,可以发送下一个字节。
  3. TC:整帧数据全部从TX引脚物理发送完毕,需要手动软件清零;本代码没有使用TC中断。

五、高频踩坑点

  1. 时钟忘记开启,串口完全无反应。
  2. 中断两个开关缺一不可:NVIC使能、USART_ITConfig(USART_IT_RXNE,ENABLE)。只置RXNE标志不会进中断。
  3. 中断服务函数名字不能写错。
  4. 中断内部不要写Delay()、复杂运算;只做读取赋值,业务逻辑放主循环while(1)
  5. 中断优先级分组函数必须放在main开头。

六、极简流程口诀

外部发来字节 → 硬件置RXNE标志 → 外设发起中断请求 → NVIC裁决放行 → 跳中断服务函数读数据 → 返回主循环。
明白,笔记文字保留,完整代码放在笔记的最末尾,笔记内部不粘贴大段代码,只用代码片段讲逻辑,重新整理:

USART1串口接收中断笔记|通俗版

硬件:STM32F103,对接JDY‑31蓝牙模块
引脚:PA9(TX),PA10(RX)
全局变量 RxData:用来保存中断收到的1字节

一、整体功能

初始化串口1,开启接收中断
蓝牙发来字节,不需要while(1)不停查询,硬件自动触发中断,把数据存入RxData,主循环直接读取RxData使用。

二、USART1_Init 初始化讲解

  1. 开启时钟
    给GPIOA、USART1打开时钟,外设不上电无法工作。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
  1. 配置TX引脚 PA9
    模式:复用推挽输出,引脚交给串口外设控制,不作为普通GPIO。

  2. 配置RX引脚 PA10
    模式:浮空输入,接收外部蓝牙发来的电平信号。

  3. 串口参数配置
    波特率、8位数据、1停止位、无校验、收发模式,最后USART_Cmd(USART1,ENABLE)使能串口。

  4. NVIC配置(内核中断控制器)

  • USART1_IRQn:串口1中断通道
  • NVIC_IRQChannelCmd = ENABLENVIC总中断开关
  • 设置抢占优先级、子优先级

⚠️坑:NVIC_PriorityGroupConfig()不能写在这里,要放在main()最开头,否则优先级无效。

  1. 外设中断开关
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

打开RXNE接收中断开关

中断两层开关:NVIC总开关 + 这行外设中断开关,两个都开才会进中断。

USART_ClearFlag(USART1, USART_FLAG_TC);

清除发送完成TC标志,和接收无关,可以删掉

三、中断服务函数

函数名USART1_IRQHandler固定,不能改名,启动文件靠这个名字找到中断入口。

真实过程举例:蓝牙发字符 '1'

  1. JDY‑31通过PA10把'1'发给单片机。
  2. 串口硬件收完整1字节,硬件自动置 RXNE = SET
  3. 因为开启USART_IT_RXNE,串口向NVIC发起中断请求。
  4. NVIC允许响应中断,CPU暂停while(1),保存现场,跳转到中断服务函数。
  5. 判断RXNE==SET,确认是收到数据触发中断。
  6. RxData = USART_ReceiveData(USART1); 读取数据。

USART_ReceiveData()硬件自动清除RXNE;代码里手动ClearFlag属于冗余,不报错。

  1. 中断执行完毕,CPU恢复现场,回到主循环,主循环读RxData拿到数据。

注释掉的回显代码:收到数据立刻原路发回;中断里面不要写while阻塞,影响实时性。

四、三个标志对比

  1. RXNE:接收寄存器非空。收到字节硬件置1;读数据硬件自动清零。用于接收中断。
  2. TXE:发送寄存器空,可以发送下一个字节。
  3. TC:一帧数据全部物理从引脚发完;需要软件手动清零;本代码没用到TC中断。

五、踩坑总结

  1. 忘记开时钟,串口完全不工作。
  2. 中断两个开关必须全部打开,只出现RXNE标志不会自动进中断。
  3. 中断函数名字不能写错。
  4. 中断里面禁止Delay、复杂运算;只做读取赋值,业务逻辑放while(1)。
  5. 中断优先级分组写在main最开头。

六、记忆口诀

外部发来字节 → 硬件置RXNE标志 → 外设发起中断请求 → NVIC裁决放行 → 跳中断服务函数读数据 → 返回主循环

————————

✅完整代码(放在这里,笔记全部讲完之后)

#include "USART1.h"
#include "sys.h"

u8 RxData=0x00;

/*
*** USART1  配置串口1与JDY-31通信 ***

*** 波特率:115200,8位数据位,1位停止位,无奇偶校验位 ***

*** 接口:PA9->TX   PA10->RX ***

*** 作者:陈加油嗯 ***

***关注微信公众号学习更多单片机知识:微信搜索“陈加油嗯” ***

*/
void USART1_Init(uint32_t bound)
{
	GPIO_InitTypeDef GPIO_Initstructure;
	NVIC_InitTypeDef NVIC_InitStruct;
	USART_InitTypeDef USART1_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); 

	// USART1_TX     PA9
	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);

	// USART1_RX     PA10
	GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_Initstructure);

	USART1_InitStructure.USART_BaudRate = bound;
	USART1_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
	USART1_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                 //收模式
	USART1_InitStructure.USART_Parity = USART_Parity_No;                             //无奇偶校验位
	USART1_InitStructure.USART_StopBits = USART_StopBits_1;                          //一个停止位
	USART1_InitStructure.USART_WordLength = USART_WordLength_8b;                     //字长为8位数据格式
	USART_Init(USART1, &USART1_InitStructure);
	USART_Cmd(USART1, ENABLE); //使能USART1

	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;

	NVIC_Init(&NVIC_InitStruct);
//	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	USART_ClearFlag(USART1, USART_FLAG_TC);
}

void USART1_IRQHandler(void)
{
	if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
	{
			USART_ClearFlag(USART1, USART_FLAG_RXNE);
			RxData = USART_ReceiveData(USART1);  //接收到的数据存到 RxData

//    USART_SendData(USART1, RxData);
//    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) //等待数据发完
//            ;
	}
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐