FreeRTOS及其应用,万字长文,基础入门
嵌入式系统不只是ARM+Linux,不是只有安卓,凡是电子产品都可称为嵌入式系统。物联网行业的兴起,也提升了FreeRTOS市场占有率。本文就是介绍FreeRTOS基础及其应用,只是个人整理,可能存在问题,其目的只是简要介绍系统的基础,只能作为入门资料。原文链接:https://mp.weixin.qq.com/s/MyPBlHXG0iawoUzdDFTBQw
一、 为什么要学习 RTOS进入嵌入式这个领域,入门首先接触的是单片机编程,尤其是C51 单片机来,基础的单片机编程通常都是指裸机编程,即不加入任何 RTOS(Real Time Operating System 实时操作系统)。常用的有国外的FreeRTOS、μC/OS、RTX 和国内的 RT-thread、Huawei LiteOS 和 AliOS-Things 等,其中开源且免费的 FreeRTOS 的市场占有率较高。
1.1 前后台系统在裸机系统中,所有的操作都是在一个无限的大循环里面实现,支持中断检测。外部中断紧急事件在中断里面标记或者响应,中断服务称为前台,main 函数里面的while(1)无限循环称为后台,按顺序处理业务功能,以及中断标记的可执行的事件。小型的电子产品用的都是裸机系统,而且也能够满足需求。
1.2 多任务系统多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。如果事件对应的任务的优先级足够高,中断对应的事件会立刻执行。相比前后台系统,多任务系统的实时性又被提高了。在多任务系统中,根据程序的功能,把这个程序主体分割成一个个独立的,无限循环且不能返回的子程序,称之为任务。每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,开发人员不需要关注每个功能模块之间的冲突,重心放在子程序的实现。缺点是整个系统随之带来的额外RAM开销,但对目前的单片机的来影响不大。
1.3 学习RTOS的意义学习 RTOS,一是项目需要,随着产品要实现的功能越来越多,单纯的裸机系统已经不能完美地解决问题,反而会使编程变得更加复杂,如果想降低编程的难度,就必须引入 RTOS实现多任务管理。二是技能需要,掌握操作系统,和基于RTOS的编程,实现更好的职业规划,对个人发展尤其是钱途是必不可少的。以前一直觉得学操作系统就必须是linux,实际每个系统都有其应用场景,对于物联网行业,杀**焉用牛刀,小而美,且应用广泛的FreeRTOS 是首选。有一个操作系统的基础,即使后续基于其他系统开发软件,也可触类旁通,对新技术快速入门。目前接触的几款芯片都是基于FreeRTOS。如何学习RTOS?最简单的就是在别人移植好的系统之上,看看 RTOS 里面的 API 使用说明,然后调用这些 API 实现自己想要的功能即可。完全不用关心底层的移植,这是最简单快速的入门方法。这种学习方式,如果是做产品,可以快速的实现功能,弊端是当程序出现问题的时候,如果对RTOS不够了解,会导致调试困难,无从下手。各种RTOS内核实现方式都差不多,我们只需要深入学习其中一款就行。万变不离其宗,正如掌握了C51基础,后续换其他型号或者更高级的ARM单片机,在原理和方法上,都是有借鉴意义,可以比较快的熟悉并掌握新单片机的使用。
二、 操作系统基础2.1 链表链表作为 C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多。FreeRTOS 中存在着大量的基础数据结构链表和链表项的操作(list 和 list item)。FreeRTOS 中与链表相关的操作均在 list.h 和 list.c 这两个文件中实现。链表比数组,最大优势是占用的内存空间可以随着需求扩大或缩小,动态调整。实际FreeRTOS中各种任务的记录都是依靠链表动态管理,具体的可以参考源码的任务控制块tskTCB。任务切换状态,就是将对应的链表进行操作,链表操作涉及创建和插入、删除和查找。
2.2 队列队列是一种只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。队尾放入数据,对头挤出。先进先出,称为FIFO
2.3 任务在裸机系统中,系统的主体就是 main 函数里面顺序执行的无限循环,这个无限循环里面 CPU 按照顺序完成各种事情。在多任务系统中,根据功能的不同,把整个系统分割成一个个独立的且无法返回的函数,这个函数我们称为任务。系统中的每一任务都有多种运行状态。系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度。 就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。 运行(Running):该状态表明任务正在执行,此时它占用处理器,调度器选择运行的永远是处于最高优先级的就绪态任务。 阻塞(Blocked):任务当前正在等待某个事件,比如信号量或外部中断。 挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的。挂起态与阻塞态的区别,当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到调用恢复任务的 接口;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。各任务运行时使用消息、信号量等方式进行通信,不能是全局变量。任务通常会运行在一个死循环中,不会退出,如果不再需要,可以调用删除任务。
2.4 临界区临界区就是一段在执行的时候不能被中断的代码段。在多任务操作系统里面,对全局变量的操作不能被打断,不能执行到一半就被其他任务再次操作。一般被打断,原因就是系统调度或外部中断。对临界区的保护控制,归根到底就是对系统中断的使能控制。在使用临界区时,关闭中断响应,对部分优先级的中断进行屏蔽,因此临界区不允许运行时间过长。为了对临界区进行控制,就需要使用信号量通信,实现同步或互斥操作。
三、 初识 FreeRTOS3.1 FreeRTOS源码FreeRTOS 由美国的 Richard Barry 于 2003 年发布, 2018 年被亚马逊收购,改名为 AWS FreeRTOS,版本号升级为 V10,支持MIT开源协议,亚马逊收购 FreeRTOS 也是为了进入物联网和人工智能,新版本增加了物联网行业的网络协议等功能。FreeRTOS 是开源免费的,可从官网 www.freertos.org 下载源码和说明手册。例如展锐的UIS8910使用的是V10。以FreeRTOSv10.4.1为例,包含 Demo 例程,Source内核的源码,License许可文件。
3.1.1 Source 文件夹FreeRTOS/ Source 文件夹下的文件:包括FreeRTOS 的通用的头文件include和 C 文件,包括任务、队列、定时器等,适用于各种编译器和处理器,是通用的。需要特殊处理适配的在portblle文件夹,其下内容与编译器和处理器相关, FreeRTOS 要想运行在一个单片机上面,它们就必须关联在一起,通常由汇编和 C 联合编写。通常难度比较高,不过一般芯片原厂提供移植好的接口文件。这里不介绍移植的方法,因为自己也不明白。Portblle/MemMang 文件夹下存放的是跟内存管理相关的,总共有五个 heap 文件,有5种内存动态分配方式,一般物联网产品选用 heap4.c 。
3.1.2 Demo 文件夹里面包含了 FreeRTOS 官方为各个单片机移植好的工程代码,FreeRTOS 为了推广自己,会给针对不同半导体厂商的评估板实现基础功能范例, Demo下就是参考范例。
3.1.3 FreeRTOSConfig.h配置FreeRTOSConfig.h头文件对FreeRTOS 所需的功能的宏均做了定义,需要根据应用情况配置合适的参数,其作用类似MTK功能机平台的主mak文件,部分定义如下:1. #define configUSE_PREEMPTION 1
2. #define configUSE_IDLE_HOOK 0
3. #define configUSE_TICK_HOOK 0
4. #define configCPU_CLOCK_HZ ( SystemCoreClock )
5. #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )例如系统时钟tick等参数在就这个文件配置,具体作用可以看注释。一般情况下使用SDK不需要改动,特殊情况下咨询原厂再调整。
3.2 FreeRTOS 编码规范接触一个新平台或者SDK,明白它的编码规范,文件作用,可以提高源码阅读效率,快速熟悉其内部实现。
3.2.1 数据类型FreeRTOS针对不同的处理器,对标准C的数据类型进行了重定义。1. #define portCHAR char
2. #define portFLOAT float
3. #define portDOUBLE double
4. #define portLONG long
5. #define portSHORT short
6. #define portSTACK_TYPEuint32_t
7. #define portBASE_TYPE long应用编码中,推荐使用的是下面这种风格。
1. typedef int int32_t;
2. typedef short int16_t;
3. typedef char int8_t;
4. typedef unsigned int uint32_t;
5. typedef unsigned short uint16_t;
6. typedef unsigned char uint8_t;
3.2.2 变量名FreeRTOS 中,定义变量的时候往往会把变量的类型当作前缀,好处看到就知道其类型。 char 型变量的前缀是 c short 型变量的前缀是 s long 型变量的前缀是 l 复杂的结构体,句柄等定义的变量名的前缀是 x 变量是无符号型的再加前缀 u,是指针变量则加前缀 p
3.2.3 函数名函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个 prv(private)的前缀。 例如vTaskPrioritySet()函数的返回值为 void 型,在 task.c 这个文件中定义。3.2.4 宏宏内容是由大写字母表示,前缀是小写字母,表示该宏在哪个头文件定义,如:<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> taskYIELD() portYIELD()</span>
表示该宏是在task.h。
3.2.5 个人解读1、编码不缺编码规范,但是实际使用中很难完全依照标准执行,即使freeRTOS源码也是如此。 2、关于函数或者宏定义中带文件名的作用,使用Source Insight 编辑代码,该前缀的意义不大。 3、规则是活的,只要所有人都按一个规则执行,它就是标准。
3.3 FreeRTOS应用开发关于freeRTOS的应用开发,主要是任务的创建和调度,任务间的通信与同步,涉及队列、信号量等操作系统通用接口。结合应用需求,涉及定时器、延时、中断控制等接口。特别说明,有些功能的实现方式有多种形式,只针对常用方式进行说明,例如task的创建,只说明动态创建方式,因为很少使用静态方式。
四、 任务4.1 创建任务xTaskCreate()使用动态内存的方式创建一个任务。1. ret = xTaskCreate((TaskFunction_t) master_task_main,/* 任务入口函数 */(1)
2. “MASTER”, /* 任务名字 */(2)
3. 64*1024, /* 任务栈大小 */(3)
4. NULL, ,/* 任务入口函数参数 */(4)
5. TASK_PRIORITY_NORMAL,/* 任务的优先级 */(5)
6. &task_master_handler);/* 任务控制块指针 */(6)创建任务就是软件运行时的一个while(1)的入口,一般阅读其他代码,找到这个函数,再跟踪到任务入口函数,学习基于freeRTOS系统的代码,首先就是找到main和这个接口。(1):任务入口函数,即任务函数的名称,需要我们自己定义并且实现。 (2):任务名字,字符串形式,最大长度由 FreeRTOSConfig.h 中定义的 configMAX_TASK_NAME_LEN 宏指定,多余部分会被自动截掉,只是方便调试。(3):任务堆栈大小,单位为字, 4 个字节,这个要注意,否则系统内存紧缺。(4):任务入口函数形参,不用的时候配置为 0 或者NULL 即可。
(5) :任务的优先级,在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级。基于其SDK开发,可将自定义的所有业务功能task设为同一个优先级,按时间片轮询调度。(6):任务控制块指针,使用动态内存的时候,任务创建函数 xTaskCreate()会返回一个指针指向任务控制块,也可以设为NULL,因为任务句柄后期可以不使用。