在APP组里的os_cfg_app.h文件可以设置系统时钟SysTick的时钟节拍频率(还可以设置它的任务优先级,任务栈大小,时间片数组大小),一般使用1000Hz,也就是节拍周期为1ms。
#define OS_CFG_TICK_RATE_HZ 1000u /* Tick rate in Hertz (10 to 1000 Hz) */ #define OS_CFG_TICK_TASK_PRIO 10u /* Priority */ #define OS_CFG_TICK_TASK_STK_SIZE 128u /* Stack size (number of CPU_STK elements) */ #define OS_CFG_TICK_WHEEL_SIZE 17u /* Number of 'spokes' in tick wheel; SHOULD be prime */
下面我们看看它的配置步骤的关键代码,BSP_CPU_ClkFreq()函数内部其实调用的rcc的库函数来获取参考频率的,OS_CPU_SysTickInit()直接配置SysTick(源码中是把控制寄存器或其它寄存器定义成相关变量(在cpu.h文件里定义),再操作;详情go to definition/reference of ‘something“)。
cpu_clk_freq = BSP_CPU_ClkFreq(); /* Determine SysTick reference freq.确定系统定时器参考频率 */ cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; /* Determine nbr SysTick increments */ OS_CPU_SysTickInit(cnts); /* Init uC/OS periodic time src (SysTick). */
蒜了,不要偷懒,还是把代码贴一下,这段代码在os_cpu_c.c文件里,英文注释已经说明它干了什么,如果不清楚某个变量或参数,查找定义就OK了。
void OS_CPU_SysTickInit (CPU_INT32U cnts) { CPU_INT32U prio; CPU_REG_NVIC_ST_RELOAD = cnts - 1u; //装载的值
/* Set SysTick handler prio. */ prio = CPU_REG_NVIC_SHPRI3; prio &= DEF_BIT_FIELD(24, 0); prio |= DEF_BIT_MASK(OS_CPU_CFG_SYSTICK_PRIO, 24); CPU_REG_NVIC_SHPRI3 = prio; /* Enable timer. */ CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_CLKSOURCE | CPU_REG_NVIC_ST_CTRL_ENABLE; /* Enable timer interrupt. */ CPU_REG_NVIC_ST_CTRL |= CPU_REG_NVIC_ST_CTRL_TICKINT; }
还记得之前移植的时候,将启动文件里的系统定时器中断和异常处理中断向量进行改名,改为OS_CPU_SysTickHandler的这个函数的就在上一段代码的上部,上面这个函数是表明特定时间中断一次,来处理系统事务,也就是中断的时候,干了些什么,其实仔细一看就是调用了OSTimeTick()。
void OS_CPU_SysTickHandler (void) { CPU_SR_ALLOC(); //使用临界区前用它保存中断状态,下面进出临界区都必须调用相关函数 CPU_CRITICAL_ENTER(); OSIntNestingCtr++; /* Tell uC/OS-III that we are starting an ISR */ CPU_CRITICAL_EXIT(); OSTimeTick(); /* Call uC/OS-III's OSTimeTick() */ OSIntExit(); /* Tell uC/OS-III that we are leaving the ISR */ }
那下面我们就看看这个位于os_time.c文件里的OSTimeTick()干了些什么?
也就是说上面的函数会发送信号量给OS_TickTask(),它接收到信号量之后就会进入就绪态,准备运行。
void OS_TickTask (void *p_arg) { OS_ERR err; CPU_TS ts; p_arg = p_arg; /* Prevent compiler warning */ while (DEF_ON) { (void)OSTaskSemPend((OS_TICK )0,//等待传来的信号量,以跳出循环,继续运行 (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)&ts, (OS_ERR *)&err); /* Wait for signal from tick interrupt */ if (err == OS_ERR_NONE) { if (OSRunning == OS_STATE_OS_RUNNING) { OS_TickListUpdate(); /* Update all tasks waiting for time */ } } } }
又可以看到OS_TickListUpdate的作用是更新所有任务的等待时间;也就是说,任务的执行,就是由前面说的系统时钟节拍按固定周期产生中断,以推送信号量给时基任务,时基任务再调用节拍列表更新函数,来更新所有任务的等待时间(然而这个函数又依据一些算法来实现的,太长了就不贴了)。
不急,接着往下看。。。
上一篇已经说过怎么新建任务了,那我们就直接来看app.c
********************************************************************************************************* */ /* ********************************************************************************************************* * INCLUDE FILES ********************************************************************************************************* */ #include <includes.h> /* ********************************************************************************************************* * LOCAL DEFINES ********************************************************************************************************* */ /* ********************************************************************************************************* * TCB ********************************************************************************************************* */ static OS_TCB AppTaskStartTCB; static OS_TCB AppTaskTestTCB; /* ********************************************************************************************************* * STACKS ********************************************************************************************************* */ static CPU_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; static CPU_STK AppTaskTestStk [ APP_TASK_TEST_STK_SIZE ]; /* ********************************************************************************************************* * FUNCTION PROTOTYPES ********************************************************************************************************* */ static void AppTaskStart (void *p_arg); static void AppTaskTest ( void * p_arg ); int main (void) { OS_ERR err; OSInit(&err); //初始化 uC/OS-III /* 创建起始任务 */ OSTaskCreate((OS_TCB *)&AppTaskStartTCB, //任务控制块地址 (CPU_CHAR *)"App Task Start", //任务名称 (OS_TASK_PTR ) AppTaskStart, //任务函数 (void *) 0, //传递给任务函数(形参p_arg)的实参 (OS_PRIO ) APP_TASK_START_PRIO, //任务的优先级 (CPU_STK *)&AppTaskStartStk[0], //任务堆栈的基地址 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10, //任务堆栈空间剩下1/10时限制其增长 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE, //任务堆栈空间(单位:sizeof(CPU_STK)) (OS_MSG_QTY ) 5u, //任务可接收的最大消息数 (OS_TICK ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10) (void *) 0, //任务扩展(0表不扩展) (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项 (OS_ERR *)&err); //返回错误类型 OSStart(&err); //启动多任务管理(交由uC/OS-III控制) } static void AppTaskStart (void *p_arg) { CPU_INT32U cpu_clk_freq; CPU_INT32U cnts; OS_ERR err; (void)p_arg; BSP_Init(); //板级初始化 CPU_Init(); //初始化 CPU 组件(时间戳、关中断时间测量和主机名) cpu_clk_freq = BSP_CPU_ClkFreq(); //获取 CPU 内核时钟频率(SysTick 工作时钟) cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz; //根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值 OS_CPU_SysTickInit(cnts); //调用 SysTick 初始化函数,设置定时器计数值和启动定时器 Mem_Init(); //初始化内存管理组件(堆内存池和内存池表) #if OS_CFG_STAT_TASK_EN > 0u //如果使能(默认使能)了统计任务 OSStatTaskCPUUsageInit(&err); //计算没有应用任务(只有空闲任务)运行时 CPU 的(最大) #endif //容量(决定 OS_Stat_IdleCtrMax 的值,为后面计算 CPU //使用率使用)。 CPU_IntDisMeasMaxCurReset(); //复位(清零)当前最大关中断时间 /* 创建测试任务 */ OSTaskCreate((OS_TCB *)&AppTaskTestTCB, //任务控制块地址 (CPU_CHAR *)"App Task Test", //任务名称 (OS_TASK_PTR ) AppTaskTest, //任务函数 (void *) 0, //传递给任务函数(形参p_arg)的实参 (OS_PRIO ) APP_TASK_TEST_PRIO, //任务的优先级 (CPU_STK *)&AppTaskTestStk[0], //任务堆栈的基地址 (CPU_STK_SIZE) APP_TASK_TEST_STK_SIZE / 10, //任务堆栈空间剩下1/10时限制其增长 (CPU_STK_SIZE) APP_TASK_TEST_STK_SIZE, //任务堆栈空间(单位:sizeof(CPU_STK)) (OS_MSG_QTY ) 5u, //任务可接收的最大消息数 (OS_TICK ) 0u, //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10) (void *) 0, //任务扩展(0表不扩展) (OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项 (OS_ERR *)&err); //返回错误类型 OSTaskDel ( & AppTaskStartTCB, & err ); //删除起始任务本身,该任务不再运行 } /* ********************************************************************************************************* * TEST TASK ********************************************************************************************************* */ static void AppTaskTest ( void * p_arg ) { OS_ERR err; CPU_INT32U cpu_clk_freq; CPU_TS ts_start; CPU_TS ts_end; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和定义一个局部变 //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存SR) //,开中断时将该值还原。 (void)p_arg; cpu_clk_freq = BSP_CPU_ClkFreq(); //获取CPU时钟,时间戳是以该时钟计数 while (DEF_TRUE) { //任务体,通常都写成一个死循环 ts_start = OS_TS_GET(); //获取延时前时间戳 OSTimeDly ( 1000, OS_OPT_TIME_DLY, & err ); //延时1000个时钟节拍(1s) ts_end = OS_TS_GET() - ts_start; //获取延时后的时间戳(以CPU时钟进行计数的一个计数值),并计算延时时间 OS_CRITICAL_ENTER(); //进入临界段,不希望下面串口打印遭到中断 printf ( "\r\n延时1000个时钟节拍(1s),通过时间戳测得延时 %07d us,即 %04d ms。", ts_end / ( cpu_clk_freq / 1000000 ), //将延时时间折算成 us ts_end / ( cpu_clk_freq / 1000 ) ); //将延时时间折算成 ms OS_CRITICAL_EXIT(); //进入临界段,不希望下面串口打印遭到中断 } }
主要就是通过延时1000个时钟节拍,来通过时间戳来计算延时的时间,具体直接看代码及注释就是了,下面是运行结果(由于这是软件延时,所以会有一点点小误差,但已经很精准了),那么就先到这里了。。。