Chinaunix首页 | 论坛 | 博客
  • 博客访问: 831122
  • 博文数量: 281
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2770
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-02 19:45
个人简介

邮箱:zhuimengcanyang@163.com 痴爱嵌入式技术的蜗牛

文章分类
文章存档

2020年(1)

2018年(1)

2017年(56)

2016年(72)

2015年(151)

分类: 嵌入式

2015-09-24 11:43:12


该篇目的是讲清楚在FreeRTOS如何配置中断优先级的问题。




有效优先级

首先需要清楚有效优先级的总数,这取决于微控制器制造商怎么使用Cortex内核。所以,并不是所有的Cortex-M内核微处理器都具有相同的中断优先级级别。 Cortex-M构架自身最多允许256级可编程优先级(优先级配置寄存器最多8位,所以优先级范围从0x00~0xFF),但是绝大多数微控制器制造商只是使用其中的一部分优先级。比如,TI Stellaris Cortex-M3和Cortex-M4微控制器使用优先级配置寄存器的3个位,能提供8级优先级。再比如,NXP LPC17xx Cortex-M3微控制器使用优先级配置寄存器的5个位,能提供32级优先级;而ST公司Stm32F103系列使用配置寄存器的4个位,最多有16个优先级(0-15)。


如果你的工程包含CMSIS库头文件(这里是指Cortex M3的CMSIS库头文件,也就是core_cm3.h,可以在ARM官网下载),则头文件中的宏__NVIC_PRIO_BITS定义使用多少优先级寄存器的位(默认是4位)。

应用到RTOS:
RTOS中断嵌套方案将有效的中断优先级分成两组:一组可以通过RTOS临界区屏蔽,另一组不受RTOS影响,永远都是使能的。
configMAX_SYSCALL_INTERRUPT_PRIORITY在FreeRTOSConfig.h中配置,定义两组中断优先级的边界。逻辑优先级高于此值的中断不受RTOS影响。
最优值取决于微控制器使用的优先级配置寄存器的位数。

 

与数值相反的优先级值和逻辑优先级设置

Cortex-M 硬件详述

有必要先解释一下优先级值和逻辑优先级:在Cortex-M内核中,假如有8级优先级,我们说优先级值是0~7,但数值最大的优先级7却代表着最低的逻辑优先级。很多使用传统传统中断优先级架构的工程师会觉得这样比较绕,违反直觉。以下内容提到的优先级要仔细区分是优先级数值还是逻辑优先级。

接下来需要清楚的是,在Cortex-M内核中,一个中断的优先级数值越低,逻辑优先级却越高。比如,中断优先级为2的中断可以抢占中断优先级为5的中断,但反过来就不行。换句话说,中断优先级2比中断优先级5的优先级更高。

这是Cortex-M内核最容易让人犯错之处,因为大多数的非Cortex-M内核微控制器的中断优先级表述是与之相反的。

应用到 RTOS

以“FromISR”结尾的FreeRTOS函数是具有中断调用保护的(执行这些函数会进入临界区),但是就算是这些函数,也不可以被逻辑优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断服务函数调用。(宏 configMAX_SYSCALL_INTERRUPT_PRIORITY定义在头文件FreeRTOSConfig.h中)。
因此,任何使用RTOS API函数的中断服务例程的Cortex-M架构的芯片的中断优先级的数值应该:大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY宏的值。这样就能保证中断的逻辑优先级等于或低于configMAX_SYSCALL_INTERRUPT_PRIORITY。

Cortex中断默认情况下有一个数值为0的优先级。大多数情况下0代表最高级优先级。因此,绝对不可以在优先级为0的中断服务例程中调用RTOS API函数


Cortex-M 内部优先级概述

Cortex-M 硬件详述

Cortex-M内核的中断优先级寄存器是以最高位(MSB)对齐的。比如,如果使用了3位来表达优先级,则这3个位位于中断优先级寄存器的bit5、bit6、bit7位。剩余的bit0~bit4可以设置成任何值,但为了兼容,最好将他们设置成1.

Cortex-M优先级寄存器最多有8位,如果一个微控制器只使用了其中的3位,那么这3位是以最高位对齐的,见下图:



某微控制器只使用了优先级寄存器中的3位,下图展示了优先级数值5(二进制101B)是怎样在优先级寄存器中存储的。如果优先级寄存器中未使用的位置1,下图也展示了为什么数值5(二进制0000 0101B)可以看成数值191(二进制1011 1111)的。


某微控制器只使用了优先级寄存器中的4位,下图展示了优先级数值5(二进制101B)是怎样在优先级寄存器中存储的。如果优先级寄存器中未使用的位置1, 下图也展示了为什么数值5(二进制0000 0101B)可以看成数值95(二进制0101 1111)的。



结论:
在中断服务例程中调用RTOS API函数的中断逻辑优先级必须低于或等于configMAX_SYSCALL_INTERRUPT_PRIORITY(低逻辑优先级意味着高优先级数值)。

注意:

CMSIS以及不同的微控制器供应商提供了可以设置某个中断优先级的库函数。一些库函数的参数使用最低位对齐,另一些库函数的参数可能使用最高位对齐,所以,使用时应该查阅库函数的应用手册进行正确设置。


可以在FreeRTOSConfig.h中设置宏configMAX_SYSCALL_INTERRUPT_PRIORITY和 configKERNEL_INTERRUPT_PRIORITY的值。这两个宏需要根据Cortex-M内核自身的情况进行设置,要以最高有效位对齐。比如某微控制器使用中断优先级寄存器中的3位,设置configKERNEL_INTERRUPT_PRIORITY的值为5, 则代码为:

       #define  configKERNEL_INTERRUPT_PRIORITY     (5<<(8-3))

对于每一个官方FreeRTOS演示例程,这也是在FreeRTOSConfig.h中要设置宏 configKERNEL_INTERRUPT_PRIORITY为最低优先级时,为什么要将它设置为255(1111 1111B)的原因。使用这种方式指定这个值的原因是:FreeRTOS内核是直接在Cortex-M内核硬件上运行的(没有使用第三方接口库函数),要比大多数库函数先运行。




临界区

Cortex-M 硬件详述

RTOS内核使用Cortex-M内核的BASEPRI寄存器来实现临界区(注:BASEPRI为优先级屏蔽寄存器,优先级数值大于或等于该寄存器的中断都会被屏蔽,优先级数值越大,逻辑优先级越低,但是为零时不屏蔽任何中断)。这允许RTOS内核可以只屏蔽一部分中断,因此可以提供一个灵活的中断嵌套模式。

那些需要在中断调用时保护的API函数,FreeRTOS使用寄存器BASEPRI实现中断保护临界区。当进入临界区时,将寄存器BASEPRI的值设置成configMAX_SYSCALL_INTERRUPT_PRIORITY,当退出临界区时,将寄存器BASEPRI的值设置成0。很多Bug反馈都提到,当退出临界区时不应该将寄存器设置成0,应该恢复它之前的状态(之前的状态不一定是0)。但是Cortex-M NVIC决不会允许一个低优先级中断,去打断当前正在执行的高优先级中断,不管BASEPRI寄存器中是什么值。与进入临界区前先保存BASEPRI的值,退出临界区再恢复的方法相比,退出临界区时将BASEPRI寄存器设置成0的方法可以获得更快的执行速度。

 

应用到RTOS kernel

RTOS内核通过写configMAX_SYSCALL_INTERRUPT_PRIORITY的值到BASEPRI寄存器的方法创建临界区。中断优先级0(具有最高的逻辑优先级)不能被BASEPRI寄存器屏蔽,因此,configMAX_SYSCALL_INTERRUPT_PRIORITY绝不可以设置成0。


举个例子:
有个工程配置的不错,在FreeRTOSConfig.h中配置:

点击(此处)折叠或打开

  1. #ifndef FREERTOS_CONFIG_H
  2. #define FREERTOS_CONFIG_H

  3. #ifndef __IASMARM__
  4. /* For SystemCoreClock */
  5. #include "board.h"
  6. #endif

  7. /*-----------------------------------------------------------
  8. * Application specific definitions.
  9. *
  10. * These definitions should be adjusted for your particular hardware and
  11. * application requirements.
  12. *
  13. * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
  14. * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
  15. *----------------------------------------------------------*/

  16. #define configUSE_PREEMPTION 1
  17. #define configUSE_IDLE_HOOK 1
  18. #define configMAX_PRIORITIES ( (UBaseType_t) 8 )
  19. #define configUSE_TICK_HOOK 0
  20. #define configCPU_CLOCK_HZ ( (uint32_t) SystemCoreClock )

  21. #define configTICK_RATE_HZ ( (TickType_t) 100)

  22. #define configMINIMAL_STACK_SIZE ( (uint16_t) 128 )
  23. #ifdef __CODE_RED
  24. #define configTOTAL_HEAP_SIZE ( (size_t) ( 4 * 1024 ) )
  25. #else
  26. #define configTOTAL_HEAP_SIZE ( (size_t) ( 0 ) )
  27. #endif
  28. #define configMAX_TASK_NAME_LEN ( 20 )
  29. #define configUSE_TRACE_FACILITY 1
  30. #define configUSE_16_BIT_TICKS 0
  31. #define configIDLE_SHOULD_YIELD 1
  32. #define configUSE_CO_ROUTINES 0
  33. #define configUSE_MUTEXES 1
  34. #define configUSE_TICKLESS_IDLE 1

  35. #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

  36. #define configUSE_COUNTING_SEMAPHORES 1
  37. #define configUSE_ALTERNATIVE_API 0
  38. #define configCHECK_FOR_STACK_OVERFLOW 0
  39. #define configUSE_RECURSIVE_MUTEXES 1
  40. #define configQUEUE_REGISTRY_SIZE 10
  41. #define configGENERATE_RUN_TIME_STATS 0

  42. /* Set the following definitions to 1 to include the API function, or zero
  43.    to exclude the API function. */

  44. #define INCLUDE_vTaskPrioritySet 1
  45. #define INCLUDE_uxTaskPriorityGet 1
  46. #define INCLUDE_vTaskDelete 1
  47. #define INCLUDE_vTaskCleanUpResources 0
  48. #define INCLUDE_vTaskSuspend 1
  49. #define INCLUDE_vTaskDelayUntil 1
  50. #define INCLUDE_vTaskDelay 1
  51. #define INCLUDE_uxTaskGetStackHighWaterMark 1

  52. /* Use the system definition, if there is one */
  53. #ifdef __NVIC_PRIO_BITS

  54.     // 这里 __NVIC_PRIO_BITS 被定义等于 3; 所以优先级从:0-7
  55.     #define configPRIO_BITS __NVIC_PRIO_BITS

  56. #else
  57.     #define configPRIO_BITS 5            /* 32 priority levels */
  58. #endif

  59. /* The lowest interrupt priority that can be used in a call to a "set priority"
  60.    function. */
  61. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x1f
  62. // 上面这句话表示最低优先级 = 7
  63. // 因为__NVIC_PRIO_BITS = 3; 表示优先级范围 0 -7;总共为8个等级;
  64. // 上面这句话改为: 
  65. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x07


  66. /* The highest interrupt priority that can be used by any interrupt service
  67.    routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
  68.    INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
  69.    PRIORITY THAN (higher priorities are lower numeric values. */

  70. // 这个表示最高的优先级 = 2;
  71. // 任何逻辑优先级小于等于这个的优先级都可以调用FreeRTOS API函数 (在数值上应该大于该值。)
  72. #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 2



  73. /* Interrupt priorities used by the kernel port layer itself. These are generic
  74.    to all Cortex-M ports, and do not rely on any particular library functions. */

  75. // 1. configKERNEL_INTERRUPT_PRIORITY为系统心跳时钟中断优先级,为最低优先级。
  76. #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

  77. // 2. configMAX_SYSCALL_INTERRUPT_PRIORITY 为最高优先级,任何freeRTOS API的逻辑断优先级应该小于或者等于该优先级。
  78. #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )


  79. /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
  80.    standard names - or at least those used in the unmodified vector table. */
  81. #define vPortSVCHandler SVC_Handler
  82. #define xPortPendSVHandler PendSV_Handler
  83. #define xPortSysTickHandler SysTick_Handler

  84. #endif /* FREERTOS_CONFIG_H */
__NVIC_PRIO_BITS 在cmsis.h中被重新定义:
#define __NVIC_PRIO_BITS          3            /*!< Number of Bits used for Priority Levels */
这个就表示优先级配置寄存器用3位来表示优先级,所以总的优先级从 0 - 7。如果没有定义,系统默认为4位。

而如果要配置一个串口中断程序,并且在该中断程序会调用FreeRTOS API,则怎么配置呢?
NVIC_SetPriority(UART0_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);

说明: configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1 = 3,表示数值上大于,但是逻辑优先级小于最大优先级,可以调用FreeRTOS API函数了。
注意:这个NVIC_SetPriority函数的定义,有一个参数是真正的 ” 优先级 “ ,里面进行了移位操作。

点击(此处)折叠或打开

  1. /** \brief Set Interrupt Priority

  2.     The function sets the priority of an interrupt.

  3.     \note The priority cannot be set for every core interrupt.

  4.     \param [in] IRQn Interrupt number.
  5.     \param [in] priority Priority to set.
  6.  */
  7. __STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
  8. {
  9.   if(IRQn < 0) {
  10.     SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M System Interrupts */
  11.   else {
  12.     NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */
  13. }


再举个例子 example-1401: 关于STM32F103 : CORTEX-M3
(1)优先级设置方式一:
在stm32中,用4位来配置中断优先级,其中优先级又分为:抢占优先级和子优先级。
设置了不同的组方式分配优先级:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    // 设置NVIC中断分组2, 2位抢占优先级,2为子优先级


#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_InitTypeDef NVIC_InitStructure;
   
    // Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;   // 抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;         // 子优先级为 3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                            // 根据指定的参数初始化NVIC寄存器


(2)优先级设置方式二:
利用 core_cm3.h 中定义的函数:
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)

那么这两者怎么对应起来呢??

对于Sysick使用core_cm3.h的函数,如果写成如下形式:
    NVIC_SetPriority(SysTick_IRQn, 4); 

这时不能显式的看出抢先式优先级与子优先级,写入的优先级需要根据优先级组的配置来选择。
  1. NVIC_SetPriority(SysTick_IRQn, n);  
  2. n=0x00~0x03 设置Systick为抢占优先级0
  3. n=0x04~0x07 设置Systick为抢占优先级1  
  4. n=0x08~0x0B 设置Systick为抢占优先级2  
  5. n=0x0C~0x0F 设置Systick为抢占优先级3   
NVIC_SetPriority函数指定中断优先级的寄存器位(STM32只用4位来表示优先级)的数据。
例如中断优先级组设置为了2,即高2位 用于指定抢占式优先级,低2位用于指定响应优先级,0x00~0x03高2位为0,所以抢占优先级为0;0x04~0x07高2位为1,所以抢占优先级为 1,以此类推。

所以如果以第二种方式来定义
(1)首先需要定义分组情况:
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);    // 设置NVIC中断分组1, 1位抢占优先级,3为子优先级

如果设置成分组1,则1位抢占优先级,3为子优先级,为如下:
0b0000 0000 - 0b0000 0111:    0x00 - 0x07               // 抢占优先级为0,子优先级从0 - 7
0b0000 1000 - 0b0000 1111:    0x08 - 0x0f               // 抢占优先级为1,子优先级为 0 -7
但如果按照这个NVIC_SetPriority()中优先级来定义,优先级都是从0 - 15。这下明白了吧。。

(2)其次可以利用函数NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)指定优先级,具体的抢占优先级和子优先级可以推算出来。





代码修改:
1. 优先级总数的定义:
在stm32f10x.h中有定义:


这个不用修改,按手册知道stm32总共有16个中断优先级。

2. 查看FreeRTOSConfig.h 文件,参照前面的例子修改:
增加定义:
#ifdef __NVIC_PRIO_BITS
    #define configPRIO_BITS __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS 4
#endif

修改优先级:


设置了两个优先级:
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 2,作为可以调用FreeRTOS API函数的分界线。大于这个优先级的中断不能调用API函数,而小于等于的中断可以调用API函数。

#define configKERNEL_INTERRUPT_PRIORITY         (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) // 定义表示系统时钟中断的优先级为15,为最低硬件中断优先级。

 3. 修改外设的中断优先级

比如: timer模块中断配置部分代码:
    NVIC_InitTypeDef NVIC_InitStructure;
   
    // Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

中断优先级配置:

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;   // 抢占优先级为3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;         // 子优先级为 3
上面两句修改为:
    NVIC_SetPriority(TIM3_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);
    // 说明: 这表示优先级数值大于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY,逻辑优先级反而小,所以能够调用FreeRTOS API函数。
    //       只有抢占优先级和子优先级这里不用关心,只需要知道中断之间的优先顺序就OK了。


    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                            // 根据指定的参数初始化NVIC寄存器








阅读(6929) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~