【uC/OS-III篇】uC/OS-III 入门教程-两个任务切换(滴答定时器提供OS节拍)

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的时间片关系

简单来说:

  1. 默认滴答定时器的时钟是72MHz的8分频,即9MHz

  2. 需要设置系统滴答定时器的重装载计数值,且滴答定时器中断一次的时间 = 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中断一次

在这里插入图片描述

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值