打印
[应用相关]

FreeRTOS及其应用,万字长文,基础入门

[复制链接]
手机看帖
扫描二维码
随时随地手机跟帖
41
六、 软件定时器6.1 软件定时器的概念

定时器有硬件定时器和软件定时器之分,硬件定时器是芯片本身提供的定时功能精度高,并且是中断触发方式。软件定时器是由操作系统封装的接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制,其实现的功能与硬件定时器也是类似的。

在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍配置为configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中,一般是100或者1000。根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。


使用特权

评论回复
42
我只会加减乘除|  楼主 | 2022-5-1 17:53 | 只看该作者
6.2 软件定时器创建

软件定时器需先创建才允许使用,动态创建方式是xTimerCreate(),返回一个句柄。软件定时器在创建成功后是处于休眠状态的,没有开始计时运行。FreeRTOS的软件定时器支持单次模式和周期模式。

单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数,之后不再执行。周期模式:定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器停止或删除。

实际项目中使用这种模式对单片机喂狗就比较省事。

1. TimerHandle_t xTimerCreate( const char * const pcTimerName, //定时器名称
2.                             const TickType_t xTimerPeriodInTicks,  //定时时间
3.                             const UBaseType_t uxAutoReload,  //是否自动重载
4.                             void * const pvTimerID,  //回调函数的参数
5.                             TimerCallbackFunction_t pxCallbackFunction )  //回调函数


使用特权

评论回复
43
我只会加减乘除|  楼主 | 2022-5-1 17:53 | 只看该作者
6.3 软件定时器开启

新创建的定时器没有开始计时启动,可以使用

xTimerStart()、
xTimerReset()、
xTimerStartFromISR() 、xTimerResetFromISR()
xTimerChangePeriod()、xTimerChangePeriodFromISR()

这些函数将其状态转换为活跃态,开始运行。区别:如果定时器设定60秒间隔,已经运行了30秒,reset是将定时器重置为原来设定的时间间隔,也就是重新开始延时60秒。ChangePeriod重新设置计时周期。



使用特权

评论回复
44
我只会加减乘除|  楼主 | 2022-5-1 17:54 | 只看该作者
6.4 软件定时器停止

xTimerStop() 用于停止一个已经启动的软件定时器,xTimerStopFromISR()是中断版本。


使用特权

评论回复
45
我只会加减乘除|  楼主 | 2022-5-1 17:55 | 只看该作者
6.5 软件定时器删除

xTimerDelete()用于删除一个已经被创建成功的软件定时器,释放资源,删除之后不能再使用。实际项目中,任务和队列都是按需创建,一直使用,但是定时器不使用的就应该删除,并且删除后一定要将句柄置为NULL。


使用特权

评论回复
46
我只会加减乘除|  楼主 | 2022-5-1 17:56 | 只看该作者
6.6 软件定时器源码分析

软件定时器任务是在系统开始调度的时候就被创建:vTaskStartScheduler()—xTimerCreateTimerTask。

1. BaseType_t xTimerCreateTimerTask( void )  
2. {  
3.     BaseType_t xReturn = pdFAIL;  
4.   
5.     prvCheckForValidListAndQueue();  //创建定时器任务的队列
6.   
7.     if( xTimerQueue != NULL )  
8.     {  
9.         #if ( configSUPPORT_STATIC_ALLOCATION == 1 )  
10.             {  
11.                       /**/
12.             }  
13.         #else /* if ( configSUPPORT_STATIC_ALLOCATION == 1 ) */  
14.             {  
15.                  //创建定时器任务
16.                 xReturn = xTaskCreate( prvTimerTask,  
17.                                        configTIMER_SERVICE_TASK_NAME,  
18.                                        configTIMER_TASK_STACK_DEPTH,  
19.                                        NULL,  
20.                                        ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,  
21.                                        &xTimerTaskHandle );  
22.             }  
23.         #endif /* configSUPPORT_STATIC_ALLOCATION */  
24.     }  
25.      /**/
26.     return xReturn;  
27. }  

任务创建后,等候命令执行

1.static portTASK_FUNCTION( prvTimerTask, pvParameters )  
2. {  
3.      /**/
4.   
5.     for( ; ; )  
6.     {  
7.         //最近即将超时的定时器还有多长时间溢出
8.         xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );  
9.   
10.         //阻塞等待,定时器溢出或受到命令,进入下一步(原因不明)
11.         prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );  
12.   
13.         //接收命令并处理,见下面
14.         prvProcessReceivedCommands();  
15.     }  
16. }  

所有定时器接口,都是使用xTimerGenericCommand向队列发送控制命令,命令如下:

1. #define tmrCOMMAND_START_DONT_TRACE             ( ( BaseType_t ) 0 )  
2. #define tmrCOMMAND_START                        ( ( BaseType_t ) 1 )  
3. #define tmrCOMMAND_RESET                        ( ( BaseType_t ) 2 )  
4. #define tmrCOMMAND_STOP                         ( ( BaseType_t ) 3 )  
5. #define tmrCOMMAND_CHANGE_PERIOD                ( ( BaseType_t ) 4 )  
6. #define tmrCOMMAND_DELETE                       ( ( BaseType_t ) 5 )  


使用特权

评论回复
47
我只会加减乘除|  楼主 | 2022-5-1 17:57 | 只看该作者
6.7 软件定时器使用注意点

1、查看其他开源代码,对定时器的使用并不多,但实际项目中过多依赖定时器,导致应用逻辑混乱。

2、freeRTOS 的定时器不是无限制的,其根源是接收定时器控制命令消息的队列,默认只有10个单元。

1. xTimerQueue = xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );  

定时器过多,可能出现发起定时器命令失败,原因是队列已满。可以将默认的10扩大为15,后续尽量使用信号量来优化代码。

4、软件定时器的回调函数要快进快出,而且不能有任何阻塞任务运行的情况,不能有vTaskDelay() 以及其它能阻塞任务运行的函数。特别说明,其回调函数是在定时器任务执行的,并不是开启定时器的任务。


使用特权

评论回复
48
我只会加减乘除|  楼主 | 2022-5-1 17:58 | 只看该作者
七、 信号量7.1 信号量的概念

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。可以简单认为是为支持多任务同时操作的全局变量(个人理解)。


使用特权

评论回复
49
我只会加减乘除|  楼主 | 2022-5-1 18:00 | 只看该作者
7.1.1 二值信号量

比如有一个停车位,多个人都想占用停车,这种情况就可以使用一个变量标记车位状态,它只有两种情况,被占用或者没被占用。在多任务中使用二值信号量表示,用于任务与任务、任务与中断的同步。在freeRTOS中,二值信号量看作只有一个消息的队列,因此这个队列只能为空或满。


使用特权

评论回复
50
我只会加减乘除|  楼主 | 2022-5-1 18:01 | 只看该作者
7.1.2 计数信号量

如果有100个停车位,可以停100辆车,每进去一辆车,车位的数量就要减一,当停车场停满了 100 辆车的时候,再来的车就不能停进去了。这种场景就需要计数信号量来表示多个状态。二进制信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1 的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。


使用特权

评论回复
51
我只会加减乘除|  楼主 | 2022-5-1 18:01 | 只看该作者
7.1.3 互斥信号量

还是前面车位问题,只剩一个空车位,虽然员工车离得近,但是领导车来了,要优先安排给领导使用,这就是由地位决定。互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源。

优先级翻转问题:假设有任务H,任务M和任务L三个任务,优先级逐次降低。低优先级的任务L抢先占有资源,导致高优先级的任务H阻塞等待,此时再有中等优先级的任务M,它不需要该资源,且优先级高于任务L,它优先执行;之后再执行任务L,最后才执行任务H。看起来就是高优先级的任务反而不如低优先级的任务,即优先级翻转。

改进型的互斥信号量具有优先级继承机制,操作系统对获取到临界资源的任务提高其优先级为所有等待该资源的任务中的最高优先级。一旦任务释放了该资源,就恢复到原来的优先级。

任务L先占用资源,任务H申请不到资源会进入阻塞态,同时系统就会把当前正在使用资源的任务L的优先级临时提高到与任务H优先级相同,即使任务M被唤醒了,因为它的优先级比任务H低,所以无法打断任务L,因为任务L的优先级被临时提升到 H;任务L使用完该资源,任务H优先级最高,将接着抢占 CPU 的使用权,这样保证任务H在任务M前优先执行。

上面的这些就是为了说明,二值信号量因为优先级翻转,不能用于对临界区的访问。


使用特权

评论回复
52
我只会加减乘除|  楼主 | 2022-5-1 18:02 | 只看该作者
7.1.4 递归互斥信号量

信号量是每获取一次,可用信号量个数就会减少一个,释放一次就增加一个。但是递归信号量则不同。对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只有持有递归信号量的任务才能获取与释放。类似栈的效果。


使用特权

评论回复
53
我只会加减乘除|  楼主 | 2022-5-1 18:03 | 只看该作者
7.2 二值信号量的应用

二值信号量是任务与任务间、任务与中断间同步的重要手段。例如,任务A使用串口发出AT数据后,获取二值信号量无效进入阻塞;

某个时间后,任务B中串口收到正确的回复,释放二值信号量。

任务A就立即从阻塞态中解除,进入就绪态,等待运行。这种机制用在模块AT交互很合适。


使用特权

评论回复
54
我只会加减乘除|  楼主 | 2022-5-1 18:04 | 只看该作者
7.3 计数信号量的应用

计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源。例如有公共资源车位3个,但是有多个任务要使用,这种场景就必须使用计数信号量。三个资源最多支持 3 个任务访问,那么第 4 个任务访问的时候,会因为获取不到信号量而进入阻塞。也就是第4个人无法占用车位,必须前面有车离开。等到其中一个有任务(比如任务 1) 释放掉该资源的时候,第 4 个任务才能获取到信号量从而进行资源的访问。其运作的机制类似下图。


使用特权

评论回复
55
我只会加减乘除|  楼主 | 2022-5-1 18:04 | 只看该作者
7.4 互斥信号量的应用

多任务环境下往往存在多个任务竞争同一临界资源的应用场景,互斥量可被用于对临界资源的保护从而实现独占式访问。互斥量可以降低信号量存在的优先级翻转问题带来的影响。

比如有两个任务需要对串口进行发送数据,其硬件资源只有一个,那么两个任务肯定不能同时发送,不然导致数据错误,那么就可以用互斥量对串口资源进行保护,当一个任务正在使用串口的时候,另一个任务则无法使用串口,等到前一个任务使用串口完成后, 另外一个任务才能获得串口的使用权。

另外需要注意的是互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上下文环境毫无意义。

互斥信号量可以在多个任务之间进行资源保护,而临界区只能是在同一个任务进行,但是其速度快。(个人理解)


使用特权

评论回复
56
我只会加减乘除|  楼主 | 2022-5-1 18:05 | 只看该作者
7.5 信号量接口

所有信号量semaphore使用套路相近,都是创建creat、删除delete、释放give和获取take四种;释放和获取支持任务级和中断级FromISR,其中互斥量和递归互斥量不支持中断。使用对应的信号量,需要在FreeRTOSConfig.h开启对应的功能。


使用特权

评论回复
57
我只会加减乘除|  楼主 | 2022-5-1 18:06 | 只看该作者
7.5.1 信号量创建

xSemaphoreCreateBinary()用于创建一个二值信号量,并返回一个句柄,默认二值信号量为空,在使用函数 xSemaphoreTake()获取之前必须 先 调 用 函 数 xSemaphoreGive() 释放后才可以获取。

xSemaphoreCreateCounting()创建计数信号量。

<span style="outline: 0px; max-width: 100%; font-family: " operator="" mono",="" consolas,="" monaco,="" menlo,="" monospace;="" font-size:="" 12px;="" white-space:="" pre;="" color:="" rgb(209,="" 154,="" 102);="" line-height:="" 26px;="" box-sizing:="" border-box="" !important;"="">1.</span><span style="color: rgb(171, 178, 191); font-family: " operator="" mono",="" consolas,="" monaco,="" menlo,="" monospace;="" font-size:="" 12px;="" white-space:="" pre;="" background-color:="" rgb(40,="" 44,="" 52);"=""> </span><span style="outline: 0px; max-width: 100%; font-family: " operator="" mono",="" consolas,="" monaco,="" menlo,="" monospace;="" font-size:="" 12px;="" white-space:="" pre;="" color:="" rgb(97,="" 174,="" 238);="" line-height:="" 26px;="" box-sizing:="" border-box="" !important;"="">#<span style="outline: 0px; max-width: 100%; line-height: 26px; box-sizing: border-box !important;">define</span> xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )   </span>

uxMaxCount 计数信号量的最大值,当达到这个值的时候,信号量不能再被释放。uxInitialCount 创建计数信号量的初始值。

xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄,只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。

xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量,递归信号量可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以减少优先级反转的反生。


使用特权

评论回复
58
我只会加减乘除|  楼主 | 2022-5-1 18:07 | 只看该作者
7.5.2 信号量删除

vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递 归互斥量。如果有任务阻塞在该信号量上,暂时不要删除该信号量。传入的参数为创建时返回的句柄。


使用特权

评论回复
59
我只会加减乘除|  楼主 | 2022-5-1 19:14 | 只看该作者
7.5.3 信号量释放

当信号量有效的时候,任务才能获取信号量,信号量变得有效就是释放信号量。每调用一次该函数就释放一个信号量,注意释放的次数,尤其是计数信号量。

xSemaphoreGive()是任务中释放信号量的宏,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量,递归互斥信号量用xSemaphoreGiveRecursive()释放。xSemaphoreGiveFromISR()带中断保护释放一个信号量,被释放的信号量可以是二值信号量和计数信号量,不能释放互斥量和递归互斥量,因为互斥量和递归互斥量不可在中断中使用,互斥量的优先级继承机制只能在任务中起作用。


使用特权

评论回复
60
我只会加减乘除|  楼主 | 2022-5-1 19:14 | 只看该作者
7.5.4 信号量获取

与释放信号量对应的是获取信号量,当信号量有效的时候,任务才能获取信号量,当任务获取了某个信号量的时候,该信号量的可用个数就减一,当它减到0 的时候,任务就无法再获取了,并且获取的任务会进入阻塞态(如果设定了阻塞超时时间)。

xSemaphoreTake()函数用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用它。

1. #define xSemaphoreTake( xSemaphore, xBlockTime )  

xSemaphore 信号量句柄

xBlockTime 等待信号量可用的最大超时时间,单位为 tick

获取 成 功 则 返 回 pdTRUE ,在 指定的 超时 时间 中 没 有 获 取 成 功 则 返 回errQUEUE_EMPTY。

使用xSemaphoreTakeRecursive()获取递归互斥量。xSemaphoreTakeFromISR()是获取信号量的中断版本,是一个不带阻塞机制获取信号量的函数,获取对象必须由是已经创建的信号量,信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake()函数不同,它不能用于获取互斥量,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起作用,而在中断中毫无意义。


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则