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
2
注意:函数osSemaphoreAcquire、osSemaphoreGetCount 和 osSemaphoreRelease 可以从中断服务例程中调用。

由于其灵活性,信号量涵盖了广泛的同步应用程序。 同时,它们可能是最难理解的 RTOS 对象。下面将简单介绍不同的信号量。

1.1.1 计数信号量

计数信号量可以看作是长度大于一的队列。 信号量的用户(任务、IT)对队列中存储的数据不感兴趣,只关心队列是否为空。

计数信号量通常用于两个目的:

  • 计数事件:事件处理程序将在每次事件发生时“提供”一个信号量(增加信号量计数值),并且处理程序任务将在每次处理事件时“获取”一个信号量(减少信号量计数值)。 计数值是已发生的事件数与已处理的事件数之差。 在这种情况下,希望在创建信号量时计数值为零。
  • 资源管理:计数值表示可用资源的数量。 为了获得对资源的控制,任务必须首先获得递减信号量计数值的信号量。 当计数值达到零时,没有可用资源。 当任务完成资源时,它释放(返回)信号量,增加信号量计数值。 在这种情况下,希望计数值等于创建信号量时的最大计数值。

二值信号量是计数信号量的特殊形式,其与普通计数信号量的区别如下:

在这里插入图片描述

多路复用限制了可以访问代码的关键部分的线程数。 例如,这可能是一个访问 DMA 资源的函数,它只能支持有限数量的调用。

要允许多个线程运行该函数,请将信号量初始化为允许的最大线程数。 信号量中的令牌数表示可能进入的附加线程数。 如果此数字为零,则尝试访问该函数的下一个线程将不得不等到其他线程之一退出并释放其令牌。 当所有线程都退出时,令牌编号又回到 n。 以下示例显示了可能访问资源的线程之一的代码:

1
2
3
4
5
6
7
8
9
10
11
12
osSemaphoreId\_t multiplex_id;

void thread\_n (void) {

multiplex_id = osSemaphoreNew(3U, 3U, NULL);
while(1) {
osSemaphoreAcquire(multiplex_id, osWaitForever);
// do something
osSemaphoreRelease(multiplex_id);
}
}

1.1.2 生产者/消费者信号量

生产者-消费者问题可以使用两个信号量来解决。

第一个信号量 (empty_id) 对可用(空)缓冲区进行倒计时,即生产者线程可以通过从该缓冲区获取来等待可用的缓冲区槽。

第二个信号量 (fill_id) 对已使用(已填充)的缓冲区进行计数,即消费者线程可以通过从该缓冲区获取数据来等待可用数据。

对于线程在给定序列中的两个信号量上获取和释放的正确行为至关重要。 根据这个例子,可以有多个生产者和/或消费者线程同时运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define BUFFER\_SIZE 10U

osSemaphoreId\_t empty_id = osSemaphoreNew(BUFFER_SIZE, BUFFER_SIZE, NULL);
osSemaphoreId\_t filled_id = osSemaphoreNew(BUFFER_SIZE, 0U, NULL);

void producer\_thread (void) {
while(1) {
osSemaphoreAcquire(empty_id, osWaitForever);
// produce data
osSemaphoreRelease(filled_id);
}
}
void consumer\_thread (void) {

while(1){
osSemaphoreAcquire(filled_id, osWaitForever);
// consume data
osSemaphoreRelease(empty_id);
}
}

1.3 CMSIS-RTOS V2中信号量API

在CMSIS-RTOS V2中,信号量属性osSemaphoreAttr_t的定义如下:

1
2
3
4
5
6
7
8
/// Attributes structure for semaphore.
typedef struct {
const char \*name; ///< name of the semaphore
uint32\_t attr_bits; ///< attribute bits
void \*cb_mem; ///< memory for control block
uint32\_t cb_size; ///< size of provided memory for control block
} osSemaphoreAttr\_t;

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include "cmsis\_os2.h" // CMSIS RTOS header file

osSemaphoreId\_t sid_Semaphore; // semaphore id

osThreadId\_t tid_Thread_Semaphore; // thread id

void Thread\_Semaphore (void \*argument); // thread function

int Init\_Semaphore (void) {

sid_Semaphore = osSemaphoreNew(2U, 2U, NULL);
if (sid_Semaphore == NULL) {
; // Semaphore object not created, handle failure
}

tid_Thread_Semaphore = osThreadNew(Thread_Semaphore, NULL, NULL);
if (tid_Thread_Semaphore == NULL) {
return(-1);
}

return(0);
}

void Thread\_Semaphore (void \*argument) {
osStatus\_t val;

while (1) {
; // Insert thread code here...

val = osSemaphoreAcquire(sid_Semaphore, 10U); // wait for max. 10 ticks for semaphore token to get available
switch (val) {
case osOK:
; // Use protected code here...
osSemaphoreRelease(sid_Semaphore); // return a token back to a semaphore
break;
case osErrorResource:
break;
case osErrorParameter:
break;
default:
break;
}

osThreadYield(); // suspend thread
}
}

  • **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
2
3
4
5
6
7
/\* Init scheduler \*/
osKernelInitialize(); /\* Call init function for freertos objects (in freertos.c) \*/
MX\_FREERTOS\_Init();

/\* Start scheduler \*/
osKernelStart();

freertos.c文件中,生成定义的信号量、线程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/\* Definitions for defaultTask \*/
osThreadId\_t defaultTaskHandle;
const osThreadAttr\_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for thread1 \*/
osThreadId\_t thread1Handle;
const osThreadAttr\_t thread1_attributes = {
.name = "thread1",
.stack_size = 512 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for thread2 \*/
osThreadId\_t thread2Handle;
const osThreadAttr\_t thread2_attributes = {
.name = "thread2",
.stack_size = 512 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for myBinarySem01 \*/
osSemaphoreId\_t myBinarySem01Handle;
const osSemaphoreAttr\_t myBinarySem01_attributes = {
.name = "myBinarySem01"
};

1
2
3
4
void StartDefaultTask(void \*argument);
void Thread1Task(void \*argument);
void Thread2Task(void \*argument);

线程执行代码功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/\* USER CODE END Header\_Thread1Task \*/
void Thread1Task(void \*argument) {
/\* USER CODE BEGIN Thread1Task \*/
/\* Infinite loop \*/
for (;;) {
osDelay(1000);
osSemaphoreAcquire(myBinarySem01Handle, osWaitForever);
counter++;
printf("thread1:counter = %d\r\n", counter);
// produce data
osSemaphoreRelease(myBinarySem01Handle);
}
/\* USER CODE END Thread1Task \*/
}

/\* USER CODE BEGIN Header\_Thread2Task \*/
/\*\*
\* @brief Function implementing the thread2 thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_Thread2Task \*/
void Thread2Task(void \*argument) {
/\* USER CODE BEGIN Thread2Task \*/
/\* Infinite loop \*/
for (;;) {
osDelay(1000);
osSemaphoreAcquire(myBinarySem01Handle, osWaitForever);
// consume data
counter--;
printf("thread2:counter = %d\r\n", counter);
osSemaphoreRelease(myBinarySem01Handle);
}
/\* USER CODE END Thread2Task \*/
}

freertos.c文件中的MX_FREERTOS_Init函数中,创建信号量:

1
2
3
/\* creation of myBinarySem01 \*/
myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);

创建线程:

1
2
3
4
5
6
7
8
9
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL,
&defaultTask_attributes);

/\* creation of thread1 \*/
thread1Handle = osThreadNew(Thread1Task, NULL, &thread1_attributes);

/\* creation of thread2 \*/
thread2Handle = osThreadNew(Thread2Task, NULL, &thread2_attributes);

最后编译生成固件并下载到开发板中。

4、STM32F1与STM32CubeIDE快速入门系列文章

序号 内容
1 STM32F1与STM32CubeIDE快速入门-开发环境搭建
2 STM32F1与STM32CubeIDE快速入门-STM32F1微控制器概述
3 STM32F1与STM32CubeIDE快速入门-GPIO概述与点亮LED
4 STM32F1与STM32CubeIDE快速入门-按键与LED控制
5 STM32F1与STM32CubeIDE快速入门-中断、NVIC与EXTI概述
6 STM32F1与STM32CubeIDE快速入门-外部中断配置与功能实现
7 STM32F1与STM32CubeIDE快速入门-USART/UART串口通信
8 STM32F1与STM32CubeIDE快速入门-定时器(Timer)概述
9 STM32F1与STM32CubeIDE快速入门-定时器定时模式
10 STM32F1与STM32CubeIDE快速入门-定时器计数模式
11 STM32F1与STM32CubeIDE快速入门-定时器PWM模式
12 STM32F1与STM32CubeIDE快速入门-定时器编码(Encoder)模式
13 STM32F1与STM32CubeIDE快速入门-定时器输入捕获模式(Input Capture Mode)实现频率计数
14 STM32F1与STM32CubeIDE快速入门-DMA概述
15 STM32F1与STM32CubeIDE快速入门-USART通过DMA进行数据接收与发送
16 STM32F1与STM32CubeIDE快速入门-ADC概述
17 STM32F1与STM32CubeIDE快速入门-ADC轮询方式实现PWM调光器
18 STM32F1与STM32CubeIDE快速入门-ADC中断方式实现PWM调光器
19 STM32F1与STM32CubeIDE快速入门-ADC通过DMA方式与PWM实现调光器
20 STM32F1与STM32CubeIDE快速入门-DAC概述
21 STM32F1与STM32CubeIDE快速入门-SPI概述
22 STM32F1与STM32CubeIDE快速入门-M25P16串行闪存驱动
23 STM32F1与STM32CubeIDE快速入门-I2C概述
24 STM32F1与STM32CubeIDE快速入门-I2C驱动LCD1602显示屏(基于PCF8574)
25 STM32F1与STM32CubeIDE快速入门-独立看门狗(IWDG)
26 STM32F1与STM32CubeIDE快速入门-OLED-SSD1306-I2C驱动
27 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2配置(基于FreeRTOS)
28 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-线程管理
29 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS v2-延时
30 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-定时器管理
31 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-互斥(Mutex)管理

文章来源: https://iotsmart.blog.csdn.net/article/details/124681341