目录
1.Cortex-M4中SysTick的重要性
2.Cortex-M4中的中断管理
2.1.Cortex-M4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有256 级的可编程中断优先级设置。但 STM32F4 并没有使用 Cortex-M4 内核的全部东西,而是只用了它的一部分。
2.2.Cortex-M4处理器中,每一个外部中断都可以被使能或者禁止,并且可以被设置为挂起状态或者清除状态。
2.3.中断和异常向量表
2.4. Cortex-M4寄存器组——特殊寄存器
2.4.1. xPSR:组合程序状态寄存器,该寄存器由三个程序状态寄存器组成:
应用PSR(APSR) : 包含前一条指令执行后的条件标志
中断PSR(IPSR) : 包含当前ISR的异常编号
执行PSR(EPSR) : 包含Thumb状态位
2.4.2. PRIMSK:中断屏蔽特殊寄存器
2.4.3. CONTROL:控制寄存器
[PRIV]: 为0,处理器处于线程模式的特权级,为1为非特权级
[SPSEL] : 为0时,线程模式使用MSP,为1时使用PSP
处理器模式时,固定使用MSP
3.Cortex-M4中影子栈指针
4.Cortex-M4中SVC和PendSV异常
5.多任务启动
5.1.创建空闲任务
5.2.配置SysTick,PendSV为最低优先级
优先外部中断触发,保证实时响应外部中断
5.3.配置SysTick寄存器
5.4.调用SVC中断
//多任务启动
osKernelStart ();---------->
vTaskStartScheduler();---------->
xPortStartScheduler();---------->
prvStartFirstTask();---------->
SVC------------------------->
//启动任务调度器
void vTaskStartScheduler( void )
{
BaseType_t xReturn;
/* Add the idle task at the lowest priority. */
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
}
#else
{
//动态创建一个空闲任务
/* The Idle task is being created using dynamically allocated RAM. */
xReturn = xTaskCreate( prvIdleTask,
configIDLE_TASK_NAME,
configMINIMAL_STACK_SIZE,
( void * ) NULL,
portPRIVILEGE_BIT, /* In effect ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), but tskIDLE_PRIORITY is zero. */
&xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
if( xReturn == pdPASS ) //如果创建成功
{
//关闭中断
/* Interrupts are turned off here, to ensure a tick does not occur
before or during the call to xPortStartScheduler(). The stacks of
the created tasks contain a status word with interrupts switched on
so interrupts will automatically get re-enabled when the first task
starts to run. */
portDISABLE_INTERRUPTS();
//下一个任务的锁定时间赋值为最大值
//#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
//不让时间片进行调度
xNextTaskUnblockTime = portMAX_DELAY;
//调度器的运行状态置位,标记开始运行了
xSchedulerRunning = pdTRUE;
//初始化 系统的节拍值为0
//#define configINITIAL_TICK_COUNT 0
xTickCount = ( TickType_t ) configINITIAL_TICK_COUNT;
//启动调度器
/* Setting up the timer tick is hardware specific and thus in the
portable interface. */
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(). */
}
}
else
{
}
}
//启动调度器
BaseType_t xPortStartScheduler( void )
{
//配置PendSV和SysTick为最低优先级,为了保证系统的实时性
/* Make PendSV and SysTick the lowest priority interrupts. */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;
/*
1.初始化systick---->配置为1ms的中断产生时基
2.开启systick中断
*/
/* Start the timer that generates the tick ISR. Interrupts are disabled
here already. */
vPortSetupTimerInterrupt();
//初始化临界段嵌套值
/* Initialise the critical nesting count ready for the first task. */
uxCriticalNesting = 0;
//初始化浮点数寄存器,M4特有,需要初始化
/* Ensure the VFP is enabled - it should be anyway. */
prvEnableVFP();
/* Lazy save always. */
*( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;
//启动第一个任务
/* Start the first task. */
prvStartFirstTask();
/* Should not get here! */
return 0;
}
//启动第一个任务
__asm void prvStartFirstTask( void )
{
PRESERVE8 //8字节对齐,AAPCS的标准,ARM特有
/*
0xE000ED08是中断向量表的一个地址,存储的是MSP的指针
最终获取到MSP的RAM地址
*/
/* Use the NVIC offset register to locate the stack. */
ldr r0, =0xE000ED08
ldr r0, [r0]
ldr r0, [r0]
/*
重新把MSP的地址,赋值为MSP
作用:
1.如果没有在线升级的功能,可以屏蔽这段代码
2.BSP Bootloader----可以选择程序运行的代码段
3.这个时候,中断向量表会更新,所以要重新赋值MSP
*/
/* Set the msp back to the start of the stack. */
msr msp, r0
/* Clear 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. */
mov r0, #0
msr control, r0
//开启全局中断
/* Globally enable interrupts. */
cpsie i
cpsie f
dsb
isb
//调用SVC系统
/* Call SVC to start the first task. */
svc 0
nop
nop
}
//svc异常处理
__asm void vPortSVCHandler( void )
{
PRESERVE8 //8字节对齐
/*
获取当前任务控制块
任务控制块的第一成员是 任务的栈顶
获取到栈顶之后,剩下的事就是出栈工作
出栈--------任务的堆栈
*/
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r1, [r3]
ldr r0, [r1]
/*
出栈内核寄存器
R14就是异常返回值 #define portINITIAL_EXC_RETURN ( 0xfffffffd )
返回处理模式并在返回后使用进程栈(表示异常退出后,使用PSP)
*/
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
//更新栈指针到PSP
msr psp, r0
isb
//把basepri赋值为0,即打开屏蔽中断
mov r0, #0
msr basepri, r0
//异常退出
bx r14
//为什么没有恢复其他寄存器?
//其他寄存器在出栈的时候会自动恢复,由硬件处理
//最终跳转到任务的执行函数里面
}
6.PendSV业务流程
6.1.读取当前PSP值;获取当前任务栈顶
6.2.保存s16-s31到栈中;保存r4-r11,r14到当前栈中
6.3.更新栈顶到当前任务控制块中;保存r3到栈中;关闭中断
6.4.查找优先级最高的任务,更新当前任务控制块;开启中断;出栈r3值
出栈r3,即出栈下一个任务的栈空间
6.5.出栈r4-r11,r14到当前栈中;出栈s16-s31到栈中;更新栈顶到PSP;调用异常返回指令
__asm void xPortPendSVHandler( void )
{
extern uxCriticalNesting;
extern pxCurrentTCB;
extern vTaskSwitchContext;
PRESERVE8
mrs r0, psp
isb
//获取当前任务控制块,其实就是获取任务栈顶
/* Get the location of the current TCB. */
ldr r3, =pxCurrentTCB
ldr r2, [r3]
//浮点数处理,如果使能浮点数,就需要入栈
/* Is the task using the FPU context? If so, push high vfp registers. */
tst r14, #0x10
it eq
vstmdbeq r0!, {s16-s31}
//保存内核寄存器----调用者需要做的
/* Save the core registers. */
stmdb r0!, {r4-r11, r14}
//保存当前任务栈顶,把栈顶指针入栈
/* Save the new top of stack into the first member of the TCB. */
str r0, [r2]
stmdb sp!, {r0, r3}
//使能可屏蔽的中断----临界段
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
msr basepri, r0
dsb
isb
//执行上下文切换
bl vTaskSwitchContext
//使能可屏蔽的中断
mov r0, #0
msr basepri, r0
//恢复任务控制块指向的栈顶
//在上下文切换的时候,当前任务的指针,已经指向了优先级最高的
ldmia sp!, {r0, r3}
//获取当前栈顶
/* The first item in pxCurrentTCB is the task top of stack. */
ldr r1, [r3]
ldr r0, [r1]
//出栈
/* Pop the core registers. */
ldmia r0!, {r4-r11, r14}
//出栈
/* Is the task using the FPU context? If so, pop the high vfp registers
too. */
tst r14, #0x10
it eq
vldmiaeq r0!, {s16-s31}
//更新PSP指针
msr psp, r0
isb
//异常返回,下面要执行的代码,就是要切换的任务代码了
bx r14
}
//上下文切换
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
//标记调度器状态
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
else
{
//标记调度器状态
xYieldPending = pdFALSE;
//检查任务栈是否溢出
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
//选择优先级最高的任务,把当前的任务控制块进行赋值
/* Select a new task to run using either the generic C or port
optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
traceTASK_SWITCHED_IN();
}
}
7.系统时钟节拍详解
7.1.配置SysTick装载值
默认时基为1ms,不要低于1ms;可按10的倍数设置,如10ms,100ms等
7.2.使能SysTick时钟源;使能SysTick中断;使能SysTick
//系统节拍初始化
#if( configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0 )
__weak void vPortSetupTimerInterrupt( void )
{
/* Stop and clear the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/*
1.操作系统是针对寄存器操作的,因为效率高
2.首先赋值装载寄存器值 = (CPU频率/频率周期)-1
2.1.CPU频率在硬件启动时获取
2.2.configTICK_RATE_HZ是由CubeMX配置而得
3.配置控制寄存器
3.1.开启时钟源
3.2.使能中断
3.3.使能systick
4.可以参考M4权威指南,9.5章节----systick定时器
*/
/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
#endif /* configOVERRIDE_DEFAULT_TICK_CONFIGURATION */
8.SysTick中断服务函数
8.1.关闭中断
由于SysTick中断优先级最低,需要关闭中断,否则会被不停抢断
8.2.Tick值增加;SysTick任务调度;启动PendSV
8.3.开启中断
void xPortSysTickHandler( void )
{
/*
1.配置中断屏蔽寄存器
2.不让中断打断systick中断服务
3.其实就是进入临界段
*/
/* 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 - therefore the slightly faster vPortRaiseBASEPRI() function is used
in place of portSET_INTERRUPT_MASK_FROM_ISR(). */
vPortRaiseBASEPRI();
{
/*
操作系统调度接口
如果调度器返回true,触发pendSV异常
*/
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
//触发PendSV
/* A context switch is required. Context switching is performed in
the PendSV interrupt. Pend the PendSV interrupt. */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
//清除可屏蔽中断,其实就是打开全部中断---->pendSV就会执行
vPortClearBASEPRIFromISR();
}
9.SysTick任务调度
9.1.系统节拍数加1;判断是否溢出(uint32);溢出更新任务锁定时间
9.2.判断是否有任务需要解除阻塞;获取延时列表第一个任务控制块(时间排序,第一个时间最短);获取状态列表值,判断时间是否到达,未到达退出
9.3.任务阻塞事件到达;从延时列表中删除;从事件列表中删除;添加到就绪列表
9.4.如果使用抢占内核;判断任务优先级是否大于当前任务;开启任务调度
9.5.如果使用时间片调度;判断当前优先级下是否还有其他任务;开启任务调度器
//SysTick任务调度
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
//返回值,表示是否进行上下文切换
BaseType_t xSwitchRequired = pdFALSE;
//uxSchedulerSuspended 表示内核的调度器是否挂起,pdFALSE表示内核没有挂起
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
//tick计数增加1
/* Minor optimisation. The tick count cannot change in this
block. */
const TickType_t xConstTickCount = xTickCount + ( TickType_t ) 1;
/* Increment the RTOS tick, switching the delayed and overflowed
delayed lists if it wraps to 0. */
xTickCount = xConstTickCount;
//判断tick是否溢出
if( xConstTickCount == ( TickType_t ) 0U ) /*lint !e774 'if' does not always evaluate to false as it is looking for an overflow. */
{
//如果溢出,要更新延时列表
taskSWITCH_DELAYED_LISTS();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*
1.当前节拍大于时间片的锁定时间
2.就说明有任务需要调度了,时间片用完了
*/
/* See if this tick has made a timeout expire. Tasks are stored in
the queue in the order of their wake time - meaning once one task
has been found whose block time has not expired there is no need to
look any further down the list. */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; ) //一直遍历整个任务延时列表,主要目的是找到时间片最短的任务,进行切换
{
//判断任务延时列表中是否为空,即有没有任务在等待调度
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
//如果没有任务等待,把时间片赋值为最大值,不再调度
/* The delayed list is empty. Set xNextTaskUnblockTime
to the maximum possible value so it is extremely
unlikely that the
if( xTickCount >= xNextTaskUnblockTime ) test will pass
next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
}
else
{
/*
1.从任务延时列表中,获取第一个任务控制块
延时列表插入永远是把时间片最短的任务,放在第一个
2.获取任务控制块的延时时间
*/
/* The delayed list is not empty, get the value of the
item at the head of the delayed list. This is the time
at which the task at the head of the delayed list must
be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
//再次判断,这个任务的时间片是否到达
if( xConstTickCount < xItemValue )
{
//没有到达,把此任务的时间片更新为当前系统的时间片
/* It is not time to unblock this item yet, but the
item value is the time at which the task at the head
of the blocked list must be removed from the Blocked
state - so record the item value in
xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
//直接退出,不用调度
break; /*lint !e9011 Code structure here is deedmed easier to understand with multiple breaks. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//把任务从延时列表中移除
/* It is time to remove the item from the Blocked state. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
//把任务从事件列表中移除
/* Is the task waiting on an event also? If so remove
it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//把任务添加到就绪列表中
/* Place the unblocked task into the appropriate ready
list. */
prvAddTaskToReadyList( pxTCB );
//抢占式处理
/* A task being unblocked cannot cause an immediate
context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/*
判断优先级是否大于当前任务
大于则进行调度
*/
/* Preemption is on, but a context switch should
only be performed if the unblocked task has a
priority that is equal to or higher than the
currently executing task. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
//时间片处理机制
/* Tasks of equal priority to the currently running task will share
processing time (time slice) if preemption is on, and the application
writer has not explicitly turned time slicing off. */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
/*
获取就绪列表长度
就绪列表指的是当前任务优先级列表
如果有其他任务在就绪列表中,就开始调度
*/
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
//如果是抢占模式,要开启调度
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
else //内核调度器挂起了
{
//挂起的tick+1
++xPendedTicks;
}
return xSwitchRequired;
}