STM32F103ZET6的USART 中断配置详解

发布于:2025-07-11 ⋅ 阅读:(22) ⋅ 点赞:(0)

        在串口通信中,除了轮询方式,中断方式能更高效地处理数据收发,尤其适合需要实时响应的场景。本文将详细介绍如何在 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. 配置USART1
    USART_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. 使能USART1
    USART_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


网站公告

今日签到

点亮在社区的每一天
去签到