FreeRTOS的学习(一)——STM32上的移植问题
FreeRTOS的学习(二)——任务优先级问题
FreeRTOS的学习(三)——中断机制
FreeRTOS的学习(四)——列表
FreeRTOS的学习(五)——系统延时
FreeRTOS的学习(六)——系统时钟
FreeRTOS的学习(七)——1.队列概念
FreeRTOS的学习(七)——2.队列入队源码分析
FreeRTOS的学习(七)——3.队列出队源码分析
FreeRTOS的学习(八)——1.二值信号量
FreeRTOS的学习(八)——2.计数型信号量
FreeRTOS的学习(八)——3.优先级翻转问题
FreeRTOS的学习(八)——4.互斥信号量
FreeRTOS的学习(九)——软件定时器
FreeRTOS的学习(十)——事件标志组
FreeRTOS的学习(十一)——任务通知
FreeRTOS的任务优先级在之前已经进行了比较详尽的讨论,对此也有了一个清晰的认知。然而FreeRTOS的优先级始终是针对其本身的任务而言的,那么与STM32的内核的中断控制优先级之间是有什么联系嘛?还是有其他的一些方式将FreeRTOS与STM32的中断进行融合呢?
文中涉及到的PenSV等任务切换的概念将在以后进行讲解补充。
很长时间没深入接触32了,之前好多学的东西都忘记了,借着学习操作系统的时候趁机巩固一下以前的知识。
STM32f1用的ARM的M3内核,所以对于中断的设置,也是继承于内核的。ARM cortex_m3 内核支持 256 个中断(16个内核+240 外部)和可编程 256 级中断优先级的设置,中断控制和中断优先级控制寄存器(NVIC、SYSTICK 等)也都属于cortex_m3 内核的部分。STM32虽然采用了CM3内核,但是并没有全部搬用,CM3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有 256级的可编程中断设置。STM32有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级。
STM32支持的68个外部中断通道已经固定的分配给响应的外部通道,如(DMA,TIM等)。每个中断通道都有自己的中断优先级控制字节PRI_n(8位,但在STM32中只是用高四位),每4个通道的8位中断优先级控制字PRI_n构成一个32位的优先级寄存器(Priority Register)。68个外部通道可以构成17个32位的优先级寄存器。
对于4bit的中断优先级控制位可以进行对抢占式优先级和响应式优先级(又称子优先级、亚优先级)进行分配,其中抢占式优先级在高位,响应式在低位,可以通过设置AIRCR(Application Interrupt / Reset Control Register)寄存器的[10:8]但来确认抢占式和响应式分别能在4bit的中断优先级控制位中占到几个通过分配可以有以下几种情况。
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority
*/============================================================================================================================
NVIC_PriorityGroup | NVIC_IRQChannelPreemptionPriority | NVIC_IRQChannelSubPriority | Description
============================================================================================================================
NVIC_PriorityGroup_0 | 0 | 0-15 | 0 bits for pre-emption priority
| | | 4 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_1 | 0-1 | 0-7 | 1 bits for pre-emption priority
| | | 3 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_2 | 0-3 | 0-3 | 2 bits for pre-emption priority
| | | 2 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_3 | 0-7 | 0-1 | 3 bits for pre-emption priority
| | | 1 bits for subpriority
----------------------------------------------------------------------------------------------------------------------------
NVIC_PriorityGroup_4 | 0-15 | 0 | 4 bits for pre-emption priority
| | | 0 bits for subpriority
============================================================================================================================
通过对AIRCR[10:8]写入不同的值就可以设定不同的优先级分组情况。这里值得注意的是,由于在FreeRTOS中没有子优先级的概念,所以一般只使用抢占式优先级,为了保证抢占式优先级的个数足够多,我们通常就采用NVIC_PriorityGroup_4作为NVIC的分组。
在了解了STM32如何配置中断后,我们接下来回到FreeRTOS中来,首先看之前copy原子的配置文件中部分有关中断的内容。
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级
首先看第一个宏定义判断,实际上就是来配置使用的MCU的中断优先级可以配置几位的。上面我们已经讨论过了,对于STM32可以配置4位,不同的器件和厂家都可能会给出不一样的配置,所以在移植到其他芯片时要注意。
这里的__NVIC_PRIO_BITS被配置为4。
configLIBRARY_LOWEST_INTERRUPT_PRIORITY配置的值是15,其代表了最低的中断优先级,这里要区分一下,这个优先级是针对STM32 的中断而言的,而非是FreeRTOS的任务优先级。我们使用的MCU现在最大配置16个抢占式优先级,所以最小的优先级大小为15,最大为0。
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY这个宏是用来定义FreeRTOS系统可管理的最大优先级,也就是说>5的优先级(这里的优先级指0-15的值,0最大,15最小。也就是说系统可管理的优先级数为11个)不归FreeRTOS管理。那么FreeRTOS是如何实现这个功能的呢?看下面的代码:
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical section. */
/* *INDENT-OFF* */
msr basepri, ulNewBASEPRI
dsb
isb
/* *INDENT-ON* */
}
}
/*-----------------------------------------------------------*/
通过汇编向BASEPRI传送ulNewBASEPRI的内容,也就是configMAX_SYSCALL_INTERRUPT_PRIORITY,该汇编可以屏蔽低于某个阈值的中断,也就是屏蔽优先级<5的中断,也就是5-15的优先级,而优先级为0-4的中断是不可以屏蔽的。
那么这个汇编是在什么时候用的呢?其实大家接触过FreeRTOS的代码就可以发现每次初始化开始任务时,例程里会有一个**taskENTER_CRITICAL()**函数,这个函数的意思是进入临界区,这个概念我一开始是不明白的,直到研究到这里,回看的时候才发现了他的作用。接下来对临界区进行一些介绍。
上述的所谓临界区,又称临界段代码,是指哪些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程不能被打断。具体在净土临界段代码的操作就是开始时关闭中断,处理完之后打开中断。FreeRTOS 与临界段代码保护有关的函数有 4 个:taskENTER_CRITICAL() 、taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR()、taskEXIT_CRITICAL_FROM_ISR()。这四个函数的前两个用于任务级的临界段代码保护,诸如我们的开始任务初始化,后两个是中断级的临界段代码保护。
对于任务级临界代码保护,我们可以继续上面的**taskENTER_CRITICAL()**函数的讨论,可以发现如下代码段:
#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 portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
经过层层的嵌套后,这个进入临界区的宏函数实际上就是对BASEPRI寄存器进行操作,也就是开始时先屏蔽所有的FreeRTOS可管理的中断,然后等待开始任务初始化之后再退出临界区taskEXIT_CRITICAL(),也就是对BASEPRI传送0,即开启所有的FreeRTOS可管理的中断。这
值得说明的是上面的代码段中有一个全局变量uxCriticalNesting,这个变量的作用是每次进入临界区时他就会计数,因为系统中会有很多地方用到临界区,如果每次一个任务结束就开启中断,其他正在临界区内进行初始化的任务就可能会被打断,因此,这个变量的作用就是计数总共的进入临界区的任务,然后当一个任务退出临界区后,他会减1,直到为0,就说明所有的任务都出临界区了,此时就可以开启中断了。
对于中断级临界代码保护是在中断服务函数中进行不想被其他中断打断的事情时(只能屏蔽FreeRTOS可管理的中断)使用,相关的代码如下:
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portSET_INTERRUPT_MASK_FROM_ISR()
ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
vPortSetBASEPRI(x)
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
* section. */
/* *INDENT-OFF* */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
/* *INDENT-ON* */
}
return ulReturn;
}
可以发现中断级临界段代码保护与任务级不一样的地方是会返回一个ulReturn,这个返回值保存了BASEPRI在被写入新内容前的值,这样做的目的是需要将这个值在退出临界区时,也就是重新开启中断时,重新将BASEPRI之前的值写入,类似于中断现场保护,恢复现场的作用,同样是可以用于临界代码保护嵌套的,因为当前的中断状态是会被保护的。
这部分就是FreeRTOS开关中断的方法以及临界段代码保护的一些内容。
接下来我们继续回到宏定义的讨论中来,前面插入了临界段的概念,进行了一定的说明。
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
configKERNEL_INTERRUPT_PRIORITY宏是用来设置内核中断优先级的,通过左移8 - configPRIO_BITS,将configLIBRARY_LOWEST_INTERRUPT_PRIORITY 移到高四位。这种移位的方式让我们可以更直观的设计想要的优先级,不要考虑移位的麻烦操作。宏configKERNEL_INTERRUPT_PRIORITY可以用来设置PendSV和滴答定时器的优先级,定义可以在port.c中找到:
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
那么为什么要将configKERNEL_INTERRUPT_PRIORITY 左移16位和24位呢?因为PendSV 和 SysTcik 的中断优先级设置是操作 0xE000_ED20 地址
的,一个地址代表一个8位寄存器, 0xE000_ED20~ 0xE000_ED23可以组成一个32位的寄存器,而SysTick 和 PendSV 的优先级寄存器分别对应这个 32位数据的最高 8 位和次高 8 位,故要进行移位。PendSV和 SysTick 优先级的设置见以下代码:
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
上面的代码直接向地址portNVIC_SHPR3_REG 写入优先级数据,其中portNVIC_SYSPRI2_REG是一个宏,定义为一个32位的地址。也就是0xE000ED20 。
#define portNVIC_SHPR3_REG ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
可以看到在FreeRTOS中 PendSV 和 SysTick 的中断优先级都是最低的。
configMAX_SYSCALL_INTERRUPT_PRIORITY的移位和寄存器写入操作与configKERNEL_INTERRUPT_PRIORITY一致。这个宏设置的目的是为了让低于此优先级的中断可以安全的调用FreeRTOS的API函数,那么有人会问了,如果调用了会怎么样?,很简单,FreeRTOS会报错,也就是一开始就提到过的断言configASSERT,会输出相对的错误。在经过实践可以发现,FreeRTOS并不能禁止高于设定优先级的中断运行,但是可以禁止该中断函数调用自己的API函数。如果我们有对实时性要求很高的任务就可以使用高优先级的中断,这样我们才能保证实时性。
借用原子的一张中断配置的说明图:
到这实质上我们也基本解决了FreeRTOS的中断和STM32中断之间的桥梁问题,FreeRTOS通过管理中断对自己API函数的访问,来控制什么样的中断归于系统操作。FreeRTOS需要留出高优先级的中断优先级给需要高实时性要求的任务,这种情况下的中断不会被FreeRTOS的任务调度所影响。另外归FreeRTOS管理的中断事件中恢复某项任务从挂起态被唤醒到就绪态时,会返回值xYieldRequired,该值的真假根据唤醒的任务是否比当前任务的优先级高,如果高,则后续需要调用portYIELD_FROM_ISR()来立即执行唤醒的任务,反之则正常进入就绪态。这里是根据实时性做出的一个小特点,所以这也再次验证了实时嵌入式系统的概念。
以上就是今天总结学习的内容,本文回顾了CM3内核的中断一些配置内容,另外介绍了FreeRTOS临界区的内容,中断优先级的配置以及和STM32之间的信息建立的方式。期待应用FreeRTOS完成一个FOC的高实时性项目,并能完成一些数据的传递和人机交互设计,如果顺利的话,我到时候也会把项目开源到GitHub。相信FreeRTOS是一个好的选择!