STM32F1与STM32CubeIDE编程实例CMSISRTOS V2信号量Semaphore
STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-信号量(Semaphore)
信号量(Semaphore)
CMSIS-RTOS v2 (CMSIS-RTOS2) 为基于 Arm® Cortex® 处理器的设备提供通用 RTOS 接口。 它为需要 RTOS 功能的软件组件提供标准化 API,从而为用户和软件行业带来巨大的好处。
本文将详细介绍在CMSIS-RTOS V2中如何使用信号量(Semaphore)。
文章目录
1、信号量介绍
信号量用于管理和保护对共享资源的访问。 信号量与互斥量非常相似。 Mutex 一次只允许一个线程访问共享资源,而信号量可用于允许固定数量的线程/ISR 访问共享资源池。 使用信号量,可以管理对一组相同外设的访问(例如多个 DMA 通道)。

1.1 CMSIS-RTOS V2中的信号量
信号量对象应初始化为可用令牌的最大数量。 这个可用资源的数量被指定为 osSemaphoreNew 函数的参数。 每次使用 osSemaphoreAcquire(处于可用状态)获得信号量令牌时,信号量计数都会递减。 当信号量计数为 0(即耗尽状态)时,无法再获得信号量令牌。 尝试获取信号量令牌的线程/ISR 需要等到下一个令牌空闲。 使用 osSemaphoreRelease 释放信号量,增加信号量计数。

- 信号量用于将任务与系统中的其他事件(尤其是 IRQ)同步
- 等待信号量等于 wait() 过程,任务处于阻塞状态,不占用 CPU 时间 信号量应在使用前创建
- 在 FreeRTOS 实现中,信号量是基于队列机制的。实际上,这些是长度为 1 且数据大小为 0 的队列
- FreeRTOS 中有以下几种信号量:
- 二值(Binary) - 简单的开/关机制
- 计数(Counting ) - 计算多次给予和多次接受
- 互斥体(Mutex) – 互斥类型的信号量(稍后解释)
- 递归(Recursive )(在CMSIS FreeRTOS 中,仅用Mutex)
- 打开信号量 - 提供一个信号量可以从其他任务或中断子程序完成(函数 osSemaphoreRelease() )
- 关闭信号量 - 可以从任务中获取信号量(函数 osSemaphoreWait() )
1 | 注意:函数osSemaphoreAcquire、osSemaphoreGetCount 和 osSemaphoreRelease 可以从中断服务例程中调用。 |
由于其灵活性,信号量涵盖了广泛的同步应用程序。 同时,它们可能是最难理解的 RTOS 对象。下面将简单介绍不同的信号量。
1.1.1 计数信号量
计数信号量可以看作是长度大于一的队列。 信号量的用户(任务、IT)对队列中存储的数据不感兴趣,只关心队列是否为空。
计数信号量通常用于两个目的:
- 计数事件:事件处理程序将在每次事件发生时“提供”一个信号量(增加信号量计数值),并且处理程序任务将在每次处理事件时“获取”一个信号量(减少信号量计数值)。 计数值是已发生的事件数与已处理的事件数之差。 在这种情况下,希望在创建信号量时计数值为零。
- 资源管理:计数值表示可用资源的数量。 为了获得对资源的控制,任务必须首先获得递减信号量计数值的信号量。 当计数值达到零时,没有可用资源。 当任务完成资源时,它释放(返回)信号量,增加信号量计数值。 在这种情况下,希望计数值等于创建信号量时的最大计数值。
二值信号量是计数信号量的特殊形式,其与普通计数信号量的区别如下:

多路复用限制了可以访问代码的关键部分的线程数。 例如,这可能是一个访问 DMA 资源的函数,它只能支持有限数量的调用。
要允许多个线程运行该函数,请将信号量初始化为允许的最大线程数。 信号量中的令牌数表示可能进入的附加线程数。 如果此数字为零,则尝试访问该函数的下一个线程将不得不等到其他线程之一退出并释放其令牌。 当所有线程都退出时,令牌编号又回到 n。 以下示例显示了可能访问资源的线程之一的代码:
1 | osSemaphoreId\_t multiplex_id; |
1.1.2 生产者/消费者信号量
生产者-消费者问题可以使用两个信号量来解决。
第一个信号量 (empty_id) 对可用(空)缓冲区进行倒计时,即生产者线程可以通过从该缓冲区获取来等待可用的缓冲区槽。
第二个信号量 (fill_id) 对已使用(已填充)的缓冲区进行计数,即消费者线程可以通过从该缓冲区获取数据来等待可用数据。
对于线程在给定序列中的两个信号量上获取和释放的正确行为至关重要。 根据这个例子,可以有多个生产者和/或消费者线程同时运行。
1 | #define BUFFER\_SIZE 10U |
1.3 CMSIS-RTOS V2中信号量API
在CMSIS-RTOS V2中,信号量属性osSemaphoreAttr_t的定义如下:
1 | /// Attributes structure for semaphore. |
CMSIS-RTOS V2对信号量的操作提供了如下API:
- **osSemaphoreId_t
osSemaphoreNew (uint32_t max_count, uint32_t initial_count, const osSemaphoreAttr_t *attr)**:
创建并初始化一个用于管理对共享资源的访问的信号量对象,并在出错时返回指向信号量对象标识符的指针或 NULL。 它可以在 RTOS 启动之前安全地调用(调用 osKernelStart),但不能在初始化之前调用(调用 osKernelInitialize)。
+ 参数 max\_count 指定可用令牌的最大数量。 max\_count 值为 1 会创建一个二进制信号量。
+ 参数 initial\_count 设置可用令牌的初始数量。
+ 参数 attr 指定附加的信号量属性。 如果设置为 NULL,将使用默认属性。`注意:该函数不能从中断服务中调用`
简单调用示例如下:
1 | #include "cmsis\_os2.h" // CMSIS RTOS header file |
- **const char * ·osSemaphoreGetName` ( osSemaphoreId_t semaphore_id )**:返回指向由参数 semaphore_id 标识的信号量名称字符串的指针,如果出错则返回 NULL。
注意:该函数不能从中断服务中调用
- **osStatus_t
osSemaphoreAcquire(osSemaphoreId_t semaphore_id,uint32_t timeout)**:等待直到参数 semaphore_id 指定的信号量对象的令牌可用。 如果令牌可用,该函数会立即返回并减少令牌计数。
参数 timeout 指定系统等待获取令牌的时间。 在系统等待期间,调用此函数的线程将进入 BLOCKED 状态。 参数 timeout 可以有以下值:
+ 当 timeout 为 0 时,函数立即返回(即尝试语义)。
+ 当超时设置为 osWaitForever 时,该函数将等待无限时间,直到信号量可用(即等待语义)。
+ 所有其他值在内核滴答中指定超时时间(即定时等待语义)。可能的 osStatus\_t 返回值:
+ **osOK**:令牌已获得,令牌计数递减。
+ **osErrorTimeout**:在给定时间内无法获取令牌。
+ **osErrorResource**:未指定超时时无法获取令牌。
+ **osErrorParameter**:参数 semaphore\_id 为 NULL 或无效。`注意:如果参数 timeout 设置为 0,则可以从中断服务程序调用。`
- **osStatus_t
osSemaphoreRelease(osSemaphoreId_t semaphore_id)**:释放参数 semaphore_id 指定的信号量对象的令牌。 令牌只能在创建时指定的最大数量内释放,请参阅 osSemaphoreNew。 当前等待此信号量对象的令牌的其他线程将进入就绪状态。
可能的 osStatus_t 返回值:
+ **osOK**:令牌已被释放并且计数增加。
+ **osErrorResource**:无法释放令牌(已达到最大令牌计数)。
+ **osErrorParameter**:参数 semaphore\_id 为 NULL 或无效。`注意:该函数支持在中断服务程序中调用`
- **uint32_t
osSemaphoreGetCount(osSemaphoreId_t semaphore_id)**:返回参数 semaphore_id 指定的信号量对象的可用标记数。 如果发生错误,则返回 0。
注意:该函数支持在中断服务程序中调用
**osStatus_t
osSemaphoreDelete(osSemaphoreId_t semaphore_id)**:删除由参数 semaphore_id 指定的信号量对象。 它释放用于信号量处理的内部存储器。 调用后,semaphore_id 不再有效,不能使用。 可以使用函数 osSemaphoreNew 再次创建信号量。可能的 osStatus_t 返回值:- osOK:信号量对象已被删除。
- osErrorParameter:参数 semaphore_id 为 NULL 或无效。
- osErrorResource:信号量处于无效状态。
- osErrorISR:无法从中断服务例程调用 osSemaphoreDelete。
注意:该函数不支持在中断服务程序中调用
2、在STM32CubeIDE中配置信号量
第一步: 新建工程
新建工程,请参考前面文章:
串口配置,请参考前面文章:
第二步: 配置时钟
配置时钟,请参考前面文章:
第三步:配置FreeRTOS
配置FreeRTOS,及FreeRTOS的堆栈,请参考:
第四步:添加线程
创建线程,请参考前面文章:
在本次实例中定义的线程如下:

第五步:创建信号量



第六步:保存生成代码
3、信号量功能代码实现
在main.c文件的main函数中,RTOS启动调用如下:
1 | /\* Init scheduler \*/ |
在freertos.c文件中,生成定义的信号量、线程如下:
1 | /\* Definitions for defaultTask \*/ |
1 | void StartDefaultTask(void \*argument); |
线程执行代码功能实现:
1 | /\* USER CODE END Header\_Thread1Task \*/ |
在freertos.c文件中的MX_FREERTOS_Init函数中,创建信号量:
1 | /\* creation of myBinarySem01 \*/ |
创建线程:
1 | defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, |
最后编译生成固件并下载到开发板中。
4、STM32F1与STM32CubeIDE快速入门系列文章
文章来源: https://iotsmart.blog.csdn.net/article/details/124681341