文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

ARM Cortex-M 微控制器非常流行, 它具有灵活并且强大的嵌套向量中断控制器(NVIC)。但对许多人来说,包括我自己,Cortex-M中断系统可能有些难以意料的,复杂的,矛盾的,混乱的,导致很多bug和错误。

NXP KV58F ARM Cortex-M7

ARM Cortex-M7: NXP KV58

 

理解NVIC (嵌套向量中断控制器) 和ARM Cortex-M 中断系统对每个嵌入式应用至关重要,即使使用实时操作系统: 如果你搞不清楚中断就会发生很糟糕的事情。。。

第 1 段(可获 1.06 积分)

前言

FreeRTOS可能是微控制器中最受欢迎并且最常用的操作系统。它支持许多不同的体系结构,包括ARM Cortex-M 架构。

在我大学的课堂讲义上曾经讨论过FreeRTOS和interrupts这个话题。 但我看过很多RTOS中断的错误使用,我认为应当用一篇专门的文章去介绍它。令人吃惊的是,我看到很多次即使中断配置明显错误情况下,应用程序在大多数时候“似乎”也能工作。 当然, 我认为每个人都同意“大多数时候”是不够好的。因为中断的问题通常很难跟踪,所以他们很难解决。

第 2 段(可获 1.5 积分)

在这篇文章,我将讨论ARM Cortex-M0/M0+ (ARMv6-M),M3(ARMv7-M)和M4/M7 (ARMv7E-M)。在Part1(也就是这篇文章)我给出了ARM Cortex-M中断系统的一个概述。在Part2,我将解释在FreeRTOS中怎么使用中断以及中断如何影响应用程序。

中断向量

ARM Cortex-M使用NVIC (嵌套的向量化的中断控制器)。“向量化的”意思就是它使用一个向量表,如下显示M0和M0 +和M4、M4的向量表:

Cortex-M Vector Table

Cortex-M 向量表(图片来源: ARM)

这个表是“向量化的”,因为它有32bit的入口地址(例如硬故障向量在地址0x000C’0000) 指向相应的中断服务子程序: 例如,地址0x08的入口“指向”NMI中断处理程序或函数.

第 3 段(可获 1.66 积分)

异常号1-15由ARM定义的,也就是说它们核心的一部分。异常号大于15的由厂商定义,也就是说由像NXP, TI, STM等等这样的厂商定义。

或者换句话说: 负数的IRQ号(从-1 (SysTick) 到-14 (NMI) 加上reset) 由ARM核心定义,其它IRQ号>=0的由厂商自定义,通常由类似UART/I²C/USB/等这样的设备使用。

注:在上述图像,编号或异常号和IRQ容易混淆,造成混乱。

中断优先级

第 4 段(可获 1.25 积分)

ARM Cortex-M核心使用一个令人相当困惑的中断优先级编号:数值上低的值用来用来指定逻辑上高的中断优先级。 或者换句话说:数值越低,紧急度越高。

我尝试使用 “紧急度”这个词来表明一个中断的“重要性”,不要与(ARM硬件)中断优先级混淆。在一个嵌套的中断系统如ARM Cortex-M,一个“更紧急”的中断可以中断一个“较不紧急”的中断。

这意味着中断优先级0是“紧急度最高的一个”。

在复位时,所有的中断优先级都被赋值为0。 我可以为他们分配一个优先权。 除了Reset, NMI, and HardFault 有一个固定的(负数) 优先级并且不能被禁掉。下表显示了所有的异常/中断,以及他们存在于哪个ARM Cortex-M核心:

第 5 段(可获 1.76 积分)

Exception and Priorities

异常和优先级

优先级位

ARM给每个异常/中断优先级分配了一个8位的优先级寄存器,但实现的位数取决于供应商的实现。ARM 指定M0/M0+至少两位 、 M3/M4/M7至少三位。


如果使用CMSIS标准库,实现的位数可以这样获得:

__NVIC_PRIO_BITS

下面例子是NXP的Cortex-M7(see "First steps: ARM Cortex-M7 and FreeRTOS on NXP TWR-KV58F220M"), 它实现了4位的优先级

#define __NVIC_PRIO_BITS    4   /**< Number of priority bits implemented in the NVIC */
第 6 段(可获 1.05 积分)

移位的优先级位

实现的优先级位是“左对齐的”:这使得优先级值在不同实现之间保持兼容:

3 Interrupt Bits Implemented

实现的三个中断位

对于三个实现的位,这意味着我可以有2 ^ 3(8)个优先级,有下列值 (移位的) :0x00,0x20,0x40,0x60,0x80,0xA0,0xC0,0xE0。

关于“移位的”和“没有移位的”优先权值有太多的困惑。仔细检查 CMSIS 的API函数查看它是需要移位的还是没有移位的值!使用CMSIS API处理ARM核心和它的核心寄存器是首选的方式。

第 7 段(可获 1.18 积分)

NVIC中断配置

NVIC提供许多寄存器来配置中断. 在M0/M0+上有如下寄存器:

  • NVIC_ISER (Interrupt Set Enable Register): 使能中断位,每个bit对应一个中断。
  • NVIC_ICER (Interrupt Clear Enable Register): 禁用中断位, 每个bit对应一个中断。
  • NVIC_ISPR (Interrupt Set Pending Register): 标记中断为挂起的位,  每个bit对应一个中断。
  • NVIC_ICPR (Interrupt Clear Pending Register): 清除挂起的标志位,  每个bit对应一个中断。
  • NVIC_IPRx (Interrupt Prioriy Register): 中断优先级(,每个中断8位,4个中断对应一个32位的寄存器).
第 8 段(可获 1.26 积分)

NVIC Interrupt Registers on Cortex-M0+

Cortex-M0+的NVIC中断寄存器

 

上面的寄存器都是32位的寄存器,每一个中断对应一个bit。例如, NXP KL25Z有32个厂商特定的中断(异常0x10-0x47),所以 32bits 对以上寄存器的每一位已经足够。 对应32个中断的优先级 32*8bit == 8 32bit 优先级寄存器(NVIC_PRI0…PRI7) 被使用。

下图显示了NVIC_ISER的每个位:

NVIC Interrupt Bits

NVIC中断位

Cortex-M3/4/7 除了上述一个寄存器外还有一个寄存器:

  • NVIC_IABR (中断激活位寄存器): 置1表示中断正在执行,每个中断对应一位
第 9 段(可获 1.29 积分)

再次申明,每个中断都有一个单独的位。例如,NXP K22FX512 (ARM Cortex-M4F) 有82个厂商特定的中断(异常 0x10-0x61),所以它需要4个32位寄存器来容纳所有的位并且106个32位寄存器来容纳八位的优先级。

分配中断优先级

要设置中断优先级,使用以下CMSIS函数:

void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority);

IRQn是异常号 (0为第一个厂商特定的异常, -1为SysTick, -2为 PendSV等等)。“优先级”是没有位移的(! ! !)中断优先级,例如 实现3位优先级的系统使用0-7。

第 10 段(可获 1.11 积分)

这种混合移位的和没有移位的值是非常混乱的,一断常见的错误代码 — 甚至书上也错了

例如 ...

NVIC_SetPriority(SysTick_IRQn, (1UL<<__NVIC_PRIO_BITS)-1UL); /* set Priority for Systick Interrupt to lowest interrupt */

... 设置SysTick为最低可用的中断优先级(SysTick_IRQn 是一个值为-1的宏)。

子优先级

对M3/M4/M7,中断可能有子优先级,并且子优先级的位数通过PRIGROUP寄存器进行配置。PRIGROUP可以在运行时进行改变:

第 11 段(可获 1.01 积分)

PRIGROUP Register

PRIGROUP寄存器

在上面的例子中,PRIGROUP被设置为0(没有子优先级)。PRIGROUP的值设置子优先级位的位位置:例如,如果PRIGROUP的值为5(Bit5) 且优先级位数为三个,这样有两个主/抢占优先级和一个子优先级:

System with 3 Priority bits, 2 are preemption priority and one sub priority bit

具有三个优先级位的系统,两个抢占优先级位,和一个子优先级位

加上子优先级,现在有两个不同的优先级中断:用<抢占优先级>.<子优先级>作为它的符号.:

  • 抢占优先级 定义如果一个中断可以嵌套/中断一个正在执行的中断。请记住,一个较低的抢占优先级数意味着更高的紧急度。例如,一个2.1的中断可以嵌套/中断一个正在执行的3.0的中断。

  • 子优先级 被使用当多个相同抢占优先级的中断挂起时,较低子优先级(较高紧急度)的中断会被首先执行。 例如,如果3.1和3.0正在挂起, 那么3.0将先被执行。 这也意味着3.0的中断不能中断/嵌套另一个3.1的中断。
第 12 段(可获 2.43 积分)

为了改变子优先级的位数 ... 

void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)

... 被使用。虽然子优先级提供了强大的灵活性,但是许多系统并不使用它们,因为它们增加了复杂度。更糟糕的是,一些库使用非标准的子优先级设置。在这种情况下 ...

 

</pre>
<pre>NVIC_SetPriorityGrouping(0); 

... 禁掉了优先级。

中断和设备

多个中断使能的设备 (例如 UART, USB, etc)可以分配相同的优先级,所以在Cortex-M上它们不必是唯一分配的。

复位后,中断是禁用的,所有的中断优先级设置为0。

第 13 段(可获 1.04 积分)

为了使能给定设备的中断 (例如 I²C中断),需要以下步骤:

  1. 可选的: 在NVIC中设置所需中断的优先级。
  2. 使能设备的中断(通常是一个设备特定的位,例如I²C 外设寄存器的位).
  3. 在NVIC中使能中断。

I²C中断的一个例子:

#define I2C1_IRQn  24 /* device specific interrupt for I2C */
#define I2C1_BASE  (0x40067000u)  /* address of peripheral */
#define I2C1       ((I2C_Type *)I2C1_BASE) /** Peripheral I2C0 base pointer */

NVIC_SetPriority(I2C1_IRQn, 1); /* set I2C interrupt level (note: 1 is *not* shifted! */
I2C1->C1 |= I2C_C1_IICIE_MASK; /* enable device specific interrupt flag */
NVIC_EnableIRQ(I2C1_IRQn); /* Enable NVIC interrupt */ 
第 14 段(可获 0.78 积分)

要关闭设备中断,简单用一个NVIC调用禁掉它:

NVIC_DisableIRQ(I2C1_IRQn); /* Enable NVIC interrupt */ 

事实上,有两种NVIC-和设备特定的中断使能/禁止位可能会引起混淆。我建议是在做任意设备寄存器配置前先禁止设备的内部中断位。这确保设备没有产生任何中断给NVIC。要打开/关闭中断,使用NVIC中断使能/禁止位 (NVIC_ISER and NVIC_ICER).

中断屏蔽

NVIC不仅允许设置中断优先级,它也允许你对每个中断一个个的使能/禁止。但对于临界区或原子访问,必须关掉所有的中断。

第 15 段(可获 1.48 积分)

PRIMASK寄存器

这里讨论的所有核心在PRIMASK (Primary Mask) 寄存器中都有‘I’ (中断) 位:

PRIMASK Register

PRIMASK寄存器

将这位置1则屏蔽(禁止)中断。有一个简单的汇编指令可以完成:

__asm volatile("cpsid i");  /* disable interrupts */

反之,清除这位则使能(全局的)所有中断:

__asm volatile("cpsie i"); /* enable interrupts */

如果使用CMSIS库,可用如下函数:

void __enable_irq(void);
void __disable_irq(void);

禁止系统的所有中断会增加中断延迟时间,所以这个必须越短越好。

第 16 段(可获 0.93 积分)

BASEPRI寄存器

M3/M4/M7核心(不是M0/M0+!)有另一个强大的特征:BASEPRI (Base Priority Mask) 寄存器.

BASEPRI Register

BASEPRI寄存器

BASEPRI寄存器是一个屏蔽寄存器,屏蔽所有中断优先级数值上等于或者小于BASEPRI寄存器值的中断

比如:

  • BASEPRI 设置为3:禁止中断优先级为3、2、1的中断。
  • BASEPRI 设置为5:禁止中断优先级为5、4、3、2、1的中断。

因为BASEPRI 是一个屏蔽寄存器,设置为0表示中断没有被屏蔽,因此它是使能的。这意味着不能屏蔽优先级位0的中断!使用PRIMASK寄存器去禁止优先级为0的中断。

第 17 段(可获 1.36 积分)

CMSIS提供以下函数设置BASEPRI寄存器:

__set_BASEPRI(priority);

使用BASEPRI寄存器它能屏蔽到一定级别上的中断。这对于系统的一个好的中断部分是至关重要的,FreeRTOS利用了BASEPRI的设置。文章的第二部分将会进行详细说明。

总结

ARM NVIC中断系统是复杂的,并且在CMSIS API的很多地方,它们并不直观,他们使用一个倒置的优先级而不是紧急度模式。移位的优先级值对比没有移位的优先级值会导致许多微妙的错误和问题,我在很多项目上见过这样的错误和问题。 在ARMv6-M、 ARMv7-M和ARMv7E-M之间, 中断系统有些相似和兼容, 但仍然存在差异。供应商库在处理中断或设置一些分组上可能不一致,所以应仔细检查它们的实现方式。

第 18 段(可获 1.83 积分)

了解中断系统以及如何使用它是嵌入式系统的一个重要组成部分。中断系统和控制器对驱动和中间件如何使用中断会产生一定的影响。

在RTOS中理解并掌握中断对确保正确的操作和最小化中断延迟是至关重要的。在这篇文章的第2部分,我将介绍FreeRTOS如果使用ARM Cortex-M中断,以及它对应用程序的涵义。

到目前为止,我希望这部分已经对你有用。

Happy Cortexing!

第 19 段(可获 1.15 积分)

文章评论

城府很深
排版有误,很难翻译。有没有技术人员帮忙重新排版呀??@可可
可可
这篇文章是你提交的,你可以点右侧的编辑原文重新编辑的:)
城府很深
文章已经开始翻译不能弄回复 @可可 的 #19074# 评论

可可
你说文章开始翻译后就不能编辑原文了?
城府很深
是啊 不能编辑