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

2847

被折叠的 条评论
为什么被折叠?



