代码编织梦想

        此学习笔记基于韦东山的FreeRTOS课程,旨在总结对实时操作系统的学习与应用。

一、前期准备(创建工程)

1.基础配置

        基于FreeRTOS的工程可以通过STM32CubeMX快速创建

在STM32CubeMX中选择对应型号的MCU并创建工程后,在Middleware中选择FreeRTOS,把interface设为CMSIS_V2,下方的参数可以根据自己工程的要求更改,但保持默认也可以成功创建。

2.时钟配置

在Clock Configuration界面将时钟频率设为最大值72即可。

3.任务添加与删除

在STM32CubeMX中,我们可以很方便的添加或删除任务,但为了减少对该软件的依赖,我们仅使用其默认任务(默认任务不可删除)。

4.配置MDK

当对外设配置完成后,就去“Project Manager”中设置工程的名称、存储路径和开发 IDE:

将IDE设为MDK,即可使用keil打开工程。

二、FreeRTOS的基础函数

在Core\Src\main.c 的 main 函数里,初始化了FreeRTOS环境、创建了任务,然后启动 调度器。源码如下:

/* Init scheduler */ 
osKernelInitialize();  /* 初始化FreeRTOS运行环境 */ 
MX_FREERTOS_Init();    
/* 创建任务 */ 
/* Start scheduler */ 
osKernelStart();       
/* 启动调度器 */

对OS初始化和启动调度器之后,我们便可以编写任务

1.创建任务

(1)动态创建任务

BaseType_t xTaskCreate(  
              TaskFunction_t pxTaskCode, // 函数指针,可以简单地认为任务就是一个C函数。 
                                            它稍微特殊一点:永远不退出,或者退出时要调用
                                            "vTaskDelete(NULL)"
              const char * const pcName, // 任务的名字,仅起调试作用,OS内部不使用 
              const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节 
              void * const pvParameters, // 调用任务函数时传入的参数 
              UBaseType_t uxPriority,    // 优先级,优先级范围:0~(configMAX_PRIORITIES – 1) 
                                            数值越小优先级越低, 
                                            如果传入过大的值,xTaskCreate会把它调整为
                                            (configMAX_PRIORITIES – 1)
              TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务 
/*返回值
成功:pdPASS; 
失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足)*/

BaseType_t是处理器效率最高的格式,例如32位系统该格式就占32位空间。

(2)静态创建任务

TaskHandle_t xTaskCreateStatic (  
    TaskFunction_t pxTaskCode,   // 函数指针, 任务函数 
    const char * const pcName,   // 任务的名字 
    const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节,它只指定大小,不分配空间
    void * const pvParameters,   // 调用任务函数时传入的参数 
    UBaseType_t uxPriority,      // 优先级 
    StackType_t * const puxStackBuffer, // 静态分配的栈内存,比如可以传入一个数组, 
                                           它的大小是usStackDepth*4。
    StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务,即句柄
); 

2.删除任务

void vTaskDelete( TaskHandle_t xTaskToDelete );

这个函数的参数便是任务的句柄,怎么删除任务?举个不好的例子:

⚫ 自杀:vTaskDelete(NULL)

⚫ 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句 柄

⚫ 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄

3.改变任务状态

任务一般被分为四种状态::

⚫运行(Runing),即CPU正在运行的程序。

⚫ 就绪状态(Ready),随时可以上CPU运行的程序,只需等待调度器调度。

⚫ 阻塞状态(Blocked),等待某个条件完成或者某个资源的任务,在需求满足前不会占用CPU。

⚫ 暂停状态(Suspended),被主动暂停的任务,只能主动唤醒。

暂停状态有关函数

进入暂停状态:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend 表示要暂停的任务,如果为NULL,表示暂停自己。 要退出暂停状态,只能由别人来操作:

⚫ 别的任务调用:vTaskResume

⚫ 中断程序调用:xTaskResumeFromISR

4.Delay函数

有两个Delay函数:

⚫ vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态

⚫ vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。 这2个函数原型如下:

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */ 
/* pxPreviousWakeTime: 上一次被唤醒的时间 
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement) 
* 单位都是Tick Count 
*/ 
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime, 
const TickType_t xTimeIncrement ); 

⚫ 使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick 中断

⚫ 使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间 至少是n个Tick中断

◼ 退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会

◼ 所以可以使用xTaskDelayUntil来让任务周期性地运行

三、同步与互斥

同步即为让两个及以上的任务有先后顺序的轮流执行,互斥即为无抢占式的使用资源。FreeRTOS提供了多种实现同步与互斥的方法。

1.队列

队列是一种先进先出的顺序结构,可以很好的满足同步的要求

(1)创建队列

①.动态创建
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

参数解析:

uxQueueLength 队列长度,最多能存放多少个数据(item)

uxItemSize 每个数据(item)的大小:以字节为单位

返回值 非0:成功,返回句柄,以后使用句柄来操作队列 NULL:失败,因为内存不足

②.静态创建
QueueHandle_t xQueueCreateStatic( 
                           UBaseType_t uxQueueLength, //队列长度,最多能存放多少个数据(item)
                           UBaseType_t uxItemSize, //每个数据(item)的大小:以字节为单位
                           uint8_t *pucQueueStorageBuffer, //如果uxItemSize非0,                            
                                                             pucQueueStorageBuffer必须指向一                                
                                                             个uint8_t数组,此数组大小至少        
                                                             为"uxQueueLength * uxItemSize" 
                           StaticQueue_t *pxQueueBuffer //必须执行一个StaticQueue_t结构体,        
                                                          用来保存队列的数据结构,类似句柄
                       );

(2)复位队列

/* pxQueue : 复位哪个队列; 
 * 返回值: pdPASS(必定成功) 
 */ 
BaseType_t xQueueReset( QueueHandle_t pxQueue); 

(3)删除队列

void vQueueDelete( QueueHandle_t xQueue ); 
(4)写队列
/* 等同于xQueueSendToBack 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
 */ 
BaseType_t xQueueSend( 
                                QueueHandle_t    xQueue, 
                                const void       *pvItemToQueue, 
                                TickType_t       xTicksToWait 
                            ); 
 
/*  
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait 
百问网 
 138  
 */ 
BaseType_t xQueueSendToBack( 
                                QueueHandle_t    xQueue, 
                                const void       *pvItemToQueue, 
                                TickType_t       xTicksToWait 
                            ); 
 
 
/*  
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞 
 */ 
BaseType_t xQueueSendToBackFromISR( 
                                      QueueHandle_t xQueue, 
                                      const void *pvItemToQueue, 
                                      BaseType_t *pxHigherPriorityTaskWoken 
                                   ); 
 
/*  
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait 
 */ 
BaseType_t xQueueSendToFront( 
                                QueueHandle_t    xQueue, 
                                const void       *pvItemToQueue, 
                                TickType_t       xTicksToWait 
                            ); 
 
/*  
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞 
 */ 
BaseType_t xQueueSendToFrontFromISR( 
                                      QueueHandle_t xQueue, 
                                      const void *pvItemToQueue, 
                                      BaseType_t *pxHigherPriorityTaskWoken 
                                   );

参数说明:

xQueue              :队列句柄,要写哪个队列

pvItemToQueue :数据指针,这个数据的值会被复制进队列, 复制多大的数据?在创建队列时已经指定了数据大小

xTicksToWait      :如果队列满则无法写入新数据,可以让任务进入阻塞状 态,xTicksToWait表示阻塞的最大时间(Tick Count)。 如果被设为0,无法写入数据时函数会立刻返回; 如果被设为portMAX_DELAY,则会一直阻塞直到有空间可写 

pxHigherPriorityTaskWoken:输出参数,用于标记是否需要触发任务调度。

(5)读队列

BaseType_t xQueueReceive( QueueHandle_t xQueue, 
                          void * const pvBuffer, 
                          TickType_t xTicksToWait ); 
 
BaseType_t xQueueReceiveFromISR( 
                                    QueueHandle_t    xQueue, 
                                    void             *pvBuffer, 
                                    BaseType_t       *pxTaskWoken 
                                ); 

使用xQueueReceive()函数读队列,读到一个数据后,队列中该数据会被移除。

(6)查询队列

可以查询队列中有多少个数据、有多少空余空间。函数原型如下:

/* 
 * 返回队列中可用数据的个数 
 */ 
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue ); 
 
/* 
 * 返回队列中可用空间的个数 
 */ 
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue ); 

2.信号量

相较于队列,信号量显得更为简单,它只需要一个数值来表示状态值,信号量可以分为两种:计数最大值为一的,称为二进制信号量;最大值大于一的,称为计数型信号量。

信号量即为当前可用的某种资源的数量,所以可以有两种操作:生产者生产资源,将信号量增加;消费者使用资源,将信号量减少。当信号量为0时,消费者就会产生阻塞,而当消费者使用完资源释放时,也会将信号量加一。

(1)创建信号量

/* 创建一个二进制信号量,返回它的句柄。 
 * 此函数内部会分配信号量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateBinary( void ); 
 
/* 创建一个二进制信号量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuf
 fer );
/* 创建一个计数型信号量,返回它的句柄。 
 * 此函数内部会分配信号量结构体  
 * uxMaxCount: 最大计数值 
 * uxInitialCount: 初始计数值 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t 
uxInitialCount);
/* 创建一个计数型信号量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * uxMaxCount: 最大计数值 
 * uxInitialCount: 初始计数值 
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,  
                                                 UBaseType_t uxInitialCount,  
                                                 StaticSemaphore_t *pxSemaphore
 Buffer );

(2)删除信号量

/* 
 * xSemaphore: 信号量句柄,你要删除哪个信号量 
 */ 
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

(3)增加/减少信号量

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );//信号量增加
BaseType_t xSemaphoreGiveFromISR( 
                        SemaphoreHandle_t xSemaphore, 
                        BaseType_t *pxHigherPriorityTaskWoken 
                    );                                    //中断中使用
BaseType_t xSemaphoreTake( 
                   SemaphoreHandle_t xSemaphore, 
                   TickType_t xTicksToWait 
               );                                          //减少信号量
BaseType_t xSemaphoreTakeFromISR( 
                        SemaphoreHandle_t xSemaphore, 
                        BaseType_t *pxHigherPriorityTaskWoken 
                    ); 

3.互斥量

互斥量可以看做是一种特殊的信号量,它只有0,1两个值,但与二进制信号量不同的是互斥量的初始值为一,互斥量的释放只能由占有它资源的任务进行,即谁占用,谁释放。虽然FreeRTOS并没有实现谁占用谁释放,但是编程时应注意这点。

(1)创建互斥量

注意,使用互斥量时应在件FreeRTOSConfig.h有以下定义

##define configUSE_MUTEXES 1

/* 创建一个互斥量,返回它的句柄。 
 * 此函数内部会分配互斥量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateMutex( void ); 
 
/* 创建一个互斥量,返回它的句柄。 
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的
指针 
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer ); 

(2)互斥量的其他操作

要注意的是,互斥量不能在ISR中使用。 各类操作函数,比如删除、give/take,跟一般是信号量是一样的。

/* 
 * xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量 
 */ 
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); 
 
/* 释放 */ 
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore ); 
 
/* 释放(ISR版本) */ 
BaseType_t xSemaphoreGiveFromISR( 
                       SemaphoreHandle_t xSemaphore, 
                       BaseType_t *pxHigherPriorityTaskWoken 
                   ); 
 
/* 获得 */ 
BaseType_t xSemaphoreTake( 
                   SemaphoreHandle_t xSemaphore, 
                   TickType_t xTicksToWait 
               ); 
/* 获得(ISR版本) */ 
xSemaphoreGiveFromISR( 
                       SemaphoreHandle_t xSemaphore, 
                       BaseType_t *pxHigherPriorityTaskWoken 
                   );

4.死锁问题的解决方法

死锁即为两个任务互相请求资源导致同时阻塞,举个例子:

我们只招有工作经验的人!我没有工作经验怎么办?那你就去找工作啊!

对于死锁,可以有以下解决方法

递归锁

⚫ 任务A获得递归锁M后,它还可以多次去获得这个锁

⚫ "take"了N次,要"give"N次,这个锁才会被释放

/* 创建一个递归锁,返回它的句柄。 
 * 此函数内部会分配互斥量结构体  
 * 返回值: 返回句柄,非NULL表示成功 
 */ 
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void ); 
 
 
/* 释放 */ 
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore ); 
 
/* 获得 */ 
BaseType_t xSemaphoreTakeRecursive( 
                   SemaphoreHandle_t xSemaphore, 
                   TickType_t xTicksToWait 
               );

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_64432298/article/details/141035200

freertos学习笔记——链表-爱代码爱编程

主机环境:Windows 开发环境:MDK4.7.2 FreeRTOS版本:FreeRTOS8.1.2 目标环境:STM32F030C8T6 最近打算学习一下FreeRTOS的知识,在此作下笔记以便帮助自己理解,FreeRTOS与uCos-ii不同,在查询最高优先级时不是采用查表法来得知,而是通过链表来获取,因此链表在FreeRTOS中是比较重要的

freertos自学笔记_yplwrt的博客-爱代码爱编程

FreeRTOS自学笔记(一) 本文档参考FreeRTOS V10.1.1代码。 (1)FreeRTOS的任务 在学习FreeRTOS中第一个绕不开的便是任务,让我们来逐步揭开任务的神秘面纱。 【1】不得不说的结构

【freertos】从0到1写内核(总结)_源自1994的博客-爱代码爱编程

《FreeRTOS 内核实现与应用开发实战—基于STM32》 系统 模型事件响应事件处理特点轮询主程序主程序(无限大循环)轮询响应事件,轮询处理事件前后台中断主程序(无限大循环)实时响应事件,轮询处理事件多任务中断任务实

FreeRTOS笔记(一)-爱代码爱编程

FreeRTOS笔记(一) 文章目录 FreeRTOS笔记(一)裸机系统与多任务系统裸机系统轮询系统前后台系统多任务系统列表和列表项(list & list item)单向链表双向链表FreeRTOS中链表的实现一、实现链表节点1 定义链表节点数据结构2 链表节点初始化1 定义链表根节点数据结构2 链表根节点初始化3 将节点插入到链表的尾

xsemaphoretake返回_干货 | FreeRTOS学习笔记——中断与任务切换-爱代码爱编程

原标题:干货 | FreeRTOS学习笔记——中断与任务切换 EEWorld 在FreeRTOS具备了任务的内存资源——堆栈管理机制,能根据任务状态和优先级进行CPU执行的上下文切换,并提供了任务间通信渠道以实现必要的任务同步和互斥之后,多个任务可以协同起来工作了。不过,既然名称叫做 Real-Time (实时)的操作系统,还需要能对外部(硬件)事

从零开始写FreeRTOS-爱代码爱编程

参考资料 野火的《FreeRTOS内核实现与应用开发实战指南》 数据类型 FreeRTOS对标准C的数据类型进行了重定义。给它们取了新的名字,比如portCHAR就是对char的重定义,port为接口的意思。在FreeRTOS中int型从不使用。是有short和long型。在Cortex—M内核CPU中,short为16位,long为32位。 Fre

(二)FreeRtos学习笔记——FreeRtos和裸机系统的区别-爱代码爱编程

1.裸机系统         裸机系统包括轮询系统以及前后台系统。 (1)轮询系统         关于轮询系统我们可以用以下的代码诠释: #include <xxx.h> int main() { /*初始化的相关API*/ xxx xxx /*主循环*/ while(1) {

freertos 笔记_慕容静羽的博客-爱代码爱编程

1. Support Port FreeRTOS 当前已经支持20+编译器和 30+ 芯片结构。 `Port`: FreeRTOS 支持的编译器和处理器组合称之为一个 port。 2. File Structure `FreeRTOSConfig.h`: 用户配置头文件 `FreeRTOS.h`: 对外头文件头文件 FreeRTOS │ │ │

freertos学习简易笔记-爱代码爱编程

​第1章 FreeRTOS引入及堆栈 1.1 FreeRTOS学习三阶段 1)、理解RTOS总原理,会移植官方Demo,会使用。 2)、知道内部机制,源码还没看! 3)、知道内部实现,能看懂源码!并能轻松移植任何单片机。 1.2 RTOS操作系统 与 裸机开发(前后系统)区别 RTOS:根据任务需要 人为的为任务切换CPU资源,有的任务可能不能

freertos学习笔记(创建精简freertos)_freertos 最简核心文件-爱代码爱编程

1、去FreeRTOS官网下载第一个选项的项目(里面带有demo) 2、删除多余的文件          3、按照目录格式进行删减 其他的项目均可以删除掉,只保留一个f103的文件 可以注意下面的目录,可以参照下面的连接。(12条消息) 韦东山freeRTOS系列教程之【第一章】FreeRTOS概述与体验_韦东山的博客-CSDN博客_fr

freertos 学习笔记(自用)_freertos实时操作系统笔记-爱代码爱编程

前言: 本文章用于记录学习FreeRTOS(韦老师)期间的笔记以及一些个人理解。(带完善) 一、学习内容: 1、裸机及操作系统 2、FreeRTOS功能及其实现原理 3、线程通信 4、FreeRTOS移植 1、裸机及操作系统: 裸机程序启动过程:(STM32) 1 CPU复位启动:         1、从地址0x0000 000

freertos学习笔记_freertos笔记-爱代码爱编程

FreeRTOS学习笔记 抢占式、合作式、时间片调度任务,消息队列,信号量,软件定时器。任务与任务,任务与中断之间可以使用任务通知、消息队列、二值信号量、数值型信号量、递归互斥信号量和互斥信号量进行通信和同步。堆栈溢出检测

入门freertos笔记整理-爱代码爱编程

视频名:视频专辑 - FreeRTOS实时操作系统(十年功力,力求全面,通俗易懂) https://www.bilibili.com/video/BV18R4y147DB/?spm_id_from=333.999.0.0&