FreeRTOS学习(一)

一、裸机开发与操作系统

传统的裸机,只有一个任务,无法处理多个任务,一次只能处理一个任务。如果在执行一个任务时,想去触发其他任务,只有一个方式,就是使用中断。
裸机开发是单线程,若想使用多线程,需要在裸机的基础上加上操作系统。

|--------|--------------------------|-------------------------|
| 对比维度 | 裸机系统 | RTOS |
| 任务调度 | 手动编写状态机或超级循环(while(1)轮询) | 自动基于优先级抢占式调度 |
| 实时性 | 依赖中断响应,高优先级任务可能被阻塞 | 严格优先级控制,关键任务可抢占低优先级任务 |
| 资源占用 | 极低(无OS内核) | 较高(需内核内存,任务栈等) |
| 开发复杂度 | 简单逻辑易实现,复杂系统难以维护 | 模块化设计,多任务协作更清晰 |
| 多任务并行 | 伪并行(通过状态机模拟) | 真并行(任务独立运行) |
| 同步机制 | 需手动管理标志位或全局变量 | 内置信号量、队列、事件组等同步机制 |
| 典型应用场景 | 简单控制、低功耗设备(如遥控器、温控器) | 复杂多任务系统(无人机、工业控制器、智能家居) |

1.RTOS

实时操作系统(Real-time operating system, RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。
实时操作系统与一般的操作系统相比,最大的特色就是"实时性",如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。

RTOS分类:

2.FreeRTOS

FreeRTOS 是一个完全免费的实时操作系统,源码公开,可移植性强,支持多种处理器架构。它提供了任务调度、任务间通信、时间管理等功能。其任务调度机制是嵌入式实时操作系统的重要技术,支持优先级调度和轮换调度算法。

2.1 多任务与并发

任务是什么?
进程和线程概念的结合体,任务之间可以通过全局变量通信,每个任务都会被分配到一个栈空间来存储任务运行过程中产生的数据,多个任务同时对一个资源进行访问,会造成临界资源访问的问题,使用互斥锁、信号量PV操作。任务之间也可以通过消息队列进行通信。

优先级问题,优先级高的任务先执行,优先级低的任务后执行,优先级相同的任务抢时间片,如果发生中断,则先执行中断程序。

同优先级任务之间是如何实现并行呢?

其实单核处理器一次只能执行一项任务,当有多个同优先级的任务时其实是通过"时间片"的方式进行调用的,不同任务间不停的快速切换就好像多个任务在同时执行一样。

下图展示了与时间相关的 3 个任务的执行模式。任务名称采用颜色编码,并写在左手边。时间从左向右移动, 彩色线条显示了在任何特定时间正在执行的任务。 上方展示了所 感知的并发执行模式, 下方展示了 实际的多任务执行模式。

相对于单任务系统而言,多任务系统的任务是具有优先级的,高优先级的任务可以抢占低优先级任务的 CPU 使用权。优先级相同的任务则各自轮流运行一段极短的时间(时间片),从而产生"同时"运行的错觉,这也是抢占式调度和时间片调度的基本原理。在具备优先级的多任务系统中,用户就可以将紧急的事务放在优先级高的任务中进行处理。

2.2 任务状态

在 FreeRTOS 中的任务存在四种状态,分别为运行态、就绪态、阻塞态挂起态。在 FreeRTOS 运行时,任务的状态一定是这四种状态中的一种,下面是四种任务状态的介绍。


◆ 运行态
如果一个任务得到 CPU 的使用权,即任务被实际执行时,那么这个任务处于运行态。如果运行FreeRTOS的 MCU 只有一个处理器核心,那么在任务时刻,都只能有一个任务处理运行态。


◆ 就绪态

如果一个任务已经能够被执行(不处于阻塞态后挂起态),但当前还未被执行(具有相同优先级或更高优先级的任务正持有 CPU 使用权),那么这个任务就处于就绪态。


◆ 阻塞态

如果一个任务因延时一段时间或等待外部事件发生,那么这个任务就处理阻塞态。例如任务调用了函数 vTaskDelay(),进行一段时间的延时,那么在延时超时之前,这个任务就处理阻塞态。任务也可以处于阻塞态以等待队列、信号量、事件组、通知或信号量等外部事件。通常情况下,处于阻塞态的任务都有一个阻塞的超时时间,在任务阻塞达到或超过这个超时时间后,即使任务等待的外部事件还没有发生,任务的阻塞态也会被解除。要注意的是,处于阻塞态的任务是无法被运行的。


◆ 挂起态

任务一般通过函数 vTaskSuspend()和函数 vTaskResums()进入和退出挂起态与阻塞态一样,处于挂起态的任务也无法被运行。

2.3 调度原理

调度器 是内核中负责决定在任何特定时间应执行哪些任务的部分。内核可以在任务生命周期内多次挂起并且稍后恢复一个任务。
调度策略是调度器用来决定在任何时间点执行哪个任务的算法。非实时多用户系统的策略极有可能使每个任务具有"公平"比例的处理时间。

FreeRTOS 共支持三种任务调度方式,分别为抢占式调度、时间片调度和协程式调度 。需要注意的是,FreeRTOS 官方对协程式调度做了特殊说明,协程式调度用于一些资源非常少的设备上,但是现在已经很少用到。虽然协程式调度的相关代码还没有被删除,FreeRTOS 官方并未计划继续开发协程式调度。我们主要理解抢占式调度与时间片调度的概念。

抢占式调度:
抢占式调度主要时针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占优先级低的任务,只有当优先级高的任务发生阻塞或者被挂起,低优先级的任务才可以运行。
时间片调度:
时间片调度主要针对优先级相同的任务,当多个任务的优先级相同时, 任务调度器会在每一次系统时钟节拍到的时候切换任务,也就是说 CPU 轮流运行优先级相同的任务,每个任务运行的时间就是一个系统时钟节拍。
一个节拍的时间也就是一个时间片的时间,当然这个节拍是由FreeRTOS滴答来规定的

2.4 FreeRTOS滴答

系统节拍器:是大多数嵌入式操作系统(包括 FreeRTOS)中用于提供时间基准的硬件定时器。它以固定的频率产生中断,这个中断间隔就被称为一个 "滴答"。
时间片轮转需要定时触发-->需要FreeRTOS系统节拍来实现定时切换-->需要硬件来实现这个节拍--->默认选择滴答定时器--->滴答定时器每隔1ms产生一次中断--->一个系统节拍就是1ms

时间片的长短就是 1 个滴答,而滴答中断的长短由内部的宏定义configTICK_RATE_HZ规定,一般为 1000HZ,可在FreeRTOSConfig.h 中查看

2.5 FreeRTOS项目CubeMX配置

首先要下载FreeRTOS插件

  • 项目配置FreeRTOS

按照老方法创建一个项目,然后

5种内存管理算法
各自的特点如下所示

⚫ heap_1: 最简单,只允许申请内存,不允许释放内存。

⚫ heap_2: 允许申请和释放内存,但不能合并相邻的空闲内存块。

⚫ heap_3: 简单封装 C 库的函数 malloc()和函数 free(), 以确保线程安全。

⚫ heap_4: 允许申请和释放内存,并且能够合并相邻的空闲内存块,减少内存碎片的产生。

⚫ heap_5: 能够管理多个非连续内存区域的 heap_4。

注意: heap_1 不太有用,因为 FreeRTOS 添加了静态分配支持。 heap_2 现在被视为旧版,因为较新的heap_4 实现是首选。


  • 创建任务

点击Add以后

2.6 代码与相关函数

配置完成后查看生成代码

cpp 复制代码
/*任务创建函数介绍*/
osThreadId_t 为返回值,其实就是在操作该任务时候的句柄
参数 1:osThreadFunc_t func 这是任务函数,类似于线程函数
参数 2:void *argument 给任务函数传的参数,不传填 NULL
参数 3:const osThreadAttr_t *attr 任务相关的属性值,包括任务名字、优先级、栈空间大小等

sThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
cpp 复制代码
/*任务挂起与释放*/

void vTaskSuspend( osThreadId_t xTaskToSuspend );
参数:xTaskToSuspend	要挂起的任务句柄。若传递 NULL,表示挂起当前正在执行的任务自身。

void vTaskResume( osThreadId_t xTaskToResume );
参数:xTaskToResume	要恢复的任务句柄。必须是此前通过 
cpp 复制代码
/*任务删除*/
参数:要删除的任务的句柄
osStatus_t osThreadTerminate(osThreadId_t thread_id);

2.7 空闲任务与钩子函数

空闲任务(IDLE Task)
❉作用❉
当所有的任务都处于阻塞态时,这种状态下所有的任务都不可运行,所以也不能被调度器选中。但处理器总是需要代码来执行------所以至少要有一个任务处于运行态。为了保证这一点,当调用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务。
空闲任务的核心作用是在无其他任务运行时维持系统基本运作,在 CPU 闲置时默默执行资源维护(清理内存)、节能调度(触发休眠,防止 CPU 空转),并通过钩子函数提供灵活扩展能力
❉优先级❉
空闲任务拥有最低优先级(优先级 0)以保证其不会妨碍具有更高优先级的应用任务进入运行态------当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级。运行在最低优先级可以保证一旦有更高优先级的任务进入就绪态,空闲任务就会立即切出运行态。

❉创建的过程❉
空闲任务由系统的调度器在启动时自动创建和管理的,那调度器在哪里呢
调度器说到底也是一堆代码实现的,具体位置在主函数的osKernelStart()函数中,这个函数并不是调度器本身,而是包含调度器

追进去,vTaskStartScheduler()这个函数才是调度器

继续追进去

在追进prvIdleTask()函数


宏定义:

钩子函数
❉介绍❉
空闲任务的优先级为0,是最低的优先级,其作用是其他所有高优先级任务都执行完毕时才执行这个空闲任务,如果用户想要添加空闲任务的执行内容,系统是不允许用户直接重写空闲任务函数的,因为空闲任务还在实现诸如回收资源等的操作,但是空闲任务内有一个宏判断,如果此宏为 1,则在空闲任务执行的时候会运行一个外部引用的函数。通过检索发现这个函数并没有在其他地方定义。也就是说,如果用户想让空闲函数执行指定内容,只需要打开这个宏然后在外部定义这个函数将想让空闲任务执行的内容写入这个函数就可以了,这个函数我们就叫作钩子函数。

❉ 用途

通过空闲任务钩子函数(或称回调,hook, or call-back),可以直接在空闲任务中添加应用程序相关的功能。空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。通常空闲任务钩子函数被用于:
● 执行低优先级,后台或需要不停处理的功能代码。

● 测试系统处理(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。

● 将处理器配置到低功耗模式------提供一种自动省电方法,使得在没有任何应用功能,需要处理的时候,系统自动进入省电模式。

❉ 限制
绝不能阻塞或挂起,不能使用延时!。空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
对于钩子函数,直接修改宏定义的方法在使用 cubeMX 的时候不建议使用,因为在重新生成代码后宏定义会被重写,要想一劳永逸需要在 cubeMX 上进行配置的方式修改宏定义的值,软件还会贴心的帮我们生成了钩子函数,无需自己再去写,只需要在生成的钩子函数中写入我们想要实现的逻辑即可。

3. CMSIS

3.1 介绍与结构

ARM® Cortex™ 微控制器软件接口标准 (CMSIS) 是 Cortex-M 处理器系列的与供应商无关的硬件抽象层。CMSIS 可实现与处理器和外设之间的一致且简单的软件接口,从而简化软件的重用,缩短微控制器开发人员新手的学习过程,并缩短新设备的上市时间。
CMSIS的结构:

CMSIS 包含以下组件:

  • CMSIS-CORE:提供与 Cortex-M0、Cortex-M3、Cortex-M4、SC000 和 SC300 处理器与外围寄存器之间的接口
  • CMSIS-DSP:包含以定点(分数 q7、q15、q31)和单精度浮点(32 位)实现的 60 多种函数的 DSP 库
  • CMSIS-RTOS API:用于线程控制、资源和时间管理的实时操作系统的标准化编程接口
  • CMSIS-SVD:包含完整微控制器系统(包括外设)的程序员视图的系统视图描述 XML 文件

拿CMSIS-RTOS 举例子:

对于不同的系统 大家都有自己的延时函数名,freeRTOS 自己的延时函数叫 vtaskDelay, RT-thread 的延时函数rt_thread_delay,那用户可以不使用 CMSIS 接口标准,那用户在使用不同的实时系统时就要使用不同的接口,这样容易乱,不好记。CMSIS 就是解决了这一难题,它其实就是统一接口,让大家都使用同一个延时名字 os_Delay,那只要拿到这这标准,这样用户就不用去关心用的是什么系统而选择不同的接口了,只要关心在标准中延时是 osDelay 就可以,屏蔽了操作系统之间的差异。

3.2 层次分析


1.CMSIS-RTOS:屏蔽操作系统的差别,它要调用FreeRTOS的代码

2.FreeRTOS:要去得到Tick,需要先初始化Tick中断:使用HAL库,这就是 Core Peripheral Functions

3.Core Peripheral Functions,就是HAL,会操作寄存器、中断

4.寄存器、中断的操作

  1. 硬件

3.3 ARM 提供的CMSIS-RTOS2 接口

在使用 cubeMX 配置 freeRTOS 时我们发现了有 CMSIS RTOS2 的选项,这里其实就是在选择接口标准为 CMSIS 提供的接口标准

|------|-----------------------|---------------------------------------------|
| | CMSIS接口 | FreeRTOS接口 |
| 延时函数 | osDelay() | vTaskDelay() |
| 创建任务 | osTreadNew() | xTaskCreate() 动态分配 xTaskCreateStatic() 静态分配 |
| 删除任务 | osTreadTerminal() | vTaskDelete() |
| 挂起任务 | osTreadSuspend() | vTaskSuspend() |
| 释放任务 | osTreadResume() | vTaskResume() |

相关推荐
天上的光12 分钟前
机器学习——学习路线
人工智能·学习·机器学习
董莉影15 分钟前
学习嵌入式第十九天
学习
_Kayo_1 小时前
VUE2 学习笔记17 路由
网络·笔记·学习
想睡觉的树1 小时前
windows双系统下ubuntu20.04安装教程
学习
wuxuanok2 小时前
八股——Kafka相关
java·笔记·后端·学习·kafka
不可描述的两脚兽2 小时前
学习笔记《区块链技术与应用》第六天 问答 匿名技术 零知识证明
笔记·学习·区块链
qq_386322692 小时前
华为网路设备学习-26(BGP协议 二)路径属性
学习
焊锡与代码齐飞2 小时前
嵌入式第十八课!!数据结构篇入门及单向链表
c语言·数据结构·学习·算法·链表·排序算法
Stanford_11062 小时前
关于大数据的基础知识(三)——数据安全与合规
大数据·网络·c++·物联网·学习·微信小程序·微信开放平台