FreeRTOS 第十九章 软件定时器

软件定时间是FreeRTOS提供的一种服务。

在普通的硬件中断中,时间到了是触发的硬件中断,再执行中断函数。而在FreeRTOS中,是需要在创建软件定时器的时候指定回调函数(类似于中断函数),到期了才去执行回调函数,此时回调函数的上下文是用户线程模式,在中断则是特权处理模式。虽然回调函数的上下文是任务环境,同样不允许阻塞。

连续两次调用回调函数的时间xTimerPeriodInTicks称为周期,触发模式还可以分为单次模式和周期模式,单次模式触发一次就被删除。软件定时器不像队列或者消息量,是游走在内存空间,谁都可以访问,谁都可以给它发消息,不属于谁,而软件定时器则不行,它是被一个叫做prvTimerTask的任务管理,是在启动调度器的时候只要配置了使用软件定时器的宏定义就会自动创建的。软件定时器是依赖系统节拍发生器Systick的。而Systick的初始化函数vPortSetupTimerInterrupt是在xPortStartScheduler函数下调用的,在Systick的初始化函数函数下,有这个语句

ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );

这是计算一个Tick所需要定时器计数值(Systick本质也是定时器),configSYSTICK_CLOCK_HZ就是单片机的系统时钟,如果是STM32F10C8T6就是72MHz,它不断地宏定义往下,最后可以找到以下这个数据,这就是裸机开发的时候地系统时钟值。

uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;

configTICK_RATE_HZ是代表Tick的频率,意思就是1s需要滴答多少次,一般定义configTICK_RATE_HZ=1000,也就是1ms滴答一次。

软件定时器的时间需要为1ms的周期。如果希望定时器精度更高,可以提高configTICK_RATE_HZ的值,但是相应的进入Systick中断的次数就会更多。

软件定时器的实现机理:当创建一个软件定时器时,会申请一块内存,这块内存就是软件定时器TCB,同时会根据系统当前时间和传入的定时时间参数做一个加法确定定时器的唤醒时间,并将软件定时器的TCB加入到软件定时器列表,而列表就是双向链表,它可以实现对空间上离散的内存进行管理,一个个软件定时器虽然它们的内存不连续,但是链表把它们勾结在了一起。由于守护任务prvTimerTask是一个专门管理各软件定时器的任务,因此,在创建软件定时器的时候,各任务在发出了创建命令定时器命令后,守护任务prvTimerTask必须马上得到执行把定时器TCB加入链表,这使得守护任务的任务优先级必须是最高的,而且各任务可能会发送不同的命令(创建,删除)。因此选择用消息队列来控制守护任务,即可实现同步功能,也可传递不同命令。因此,要实现软件定时器,底层就会默认创建一个消息队列和任务(守护任务)。

来看看软件定时器任务的TCB

typedef struct tmrTimerControl
{
	const char				*pcTimerName;	//定时器名字	
	ListItem_t				xTimerListItem;		//定时器既然插入列表,就需要列表项
	TickType_t				xTimerPeriodInTicks;// 定时周期
	UBaseType_t				uxAutoReload;	    //单次还是连续	
	void 					*pvTimerID;			//ID,如果一个回调函数的名字分配给多个定时 
                                                器,则可以通过ID来确认不同的执行代码,应该是 
                                                  if语句来的
	TimerCallbackFunction_t	pxCallbackFunction;	//回调函数的函数入口
	#if( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t			uxTimerNumber;		
	#endif

#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
	uint8_t 			ucStaticallyAllocated; 
	#endif
} xTIMER;

/
typedef xTIMER Timer_t;

软件定时器创建函数,xTimerCreate(),(动态创建函数)

定时器创建好休眠的,还不能运行,可想而知,它创建的时候只是根据传入的参数对定时器TCB的各成员进行初始赋值,并没有添加入守护任务的队列中。在其传入的参数中,xTimerPeriodInTicks可以直接是一个数值,表示多个个Tick,也可以填入pdMS_TO_TICKS(500),这个就表示定时500ms.因为Tick的中断刚好是1ms,这里传入500和pdMS_TO_TICKS(500)是一样的。而在xTimerCreate内层中还初始化了软件定时器列表List_t xActiveTimerList1和List_t xActiveTimerList2,仔细发现这两个列表定义在timer.c中是静态变量(说明守护任务中其实并没有自己创建列表,而它只是操作了这个预先定义好的全局列表,因此上面的说法比如守护任务的队列是错误的)同时创建消息队列,该队列用户不可见。

软件定时器启动函数,xTimerStart(),就是一个宏定义,就像消息的发送函数函数一样

#define xTimerStart( xTimer, xTicksToWait )
         xTimerGenericCommand( ( xTimer ),  //定时器句柄
                         tmrCOMMAND_START,  //命令
                    ( xTaskGetTickCount() ), //获取当前系统时间
                            NULL,            //pxHigherPriorityTaskWoken,中断中使用,老朋友了
                            ( xTicksToWait ) )//阻塞超时时间

实际调用  xTimerGenericCommand 函数

启动计时就是向一个结构体xMessage中保存定时器任务的信息,然后把xMessage打包向消息队列发送,在发送之前需要判断调度器是否执行了,因为守护任务是在开启调度器时候创建的,如果还没有开启调度说明守护任务还没有创建,那直接向消息队列发送即可,不阻塞,因为没有意义,如果开启了就需要使用阻塞超时。当然还根据tmrFisrt_From_ISR_COMMAND(大于这个数就从中断发出命令)这个参数判断了开启定时器是从哪里发出的(中断还是任务),可见xTimerStartFromISR()底层也是执行这个函数。当然还需要传入老朋友/pxHigherPriorityTaskWoken,在使用的时候,需要根据xTimerStartFromISR()的返回值判断一下是否需要任务切换,因为向消息队列发送命令,会唤醒守护任务,如果不执行任务切换,很有可能使设置失效。

定时器停止函数xTimerStop()和xTimerStopFromISR()就是使一个已经开启的定时器停止,有普通版本和中断版本,普通版本可以超时阻塞。

现在来看看守护任务preTimerTask()怎么创建的,其在开启调度的时候创建,其中调用了xTimerCreateTimerTask(),在这个函数中又调用了

xReturn = xTaskCreate(	prvTimerTask,
						"Tmr Svc",
						configTIMER_TASK_STACK_DEPTH,
						NULL,
						( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
						&xTimerTaskHandle );

可见,守护任务也是由xTaskCreate(),动态任务创建函数创建的,其任务执行的函数入口是prvTimerTask,句柄是xTimerTaskHandle,这个用户不可见的,而优先级

#define configTIMER_TASK_PRIORITY		        (configMAX_PRIORITIES-1)

说明守护任务的优先级是最高的任务之一。

在prvTimerTask函数中,执行了三个函数

TickType_t xNextExpireTime;
BaseType_t xListWasEmpty;
for( ;; )
	{
		
		xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
		
		prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );

		prvProcessReceivedCommands();
	}

第一个是获取最快到期的软件定时器时间,传入的参数是xListWasEmpty,就是判断定时器列表是不是空的,如是是空的,就会给它置pdTURE.第二个函数prvProcessTimerOrBlockTask,就是判断计时到期没有,到期了就去执行回调函数,没有到期就把守护任务阻塞到下一个最快到达的时间或者在收到命令时候唤醒。(这里挺复杂的)

删除定时器函数xTimerDelete()

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值