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" ); }待续...