FreeRTOS学习笔记【四】——FreeRTOS 中断配置和临界段

2023-05-16

                    目录

4.1 Cortex-M 中断

4.1.1 中断简介

4.1.2 中断管理简介

4.1.3 优先级分组定义

4.1.4 优先级设置

4.1.5 用于中断屏蔽的特殊寄存器

4.2 FreeRTOS 中断配置宏

4.2.1 configPRIO_BITS

4.2.3 configKERNEL_INTERRUPT_PRIORITY

4.2.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY

4.2.5 configMAX_SYSCALL_INTERRUPT_PRIORITY

4.3 FreeRTOS 开关中断

4.4 临界段代码

4.4.1 任务级临界段代码保护

4.4.2 中断级临界段代码保护

4.5 FreeRTOS 中断测试实验

4.5.1 实验程序设计

4.5.2 实验程序运行结果

         FreeRTOS 的中断配置是一个很重要的内容,需要根据所使用的 MCU 来具体配置。这需要了解 MCU 架构中有关中断的知识,本章会结合 Cortex-M 的 NVIC 来讲解 STM32 平台下的
FreeRTOS 中断配置, 本章分为如下几部分:

4.1 Cortex-M 中断


4.1.1 中断简介


        中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务, Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。
        Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个Systick(滴答定时器)定时器中断和多个系统异常。


4.1.2 中断管理简介


        Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器, 这些寄存器大多数都在NVIC 和系统控制块(SCB)中, CMSIS 将这些寄存器定义为结构体。以 STM32F103 为例,打开core_cm3.h,有两个结构体, NVIC_Type 和 SCB_Type,如下:

typedef struct
{
    __IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */
    uint32_t RESERVED0[24];
    __IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */
    uint32_t RSERVED1[24];
    __IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */
    uint32_t RESERVED2[24];
    __IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */
    uint32_t RESERVED3[24];
    __IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */
    uint32_t RESERVED4[56];
    __IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
    uint32_t RESERVED5[644];
    __O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
}
NVIC_Type;
typedef struct
{
   __I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
   __IO uint32_t ICSR /*!< Offset: 0x04 Interrupt Control State Register */
   __IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
   __IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
   __IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
   __IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
   __IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15)*/
   __IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
   __IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
   __IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
   __IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
   __IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
   __IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
   __IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
   __I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
   __I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
   __I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
   __I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
   __I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;

        NVIC 和 SCB 都位于系统控制空间(SCS)内, SCS 的地址从 0XE000E000 开始,SCB 和 NVIC的地址也在 core_cm3.h 中有定义,如下:

#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
#define SCB_BASE (SCS_BASE + 0x0D00) /*!< System Control Block Base Address */
#define SCB ((SCB_Type * ) SCB_BASE ) /*!< SCB configuration struct */
#define NVIC ((NVIC_Type* ) NVIC_BASE ) /*!< NVIC configuration struct *//

        以上的中断控制寄存器我们在移植 FreeRTOS 的时候是不需要关心的,这里只是提一下,大家要是感兴趣的话可以参考 Cortex-M 的权威指南,我们重点关心的是是三个中断屏蔽寄存器: PRIMASK、 FAULTMASK 和 BASEPRI,这三个寄存器后面会详细的讲解。

4.1.3 优先级分组定义


        当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,这个就是中断嵌套。 Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、 NMI、HardFault,这些中断的优先级都是负数,优先级也是最高的。
        Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但是实际的优先级数量是由芯片厂商来决定的。但是,绝大多数的芯片都会精简设计的,以致实际上支持的优先级数会更少,如 8 级、 16 级、 32 级等, 比如 STM32 就只有 16 级优先级。在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,所以不管用多少位来表达优先级,都是 MSB 对齐的,如图 4.1.3.1 就是使用三位来表达优先级。

图 4.1.3.1 使用三位表达优先级

        在图 4.1.3.1 中, Bit0~Bit4 没有实现,所以读它们总是返回零,写如它们的话则会忽略写入的值。因此,对于 3 个位的情况,可是使用的优先级就是 8 个: 0X00(最高优先级)、 0X20、0X40、0X60、 0X80、 0XA0、 0XC0 和 0XE0。 注意,这个是芯片厂商来决定的!不是我们能决定的,比如 STM32 就选择了 4 位作为优先级!

        有读者可能就会问,优先级配置寄存器是 8 位宽的,为什么却只有 128 个抢占等级? 8 位不应该是 256 个抢占等级吗?为了使抢占机能变得更可控, Cortex-M 处理器还把 256 个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级), NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器(AIRCR)”, AIRCR 寄存器里面有个位段名为“优先级组”,
如表 4.1.3.1 所示:

表 4.1.3.1 AIRCR 寄存器

        表 4.1.3.1 中 PRIGROUP 就是优先级分组, 它把优先级分为两个位段: MSB 所在的位段(左边的)对应抢占优先级, LSB 所在的位段(右边的)对应亚优先级,如表 4.1.3.2 所示。


表 4.1.3.2 抢占优先级和亚优先级的表达,位数与分组位置的关系

        在看一下 STM32 的优先级分组情况,我们前面说了 STM32 使用了 4 位,因此最多有 5 组优先级分组设置,这 5 个分组在 msic.h 中有定义,如下:

#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 */

        可以看出 STM32 有 5 个分组,但是一定要注意! STM32 中定义的分组 0 对应的值是 7!如果我们选择分组 4,即 NVIC_PriorityGroup_4 的话,那 4 位优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。而移植 FreeRTOS 的时候我们配置的就是组 4,如图 4.1.3.2 所示:

 图 4.1.3.2 优先级分组配置

        如果使用 ALIENTEK 的基础例程的话默认配置的组 2,所以在将基础例程中的外设驱动移植到 FreeRTOS 下面的时候需要修改优先级配置。 主要是 FreeRTOS 的中断配置没有处理亚优先级这种情况,所以只能配置为组 4,直接就 16 个优先级,使用起来也简单!

4.1.4 优先级设置


        每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,但是最小为 3 位。 4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组的设置,优先级又可以分为高、低两个位段,分别抢占优先级和亚优先级。 STM32 我们已经设置位组 4,所以就只有抢占优先级了。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问,有意义的优先级寄存器数目由芯片厂商来实现,如表 4.1.4.1 和 4.1.4.2 所示:

表 4.1.4.1 中断优先级寄存器阵列(地址: 0xE000_E400~0xE000_E4EF)

 表 4.1.4.2 系统异常优先级阵列(地址: 0XE000_ED18~0xE000_ED23)

 上 面 说 了 , 4 个 相 临 的 寄 存 器 可 以 拼 成 一 个 32 位 的 寄 存 器 , 因 此 地 址0xE000_ED20~0xE000_ED23 这四个寄存器就可以拼接成一个地址为 0xE000_ED20 的 32 位寄存器。 这一点很重要! 因为 FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接操作的地址 0xE000_ED20。


4.1.5 用于中断屏蔽的特殊寄存器


        在 4.1.2 小节中说了我们在 STM32 上移植 FreeRTOS 的时候需要重点关注 PRIMASK、FAULTMASK 和 BASEPRI 这三个寄存器,本节就来学习一下这三个寄存器。
1、 PRIMASK 和 FAULTMASK 寄存器
        在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就可以使用 PRIMASK 寄存器, PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断,汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值:

CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)

        PRIMASK 寄存器还可以通过 MRS 和 MSR 指令访问,如下:

MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断

        以及:

MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断

        UCOS 中的临界区代码代码保护就是通过开关中断实现的(UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护,这里不讨论这种情况),而开关中断就是直接操作 PRIMASK寄存器的,所以在 UCOS 中关闭中断的时候时关闭了除复位、 NMI 和 HardFault 以外的所有中断!
        FAULTMASK 比 PRIMASK 更狠, 它可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类似, FAULTMASK 会在退出时自动清零。

        汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态

CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK

        还可以利用 MRS 和 MSR 指令访问 FAULTMASK 寄存器:

MOVS R0, #1
MSR FAULTMASK, R0 ;将 1 写入 FAULTMASK 禁止所有中断

以及:

MOVS R0, #0
MSR FAULTMASK, R0 ;将 0 写入 FAULTMASK 使能中断

2、 BASEPRI 寄存器
        PRIMASK 和 FAULTMASK 寄存器太粗暴了,直接关闭除复位、 NMI 和 HardFault 以外的其他所有中断,但是在有些场合需要对中断屏蔽进行更细腻的控制, 比如只屏蔽优先级低于某一个阈值的中断。那么这个作为阈值的优先级值存储在哪里呢?在 BASEPRI 寄存器中,不过如果向 BASEPRI 写 0 的话就会停止屏蔽中断。 比如,我们要屏蔽优先级不高于 0X60 的中断,则可以使用如下汇编编程:

MOV R0, #0X60
MSR BASEPRI, R0

如果需要取消 BASEPRI 对中断的屏蔽,可以使用如下代码:
 

MOV R0, #0
MSR BASEPRI, R0

        注意! FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭!

4.2 FreeRTOS 中断配置宏


4.2.1 configPRIO_BITS


        此宏用来设置 MCU 使用几位优先级, STM32 使用的是 4 位,因此此宏为 4!4.2.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY

        此宏是用来设置最低优先级,前面说了,STM32 优先级使用了 4 位,而且 STM32 配置的使用组 4,也就是 4 位都是抢占优先级。 因此优先级数就是 16 个,最低优先级那就是 15。 所以此宏就是 15,注意!不同的 MCU 此值不同,具体是多少要看所使用的 MCU 的架构,本教程只针对 STM32 讲解!

4.2.3 configKERNEL_INTERRUPT_PRIORITY


        此宏用来设置内核中断优先级, 此宏定义如下:

#define configKERNEL_INTERRUPT_PRIORITY
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

        宏 configKERNEL_INTERRUPT_PRIORITY 为 , 宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移 8-configPRIO_BITS 位,也就是左移 4位。为什么要左移 4 位呢?前面我们说了, STM32 使用了 4 位作为优先级,而这 4 位是高 4位,
因 此 要 左 移 4 位 才 是 真 正 的 优 先 级 。 当 然 了 也 可 以 不 用 移 位 , 直 接 将 宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 定义为 0XF0! 不过这样看起来不直观。
        宏 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 )

        可 以 看 出 , portNVIC_PENDSV_PRI 和 portNVIC_SYSTICK_PRI 都 是 使 用 了 宏configKERNEL_INTERRUPT_PRIORITY , 为 什 么 宏 portNVIC_PENDSV_PRI 是 宏configKERNEL_INTERRUPT_PRIORITY 左移 16 位呢?宏 portNVIC_SYSTICK_PRI 也同样是左移 24 位。 4.1.4 小节讲过了, PendSV 和 SysTcik 的中断优先级设置是操作 0xE000_ED20 地址的, 这样一次写入的是个 32 位的数据, SysTick 和 PendSV 的优先级寄存器分别对应这个 32位数据的最高 8 位和次高 8 位,不就是一个左移 16 位,一个左移 24 位了。
        PendSV 和 SysTick 优先级是在哪里设置的呢?在函数 xPortStartScheduler()中设置,此函数在文件 port.c 中,函数如下:

BaseType_t xPortStartScheduler( void )
{
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
    configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
    #if( configASSERT_DEFINED == 1 )
    {
        volatile uint32_t ulOriginalPriority;
        volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * )
        ( portNVIC_IP_REGISTERS_OFFSET_16 +
        portFIRST_USER_INTERRUPT_NUMBER );
        volatile uint8_t ucMaxPriorityValue;
        ulOriginalPriority = *pucFirstUserPriorityRegister;
        *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
        ucMaxPriorityValue = *pucFirstUserPriorityRegister;
        configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY &
        ucMaxPriorityValue ) );
        ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY &
        ucMaxPriorityValue;
        ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
        while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
        {
            ulMaxPRIGROUPValue--;
            ucMaxPriorityValue <<= ( uint8_t ) 0x01;
        }
        ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
        ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
        *pucFirstUserPriorityRegister = ulOriginalPriority;
    }
    #endif /* conifgASSERT_DEFINED */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置 PendSV 中断优先级
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置 SysTick 中断优先级
    vPortSetupTimerInterrupt();
    uxCriticalNesting = 0;
    prvStartFirstTask();
    return 0;
}

        上述代码中红色部分就是设置 PendSV 和 SysTick 优先级的,它们是直接向地址portNVIC_SYSPRI2_REG 写入优先级数据, portNVIC_SYSPRI2_REG 是个宏,在文件 port.c 中由定义,如下:

#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )

可以看到宏 portNVIC_SYSPRI2_REG 就是地址 0XE000ED20!同时也可以看出在 FreeRTOS中 PendSV 和 SysTick 的中断优先级都是最低的!


4.2.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY


        此宏用来设置 FreeRTOS 系统可管理的最大优先级,也就是我们在 4.1.5 小节中讲解BASEPRI 寄存器说的那个阈值优先级,这个大家可以自由设置,这里我设置为了 5。也就是高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理!

4.2.5 configMAX_SYSCALL_INTERRUPT_PRIORITY

        此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,原因和宏 configKERNEL_INTERRUPT_PRIORITY 一样。此宏设置好以后,低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数!

        以 STM32 为例,有 16 个优先级, 0 为最高优先级, 15 为最低优先级,配置如下:
        ● configMAX_SYSCALL_INTERRUPT_PRIORITY==5
        ● configKERNEL_INTERRUPT_PRIORITY==15
        结果如图 4.2.5.1 所示:

图 4.2.5.1 中断配置结果图
 

        由于高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的优先级不会被 FreeRTOS 内核屏蔽,因此那些对实时性要求严格的任务就可以使用这些优先级,比如四轴飞行器中的壁障检测。

4.3 FreeRTOS 开关中断


        FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)

        可以看出开关中断实际上是通过函数 vPortSetBASEPRI(0)和 vPortRaiseBASEPRI()来实现的,这两个函数如下:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {
    msr basepri, ulBASEPRI
    }
}
    /*-----------------------------------------------------------*/
    static portFORCE_INLINE void vPortRaiseBASEPRI( void )
    {
        uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
        __asm
        {
            msr basepri, ulNewBASEPRI
            dsb
            isb
        }
    }

        函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传递进来, portENABLE_INTERRUPTS()是开中断,它传递了个 0 给 vPortSetBASEPRI(),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。
        函 数 vPortRaiseBASEPRI() 是 向 寄 存 器 BASEPRI 写 入 宏configMAX_SYSCALL_INTERRUPT_PRIORITY , 那 么 优 先 级 低 于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!

4.4 临界段代码


        临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。 FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。 FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。
        FreeRTOS 与 临 界 段 代 码 保 护 有 关 的 函 数 有 4 个 : taskENTER_CRITICAL() 、taskEXIT_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 和taskEXIT_CRITICAL_FROM_ISR(),这四个函数其实是宏定义,在 task.h 文件中有定义。 这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。

4.4.1 任务级临界段代码保护

        taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段, 这两个函数是成对使用的,这函数的定义如下:

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()

        而 portENTER_CRITICAL()和 portEXIT_CRITICAL()也是宏定义,在文件 portmacro.h 中有定义,如下:

#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()

函数 vPortEnterCritical()和 vPortExitCritical()在文件 port.c 中,函数如下:
 

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}
void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;
    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}

        可以看出在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量 uxCriticalNesting加一, uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断!
        任务级临界代码保护使用方法如下:

void taskcritical_test(void)
{
    while(1)
    {
        taskENTER_CRITICAL(); (1)
        total_num+=0.01f;
        printf("total_num 的值为: %.4f\r\n",total_num);
        taskEXIT_CRITICAL(); (2)
        vTaskDelay(1000);
    }
}

(1)、进入临界区。
(2)、退出临界区。
        (1)和(2)中间的代码就是临界区代码,注意临界区代码一定要精简!因为进入临界区会关闭中断,这样会导致优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!

4.4.2 中断级临界段代码保护

        函数 taskENTER_CRITICAL_FROM_ISR()和 taskEXIT_CRITICAL_FROM_ISR()中断级别临 界 段 代 码 保 护 , 是 用 在 中 断 服 务 程 序 中 的 , 而 且 这 个 中 断 的 优 先 级 一 定 要 低 于configMAX_SYSCALL_INTERRUPT_PRIORITY!原因前面已经说了。这两个函数在文件 task.h中有如下定义:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

        接着找portSET_INTERRUPT_MASK_FROM_ISR()和portCLEAR_INTERRUPT_MASK_FROM_ISR(),这两个在文件 portmacro.h 中有如下定义:

#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

        vPortSetBASEPRI()前面已经讲解了,就是给 BASEPRI 寄存器中写入一个值。函数 ulPortRaiseBASEPRI()在文件 portmacro.h 中定义的,如下:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
    uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
    __asm
    {
        mrs ulReturn, basepri (1)
        msr basepri, ulNewBASEPRI (2)
        dsb
        isb
    }
    return ulReturn; (3)
}

        (1)、 先读出 BASEPRI 的值,保存在 ulReturn 中。
        (2)、将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入到寄存器 BASEPRI 中。
        (3)、返回 ulReturn,退出临界区代码保护的时候要使用到此值!
        中断级临界代码保护使用方法如下:

//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
    {
        status_value=taskENTER_CRITICAL_FROM_ISR(); (1)
        total_num+=1;
        printf("float_num 的值为: %d\r\n",total_num);
        taskEXIT_CRITICAL_FROM_ISR(status_value); (2)
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}

(1)、进入临界区。
(2)、退出临界区。

4.5 FreeRTOS 中断测试实验

4.5.1 实验程序设计

1、实验目的
        上面我们讲了在 FreeRTOS 中优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY的中断会被屏蔽掉,高于的就不会,那么本节我们就写个简单的例程测试一下。使用两个定时器,一个优先级为 4,一个优先级为 5,两个定时器每隔 1s 通过串口输出一串字符串。然后在某个任务中关闭中断一段时间,查看两个定时器的输出情况。。
2、实验设计
        本实验设计了两个任务 start_task()和 interrupt_task(), 这两个任务的任务功能如下:start_task():创建另外一个任务。interrupt_task() : 中 断 测 试 任 务 , 任 务 中 会 调 用 FreeRTOS 的 关 中 断 函 数portDISABLE_INTERRUPTS()来将中断关闭一段时间。

3、实验工程
        FreeRTOS 实验 4-1 FreeRTOS 中断测试实验。
4、实验程序与分析
        ● 任务设置

#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 256 //任务堆栈大小
TaskHandle_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数

● main()函数

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
    delay_init(); //延时函数初始化
    uart_init(115200); //初始化串口
    LED_Init(); //初始化 LED
    TIM3_Int_Init(10000-1,7200-1); //初始化定时器 3,定时器周期 1S
    TIM5_Int_Init(10000-1,7200-1); //初始化定时器 5,定时器周期 1S
    //创建开始任务
    xTaskCreate((TaskFunction_t )start_task, //任务函数
    (const char* )"start_task", //任务名称
    (uint16_t )START_STK_SIZE, //任务堆栈大小
    (void* )NULL, //传递给任务函数的参数
    (UBaseType_t )START_TASK_PRIO, //任务优先级
    (TaskHandle_t* )&StartTask_Handler); //任务句柄
    vTaskStartScheduler(); //开启任务调度
}

● 任务函数
 

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); //进入临界区
    //创建中断测试任务
    xTaskCreate((TaskFunction_t )interrupt_task, //任务函数 (1)
    (const char* )"interrupt_task", //任务名称
    (uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
    (void* )NULL, //传递给任务函数的参数
    (UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
    (TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
    static u32 total_num=0;
    while(1)
    {
        total_num+=1;
        if(total_num==5) (2)
        {
            printf("关闭中断.............\r\n");
            portDISABLE_INTERRUPTS(); //关闭中断 (3)
            delay_xms(5000); //延时 5s (4)
            printf("打开中断.............\r\n"); //打开中断
            portENABLE_INTERRUPTS(); (5)
        }
        LED0=~LED0;
        vTaskDelay(1000);
    }
}

        (1)、创建一个任务来执行开关中断的动作,任务函数为 interrupt_task()。
        (2)、当任务 interrupt_task()运行 5 次以后关闭中断。
        (3) 、 调 用 函 数 portDISABLE_INTERRUPTS() 关 闭 中 断 。 优 先 级 低 于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断都会被关闭,高于的不会受任何影响。
        (4)、调用函数 delay_xms()延时 5S,此函数是对 delay_us()的简单封装, delay_xms()会用来模拟关闭中断一段时间,此函数不会引起任务调度!
        (5)、调用函数 portENABLE_INTERRUPTS()重新打开中断。
        ● 中断初始化及处理过程

//通用定时器 3 中断初始化
//这里时钟选择为 APB1 的 2 倍,而 APB1 为 36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器 3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
    //定时器 TIM3 初始化
    TIM_TimeBaseStructure.TIM_Period = arr; //自动重装载值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //定时器分频
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM 向上计数模式
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的 TIM3 中断,允许更新中断
    //中断优先级 NVIC 设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3 中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级 4 级 (1)
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级 0 级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
    NVIC_Init(&NVIC_InitStructure); //初始化 NVIC 寄存器
    TIM_Cmd(TIM3, ENABLE); //使能 TIMx
}
    //通用定时器 5 中断初始化
    //这里时钟选择为 APB1 的 2 倍,而 APB1 为 36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器 5!
void TIM5_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
    //定时器 TIM5 初始化
    TIM_TimeBaseStructure.TIM_Period = arr; //自动重装载值
    TIM_TimeBaseStructure.TIM_Prescaler =psc; //定时器分频
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的 TIM5 中断,允许更新中断
    //中断优先级 NVIC 设置
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5 中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5; //先占优先级 5 级 (2)
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级 0 级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道被使能
    NVIC_Init(&NVIC_InitStructure); //初始化 NVIC 寄存器
    TIM_Cmd(TIM5, ENABLE); //使能 TIM5
}
    //定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
    {
        printf("TIM3 输出.......\r\n"); (3)
    }
    TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器 5 中断服务函数
void TIM5_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
    {
        printf("TIM5 输出.......\r\n"); (4)
    }
    TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}

        (1)、设置定时器 3 的抢占优先级为 4,高于configMAX_SYSCALL_INTERRUPT_PRIORITY,因此在调用函数portDISABLE_INTERRUPTS()关闭中断的时候定时器 3 是不会受影响的。
        (2)、设置定时器 5 的抢占优先级为 5,等于configMAX_SYSCALL_INTERRUPT_PRIORITY,因此在调用函数portDISABLE_INTERRUPTS()关闭中断的时候定时器 5 中断肯定会被关闭的。
        (3)和(4)、定时器 3 和定时 5 串口输出信息。

4.5.2 实验程序运行结果


        编译并下载代码到开发板中,打开串口调试助手查看数据输出,结果如图 4.5.2.1 所示:

 图 4.5.2.1 串口调试助手


        从图 4.5.2.1 可以看出,一开始没有关闭中断,所以 TIM3 和 TIM5 都正常运行,红框所示部分。当任务 interrupt_task()运行了 5 次以后就关闭了中断,此时由于 TIM5 的中断优先级为 5,等于 configMAX_SYSCALL_INTERRUPT_PRIORITY,因此 TIM5 被关闭。但是, TIM3 的中断优先级高于 configMAX_SYSCALL_INTERRUPT_PRIORITY,不会被关闭,所以 TIM3 正常运行,绿框所示部分。 中断关闭 5S 以后就会调用函数 portENABLE_INTERRUPTS()重新打开中断,重新打开中断以后 TIM5 恢复运行, 蓝框所示部分。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS学习笔记【四】——FreeRTOS 中断配置和临界段 的相关文章

  • 定时器与计数器的区别

    曾经我在北方工业大学复试的时候 xff0c 有个老师问了我个问题 他说同学你好 xff0c 我看你前面一直都在说深度学习的问题 xff0c 请问你对单片机了解吗 xff1f xff0c 请问定时器与计数器的区别是什么 xff1f 我告诉他
  • 编译make px4_fmu-v2_default upload时,报错ERROR Board can accept larger flash images (2080768 bytes)

    编译make px4 fmu v2 default upload时 xff0c 报错ERROR Board can accept larger flash images 2080768 bytes than board config 103
  • vue中实现axios封装

    vue中实现axios封装 为什么要进行axios封装vue项目安装axios封装前的get和post请求封装后的get和post请求 为什么要进行axios封装 实际开发过程中可能有几种环境 xff0c 开发环境 xff0c 测试环境 x
  • 项目实训—场景划分(一)

    1 什么是场景 xff08 Scene xff09 场景作为电影讲故事的关键单元 xff0c 包含了演员的复杂活动及其在物理位置上的互动 识别场景的组成和语义是视觉理解复杂长视频 xff08 如电影 电视剧 娱乐节目和综艺节目 xff09
  • 手把手教你FreeRTOS源码解析(一)——内存管理

    FreeRTOS中一共有5种内存分配的方法 xff0c 分别在文件heap 1 c heap 2 c heap 3 c heap 4 c heap 5 c种 虽然标准C库中的 malloc 和 free 也可以实现动态内存管理 xff0c
  • 手把手教你FreeRTOS源码详解(二)——任务管理

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS
  • 手把手教你FreeRTOS源码详解(三)——队列

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS
  • 手把手教你FreeRTOS源码详解(四)——信号量、互斥量、递归互斥量

    FreeRTOS源码解析集合 xff08 全网最详细 xff09 手把手教你FreeRTOS源码解析 xff08 一 xff09 内存管理 手把手教你FreeRTOS源码详解 xff08 二 xff09 任务管理 手把手教你FreeRTOS
  • python 随机数 random 库的使用总结

    目录 1 random 库中的常用随机数生成函数 2 一些用法举例 3 随机数种子 seed a 的理解与使用 random 库的主要目的就是生成随机数 1 random 库中的常用随机数生成函数 from random import 1
  • JDBC 配置

    JDBC Java DataBase Connectivity 规范 JDBC是一套协议 xff08 规范 xff09 是java开发人员和数据库厂商达成的协议 是由sun定义一组接口 由数据库厂商来实现 并规定了java开发人员访问数据库
  • Gazebo下载所有模块到本地

    从https bitbucket org osrf gazebo models上下载model库 xff1b 将下载的文件解压 xff0c 将解压后的文件夹重命名为models将models复制到 gazebo文件夹中 xff08 如果 g
  • linux操作系统和系统资源理论基础

    目录 一 xff1a 操作系统 二 xff1a 程序如何被CPU读取 三 xff1a CPU缓存 四 xff1a CPU缓存策略 五 xff1a 如何实现系统多任务 六 xff1a 进程切换过程初探 七 xff1a 进程的调度规则 八 xf
  • containerd的安装和使用

    containerd介绍 containerd是从docker项目中剥离出来的一个容器运行时 几乎囊括了容器管理的所有功能 xff0c 并且containerd内置了CRI插件K8S的kubelet组件可以直接调用containerd xf
  • 部署harbor并实现https(SAN签发证书)

    目录 一 安装docker docker compose 二 安装harbor 三 签发证书 四 修改配置文件 五 运行harbor 六 客户端测试 xff1a 使用系统 xff1a ubuntu 20 04 3 harbor依赖于dock
  • 二进制部署kubernetes高可用集群

    目录 一 集群外围节点准备 二 部署kubeasz 3 3 1集群 三 初始化集群 四 测试集群网络可用性 五 集群扩容 需要master node etcd节点各三台 xff1b harbor服务器 deploy部署节点 负载均衡主机各一
  • coredns部署

    目录 一 获取coredns yaml文件 二 修改coredns yaml文件 三 基于yaml文件启动coredns服务 coredns域名解析流程 xff1a 举例 xff1a 当pod1应用想通过dns域名的方式访问pod2则首先根
  • dashboard的安装使用

    目录 一 下载dashboard的yaml文件 二 修改dsashboard的yaml文件镜像 三 修改dashboard的yaml文件内容 四 创建dashboard 通常而言kubernetes中完成的所有操作都是通过命令行工具kube
  • K8S内部pod之间相互调用案例和详解

    目录 一 部署nginx容器 二 部署tomcat服务 三 使用nginx代理tomcat服务 四 测试 服务发现简介 xff1a 1 service是用于K8S的服务发现的重要组件 xff0c pod作为运行业务的承载方式 xff0c 要
  • 记一次rsyslog配置问题,导致系统无法打印日志

    工作中发现有些机器发生异常 xff0c 想看下messages日志 xff0c 却没有数据 xff0c 看了下rsyslog中日志都定义了打印出的路径 xff0c 但是就是没有日志 查看结果 xff1a 所有的日志文件都没有打印数据 查看了
  • python 字符串操作总结

    目录 1 求字符串长度及字符串转换 2 删除空格 分割字符串 3 连接字符串 4 大小写转换 5 字符串替换 6 统计子字符串的出现次数 7 查找子字符串第一次出现的位置 8 判断字符串的某些属性是否为True 9 字符串转化为数字 10

随机推荐

  • K8S pod 亲和与反亲和 (podAffinity与podAntiAffinity)

    目录 1 简介 xff1a 2 配置须知 xff1a 示例 xff1a 一 创建前端nginx容器 二 创建后端容器 xff0c 与nginx容器做软亲和 三 创建后端容器 xff0c 与nginx容器做硬亲和 1 简介 xff1a 前面介
  • K8S-污点(taints)与容忍(toleration)

    简介 xff1a 污点 xff08 taints xff09 打上了污点的node就不会承载新pod的调度 容忍 xff08 toleration xff09 设置了有容忍的pod xff0c 仍能调度到有污点的node上 一 污点介绍 x
  • Kafka 客户端管理工具 Offset Explorer

    简介 xff1a 我们在项目开发中根据实际业务需求会使用到一些中间件 xff0c 比如缓存redis xff0c 消息队列 xff0c kafka rabbitMQ等 xff0c 在代码中集成后 xff0c 可以通过命令来查看数据的走向 x
  • 力扣(Leetcode)——python3

    目录 动态规划 70 爬楼梯 198 打家劫舍 213 打家劫舍 509 斐波那契数 740 删除并获得点数 746 使用最小花费爬楼梯 1137 第N个泰波那契序列 动态规划 Dynamic Programming 递归 43 迭代 45
  • Python 无参装饰器详解

    1 储备知识 args xff0c kwargs span class token keyword def span span class token function index span span class token punctua
  • 操作系统进程与线程实验二

    操作系统进程与线程实验二 一 实验目的 xff1a 对比进程与线程 xff0c 理解进程与线程的联系与区别 xff0c 学会使用多线程相关函数 一个进程中的多个线程之间使用相同的地址空间 xff0c 共享大部分数据 xff0c 启动一个线程
  • STM32CubeMX+keil5+esp8266(基于hal库的stm32+esp8266连接)

    MX的烧录口和时钟设置 xff1a 配置时钟 xff1a 串口1配置 xff08 用来输出提示信息 xff09 串口2配置波特率115200 xff0c 使能全局中断 xff08 用来做esp8266的串口通信 xff09 命好名生成代码并
  • Vue.js基础---简单的模板页面

    Vue js基础 简单的模板页面 1 模板页面包含了哪些东西 xff1f gt html 43 js 2 js以什么形式 语法 存在 xff1f 插值 双大括号表达式 xff0c 插入一个动态的值在标签内文本内容上 xff0c 动态显示数据
  • STM32单片机,STLINK的SW仿真时“no target connected”问题及处理

    1 问题介绍 在KEIL5软件中使用STLINK仿真器仿真时 使用SW仿真时 xff0c 遇到了no target connected的问题 二 解决方案 通过询问买家 xff0c 自己反复调试后 xff0c 终于解决了这一问题 xff0c
  • Docker comsul(注册中心)

    文章目录 Docker consul xff08 注册中心 xff09 什么是consul xff1f consul干什么的 xff1f 一 consul的使用场景二 consul集群三 consul部署1 建立consul服务2 设置代理
  • python 输入 input() 与输出 print()

    目录 1 输入字符串 2 输入数字 3 输入列表 理解 eval 函数的使用 4 输出 print python 使用 input 获取用户的输入 xff0c 该操作属于无脑式输入 xff0c 不管在键盘上敲入什么内容都会当作字符串类型返回
  • Linux 禁用/启用 网络唤醒功能

    使用ifconfig查看网卡设备 xff0c 这里以eth0为例 1 输入 ethtool eth0 2 在结果内查看是否有如下内容 Supports Wake on pumbgWake on g 若Wake on的值为d xff0c 表示
  • xrandr详细介绍

    简介 xrandr是一款官方的扩展配置工具 它可以设置屏幕显示的大小 方向 镜像等 xff0c 包括对多屏的设置 详细的使用方法可以通过man xrandr查看 单屏设置 查询 使用xrandr q或者xrandr命令可以查询当前的显示状态
  • VS Code中使用git(创建本地仓库及远程仓库、将本地仓库推送至远程仓库、克隆远程仓库、拉取远程仓库、解决冲突)

    创建本地仓库及远程仓库 将本地仓库推送至远程仓库 克隆远程仓库 拉取远程仓库 解决冲突 文章目录 一 创建远程仓库 使用Gitee或者GitHub 二 git推送本地代码至远程仓库三 git克隆远程仓库代码到本地四 VS Code推送代码至
  • Java中的equals和==

    一 equals和 61 61 的区别 1 根本的区别在于 equals Object obj 是一个方法 xff0c 而 61 61 是一个运算符 2 一般来说 xff0c 61 61 运比较的是在内存中的物理地址 xff0c equal
  • [欠驱动机器人]1.概念

    目录 定义反馈控制输入或状态限制不完整约束欠驱动机器人举例参考文献 定义 形象来看 xff0c 就是机器人驱动数量小于关节数量 从数学来看 xff0c 根据牛顿定律 xff0c 机械系统的动力学方程为二阶导数形式 F 61 ma xff0c
  • Prometheus自主无人机入门笔记

    本部分介绍自主无人机组成框架 自主无人机关键技术 1 1 自主无人机的组成框架 1 1 1 硬件框架 自主无人机硬件组成有 机架 动力系统 xff08 包括电机 电调和浆 xff09 指挥 系统 xff08 机载电脑 飞控板 xff09 通
  • 自主导航与路径规划无人机研究现状

    目录 1 SLAM算法的研究现状 2 无人机定位研究现状 3 路径规划的研究现状 参考文献 1 SLAM算法的研究现状 移动机器人根据传感器获取的自身状态信息和环境信息构建环境地图的过程被称之为SLAM问题 xff0c SLAM问题能否解决
  • FreeRTOS学习笔记【一】——初识FreeRTOS

    1 1 初识 FreeRTOS 1 1 1 什么是 FreeRTOS 我们看一下 FreeRTOS 的名字 xff0c 可以分为两部分 Free 和 RTOS xff0c Free 就是免费的 自由的 不受约束 的意思 xff0c RTOS
  • FreeRTOS学习笔记【四】——FreeRTOS 中断配置和临界段

    目录 4 1 Cortex M 中断 4 1 1 中断简介 4 1 2 中断管理简介 4 1 3 优先级分组定义 4 1 4 优先级设置 4 1 5 用于中断屏蔽的特殊寄存器 4 2 FreeRTOS 中断配置宏 4 2 1 configP