freertos学习笔记——从0到1_freertos笔记-爱代码爱编程
此学习笔记基于韦东山的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
);