目录
FreeRTOS%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A-toc" name="tableOfContents" style="margin-left:40px">一、FreeRTOS 二进制信号量:
(1)二进制信号量作用:
(2)二进制信号量与互斥锁的区别:
(3)信号量阻塞时间:
(4)信号量的获取与提供:
(5)优先级在信号量中的应用:
FreeRTOS%20%E8%AE%A1%E6%95%B0%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A-toc" name="tableOfContents" style="margin-left:40px">二、FreeRTOS 计数信号量:
(1)盘点事件:
(2)资源管理:
三、二进制信号量与计数信号量 API:
(1)动态创建二进制信号量:
(2)静态创建二进制信号量:
(3)队列机制创建信号量:
(4)动态创建计数信号量:
(5)静态创建计数信号量:
四、信号量控制 API :
(1)获取指定的互斥锁的任务句柄:
(2)返回信号量计数:
(3)获取信号量:
(4)获取信号量(ISR):
(5)递归获取互斥锁信号量:
(6)释放信号量:
(7)递归释放互斥锁信号量:
(8)释放信号量(ISR):
(9)删除信号量:
五、信号量使用示例:
(1)二进制信号量:
(2)计数信号量:
FreeRTOS%E6%95%99%E7%A8%8B%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%E4%B8%8B%E8%BD%BD%EF%BC%9A-toc" name="tableOfContents" style="margin-left:40px">六、FreeRTOS教程示例代码下载:
FreeRTOS%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A" name="%E4%B8%80%E3%80%81FreeRTOS%20%E4%BA%8C%E8%BF%9B%E5%88%B6%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A">一、FreeRTOS 二进制信号量:
在许多情况下,任务通知可以提供二进制信号量的轻量级替代方案。
(1)二进制信号量作用:
二进制信号量用于互斥和同步目的。
(2)二进制信号量与互斥锁的区别:
二进制信号量和互斥锁极为相似,但存在一些细微差别:互斥锁包括优先继承机制,而二进制信号量则不然。因此,二进制信号量是实现同步的更好选择(任务之间或任务与中断之间),而互斥锁是实现简单互斥的更好选择。 互斥锁如何用作互斥机制的描述同样适用于二进制信号量。文章只描述使用二进制信号量进行同步。
(3)信号量阻塞时间:
信号量 API 函数允许指定阻塞时间。阻塞时间表示在尝试“获取”信号量时,如果信号量不足立即可用,任务应进入阻塞状态的最大“滴答”数。如果多个任务在同一信号量上阻塞,则具有最高优先级的任务将成为下次信号量可用时解除阻塞的任务。
(4)信号量的获取与提供:
可以把二进制信号量看作一个只能保存一个项目的队列。因此,队列只能为空或满(因此为二进制)。使用队列的任务和中断并不关心队列持有什么——它们只想知道队列是空的还是满的。可以利用这种机制来同步(例如)具有中断的任务。
考虑使用任务为外围设备提供服务的情形。轮询外围设备将会耗费 CPU 资源,阻止执行其他任务。因此,最好让任务大部分时间处于阻塞状态(允许其他任务执行),只有在确实有事情需要执行时才执行自身。可以通过使用二进制信号量来实现,方法是“获取”信号量时使任务阻塞。然后为外围设备编写中断例程,当外围设备需要服务时,只是“提供”信号量。任务始终“接收”信号量(从队列中读取信号以使队列变空),但从不“提供”信号量。中断始终“提供”信号量(将写入队列使其为满),但从不获取信号量。另外 RTOS 任务通知,在某些情况下,它可以作为二进制信号量的更快、更轻的替代品。
(5)优先级在信号量中的应用:
任务优先级可确保外围设备及时获得服务,进而有效生成“延迟中断”方案。(注意 FreeRTOS 还具有内置的延迟中断机制)。一种替代方法是使用队列代替信号量。完成此操作后,中断例程可以捕获与外设事件关联的数据并将其发送到任务的队列中。队列数据可用时,任务将取消阻塞,从队列中检索数据,然后执行必要的数据处理。此第二种方案要求中断尽可能短,在一个任务中进行所有后置处理。请注意,中断只能调用以“FromISR”结尾的 API 函数。
使用信号量同步任务与中断。中断仅“提供”信号量,而任务仅“获取”信号量。
FreeRTOS%20%E8%AE%A1%E6%95%B0%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A" name="%E4%BA%8C%E3%80%81FreeRTOS%20%E8%AE%A1%E6%95%B0%E4%BF%A1%E5%8F%B7%E9%87%8F%EF%BC%9A">二、FreeRTOS 计数信号量:
在许多情况下, “任务通知”可以提供计数信号量的轻量级替代方案。
正如二进制信号量可以被认为是长度为 1 的队列那样, 计数信号量也可以被认为是长度大于 1 的队列。同样,信号量的使用者对存储在队列中的数据并不感兴趣, 他们只关心队列是否为空。
计数信号量通常用于两种情况:
(1)盘点事件:
在此使用场景中,事件处理程序将在每次事件发生时“提供”信号量(递增信号量计数值), 而处理程序任务将在每次处理事件时“获取”信号量 (递减信号量计数值)。因此,计数值是已发生的事件数与已处理的事件数之间的差值。在这种情况下, 创建信号量时希望计数值为零。
(2)资源管理:
在此使用情景中,计数值表示可用资源的数量。为了获得对资源的控制,任务必须首先获得信号量——递减信号量计数值。当计数值达到零时, 表示没有空闲资源可用。当任务结束使用资源时, 它会“返还”一个信号量——同时递增信号量计数值。在这种情况下, 创建信号量时希望计数值等于最大计数值。
三、二进制信号量与计数信号量 API:
在许多使用场景中,使用直达任务通知要比使用二进制信号量的速度更快,内存效率更高。
(1)动态创建二进制信号量:
函数原型:
SemaphoreHandle_t xSemaphoreCreateBinary(void);
作用:创建一个二进制信号量,并返回一个可以引用该信号量的句柄。
- configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或处于未定义状态(默认为 1),才可使用此 RTOS API 函数。
- 每个二进制信号量需要少量 RAM,用于保存信号量状态。如果使用 xSemaphoreCreateBinary() 创建了二进制信号量,那么所需的 RAM 将自动从 FreeRTOS 堆中分配。如果二进制信号量是使用 xSemaphoreCreateBinaryStatic() 创建的,则 RAM 由应用程序写入器提供,这需要一个附加参数,但允许 RAM 在编译时进行静态分配。
- 信号量是在“空”状态下创建的,这意味着必须先用 xSemaphoreGive() API 函数给出信号量,然后才能使用 xSemaphoreTake() 函数来获取(获得)该信号量。
- 二进制信号量和互斥锁非常相似,但有一些细微差异:互斥锁具有优先级继承机制,但二进制信号量没有。因此,二进制信号量是实现同步的更好选择(任务之间或任务与中断之间),而互斥锁是实现简单互斥的更好选择。
- 二值信号量并不需要在得到后立即释放,因此,任务同步可以通过一个任务/中断持续释放信号量而另外一个持续获得信号量来实现。请注意,使用直达任务通知往往可以更有效地实现相同的功能。
- 如果另一个优先级更高的任务试图获得相同的互斥锁,那么“获取”互斥锁的任务的优先级就有可能被提高。拥有互斥锁的任务“继承”了试图“获取”相同互斥锁任务的优先级,这意味着必须始终“返回”互斥锁,否则优先级较高的任务将永远无法获得互斥锁,而优先级较低的任务将永远无法“取消继承”优先级。
- 互斥锁和二进制信号量都由 SemaphoreHandle_t 类型的变量引用,同时可以在任何采用该类型参数的任务级 API 函数中使用。与互斥锁不同,二进制信号量可用于中断服务程序。
返回值类型 | 描述 |
---|---|
NULL | 因为可用 FreeRTOS 堆不足,所以无法创建信号量。 |
其他任何值 | 信号量已成功创建。返回值是一个句柄,通过该句柄可以引用信号量。 |
用法示例:
SemaphoreHandle_t xSemaphore;
void vATask( void * pvParameters )
{
//尝试创建一个信号量。
xSemaphore = xSemaphoreCreateBinary();
if( xSemaphore == NULL )
{
// 没有足够的FreeRTOS堆可用的信号量创建成功。
}
else
{
// 信号量现在可以被使用。它的句柄存储在xSemahore变量。
// 在这个信号量上调用xSemaphoreTake()将失败,直到第一次给出信号量。
}
}
(2)静态创建二进制信号量:
函数原型:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic(
StaticSemaphore_t *pxSemaphoreBuffer );
作用:创建一个二进制信号量,并返回一个可以引用该信号量的句柄。
- configSUPPORT_STATIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,才可使用此 RTOS API 函数。
- 每个二进制信号量需要少量 RAM,用于保存信号量状态。如果使用 xSemaphoreCreateBinary() 创建了二进制信号量,那么所需的 RAM 将自动从 FreeRTOS 堆中分配。如果二进制信号量是使用 xSemaphoreCreateBinaryStatic() 创建的,则 RAM 由应用程序写入器提供,这需要一个附加参数,但允许 RAM 在编译时进行静态分配。
参数/返回值类型 | 描述 |
---|---|
pxSemaphoreBuffer | 必须指向 StaticSemaphore_t 类型的变量,该变量将用于保存信号量的状态。 |
NULL | 因为 pxSemaphoreBuffer 为 NULL,所以无法创建信号量。 |
其他任何值 | 信号量已成功创建。返回值是一个句柄,通过该句柄可以引用信号量。 |
附加参数见:
【FreeRTOS 教程 一】任务结构体及其基础创建使用
用法示例:
SemaphoreHandle_t xSemaphore = NULL; // 声明一个信号量句柄变量,并初始化为NULL。
StaticSemaphore_t xSemaphoreBuffer; // 声明一个静态信号量缓冲区变量,用于保存信号量的状态。
void vATask( void * pvParameters ) // 定义一个任务函数vATask,它接收一个void指针类型的参数pvParameters。
{
/* 创建一个二进制信号量,不使用任何动态内存分配。
信号量的数据结构将保存到xSemaphoreBuffer变量中。 */
xSemaphore = xSemaphoreCreateBinaryStatic( &xSemaphoreBuffer );
/* pxSemaphoreBuffer不为NULL,因此预期句柄也不会为NULL。 */
configASSERT( xSemaphore ); // 使用configASSERT宏来断言xSemaphore不为NULL,如果为NULL则触发断言失败。
/* 任务代码的其余部分在这里。 */
}
(3)队列机制创建信号量:
函数原型:
vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
- 注意:vSemaphoreCreateBinary() 宏仍保留在源代码中,以确保向后兼容性,但不应在新设计中使用。使用 xSemaphoreCreateBinary() 函数代替。
- 此外,在许多情况下,使用直达任务通知来代替二进制信号量更快、更节省内存。
- 使用现有队列机制创建信号量的宏。队列长度为 1,因为这是二进制信号量。数据大小为 0,因为实际上我们并不会存储任何数据,只想知道队列为空还是满。
- 二进制信号量和互斥锁非常相似,但有一些小差异:互斥锁包含优先级继承机制,而二进制信号量不包含。这使得二进制信号量成为实现同步(任务之间或任务与中断之间)的更好选择,而互斥锁则成为实现简单互斥的更好选择。
- 在获得二进制信号量后无需返回,因此任务同步可以通过一个任务/中断持续“给予”信号量,而另一个任务/中断持续“获取”信号量来实现。
- 如果另一个优先级更高的任务尝试获取相同的互斥锁,那么“获取”互斥锁的任务的优先级就有可能被提高。拥有互斥锁的任务“继承”了试图“获取”相同互斥锁任务的优先级,这意味着必须始终“返回”互斥锁,否则优先级较高的任务将永远无法获得互斥锁,而优先级较低的任务将永远无法“取消继承”优先级。
- 互斥锁和二进制信号量都分配给了 SemaphoreHandle_t 类型的变量,可在任何采用此类型参数的 API 函数中使用。
参数 | 类型 | 描述 |
---|---|---|
xSemaphore | SemaphoreHandle_t | 已创建信号量的句柄,应为 SemaphoreHandle_t 类型。 |
用法示例:
SemaphoreHandle_t xSemaphore; // 声明一个信号量句柄变量 xSemaphore,用于引用信号量。
void vATask( void * pvParameters ) // 定义一个任务函数 vATask,它接收一个 void 指针类型的参数 pvParameters。
{
// 在调用 vSemaphoreCreateBinary() 之前,信号量不能被使用。
// 这是一个宏,因此直接传递变量。
vSemaphoreCreateBinary( xSemaphore ); // 创建一个二进制信号量,并将信号量的句柄存储在 xSemaphore 中。
if( xSemaphore != NULL ) // 检查信号量是否创建成功
{
// 信号量创建成功。
// 现在可以使用信号量了。
}
}
(4)动态创建计数信号量:
函数原型:
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);
作用:创建一个计数信号量,并返回一个可以引用该新建信号量的句柄。
- configSUPPORT_DYNAMIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,或处于未定义状态(在这种情况下,默认为 1),该 RTOS API 函数才可用。
- 每个计数信号量需要少量 RAM,用于保存信号量的状态。如果使用 xSemaphoreCreateCounting() 创建计数信号量 则所需的 RAM 将从 FreeRTOS 堆自动分配。如果使用 xSemaphoreCreateCountingStatic() 创建计数信号量,则 RAM 会由应用程序编写器提供,这需要其他的但允许在编译时静态分配 RAM。
- configUSE_COUNTING_SEMAPHORES 必须在 FreeRTOSConfig.h 中设置为 1。
参数/返回值类型 | 描述 |
---|---|
uxMaxCount | 可以达到的最大计数值。当信号量达到此值时,它不能再被“给定”。 |
uxInitialCount | 创建信号量时分配给信号量的计数值。 |
信号量句柄 | 如果已成功创建信号量,则将返回该信号量的句柄。 |
NULL | 如果因为保留信号量所需的 RAM 无法分配而无法创建信号量,则会返回 NULL。 |
用法示例:
void vATask( void * pvParameters )
{
SemaphoreHandle_t xSemaphore; // 声明一个信号量句柄变量 xSemaphore,用于引用信号量。
/* 创建一个计数信号量,其最大计数值(最大可“给定”次数)为 10,初始计数值(创建时的计数值)为 0。 */
xSemaphore = xSemaphoreCreateCounting( 10, 0 );
if( xSemaphore != NULL ) // 检查信号量是否创建成功
{
/* 信号量创建成功。 */
// 这里可以添加代码来使用信号量进行同步或资源管理。
}
}
(5)静态创建计数信号量:
函数原型:
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount
StaticSemaphore_t *pxSemaphoreBuffer );
作用:创建一个计数信号量,并返回一个可以引用该新建信号量的句柄。
- configSUPPORT_STATIC_ALLOCATION 必须在 FreeRTOSConfig.h 中设置为 1,才可使用此 RTOS API 函数。
- 每个计数信号量需要少量 RAM,用于保存信号量的状态。如果使用 xSemaphoreCreateCounting() 创建计数信号量,则所需的 RAM 将从 FreeRTOS 堆自动分配。如果使用 xSemaphoreCreateCountingStatic() 创建计数信号量,则 RAM 由应用程序编写器提供,这需要用到一个附加参数,但允许在编译时静态分配 RAM。
附加参数见:
【FreeRTOS 教程 一】任务结构体及其基础创建使用
参数/返回值类型 | 描述 |
---|---|
uxMaxCount | 可以达到的最大计数值。当信号量达到此值时,它不能再被“给出”。 |
uxInitialCount | 创建信号量时分配给信号量的计数值。 |
pxSemaphoreBuffer | 必须指向一个 StaticSemaphore_t 类型的变量,然后用它来保存信号量的数据结构体。 |
信号量句柄 | 如果已成功创建信号量,则将返回该信号量的句柄。 |
NULL | 如果 pxSemaphoreBuffer 为 NULL,则返回 NULL。 |
用法示例:
static StaticSemaphore_t xSemaphoreBuffer; // 声明一个静态信号量缓冲区变量,用于保存信号量的状态。
void vATask( void * pvParameters ) // 定义一个任务函数 vATask,它接收一个 void 指针类型的参数 pvParameters。
{
SemaphoreHandle_t xSemaphore; // 声明一个信号量句柄变量 xSemaphore,用于引用信号量。
/* 创建一个计数信号量,其最大计数值(最大可“给定”次数)为 10,初始计数值(创建时的计数值)为 0。
信号量的数据结构将存储在 xSemaphoreBuffer 变量中,不进行动态内存分配。 */
xSemaphore = xSemaphoreCreateCountingStatic( 10, 0, &xSemaphoreBuffer );
/* pxSemaphoreBuffer 不为 NULL,因此预期信号量将被创建。 */
configASSERT( xSemaphore ); // 使用 configASSERT 宏来断言 xSemaphore 不为 NULL,如果为 NULL 则触发断言失败。
}
四、信号量控制 API :
(1)获取指定的互斥锁的任务句柄:
函数原型:
TaskHandle_t xSemaphoreGetMutexHolder( SemaphoreHandle_t xMutex );
- 必须在 FreeRTOSConfig.h 中将 INCLUDE_xSemaphoreGetMutexHolder 设置为 1, 此函数才可用。
- 返回保存函数参数指定的互斥锁的任务的句柄(若有)。
- xSemaphoreGetMutexHolder () 可以可靠地用于确定调用任务是否是互斥锁持有者,但如果由调用任务之外的任务持有互斥锁,则无法可靠地使用 xSemaphoreGetMutexHolder () 。这是因为 MUTEX 支架可能会在调用该函数的调用任务与测试该函数返回值之间更改。
- configUSE_MUTEXES 必须在 FreeRTOSConfig.h 中设置为 1,xSemaphoreGetMutexHolder() 才可用。
参数/返回值 | 描述 |
---|---|
xMutex | 正在查询的互斥体的句柄。 |
保存 xMutex 参数指定的互斥锁的任务的句柄。如果在 xMutex 参数中传递的信号量不是互斥锁型信号量,或者如果互斥锁可用,但未被任何任务保存,则返回 NULL。 |
(2)返回信号量计数:
函数原型:
UBaseType_t uxSemaphoreGetCount( SemaphoreHandle_t xSemaphore );
作用:返回信号量计数。
参数/返回 | 描述 |
---|---|
xSemaphore | 正在查询的信号量的句柄。 |
返回值 | 如果信号量是计数信号量,则返回信号量的当前计数值。如果信号量是二进制信号量,则当信号量可用时,返回 1,当信号量不可用时,返回 0。 |
(3)获取信号量:
函数原型:
xSemaphoreTake( SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait );
- 用于获取信号量的宏。信号量必须在之前已经通过调用 xSemaphoreCreateBinary()、xSemaphoreCreateMutex() 或 xSemaphoreCreateCounting()创建。
- 不得从 ISR 调用此宏。如果需要,xQueueReceiveFromISR()可用来从中断中获取一个信号量, 尽管这不是正常操作。
- 信号量使用队列作为其底层机制,因此函数在某种程度上可互操作。
参数/返回值 | 描述 |
---|---|
xSemaphore | 正在获取的信号量的句柄——在创建信号量时获得。 |
xTicksToWait | 等待信号量变为可用的时间(以滴答为单位)。宏 portTICK_PERIOD_MS 可以将其转换为实际时间。可以用一个为零的阻塞时间来轮询信号量。如果 INCLUDE_vTaskSuspend 设置为 1,则将阻塞时间指定为 portMAX_DELAY 会导致任务无限期地阻塞(没有超时限制)。 |
返回值 | 如果获得信号量,则返回 pdTRUE 。如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE 。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL; // 定义一个信号量句柄变量,初始化为NULL。
/* 创建信号量的任务 */
void vATask( void * pvParameters )
{
/* 创建一个信号量来保护共享资源。由于我们使用信号量进行互斥,所以我们创建一个互斥信号量而不是二进制信号量。 */
xSemaphore = xSemaphoreCreateMutex(); // 创建一个互斥信号量。
}
/* 使用信号量的任务 */
void vAnotherTask( void * pvParameters )
{
/* ... 做其他事情。 */
if( xSemaphore != NULL ) // 检查信号量是否已经被创建。
{
/* 尝试获取信号量。如果信号量不可用,等待10个时钟周期看看它是否变得可用。 */
if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE ) // 尝试获取信号量,最多等待10个时钟周期。
{
/* 我们成功获取了信号量,现在可以访问共享资源。 */
/* ... */
/* 我们已经完成了对共享资源的访问。释放信号量。 */
xSemaphoreGive( xSemaphore ); // 释放信号量。
}
else
{
/* 我们未能获取信号量,因此无法安全地访问共享资源。 */
}
}
}
(4)获取信号量(ISR):
函数原型:
xSemaphoreTakeFromISR
(
SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken
)
- 可从 ISR 调用的 xSemaphoreTake() 版本。与 xSemaphoreTake() 不同,xSemaphoreTakeFromISR() 不允许指定阻塞时间。
参数/返回值 | 描述 |
---|---|
xSemaphore | 信号量被“获取”。信号量由 SemaphoreHandle_t 类型的变量引用,必须在使用之前显式创建。 |
pxHigherPriorityTaskWoken | 信号量可能阻塞一个或多个任务,等待给出信号量。调用 xSemaphoreTakeFromISR() 将使被阻塞的任务等待信号量离开阻塞状态。如果调用 API 函数导致任务离开阻塞状态,且未阻塞任务的优先级等于或高于当前正在执行的任务(被中断的任务),那么 API 函数将从内部把 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xSemaphoreTakeFromISR() 将 *pxHigherPriorityTaskWoken 设置为 pdTRUE ,则应在退出中断之前执行上下文切换。这将确保中断直接返回到最高优先级的就绪状态任务。该机制与 xQueueReceiveFromISR() 函数中使用的机制相同,读者可以参考 xQueueReceiveFromISR() 文档以获得进一步解释。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 是一个可选参数,可设置为 NULL 。 |
返回值 | 如果成功获取信号量,则返回 pdTRUE 。如果因为信号量不可用而未成功获取信号量,则返回 pdFALSE 。 |
(5)递归获取互斥锁信号量:
函数原型:
xSemaphoreTakeRecursive( SemaphoreHandle_t xMutex,
TickType_t xTicksToWait );
- 递归地获得或“获取”一个互斥锁型信号量的宏。 此互斥锁必须已经事先通过调用 xSemaphoreCreateRecursiveMutex() 完成创建;
- 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1, 此宏才可用。
- 不得在使用 xSemaphoreCreateMutex() 创建的互斥锁上使用此宏。
- 所有者可以反复“获取”递归使用的互斥锁。在所有者为每个成功的“获取”请求调用 xSemaphoreGiveRecursive() 之前,该互斥锁不会再次变得可用。例如, 如果一个任务成功地“获取”了同一个互斥锁 5 次, 那么任何其他任务都无法使用此互斥锁, 直到任务也把这个互斥锁“解锁”5 次。
参数 | 描述 |
---|---|
xMutex | 正在获得的互斥锁的句柄。这是由 xSemaphoreCreateRecursiveMutex() 返回的句柄。 |
xTicksToWait | 等待信号量变为可用的时间(以滴答为单位)。可以使用 portTICK_PERIOD_MS 宏将其转换为实际时间。可以用一个为零的阻塞时间来轮询信号量。如果任务已有信号量,则无论 xTicksToWait 的值是多少,xSemaphoreTakeRecursive() 都将立即返回。 |
返回值 | 如果获得信号量,则返回 pdTRUE ;如果 xTicksToWait 过期,信号量不可用,则返回 pdFALSE 。 |
用法示例:
SemaphoreHandle_t xMutex = NULL; // 定义一个信号量句柄变量,初始化为NULL。
// 创建互斥锁的任务
void vATask( void * pvParameters )
{
// 创建一个互斥锁来保护共享资源。
xMutex = xSemaphoreCreateRecursiveMutex(); // 创建一个可递归的互斥锁。
}
// 使用互斥锁的任务
void vAnotherTask( void * pvParameters )
{
// ... 执行其他任务。
if( xMutex != NULL ) // 检查互斥锁是否已经被创建。
{
// 尝试获取互斥锁。如果互斥锁不可用,等待10个时钟周期看看它是否变得可用。
if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE ) // 尝试获取互斥锁,最多等待10个时钟周期。
{
// 我们成功获取了互斥锁,现在可以访问共享资源。
// ...
// 由于代码的特性,可能会进一步调用 xSemaphoreTakeRecursive() 来获取同一个互斥锁。
// 在实际代码中,这些调用不会是简单的连续调用,而是可能嵌套在更复杂的调用结构中。
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ); // 再次获取互斥锁
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ); // 再次获取互斥锁
// 现在互斥锁已经被“获取”三次,所以在释放三次之前,其他任务无法获取它。
// 同样,在实际代码中,这些调用不会是简单的连续调用,而是可能嵌套在更复杂的调用结构中。
// 这里只是为了演示。
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
xSemaphoreGiveRecursive( xMutex ); // 释放互斥锁
// 现在其他任务可以获取互斥锁。
}
else
{
// 我们未能获取互斥锁,因此无法安全地访问共享资源。
}
}
}
(6)释放信号量:
函数原型:
xSemaphoreGive( SemaphoreHandle_t xSemaphore );
- 用于释放信号量的宏。释放前信号量必须已经通过调用 xSemaphoreCreateBinary()、xSemaphoreCreateMutex() 或 xSemaphoreCreateCounting()创建。
- 不得在 ISR 中使用此宏。可以参考 xSemaphoreGiveFromISR(),可以从 ISR 中使用的替代方案。
- 此宏也不得用于使用 xSemaphoreCreateRecursiveMutex() 创建的信号量。
参数/返回值 | 描述 |
---|---|
xSemaphore | 要释放的信号量的句柄。这是创建信号量时返回的句柄。 |
返回值 | 如果信号量被释放,则返回 pdTRUE 。如果发生错误,则返回 pdFALSE 。信号量是使用队列实现的。发布消息时,如果队列上没有空间,那么可能会发生错误,这表明最初未能正确获取信号量。 |
用法示例:
SemaphoreHandle_t xSemaphore = NULL; // 定义一个信号量句柄变量,初始化为NULL。
void vATask( void * pvParameters )
{
// 创建一个信号量来保护共享资源。由于我们使用信号量进行互斥,所以我们创建一个互斥信号量而不是二进制信号量。
xSemaphore = xSemaphoreCreateMutex(); // 创建一个互斥信号量。
if( xSemaphore != NULL ) // 检查信号量是否创建成功。
{
if( xSemaphoreGive( xSemaphore ) != pdTRUE ) // 尝试释放信号量。
{
// 我们预期这个调用会失败,因为我们不能在没有先“获取”信号量的情况下释放它!
}
// 获取信号量 - 如果信号量不可立即获得,则不阻塞。
if( xSemaphoreTake( xSemaphore, ( TickType_t ) 0 ) ) // 尝试立即获取信号量。
{
// 我们现在拥有信号量,可以访问共享资源。
// ...
// 我们已经完成了对共享资源的访问,所以可以释放信号量。
if( xSemaphoreGive( xSemaphore ) != pdTRUE ) // 释放信号量。
{
// 我们不预期这个调用会失败,因为我们必须已经获取了信号量才能到达这里。
}
}
}
}
(7)递归释放互斥锁信号量:
函数原型:
xSemaphoreGiveRecursive( SemaphoreHandle_t xMutex )
- 递归地释放或“给出”一个互斥锁型信号量的宏。 此互斥锁必须已经事先通过调用 xSemaphoreCreateRecursiveMutex() 完成创建;
- 必须在 FreeRTOSConfig.h 中将 configUSE_RECURSIVE_MUTEXES 设置为 1, 此宏才可用。
- 不得在使用 xSemaphoreCreateMutex() 创建的互斥锁上使用此宏。
- 所有者可以反复“获取”递归使用的互斥锁。在所有者为每个成功的“获取”请求调用 xSemaphoreGiveRecursive() 之前,该互斥锁不会再次变得可用。例如, 如果一个任务成功地“获取”了同一个互斥锁 5 次, 那么任何其他任务都无法使用此互斥锁, 直到任务也把这个互斥锁“解锁”5 次。
参数/描述 | 说明 |
---|---|
xMutex | 正在释放或“给出”的互斥锁的句柄。这是由 xSemaphoreCreateRecursiveMutex() 返回的句柄。 |
返回值 | 如果成功给出信号量,则返回 pdTRUE 。 |
用法示例:
// 声明一个互斥量句柄,初始值为NULL,表示尚未创建互斥量。
SemaphoreHandle_t xMutex = NULL;
// 创建互斥量的任务
void vATask( void * pvParameters )
{
// 创建一个递归互斥量,用于保护共享资源。
// 递归互斥量允许同一个任务多次获取同一个互斥量,
// 但必须相应地多次释放它。
xMutex = xSemaphoreCreateRecursiveMutex();
}
// 使用互斥量的任务
void vAnotherTask( void * pvParameters )
{
// ... 执行其他任务逻辑。
// 检查互斥量是否已经创建。
if( xMutex != NULL )
{
// 尝试获取互斥量。如果互斥量当前不可用,
// 等待最多10个系统滴答(tick)时间,看看它是否会变为空闲。
if( xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 ) == pdTRUE )
{
// 成功获取了互斥量,可以安全地访问共享资源。
// ...
// 由于代码的性质,可能需要进一步调用xSemaphoreTakeRecursive()
// 来获取同一个互斥量。在实际代码中,这些调用通常不会是连续的,
// 而是嵌入在更复杂的调用结构中。
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
// 此时,互斥量已经被“获取”了三次,
// 因此在另一个任务可以获取它之前,
// 它也必须被“释放”三次。在实际代码中,
// 这些释放调用通常不会是连续的,而是在调用栈展开时调用。
// 这里只是为了演示目的。
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
xSemaphoreGiveRecursive( xMutex );
// 现在,互斥量可以被其他任务获取了。
}
else
{
// 无法获取互斥量,因此无法安全地访问共享资源。
}
}
}
(8)释放信号量(ISR):
函数原型:
xSemaphoreGiveFromISR
(
SemaphoreHandle_t xSemaphore,
signed BaseType_t *pxHigherPriorityTaskWoken
)
- 用于释放信号量的宏。释放前信号量必须已经 通过调用 xSemaphoreCreateBinary() 或 xSemaphoreCreateCounting() 创建。
- 互斥锁型信号量(那些调用 xSemaphoreCreateMutex() 创建的信号量) 不得与此宏一起使用。
- 此宏可在 ISR 中使用。
参数/返回 | 描述 |
---|---|
xSemaphore | 要释放的信号量的句柄。这是创建信号量时返回的句柄。 |
pxHigherPriorityTaskWoken | 如果给出信号量会导致任务解除阻塞,并且解除阻塞的任务的优先级高于当前正在运行的任务,则 xSemaphoreGiveFromISR() 会将 *pxHigherPriorityTaskWoken 设置为 pdTRUE 。如果 xSemaphoreGiveFromISR() 将此值设置为 pdTRUE ,则应在退出中断之前请求上下文切换。从 FreeRTOS V7.3.0 开始,pxHigherPriorityTaskWoken 为可选参数,可设置为 NULL 。 |
返回值 | 如果成功给出信号量,则返回 pdTRUE ,否则返回 errQUEUE_FULL 。 |
用法示例:
#define LONG_TIME 0xffff // 定义一个常量,表示等待信号量的最长时间
#define TICKS_TO_WAIT 10 // 定义一个常量,表示等待的滴答数
SemaphoreHandle_t xSemaphore = NULL; // 声明一个信号量句柄,初始值为NULL
/* 重复执行的任务 */
void vATask( void * pvParameters )
{
/* 我们使用信号量进行同步,因此创建一个二进制信号量而不是互斥量。
必须确保在创建信号量之前中断不会尝试使用它! */
xSemaphore = xSemaphoreCreateBinary(); // 创建一个二进制信号量
for( ;; ) // 无限循环
{
/* 我们希望这个任务每10个定时器滴答执行一次。信号量在启动此任务之前创建。
阻塞等待信号量变为可用。 */
if( xSemaphoreTake( xSemaphore, LONG_TIME ) == pdTRUE ) // 如果成功获取信号量
{
/* 执行任务。 */
...
/* 任务执行完毕。返回循环顶部,在那里我们将阻塞信号量,直到再次执行。
注意,当以这种方式使用信号量与ISR进行同步时,无需“释放”信号量。 */
}
}
}
/* 定时器ISR */
void vTimerISR( void * pvParameters )
{
static unsigned char ucLocalTickCount = 0; // 静态变量,用于计数滴答数
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 用于指示是否唤醒了更高优先级的任务
/* 定时器滴答发生。 */
... 执行其他定时器功能。
/* 是否到了vATask()执行的时间? */
xHigherPriorityTaskWoken = pdFALSE; // 初始化为pdFALSE
ucLocalTickCount++; // 增加计数
if( ucLocalTickCount >= TICKS_TO_WAIT ) // 如果计数达到指定的滴答数
{
/* 通过释放信号量来解除任务的阻塞。 */
xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken ); // 释放信号量
/* 重置计数,以便在10个滴答时间后再次释放信号量。 */
ucLocalTickCount = 0;
}
/* 如果xHigherPriorityTaskWoken为true,则进行上下文切换。实际使用的宏是特定于端口的。 */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); // 请求上下文切换(如果需要)
}
(9)删除信号量:
函数原型:
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore);
- 删除信号量,包括互斥锁型信号量和递归信号量。
- 请勿删除已有阻塞任务的信号量(正在等待信号灯可用的阻塞状态任务)。
参数 | 描述 |
---|---|
xSemaphore | 被删除的信号量的句柄。 |
五、信号量使用示例:
(1)二进制信号量:
- 使用二进制信号量在两个任务之间实现同步。程序初始化UART和DMA1,创建一个二进制信号量,并定义两个任务:任务
vBinaryTask2
每2秒释放一次二进制信号量,而任务vBinaryTask
在获取到信号量后会打印一条消息,处理1秒钟,然后再次释放信号量。
方法一:动态创建二进制信号量
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 使用二进制信号量在两个任务之间实现同步
*
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
SemaphoreHandle_t xBinarySemaphore = NULL; // 声明一个二进制信号量的句柄变量,并初始化为 NULL
// 定义一个任务函数,该任务将使用二进制信号量
void vBinaryTask(void *pvParameters)
{
for (;;)
{
// 尝试获取信号量,如果信号量不可用,则任务将被阻塞,直到超时
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE)
{
printf("Binary Semaphore Taken\n"); // 通过 UART 打印信号量已获取的信息
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务处理时间,延迟 1 秒
printf("Binary Semaphore Given\n"); // 通过 UART 打印信号量已释放的信息
}
}
}
// 定义另一个任务函数,该任务将释放二进制信号量
void vBinaryTask2(void *pvParameters)
{
for (;;)
{
vTaskDelay(pdMS_TO_TICKS(2000)); // 模拟外部事件,延迟 2 秒
xSemaphoreGive(xBinarySemaphore); // 释放信号量
printf("Binary Semaphore Given from Task 2\n"); // 通过 UART 打印信号量已释放的信息
}
}
int main(void)
{
Uart_Init(115200); // 初始化 UART,波特率设置为 115200
DMA1_Init(); // 初始化 DMA1
// 创建一个二进制信号量
xBinarySemaphore = xSemaphoreCreateBinary();
if (xBinarySemaphore == NULL) // 检查信号量是否创建成功
{
printf("Failed to create binary semaphore\n"); // 如果创建失败,通过 UART 打印错误信息
return 0; // 返回 0,表示程序异常终止
}
// 创建两个任务
xTaskCreate(vBinaryTask, "Binary Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建任务 1
xTaskCreate(vBinaryTask2, "Binary Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // 创建任务 2
// 启动 RTOS 调度器
vTaskStartScheduler(); // 启动任务调度器,开始执行任务
return 0; // 程序不应该到达这里,除非有错误
}
方法二:静态创建二进制信号量
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 静态创建二进制信号量,
* 在两个任务之间实现同步
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 声明一个静态信号量缓冲区变量,并初始化
StaticSemaphore_t xBinarySemaphoreBuffer;
// 声明一个二进制信号量的句柄变量,并初始化为 NULL
SemaphoreHandle_t xBinarySemaphore = NULL;
// 定义一个任务函数,该任务将使用二进制信号量
void vBinaryTask(void *pvParameters)
{
for (;;)
{
// 尝试获取信号量,如果信号量不可用,则任务将被阻塞,直到超时
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE)
{
printf("Binary Semaphore Taken\n"); // 通过 UART 打印信号量已获取的信息
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务处理时间,延迟 1 秒
printf("Binary Semaphore Given\n"); // 通过 UART 打印信号量已释放的信息
}
}
}
// 定义另一个任务函数,该任务将释放二进制信号量
void vBinaryTask2(void *pvParameters)
{
for (;;)
{
vTaskDelay(pdMS_TO_TICKS(2000)); // 模拟外部事件,延迟 2 秒
xSemaphoreGive(xBinarySemaphore); // 释放信号量
printf("Binary Semaphore Given from Task 2\n"); // 通过 UART 打印信号量已释放的信息
}
}
int main(void)
{
Uart_Init(115200); // 初始化 UART,波特率设置为 115200
DMA1_Init(); // 初始化 DMA1
// 创建一个二进制信号量,使用静态分配的内存
xBinarySemaphore = xSemaphoreCreateBinaryStatic(&xBinarySemaphoreBuffer);
if (xBinarySemaphore == NULL) // 检查信号量是否创建成功
{
printf("Failed to create binary semaphore\n"); // 如果创建失败,通过 UART 打印错误信息
return 0; // 返回 0,表示程序异常终止
}
// 创建两个任务
xTaskCreate(vBinaryTask, "Binary Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建任务 1
xTaskCreate(vBinaryTask2, "Binary Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // 创建任务 2
// 启动 RTOS 调度器
vTaskStartScheduler(); // 启动任务调度器,开始执行任务
return 0; // 程序不应该到达这里,除非有错误
}
(2)计数信号量:
- 定义两个任务,其中一个任务(
vCountingTask2
)周期性地释放信号量,另一个任务(vCountingTask
)在获取到计数信号量后执行延迟操作并释放信号量,从而演示了利用计数信号量进行任务间的同步和资源管理,确保了任务执行的有序性和资源访问的互斥性。
方法一:动态创建计数信号量
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 动态创建计数信号量,
* 进行任务间的同步和资源管理
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
SemaphoreHandle_t xCountingSemaphore = NULL; // 声明一个计数信号量的句柄变量,并初始化为 NULL
// 定义一个任务函数,该任务将使用计数信号量
void vCountingTask(void *pvParameters)
{
for (;;)
{
// 尝试获取信号量,如果信号量不可用,则任务将被阻塞,直到超时
if (xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE)
{
printf("Counting Semaphore Taken\n"); // 通过 UART 打印信号量已获取的信息
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务处理时间,延迟 1 秒
printf("Counting Semaphore Given\n"); // 通过 UART 打印信号量已释放的信息
}
}
}
// 定义另一个任务函数,该任务将释放计数信号量
void vCountingTask2(void *pvParameters)
{
for (;;)
{
vTaskDelay(pdMS_TO_TICKS(2000)); // 模拟外部事件,延迟 2 秒
for (int i = 0; i < 5; i++)
{
xSemaphoreGive(xCountingSemaphore); // 释放信号量
printf("Counting Semaphore Given from Task 2\n"); // 通过 UART 打印信号量已释放的信息
}
}
}
int main(void)
{
Uart_Init(115200); // 初始化 UART,波特率设置为 115200
DMA1_Init(); // 初始化 DMA1
// 创建一个计数信号量,最大计数值为 5,初始计数值为 0
xCountingSemaphore = xSemaphoreCreateCounting(5, 0);
if (xCountingSemaphore == NULL) // 检查信号量是否创建成功
{
printf("Failed to create counting semaphore\n"); // 如果创建失败,通过 UART 打印错误信息
return 0; // 返回 0,表示程序异常终止
}
// 创建两个任务
xTaskCreate(vCountingTask, "Counting Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建任务 1
xTaskCreate(vCountingTask2, "Counting Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // 创建任务 2
// 启动 RTOS 调度器
vTaskStartScheduler(); // 启动任务调度器,开始执行任务
return 0; // 程序不应该到达这里,除非有错误
}
方法二:静态创建计数信号量:
#include "stm32f10x.h" // 包含STM32F10x系列微控制器的头文件
#include "FreeRTOS.h"
#include "task.h" // 包含任务相关函数的头文件,用于任务创建和管理。
#include "semphr.h" // 引入信号量相关的头文件
#include "stdio.h"
#include "uart.h"
/***********************************
* @method 静态创建二进制信号量,
* 在两个任务之间实现同步
* @Platform CSDN
* @author The_xzs
* @date 2025.2.2
************************************/
// 声明一个静态信号量缓冲区变量,并初始化
StaticSemaphore_t xBinarySemaphoreBuffer;
// 声明一个二进制信号量的句柄变量,并初始化为 NULL
SemaphoreHandle_t xBinarySemaphore = NULL;
// 定义一个任务函数,该任务将使用二进制信号量
void vBinaryTask(void *pvParameters)
{
for (;;)
{
// 尝试获取信号量,如果信号量不可用,则任务将被阻塞,直到超时
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE)
{
printf("Binary Semaphore Taken\n"); // 通过 UART 打印信号量已获取的信息
vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟任务处理时间,延迟 1 秒
printf("Binary Semaphore Given\n"); // 通过 UART 打印信号量已释放的信息
}
}
}
// 定义另一个任务函数,该任务将释放二进制信号量
void vBinaryTask2(void *pvParameters)
{
for (;;)
{
vTaskDelay(pdMS_TO_TICKS(2000)); // 模拟外部事件,延迟 2 秒
xSemaphoreGive(xBinarySemaphore); // 释放信号量
printf("Binary Semaphore Given from Task 2\n"); // 通过 UART 打印信号量已释放的信息
}
}
int main(void)
{
Uart_Init(115200); // 初始化 UART,波特率设置为 115200
DMA1_Init(); // 初始化 DMA1
// 创建一个二进制信号量,使用静态分配的内存
xBinarySemaphore = xSemaphoreCreateBinaryStatic(&xBinarySemaphoreBuffer);
if (xBinarySemaphore == NULL) // 检查信号量是否创建成功
{
printf("Failed to create binary semaphore\n"); // 如果创建失败,通过 UART 打印错误信息
return 0; // 返回 0,表示程序异常终止
}
// 创建两个任务
xTaskCreate(vBinaryTask, "Binary Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建任务 1
xTaskCreate(vBinaryTask2, "Binary Task 2", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // 创建任务 2
// 启动 RTOS 调度器
vTaskStartScheduler(); // 启动任务调度器,开始执行任务
return 0; // 程序不应该到达这里,除非有错误
}
FreeRTOS%E6%95%99%E7%A8%8B%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%E4%B8%8B%E8%BD%BD%EF%BC%9A" name="%E4%BA%94%E3%80%81FreeRTOS%E6%95%99%E7%A8%8B%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81%E4%B8%8B%E8%BD%BD%EF%BC%9A">六、FreeRTOS教程示例代码下载:
通过网盘分享的文件:FreeRTOS教程示例代码