FreeRTOS学习笔记(五)——应用开发(三)

文章详细介绍了FreeRTOS操作系统中软件定时器的工作原理、API函数以及使用注意事项,包括定时器的创建、启动、停止和删除。同时,讨论了任务通知的概念,包括发送和接收通知的函数,以及它们在中断环境中的版本。这些机制在实时操作系统中用于实现轻量级的任务同步和通信。

0x01 软件定时器

定时器概念: 定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。

  • 硬件定时器:硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式,用户在中断中处理信息。
  • 软件定时器:软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。需要我们创建的软件定时器时指定时间到达后要调用的函数(也称为超时函数/回调函数)中处理信息。

软件定时器回调函数的上下文是任务,下文所说的定时器均为软件定时器。软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。定时精度与系统时钟的周期有关。

一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况。

RTOS中的软件定时器支持单次模式和周期模式:

  • 单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
  • 周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

FreeRTOS 通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。要使用软件定时器需要将FreeRTOSConfig.h中的宏定义configUSE_TIMERS设置为1。开启这个宏也需要开启configTIMER_TASK_PRIORITYconfigTIMER_QUEUE_LENGTHconfigTIMER_TASK_STACK_DEPTH这些宏,否则编译会报如下错误:

#if configUSE_TIMERS == 1

	#ifndef configTIMER_TASK_PRIORITY
		#error If configUSE_TIMERS is set to 1 then configTIMER_TASK_PRIORITY must also be defined.
	#endif /* configTIMER_TASK_PRIORITY */

	#ifndef configTIMER_QUEUE_LENGTH
		#error If configUSE_TIMERS is set to 1 then configTIMER_QUEUE_LENGTH must also be defined.
	#endif /* configTIMER_QUEUE_LENGTH */

	#ifndef configTIMER_TASK_STACK_DEPTH
		#error If configUSE_TIMERS is set to 1 then configTIMER_TASK_STACK_DEPTH must also be defined.
	#endif /* configTIMER_TASK_STACK_DEPTH */

#endif /* configUSE_TIMERS */
  • configTIMER_TASK_PRIORITY定义的是定时器任务的优先级,值可以是0~(configMAX_PRIORITIES-1),这个值定义的就是定时器任务prvTimerTask的优先级;

  • configTIMER_QUEUE_LENGTH定义的是定时器队列的长度,也就是能定义几个软件定时器;

  • configTIMER_TASK_STACK_DEPTH定义的是定时器任务的堆栈大小,图中定义的是256字,这个堆栈也是定时器回调函数会使用的堆栈,所以不能定义得太小。

这里使用预编译指令#if(依据),#error(提示信息)。这个可以学起来。如果报错没有加入这几个宏定义,可以自己加入:

	#define configTIMER_TASK_PRIORITY 		(2)
	#define configTIMER_QUEUE_LENGTH		10
	#define configTIMER_TASK_STACK_DEPTH 	256

加入后如果报错: L6218E: Undefined symbol vApplicationGetTimerTaskMemory (referred from timers.o).,可以检查自己的宏configSUPPORT_STATIC_ALLOCATION是否置位,需要更改为0:

应用场景

需要定时任务,但是硬件定时器受到硬件的限制,数量上不足以满足其要求,其缺陷是容易被其他中断打断,因为软件定时器执行的上下文环境为任务,所以软件定时器适用于对时间精度要求不高的任务,一些辅助型的任务

定时器精度

在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍配置为 configTICK_RATE_HZ,表示系统时钟频率,1s跳动了多少次,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000,则系统时钟节拍周期就为1ms。软件定时器的所定时数值必须为这个节拍周期的整倍数。由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。

运作机制

软件定时器是可选的系统资源,在创建定时器的时候就会分配一块内存空间。当用户创建并启动一个软件定时器时, FreeRTOS 会根据当前系统时间及用户设置的定时确定该定时器唤醒时间,并将该定时器控制块挂入软件定时器列表,FreeRTOS 中采用两个定时器列表维护软件定时器,pxCurrentTimerListpxOverflowTimerList是列表指针,在初始化的时候分别指向 xActiveTimerList1 xActiveTimerList2:

PRIVILEGED_DATA static List_t xActiveTimerList1 = {
   
   0};
PRIVILEGED_DATA static List_t xActiveTimerList2 = {
   
   0};
PRIVILEGED_DATA static List_t *pxCurrentTimerList = NULL;
PRIVILEGED_DATA static List_t *pxOverflowTimerList = NULL;
  • pxCurrentTimerList:系统新创建并激活的定时器都会以超时时间升序的方式插入到pxCurrentTimerList 列表中。系统在定时器任务中扫描 pxCurrentTimerList 中的第一个定时器,看是否已超时,若已经超时了则调用软件定时器回调函数。否则将定时器任务挂起,因为定时时间是升序插入软件定时器列表的,列表中第一个定时器的定时时间都还没到的话,那后面的定时器定时时间自然没到。

  • pxOverflowTimerList 列表是在软件定时器溢出的时候使用,作用与 pxCurrentTimerList一致。

软件定时器可以采用消息队列进行通讯,利用“定时器命令队列”向软件定时器任务发送一些命令,任务在接收到命令就会去处理命令对应的程序,比如启动定时器,停止定时器等。

假如定时器任务处于阻塞状态,我们又需要马上再添加一个软件定时器的话,就是采用这种消息队列命令的方式进行添加,才能唤醒处于等待状态的定时器任务,并且在任务中将新添加的软件定时器添加到软件定时器列表中,所以,在定时器启动函数中,FreeRTOS 是采用队列的方式发送一个消息给软件定时器任务任务被唤醒从而执行接收到的命令

添加软件定时器任务:

系统当前时间 xTimeNow 值为 0,注意:xTimeNow 其实是一个局部变量,是根据 xTaskGetTickCount()函数获取的,实际它的值就是全局变量 xTickCount 的值,下文都采用它表示当前系统时间。在当前系统中已经创建并启动了 1 个定时器 Timer1;系统继续运行,当系统的时间 xTimeNow 为 20 的时候,用户创建并且启动一个定时时间为 100 的定时器 Timer2,此 时 Timer2 的 溢出 时间 xTicksToWait 就 为定 时时间 +系统 当前时 间(100+20=120),然后将 Timer2 按 xTicksToWait 升序插入软件定时器列表中;假设当前系统时间 xTimeNow 为 40 的时候,用户创建并且启动了一个定时时间为 50 的定时器Timer3 , 那 么 此 时 Timer3 的 溢 出 时 间 xTicksToWait 就 为 40+50=90 , 同 样 安 装xTicksToWait 的数值升序插入软件定时器列表中:

在这里插入图片描述

在这里插入图片描述

那么系统如何处理软件定时器列表?系统在不断运行,而 xTimeNow(xTickCount)随着 SysTick 的触发一直在增长(每一次硬件定时器中断来临,xTimeNow 变量会加 1),在软件定时器任务运行的时候会获取下一个要唤醒的定时器,比较当前系统时间xTimeNow 是否大于或等于下一个定时器唤醒时间 xTicksToWait,若大于则表示已经超时,定时器任务将会调用对应定时器的回调函数,否则将软件定时器任务挂起,直至下一个要唤醒的软件定时器时间到来或者接收到命令消息。执行完对应的回调函数后,会将其从软件定时器列表中删除,如果这个定时器是周期性的,则系统会根据Timer下一次唤醒时间重新将定时器添加到软件定时器列表中,也是根据时间来进行升序排序。

使用软件定时器需要注意:

在这里插入图片描述

软件定时器控制模块

typedef struct tmrTimerControl
{
   
   
	const char				*pcTimerName;		//软件定时器名字,用于调试的,一般通过其句柄来使用
	ListItem_t				xTimerListItem;		//软件定时器列表项,用于插入定时器列表
	TickType_t				xTimerPeriodInTicks;//软件定时器的周期
	UBaseType_t				uxAutoReload;		//pdFALSE为单次,pdTRUE为周期
	void 					*pvTimerID;			//软件定时器ID,在回调中根据ID来执行任务
	TimerCallbackFunction_t	pxCallbackFunction;	//回调函数
	#if( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t			uxTimerNumber;		/*<< An ID assigned by trace tools such as FreeRTOS+Trace */
	#endif

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t 			ucStaticallyAllocated; //标记定时器使用的内存,删除时判断是否需要释放内存。
	#endif
} xTIMER;

函数接口

软件定时器的很多 API 函数通过一个名字叫“定时器命令队列”的队列来给定时器守护任务发送命令。该定时器命令队列由 RTOS 内核提供,且应用程序不能够直接访问,其消息队列的长度由宏 configTIMER_QUEUE_LENGTH 定义。

xTimerCreate()

创建软件定时器函数,软件定时器在创建成功后是处于休眠状态的,可以使用 xTimerStart()、xTimerReset()、 xTimerStartFromISR() 、 xTimerResetFromISR() 、 xTimerChangePeriod() 和 xTimerChangePeriodFromISR()这些函数将其状态转换为活跃态。

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

	TimerHandle_t xTimerCreate(	const char * const pcTimerName,			//软件定时器名字
								const TickType_t xTimerPeriodInTicks,	//软件定时器周期
								const UBaseType_t uxAutoReload,			//设置周期or单次模式
								void * const pvTimerID,					//软件定时器ID
								TimerCallbackFunction_t pxCallbackFunction )	//回调函数
	{
   
   
        Timer_t *pxNewTimer;
		// 申请内存
		pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) );

		if( pxNewTimer != NULL )
		{
   
   
            //内存申请成功,进行初始化软件定时器
			prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer );

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
   
   
				/* Timers can be created statically or dynamically, so note this
				timer was created dynamically in case the timer is later
				deleted. */
				pxNewTimer->ucStaticallyAllocated = pdFALSE;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */
		}

		return pxNewTimer;
	}

#endif /* configSUPPORT_STATIC_ALLOCATION */
  • xTimerPeriodInTicks:软件定时器的周期,单位为系统节拍周期(即 tick)。使用pdMS_TO_TICKS()可以把时间单位从 ms 转换为系统节拍周期。如果软件定时器的周期为100个 tick,那么只需要简单的设置 xTimerPeriod 的值为 100 即可。如果软件定时器的周期为 500ms,那么 xTimerPeriod 应设置为 pdMS_TO_TICKS(500)。宏 pdMS_TO_TICKS()只有当 configTICK_RATE_HZ 配置成小于或者等于 1000HZ 时才可以使用。

prvInitialiseNewTimer()

初始化一个软件定时器。

static void prvInitialiseNewTimer(	const char * const pcTimerName,			/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
									const TickType_t xTimerPeriodInTicks,
									const UBaseType_t uxAutoReload,
									void * const pvTimerID,
									TimerCallbackFunction_t pxCallbackFunction,
									Timer_t *pxNewTimer )
{
   
   
	//断言,判断定时器的周期是否大于0
	configASSERT( ( xTimerPeriodInTicks > 0 ) );

	if( pxNewTimer != NULL )
	{
   
   
		//初始化软件定时器列表与创建软件定时器消息队列
		prvCheckForValidListAndQueue();

		//初始化软件定时信息,这些信息保存在软件定时器控制块中
		pxNewTimer->pcTimerName = pcTimerName;
		pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
		pxNewTimer->uxAutoReload = uxAutoReload;
		pxNewTimer->pvTimerID = pvTimerID;
		pxNewTimer->pxCallbackFunction = pxCallbackFunction;
		vListInitialiseItem( &( pxNewTimer->xTimerListItem ) );
		traceTIMER_CREATE( pxNewTimer );
	}
}

在 prvCheckForValidListAndQueue()函数中系统将初始化软件定时器列表与创建软件定时器消息队列,也叫“定时器命令队列”,因为在使用软件定时器的时候,用户是无法直接控制软件定时器的,必须通过“定时器命令队列”向软件定时器发送一个命令,软件定时器任务被唤醒就去执行对应的命令操作。其中调用了xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );

xTimerStart()

软件定时器启动函数,上面只是创建,处于休眠状态,需要使用这个函数来使其开始工作。系统开始运行时,会自动创建一个任务prvTimerTask,如果没有运行中的定时器,则会进入等待阻塞的状态,启动函数就是通过“定时器命令队列”向定时器任务发送一个启动命令,定时器任务获得命令就解除阻塞,然后执行启动软件定时器命令。

#define xTimerStart( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ),	//定时器句柄
											tmrCOMMAND_START, 					///启动命令
											( xTaskGetTickCount() ), 			//获取当前系统时间
											NULL, 								//中断发送命令才有用,其他时NULL
											( xTicksToWait ) )					//超时阻塞时间

xTicksToWait:用户指定超时阻塞时间,单位为系统节拍周期(即 tick)。调用xTimerStart()的任务将被锁定在阻塞态,在软件定时器把启动的命令成功发送到定时器命令队列之前。如果在 FreeRTOS 调度器开启之前调用 xTimerStart(),形参将不起作用。命令有如下:

#define tmrCOMMAND_EXECUTE_CALLBACK_FROM_ISR 	( ( BaseType_t ) -2 )
#define tmrCOMMAND_EXECUTE_CALLBACK				( ( BaseType_t ) -1 )
#define tmrCOMMAND_START_DONT_TRACE				( ( BaseType_t ) 0 )
#define tmrCOMMAND_START					    ( ( BaseType_t ) 1 )
#define tmrCOMMAND_RESET						( ( BaseType_t ) 2 )
#define tmrCOMMAND_STOP							( ( BaseType_t ) 3 )
#define tmrCOMMAND_CHANGE_PERIOD				( ( BaseType_t ) 4 )
#define tmrCOMMAND_DELETE						(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值