UCOS是一个实时的多任务任务基于优先级的操作系统,要运行多任务的话,在启动多任务之后,需要在每隔一段时间(一个滴答)内进行任务调度和相关数据的更新,以确保满足运行要求的最高优先级任务以便在下一次任务调度切换的时候能够得到一个及时的响应。
这个时间间隔是由时间中断来实现,而且每个中断的中断函数需要对相关的数据进行一个周期性更新。
在STM32中时间的中断由SysTick定时器(是cortexM3内核的定时器)实现。
系统时间中断函数OS_CPU_SysTickHandle 函数实现。
系统时钟中断
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(详情请见《CM3权威指南》8.7节)。
在SysTick定时器开始工作之前,需要对定时器进行
初始化,确定其中断时间间隔,以及相关寄存器的配置。其功能由OS_CPU_SysTickInit(os_cpu_c.c)函数实现。
这里配置没有用到st的官方库,而是直接使用的寄存器操作。
代码如下:
//对应寄存器的地址
#define OS_CPU_CM3_NVIC_ST_CTRL (*((volatile INT32U *)0xE000E010)) /* SysTick Ctrl & Status Reg. */
#define OS_CPU_CM3_NVIC_ST_RELOAD (*((volatile INT32U *)0xE000E014)) /* SysTick Reload Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018)) /* SysTick Current Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CAL (*((volatile INT32U *)0xE000E01C)) /* SysTick Cal Value Reg. */
#define OS_CPU_CM3_NVIC_PRIO_ST (*((volatile INT8U *)0xE000ED23)) /* SysTick Handler Prio Reg. */
#define OS_CPU_CM3_NVIC_ST_CTRL_COUNT 0x00010000 /* Count flag. */
#define OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004 /* Clock Source. */
#define OS_CPU_CM3_NVIC_ST_CTRL_INTEN 0x00000002 /* Interrupt enable. */
#define OS_CPU_CM3_NVIC_ST_CTRL_ENABLE 0x00000001 /* Counter mode. */
#define OS_CPU_CM3_NVIC_ST_RELOAD (*((volatile INT32U *)0xE000E014)) /* SysTick Reload Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018)) /* SysTick Current Value Reg. */
#define OS_CPU_CM3_NVIC_ST_CAL (*((volatile INT32U *)0xE000E01C)) /* SysTick Cal Value Reg. */
#define OS_CPU_CM3_NVIC_PRIO_ST (*((volatile INT8U *)0xE000ED23)) /* SysTick Handler Prio Reg. */
#define OS_CPU_CM3_NVIC_ST_CTRL_COUNT 0x00010000 /* Count flag. */
#define OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC 0x00000004 /* Clock Source. */
#define OS_CPU_CM3_NVIC_ST_CTRL_INTEN 0x00000002 /* Interrupt enable. */
#define OS_CPU_CM3_NVIC_ST_CTRL_ENABLE 0x00000001 /* Counter mode. */
#define OS_CPU_CM3_NVIC_PRIO_MIN 0xFF /* Min handler prio. */
void OS_CPU_SysTickInit (void)
{
INT32U cnts;
cnts = OS_CPU_SysTickClkFreq() / OS_TICKS_PER_SEC; //OS_CPU_SysTickClkFreq()是得到systick的时钟频率。则也就是说,计数器一秒钟能计数的次数。OS_TICKS_PER_SEC(os_cfg.h)表示的是一秒钟计数器需要清零的次数,这里配置为1000(可以根据系统的需求进行配置),也就是说1s/1000 = 1ms进行一次systick时间中断。那么1ms内定时器需要计数次数为OS_CPU_SysTickClkFreq() / OS_TICKS_PER_SEC
OS_CPU_CM3_NVIC_ST_RELOAD = (cnts - 1);//将cnts的值填进RELOAD寄存器中
OS_CPU_CM3_NVIC_PRIO_ST = OS_CPU_CM3_NVIC_PRIO_MIN; //设置systick中断的优先级
OS_CPU_CM3_NVIC_ST_CTRL |= OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC | OS_CPU_CM3_NVIC_ST_CTRL_ENABLE;//定时器使能,开启内核时钟
OS_CPU_CM3_NVIC_ST_CTRL |= OS_CPU_CM3_NVIC_ST_CTRL_INTEN; //定时器中断使能
}
系统时钟中断中断函数
在系统时间中断后,便会执行相应的中断函数void OS_CPU_SysTickHandler (void)(os_cpu_c.c)
该函数的主要功能就是将中断状态标志全局变量OSIntNesting加1,然后调用OSTimeTick()进行调度。
在退出中断程序之前,调用OSIntExit,完成退出之前的最后处理。
该函数在之后的中断管理说明。
时间中断任务调度器OSTimeTick
OSTimeTick函数(os_core.c)的主要作用是在系统时钟中断的时候,由中断程序调用,对任务的时延、任务的状态进行修改,设置就绪状态,但是不会实现任务的切换。
函数的核心代码部分如下:
ptcb = OSTCBList; //指向就绪任务控制块链表
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { //遍历链表
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { //如果设置了任务时延或者等待事件时延
ptcb->OSTCBDly--; //将时延时间减1
if (ptcb->OSTCBDly == 0u) { //若是时延时间为0
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { //如果任务等待事件有一个发生了
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; //清除事件等待标志,因为时间已经到了,不管有没有事件发生,都得清零
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; //表示等待事件以为时间超时不在等待
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK; //任务等待完成,或是因为是简单的任务延时,或者是因为任务等待事件已经发生
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { //任务处于就绪状态,而不是阻塞状态(就绪链表中的任务状态只有三种,就绪,阻塞和运行态)
OSRdyGrp |= ptcb->OSTCBBitY; //更新就绪任务表和就绪任务数组
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
在就绪任务链表中,若ptcb->OSTCBDly != 0u,说明该任务处于时延状态(任务时延或者等待时间时延),为阻塞态。(两者等价)