RTOS学习之旅(三)(ucosIII 任务时间片 阻塞延时与空闲任务 时间戳)

在之前的代码写的任务切换,虽然实现了任务切换,但是还是没做到双任务同时运行。如果在这个基础上加入Systick中断,就可以实现双任务时间片运行,即每个任务运行时间是一样的。

1.Systick简介

RTOS 需要一个时基来驱动,系统任务调度的频率等于该时基的频率。通常该时基由一个定时器来提供,也可以从其它周期性的信号源获得。刚好 Cortex-M 内核中有一个系统定时器SysTick,它内嵌在 NVIC 中,是一个 24 位的递减的计数器,计数器每计数一次的时间为 1/SYSCLK。当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复。

1.1初始化Systick

Systick很简单,一个初始化就搞定。用的OS_CPU_SysTickInit()函数。在os_cpu_c.c中定义。

void OS_CPU_SysTickInit (CPU_INT32U ms) 
{
 /* 设置重装载寄存器的值 */
 SysTick->LOAD = ms * SystemCoreClock / 1000 - 1; 

 /* 配置中断优先级为最低 */
 NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); 

 /* 复位当前计数器的值 */
 SysTick->VAL = 0; 
 /* 选择时钟源、使能中断、使能计数器 */
 SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | 
 SysTick_CTRL_TICKINT_Msk | 
 SysTick_CTRL_ENABLE_Msk; 
}

配置重装载寄存器的值,如果需要配置为 10ms 产生一次中断,形参设置为 10 即可。

1.2Systick中断服务函数

void SysTick_Handler(void)
{
 	OSTimeTick();
}
void OSTimeTick (void)
{
	 /* 任务调度 */
 	OSSched();
}
void OSSched (void)
{
 	if ( OSTCBCurPtr == OSRdyList[0].HeadPtr ) {
		 OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
	 } else {
	 OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
	 } 
	OS_TASK_SW();
}

这个OSSched和之前的一样,还是没变。

1.3main函数

main函数大部分都没修改,只是加了两行。

int main(void)
{
	OS_ERR err;
 
 	/* 关闭中断 */
 	CPU_IntDis();       (1)
	 /* 配置 SysTick 10ms 中断一次 */
 	OS_CPU_SysTickInit (10);    (2)

 	/* 初始化相关的全局变量 */
 	OSInit(&err);
 ..........

(1):关闭中断。因为在 OS 系统初始化之前我们使能了 SysTick 定时器产生 10ms 的中断,在中断里面触发任务调度,如果一开始我们不关闭中断,就会在 OS还有启动之前就进入 SysTick 中断,然后发生任务调度,既然 OS 都还没启动,那调度是不允许发生的,所以先关闭中断。系统启动后,中断由 OSStart()函数里面的OSStartHighRdy()重新开启

1.4总结

虽然这个地方使用了Systick中断,但是和之前任务切换的效果是一样的,但是是为后面做准备的。

2.阻塞延时与空闲任务

RTOS中的延时叫阻塞延时,即任务需要延时的时候,任务会放弃 CPU 的使用权,CPU 可以去干其它的事情,当任务延时时间到,重新获取 CPU 使用权,任务继续运行,这样就充分地利用了 CPU 的资源,而不是干等着。
当任务需要延时,进入阻塞状态,如果没有其它任务可以运行,RTOS 都会为 CPU 创建一个空闲任务,这个时候 CPU 就运行空闲任务。类似于低功耗。

2.1定义任务堆栈

#define    OS_CFG_IDLE_TASK_STK_SIZE 128u
CPU_STK    OSCfg_IdleTaskStk[OS_CFG_IDLE_TASK_STK_SIZE];

2.2定义空闲任务函数

其实空闲任务和普通任务一样,只是Task里面做什么不一样。

typedef   CPU_INT32U   OS_IDLE_CTR;
OS_EXT    OS_IDLE_CTR  OSIdleTaskCtr;

void OS_IdleTask (void *p_arg)
{
	 p_arg = p_arg;
 /* 空闲任务什么都不做,只对全局变量 OSIdleTaskCtr ++ 操作 */
 while(1) {
	OSIdleTaskCtr++;
	}
}

2.3空闲任务初始化

空闲任务的初始化在 OSInit()在完成,意味着在系统还没有启动之前空闲任务就已经创建好,具体在 os_core.c 定义。

void OSInit (OS_ERR *p_err)
{
/* 配置 OS 初始状态为停止态 */
 OSRunning = OS_STATE_OS_STOPPED;
 /* 初始化两个全局 TCB,这两个 TCB 用于任务切换 */
 OSTCBCurPtr = (OS_TCB *)0;
 OSTCBHighRdyPtr = (OS_TCB *)0;

 /* 初始化就绪列表 */
 OS_RdyListInit();

 /* 初始化空闲任务 */
 OS_IdleTaskInit(p_err); (1)
 if (*p_err != OS_ERR_NONE) {
 return;
 }
}

 /* 空闲任务初始化 */
void OS_IdleTaskInit(OS_ERR *p_err)
{
	 /* 初始化空闲任务计数器 */
 	OSIdleTaskCtr = (OS_IDLE_CTR)0; (2)
	 /* 创建空闲任务 */
 	OSTaskCreate( (OS_TCB *)&OSIdleTaskTCB, (3)
				  (OS_TASK_PTR )OS_IdleTask,
 				  (void *)0,
 				  (CPU_STK *)OSCfg_IdleTaskStkBasePtr,
 				  (CPU_STK_SIZE)OSCfg_IdleTaskStkSize,
 				  (OS_ERR *)p_err );
}

2.4阻塞延时

阻塞延时的阻塞是指任务调用该延时函数后,任务会被剥离 CPU 使用权,然后进入阻塞状态,直到延时结束,任务重新获取 CPU 使用权才可以继续运行。在任务阻塞的这段时间,CPU 可以去执行其它的任务,如果其它的任务也在延时状态,那么 CPU 就将运行空闲任务。阻塞延时函数在 os_time.c 中定义。

我们先看看main函数,就能更好知道是怎么运行的。

int main(void)
{
 OS_ERR err;

/* 关闭中断 */
 CPU_IntDis();

 /* 配置 SysTick 10ms 中断一次 */
 OS_CPU_SysTickInit (10);

 /* 初始化相关的全局变量 */ 
 OSInit(&err);           (1) 

 /* 创建任务 */
 OSTaskCreate ((OS_TCB*) &Task1TCB,
 (OS_TASK_PTR ) Task1,
 (void *) 0,
 (CPU_STK*) &Task1Stk[0],
 (CPU_STK_SIZE) TASK1_STK_SIZE,
 (OS_ERR *) &err);

 OSTaskCreate ((OS_TCB*) &Task2TCB,
 (OS_TASK_PTR ) Task2,
 (void *) 0,
(CPU_STK*) &Task2Stk[0],
 (CPU_STK_SIZE) TASK2_STK_SIZE,
 (OS_ERR *) &err);

 /* 将任务加入到就绪列表 */
 OSRdyList[0].HeadPtr = &Task1TCB;
 OSRdyList[1].HeadPtr = &Task2TCB;
 
 /* 启动 OS,将不再返回 */
 OSStart(&err);
}


void Task1( void *p_arg )
{
 while(1) {
 flag1 = 1;
 //delay( 100 ); 
 OSTimeDly(2);        (2) 
 flag1 = 0;
 //delay( 100 ); 
 OSTimeDly(2); 
 
 /* 任务切换,这里是手动切换 */
 //OSSched();
 	}
 }
}

(1)和(2)是和之前不一样的地方。

(2) 延时函数均替代为阻塞延时,延时时间均为 2 个 SysTick 中断周期,即 20ms。

struct os_tcb {
 CPU_STK *StkPtr;
 CPU_STK_SIZE StkSize;
 /* 任务延时周期个数 */
 OS_TICK TaskDelayTicks;
};

void OSTimeDly(OS_TICK dly)
{
	 /* 设置延时时间 */
	OSTCBCurPtr->TaskDelayTicks = dly; 

	 /* 进行任务调度 */
 	OSSched(); 
}

os_tcb结构体里面再加一个成员,就像下面这个设置的,如果形参传的是2,那么就延时2个周期,一个周期10ms。

void OSSched(void)
{
 	if ( OSTCBCurPtr == &OSIdleTaskTCB ) {         //(1)
		if (OSRdyList[0].HeadPtr->TaskDelayTicks == 0) {
 			OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
 		}		 
 		else if (OSRdyList[1].HeadPtr->TaskDelayTicks == 0) {
			 OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
		 }
		 else {
 			/* 任务延时均没有到期则返回,继续执行空闲任务 */
 			return;
 		}
 	} else {        //(2)
 		if (OSTCBCurPtr == OSRdyList[0].HeadPtr) {
 			if (OSRdyList[1].HeadPtr->TaskDelayTicks == 0) {
				 OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;
 			} 
 			else if (OSTCBCurPtr->TaskDelayTicks != 0) {
 				OSTCBHighRdyPtr = &OSIdleTaskTCB;
 	}
 	 	else {
 			/* 返回,不进行切换,因为两个任务都处于延时中 */
 			return;
 		}
 	} 
 		else if (OSTCBCurPtr == OSRdyList[1].HeadPtr) {
 			if (OSRdyList[0].HeadPtr->TaskDelayTicks == 0) {
 					OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;
		 } 
		 	else if (OSTCBCurPtr->TaskDelayTicks != 0) {
					 OSTCBHighRdyPtr = &OSIdleTaskTCB;
	 } else {
 		/* 返回,不进行切换,因为两个任务都处于延时中 */
		 return;
  			}
 	}
}
 
 /* 任务切换 */
 OS_TASK_SW();       //(3)
}

(1)如果当前任务是空闲任务,那么就去尝试执行任务 1 或者任务 2,看看他们的延时时间是否结束,如果任务的延时时间均没有到期,那就返回继续执行空闲任务。
(2)无论是哪个任务,都要检查下另外一个任务是否在延时中,如果没有在延时,那就切换到该任务,如果有在延时,那就判断下当前任务是否应该进入延时状态,如果是的话,就切换到空闲任务。否则就不进行任务切换 。
(3)任务切换,实际就是触发 PendSV 异常。

2.5总结

这时候任务就是同时进行的,比如有两个任务,每个任务都是翻转的。
在这里插入图片描述

3.时间戳

在 uC/OS-III 中,很多地方的代码都加入了时间测量的功能,比如任务关中断的时间,关调度器的时间等。知道了某段代码的运行时间,就明显地知道该代码的执行效率。
如果要测量一段代码 A 的时间,那么可以在代码段 A 运行前记录一个时间点 TimeStart,在代码段 A 运行完记录一个时间点 TimeEnd,那么代码段 A 的运行时间 TimeUse 就等于 TimeEnd 减去 TimeStart。这里面的两个时间点TimeEnd 和 TimeStart,就叫做时间戳,时间戳实际上就是一个时间点。
通常执行一条代码是需要多个时钟周期的,即是 ns 级别。

时间戳差不多就是一些初始化啊什么的,然后减一下什么的,如果有需要就看看手册。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值