1.认识实施操作系统
1.1 裸机和实时操作系统
裸机:
早期嵌入式开发没有嵌入式操作系统的概念,直接操作裸机,在裸机上写程序,比如用51单片机基本就没有操作系统的概念。
通常把程序设计为前后台系统,主要分为两部分:前台系统和后台系统。这样的程序包括一个死循环和若干个中断服务程序(应用程序是一个无限循环,循环中调用API函数完成所需的操作,这个大循环就叫做后台系统;中断服务程序用于处理系统的异步事件,也就是前台系统),前台是中断级,后台是任务级。
RTOS:
RTOS全称为:Real Time OS,就是实时操作系统,强调的是:实时性。在实时操作系统中,我们可以把要实现的功能划分为多个任务,每个任务负责实现其中的一部分,每个任务都是一个很简单的程序,通常是一个死循环。 RTOS操作系统:FreeRTOS,UCOS,RTX,RT-Thread,DJYOS等。 RTOS操作系统的核心内容在于:实时内核。
1.2 嵌入式操作系统的作用
操作系统是个软件(管理底层硬件,并且上层应用提供接口)
嵌入式(实时)操作系统特点:用于嵌入式设备的操作系统,具有通用操作系统的基本特点,又具有系统实时性、硬件的相关依赖性、软件固态化以及应用的专用性等特点;
评判嵌入式(实时)操作系统的重要指标:实时性(中断响应时间、任务切换时间等)、尺寸(可裁剪性 )、可扩展性(内核、中间件);
2 FreeRTOS
2.1 FreeRTOS介绍
- Free 即免费的,RTOS 全称是 Real Time Operating System,中文就是实时操作系统。注意,RTOS 不是指某一个确定的系统,而是指一类系统。比如 uC/OS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类操作系统。
- 操作系统允许多个任务同时运行,这个叫做多任务。实际上,一个处理器核心在某一时刻只能运行一个任务。操作系统中任务调度器的责任就是决定在某一时刻究竟运行哪个任务。任务调度在各个任务之间的切换非常快,就给人们造成了同一时刻有多个任务同时运行的错觉。
- FreeRTOS 是 RTOS 系统的一种,FreeRTOS 十分的小巧,可以在资源有限的微控制器中运行,当然,FreeRTOS 不仅局限于在微控制器中使用。但从文件数量上来看 FreeRTOS 要比uC/OSII 和 uC/OSIII 小的多。
- 选择FreeRTOS:
FreeRTOS是免费的,学习RTOS操作系统的话 uC/OS是首选,但要做产品的话,免费的FreeRTOS操作系统就是个不错的选择。
许多半导体厂商产品的 SDK(Software Development Kit—软件开发工具包) 包就使用 FreeRTOS 作为其操作系统,尤其是 WIFI、蓝牙这些带协议栈的芯片或模块。
简单,因为FreeRTOS 的文件数量很少。
- FreeRTOS操作系统特点:
FreeRTOS 的内核支持抢占式,合作式和时间片调度。
抢占式:高优先级任务抢占低优先级的任务
时间片调度:如果两个任务优先级一样,每个任务各执行1ms,在各个任务之间快速切换--具体时间是由时间片决定的
合作式:用的比较少
2.2 FreeRTOS 移植
原文件的获取:FreeRTOS官方下载
其中的相应内容查看相关的移植文档,这里就不过多介绍。
2.3 任务创建
任务创建功能分为两种:动态创建和静态创建。
动态创建函数:xTaskCreate()
静态创建函数:xTaskCreateStatic()
其中一般使用最广的就是动态创建任务,创建流程以及先关过程为:
第一步先创建对应的任务句柄:
第二步为创建任务函数,其中的任务函数都是不退出的循环。任务中必须要加入相对应的延时函数vTaskDelay();这个函数等到时间延时到了之后就会释放CPU资源。
第三部为使用动态创建函数:xTaskCreate(),创建任务其中的参数以及返回值为:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
{
//参数1 任务的入口函数地址
//参数2 任务函数名字 没有啥具体意义
//参数3 任务堆栈的大小 如果堆栈的宽度为 32 位,实际大小为128*4字节 任务越复杂,需要的空间越大
//如何确定任务栈空间需要的大小 先给大一点,通过接口函数查询,再留够余量,缩小回去 https://www.freertos.org/zh-cn-cmn-s/FAQMem.html#StackSize
//参数4 传递给任务的参数 不需要填NULL
//参数5 任务优先级 数字越大,优先级越高 优先级低于 configMAX_priority。 如果未定义 configASSERT,则优先级会被静默限制为 ( configMAX_priority - 1)。
//参数6 任务句柄
//返回值 创建成功 pdPASS 失败返回 xReturn
2.4 任务删除
任务删除函数:vTaskDelete();
删除的任务将从所有就绪,阻塞,挂起和事件列表中删除。
函数参数为:对应任务的句柄。
2.5 任务之间的4种运行状态
就绪态:新创建的任务处于就绪态,可以有多个任务处于就绪态,调度器会调度优先级最高的处于就绪态的任务去执行
挂起态:任务挂起,暂时不执行 只能等待解除挂起 -- 接触挂起任务的状态是就绪态
运行态:有且只有一个任务处于运行态 -- 单核处理器
阻塞态:运行的任务遇到系统的延时(vTaskDelay包括相对和绝对延时),信号量的阻塞,运行的任务进入阻塞态
2.6 临界区保护
如果有一部分代码,在执行的过程中,不希望被打断,就放入临界区
- DHT11 SPI IIC 时序类的一般不能被打断 放入临界区中
- 临界资源的保护 比如:printf
- 进入临界区 和 退出临界区 要成对使用
taskENTER_CRITICAL(); 进入临界区
taskEXIT_CRITICAL(); 退出临界区
注意:临界区函数必须是成对使用的
2.7 挂起函数和解除挂起函数
vTaskSuspend() 挂起某个任务
vTaskSuspendAll() 挂起所有任务
vTaskResume() 解挂某个任务
xTaskResumeFromISR() 在中断中解挂任务
xTaskResumeAll() 解挂所有任务
2.8 二值信号量
任务间的通信和同步方式:二值信号量,计数信号量,互斥信号量,消息队列,事件
通信:消息队列
同步:二值信号量 计数信号量 互斥信号量 事件
使用信号量的好处:
实现任务和任务同步或者中断和任务的同步,响应比较及时,比较节省CPU。
二值信号量的使用函数:
创建二值信号量 xSemaphoreCreateBinary()
信号量删除函数 vSemaphoreDelete()
任务中释放信号量 xSemaphoreGive()(任务)
中断中释放信号量 xSemaphoreGiveFromISR()(中断)
任务中获取信号量 xSemaphoreTake()(任务)
中断中获取信号量 xSemaphoreTakeFromISR()(中断)
2.9 计数信号量
创建计数信号量 xSemaphoreCreateCounting()
信号量删除函数 vSemaphoreDelete()
任务中释放信号量 xSemaphoreGive()(任务)
中断中释放信号量 xSemaphoreGiveFromISR()(中断)
任务中获取信号量 xSemaphoreTake()(任务)
中断中获取信号量 xSemaphoreTakeFromISR()(中断)
2.10 互斥信号量
前提条件:高优先级和低优先级的任务,要申请相同的二值信号量;中优先级的任务和信号量没有关系
- 低优先级的任务运行,成功申请信号量
- 低优先级任务运行过程中,被高优先级任务打断,但是高优先级任务也需要用这个信号量,但是高优先级申请不成功,被阻塞
- 低优先级运行
- 低优先级运行过程中,被中优先级打断,地优先级无法快速执行,释放信号量
- 中优先级执行完毕,继续执行低优先级
- 低优先级执行完毕,释放信号量,高优先级申请成功,继续执行
所谓优先级翻转:高优先级虽然优先级高,但是也没有办法快速获取信号量,优先级的优势没有体现出来。
当低优先级的任务执行的时候,高优先级暂时把自己的优先级继承给低优先级,那么中优先级就无法打断低优先级,能够让低优先级尽快执行完毕,释放信号量,让高优先级任务使用,这叫做优先级的继承。
互斥量创建函数 xSemaphoreCreateMutex()
互斥量删除函数 vSemaphoreDelete()
互斥量获取函数 xSemaphoreTake()
互斥量释放函数 xSemaphoreGive()
2.11 队列
FreeRTOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:
消息支持先进先出方式排队,支持异步读写工作方式。
读写队列均支持超时机制。
消息支持后进先出方式排队, 往队首发送消息( LIFO) 。
可以允许不同长度(不超过队列节点最大值)的任意类型消息。
一个任务能够从任意一个消息队列接收和发送消息。
多个任务能够从同一个消息队列接收和发送消息。
当队列使用结束后,可以通过删除队列函数进行删除。
消息队列创建函数 xQueueCreate()
消息队列静态创建函数 xQueueCreateStatic()
消息队列删除函数 vQueueDelete()
消息队列发送函数 xQueueSend()
消息队列发送函数 xQueueSendToBack() 和上面一个一样,在不同的版本里面
消息队列发送函数 xQueueSendFromISR() 中断中使用
消息队列发送函数 xQueueSendToBackFromISR() 中断中使用
消息队列接收函数 xQueueReceive()与 xQueuePeek()
xQueueReceive接收消息之后 删除
xQueuePeek 接收消息之后不删除
消息队列接收函数xQueueReceiveFromISR()与 xQueuePeekFromISR()
2.12 事件
将KQM6600&SU03T功能做到一个任务中,使用事件同步做处理
信号量:能够实现任务和任务或者任务和中断的同步,大部分发生在两者之间
事件:可以发生在多对一
KQM6600 是一个任务
DHT11 烟雾 光照 是一个任务
往屏幕上更新数据,希望两个任务都采集到数据,再把数据更新到屏幕上,使用事件可以实现多个任务之间的同步
本质上就是全局变量
事件创建函数 xEventGroupCreate()
事件删除函数 vEventGroupDelete()
事件组任务中置位函数 xEventGroupSetBits()(任务)
事件组中断置位函数 xEventGroupSetBitsFromISR()(中断)
等待事件函数 xEventGroupWaitBits()
事件组清除函数xEventGroupClearBits()
事件组在中断中清除函数xEventGroupClearBitsFromISR()