FreeRTOS学习笔记——从0到1

        此学习笔记基于韦东山的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 
               );

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值