当前位置: 首页 > news >正文

STM32F103ZET6的USART 中断配置详解

        在串口通信中,除了轮询方式,中断方式能更高效地处理数据收发,尤其适合需要实时响应的场景。本文将详细介绍如何在 STM32 中配置 USART 中断(包括接收中断和空闲中断),并通过中断处理函数实现数据的实时接收与命令解析。

一、为什么需要 USART 中断

        轮询方式虽然简单,但在等待数据时会阻塞 CPU,导致系统效率低下。而中断方式让 CPU 可以在没有数据时处理其他任务,只有当数据到来或满足特定条件(如总线空闲)时,才会触发中断并暂停当前任务去处理串口数据。这种方式能显著提高系统的实时性和资源利用率。

对于串口通信,常用的中断有两种:

  • 接收中断(RXNE):当接收缓冲区有数据时触发,用于实时接收单个字节。
  • 空闲中断(IDLE):当串口总线在数据传输后处于空闲状态时触发,通常用于判断一帧数据(如一个字符串)接收完成。

二、开启 USART 中断(接收中断与空闲中断)

要使用中断,首先需要开启对应的中断使能位。以下是寄存器和库函数两种实现方式:

1. 寄存器方式

// 打开接收中断(RXNEIE,CR1寄存器第5位)和空闲中断(IDLEIE,CR1寄存器第4位)
USART1->CR1 |= (1 << 5);  // 使能接收缓冲区非空中断(RXNE)
USART1->CR1 |= (1 << 4);  // 使能空闲线路检测中断(IDLE)

2. 库函数方式

// 打开接收中断和空闲中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);  // 使能RXNE中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);  // 使能IDLE中断

三、配置中断控制器 NVIC

STM32 的中断由 NVIC(嵌套向量中断控制器)管理,需要配置中断优先级和使能中断通道。

1. 中断优先级分组

STM32 的中断优先级由抢占优先级响应优先级组成,通过优先级分组设置两者的位数。常用的分组方式是 “2+2”(2 位抢占优先级,2 位响应优先级),共支持 16 级优先级。

// 设置中断优先级分组为2(2位抢占优先级,2位响应优先级)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

2. 配置 USART1 中断通道

// 定义NVIC初始化结构体
NVIC_InitTypeDef nvic;// 指定中断通道为USART1
nvic.NVIC_IRQChannel = USART1_IRQn;
// 使能该中断通道
nvic.NVIC_IRQChannelCmd = ENABLE;
// 设置抢占优先级(1级)
nvic.NVIC_IRQChannelPreemptionPriority = 1;
// 设置响应优先级(1级)
nvic.NVIC_IRQChannelSubPriority = 1;// 初始化NVIC
NVIC_Init(&nvic);
  • 抢占优先级决定中断的嵌套能力(高抢占优先级的中断可以打断低抢占优先级的中断)。
  • 响应优先级决定同抢占优先级中断的执行顺序(数值越小,优先级越高)。

四、实现中断处理函数

        中断处理函数是中断发生时的执行逻辑,STM32 规定了固定的函数名(如USART1_IRQHandler)。我们需要在函数中区分中断类型(接收中断 / 空闲中断),并进行相应处理。

1. 全局变量定义

首先定义用于缓存数据和命令的全局变量:

uint8_t buffer[255] = {0};  // 接收缓冲区(最大255字节)
uint8_t data;               // 临时变量,用于清除中断标志
int cnt = 0;                // 缓冲区计数
// 命令定义
uint8_t cmd0[] = "关灯";    // 关灯命令
uint8_t cmd1[] = "开灯";    // 开灯命令
uint8_t cmd2[] = "放歌";    // 放歌命令

2. 寄存器方式的中断处理函数

void USART1_IRQHandler(void)
{// 接收中断(RXNE:接收缓冲区非空)if (USART1->SR & (0X1 << 5))  // 判断SR寄存器第5位(RXNE标志){buffer[cnt++] = USART1->DR;  // 读取数据寄存器,保存到缓冲区// 注意:读取DR会自动清除RXNE标志}// 空闲中断(IDLE:总线空闲)if (USART1->SR & (0X1 << 4))  // 判断SR寄存器第4位(IDLE标志){// 清除空闲中断标志(必须先读SR,再读DR)data = USART1->SR;  // 读SR寄存器data = USART1->DR;  // 读DR寄存器(数据无用,仅用于清除标志)// 命令匹配与执行if (strcmp((char*)cmd0, (char*)buffer) == 0){GPIOB->ODR |= (0X1 << 5);  // PB5置高,关灯}else if (strcmp((char*)cmd1, (char*)buffer) == 0){GPIOB->ODR &= ~(0X1 << 5);  // PB5置低,开灯}else if (strcmp((char*)cmd2, (char*)buffer) == 0){play_two_tigers();  // 调用放歌函数(需提前实现)}// 清空缓冲区,准备下次接收memset(buffer, 0, cnt);cnt = 0;}
}

3. 库函数方式的中断处理函数

void USART1_IRQHandler(void)
{// 接收中断(RXNE)if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){buffer[cnt++] = USART_ReceiveData(USART1);  // 读取数据到缓冲区USART_ClearITPendingBit(USART1, USART_IT_RXNE);  // 清除接收中断标志}// 空闲中断(IDLE)if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){// 清除空闲中断标志(先读SR,再读DR)data = USART1->SR;  // 读SR寄存器data = USART_ReceiveData(USART1);  // 读DR寄存器// 命令匹配与执行if (strcmp((char*)cmd0, (char*)buffer) == 0){GPIO_SetBits(GPIOB, GPIO_Pin_5);  // PB5置高,关灯}else if (strcmp((char*)cmd1, (char*)buffer) == 0){GPIO_ResetBits(GPIOB, GPIO_Pin_5);  // PB5置低,开灯}else if (strcmp((char*)cmd2, (char*)buffer) == 0){play_two_tigers();  // 调用放歌函数}// 清空缓冲区memset(buffer, 0, cnt);cnt = 0;}
}

4. 关键逻辑说明

  • 接收中断(RXNE):每次收到一个字节就触发,将数据存入buffer并递增计数cnt
  • 空闲中断(IDLE):当串口在数据传输后空闲(无数据达一定时间)时触发,此时认为一帧数据接收完成。我们需要:
    1. 清除空闲中断标志(必须先读SR再读DR,否则标志无法清除);
    2. 对比缓冲区数据与预设命令,执行对应操作(如开灯、关灯);
    3. 清空缓冲区,重置计数,准备下一次接收。

五、完整代码示例(库函数版)

将上述配置整合,完整代码如下:

#include "stm32f10x.h"
#include <string.h>// 全局变量
uint8_t buffer[255] = {0};
uint8_t data;
int cnt = 0;
uint8_t cmd0[] = "关灯";
uint8_t cmd1[] = "开灯";
uint8_t cmd2[] = "放歌";// 函数声明
void USART1_Init(void);
void GPIO_Init_LED(void);
void play_two_tigers(void);  // 假设已实现int main(void)
{// 初始化LED(PB5)GPIO_Init_LED();// 初始化USART1(含中断配置)USART1_Init();while (1){// 主循环可处理其他任务,中断会实时响应}
}// USART1初始化(含GPIO、USART配置和中断使能)
void USART1_Init(void)
{// 1. 使能时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);// 2. 配置GPIO(PA9=TX,PA10=RX)GPIO_InitTypeDef gpio;// PA9:复用推挽输出gpio.GPIO_Pin = GPIO_Pin_9;gpio.GPIO_Mode = GPIO_Mode_AF_PP;gpio.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &gpio);// PA10:浮空输入gpio.GPIO_Pin = GPIO_Pin_10;gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &gpio);// 3. 配置USART1USART_InitTypeDef usart;usart.USART_BaudRate = 9600;usart.USART_WordLength = USART_WordLength_8b;usart.USART_StopBits = USART_StopBits_1;usart.USART_Parity = USART_Parity_No;usart.USART_HardwareFlowControl = USART_HardwareFlowControl_None;usart.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;USART_Init(USART1, &usart);// 4. 配置中断优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef nvic;nvic.NVIC_IRQChannel = USART1_IRQn;nvic.NVIC_IRQChannelCmd = ENABLE;nvic.NVIC_IRQChannelPreemptionPriority = 1;nvic.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&nvic);// 5. 使能USART中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 6. 使能USART1USART_Cmd(USART1, ENABLE);
}// LED初始化(PB5推挽输出)
void GPIO_Init_LED(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef gpio;gpio.GPIO_Pin = GPIO_Pin_5;gpio.GPIO_Mode = GPIO_Mode_Out_PP;gpio.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init(GPIOB, &gpio);
}// 中断处理函数
void USART1_IRQHandler(void)
{if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){buffer[cnt++] = USART_ReceiveData(USART1);USART_ClearITPendingBit(USART1, USART_IT_RXNE);}if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){data = USART1->SR;data = USART_ReceiveData(USART1);if (strcmp((char*)cmd0, (char*)buffer) == 0){GPIO_SetBits(GPIOB, GPIO_Pin_5);  // 关灯}else if (strcmp((char*)cmd1, (char*)buffer) == 0){GPIO_ResetBits(GPIOB, GPIO_Pin_5);  // 开灯}else if (strcmp((char*)cmd2, (char*)buffer) == 0){play_two_tigers();  // 放歌}memset(buffer, 0, cnt);cnt = 0;}
}

六、总结与注意事项

  1. 中断标志的清除

    • 接收中断(RXNE):读取DR寄存器后自动清除。
    • 空闲中断(IDLE):必须先读SR寄存器,再读DR寄存器才能清除,否则会重复触发中断。
  2. 缓冲区溢出处理
    示例中未处理缓冲区溢出(cnt超过 255),实际应用中需添加判断(如if (cnt >= 255) cnt = 0;)。

  3. 命令匹配的局限性
    示例使用strcmp匹配命令,要求输入与命令完全一致(包括长度)。实际应用中可使用字符串查找函数(如strstr)提高灵活性。

  4. 中断优先级的设置
    根据系统需求调整抢占优先级和响应优先级,确保关键中断优先执行。

0voice · GitHub

http://www.dtcms.com/a/270970.html

相关文章:

  • 自动化测试报告优化:jenkins+jmeter定制化HTML报告生成
  • 【LeetCode】大厂面试算法真题回忆(102)--集五福
  • Java学习第二十七部分——bug检修
  • 前端接收流式数据demo,并用markdown解析数据,包括EventSource和fetch两种方式
  • LeetCode 138题解 | 随机链表的复制
  • 力扣 hot100 Day39
  • 【保姆级喂饭教程】Windows下安装Git Flow
  • 电网的智能觉醒——人工智能重构能源生态的技术革命与公平悖论
  • JAVA策略模式demo【设计模式系列】
  • 自动化Trae Apollo参数解释的批量获取
  • 苍穹外卖项目日记(day04)
  • ASP.NET Core 8 轻松配置Serilog日志
  • 智慧码头船舶网络部署5G工业路由器无人值守场景应用
  • 无人设备遥控器之双向通讯技术篇
  • 【机器人】Aether 多任务世界模型 | 4D动态重建 | 视频预测 | 视觉规划
  • C++并发编程-11. C++ 原子操作和内存模型
  • Linux驱动学习day20(pinctrl子系统驱动大全)
  • Ubuntu防火墙缺失问题(unit firewalld.service could not be found, ubuntu 22)
  • EFK9.0.3 windows搭建
  • Linux系统管理实战:生成大文件与定位磁盘挂载点
  • 专题:2025母婴行业洞察报告|附60+份报告PDF汇总下载
  • Linux中shell(外壳)和内核(kernel)的关系
  • Claude Code:终端上的 AI 编码助手,潜力与挑战并存
  • 从零用java实现 小红书 springboot vue uniapp(13)模仿抖音视频切换
  • 华为数通HCIA vs HCIP:新手入门选哪个更合适?
  • 利用sCMOS科学相机测量激光散射强度
  • Rk3568驱动开发_阻塞IO_15
  • SQL Server通过存储过程实现飞书消息卡片推送
  • Live555-RTSP服务器
  • nl2sql的解药pipe syntax