基于STM32C8T6
二、使用步骤
1.开启任务调度器
创建任务后不会马上执行任务,而是开启任务调度器
vTaskStartScheduler();
2.vTaskStartScheduler##
- 进入该函数若该宏定义设置为1则则会创建静态空闲任务,为0则会创建动态空闲任务
configSUPPORT_STATIC_ALLOCATION
- 是否使能软件定时器,1为创建软件定时器任务
#if ( configUSE_TIMERS == 1 )
{
if( xReturn == pdPASS )
{
xReturn = xTimerCreateTimerTask();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
- 创建成功后才会进行下一步,关中断,防止开启调度器是被中断打扰,在执行第一个任务时关闭中断
portDISABLE_INTERRUPTS();
- xNextTaskUnblockTime为下一个任务的阻塞超时时间。 如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,xNextTaskUnblockTime 的值等于系统时基计数器的值 xTickCount 加上任务需要延时的值 xTicksToDelay。其更新要运行的任务中最小的xTicksToDelay,当系统时基计数器 xTickCount 的值与 xNextTaskUnblockTime 相等时,就表示有任务延时到期了,需要将该任务就绪。
但是此时还没有任务运行,给其赋值最大值
xNextTaskUnblockTime = portMAX_DELAY;
xSchedulerRunning = pdTRUE;//表示调度器正在运行
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;//系统节拍赋值为零,准备看开始执行任务
- 初始化任务运行时间统计功能的定时器
- xPortStartScheduler
if( xPortStartScheduler() != pdFALSE )
{
/* Should not reach here as if the scheduler is running the
* function will not return. */
}
else
{
/* Should only reach here if a task calls xTaskEndScheduler(). */
}
- 完成启动任务调度器中与硬件架构相关的配置部分并启动第一个任务
vPortSetupTimerInterrupt();//启动滴答定时器
- 滴答定时器初始化
__weak void vPortSetupTimerInterrupt( void )
{
/* Calculate the constants required to configure the tick interrupt. */
#if ( configUSE_TICKLESS_IDLE == 1 )
{
ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#endif /* configUSE_TICKLESS_IDLE */
/* Stop and clear the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;//
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;//系统主频除系统节拍M3的主频为72兆赫兹,持续递减,72000,当减到0时,将被重装载触发中断
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );//内部时钟主频为72MHz,数一个拍子的时间为1/72MHz,数72000个拍子产生中断,每1毫秒中断一次
}
- List item
uxCriticalNesting = 0;//临界区嵌套次数计数器为0,为开始第一个任务做准备
/* Start the first task. */
prvStartFirstTask();//开启第一个任务
-
prvStartFirstTask();
将第一个任务的寄存器的值恢复到CPU寄存器中,该值初始被保存在任务堆栈中。
中断产生时,xPSR,PC(R15),LR,R12,R3,R2,R1,R0,硬件自动保存和回复。
进入中断后硬件强行使用MSP指针,此时(LR)R14的值将会自动被更新为特殊的EXC_RETURN 。
- msp
裸机中只能使用一个
在FreeRTOS中,中断使用MSP,中断以外使用PSP
__asm void prvStartFirstTask( void )
{
/* *INDENT-OFF* */
PRESERVE8//八字节对齐,汇编语言,得手动对齐
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08//获取r0的寄存器地址
ldr r0, [ r0 ]//获取向量表存储的地址
ldr r0, [ r0 ]//获取该地址下第一个元素也就是MSP的初始值
/* Set the msp back to the start of the stack. */
msr msp, r0//初始化MSP
/* Globally enable interrupts. */
cpsie i//使用汇编语言开启全局中断,对应一开始的关中断
cpsie f
dsb
isb
/* Call SVC to start the first task. */
svc 0//调用SVC启动第一个函数在这触发了SVC中断
nop
nop
/* *INDENT-ON* */
}
上电之后MSP作为主堆栈指针运行到执行第一个任务时他的值早就不是原来的值了,现在把他的初始值重新赋值给他,代表在这期间的产生的寄存器的值都不保存了,意味着从开始第一个任务开始cpu就在任务与任务之间跳动了,不在返回
- SVC
SVC中断只会在第一次执行任务前进入,从此之后就是PSV了
__asm void vPortSVCHandler( void )
{
/* *INDENT-OFF* */
PRESERVE8//对齐
ldr r3, = pxCurrentTCB /*获取当前任务控制块的地址. */
ldr r1, [ r3 ] /* Use pxCurrentTCBConst to get the pxCurrentTCB address. *///通过TCB的地址获取其首成员的地址也就是栈顶
ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. *///通过首成员的地址获取首成员的值
ldmia r0 !, { r4 - r11 } /* 出栈,通过获取的栈顶指针将任务栈中的内容从下往上 一个一个将值保存在R4——R11中*/
msr psp, r0 /* Restore the task stack pointer. *///将R0这个地址赋值给PSP
isb
mov r0, # 0//r0=0
msr basepri, r0//0=basepri
orr r14, # 0xd
bx r14//return R14 CPU会从psp指向的栈中出栈那些特殊寄存器。然后PSP会跳转到PC寄存器(任务函数地址)执行任务
/* *INDENT-ON* */
}