STM32串口中断控制LED闪烁速度:原理详解与代码实现指南

一、项目背景与目标

在嵌入式系统开发中,经常需要通过外部输入动态调整设备行为。本项目实现通过串口中断接收上位机指令,实时修改 LED 的闪烁间隔,从而控制闪烁速度。核心目标是理解 STM32 的串口中断机制GPIO 输出控制以及中断优先级配置,掌握通过外部输入动态调整程序逻辑的方法。

二、硬件准备

1.硬件平台

  • STM32F103 开发板(如 Blue Pill,核心为 STM32F10x 系列)
  • 板载 LED(通常连接到 GPIOC Pin13,即 PC13)
  • USB 转 TTL 模块(用于串口通信,连接 STM32 的 USART1)
  • 连接线(杜邦线)
  • 2.硬件连接
    STM32 引脚 功能 连接对象
    PA9 USART1_TX 串口模块 TX
    PA10 USART1_RX 串口模块 RX
    PC13 LED 控制引脚 板载 LED 阴极
    3.3V/GND 电源 串口模块电源

    三、软件设计核心思路

    1. 核心逻辑

    2. 通过串口(USART1)接收上位机发送的字符(如 '0'、'1'、'2')。
    3. 使用中断方式处理串口接收,避免占用 CPU 资源。
    4. 根据接收到的字符修改全局变量BlinkTime,从而改变 LED 闪烁间隔。
    5. 关键技术点

    6. GPIO 初始化:配置 PC13 为推挽输出,控制 LED 亮灭。
    7. USART 初始化:设置波特率、数据位、停止位等参数,使能接收中断。
    8. NVIC 中断配置:设置串口中断的优先级,确保中断正确响应。
    9. 中断处理函数:解析接收数据,更新闪烁间隔变量。

    四、代码逐行解析

    1. 头文件与全局变量

    #include "stm32f10x.h"       // STM32标准库头文件
    #include "delay.h"           // 延时函数头文件(需自行实现简单延时)
    // #include "usart.h"         // 若封装串口功能,可包含此文件
    
    volatile uint32_t BlinkTime = 1000;  // 闪烁间隔(单位:ms),volatile防止优化
  • volatile:确保编译器不优化该变量,允许中断函数直接修改。
  • BlinkTime初始值为 1000ms,即 LED 每秒闪烁一次。
  • 2. GPIO 初始化(LED 控制)

    void My_GPIO_Init(void) {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);  // 使能GPIOC时钟
        
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;                // PC13引脚
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;          // 推挽输出模式
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;         // 最大输出速度50MHz
        GPIO_Init(GPIOC, &GPIO_InitStruct);                    // 初始化GPIOC
    }
  • 推挽输出:直接控制引脚高低电平,适合驱动 LED。
  • 时钟使能:使用外设前必须开启对应 RCC 时钟,否则无法工作。
  • 3. USART1 初始化(串口配置)

    void My_USART1_Init(void) {
        // 初始化USART1的TX/RX引脚(PA9/PA10)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  // 使能GPIOA时钟
        
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        // TX引脚(PA9)配置为复用推挽输出
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;           // 复用功能推挽输出
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // RX引脚(PA10)配置为上拉输入
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;             // 上拉输入模式
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 初始化USART1参数
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能USART1时钟
        
        USART_InitTypeDef USART_InitStruct = {0};
        USART_InitStruct.USART_BaudRate = 115200;              // 波特率115200
        USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  // 无硬件流控制
        USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;  // 使能接收和发送
        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);                 // 初始化USART1
        
        // 使能USART1及接收中断
        USART_Cmd(USART1, ENABLE);                             // 使能USART1
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);         // 使能接收中断(RXNE标志)
        
        // 配置NVIC中断优先级
        NVIC_InitTypeDef NVIC_InitStruct = {0};
        NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;         // 选择USART1中断通道
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;        // 子优先级1
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;           // 使能中断通道
        NVIC_Init(&NVIC_InitStruct);                           // 初始化NVIC
    }
  • 复用推挽输出(TX):USART 的 TX 引脚需配置为复用功能,由 USART 外设控制。
  • 上拉输入(RX):接收引脚通常需要上拉,确保未接收到数据时电平稳定。
  • NVIC 优先级分组:主函数中NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)设置优先级分组为 2(2 位抢占优先级,2 位子优先级),需在所有 NVIC 配置前调用(用户代码中已在main函数开头调用)。
  • 4. 主函数逻辑

    int main(void) {
        My_GPIO_Init();          // 初始化LED引脚
        My_USART1_Init();        // 初始化串口
        
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  // 设置优先级分组(需在NVIC初始化前)
        
        while (1) {
            // 点亮LED并延时
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_RESET);  // PC13输出低电平(LED亮)
            Delay(BlinkTime);                              // 延时BlinkTime ms
            // 熄灭LED并延时
            GPIO_WriteBit(GPIOC, GPIO_Pin_13, Bit_SET);    // PC13输出高电平(LED灭)
            Delay(BlinkTime);                              // 延时BlinkTime ms
        }
    }
  • 循环逻辑:通过GPIO_WriteBit控制 LED 亮灭,间隔由BlinkTime决定。
  • 非阻塞设计:串口中断会在后台处理,不影响主循环的延时操作,实现 “异步” 修改闪烁速度。
  • 5. 串口中断处理函数

    void USART1_IRQHandler(void) {
        if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {  // 检查接收中断标志
            uint32_t dataRcvd = USART_ReceiveData(USART1);          // 读取接收数据
            
            // 根据接收到的字符修改闪烁间隔
            if (dataRcvd == '0') {
                BlinkTime = 80;       // 快速闪烁(80ms)
            } else if (dataRcvd == '1') {
                BlinkTime = 300;      // 中速闪烁(300ms)
            } else if (dataRcvd == '2') {
                BlinkTime = 500;      // 低速闪烁(500ms)
            }
            
            USART_ClearITPendingBit(USART1, USART_IT_RXNE);  // 清除中断标志位
        }
    }
  • 中断触发条件:当 USART 接收数据寄存器非空(RXNE 标志置 1)时触发中断。
  • 数据读取:通过USART_ReceiveData获取 8 位数据(实际为uint16_t,低 8 位有效)。
  • 标志位清除:必须手动清除 RXNE 标志,否则中断会重复触发。
  • 6. 依赖函数(需自行实现)

    Delay函数
    示例实现(基于 SysTick,需包含delay.h):

    void Delay(uint32_t ms) {
        uint32_t i, j;
        for (i = 0; i < ms; i++)
            for (j = 0; j < 8400; j++);  // 假设系统时钟72MHz,此延时非精确,仅作示例
    }

    七、总结

    本项目通过 STM32 的串口中断实现了外部输入对 LED 闪烁速度的动态控制,核心在于:

    1. 中断机制:利用 USART 的接收中断(RXNE)异步处理数据,避免阻塞主程序。
    2. 优先级配置:通过 NVIC 合理设置中断优先级,确保关键事件及时响应。
    3. 全局变量共享:通过volatile修饰的全局变量BlinkTime,在中断与主循环之间安全通信。

    作者:Kryon__VVN

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32串口中断控制LED闪烁速度:原理详解与代码实现指南

    发表回复