一、在线下载 FreeRTOS 内核

图 1

图 2


二、使用STM32CubeMX配置FreeRTOS实时操作系统

1、安装实时操作系统

图3

图4

图5

2、CubeMX配置实时操作系统

图6

图7

图8

图9

三、FreeRTOS 实时操作系统介绍

FreeRTOS 实时操作系统是一种轻量型实时操作系统,实现快速响应

FreeRTOS 实时操作系统是 RTOS 实时操作系统下的一个版本(子集)


RTOS 实时操作系统具有很多版本:FreeRTOS、RT-Thread、Thread-X……

2.1 裸机开发和系统开发的区别

裸机开发:不使用操作系统,所有驱动代码按照 main.c 中的编写顺序按顺序执行代码。

系统开发:使用操作系统,所有驱动代码支持多进程 / 多线程的并发处理机制,可以实现快速响应

2.2 多进程和多线程的区别

  1. 资源量:进程是资源分配的最小单位,线程是任务调度的最小单位,1 个线程 = 1 个任务;
  2. 安全性:进程的用户空间相互独立(IPC 机制),线程同享同一进程下的资源(同步互斥);
  3. 效率:多线程的并发处理的效率更高(进程的上下文切换)。

四、FreeRTOS 实时操作系统的特点

  • 小巧与灵活性:FreeRTOS 非常小巧,适合在资源有限的微控制器中运行,但其应用并不仅限于微控制器。开发者可以根据实际应用需求进行定制和配置,选择所需的功能模块。
  • 可移植性:FreeRTOS 支持多种处理器架构,并能在不同的硬件平台上运行,这为开发者提供了更大的硬件选择灵活性。
  • 实时性:FreeRTOS 提供严格的任务调度机制,确保任务按照预定的优先级和时间约束执行,使其非常适合对时间要求较高的应用场景,如工业控制和汽车电子等领域。
  • 丰富的功能:包括任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等。
  • 开源与免费:FreeRTOS 是一个开源项目,其源代码可以免费获取和使用,这有助于促进其在开发者社区中的普及和发展。
  • FreeRTOS 的应用领域非常广泛,包括但不限于工业控制、汽车电子、智能家居、医疗设备、航空航天等需要实时响应和稳定运行的领域。

五、FreeRTOS 实时操作系统的任务调度机制

linux 操作系统的任务调度机制:默认使用时间片轮询机制,也可以使用抢占式任务调度机制;

FreeRTOS 操作系统的任务调度机制:默认使用抢占式任务调度机制,也可以使用时间片轮询机制。

抢占式任务调度机制:给每个任务/线程分配对应的优先级等级,优先级高的先执行,优先级低的后执行。

注意

        优先级等级的数字越小,优先级越高

        优先级相同时,使用时间片轮询机制

六、任务的状态

  • 就绪态:任务的资源分配成功,等待被运行。
  • 运行态:被分配资源的任务使用系统的时间片,成功开始运行。
  • 阻塞态:任务中存在耗时、延时(Delay 函数、超时检测等)时,任务处于阻塞状态;处于阻塞状态的任务,可以被别的任务抢占系统的资源,从而运行。
  • 挂起态:在任务 1 中使用挂起函数挂起任务 2 ,使任务 2 处于挂起状态,系统不会调用任务 2;需要在别的任务/任务 1 中使用解除挂起函数,解除任务 2 的挂起,使任务 2 处于就绪态。

图 3 任务的状态

七、CubeMX 中配置 FreeRTOS

图 4 设置 FreeRTOS

图 5 heap_1 - heap_5 的选择

图 6 设置 USART1

图 7 设置 Debug 下载接口

图 8 配置 X-CUBE-FREERTOS

将如图所示的 CMSIS RTOS2 选项勾上,在下面进行任务的配置。

图 9 创建任务的相关参数

图 10 新任务创建参数

       一个任务的参数有:任务名、当前优先级、任务大小、任务的入口函数等。

图 11 FreeRTOS 代码生成出现 RTOS 相关警告

如果出现如图 11 所示的 RTOS 相关警告,解决方法如下图:

图 12 警告解决方法

八、CMSIS_RTOS2和FreeRTOS提供的库函数

  os开头的函数:是由ARM定义的所有操作系统都支持的CMSIS_RTOS2通用接口(相当于标准IO)
  x开头的函数:FreeRTOS内核自身提供的函数(相当于文件IO)

1、CMSIS_RTOS2和FreeRTOS的区别

CMSIS_RTOS2是由ARM公司定义的,专门作用于Cortex-M内核的操作系统的通用API标准接口,优点是可移植性强

FreeRTOS是RTOS操作系统中的一个子集,一个具体的操作系统内核

2、CMSIS_RTOS2提供的函数

在cmsis_os2.c这个文件中的所有函数都是由CMSIS_RTOS2所提供的API函数接口
这些API接口在所有Cortex-M内核的实时操作系统中都可以使用
由CMSIS_RTOS2所提供的API接口本质上是由FreeRTOS所提供的API接口包装而成

3、FreeRTOS提供的函数

九、创建线程

1、静态创建线程分配任务资源

1)CubeMX创建线程

2)CubeMX初始化的代码

(1)主函数

(2)定义结构体并填充

(3)定义任务入口函数(线程执行函数)

(4)向内核申请线程资源

2、动态创建线程(手动创建线程)

1)osThreadNew(新建线程函数)

osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr)
功能:
    CMSIS_RTOS2通用接口下提供的用于创建任务的函数
参数:
    func:当前创建的任务对应的入口函数名(入口函数地址)
    argument:给当前创建的任务对应的入口函数的传参
    attr:当前创建的任务的信息结构体
返回值:
    返回线程标识符,线程对象,创建失败返回NULL
 
// 线程/任务的信息结构体,他需要被传输给内核进行识别
typedef struct {
  const char                   *name;       // 当前被创建的任务的名字,内核通过这个名字识别他
  uint32_t                 attr_bits;       // 当前被创建的任务的特殊属性位
  void                      *cb_mem;        // 当前被创建的任务需要创建在堆区的哪片地址上
  uint32_t                   cb_size;       // 当前被创建的任务需要创建在堆区的空间大小
  void                   *stack_mem;        // 当前被创建的任务需要创建在栈区的哪片地址上
  uint32_t                stack_size;       // 当前被创建的任务需要创建在栈区的空间大小
  osPriority_t              priority;       // 当前被创建的任务的优先级等级
  TZ_ModuleId_t            tz_module;       // 使用MON模式时,受信模块的属性
  uint32_t                  reserved;       // 保留信息
} osThreadAttr_t;
 

2)定义线程对象和参数结构体

3)定义任务的入口函数

4)初始化新建线程向内核申请资源

十、任务/线程间的同步互斥

由于FreeRTOS操作系统中提供的任务的优先等级个数有限
如果任务很多,数量超过优先级个数,就会被操作系统按照任务调取机制随机分配
此时可以引入同步互斥,使相同优先级的任务按照规定的顺序执行

1、二值信号量

信号量本质就是一个数字,一个计数单位,用于记录空间资源的个数
当信号量 > 0 时,代表存在空闲资源,可以申请空闲资源使用
当信号量 = 0 时,代表没有空闲资源,无法申请空闲资源使用
 
二值信号量(二进制信号量),就是信号量的值只有 0 和 1
 
信号量的核心操作(PV操作)
P操作:申请信号量(信号量 -1)
V操作:释放信号量(信号量 +1)

1)xSemaphoreCreateBinary(创建二值信号量)

SemaphoreHandle_t xSemaphoreCreateBinary(void);
功能:
    FreeRTOS内核提供的用于创建二值信号量的函数(初始信号量的值为0,也就是无法申请到信号量)
参数:
    无
返回值:
    函数执行成功,二值信号量创建成功,返回创建成功的二值信号量对象
    函数执行失败,返回NULL
osSemaphoreNew函数也能实现创建信号量的效果

2)xSemaphoreTake(申请信号量)

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
功能:
    FreeRTOS内核提供的用于申请信号量的函数(不管什么类型的信号量都可以申请,信号量-1)
参数:
    xSemaphore:需要申请的信号量对象
    xBlockTime:超时检测时间,当前函数的最大阻塞时间
返回值:
    函数执行成功,返回pdTRUE(1)
    函数执行失败,返回pdFALSE(0)
osSemaphoreAcquire函数也能实现申请信号量的效果

3)xSemaporeGive(释放信号量)

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
功能:
    FreeRTOS内核提供的用于释放信号量的函数(不管什么类型的信号量都可以释放,信号量+1)
参数:
    xSemaphore:需要释放的信号量对象
返回值:
    函数执行成功,释放信号量成功,返回pdTRUE(1)
    函数执行失败,释放信号量失败,返回pdFALSE(0)
osSemaphoreRelease函数也能实现释放信号量的效果

4)示例代码

 使用二进制信号量,实现默认任务、任务1、任务2、按顺序执行

(1)定义信号量对象

(2)创建二值信号量

(3)使用二值信号量实现同步

(4)程序现象

2、计数型信号量

1)xSemaporeCounting(创建计数型信号量)

SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
功能:
    FreeRTOS内核提供的用于创建计数型信号量的函数
    计数型信号量:就是信号量的个数有很多
参数:
    uxMaxCount:创建出来的计数型信号量个数的最大值
    uxInitialCount:创建出来的计数型信号量初始个数
返回值:
    函数执行成功,计数型信号量创建成功,函数返回创建成功的计数型信号量对象
    函数执行失败,返回NULL

2)xSemaporeTake(申请信号量)

BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime)
功能:
    FreeRTOS内核提供的用于申请信号量的函数(不管什么类型的信号量都可以申请,信号量-1)
参数:
    xSemaphore:需要申请的信号量对象
    xBlockTime:超时检测时间,当前函数的最大阻塞时间
返回值:
    函数执行成功,返回pdTRUE(1)
    函数执行失败,返回pdFALSE(0)
osSemaphoreAcquire函数也能实现申请信号量的效果

3)xSemaporeGive(释放信号量)

BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore)
功能:
    FreeRTOS内核提供的用于释放信号量的函数(不管什么类型的信号量都可以释放,信号量+1)
参数:
    xSemaphore:需要释放的信号量对象
返回值:
    函数执行成功,释放信号量成功,返回pdTRUE(1)
    函数执行失败,释放信号量失败,返回pdFALSE(0)
osSemaphoreRelease函数也能实现释放信号量的效果

4)示例代码

使用二进制信号量和计数信号量,实现任务1、任务2,交替执行5次,再执行默认任务

(1)定义信号量对象

(2)创建信号量

(3)使用信号量实现操作

(4)程序现象

3、事件组(事件标志位)

事件组又称为事件标志位
事件组就是多个事件标志位的集合
 
事件组可以理解为一个寄存器,这个寄存器的每一位都代表一个事件
每一位的值都代表一个事件是否执行成功
如果 某一位的值 = 0 ,则代表这一位对应的事件没有执行
如果 某一位的值 = 1 ,则代表这一位对应的之间执行成功

1)事件组的使用情景

通过多个任务管理一个任务
1. 当所有任务均执行成功后,执行主任务
2. 当诸多任务中有一个执行成功后,执行主任务

2)(创建事件组)

osEventFlagsId_t osEventFlagsNew (const osEventFlagsAttr_t *attr);
功能:
    适用于CMSIS_RTOS V2通用接口下用于创建事件组的函数
参数:
    attr:需要创建出来的事件组的信息结构体(可以填充NULL默认属性信息,由FreeRTOS自动分配)
          如果自己填充填充name即可,有特殊需求可以全部自行填充
返回值:
    函数执行成功,代表事件组创建成功,返回创建成功的事件组对象
    函数执行失败,返回NULL
 
// 参数attr的结构体变量
// Attributes structure for event flags.
typedef struct {
  const char                   *name;   ///< name of the event flags
  uint32_t                 attr_bits;   ///< attribute bits
  void                      *cb_mem;    ///< memory for control block
  uint32_t                   cb_size;   ///< size of provided memory for control block
} osEventFlagsAttr_t;

3)(将标志位置一)

uint32_t osEventFlagsSet (osEventFlagsId_t ef_id, uint32_t flags);
功能:
    适用于CMSIS_RTOS V2通用接口下用于对事件组中某一位/某些位置1的函数
    置1:代表当前事件执行成功,也就是当前任务执行成功
参数:
    ef_id:需要操作的事件组对象
    flags:需要被置1的事件标志位(需要被置1的对应任务的事件标志位)
返回值:
    函数执行成功,返回osOK(0)
    函数执行失败,返回错误码
 
// os函数使用的返回值
typedef enum {
  osOK                      =  0,         ///< Operation completed successfully.
  osError                   = -1,         ///< Unspecified RTOS error: run-time error but no other error message fits.
  osErrorTimeout            = -2,         ///< Operation not completed within the timeout period.
  osErrorResource           = -3,         ///< Resource not available.
  osErrorParameter          = -4,         ///< Parameter error.
  osErrorNoMemory           = -5,         ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
  osErrorISR                = -6,         ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
  osStatusReserved          = 0x7FFFFFFF  ///< Prevents enum down-size compiler optimization.
} osStatus_t;

4)(阻塞等待标志位置一)

uint32_t osEventFlagsWait (osEventFlagsId_t ef_id,
                           uint32_t flags,
                           uint32_t options,
                           uint32_t timeout);
功能:
    适用于CMSIS_RTOS V2通用接口下用于阻塞等待对应事件标志位被置1的函数
参数:
    ef_id:需要操作的事件组对象
    flags:需要等待被置1的事件标志位
    options:
            osFlagsWaitAny:等待第二个参数flags中任意一个标志位被置1
            osFlagsWaitAll:等待第二个参数flags中所有标志位被置1
    timeout:超时检测时间,当前函数的最大阻塞时间
            // Timeout value.
            #define osWaitForever         0xFFFFFFFFU ///< Wait forever timeout value.
返回值:
    函数执行成功,返回osOK(0)
    函数执行失败,返回错误码
 
//选择阻塞的状态
// Flags options (\ref osThreadFlagsWait and \ref osEventFlagsWait).
#define osFlagsWaitAny        0x00000000U //等待其中1位被置1
#define osFlagsWaitAll        0x00000001U //等待所有位被置1
#define osFlagsNoClear        0x00000002U //不清除那些已经被设置为等待的标志位
 
//当前函数的错误码
// Flags errors (returned by osThreadFlagsXxxx and osEventFlagsXxxx).
#define osFlagsError          0x80000000U ///< Error indicator.
#define osFlagsErrorUnknown   0xFFFFFFFFU ///< osError (-1).
#define osFlagsErrorTimeout   0xFFFFFFFEU ///< osErrorTimeout (-2).
#define osFlagsErrorResource  0xFFFFFFFDU ///< osErrorResource (-3).
#define osFlagsErrorParameter 0xFFFFFFFCU ///< osErrorParameter (-4).
#define osFlagsErrorISR       0xFFFFFFFAU ///< osErrorISR (-6).
 
 
任务1:对应事件组中的第0位        0x1 << 0    
任务2:对应事件组中的第1位        0x1 << 1
任务3:等待任务1和任务2都被执行完,任务3才能执行
       也就是任务1的事件标志位被置1并且任务2的事件标志位也要被置1,任务3才能执行
       flags = (0x1 << 0)| (0x1 << 1)

5)(将标志位清零)

uint32_t osEventFlagsClear (osEventFlagsId_t ef_id, uint32_t flags)
功能:
    适用于CMSIS_RTOS V2通用接口下用于清除对应标志位的函数
参数:
    ef_id:需要操作的事件组对象
    flags:需要清除的事件标志位
返回值:
    函数执行成功,返回osOK
    函数执行失败,返回错误码

6)示例代码

 实现所有任务都完成后,执行默认任务

(1)定义事件组对象

(2)定义宏体代表事件组某一位

(3)创建事件组

(4)操作事件标志位完成功能

(5)程序现象

7)示例代码

任务1、任务2、任务3中随便一个任务执行成功,都能让默认任务执行

4、消息队列

消息队列:
    1.默认使用FIFO(先进先出,队列)
    2.可以使用优先级划分消息处理等级(优先级高的消息先被处理,优先级低的消息后被处理)
    3.相同优先级下,遵循FIFO规则,先进先出
    4.消息队列中的消息是一次性的,读取后就不存在于消息队列中了

1)(创建消息队列)

osMessageQueueId_t osMessageQueueNew (uint32_t msg_count, uint32_t msg_size, const osMessageQueueAttr_t *attr) 
功能:
    适用于CMSIS_RTOS V2通用接口用于创建一个消息队列的函数
参数:
    msg_count:当前创建的消息队列支持的消息最大个数
    msg_size:当前创建的消息队列中的每个消息的大小,单位为字节
    attr:当前创建的消息队列的信息结构体,需要传递给内核(NULL默认信息)
返回值:
    函数执行成功,代表消息队列创建成功,返回创建成功的消息队列对象
    函数执行失败,返回NULL

2)(向消息队列写入数据)

osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout) 
功能:
    适用于CMSIS_RTOS V2通用接口用于向消息队列中写入数据的函数
参数:
    mq_id:需要操作的消息队列对象
    msg_ptr:需要写入到消息队列中的消息
    msg_prio:当前写入到消息队列中的消息的优先级等级
    timeout:超时检测时间,也就是当前函数的最大阻塞时间
返回值:
    函数执行成功,返回osOK(0)
    函数执行失败,返回错误码

3)(从消息队列读取数据)

osStatus_t osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout) 
功能:
    适用于CMSIS_RTOS V2通用接口用于从消息队列中读取数据的函数
参数:
    mq_id:需要操作的消息队列对象
    msg_ptr:从消息队列中读取的消息存储的位置
    msg_prio:需要读取的消息队列中消息的优先级等级
    timeout:超时检测时间,也就是当前函数的最大阻塞时间
返回值:
    函数执行成功,返回osOK(0)
    函数执行失败,返回错误码
 
消息队列的消息优先级等级(0-9级,数字越大,优先级等级越高)
0:普通日志消息
9:紧急消息

4)示例代码

(1)定义消息队列对象

(2)创建消息队列

(3)操作消息队列完成功能

(4)程序现象

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐