目录

1. 异常与中断的基本概念

1.1 异常分类

1.2 中断概述

1.2.1 中断处理宜短暂

1.2.2 临界段影响中断实时性

1.3 中断硬件基础

1.3.1 外设

1.3.2 中断控制器

1.3.3 CPU

1.4 中断发生环境

1.4.1 在任务上下文中发生中断

1.4.2 在中断上下文发生中断

1.5 中断相关术语

2. Cortex-M体系结构中断简介

2.1 NVIC简介

2.2 中断向量表

2.3 中断优先级分组

2.3.1 中断优先级

2.3.2 优先级分组

2.4 中断屏蔽寄存器

2.4.1 PRIMASK

2.4.2 FAULTMASK

2.4.3 BASEPRI

2.5 进入异常流程

2.6 退出异常流程

3. FreeRTOS中断配置

3.1 configPRIO_BITS

3.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY

3.3 configKERNEL_INTERRUPT_PRIORITY

3.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

3.5 configMAX_SYSCALL_INTERRUPT_PRIORITY

4. FreeRTOS开关中断

4.1 关闭中断

4.2 打开中断

5. FreeRTOS临界段代码保护

5.1 任务级临界段保护

5.2 中断级临界段保护

6. SMP支持

6.1 打开 / 关闭中断

6.2 任务级临界段保护

6.3 中断级临界段保护


1. 异常与中断的基本概念

1.1 异常分类

① 异常分为同步异常和异步异常

② 同步异常事件是由于执行某些指令而从处理器内部产生的,例如被除数为0异常

③ 异步异常事件的来源是外部硬件装置,中断就属于异步异常

1.2 中断概述

1.2.1 中断处理宜短暂

无论任务具有何种优先级,中断都能打断任务的运行,因此中断一般用于处理紧急事件,而且只做简单的处理(e.g. 标记该事件)

在使用FreeRTOS时,一般建议使用信号量、消息或事件标志组等标记中断的发生,将这些内核对象发布给处理任务,由处理任务再做具体处理

1.2.2 临界段影响中断实时性

FreeRTOS源码中有很多临界段,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时性

因此调用中断屏蔽函数进入临界段时,也需要快进快出

1.3 中断硬件基础

1.3.1 外设

当外设需要请求CPU时,产生一个中断信号,该信号连接至中断控制器

1.3.2 中断控制器

① 中断控制器一方面接收外设中断信号的输入,另一方面会发送中断信号给CPU

② 可以通过对中断控制器编程实现对中断源的优先级、触发方式和打开关闭的控制

③ 在Cortex-M体系结构中,常用的中断控制器为NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)

1.3.3 CPU

CPU会响应中断请求,中断当前正在执行的任务,转而执行中断处理程序

1.4 中断发生环境

1.4.1 在任务上下文中发生中断

如果在运行任务时发生中断,无论任务的优先级,都会打断当前任务的运行,转而运行相应的中断服务函数

1.4.2 在中断上下文发生中断

如果在执行中断服务函数的过程中,有更高优先级的中断源触发中断,根据不同的体系结构有不同的处理方式,典型为如下2种,

① 将新的中断挂起,直到当前中断处理完成后再响应

② 新的高优先级中断打断当前中断处理过程,先响应更高优先级的中断,即允许中断嵌套

说明1:Cortex-M体系结构使用的NVIC支持中断嵌套

说明2:在硬实时环境中,第1种情况是不允许发生的,因为不能尽快响应高优先级中断

1.5 中断相关术语

① 中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断的重要性和紧迫程度,将中断源分为若干个级别

② 中断触发类型:外部中断请求通过一个物理信号发送到NVIC,可以是电平触发或边沿触发

③ 中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备发出了中断请求,这个标志就是中断号

④ 中断处理程序:当外设产生中断请求后,CPU暂停当前任务,转而响应中断申请,即执行中断处理程序

⑤ 中断向量:中断服务程序的入口地址

⑥ 中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储

2. Cortex-M体系结构中断简介

2.1 NVIC简介

① NVIC属于Cortex-M内核,Cortex-M3/4的NVIC最多支持240个中断请求(IRQ)、1个不可屏蔽中断(NMI)、1个滴答定时器中断(SysTick)和多个系统异常

② Cortex-M处理器有多个用于管理中断和异常的寄存器,定义在NVIC和系统控制块(SCB)中,CMSIS将这些寄存器定义为结构体

以CMSIS中的core_cm3.h文件为例,

说明1:SCB / NVIC / SysTick的寄存器都位于系统控制空间(SCS)中

说明2:实际芯片支持的中断数量由芯片厂商裁剪决定

2.2 中断向量表

在Cortex-M体系结构中,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器直接判定中断源,然后直接跳转到相应的固定位置进行处理

下面为CMSIS中Cortex-M3的中断向量表,

当用户需要使用自定义的中断服务函数时,只需要定义与中断向量同名的函数即可

说明:中断向量表重定位

Cortex-M体系结构支持中断向量表重定位,通过设置SCB中的VTOR寄存器实现,下面给出实例(STM32 lib库)

2.3 中断优先级分组

2.3.1 中断优先级

在Cortex-M体系结构中,有3个固定优先级(且均为负数)和256个可编程优先级(NVIC的Interrupt Priority寄存器为8位),但实际的优先级数量由芯片厂商决定,绝大多数芯片都会进行精简设计,以致实际上支持的优先级级数会更少

以STM32为例,只有16个优先级

说明1:裁剪优先级方式

NVIC的Interrupt Priority寄存器为8位,即最多有256个优先级,裁剪的方式就是限制Interrupt Priority寄存器的有效位

以下图为例,在设计芯片时裁剪掉表达优先级的5个低端有效位,则可使用的优先级为8个

说明2:每个优先级寄存器为8位,4个相邻的优先级寄存器可以组合成一个32位寄存器,因此优先级寄存器可以按字节 / 半字 / 字来访问

2.3.2 优先级分组

在确定了Interrupt Priority寄存器的有效位基础上,还有一个优先级分组的概念,即将Interrupt Priority寄存器的比特位划分为抢占优先级 + 亚优先级

共有如下8种优先级分组模式,

说明1:256个优先级,128个抢占等级的由来

如果Interrupt Priority寄存器的8位全部有效,则有256个优先级。但是即使选择分组0,也只有7bit表示抢占优先级,所以只有128个抢占等级

说明2:优先级分组设置

SCB的AIRCR寄存器可设置优先级分组

说明3:芯片可用的优先级分组

以STM32为例,只使用了bit[7:4]表示优先级,所以可用的优先级分组只有3 ~ 7

说明4:STM32的优先级分组设置

可见STM32将优先级寄存器中可用的4位均作为抢占优先级使用,共16个优先级,没有亚优先级

2.4 中断屏蔽寄存器

2.4.1 PRIMASK

PRIMASK寄存器用于禁止除NMI和HardFault之外的所有异常和中断,可使用如下两种方式开关中断

① CPS指令

CPSIE I // 清除PRIMASK寄存器,使能中断
CPSID I // 设置PRIMASK寄存器,禁止中断

② MSR指令

MOVS R0, #1
MSR PRIMASK, R0 // 将PRIMASK寄存器置1,禁止中断MOVS R0, #0
MSR PRIMASK, R0 // 将PRIMASK寄存器清零,使能中断

2.4.2 FAULTMASK

FAULTMASK在PRIMASK的基础上,连HardFault都屏蔽,也是通过两种方式开关中断

① CPS指令

CPSIE F // 清除FAULTMASK寄存器,使能中断
CPSID F // 设置FAULTMASK寄存器,禁止中断

② MSR指令

MOVS R0, #1
MSR FAULTMASK, R0 // 将FAULTMASK寄存器置1,禁止中断MOVS R0, #0
MSR FAULTMASK, R0 // 将FAULTMASK寄存器清零,使能中断

2.4.3 BASEPRI

BASEPRI寄存器用于设置屏蔽优先级的阈值,只屏蔽优先级低于阈值的中断,向该寄存器写0,则停止屏蔽

MOV R0, #0x50
MSR BASEPRI, R0 // 在STM32中,屏蔽优先级小于等于5的中断// 只使用优先级寄存器中的4位MOV R0, #0
MSR BASEPRI, R0 // 将BASEPRI清零,停止屏蔽

2.5 进入异常流程

说明1:Cortex-M中需要保存LR,是因为LR只有一个,并非banked register(在Cortex-A系列中,LR为banked register)

而且保存后LR寄存器还要在异常返回时起特殊作用,这点和Cortex-A系列非常不同。Cortex-A系列异常处理的思路是通过banked LR保存返回地址,然后用LR恢复PC

说明2:进入异常时,LR寄存器值在入栈后,会被设置为特殊的EXC_RETURN值,这个值在异常退出时影响返回动作

经过仿真,Cortex-M在首次进入PendSV异常时,LR值为0xFFFFFFF9,即退出异常时会返回线程模式,并使用MSP

2.6 退出异常流程

说明:将进入异常时设置的特殊LR(即EXC_RETURN值)写入PC,就会进入异常返回流程

在Cortex M中,只有bit 3 & bit 2是可变动的,各种组合情况如下,

3. FreeRTOS中断配置

注意:某些平台并不一定定义了所有配置宏

3.1 configPRIO_BITS

表示使用多少位来表示中断优先级,STM32芯片该宏应该为4

#define configPRIO_BITS  4

3.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY

表示最低优先级,STM32使用4位表示优先级,所以最低优先级为15

#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15

3.3 configKERNEL_INTERRUPT_PRIORITY

FreeRTOS中设置最低优先级时使用的值,由于STM32只使用优先级寄存器的最高4位,所以255(0xFF)就是设置最低优先级

#define configKERNEL_INTERRUPT_PRIORITY  255// 一种更直观的设置方式
#define configKERNEL_INTERRUPT_PRIORITY \
(configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))

3.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

表示FreeRTOS系统可管理的最大优先级,高于该优先级的中断不在系统的管理范围之内,不会被系统屏蔽(当然也就不可以使用FreeRTOS的API)

#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5

说明:对于实时性要求严格的中断可以使用不在FreeRTOS管理范围内的中断优先级,比如四轴飞行器中的壁障检测

3.5 configMAX_SYSCALL_INTERRUPT_PRIORITY

FreeRTOS中设置系统可管理最大优先级的值,也是考虑到STM32只使用优先级寄存器中的4位

#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

4. FreeRTOS开关中断

4.1 关闭中断

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{msr basepri, ulNewBASEPRIdsbisb}
}

在FreeRTOS中关中断,只是屏蔽在系统管理范围内的中断

4.2 打开中断

define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{msr basepri, ulBASEPRI}
}

在FreeRTOS中开中断,就是将BASEPRI寄存器清零,取消屏蔽

5. FreeRTOS临界段代码保护

5.1 任务级临界段保护

// 进入临界段
#define taskENTER_CRITICAL() portENTER_CRITICAL()#define portENTER_CRITICAL() vPortEnterCritical()void vPortEnterCritical( void )
{// 关闭中断portDISABLE_INTERRUPTS();// 增加临界段嵌套计数uxCriticalNesting++;if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}
}// 退出临界段
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()#define portEXIT_CRITICAL() vPortExitCritical()void vPortExitCritical( void )
{// 调用该函数时,临界段嵌套计数必须大于0configASSERT( uxCriticalNesting );// 减少临界段嵌套计数uxCriticalNesting--;// 当临界段嵌套计数减少到0时,打开中断if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();}
}

说明1:任务级临界段保护只能在任务上下文中调用

taskENTER_CRITICAL函数只能在任务上下文中调用(个人觉得是使用方法上的限定,不是理论上的限制),因此当临界段嵌套计数为1时(也就是首次进入临界段时),判断当前是否在中断上下文中,如果在,则使用assert断言报错

说明2:任务级临界段保护通过临界段嵌套计数支持临界段的嵌套进入

5.2 中断级临界段保护

// 进入临界段
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}// 返回之前的中断屏蔽状态return ulReturn;
}// 退出临界段
#define taskEXIT_CRITICAL_FROM_ISR( x ) \
portCLEAR_INTERRUPT_MASK_FROM_ISR( x )#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

说明:中断级临界段保护的要点,是在进入临界段时会返回当前中断屏蔽状态,并在退出临界段时恢复之前的中断屏蔽状态

6. SMP支持

说明:每篇笔记的SMP支持章节,以ESP FreeRTOS(xtensa体系结构)为例,分析如何修改FreeRTOS使其支持SMP

6.1 打开 / 关闭中断

// 关闭中断
#define portDISABLE_INTERRUPTS() \
do { XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); \portbenchmarkINTERRUPT_DISABLE();\
} while (0)// 打开中断
#define portENABLE_INTERRUPTS() \
do { portbenchmarkINTERRUPT_RESTORE(0); XTOS_SET_INTLEVEL(0); } while (0)

打开 / 关闭中断的命令随体系结构不同,但是思路是一致的,都是设置中断屏蔽寄存器

6.2 任务级临界段保护

// 进入临界段
#define taskENTER_CRITICAL(mux) portENTER_CRITICAL(mux)#define portENTER_CRITICAL(mux)  vPortEnterCritical(mux)void __attribute__((optimize("-O3"))) vPortEnterCritical(portMUX_TYPE *mux)
{// 关中断,并返回之前的中断屏蔽状态BaseType_t oldInterruptLevel = portENTER_CRITICAL_NESTED();// 获取自旋锁vPortCPUAcquireMutex( mux );// 获取CPU编号BaseType_t coreID = xPortGetCoreID();// 临界段嵌套计数加1// 临界段嵌套计数按CPU个数分配BaseType_t newNesting = port_uxCriticalNesting[coreID] + 1;port_uxCriticalNesting[coreID] = newNesting;if( newNesting == 1 ){// 如果是首次进入临界段,保存之前的中断屏蔽状态port_uxOldInterruptState[coreID] = oldInterruptLevel;}
}// 退出临界段
#define taskEXIT_CRITICAL(mux) portEXIT_CRITICAL(mux)#define portEXIT_CRITICAL(mux)  vPortExitCritical(mux)void __attribute__((optimize("-O3"))) vPortExitCritical(portMUX_TYPE *mux)
{// 释放自旋锁vPortCPUReleaseMutex( mux );BaseType_t coreID = xPortGetCoreID();BaseType_t nesting = port_uxCriticalNesting[coreID];// 递减对应CPU核的临界段嵌套计数if(nesting > 0U){nesting--;port_uxCriticalNesting[coreID] = nesting;// 当该核临界段嵌套计数为0,恢复之前的中断屏蔽状态if( nesting == 0U ){portEXIT_CRITICAL_NESTED(port_uxOldInterruptState[coreID]);}
}

说明1:自旋锁的引入

在单核FreeRTOS中,进入临界段只需要关闭中断即可,因为关闭中断后PendSV中断也不会响应,则任务切换也被终止,所以不会有任务 & 中断进入当前临界区

但是在SMP中,单纯关闭一个核的中断无法达到保护临界区的目的,因为另一个核仍在运行,所以ESP FreeRTOS中引入了自旋锁作为进出临界段函数的参数

自旋锁类型如下,

获取与释放自旋锁的操作如下,

而具体自旋锁的实现,则依据不同的体系结构而异

说明2:按CPU保存临界段嵌套计数 & 之前的中断屏蔽状态

6.3 中断级临界段保护

// 进入临界段
#define taskENTER_CRITICAL_ISR(mux) portENTER_CRITICAL_ISR(mux)#define portENTER_CRITICAL_ISR(mux) vPortEnterCritical(mux)// 退出临界段
#define taskEXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL_ISR(mux)#define portEXIT_CRITICAL_ISR(mux)  vPortExitCritical(mux)

在ESP FreeRTOS中,中断级临界段保护和任务级临界段保护的实现本质上是一样的,但是使用时仍然遵循FreeRTOS的标准,区分不同的上下文

个人:引入自旋锁之后,解决了多核间的临界段保护问题,但是由于自旋锁为忙等锁,因此对性能是有影响的

为了将降低对性能的影响,对于不同的共享资源使用了不同的自旋锁处理多核互斥,但是在实现中xTaskQueueMutex自旋锁保护的任务队列资源使用非常广泛,所以也只是一种缓解,还是要依靠临界段的快进快出

FreeRTOS源码分析与应用开发01:中断配置与临界段相关推荐

  1. FreeRTOS源码分析与应用开发02:任务管理

    目录 1. 任务概述 1.1 任务表示 1.2 任务状态 1.2.1 运行态 1.2.2 就绪态 1.2.3 阻塞态 1.2.4 挂起态 1.3 任务优先级 1.3.1 FreeRTOS优先级配置 1 ...

  2. FreeRTOS源码分析与应用开发04:消息队列

    目录 1. 队列结构 2. 创建队列 2.1 动态创建队列 2.1.1 xQueueCreate函数 2.1.2 xQueueGenericCreate函数 2.1.3 xQueueGenericRe ...

  3. FreeRTOS源码分析与应用开发07:事件标志组

    目录 1. 概述 2. 事件标志组类型 3. 创建事件标志组 4. 删除事件标志组 5. 设置事件标志位 5.1 任务级设置 5.2 中断级设置 6. 清除事件标志位 6.1 任务级清除 6.2 中断 ...

  4. FreeRTOS源码分析与应用开发08:任务通知

    目录 1. 概述 1.1 任务通知概念 1.2 任务通知控制结构 2. 发送任务通知 2.1 任务级发送 2.2 中断级发送 2.2.1 xTaskNotifyFromISR函数 2.2.2 vTas ...

  5. FreeRTOS源码分析与应用开发06:软件定时器

    目录 1. 概述 1.1 软件定时器 & 硬件定时器 1.2 软件定时器精度 1.3 单次模式 & 周期模式 2. 软件定时器组件 2.1 定时器任务 2.2 定时器列表 2.3 定时 ...

  6. FreeRTOS源码分析与应用开发05:信号量

    目录 1. 信号量概述 1.1 信号量概念 1.2 4种信号量 1.2.1 二值信号量 1.2.2 计数信号量 1.2.3 互斥信号量 1.2.4 递归互斥信号量 1.3 信号量相关控制结构 1.3. ...

  7. FreeRTOS源码分析与应用开发09:低功耗Tickless模式

    目录 1. STM32F4低功耗模式简介 2. Tickless模式详解 2.1 如何降低功耗 2.2 关闭SysTick的问题与解决方案 2.2.1 关闭SysTick导致系统节拍计数器停止 2.2 ...

  8. FreeRTOS源码分析与应用开发11(完):编译、链接与部署

    目录 1. 存储设备布局 2. 链接器脚本 2.1 链接器脚本生成 2.2 链接器脚本分析 2.2.1 分散加载文件 2.2.2 加载区 & 运行区 2.2.3 ER_IROM1运行区分析 2 ...

  9. FreeRTOS源码分析与应用开发10:内存管理

    目录 1. 概述 1.1 RTOS中内存分配特点 1.2 内存堆(heap space)来源 1.2.1 ucHeap数组 1.2.2 链接器设置的堆 1.2.3 多个非连续内存堆 1.3 关于字节对 ...

最新文章

  1. “因为这 4 个回答,我决定录用这位软件工程师!”
  2. PreparedStatement动态参数的引入
  3. oracle中的open,Oracle 深入分析Open过程
  4. Spring AOP基础—JDK动态代理
  5. android 圆环温度控件,android 圆环倒计时控件
  6. 深入浅出 ASP.NET Core 与 Docker 入门课程说明
  7. 题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】
  8. laravel 项目迁移_在Laravel迁移
  9. ionic 去掉启动页的加载动画 菊花转
  10. 《Essential C++》笔记之(static)静态类成员
  11. 2020年10月“省时查报告”十大热门报告盘点(附下载链接)
  12. 初探HTML5.x新特性《dialog》标签
  13. 数据库原理及应用(思维导图、索引、合集)
  14. 使用websocket实现协同编辑
  15. 数据集的文字标签(label)转成数字标签
  16. ROS导航【01】: move_base包(导航和路径规划)
  17. 《运营力——微信公众号 设计 策划 客服 管理 一册通》一一1.2 团队岗位介绍...
  18. 全景图的种类及opencv实现
  19. 关于adsl宽带猫的一个奇怪问题
  20. NULL与nullptr

热门文章

  1. 远程桌面配置php,Win2008 R2实现多用户远程连接设置方法(图)
  2. centos安装python3.6_Centos安装python3.6和pip步骤记录
  3. Python中使用xpath获取select option的每一行的text和value
  4. kafka集群部署成功后,创建生产者往指定主题里面发送消息时出错
  5. Spring Cloud 负载均衡
  6. graphpad如何加标注_如何以YOLOv3训练自己的数据集 以小蕃茄为例
  7. python的pip_Python3中安装pip3
  8. 输出100以内所有的质数
  9. vue 请求在子组件加载后了_从零单排vue第九课--Vue实例及生命周期
  10. mysql主从io为no_mysql主从同步错误解决和Slave_IO_Running: NO