打印

FreeRTOS使用总结

[复制链接]
1385|19
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
ADZ2016|  楼主 | 2017-11-24 13:39 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
在实际开发中,如果程序等待一个事件发生,传统的无RTOS情况下,要么在原地一直等待而不能执行其它任务,要么使用状态机机制处理。而RTOS提供事件驱动型设计方式,只是在处理实际任务时才会运行,这能够更合理的利用CPU,也可以很方便的将当前任务阻塞在该事件下,然后自动去执行别的任务,这显然更方便,并且可以高效的利用CPU。处理这类事件,是我使用RTOS的最大动力。

相关帖子

沙发
ADZ2016|  楼主 | 2017-11-24 13:41 | 只看该作者
(1)大多数RTOS代码都具有一定规模,任何代码都可能带来BUG,何况是代码具有一定规模的RTOS,因此引入RTOS的同时也可能会引入该RTOS的BUG,这些RTOS本身的BUG一旦被触发,影响可能是是灾难性的。

使用特权

评论回复
板凳
ADZ2016|  楼主 | 2017-11-24 13:41 | 只看该作者
(2)不将RTOS分析透彻,很容易为项目埋下错误。典型的,像中断优先级、任务堆栈分配、可重入等,都是更容易出错的地方。

使用特权

评论回复
地板
ADZ2016|  楼主 | 2017-11-24 13:41 | 只看该作者
(3)RTOS的优先级嵌套使得任务执行顺序、执行时序更难分析,甚至变成不可能。任务嵌套对所需的最大堆栈RAM大小估计也变得困难。这对很多对安全有严格要求的场合是不可想象的。

使用特权

评论回复
5
ADZ2016|  楼主 | 2017-11-24 13:42 | 只看该作者
(4)RTOS应该用于任务复杂的场合,以至于对任务调度的需求可以抵消RTOS所带来的稳定性影响,但大部分的应用并非复杂到需要RTOS。

使用特权

评论回复
6
ADZ2016|  楼主 | 2017-11-24 13:42 | 只看该作者
一、内核配置说明

1、 时钟配置

configCPU_CLOCK_HZ    (/*你的硬件平台CPU系统时钟,Fcclk*/)
configTICK_RATE_HZ      ((portTickType)100)         
      第一个宏定义CPU系统时钟,也就是CPU执行时的频率。第二个宏定义FreeRTOS的时间片频率,这里定义为100,表明RTOS一秒钟可以切换100次任务,也就是每个时间片为10ms。
      在prot.c中,函数vPortSetupTimerInterrupt()设置节拍时钟。该函数根据上面的两个宏定义的参数,计算SysTick定时器的重装载数值寄存器,然后设置SysTick定时器的控制及状态寄存器,设置如下:使用内核时钟源、使能中断、使能SysTick定时器。另外,函数vPortSetupTimerInterrupt()由函数vTaskStartScheduler()调用,这个函数用于启动调度器。
系统节拍中断用来测量时间,因此,越高的测量频率意味着可测到越高的分辨率时间。但是,高的系统节拍中断频率也意味着RTOS内核占用更多的CPU时间,因此会降低效率。多个任务可以共享一个优先级,RTOS调度器为相同优先级的任务分享CPU时间,在每一个RTOS 系统节拍中断到来时进行任务切换。

使用特权

评论回复
7
ADZ2016|  楼主 | 2017-11-24 13:42 | 只看该作者
2、configMAX_PRIORITIES

    在RTOS内核中,每个有效优先级都会消耗一定量的RAM,因此这个值不要超过你的应用实际需要的优先级数目。每一个任务都会被分配一个优先级,优先级值从0~ (configMAX_PRIORITIES- 1)之间。

使用特权

评论回复
8
ADZ2016|  楼主 | 2017-11-24 13:43 | 只看该作者
3、configMINIMAL_STACK_SIZE

       定义空闲任务使用的堆栈大小。通常此值不应小于对应处理器演示例程文件FreeRTOSConfig.h中定义的数值。
       就像xTaskCreate()函数的堆栈大小参数一样,堆栈大小不是以字节为单位而是以字为单位的,比如在32位架构下,栈大小为100表示栈内存占用400字节的空间。

使用特权

评论回复
9
ADZ2016|  楼主 | 2017-11-24 13:43 | 只看该作者
4、configMAX_TASK_NAME_LEN

       调用任务函数时,需要设置描述任务信息的字符串,这个宏用来定义该字符串的最大长度。这里定义的长度包括字符串结束符’\0’。

使用特权

评论回复
10
ADZ2016|  楼主 | 2017-11-24 13:44 | 只看该作者
5、configUSE_TRACE_FACILITY

       设置成1表示启动可视化跟踪调试,会激活一些附加的结构体成员和函数xTASK_STATUS。

使用特权

评论回复
11
ADZ2016|  楼主 | 2017-11-24 13:44 | 只看该作者
6、configUSE_16_BIT_TICKS

       定义系统节拍计数器的变量类型,即定义portTickType是表示16位变量还是32位变量。
       定义configUSE_16_BIT_TICKS为1意味着portTickType代表16位无符号整形,定义configUSE_16_BIT_TICKS为0意味着portTickType代表32位无符号整形。
       使用16位类型可以大大提高8位和16位架构微处理器的性能,但这也限制了最大时钟计数为65535个’Tick’。因此,如果Tick频率为100HZ(10MS中断一次),对于任务最大延时或阻塞时间,16位计数器是655秒。

使用特权

评论回复
12
ADZ2016|  楼主 | 2017-11-24 13:44 | 只看该作者
二、任务概述

通常任务函数都是一个死循环,任务由xTaskCreate()函数创建,由vTaskDelete()函数删除。删除任务后,空闲任务用来释放RTOS分配给被删除任务的内存。因此,在应用中使用vTaskDelete()函数后确保空闲任务能获得处理器时间。除此之外,空闲任务没有其它有效功能,所以可以被合理的剥夺处理器时间,并且它的优先级也是最低的。
vTaskDelay( 500 / portTICK_RATE_MS ),portTICK_RATE_MS将系统时钟节拍周期转变为ms。
在任务进行过程中可以获取、改变任务优先级,挂起、恢复和删除等操作。

使用特权

评论回复
13
ADZ2016|  楼主 | 2017-11-24 13:45 | 只看该作者
三、队列管理

队列的基本用法:
1、定义一个队列句柄变量,用于保存创建的队列:xQueueHandle xQueue1;
2、使用API函数xQueueCreate()创建一个队列。
3、如果希望使用先进先出队列,使用API函数xQueueSend()或xQueueSendToBack()向队列投递队列项。如果希望使用后进先出队列,使用API函数xQueueSendToFront()向队列投递队列项。如果在中断服务程序中,切记使用它们的带中断保护版本。
4、使用API函数xQueueReceive()从队列读取队列项,如果在中断服务程序中,切记使用它们的带中断保护版本。
目前串口收发数据使用队列进行传输,串口接收数据时,将收到的数据放入队列,开启uart_rx_hook()函数,超时之后发送一个二值信号量激活对应任务进行执行。

使用特权

评论回复
14
ADZ2016|  楼主 | 2017-11-24 13:45 | 只看该作者
四、信号量管理

    FreeRTOS的信号量包括二进制信号量、计数信号量、互斥信号量(以后简称互斥量)和递归互斥信号量(以后简称递归互斥量)。我们可以把互斥量和递归互斥量看成特殊的信号量。互斥量和信号量在用法上不同:
(1)信号量用于同步,任务间或者任务和中断间同步;互斥量用于互锁,用于保护同时只能有一个任务访问的资源,为资源上一把锁。
(2)信号量用于同步时,一般是一个任务(或中断)给出信号,另一个任务获取信号;互斥量必须在同一个任务中获取信号、同一个任务给出信号。
(3)互斥量具有优先级继承,信号量没有。
(4)互斥量不能用在中断服务程序中,信号量可以。
(5)创建互斥量和创建信号量的API函数不同,但是共用获取和给出信号API函数;
    可以将二进制信号量看作只有一个项目(item)的队列,因此这个队列只能为空或满(因此称为二进制)。任务和中断使用信号量无需关注谁控制---只需要知道二值信号量是空还是满。利用这个机制可以在任务和中断之间同步。
互斥资源的使用方式:

当一个任务在使用某个资源的过程中,即还没有完全结束对资源的访问时,便被切出运行态,使得资源处于非一致,不完整的状态。如果这个时候有另一个任务或者中断来访问这个资源,则会导致数据损坏或是其它相似的错误。
例子:访问外设考虑如下情形,有两个任务都试图往一个 LCD 中写数据:
 任务 A 运行,并往 LCD 写字符串”Hello world”。
 任务 A 被任务 B 抢占,但此时字符串才输出到”Hello w”。
 任务 B 往 LCD 写”Abort, Retry, Fail?”,然后进入阻塞态。
 任务 A 从被抢占处继续执行,完成剩余的字符输出——“orld”。 现在 LCD 显示的是被破坏了的字符串”HellowAbort, Retry, Fail?orld”。
1、基本临界区是指宏 taskENTER_CRITICAL()与 taskEXIT_CRITICAL()之间的代码区间。临界区的工作仅仅是简单地把中断全部关掉,抢占式上下文切换只可能在某个中断中完成,所以调用 taskENTER_CRITICAL()的任务可以在中断关闭的时段一直保持运行态,直到退出临界区。
坏处:临界区之间的代码必须简短,像上面例子这种中断输出是不行的。长时间没有任务切换影响系统的实时性。
2、挂起调度器。
通过调用 vTaskSuspendAll()来挂起调度器。挂起调度器可以停止上下文切换而不用关中断。如果某个中断在调度器挂起过程中要求进行上下文切换,则这个请求也会被挂起,直到调度器被唤醒后才会得到执行。xTaskResumeAll()解除调度器的挂起状态。
3、互斥量
使用xSemaphoreCreateMutex()API函数创建互斥量,xSemaphoreTake(xMutex, portMAX_DELAY )拿走信号量,xSemaphoreGive( xMutex )归还信号量。
使用互斥量需要避免进入死锁:
例子:任务A与任务B都需要获得互斥量X与互斥量Y以完成各自的工作:
1. 任务A执行,并成功获得了互斥量 X。
2. 任务 A 被任务 B 抢占。
3. 任务 B 成功获得了互斥量 Y,之后又试图获取互斥量 X——但互斥量 X 已 经被任务 A 持有,所以对任务 B 无效。任务 B 选择进入阻塞态以等待互斥量 X 被释放。
4. 任务A得以继续执行。其试图获取互斥量Y——但互斥量 Y 已经被任务B持有而对任务A无效。任务A也选择进入阻塞态以等待互斥量 Y 被释放。
这种情形的最终结局是,任务A在等待一个被任务B持有的互斥量,而任务B也在等待一个被任务A持有的互斥量。死锁于是发生,因为两个任务都不可能再执行下去了。

使用特权

评论回复
15
ADZ2016|  楼主 | 2017-11-24 13:46 | 只看该作者
五、链表管理

    从整段代码中可以看出,初始化后的链表是一个循环链表,以xListEnd为尾。整个链表只有一个节点(就是xListEnd)。而头节点则是xListEnd的下一个节点。另外,尾节点的节点值为portMAX_DELAY。上图描述:可以说,整条链表是按以下方式进行管理的。
P指针为前一节点指针,N为下一节点指针。接下来是插入操作。插入节点有两种类型:(1)是添加新节点到尾部。根据代码描述,这种类型的插入结果如下图所示。可以看出整条链表是一条循环链表,用链表中的xListEnd来指示链表结尾,并用来给出链表头入口。添加节点New4是属于第(2)种节点插入的情况,根据代码描述,是按值的顺序来决定插入位置的,只要遇到链表中的其中一个节点B的值比新节点A的值要大,那么A就插入到B的前面。例如,节点值排序为new1<new2<new4<new3,new4为新节点,那么new4就插入到new3的前面。
至于移除的话就是很普通的链表操作,注意一下链表指针没有指错,以及保持链表为循环链表就行了。

使用特权

评论回复
16
ADZ2016|  楼主 | 2017-11-24 13:47 | 只看该作者
六、内存管理

    目前官方提供了5种内存管理策略:对于heap_1.c、heap_2.c和heap_4.c这三种内存管理策略,内存堆实际上是一个很大的数组,定义为:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
Heap_1只能简单的申请内存,不能进行释放,主要适用于不需要动态删除任务、信号量、队列等,而是在初始化的时候一次性创建好,便一直使用,永远不用删除的程序。
   Heap_2.c这个方案使用一个匹配算法,它允许释放之前分配的内存块,但不会把相邻的空闲块合成一个更大的块(换句话说,这会造成内存碎片)。
heap_3.c,这种策略只是简单的包装了标准库中的malloc()和free()函数,包装后的malloc()和free()函数具备线程保护。因此,内存堆需要通过编译器或者启动文件设置堆空间。
    heap_4.c与第二种比较相似,只不过增加了一个合并算法,将相邻的空闲内存块合并成一个大块。
    heap_5.c比较有趣,它允许程序设置多个非连续内存堆,比如需要快速访问的内存堆设置在片内RAM,稍微慢速访问的内存堆设置在外部RAM。每个内存堆的起始地址和大小由应用程序设计者定义。
     API函数xPortGetFreeHeapSize()返回剩下的未分配堆栈空间的大小(可用于优化设置configTOTAL_HEAP_SIZE宏的值),但是不能提供未分配内存的碎片细节信息。
      heap_2功能简介:
    目前我们使用第二种策略,可以用于重复的分配和删除具有相同堆栈空间的任务、队列、信号量、互斥量等等,并且不考虑内存碎片的应用程序。不能用在分配和释放随机字节堆栈空间的应用程序。
如果一个应用程序动态的创建和删除任务,并且分配给任务的堆栈空间总是同样大小,那么大多数情况下heap_2.c是可以使用的。但是,如果分配给任务的堆栈不总是相等,那么释放的有效内存可能碎片化,形成很多小的内存块。最后会因为没有足够大的连续堆栈空间而造成内存分配失败。在这种情况下,heap_4.c是一个很好的选择。虽然不具有确定性,但是它比标准库中的malloc函数具有高得多的效率。

使用特权

评论回复
17
ADZ2016|  楼主 | 2017-11-24 13:49 | 只看该作者
各种OS的排行榜

1.png (477.42 KB )

1.png

使用特权

评论回复
18
ADZ2016|  楼主 | 2017-11-24 13:49 | 只看该作者
2012和2013年RTOS使用榜

使用特权

评论回复
19
ADZ2016|  楼主 | 2017-11-24 13:50 | 只看该作者
2012和2013年RTOS使用榜

使用特权

评论回复
20
ADZ2016|  楼主 | 2017-11-24 13:52 | 只看该作者
2014--2015年

3.jpg (162.55 KB )

3.jpg

使用特权

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

本版积分规则

61

主题

1209

帖子

7

粉丝