STM32开发——中断、定时器

一、中断系统

1. 中断控制器对比

NVIC(Nested Vectored Interrupt Controller)
  • 适用:Cortex-M内核(M0、M3、M4、M7等)

  • 特点:只给Cortex-M单核使用

  • 中断数量:Cortex-M3内核最多支持256个中断

  • 优先级宽度:固定为4bit(0-15),数值越小优先级越高

GIC(Generic Interrupt Controller)
  • 适用:Cortex-A内核(A7、A9、A53等多核处理器)

  • 特点:多核使用,管理范围更大

  • 优先级宽度:可以自己配置,数值越小优先级越高

  • 复杂度:比NVIC更复杂

2. 中断优先级分类

优先级类型 说明
抢占优先级(主优先级) 决定是否可以打断正在执行的中断,数值越小优先级越高
响应优先级(子优先级) 当抢占优先级相同时,决定谁先执行
自然优先级 中断向量表中的硬件优先级(数值越小越高)

3. 优先级规则

text

1. 抢占优先级高的可以打断抢占优先级低的中断
2. 抢占优先级相同,响应优先级高的先执行
3. 抢占优先级和响应优先级都相同,自然优先级高的先执行
4. 所有优先级都是数值越小,优先级越高

二、按键中断配置(CubeMX + Keil)

1. 硬件连接

  • 按键K1:PA0引脚

  • 按键K2:PC13引脚

2. CubeMX配置步骤

步骤1:GPIO配置外部中断
  • 在Pinout视图找到PA0和PC13

  • 配置为GPIO_EXTI0GPIO_EXTI13(外部中断模式)

  • 触发方式选择:Rising上升沿触发

步骤2:NVIC配置
  • 进入NVIC设置页面

  • 找到对应的EXTI线中断(EXTI line0 interrupt、EXTI line13 interrupt)

  • 勾选Enabled(使能中断)

  • 设置抢占优先级(如P=7)

  • 设置响应优先级(如需要)

步骤3:GPIO输出配置
  • 配置LED引脚(如PC2、PC3)为输出模式

  • 配置蜂鸣器引脚(如PC1)为输出模式

3. Keil程序实现

中断回调函数
/* 外部中断回调函数 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    UNUSED(GPIO_Pin);  // 防止未使用参数警告
    
    /* 根据触发的中断引脚执行不同操作 */
    switch(GPIO_Pin)
    {
        case GPIO_PIN_0:  // K1按键按下(PA0)
            /* 执行K1对应操作:翻转LED1 */
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_2);
            break;
            
        case GPIO_PIN_13: // K2按键按下(PC13)
            /* 执行K2对应操作:翻转蜂鸣器 */
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_1);
            break;
            
        default:
            break;
    }
}
主函数
int main(void)
{
    /* 初始化HAL库 */
    HAL_Init();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化所有已配置的外设 */
    MX_GPIO_Init();
    
    /* 无限循环 */
    while(1)
    {
        /* 主循环中可以执行其他任务 */
        /* 按键响应已经在中断中处理,无需轮询 */
    }
}

三、定时器(TIM)

1. STM32F103定时器资源

STM32F103系列共有8个定时器:TIM1 - TIM8

2. 定时器分类

类型 定时器 位数 特点 用途
高级定时器 TIM1、TIM8 16位 向上/向下计数,输入捕获,输出比较 电机控制、PWM、死区控制
通用定时器 TIM2-TIM5 16位 向上/向下计数,输入捕获,输出比较 通用定时、PWM、输入捕获
基本定时器 TIM6、TIM7 16位 向上计数 基础定时功能

3. CubeMX配置定时器

  1. 在Pinout中找到TIM3

  2. 选择时钟源为Internal Clock(内部时钟)

  3. 配置参数:

    • Prescaler(预分频器):设置分频系数

    • Counter Period(自动重载值):设置计数周期

    • auto-reload preload(自动重载预装载):使能

4. Keil程序实现

定时器启动
/* 开启定时器3 */
HAL_TIM_Base_Start(&htim3);
微秒级延时函数
/**
 * @brief 微秒级延时函数(使用TIM3)
 * @param us 需要延时的微秒数
 */
void HAL_Delayus(uint16_t us)
{
    uint16_t tick = us - 1;                // 需要计数的时钟周期
    uint16_t tcnt = 0;                     // 已经计数的时钟周期
    uint16_t told = 0;                     // 上一次读取到定时器的计数值
    uint16_t tnow = 0;                      // 当前读取到定时器的计数值
    
    /* 获取定时器3当前计数值 */
    told = __HAL_TIM_GET_COUNTER(&htim3);
    
    while(1)
    {
        /* 循环获取定时器3当前计数值 */
        tnow = __HAL_TIM_GET_COUNTER(&htim3);
        
        /* 当前定时器计数值发生了变化 */
        if(tnow != told)
        {
            if(tnow > told)
            {
                /* tnow>told:定时器没有溢出 */
                /* tnow-told获得已经计数的时钟周期 */
                tcnt += tnow - told;
            }
            else
            {
                /* tnow<=told:定时器溢出 */
                /* 计算从told到0xFFFF经历的时钟周期 + 从0到tnow的时钟周期 */
                tcnt += 0xFFFF - told + tnow;
            }
            
            told = tnow;  // 更新上一次计数值
            
            /* tcnt>=tick:累加时间已经到达目标延时时间 */
            if(tcnt >= tick)
            {
                break;
            }
        }
    }
}

5. 延时函数原理分析

定时器时钟频率(如72MHz时,每计数1次约13.9ns)
    ↓
需要延时us微秒 → 计算出需要计数的次数 tick
    ↓
tick = us × (定时器时钟频率 / 1000000) - 1
    ↓
不断读取定时器当前计数值
    ↓
计算两次读取之间的差值
    ↓
处理定时器溢出情况
    ↓
累加已计数的周期
    ↓
达到tick后退出循环,完成延时

6. 定时器溢出处理

正常情况:tnow > told → 已计数周期 = tnow - told
溢出情况:tnow < told → 已计数周期 = (0xFFFF - told) + tnow
         (从told到0xFFFF的周期) + (从0到tnow的周期)
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐