uC/OS-III 入门教程-两个任务切换
上一篇博客:【uC/OS-III篇】uC/OS-III 创建第一个任务(For STM32)-CSDN博客 中使用的延时函数是软件延时
但是软件延时一方面不精确,另一方面并不会引起任务的调度
所以想要使用精确的延时,就需要使用到STM32的滴答定时器,并且利用滴答定时器为 uC/OS-III 操作系统提供节拍
一、STM32的滴答定时器简介
观察STM32F103的参考手册中的时钟章节,能够发现SYSTEM时钟是经过 72MHz(最高)8分频后的,所以滴答定时器的时钟一般为:
72MHz / 8 = 9MHz
二、系统滴答定时器与OS的时间片关系
简单来说:
-
默认滴答定时器的时钟是72MHz的8分频,即9MHz
-
需要设置系统滴答定时器的重装载计数值,且滴答定时器中断一次的时间 = OS的节拍时间
计算系统滴答定时器的重装载计数值 reload 的公式原理:滴答定时器中断一次的时间 = OS的节拍时间
即 1 1
--------------------- * reload = ---------------------
滴答定时器主频 OS的频率
带入变量得:
1 1
--------------------- * reload = ----------------------
SystemCoreClock/8(分频) OS_CFG_TICK_RATE_HZ
推导得到:
SystemCoreClock
reload = --------------------------
8 * OS_CFG_TICK_RATE_HZ
以上推导中,reload 为滴答定时器计数重装载值,OS_CFG_TICK_RATE_HZ 为设置的 OS 的节拍频率,即 1s/OS_CFG_TICK_RATE_HZ 为 OS的最小时间片
三、初始化系统滴答定时器代码
// 初始化系统时钟
void System_init(void)
{
// 默认滴答定时器的时钟是72M的8分频,即9M
/* 计算 reload 的公式原理:滴答定时器中断一次的时间 = OS的节拍时间
即 1 1
--------------------- * reload = ---------------------
滴答定时器主频 OS的频率
带入变量得:
1 1
--------------------- * reload = ----------------------
SystemCoreClock/8(分频) OS_CFG_TICK_RATE_HZ
推导得到:
SystemCoreClock
reload = --------------------------
8 * OS_CFG_TICK_RATE_HZ
*/
u32 reload;
reload=SystemCoreClock/8; //每秒钟的计数次数
reload*=1/OS_CFG_TICK_RATE_HZ; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777215,在72M下,约合1.86s左右
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/reload秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
针对这句:reload=SystemCoreClock/8000000;
查看 SystemCoreClock 宏定义
查看 SYSCLK_FREQ_72MHz 宏定义
所以得到 reload 一开始的值是 9000000,即 9MHz的意思,表示 1秒钟计数 9000000 次;
接着,我们设置了 OS 的节拍频率为 OS_CFG_TICK_RATE_HZ ,比如 OS_CFG_TICK_RATE_HZ 为 1000,即我们希望 OS 的时间片为 1ms,也就是 1秒钟有1000次的节拍数
综合以上内容,可以得出滴答定时器 1秒钟计数 9000000 次,但是 OS 希望的是 1秒钟有1000次的节拍数,所以根据等比例原则,
只要让滴答定时器每计数 9000000 / 1000 = 9000 次时中断一次,触发一次 OS 的 OS_CPU_SysTickHandler 系统滴答中断即可
所以综合起来的公式就是:reload = SystemCoreClock / (8*OS_CFG_TICK_RATE_HZ); 也对应了“二、系统滴答定时器与OS的时间片关系”中总结的公式
四、新建两个任务,实现任务切换
直接上代码
#include "os.h"
#include "stm32f10x.h"
#include "stdio.h"
#include "led.h"
#include "sys.h"
#include "bsp.h"
#include "usart.h"
/* 定义栈深度 */
#define TASK1_START_STK_SIZE 100 // 栈深度,单位4字节,最小 64 * 4字节
#define TASK2_START_STK_SIZE 100 // 栈深度,单位4字节,最小 64 * 4字节
/* 定义栈空间 */
CPU_STK MyTask1_StartStk[TASK1_START_STK_SIZE];
CPU_STK MyTask2_StartStk[TASK2_START_STK_SIZE];
/* 任务函数 */
void MyTask1(void *p_arg);
void MyTask2(void *p_arg);
/* 定义任务控制块 */
OS_TCB p_tcb1;
OS_TCB p_tcb2;
int main(void)
{
BSP_Init();
Usart1_Init(115200);
// 初始化滴答定时器
System_init();
// 用于使用uC/OS函数时返回错误码
OS_ERR err;
// 初始化 uC/OS-III 中的内部变量和数据结构
OSInit(&err);
// 创建第一个uC/OS任务
OSTaskCreate ((OS_TCB *)&p_tcb1, // 任务控制块地址
(CPU_CHAR *)"MyTask1", // 任务名
(OS_TASK_PTR )MyTask1, // 任务函数地址
(void *)0, // 参数
(OS_PRIO )4, // 任务优先级
(CPU_STK *)&MyTask1_StartStk[0], // 任务栈空间基地址
(CPU_STK_SIZE )TASK1_START_STK_SIZE/10, // 任务栈的深度标记,代表栈溢出警告之前栈内应该剩余的空间,即栈极限深度
(CPU_STK_SIZE )TASK1_START_STK_SIZE, // 任务栈深度,单位4字节
(OS_MSG_QTY )0,
(OS_TICK )0, // 拥有多少个时间片
(void *)0,
// 任务可选项,创建任务时清空栈空间,运行时检查任务栈的使用情况
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
// 创建第二个uC/OS任务
OSTaskCreate ((OS_TCB *)&p_tcb2, // 任务控制块地址
(CPU_CHAR *)"MyTask2", // 任务名
(OS_TASK_PTR )MyTask2, // 任务函数地址
(void *)0, // 参数
(OS_PRIO )4, // 任务优先级
(CPU_STK *)&MyTask2_StartStk[0], // 任务栈空间基地址
(CPU_STK_SIZE )TASK2_START_STK_SIZE/10, // 任务栈的深度标记,代表栈溢出警告之前栈内应该剩余的空间,即栈极限深度
(CPU_STK_SIZE )TASK2_START_STK_SIZE, // 任务栈深度,单位4字节
(OS_MSG_QTY )0,
(OS_TICK )0, // 拥有多少个时间片
(void *)0,
// 任务可选项,创建任务时清空栈空间,运行时检查任务栈的使用情况
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)&err);
OSStart(&err);
return 0;
}
/* 任务一 */
int task1_flag = 0; // 需要定义成全局变量才能添加到仿真中的逻辑分析仪,且非静态
void MyTask1(void *p_arg)
{
OS_ERR err;
while(1)
{
task1_flag = 1;
OSTimeDly(500,OS_OPT_TIME_DLY, &err);
task1_flag = 0;
OSTimeDly(500,OS_OPT_TIME_DLY, &err);
}
}
/* 任务二 */
int task2_flag = 0;
void MyTask2(void *p_arg)
{
OS_ERR err;
while(1)
{
task2_flag = 1;
OSTimeDly(500,OS_OPT_TIME_DLY, &err);
task2_flag = 0;
OSTimeDly(500,OS_OPT_TIME_DLY, &err);
}
}
通过仿真中的逻辑分析仪可以看出任务切换的时间正好是500ms,精确度高,从变量的变化也看得出两个任务在有序的切换运行
五、观察OS_CPU_SysTickHandler函数
OS_CPU_SysTickHandler 函数原型如下
/*
*********************************************************************************************************
* SYS TICK HANDLER
*
* Description: Handle the system tick (SysTick) interrupt, which is used to generate the uC/OS-III tick
* interrupt.
*
* Arguments : None.
*
* Note(s) : 1) This function MUST be placed on entry 15 of the Cortex-M vector table.
*********************************************************************************************************
*/
void OS_CPU_SysTickHandler (void)
{
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OSIntEnter(); /* 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 */
}
修改如下
/*
*********************************************************************************************************
* SYS TICK HANDLER
*
* Description: Handle the system tick (SysTick) interrupt, which is used to generate the uC/OS-III tick
* interrupt.
*
* Arguments : None.
*
* Note(s) : 1) This function MUST be placed on entry 15 of the Cortex-M vector table.
*********************************************************************************************************
*/
int os_flag = 0;
void OS_CPU_SysTickHandler (void)
{
os_flag = 1;
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OSIntEnter(); /* 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_flag = 0;
}
打开逻辑分析仪观察 os_flag 变量的变化,可以看到 os_flag 变量有规律地发生了跳变
我们放大再看,发现 os_flag 从高电平变回低电平只有 5us 左右的时间,说明中断里的几行代码的执行时间只有 5us 的样子
这是因为 OS 要在 OS_CPU_SysTickHandler 中断中判断是否有任务需要被切换等其他工作,这些过程不能耽误太长的时间,否则会影响整体的实时性,所以要求 OS_CPU_SysTickHandler 中断的时间要极短
放大测量两次中断的时间间隔,正好是设置的 1/OS_CFG_TICK_RATE_HZ 秒,即频率1000Hz,1ms中断一次