freertos随意记录

freertos是一个开源免费的嵌入式实时操作系统,非常好用,也比较简单,对freertos的学习过程做一些记录。

1、移植:

        freertos的移植应该是比较简单的,下载官方最新版的LTS版本,然后拷贝一些文件和做一些设置就行了。

a、文件复制:

        在目标工程下新建文件夹freertos,将freertos-kernel根目录下的C文件复制到前面的文件夹中

        复制including文件夹

        复制portable文件夹中的文件,我使用的是正点原子的STM32H750开发板,使用的keil ac6创建的工程,文件目录及文件如下:

        另外,freertos还有内存管理,复制下列文件到工程目录

        freertos还需要一个用于裁剪代码的配置文件,直接复制example里面的

        至此,文件的拷贝已经完成了。完整目录结构如下:

        

        b、工程配置

        新建一个hal工程,把开发板上的led灯和串口初始化,然后配置工程

添加头文件

freertos的任务切换有几个非常重要的中断函数PendSV_Handler SysTick_Handler SVC_Handler,这是任务切换的关键,需要注释掉hal工程中stm32h7xx_it.c中的这几个函数,并在freertosconfig.h文件中添加下列代码,让stm32h750的中断和freertos内核的中断关联起来

freertos的框架已经搭建起来了,下面需要调用freertos的入口函数vTaskStartScheduler(),这个地方要注意,不要添加错了,我之前就添加成xPortStartScheduler(),找了好久才发现。vTaskStartScheduler里面会调用xPortStartScheduler,不过在这之前需要进行一些闲置任务这些初始化。

完成上面这些,freertos已经在运行了,为了观看效果,新建几个任务

这是几个简单的任务,led和printf任务,其中led是可以阻塞的,print则没有阻塞。非常不建议print这种写法,因为该任务不会离开RUN状态,会一直执行,导致优先级比他低的任务没有机会执行(包括IDLE任务),这里仅仅是为了测试,项目中千万不要这样写,每个任务一定需要离开RUN状态,要么延时等待,要么等待信号量这些。通过测试,print优先级18,几个任务都可以正常运行,print优先级高于20,print任务正常运行,led任务无法运行。

2、学习记录

        任务切换原理:

                某个任务正在执行,但是系统时钟也在运行,其中systick中断设置为1000HZ,每1ms进入一次systick中断,看下systickHandler干了啥:

#define xPortSysTickHandler SysTick_Handler;

void xPortSysTickHandler( void )
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
     * executes all interrupts must be unmasked.  There is therefore no need to
     * save and then restore the interrupt mask value as its value is already
     * known. */
    // 屏蔽掉中断优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,我们认为优先级低于这个值的中断可以被屏蔽
    portDISABLE_INTERRUPTS();
    traceISR_ENTER();
    {
        /* Increment the RTOS tick. */
        // 系统tick累加
        if( xTaskIncrementTick() != pdFALSE )
        {
            traceISR_EXIT_TO_SCHEDULER();

            /* A context switch is required.  Context switching is performed in
             * the PendSV interrupt.  Pend the PendSV interrupt. */
            // 产生pendSV中断请求 
// portmacro.h文件如下
// #define portNVIC_INT_CTRL_REG     ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
// #define portNVIC_PENDSVSET_BIT    ( 1UL << 28UL )
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
        else
        {
            traceISR_EXIT();
        }
    }
    portENABLE_INTERRUPTS();
}

其中,产生pendSV中断请求:

从上面的代码可以看出,systick中断就干了一件事:产生pendSV中断。下面看看pendSV中断又干了啥:

void xPortPendSVHandler( void )
{
    /* This is a naked function. */

    __asm volatile
    (
        "   mrs r0, psp                         \n"
        "   isb                                 \n"
        "                                       \n"
        "   ldr r3, =pxCurrentTCB               \n" /* Get the location of the current TCB. */
        "   ldr r2, [r3]                        \n"
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, push high vfp registers. */
        "   it eq                               \n"
        "   vstmdbeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   stmdb r0!, {r4-r11, r14}            \n" /* Save the core registers. */
        "   str r0, [r2]                        \n" /* Save the new top of stack into the first member of the TCB. */
        "                                       \n"
        "   stmdb sp!, {r0, r3}                 \n"
        "   mov r0, %0                          \n"
        "   cpsid i                             \n" /* ARM Cortex-M7 r0p1 Errata 837070 workaround. */
        "   msr basepri, r0                     \n"
        "   dsb                                 \n"
        "   isb                                 \n"
        "   cpsie i                             \n" /* ARM Cortex-M7 r0p1 Errata 837070 workaround. */
        "   bl vTaskSwitchContext               \n"
        "   mov r0, #0                          \n"
        "   msr basepri, r0                     \n"
        "   ldmia sp!, {r0, r3}                 \n"
        "                                       \n"
        "   ldr r1, [r3]                        \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldr r0, [r1]                        \n"
        "                                       \n"
        "   ldmia r0!, {r4-r11, r14}            \n" /* Pop the core registers. */
        "                                       \n"
        "   tst r14, #0x10                      \n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
        "   it eq                               \n"
        "   vldmiaeq r0!, {s16-s31}             \n"
        "                                       \n"
        "   msr psp, r0                         \n"
        "   isb                                 \n"
        "                                       \n"
        #ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
            #if WORKAROUND_PMU_CM001 == 1
                "           push { r14 }                \n"
                "           pop { pc }                  \n"
            #endif
        #endif
        "                                       \n"
        "   bx r14                              \n"
        "                                       \n"
        "   .ltorg                              \n"
        ::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY )
    );
}

void vTaskSwitchContext( void )
{
/** 无关代码 ***/
    // 查找当前ready的最高优先级,并把pxCurrentTCB赋值为最高优先级TCB中的下一个TCB
    taskSELECT_HIGHEST_PRIORITY_TASK();
    portTASK_SWITCH_HOOK( pxCurrentTCB );

}

 #define taskSELECT_HIGHEST_PRIORITY_TASK()                                       \
    do {                                                                                 \
        UBaseType_t uxTopPriority = uxTopReadyPriority;                                  \
                                                                                         \
        /* Find the highest priority queue that contains ready tasks. */                 \
        while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) != pdFALSE ) \
        {                                                                                \
            configASSERT( uxTopPriority );                                               \
            --uxTopPriority;                                                             \
        }                                                                                \
                                                                                         \
        /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of \
         * the  same priority get an equal share of the processor time. */                    \
        listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
        uxTopReadyPriority = uxTopPriority;                                                   \
    } while( 0 ) /* taskSELECT_HIGHEST_PRIORITY_TASK */

"   ldr r3, =pxCurrentTCB               \n" /* Get the location of the current TCB. */

获取到当前的任务的TCB;

"   bl vTaskSwitchContext               \n"

任务切换函数,寻找处于ready的最高优先级,并把pxCurrentTCB赋值为最高优先级中的下一个(如果有,如果没有,不变)

后面的代码看注释知道是加载pxCurrentTCB中的寄存器值,然后进行跳转,完成任务切换。

最后看一下SVCHandler

void vPortSVCHandler( void )
{
    __asm volatile (
        "   ldr r3, =pxCurrentTCB           \n" /* Restore the context. */
        "   ldr r1, [r3]                    \n" /* Get the pxCurrentTCB address. */
        "   ldr r0, [r1]                    \n" /* The first item in pxCurrentTCB is the task top of stack. */
        "   ldmia r0!, {r4-r11, r14}        \n" /* Pop the registers that are not automatically saved on exception entry and the critical nesting count. */
        "   msr psp, r0                     \n" /* Restore the task stack pointer. */
        "   isb                             \n"
        "   mov r0, #0                      \n"
        "   msr basepri, r0                 \n"
        "   bx r14                          \n"
        "                                   \n"
        "   .ltorg                          \n"
        );
}

static void prvPortStartFirstTask( void )
{
    /* Start the first task.  This also clears the bit that indicates the FPU is
     * in use in case the FPU was used before the scheduler was started - which
     * would otherwise result in the unnecessary leaving of space in the SVC stack
     * for lazy saving of FPU registers. */
    __asm volatile (
        " ldr r0, =0xE000ED08   \n" /* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0]          \n"
        " ldr r0, [r0]          \n"
        " msr msp, r0           \n" /* Set the msp back to the start of the stack. */
        " mov r0, #0            \n" /* Clear the bit that indicates the FPU is in use, see comment above. */
        " msr control, r0       \n"
        " cpsie i               \n" /* Globally enable interrupts. */
        " cpsie f               \n"
        " dsb                   \n"
        " isb                   \n"
        " svc 0                 \n" /* System call to start first task. */
        " nop                   \n"
        " .ltorg                \n"
        );
}

BaseType_t xPortStartScheduler( void )
{
    prvPortStartFirstTask();
}

这个代码貌似只执行一次,就是在vTaskStartScheduler中,会执行一次,启动第一个任务。

        configMAX_SYSCALL_INTERRUPT_PRIORITY,设置系统最高优先级,即如果代码中有优先级大于(优先级数值小于)configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,不受freertos控制,可以正常产生中断,如果代码中有优先级小于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断,则会因为freertos调用vPortEnterCritical()函数进入临界状态,无法进入中断。

比如:串口非常重要,不想让串口被系统打断,那么,如果configMAX_SYSCALL_INTERRUPT_PRIORITY设置为16,串口的中断优先级就需要设置为<16;

        按键中断不是很重要,可能在发送数据的时候不需要响应按键,可以把按键的中断优先级设置为大于16,那么在进入临界状态时候,按键中断是无法响应的。


// 进入临界状态,禁止中断
void vPortEnterCritical( void )
{
    // 进入临界状态,禁止中断
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in "FromISR" can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

#define taskDISABLE_INTERRUPTS()    portDISABLE_INTERRUPTS()

#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()

// 将configMAX_SYSCALL_INTERRUPT_PRIORITY 的值写入basepri,屏蔽优先级小于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断(优先级数值大于configMAX_SYSCALL_INTERRUPT_PRIORITY )
portFORCE_INLINE static void vPortRaiseBASEPRI( void )
{
    uint32_t ulNewBASEPRI;

    __asm volatile
    (
        "   mov %0, %1                                              \n" \
        "   cpsid i                                                 \n" \
        "   msr basepri, %0                                         \n" \
        "   isb                                                     \n" \
        "   dsb                                                     \n" \
        "   cpsie i                                                 \n" \
        : "=r" ( ulNewBASEPRI ) : "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "memory"
    );
}
在更精巧的设计中,需要对中断掩蔽进行更细腻的控制——只掩蔽优先级低于某一阈值
的中断——它们的优先级在数字上大于等于某个数。那么这个数存储在哪里?就存储在
BASEPRI中。不过,如果往BASEPRI中写0,则另当别论——BASEPRI将停止掩蔽任何中断。例
如,如果你需要掩蔽所有优先级不高于0x60的中断,则可以如下编程:
MOV R0, #0x60
MSR BASEPRI, R0
如果需要取消BASEPRI 对中断的掩蔽,则示例代码如下:
MOV R0, #0
MSR BASEPRI, R0

《CORTEX M3 权威指南》

退出临界状态:        

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS();
    }
}

#define taskENABLE_INTERRUPTS()     portENABLE_INTERRUPTS()

#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )

// 将0写入basepri,恢复所有中断
portFORCE_INLINE static void vPortSetBASEPRI( uint32_t ulNewMaskValue )
{
    __asm volatile
    (
        "   msr basepri, %0 " ::"r" ( ulNewMaskValue ) : "memory"
    );
}

待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值