FreertosHAL库笔记
堆和栈
堆:一块内存空间,用于存储程序运行时动态申请的内存空间。在堆上分配内存可以根据程序的需要灵活地申请和释放不同大小的内存块。可用pvProMalllloc()和vPortFree()函数来开辟和释放
栈:也是一块内存空间,主要用于函数调用、局部变量、多任务系统里保存现场
创建和删除任务
创建任务
1.动态创建
BaseType_t xTaskCreate(
TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务
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, // 静态分配的栈,就是一个buffer
StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);
可以使用多个函数创建多个任务也可以用一个函数创建多个任务,只不创建时要输入不同参数到该函数
删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete );
Tick
对于同优先级的任务,它们“轮流”执行
FreeRTOS中,使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms
发生一次时钟中断
假设 t1、t2、t3 发生时钟中断 (两次中断之间的时间被称为时间片(time slice、tick period))
时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100, 那么时间片长度就是 10ms
vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms
// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms 注意,基于Tick实现的延时并不精确,比如vT
注:当任务使用vTaskDelay();则在等待的时间里该任务处于阻塞状态,会被放到管理阻塞任务的链表里这时任务调度器在管理就绪态的链表里选择任务时就不会选到该任务,当等待的时间到了时该任务又会被放进就绪态任务链表,当每个函数都阻塞态时空闲任务就会出来处理那些“自杀”的任务,释放他们的栈
两个delay函数
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 );
同步和互斥通信
同步:
指多个线程或进程在执行过程中,按照一定的顺序和规则进行协调,以确保它们之间的操作能够正确地相互配合。例如,一个线程需要等待另一个线程完成某个任务后才能继续执行,这就需要通过同步机制来实现。
互斥:
是指在同一时刻,只允许一个线程或进程访问某个共享资源,以防止多个线程或进程同时访问导致数据不一致或其他错误。例如,多个线程同时对一个全局变量进行写操作时,就需要使用互斥机制来保证每次只有一个线程能够成功写入
队列和队列集
队列
功能
• 任务间通信:允许不同任务之间传递数据。例如,一个任务可以将数据发送到队列,另一个任务从队列中获取数据,实现任务之间的信息交互。
• 任务同步:可以用于任务之间的同步。当一个任务向队列发送数据时,其他等待数据的任务可以被唤醒,从而实现任务之间的协调执行。
特点
• 灵活的数据类型:可以存储多种数据类型,包括基本数据类型、结构体、指针等。用户可以根据实际需求定义队列中数据的类型。
• 阻塞机制:具有阻塞特性。当任务尝试从空队列中读取数据时,任务会被阻塞,直到队列中有数据可用;当任务尝试向已满队列中写入数据时,任务也会被阻塞,直到队列有空间可用。
队列函数
1.动态创建
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
2.静态创建
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer
);
参数 | 说明 |
uxQueueLength
| 队列长度,最多能存放多少个数据(item) |
uxItemSize
|
每个数据(item)的大小:以字节为单位
|
pucQueueStorageBuffer
| 如果 uxItemSize 非 0,pucQueueStorageBuffer 必须指向一个 uint8_t 数组,此数组大小至少为"uxQueueLength * uxItemSize" |
pxQueueBuffer
|
必须执行一个 StaticQueue_t 结构体,用来保存队列的数据结构
|
返回值
|
非 0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为 pxQueueBuffer 为 NULL
|
/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
4.删除队列
void vQueueDelete( QueueHandle_t xQueue );
参数为队列句柄
5.写队列
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
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,则会一直阻塞直到有空间可写
|
返回值
|
pdPASS:数据成功写入了队列
errQUEUE_FULL:写入失败,因为队列满了
|
6.读队列
//在任务中读
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
//在中断中读
BaseType_t xQueueReceiveFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken
);
参数 | 说明 |
xQueue
|
队列句柄,要读哪个队列
|
pvBuffer
|
bufer 指针,队列的数据会被复制到这个 buffer
复制多大的数据?在创建队列时已经指定了数据大小
|
xTicksToWait
|
如果队列空则无法读出数据,可以让任务进入阻塞状态,
xTicksToWait 表示阻塞的最大时间(Tick Count)
如果被设为 0,无法读出数据时函数会立刻返回;
如果被设为 portMAX_DELAY,则会一直阻塞直到有数据可写
|
返回值
|
pdPASS:从队列读出数据入
errQUEUE_EMPTY:读取失败,因为队列空了。
|
7.查询
查询队列有多少数据、多少空余空间
/*
* 返回队列中可用数据的个数
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回队列中可用空间的个数
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
8.覆盖/偷看
当队列长度为 1 时,可以使用 xQueueOverwrite()或 xQueueOverwriteFromISR()
来覆盖数据。
注意:队列长度必须为 1。当队列满时,这些函数会覆盖里面的数据,这也以为着这些
函数不会被阻塞
/* 覆盖队列
* xQueue: 写哪个队列
* pvItemToQueue: 数据地址
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueueOverwrite(
QueueHandle_t xQueue,
const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(
QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
如果想让队列中的数据供多方读取,也就是说读取时不要移除数据,要留给后来人。那
么可以使用"窥视",也就是xQueuePeek()或xQueuePeekFromISR()。这些函数会从队列中
复制出数据,但是不移除数据。这也意味着,如果队列中没有数据,那么"偷看"时会导致阻
塞;一旦队列中有数据,以后每次"偷看"都会成功。
/* 偷看队列
* xQueue: 偷看哪个队列
* pvItemToQueue: 数据地址, 用来保存复制出来的数据
* xTicksToWait: 没有数据的话阻塞一会
* 返回值: pdTRUE表示成功, pdFALSE表示失败
*/
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
);
BaseType_t xQueuePeekFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
);
队列集
队列集(Queue Set)是一种用于管理多个队列的机制,本质也是一个队列,只不过里面存放的是各队列的句柄,它允许任务同时等待多个队列中的数据,多队列管理:可以将多个队列组合在一起,通过一个队列集句柄进行统一管理。任务可以在一个操作中等待多个队列中的任意一个有数据到来,而无需分别对每个队列进行轮询或阻塞等待。
1.创建队列集
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )
2.把队列加入队列集
BaseType_t xQueueAddToSet(
QueueSetMemberHandle_t xQueueOrSemaphore,
QueueSetHandle_t xQueueSet );
3.读取队列集
QueueSetMemberHandle_t xQueueSelectFromSet(
QueueSetHandle_t xQueueSet,
TickType_t const xTicksToWait );
信号量
信号量是一种用于任务间同步和互斥的机制,它基于计数原理来控制对共享资源的访问
原理:维护一个计数值,当任务获取信号量时,计数值减1;当任务释放信号量时,计数值加1。计数值表示可用资源的数量
和队列的区别
两种信号量
二进制信号量:是一种特殊的计数信号量,计数值只能是0或1。它通常用于实现互斥访问,相当于一个简单的锁
动态创建:
xSemaphoreCreateBinary(void)
参数:无
返回值:成功创建则返回信号量句柄,否则返回NULL
静态创建:
xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer)
参数:pxSemaphoreBuffer是一个指向StaticSemaphore_t类型结构体的指针,用于存储信号量的相关信息,由用户自行分配内存。
返回值:成功创建则返回信号量句柄,否则返回NULL。
计数型信号量:被创建时初始值最大计数值和初始计数值可以设定
动态创建:
xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount)
参数:uxMaxCount表示信号量的最大计数值,uxInitialCount表示信号量的初始计数值,且uxInitialCount不能大于uxMaxCount
返回值:成功创建则返回信号量句柄,否则返回NULL
静态创建:
xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer)
参数:uxMaxCount和uxInitialCount含义与动态创建函数中的相同。pxSemaphoreBuffer是一个指向StaticSemaphore_t类型结构体的指针,用于存储信号量的相关信息,由用户自行分配内存
返回值:成功创建则返回信号量句柄,否则返回NULL
删除信号量:
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
参数:信号量句柄
give(释放)/take(获取)信号量:

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
参数 | 说明 |
xSemaphore
|
信号量句柄,释放哪个信号量
|
pxHigherPriorityTaskWoken
| 如果释放信号量导致更高优先级的任务变为了绪态,则*pxHigherPriorityTaskWoken = pdTRUE |
返回值
|
pdTRUE 表示成功,
如果二进制信号量的计数值已经是 1,再次调用此
函数则返回失败;
如果计数型信号量的计数值已经是最大值,再次调
用此函数则返回失败
|
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
互斥量
/* 创建一个互斥量,返回它的句柄。
* 此函数内部会分配互斥量结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 创建一个互斥量,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个 StaticSemaphore_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
事件组
定义:事件组是一组相关事件的集合,它可以同时管理多个事件。每个事件在事件组中都有一个对应的位,通过对事件组的操作,可以同时设置、清除或查询多个事件的状态
创建和删除事件组
/* 创建一个事件组,返回它的句柄
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非 NULL 表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 创建一个事件组,返回它的句柄
* 此函数无需动态分配内存,所以需要先有一个 StaticEventGroup_t 结构体,并传入它的
指针
* 返回值: 返回句柄,非 NULL 表示成功
*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer
);
/*
* xEventGroup: 事件组句柄,你要删除哪个事件组
*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
设置事件组
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位
* 如果 uxBitsToSet 的 bitX, bitY 为 1, 那么事件组中的 bitX, bitY 被设置为 1
* 可以用来设置多个位,比如 0x15 就表示设置 bit4, bit2, bit0
* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了) */
EventBits_t xEventGroupSetBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
/* 在中断中设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
* 如果 uxBitsToSet 的 bitX, bitY 为 1, 那么事件组中的 bitX, bitY 被设置为 1
* 可以用来设置多个位,比如 0x15 就表示设置 bit4, bit2, bit0
* pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, p
dFALSE-没有
* 返回值: pdPASS-成功, pdFALSE-失败
*/
BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
等待事件
EventBits_t xEventGroupWaitBits(
EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait
);
参数 | 说明 |
xEventGroup
|
等待哪个事件组
|
uxBitsToWaitFor
|
等待哪些位?哪些位要被测试
|
xWaitForAllBits
|
"AND"还是"OR"
pdTRUE: 等待的位,全部为 1;
pdFALSE: 等待的位,某一个为 1 即可
|
xClearOnExit
|
函数退出前是否要清除事件
pdTRUE: 清除 uxBitsToWaitFor 指定的位
pdFALSE: 不清除
|
xTicksToWait
|
如果期待的事件未发生,阻塞多久
可以设置为 0:判断后即刻返回;
可设置为 portMAX_DELAY:一定等到成功才返回;
可以设置为期望的 Tick Count,一般用
pdMS_TO_TICKS()
把 ms 转换为 Tick Count
|
返回值
|
返回的是事件值,
如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件 值;
如果是超时退出,返回的是超时时刻的事件值
|