RTOS基础知识笔记

embedded/2024/9/25 21:30:36/

RTOS

RTOS属于操作系统(OS)

软件和硬件之间的桥梁,

本质

专用设备:C51单片机、STM32、嵌入式

OS:硬件驱动(内存管理、GPIO、Timer)

应用:直接调用驱动,开发应用逻辑

OS的使用场景

外设资源多、有并行的需求(多任务)、实时性

所以OS的本质就是一种分工的思想

逻辑系统:主程序在while(1)循环中,有中断机制

main( ){

//硬件初始化while(1){//应用}

}

xxx handler(){

//处理中断

}

多任务系统:任务去处理程序、中断

main( ){

//硬件初始化//RTOS初始化//多种任务初始化//开启任务:while(1)//这是每个任务中的whilewhile(1)//程序不能执行到这里

}

逻辑系统和多任务系统的区别:

逻辑系统主程序在while循环里面,实时性是中断保证的;

多任务是用任务去处理事件,每个事件对于每个任务而言都是主逻辑,实时性也是靠中断保证的;

程序结构 事件处理 紧急处理 特点:

裸机 主程序中轮询 中断 轮询+中断

多任务 任务为主 中断 任务+中断

正常的操作系统,其实也是有实时性的,但是性能稍微差点,主要去管理庞大的底层资源,内核特别大

RTOS对实时性要求非常高,对于管理底层资源可是可以做到了,因为单片机底层资源很少;

RTOS特点:

稳定性;开源协议;开发成本;外部扩展资源:有丰富的第三方资源库

(LOT:物联网) 实时性;市场占有率(bug少,学习资源多,产品推广方便)

以前的RTOS,追求稳定和实时,实现多任务和中断,设备的每个地址是实地址绑定的;

现在的RTOS加了第三方物联网组件(因为万物互联的趋势LOT)

RTOS学什么

应用开发、内核开发(kernel):kernel,port是具体的板卡资源,需要单独移植(软件控制硬件)、LOT开发:第三方资源

RTOS怎么学

RTOS的概念:任务、空闲、抢占、消息等等、RTOS的设计思路:就绪队列中如何去挑出最适合运行的任务(任务的优先级)、RTOS的接口使用:常见的API用熟

最终目标:会看(看懂源码)、会用(会用API)、会改(遇到的bug)

CMSIS标准

CMSIS: Cortex-M(微处理器)定义的一套标准

通用的数据类型;通用API:RTOS、Driver

(GPIO、Timer、ADC、DMA)

用这套标准打造一个生态

FreeRTOS

起源:一个英国公司发起的,被亚马逊看中,被收购,(电商,云计算:部署一些LOT设备,用rtos能够扩大团队、优化程序,开源用来推广产品)

特性:开源、商用免费、市场占有率高

组成:kernel、port、第三方库

任务;IPC;内存

内存管理:

五个策略:正常使用第四个就足够了;

1、可申请,不释放;

2、可申请,可释放,不整理;

3、2+线程安全优化

4、可申请,可释放,可整理

5、4+识别外扩的内存芯片

编程规范:

变量名:数据类型+单词(见名知意)

函数名:返回值类型+文件名+单词

宏名:文件名+单词

数据类型:标准数据类型和非标准数据类型

例:定义一个char类型的无符号计数值

uint8-t uccount;

定义一个赋值函数:

void vtimGetCNT( ){ }

接口API:

FreeRTOS的原生API

符合CMSIS标准的API

符合HAL库的API

配置:

FreeRTOS的Config.h(程序员根据应用去做配置,自定义Kernel)

FreeRTOS.h:默认文件 ifdef define

任务管理:

什么是任务:

竞争系统资源的最小单位;

每个任务都有属于自己的私有栈;

每个任务都在各自的while(1)中去死循环;

时间点上是串行;时间段上是并行;

任务的状态切换:

创建、删除、挂起、恢复、阻塞

Task1:每隔1s 打印一次 “task1删除”

Task2:每隔1s 打印一次 “task2创建”

Task3:{

while(1){if("1"){删除}if("2"){创建}if("3"){挂起}if("4"){恢复}}

}

1任务管理

概念、

状态切换流程

相关函数

系统启动

空闲任务

任务切换

2状态切换:创建、就绪、运行、阻塞、挂起

3相关函数:

1、创建、

2、删除、

3、挂起、

4、恢复挂起

1、可在任务中恢复

函数:vTaskResume( )

可以有多次挂起,但只需要在最后恢复一次即可;

不可以在中断中使用;

2、可在中断中恢复

vTaskResumeFromISR()

(fromISR:在中断中使用的API)

xxx–callback ( ){

BaseType-t temp;(判断是否挂起成功)

temp=vTaskResumeFromISR(句柄)

if(temp==pdtrue){

portYIEID–from–ISP( ) //在中断中主动进行任务切换

}

}

4系统启动:

osKernelStart() -> vTaskStartScheldmr( )

1、启动RTOS多任务

2、创建空闲任务和可选的软件定时器;

3、正常状态下无返回值,如果出现返回值,要么是空闲任务的创建异常,要么是软件定时器的创建异常;

5空闲任务:

1、主要是用来回收要被删除的任务的资源;

2、实现低功耗;

6任务切换

1阻塞:延时阻塞/条件阻塞;

2时间片时间到了,让出CPU(本质上是触发中断:pendSV(优先级是15,这个中断优先级数字越大,优先级越低,所以这个触发中断的优先级是最低的)->触发中断)

任务调度:

三种方式:

合作式调度:上一个结束,下一个才开始

时间片调度:同等任务优先级的时候,使用时间片来进行任务切换

抢占式调度:高优先级任务-> 低优先级任务;当前运行的任务是低优先级,如果来了一个高优先级任务,CPU会立刻将低的停下,让高任务去运行;

任务栈:

常用的栈:满减栈,满增栈;空减栈;空增栈;

sp指向的位置有没有数据,有的话,再看往大的地址加入数据还是往小的数据加入数据

每个任务都会有一个私有栈:

如何确定栈的大小:

首先,是估计可能的栈的大小:

函数:局部变量,形参,返回地址,状态;

中断;

任务切换

以上是可以评估的;

不可评估的:

printf( )

函数指针 ->接口

递归代码

先给一个大的栈空间

通过调试函数,得到剩余栈空间(真是利用的栈的大小)

栈的大小乘1.5或者 乘2

栈溢出检测:

有一个配置, xxx-stack-overflow,它的值分为1和2,代表两个不同的检测标准;1:看sp的地址是否超出;2:将整个栈都赋值为一个特殊的值:0xa5,每次都去查看最后16位是否为0xa5;如果是,那就说明还有一些值没有用;

配置:vApplicationStartOverFlowHook( ){ }

硬件错误:中断:HandFault-handler(void){}

中断优先级

任务优先级:

配置:MAX-priority

值越大,等级越高

不同优先级的任务:抢占式调度

同一优先级的任务:时间片调度

明确抢占时的场景

2个API:配置

设置优先级/获取优先级

uxRTaskpritorityGet( )

vTaskpritoritySet( )

6、临界区

当前运行的代码不可被高优先级的中断打断;

何时用临界区:全局可写变量和全局函数

实现:

7、调度锁、中断锁、任务锁

调度锁:防止当前任务被其他更高优先级的任务切换,将其他人都挂起

阻塞延时:相对延时/绝对延时

IPC:

中断和任务之间的通讯;

重点在于API函数的调用;

首先是消息队列(先进先出,用来存放消息)

在任务与任务或者中断与任务之间传递不固定长度的信息;

全局数组 VS 消息队列 使用消息队列的好处:

消息队列可以防止多任务同时操作;

还有超时的机制,可以帮助RTOS更好地管理任务;

先进先出,后进后出-----可以更好地管理数据;

内部的数据都是直接复制的,并非引用的;如果给消息队列传数据进去,并不是传的引用,虽然传地址省空间,但是消息队列本身为了安全性,所以直接传消息数据本身,并非传引用;

执行流程:

消息队列的TCB(控制块(也就是所谓的句柄)消息队列也是有句柄的)

有一个头指针,一个尾指针;要将数据不断插入;插入的数据,就在消息空间;发送方就是任务,中断,任务1,任务2,接收中断(Rx中断–>中断函数)也可以发送数据;

任务和中断都可以接收数据(可以是字符串,数组,结构体等等,都能发送)

应用场景:

双方发送一个不定长的消息

消息队列的API :

1、创建消息队列

□ xQueueCreate(消息队列的大小,每个消息的内存大小)

返回值就是消息队列的句柄

基本使用:

首先就是要创建一个消息队列

Queuehandle-t temp;

temp;=xQueueCreate(2,10);

if(temp==pdtrue){

成功;

}else{

失败;

}

2、删除消息队列

xQueueDelete(句柄)

删除消息队列

删除队列中的数据

3、发送消息队列(任务中发送/中断中发送)

xQueueSend(句柄,数据地址,)

数据是复制而不是引用进去;不能中断使用;

若第三个参数=0,返回

if(xQueueSend…==pdturn){

消息队列写入成功;

}else{

消息队列写入失败;	

}

中断里:

xxx-Callback( ){

BaseType-t temp;xQueueSendFromISR( ,,&temp){portYIEID-From-ISR(temp);//temp的值就决定要不要把值传入进去

}

}

4、获取消息队列

2、信号量

消息队列 ->数组

信号量 ->标志位

三大用途:

同步应用;二值信号量

资源管理;计数信号量

临界资源的互斥访问;互斥信号量

二值信号量取值:0/1

计数信号量取值:0到N

互斥信号量数值就是真或假

0代表信号量中没有数据,想要获取数据就要一直等待,这就是一种阻塞;如果是1或者N,那就是有数据了,接触阻塞

专业术语:

0代表信号量为空,任务要获取必须先阻塞延时;有两种退出的情况:一是超时;二是其他任务主动释放资源

二值信号量本质是一个长度为1,消息大小为0的消息队列;

整个状态就2种:空/满

0:当前二值信号量未被获取,此时再次获取信号量的任务,就必须阻塞;

1:可以正常获取

应用:

有数据变化 LCD刷新

无数据变化 LCD不刷新

运行流程:

二值信号量的API:

创建二值信号量:

□ xSemaphoreCreate( ){

osSemaphoreID temp;temp=xSemaphoreCreate( );

if(temp==pdture){

成功

}else失败

}

删除二值信号量:

xSemaphoreDelete( 句柄){

}

释放二值信号量

xSemaphoreGive( 句柄){

//调用此函数时,句柄要存在//不可用于中断

}

中断:

xSemaphoreGiveFromISR( ){

BaseType-t temp;xSemaphoreGiveFromISR(句柄,&temp);portYIEID-From-ISR(temp);

}

获取二值信号量:

xSemaphoreTake(句柄,等待时间){

//等待时间为0:

}

计数信号量:

取值>1的特殊的二值信号量(长度为1,消息大小为0的特殊的消息队列)

应用场景:允许多任务访问同一资源,但是又限制了任务的个数

运行流程:

释放资源后,task1可以继续运行,资源不够的时候,task1是阻塞的,task1占用资源之后也是要释放资源的;task2也需要调用资源,如果无法调用到资源,那么task2就是原地打转;

计数信号量的API:

创建:

xSemaphoreCreateCounting(支持的最大数,初始值 )

创建句柄;

句柄函数;

if(句柄,成功)else

删除:

xSemaphoreDelete(句柄)

释放:

xSemaphoreGive(句柄)

还有中断的函数

互斥信号量本质上也是一个特殊的二值信号量;

区别在于:二值信号量

task1和task2都要打印数据,共用串口资源(只有一个串口)

二值信号量在做同步 出现一个优先级问题,违反了抢占式调度的原则

原因:低优先级抢占资源,不放手,高优先级任务去获取时被阻塞,此时若有次高优先级任务加入,则当高优先级条件无法满足,出现次高先行的情况->抢占原则

解决:将正在使用的资源的低优先级任务,临时将其优先级提升至和阻塞的任务平级,当资源释放后,恢复

互斥信号量的API:

创建互斥信号量

删除互斥信号量

获取互斥信号量

释放互斥信号量

创建: □(互斥句柄) xSemaphoreMutux( )

句柄变量,赋值,判断是否成功

删除:vSemaphoreDelete(句柄)

释放:vSemaphoreGive(句柄)

vSemaphoreGiveFromISR(句柄,高优先级的状态唤醒)

portYIEID-From-ISR

获取:xSemaphoreTask( )

事件:

任务与任务之间,任务与中断中的一种同步机制;

应用:一些危险的(机器)启动,要经过很多的检测(校验),每个或者部分条件满足才可启动;

运行流程:

Task1 Task2 Task3等等

     事件组

中断1 中断2

task1的启动,几个条件都要满足,如果有一个条件不满足,那么task1只能打转;

事件可以做到一对多,多对多;

而且没有一个具体的数据的传输,只有一个标志位;

32位的,只用到了低的24位,整个事件组用到了0到23位;

事件发生了,就置为1,如果四个都置为1,就说明了条件都满足了,就可启动。

事件的API:

创建事件组:

□(事件句柄) xEventGroupCreate( )

创建句柄;句柄赋值;判断成功/失败

删除事件组:

xEventGroupDelete(句柄)

置位事件组:

xEventGroupSitBits(句柄,事件标志位)

事件标志位:

在中断里的置位:

xEventGroupSitBitsFromISR(句柄,事件标志位,唤醒状态位)//用守护任务,去负责给事件组置位;因为freeRTOS有个原则:在中断里不能去处理不确定的任务(不知道事件组给谁用,给哪些任务或者中断去用)守护任务其实就是软件定时器

等待事件组:

xEventGroupWaitBits(句柄,等待被设置的标志位,选择清零,选择是否满足所有事件,超时等待)

//3:如果不清除,表示以后会反复被触发(和中断一样,如果中断标志位不清除的话,就会一直被触发)

//4:事件组中有几个标志位已经被设置为1了,选择满足几个条件事件才触发,其实就是一个满足规则

freeRTOS的软件定时器

介绍:

基于SYSTick实现的

与硬件无关

可以创建N个 -->软件定时器组

应用:当有多个定时需求而硬件定时器资源不多时,就要用软件定时器在一些对精准要求不高的定时场景下(SYSTick负责计时,但是中断优先级不高)

运行流程:RTOS启动------守护任务(prvTimertask/Daemon),在这个守护进程中,主要做的事:获取到定时器相关的回调函数;将所有的定时器指令放到定时器命名队列(定时器指令:开启、停止、恢复、删除等等,这些指令会被放到特殊的队列里,Time Commod queue定时器命名队列)(定时器指令下发时机在守护进程创建之后)

定时器的API:

创建定时器:

□定时器的句柄 xTimerCreate (定时器名字,定时时间,模式,ID,回调函数)

//模式分为:单次模式和周期模式

//回调函数:当前定时器要执行的业务(可能是lcd屏幕的刷新,也可能是DHT11的数据读取)

开启定时器:

停止定时器:

删除定时器:

这三个是一组函数:

pdfault/pdpass(两种返回值,前者是指令发送失败,后者是指令通过) xTimer Delete/Start/Stop(句柄,超时)

//超时:有两层含义:

1、具体的任务执行由守护进程去做;

2、当定时器消息队列已满,当前指令要等待队列有空位才能加入

获取定时器的ID:

ID prTimerGetTimerIDC(句柄)

//返回的是ID

独立看门狗:

这里的看门狗与STM32中的是同一个,但是用法不同;

在STM32中的应用:喂狗,改变OVT

在freeRTOS中的应用:

监控任务

监控任务由独立看门狗去监控自身;

只有规定的任务事件发生了,才回去喂狗

注意:监控任务的优先级必须最高—发条件满足,立即喂狗;

当前项目中的所有任务,不可阻塞/挂起/删除

(所有核心任务)

喂狗给的时间要足够长—被监控任务重最长发送事件标志位的时间

被监控的任务最多有24个(事件组只有24个标志位可用)

动态内存管理:

任务、消息队列、信号量、定时器都需要RAM(内存空间),所以就需要动态内存管理

主要管理:

空间申请,释放,合并

5种管理策略:

1、只申请,不释放

2、申请然后释放,但是不合并

3、2+线程安全(malloc/free)

4、申请,释放,合并

5、4+支持将动态内存设置在不连续的区域上

Tickless低功耗模式:

电池类电子产品–>低功耗

低功耗的核心思想:如果不工作,就停止运行;就不去耗电;

睡眠模式:CPU不工作,其他外设工作

停止:时钟总线不工作,杀死了所有外设

待机模式:切断CPU的供电(1.8V)

freeRTOS中的低功耗

Tickless模式:

工作原理:

减少SYSTick的节拍运行

Tickless的低功耗模式使我们现在所有小型的RTOS中通用的;

触发时机:当所有任务都被阻塞/挂起时,由空闲任务去执行低功耗

低功耗主要针对的对象是SYSTick,让其在无任务时尽量不工作;

运行流程:

1、在空闲任务中关闭SYSTick中断(坏处:只让任务一阻塞一秒钟–osDelay(1000),然而systick被关闭,这个任务就无法被唤醒了)–因此这个方案不可行;

2、当进入空闲任务后,计算出下一个执行的高优先级任务还剩多少阻塞时间,以此去改变SYSTick的重装载值,作为这段时间的唤醒时间;

Tickless-IDIE设置为1,自动进入低功耗


http://www.ppmy.cn/embedded/116839.html

相关文章

格式串详解

不同语言格式串五花八门,究其本质,无非是如何处理格式串和变量对应关系。 示例: 输出右对齐宽度为8个字符整形数据 C/ObjC/Java/Ruby/Go %8d 直接依赖库函数解析格式串,区分格式符前缀%、对齐长度、对齐方向、类型等,做填充、对齐…

我的AI工具箱Tauri版-VideoReapeat视频解说复述克隆

本教程基于自研的AI工具箱Tauri版进行VideoReapeat视频解说复述克隆。 VideoReapeat视频解说复述克隆 是自研的AI工具箱Tauri版中的一款专用模块,旨在通过AI技术对视频解说内容进行复述和克隆。该工具可自动洗稿并重新生成视频解说,通过简单配置即可对大…

初识爬虫8

1.selenium的作用和工作原理 2. 使用selenium,完成web浏览器调用 # -*- coding: utf-8 -*- # 自动化测试工具,降低难度,性能也降低 from selenium import webdriverdriver webdriver.Edge()driver.get("https://www.itcast.cn/")…

『功能项目』QFrameWork框架重构OnGUI【63】

我们打开上一篇62QFrameWork背包框架的项目, 上文将功能实现在一个脚本中 本章要做的事情让脚本实现背包框架思想 首先按照图示创建脚本: 创建脚本:Item.cs namespace QFramework {public class Item{//道具public string Key;public string …

oracle direct path read处理过程

文章目录 缘起处理过程1.AWR Report 分析2.调查direct path read发生的table3.获取sql text4.解释sql并输出执行计划: 结论:补充direct path read等待事件说明 缘起 记录direct path read处理过程 处理过程 1.AWR Report 分析 问题发生时间段awr如下…

使用 Istio 缓解电信 5G IoT 微服务 Pod 架构的安全挑战

在 Kubernetes 集群中部署微服务在 5G 电信中至关重要。但是,它也带来了重大的安全风险。虽然防火墙规则和代理提供了初始安全性,但 Kubernetes 中的默认通信机制(例如未加密的网络流量和缺乏访问控制)本质上是不安全的。这种不安…

如何防止U盘资料被复制?(最全攻略来了,第一种你Get了吗?)

防止U盘资料被复制是一个涉及多个层面的策略。 以下是最全面的攻略,旨在帮助您确保U盘中的数据安全: 1. 使用加密软件 加密U盘:利用专业的加密软件(如安企神、Verypt等)对U盘进行全盘或分区加密。 这些软件可以确保只…

MySQL内存(Buffer Pool)

Buffer Pool MySQL 的数据存在磁盘,但是不能每次读取数据都从磁盘里去,这样磁盘IO太频繁,存在性能问题。 InnoDB设计了一个缓存池(Buffer Pool),缓冲池在内存中。 默认配置Buffer Pool大小为128MB&#xf…