FreeRTOS学习总结 (二)

FreeRTOS学习总结

四、软件定时器

软件计时器由FreeRTOS内核实现,并在其控制之下。它们不需要硬件支持,也与硬件计时器或硬件计数器无关。软件计时器功能是可选的。要使用软件计时器功能:
1、构建FreeRTOS源文件 FreeRTOS/Source/timers.c作为项目的一部分。
2、在FreeRTOSConfig.h中将configUSE_TIMERS设置为1。
软件定时器的回调函数模型如下:

//Listing 72. The software timer callback function prototype
void ATimerCallback( TimerHandle_t xTimer );

软件计时器回调函数从开始执行到结束尾执行,并以正常方式退出。它们应保持短小,并且不能进入阻塞状态。

软件定时器的回调函数,是在一个FreeRTOS调度器启动时,自动创建的任务上下文中执行。因此该回调函数不能调用FreeRTOS的API函数,否则将导致该任务进入阻塞状态。
只有当函数的xTicksToWait参数(指定函数的块时间)设置为0的时候,可以调用函数如xQueueReceive()。调用像vTaskDelay()这样的函数是不允许的,因为调用vTaskDelay()总是会将调用任务置于阻塞状态。

有两种计时器:
1、一次性计时器
2、自动重载计时器

软件计时器的两种状态:
1、休眠
2、运行
在这里插入图片描述

RTOS守护进程(计时器服务)任务

软件定时器的回调函数是在FreeRTOS的守护任务的上下文中执行的。它的优先级和栈大小分别由configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH编译时配置常量设置。这两个常量都是在FreeRTOSConfig.h中定义的。
回调函数绝对不能调用任何会导致守护任务进入阻塞状态的FreeRTOS的API函数。

软件计时器API函数将命令通过一个名为“计时器命令队列”的队列,从函数的调用任务发送到守护任务。
计时器命令队列是一个标准的FreeRTOS队列,它在调度程序启动时自动创建。计时器命令队列的长度是由FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH编译时配置常量设置的。

在这里插入图片描述
注意:软件计时器将的时间是从“启动计时器”命令发送到计时器命令队列开始计算的——它不是从守护进程任务从计时器命令队列收到“启动计时器”命令开始计算的。例如,如果发送一个“启动计时器”命令来启动一个周期为10个tick的计时器,则时间戳用于确保这10个tick是应用程序从发送命令开始,而不是在守护进程任务处理命令后开始的的第10个tick。

软件计时器相关API

创建计时器:

TimerHandle_t xTimerCreate( const char * const pcTimerName,
 							TickType_t xTimerPeriodInTicks,
 							UBaseType_t uxAutoReload,
 							void * pvTimerID,
 							TimerCallbackFunction_t pxCallbackFunction );

启动计时器:

BaseType_t xTimerStart ( TimerHandle_t xTimer, TickType_t xTicksToWait );

设置和获取计时器ID:

void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
void *pvTimerGetTimerID( TimerHandle_t xTimer );

更改计时器周期:
对于运行状态中的计时器,重新计算的过期时间相对于调用xTimerChangePeriod()的时间,而不是相对于计时器最初启动的时间。
对于休眠状态中的计时器,则只改变计时器周期,不会启动计时器。

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t NewTimerPeriodInTicks,TickType_t xTicksToWait );

重置计时器:
该函数也可以用来启动一个处于休眠状态的计时器。

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

五、中断管理

从中断函数中调用FreeRTOS的API,需要调用带FromISR尾缀的版本。

xHigherPriorityTaskWoken参数

如果在中断中调用了FromISR()的API函数,并且该API函数可能造成任务的切换,那么这种切换并不会在中断服务程序中自动的发生。而是需要通过FromISR()函数的xHigherPriorityTaskWoken参数,通知调度器执行任务的上下文切换。
使用xHigherPriorityTaskWoken参数是可选的。如果不需要,则将参数设置为NULL。

portYIELD_FROM_ISR()和portEND_SWITCHING_ISR()宏

这两个宏是taskYEILD()的ISR安全版本,他们的使用方式相同,并且做同样的事情。一些FreeRTOS分支只提供这两个宏中的一个。较新的FreeRTOS分支提供了这两个宏。
xHigherPriorityTaskWoken参数传递给这两个宏,如果为pdTRUE,则会请求一个上下文切换。
注意:中断将始终返回到处于运行状态下的任务,即使在中断执行时,处于运行状态下的任务已经变了。

延迟中断处理

中断的ISR程序应尽量短小,因此和中断相关的其他处理,如数据处理,都可以延迟到任务中执行,从而保证ISR尽快退出。
如果延迟处理任务的优先级高于任何其他任务,那么中断独处后将立即被执行。就像在ISR本身中执行一样。
在这里插入图片描述
中断延时被立即处理的条件:
1、二进制信号量通知任务进入就绪状态
2、延时任务的优先级是当前最高。
3、ISR程序的末尾调用portYIELD_FROM_ISR(xHigherPriorityTaskWoken)中参数为pdTRUE。

二进制信号量相关API

创建:

SemaphoreHandle_t xSemaphoreCreateBinary( void );

take:

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

give:

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,  BaseType_t *pxHigherPriorityTaskWoken );
计数信号量

使用计数信号量需要在FreeRTOSConfig.h中设置configUSE_COUNTING_SEMAPHORES必须在为1。
计数信号量通常用于两件事:
1、计数事件,初始值为0,每个事件give一个信号量,计数增加。
2、资源管理,初始值为资源总数,每个任务take一个资源,计数减少。

创建计数信号量函数,需要设置初始值和最大值:

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount );
中断延时到守护任务

可以使用xTimerPendFunctionCallFromISR()API函数来将中断处理延迟到RTOS守护进程任务,不需要为每个中断创建一个单独的任务。将中断处理延迟到守护进程任务称为“集中式延迟中断处理”。
被延时的任务模型:

void vPendableFunction( void *pvParameter1, uint32_t ulParameter2 );

延时函数:

BaseType_t xTimerPendFunctionCallFromISR( PendedFunction_t vPendableFunction, void *pvParameter1, uint32_t ulParameter2, BaseType_t *pxHigherPriorityTaskWoken );

BaseType_t xTimerPendFunctionCall( PendedFunction_t vPendableFunction, void *pvParameter1, uint32_t ulParameter2 );
在中断服务程序中使用队列

二进制信号量和计数信号量都是用于通知事件的产生。而队列则用于通知事件的产生并传输数据。

BaseType_t xQueueSendToBackFromISR( QueueHandle_t xQueue, void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
BaseType_t xQueueSendToFrontFromISR( QueueHandle_t xQueue, void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );

使用队列从中断传递数据到任务,简单方便,但如果数据到达的频率较高,则不适合,如UART接口不断有字符传入时。此时可以:
1、使用DMA传递数据,使用任务通知来把任务解除阻塞。
2、把所有接收到的数据复制到一个线程安全的RAM缓冲区,再使用任务通知。
3、在ISR中直接处理数据,把处理后的结果(而不是原始数据)使用队列发送给任务。

中断嵌套

支持中断嵌套的分支需要在FreeRTOSConfig.h中定义表39中详细说明的一个或两个常量。configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY都定义了相同的属性。旧的FreeRTOS端口使用configMAX_SYSCALL_INTERRUPT_PRIORITY,而更新的FreeRTOS端口使用configMAX_API_CALL_INTERRUPT_PRIORITY。
在这里插入图片描述
注意数字优先级和逻辑优先级的区别,通过将configMAX_SYSCALL_INTERRUPT_PRIORITY设置为比configKERNEL_INTERRUPT_PRIORITY更高的逻辑中断优先级来创建一个完整的中断嵌套模型。例如:

  • 该处理器有七个独特的中断优先级。
  • 分配的数字优先级为7的中断比分配的数字优先级为1的中断具有更高的逻辑优先级。
  • configKERNEL_INTERRUPT_PRIORITY被设置为1。
  • configMAX_SYSCALL_INTERRUPT_PRIORITY被设置为3。

在这里插入图片描述

  • 当内核或应用程序处于关键部分内时,使用优先级1到3的中断会被阻止执行。在这些优先级上运行的ISR可以使用中断安全的FreeRTOS API函数。
  • 使用优先级4或以上的中断不受关键部分的影响,因此调度程序所做的任何事情都不会阻止这些中断立即执行——在硬件本身的限制范围内。在这些优先级上执行的ISR不能使用任何FreeRTOS API函数。
  • 通常,需要非常严格的定时精度的功能(例如,电机控制)将使用高于configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级,以确保调度器不会在中断响应时间中引入抖动。

六、资源管理

基本的关键部分,是被宏taskENTER_CRITICAL()和taskEXIT_CRITICAL()所包围的代码区域。这两个宏不接受参数,也不返回值。例如:

void vPrintString( const char *pcString )
{
 	/* Write the string to stdout, using a critical section as a crude method of  mutual exclusion. */
 	taskENTER_CRITICAL();
 	{
 		printf( "%s", pcString );
 		fflush( stdout );
 	}
 	taskEXIT_CRITICAL();
 }
  • 它们通过禁用中断来工作,要么完全禁用,要么禁用中断优先级
    configmax_syscall_interrupt_priority -取决于FreeRTOS分支。
  • 基本的关键部分必须保持非常短,否则它们将对中断响应时间并产生不利影响。
  • 且taskENTER_CRITICAL()和taskEXIT_CRITICAL()必须成对出现。
  • 关键部分嵌套是安全的,因为内核会计算嵌套深度。只有当嵌套深度返回到零时,关键部分才会退出。
  • 调用taskENTER_CRITICAL()和taskEXIT_CRITICAL()是一个任务改变运行FreeRTOS的处理器的中断启用状态的唯一合法方式。通过任何其他方式改变中断启用状态都将使宏的嵌套计数无效。
  • taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()是中断安全版本。中断安全版本只提供给允许中断嵌套的FreeRTOS分支,在不允许中断嵌套的分支中将会废弃。
暂停(或锁定)调度器
  • 基本关键部分保护代码区域不受其他任务和中断的访问。
  • 暂停调度程序而实现的关键部分只保护一个代码区域不被其他任务访问,因为中断仍然处于启用状态。
  • 如果通过简单地禁用中断来实现的关键部分太长,而可以通过挂起调度器来实现。但是,在暂停调度程序时,中断活动可能使调度程序恢复(或“取消暂停”)成为一个相对较长的操作,因此必须考虑在每种情况下使用哪一种是最佳方法。
//Listing 119. The implementation of vPrintString()
void vPrintString( const char *pcString )
{
 	/* Write the string to stdout, suspending the scheduler as a method of mutual
 exclusion. */
 	vTaskSuspendScheduler();
 	{
 		printf( "%s", pcString );
 		fflush( stdout );
 	}
 	xTaskResumeScheduler();
}
互斥锁

互斥锁是一种特殊类型的二进制信号量,必须在FreeRTOSConfig.h中设置configUSE_MUTEXES为1,才能可用。

  • 用于互斥的信号量,要访问资源前,必须先申请"take"锁,使用完了要释放"give"锁。
  • 用于同步的信号量,发送事件时直接"give",不需要先"take";接收事件"take"后不需要"give"释放。

互斥锁相关函数:
创建:

SemaphoreHandle_t xSemaphoreCreateMutex( void );

take和give使用与二进制信号量相同的API

BaseType_t xSemaphoreTake( SemaphoreHandle_t xMutex, TickType_t xTicksToWait );
BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex );

使用互斥锁硬注意一下几个问题:
1、优先级翻转
2、死锁:多个任务间死锁,自己和自己死锁
3、多个具有相同优先级的任务间抢锁,可能导致锁一直被一个任务抢到。

守门员任务

将资源仅交给守门员任务,它是资源的唯一拥有者和访问者。其他任务或中断都可以将信息和数据通过队列等手段传递给守门员任务,然后由守门员任务统一处理给对应资源。

七、事件组

事件组用于处理多任务和多个事件之间的关系。它可以同步多个任务、向多个任务广播事件、允许任务在Blocked状态下等待一组事件中的任何一个发生,以及允许任务在Blocked状态下等待多个操作完成。
相关API函数:
创建:

EventGroupHandle_t xEventGroupCreate( void );

SET和WAIT

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );

EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
 								 const EventBits_t uxBitsToWaitFor,
 								 const BaseType_t xClearOnExit,
 								 const BaseType_t xWaitForAllBits,
 								 TickType_t xTicksToWait );
多个任务间同步

假设:
1、任务A,B,C使用一个事件组进行同步。
2、任务A和任务B已经到达同步点,所以它们的事件位在事件组中被设置,并且它们处于阻塞状态,等待任务C的事件位也被设置。
3、任务C到达同步点,并使用xEventGroupSetBits()来设置其在事件组中的位。一旦设置了任务C的位,任务A和任务B就会离开阻塞状态,并清除所有三个事件位。
4、然后任务C调用xEventGroupWaitBits()等待所有三个事件位被设置,但此时,所有三个事件位已经被清除,任务A和任务B已经离开各自的同步点,因此同步失败。
事件同步API函数

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
							 const EventBits_t uxBitsToSet,
							 const EventBits_t uxBitsToWaitFor,
							 TickType_t xTicksToWait );

八、任务通知

在FreeRTOSConfig.h中将configUSE_TASK_NOTIFICATIONS设置为1启用任务通知。
当启用任务通知时,每个任务都有一个“通知状态”,可以是“挂起”或“非挂起”,还有一个“通知值”,这是一个32位的无符号整数。当任务收到通知时,其通知状态被设置为挂起。当任务读取其通知值时,其通知状态被设置为不挂起。任务可以在一个超时时间段内阻塞状态等待,使其通知状态挂起。
1、任务通知比通信对象更快,使用更少的RAM。
2、任务通知可用于将事件和数据从ISR发送到任务,但它们不能用于将事件或数据从任务发送到ISR。
3、任务通知被直接发送到接收任务,因此只能由被通知的任务进行处理。
4、一个任务的通知值一次只能保存一个值。
5、如果接收任务已经有一个通知没有处理而处于挂起状态,则发送任务不会阻塞而等待它取消挂起。

简化的任务通知相关API函数
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken );

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

give会将接收任务的增加通知值(+1)。
take的参数xClearCountOnExit,如果被设置成pdTRUE,则ulTaskNotifyTake返回前,会把接受任务的通知值清0;类似于二进制信号量
如果设置成pdFALSE,则减少通知值(-1),不会清0。类似于计数信号量

更复杂强大的任务通知API函数

发送通知函数:

BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
 						uint32_t ulValue, 
 						eNotifyAction eAction );

BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, 
 							   uint32_t ulValue, 
 							   eNotifyAction eAction,
 							   BaseType_t *pxHigherPriorityTaskWoken );

1、增加(+1)接收任务的通知值,在这种情况下xTaskNotify()等效于xTaskNotifyGive()。
2、在接收任务的通知值中设置一个或多个位。类似于事件组。
3、将一个全新的数字写入接收任务的通知值,但前提是接收任务自上次更新以来已读取其通知值。类似于长度为1的队列。
4、将一个全新的数字写入接收任务的通知值,即使接收任务自上次更新后还没有读取其通知值。类似于邮箱的xQueueOverwrite() API函数。
5、参数ulValue的作用取决于参数eActon的设置。具体如下表:

eAction值效果
eNoAction提供类似于二进制信号量的功能,ulValue不使用
eSetBits提供类似于事件组的功能,ulValue为事件组的各个位
eIncrement提供类似于计数信号量的功能,ulValue不使用
eSetValueWithoutOverwrite提供类似于长度为1的队列功能,ulValue为传递的值
eSetValueWithOverwrite提供类似于邮箱的功能,ulValue为传递的值

接收任务通知函数:

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
 							uint32_t ulBitsToClearOnExit,
							uint32_t *pulNotificationValue,
 							TickType_t xTicksToWait );
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值