请选择 进入手机版 | 继续访问电脑版
查看: 1590|回复: 0

[分享] [痞子衡]MCU中标准的三重中断控制设计

[复制链接]
  • TA的每日心情
    开心
    3 天前
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3296

    主题

    6541

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    31904
    最后登录
    2024-3-28
    发表于 2021-7-26 10:32:16 | 显示全部楼层 |阅读模式
    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是嵌入式MCU中标准的三重中断控制设计。


    我们知道在 MCU 裸机中程序代码之所以能完成多任务并行实时处理功能,其实主要是靠中断来调度的,没有中断,CPU 就只能按顺序"呆板"地执行代码。很多人都说是中断能力赋予了 MCU 真正的灵魂,能正确认识和熟练使用 MCU 中断,基本上就算玩熟了这颗 MCU。


    痞子衡之前写过一篇 《中断处理函数(IRQHandler)的标准流程》,里面详细讲了中断处理函数里的标准代码流程与写法,这篇文章可让大家对 MCU 里的中断用法有个初步认识。今天痞子衡以 ARM Cortex-M 内核 MCU 为例再来介绍下业界标准的三重中断控制设计:


    一、外设事件中断控制
    MCU 中最底层的中断控制针对的是外设里某个具体的事件,这个控制来自于外设模块本身,以恩智浦 i.MXRT 系列 MCU 的 GPT 定时器模块为例。如下是 GPT 模块寄存器列表,你可以发现其中有经典的 IR 和 SR 寄存器,SR 是事件状态寄存器,IR 是中断事件控制寄存器:
    11.png
    GPT 定时器一旦被使能,其运行状态(一共支持 6 个事件:超时、输入捕获 x 2ch、比较输出 x 3ch)都会实时记录在 SR 寄存器中,如果不在 IR 寄存器中将事件中断开启(默认是关闭的),那么就需要用户在代码里手动去查询 SR 寄存器置起的事件标志位以处理对应事件。


    Note:SR 寄存器中置起的事件标志位需要在事件处理前手动清除掉。如果标志位不及时清除,可能会遗漏下一次事件的处理(比如先处理当前事件,后清除事件标志位,那么处理事件期间再次发生的事件就会被漏掉)。如果标志位忘了清除,同一次事件就会被处理两次及以上。
    当然在实际应用中,为了节省 CPU 带宽,我们都是要开启外设事件中断的,MCU 厂商 SDK 包里一般都会提供相应接口函数(取自 fsl_gpt.h):
    1. typedef enum _gpt_interrupt_enable
    2. {
    3.     kGPT_OutputCompare1InterruptEnable = GPT_IR_OF1IE_MASK,
    4.     kGPT_OutputCompare2InterruptEnable = GPT_IR_OF2IE_MASK,
    5.     kGPT_OutputCompare3InterruptEnable = GPT_IR_OF3IE_MASK,
    6.     kGPT_InputCapture1InterruptEnable  = GPT_IR_IF1IE_MASK,
    7.     kGPT_InputCapture2InterruptEnable  = GPT_IR_IF2IE_MASK,
    8.     kGPT_RollOverFlagInterruptEnable   = GPT_IR_ROVIE_MASK,
    9. } gpt_interrupt_enable_t;

    10. // 开启 GPTx 的 xx 事件中断
    11. static inline void GPT_EnableInterrupts(GPT_Type *base, uint32_t mask)
    12. {
    13.     base->IR |= mask;
    14. }

    15. // 关闭 GPTx 的 xx 事件中断
    16. static inline void GPT_DisableInterrupts(GPT_Type *base, uint32_t mask)
    17. {
    18.     base->IR &= ~mask;
    19. }
    复制代码
    使能 GPT1 的超时事件中断代码示例如下:
    1. void periph_int_config(void)
    2. {
    3.     // 初始化 GPT1...
    4.     GPT_Init(GPT1, &gptConfig);
    5.     // ...

    6.     // 开启 GPT1 的超时事件中断
    7.     GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);
    8. }
    复制代码
    二、外设全局中断控制
    MCU 中第二层的中断控制针对的是整个外设,这个控制来自于 Cortex-M 内核的 NVIC 模块。如下是 NVIC 模块寄存器列表(取自 ARMv8-M 手册,除了 IABRn 和 ITNSn 寄存器组外,其余寄存器适用全部的 Cortex-M 家族),其中跟中断开关相关的是 ISER 和 ICER 寄存器:
    12.png
    当 MCU 中某外设(比如上一节里的 GPT)被使能后,即使其内部事件中断已被开启,也不意味着系统中断一定会被触发,因为 NVIC 里对于这个外设的全局中断开关(同一外设中所有事件共享一个系统中断资源,即一个中断号)还没有开启。ARM CMSIS 包里提供了外设全局中断控制函数(取自 core_cm7.h 文件):
    1. #define NVIC_EnableIRQ              __NVIC_EnableIRQ
    2. #define NVIC_DisableIRQ             __NVIC_DisableIRQ

    3. // 开启 xx 外设的全局中断
    4. __STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
    5. {
    6.   if ((int32_t)(IRQn) >= 0)
    7.   {
    8.     __COMPILER_BARRIER();
    9.     NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    10.     __COMPILER_BARRIER();
    11.   }
    12. }

    13. // 关闭 xx 外设的全局中断
    14. __STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
    15. {
    16.   if ((int32_t)(IRQn) >= 0)
    17.   {
    18.     NVIC->ICER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    19.     __DSB();
    20.     __ISB();
    21.   }
    22. }
    复制代码
    增加了使能 GPT1 的全局中断代码示例如下,其中 GPT1_IRQn 和  GPT1_IRQHandler 是固定名字,在 MCU 厂商提供的头文件(MIMXRT1176_cm7.h)和启动文件(startup_MIMXRT1176_cm7.s)里有定义。
    1. void periph_int_config(void)
    2. {
    3.     // 初始化 GPT1...
    4.     GPT_Init(GPT1, &gptConfig);
    5.     // ...

    6.     // 开启 GPT1 的超时事件中断
    7.     GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);

    8.     // 开启 GPT1 的全局中断
    9.     NVIC_EnableIRQ(GPT1_IRQn);
    10. }

    11. // GPT1 的中断响应函数
    12. void GPT1_IRQHandler(void)
    13. {
    14.     GPT_ClearStatusFlags(GPT1, kGPT_RollOverFlagInterruptEnable);

    15.     // 中断业务处理代码
    16. }
    复制代码
    三、系统全局中断控制
    MCU 中最顶层的中断控制针对的是整个芯片系统,这个控制来自于 Cortex-M 内核的 CPS 指令。如下是 CPS 指令用法(取自 ARMv7-M 手册):
    13.png
    当你想对 MCU 整个芯片的所有中断进行统一开关控制时,就必须借助 CPS 指令。一般情况下开启芯片系统全局中断动作在 MCU 启动文件里已经做好了,所以在用户代码环境里常常不需要使能系统全局中断的动作。如下是 IAR 环境下 i.MXRT1170 启动文件中系统全局中断操作,基于汇编指令实现:
    14.png
    为了便于用户在 C 代码中操作系统全局中断,各 IDE 下均按同样的接口函数( __disable_irq / __enable_irq )做了封装实现。IAR 环境见 \IAR Systems\Embedded Workbench 8.50.6\arm\inc\c\iccarm_builtin.h 文件,但是封装进其 Lib 了,没有暴露源码:
    1. #include "iccarm_builtin.h"

    2. #define __disable_irq       __iar_builtin_disable_interrupt
    3. #define __enable_irq        __iar_builtin_enable_interrupt
    复制代码
    Keil 环境见 \Keil_v5\ARM\ARMCLANG\include\arm_compat.h 文件,我们可以看到源码:
    1. static __inline__ unsigned int __attribute__((__always_inline__, __nodebug__))
    2. __disable_irq(void) {
    3.   unsigned int cpsr;
    4. #if __ARM_ARCH >= 6
    5. #if defined(__ARM_ARCH_PROFILE) && __ARM_ARCH_PROFILE == 'M'
    6.   __asm__ __volatile__("mrs %[cpsr], primask\n"
    7.                        "cpsid i\n"
    8.                        : [cpsr] "=r"(cpsr));
    9.   return cpsr & 0x1;
    10. #endif
    11. #endif
    12. }

    13. static __inline__ void __attribute__((__always_inline__, __nodebug__))
    14. __enable_irq(void) {
    15. #if __ARM_ARCH >= 6
    16.   __asm__ __volatile__("cpsie i");
    17. #endif
    18. }
    复制代码
    最终 GPT 例程里完整的三重中断使能代码应如下:
    1. void periph_int_config(void)
    2. {
    3.     // 初始化 GPT1...
    4.     GPT_Init(GPT1, &gptConfig);
    5.     // ...

    6.     // 开启 GPT1 的超时事件中断
    7.     GPT_EnableInterrupts(GPT1, kGPT_RollOverFlagInterruptEnable);

    8.     // 开启 GPT1 的全局中断
    9.     NVIC_EnableIRQ(GPT1_IRQn);

    10.     // 开启芯片系统全局中断
    11.     __enable_irq();
    12. }
    复制代码
    至此,嵌入式MCU中标准的三重中断控制设计痞子衡便介绍完毕了,掌声在哪里~~~





    签到签到
    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-3-29 01:53 , Processed in 0.118858 second(s), 22 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表