IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> 【FreeRTOS】FreeRTOS学习笔记(12)— FreeRTOS的线程间通信(CMSIS_API) -> 正文阅读

[嵌入式]【FreeRTOS】FreeRTOS学习笔记(12)— FreeRTOS的线程间通信(CMSIS_API)


FreeRTOS的线程间通信

线程间通信

1、什么是线程间通信

线程间通信就是线程间进行资源(信息)共享。

2、最简单的通信方式

最简单的通信方式:使用全局变量来通信。

在我们前面所举的例子中,LED 线程和按键线程之间就是通过全局变量来通信的,使用全局变量通信的方式是线程间通信的最简单方式。

请添加图片描述

但是使用全局变量通信不够安全,之所以不够安全是因为除了通信双方线程外,其它所有线程也能访问全局变量,很容易被其它线程篡改内容,因此我们需要一种仅与通信双方有关的专用通信方式,本小节我们就是来介绍这些专用的通信方式。

3、线程间的专用通信方式

线程间的专用通信方式有哪些如下图所示:

Signal Events //信号
    osSignalSet : Set signal flags of a thread.
    osSignalClear : Reset signal flags of a thread.
    osSignalWait : Suspend execution until specific signal flags are set.
Mutexes //互斥锁
    osMutexCreate : Define and initialize a mutex.
    osMutexWait : Obtain a mutex or Wait until it becomes available.
    osMutexRelease : Release a mutex.
    osMutexDelete : Delete a mutex.
Semaphores //信号量
    osSemaphoreCreate : Define and initialize a semaphore.
    osSemaphoreWait : Obtain a semaphore token or Wait until it becomes available.
    osSemaphoreRelease : Release a semaphore token.
    osSemaphoreDelete : Delete a semaphore.
Memory Pool //内存池
    osPoolCreate : Define and initialize a fix-size memory pool.
    osPoolAlloc : Allocate a memory block.
    osPoolCAlloc : Allocate a memory block and zero-set this block.
    osPoolFree : Return a memory block to the memory pool.
Message Queue //消息队列
    osMessageCreate : Define and initialize a message queue.
    osMessagePut : Put a message into a message queue.
    osMessageGet : Get a message or suspend thread execution until message arrives.
Mail Queue //邮箱队列
    osMailCreate : Define and initialize a mail queue with fix-size memory blocks.
    osMailAlloc : Allocate a memory block.
    osMailCAlloc : Allocate a memory block and zero-set this block.
    osMailPut : Put a memory block into a mail queue.
    osMailGet : Get a mail or suspend thread execution until mail arrives.
    osMailFree : Return a memory block to the mail queue.

其实我们在前面就介绍过过:

  • 信号、消息队列、邮箱队列是真正的通信
  • 内存池:其实是 malloc 的替代方式,严格来说不算是通信
  • 互斥锁、信号量:借助通信而实现的资源保护

4、专用通信方式的通信原理

所有线程都是共享RTOS,所以所有的专用通信方式都是由RTOS来提供的,专用通信方式的原理说白了就是让共享的RTOS来转发信息。

一、信号(Signal Events)

1.1、什么是信号通信

通过调用RTOS的信号API来向指定线程发送一个整形数,这个整形数就被称为信号,发送什么整形数,以及收到该整形数后是什么意义,这个是由我们自己来约定的。

假设编程时约定好 A 线程发送 1 给 B 线程时就代表 k1 按键被按下了,那么 1 这个整形数(信号)作用就是告诉 B 线程按键 k1 被按下了,调用 RTOS 的信号 API 来收发信号(整形数),其实就是让 RTOS 转发信号这个整形数给对方线程。

1.2、信号这个整形数的范围

通过查看源码可知0~0x80000000(不包括0x80000000)之间的数都可以用来当作信号,至于说信号的含义是什么,这个就由编程者来自己来约定。

0x80000000之所以不能作为信号,是因为 0x80000000 被用来代表信号错误,后面还会提到0x80000000 这个玩意。如果你不知道这个范围也没关系,因为平时我们根本不会使用那么大数的信号,一般使用 100 以内的就很了不起了

1.3、宏

osFeature_Signals

这个宏的的作用是用来对信号进行限制的,但是ST公司在进行封装时虽然按照 CMSIS 的规则定义了这个宏,但是实际情况是并没有使用这个宏,既然没有使用,因此这里就不再介绍宏的具体作用。

1.4、API

Signal Events 
    osSignalSet : Set signal flags of a thread.
    osSignalClear : Reset signal flags of a thread.
    osSignalWait : Suspend execution until specific signal flags are set.

osSignalClear(清除信号)

这个函数用于清除信号,但是由于这个函数也没有定义,因此这里不再介绍。

osSignalSet(发送信号)

函数原型:int32_t osSignalSet(osThreadId thread_id,int32_t signal)
功能:向指定 ID 的线程发送信号。
参数:

  • thread_id:线程 ID句柄
  • signal:指定要发送的信号
  • 返回值:成功就返回上一次发送的信号,失败就返回 0x80000000(前面提到过)

osSignalWait(接收信号)

函数原型:osEvent osSignalWait (int32_t signals, uint32_t millisec)

osEvent osSignalWait (int32_t signals, uint32_t millisec)
{
  osEvent ret;

#if( configUSE_TASK_NOTIFICATIONS == 1 )
	
  TickType_t ticks;

  ret.value.signals = 0;  
  ticks = 0;
  if (millisec == osWaitForever) {
    ticks = portMAX_DELAY;
  }
  else if (millisec != 0) {
    ticks = millisec / portTICK_PERIOD_MS;
    if (ticks == 0) {
      ticks = 1;
    }
  }  
  
  if (inHandlerMode())
  {
    ret.status = osErrorISR;  /*Not allowed in ISR*/
  }
  else
  {
    if(xTaskNotifyWait( 0,(uint32_t) signals, (uint32_t *)&ret.value.signals, ticks) != pdTRUE)
    {
      if(ticks == 0)  ret.status = osOK;
      else  ret.status = osEventTimeout;
    }
    else if(ret.value.signals < 0)
    {
      ret.status =  osErrorValue;     
    }
    else  ret.status =  osEventSignal;
  }
#else
  (void) signals;
  (void) millisec;
	
  ret.status =  osErrorOS;	/* Task Notification not supported */
#endif
  
  return ret;
}

功能:接收某个信号。
参数:

  • signals:指定要接收信号
  • millisec:超时设置
    0: 不管有没有成功收到信号都立即返回
    osWaitForever:没有收到信号时休眠(阻塞),直到收到信号为止
    其它值,比如 100:如果没有收到信号时休眠阻塞 100ms,然后就超时返回,继续往
    后运行。
  • 返回值:返回类型为 osEvent 这个结构体类型
    事实上信号、消息队列、邮箱队列都会用到这个结构体类型。
typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;
  • osStatus status :存放的枚举值用于表示是收到消息成功、失败或者超时。
    如果成功接收到信号:status 里面就放 osEventSignal,表示成功收到信号,所以当检测到 status的值为osEventSignal 时就表示成功收到了信号。至于说消息队列、邮箱队列的情况后面再介绍。如果接收失败:status 里面放的就是错误码。如果超时:里面放的就是osEventTimeout
  • value:联合体

联合体(union)的使用方法及其本质
C语言 | 联合体详解
【动画教程】研讨共用体,探究大小端存储模式(C语言)

  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value

当使用的信号这种通信方式时,value里面的放的是信号这个整形数,我们此时应该使用signal来获取这个整形数,至于vp,后面讲消息队列和邮箱队列时再介绍。

  • def:联合体
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;   

使用信号通信时,用不到def这个成员,同样的后面讲消息队列和邮箱队列时再介绍。

4.5、例子

osEvent evt = osSignalWait(3, 1000);// 在 1s 内如果没有收到信号 3 的话,就超时返回
if(evt.status == osEventSignal)
{
	printf("signal = %d\r\n", evt.value.signals);
	... //处理信号通知的事情
	...
}
else if(evt.status == osEventTimeout)
{
	printf("超时\r\n");
}
else
{
	printf("出错了\r\n");
}

1.6、案例

我们将之前通过全局变量通信的案例,改为使用信号来通信。

按键线程 ——> 信号 ——> LED 线程

为了简单一点,我们这里只使用k1LED1

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
uint8_t keyValue = 0;

/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
uint8_t KEY_Scan(void);
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
    /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
    /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
    /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
    /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of myTask01 */
  osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
  myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

  /* definition and creation of myTask02 */
  osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
  myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */
	osEvent evt;
	static uint8_t flag1=0;	
	for(;;)
	{
		//没有收到信号时就一直休眠
		evt = osSignalWait(1, osWaitForever);//接收任务2发送来的信号数字 1,osWaitForever宏定义为0xFFFFFFFF
		if((evt.status == osEventSignal) && (1 == evt.value.signals))  
		{	
			SEGGER_RTT_printf(0, "recv signal = %d\r\n", evt.value.signals);			
			if(flag1 == 0) 	//点亮LED1
			{
				HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
				flag1 = 1;
			}
			else if(flag1 == 1) //熄灭LED1
			{
				HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
				flag1 = 0;
			}
		}
		//这里为什么没有超时打印?
		//因为如果没有是收到信号,而且也没有出错的话,osWaitForever会导致一直休眠,不会超时返回
		else
		{
			SEGGER_RTT_printf(0, "出错了\r\n");
		}
		osDelay(200);	
	}  
  
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	int32_t ret = 0;
	int keyValue = 0;	
	for(;;)
	{
		keyValue = KEY_Scan(); //获取按键的键值
		if(1 == keyValue)
		{
			ret = osSignalSet(myTask01Handle, 1); //向任务0的句柄ID 发送信号1,表示k1按下了
			if(0x80000000 == ret)
			{
				SEGGER_RTT_printf(0, "osSignalSet fail\r\n");//发送失败
			}
		}
		osDelay(200);	//延时200ms
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t KEY_Scan(void)
{
    //没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
    //被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
    //key_up检测到已经返回过一次键值了,不再返回键值
    static uint8_t key_up = 0;	
	
	
    int KEY1 = HAL_GPIO_ReadPin(KEY1_GPIO_Port, GPIO_PIN_3);
    int KEY2 = HAL_GPIO_ReadPin(KEY2_GPIO_Port, GPIO_PIN_4);

    if((key_up == 0) && (KEY1 == 0 || KEY2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
    {
        osDelay(100);                      //去抖动
        key_up = 1;                        //设置标志位,表示按下

        if(KEY1 == 0)return 1; 				//如果k1按下就返回1
        else if(KEY2 == 0)return 2; 		//如果k2按下就返回2
    }
    else if(KEY1 == 1 && KEY2 == 1) key_up = 0; 	 //按键松开,清标志位

    return 0;                              //无按键按下或松开了时就返回0
}

/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

如果线程里面要多次调用osSignalWait来接收多个信号时,不要将其设置为osWaitForever,这会导致多个osSignalWait相互堵,此时我们应该设置一个超时时间,如果没有收到信的话就超时返回,防止一直休眠下去,继续调用后面的osSignalWait,如此就不会照成多个osSignalWait
的相互阻塞。

二、消息队列

2.1、回顾信号

信号这种通信方式有缺点,缺点就是无法进行精确通信,信号只能告诉对方某事情发生了,但是无法告诉对方更多的信息,之所以这样是因为信号无法携带更多信息,这就好比长城上的狼烟信号,只能告诉你敌人来了,但是无法精确告诉你是什么敌人、有多少人、从哪个山头上来的等更多详细的信息,但是消息队列这种通信方式就可以发送更多的详细信息。

2.2、消息队列的实现原理

野火FreeRTOS 消息队列运作过程

上图描述了消息队列的原理,RTOS 提供了一个“队列”,发送消息就是将消息挂入队列,取消息就是从队列中将消息取出。

消息队列可以发送简单的整形数,也可以发送复杂数据,发送复杂数据时就通过结构体来封装,然后将结构变量的指针发送给对方,对方即可从结构中取出复杂信息。

2.3、宏

osFeature_MessageQ(打开消息队列的宏)

  • 宏为 1:表示可以使用消息队列的 API
  • 宏为 0:表示不能使用消息队列的 API
    也就说可以通过这个宏来开启和关闭消息队列的 API,这个宏默认就为 1,表示可以使用消息队列的 API,如果不可用的话,我们就没办法使用消息队列了。

osMessageQDef

宏原型

#define osMessageQDef(name, queue_sz, type) \
const osMessageQDef_t os_messageQ_def_##name = { (queue_sz), sizeof(type)}

这个宏的原理与osThreadDef是一样的。

osMessageQDef(msgq, 10, Msg *); 
等价于
const osMessageQDef_t os_messageQ_def_msgq = { (10), sizeof(Msg *)}
  • 作用:使用 osMessageQDef_t定义一个消息队列的结构体变量,后面创建消息队列时需要用到该变量。
  • 参数
    name:指定的名字,结构体变量名会在前面加上os_messageQ_def_前缀
    queue_sz:指定消息队列可容纳消息的上限,一般指定为 10即可
    type:指定消息的类型,如果是整形就指定为uint32_t等整数类型,如果是指针,就指定指针类型。

2.4、API

osMessageCreate(创建消息)

  • 函数原型:osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id)

  • 功能:使用osMessageQDef宏所定义的结构体变量来创建一个消息队列。这里讲的是CMSIS API,如果是FreeRTOS的原生API的话就是直接调用的xQueueCreate();函数。
  • 参数
    queue_def:通过osMessageQ 宏来指定osMessageQDef宏所定义结构体变量的地址。
    thread_id:指定创建该消息队里的那个线程的 ID,说白了就是记录下谁创建了这个线程,不过一般不需要,不需要时就设置为 NULL。

疑问:为什么是 NULL?
答:线程 ID句柄 的本质是一个指针,所以osThreadId这个类型是一个指针类型,由于是指针类型,不使用这个参数时就应该设置为 NULL。

  • 返回值:返回唯一识别消息队列的 ID,后续就是通过这个 ID 来操作消息队列。
  • 例子:
osMessageQId msgqId; //用于存放消息队列ID 句柄
/* 创建消息队列,创建消息队列的代码也可以放到某个线程函数中 */
osMessageQDef(msgq, 10, Msg *); 
msgqId = osMessageCreate(osMessageQ(msgq), NULL);	

osMessagePut(发送消息)

  • 函数原型:osStatus osMessagePut(osMessageQId queue_id,uint32_t info,uint32_t millisec)
  • 功能:发送消息
  • 参数:
    queue_id:指定消息队列的 ID,用于识别消息队列,然后将消息挂到该消息队列上。
    info:如果发送的是整形数就直接写该整形数,如果是指针则强转为 uint32_t 类型。
    millisec:超时设置
    osWaitForever:表示如果没有发送成功就一直休眠(阻塞),直到发送成功为止
    0:则表示不管发送成功没有都立即返回,发送消息时一般都设置为 0。
  • 其它值,比如 200:在 200ms 内如果没有发送成功的话就超时返回,继续往后运行。
  • 返回值:osStatus
  • 例子:

osMessageGet(接收消息)

  • 函数原型:osEvent osMessageGet(osMessageQId queue_id,uint32_t millisec)
  • 功能:接收消息
  • 参数
    queue_id:消息队列 ID,从指定从什么消息队列上获取取消息。
    millisec:超时设置
    osWaitForever:未接受到消息时一直阻塞,直到收到消息或者出错为止
    0:不管有没有接收到消息,函数被调用后都会立即返回,继续往后运行,不会阻塞
  • 其它值,比如 300:设置具体超时时间 300ms,如果在指定时间内容没有收到消息则超时返回,然后继续往后运行。
  • 返回值:osEvent,信号通信也用到了这个结构体
typedef struct  {
  osStatus                 status;     ///< status code: event or error information
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;
  • osStatus status
    成功接收到消息时:里面放的是osEventMessage,只要检测到里面放的是osEventMessage,就表示成功接收到了消息。超时:里面放的是osEventTimeout。错误:错误码
  • value
    当通信方式为消息队列时。
    如果传输的是一个整形数:value 里面放的这个整形数,使用v来获取这个整形数如果传递的是一个指针:value里面放的就是这个指针,此时我们需要使用p来获取该指针,由于类型为void *,使用是需要强转为需要的指针类型。
  • def
    目前使用的是消息队列,因此里面放的是消息队列的ID,通过osMailQId即可取出消息队列的ID句柄。
  • 例子:后面再举

案例

我们这里使用消息队列来发送按键的键值,并且发送“key k1 pressed”字符串给对方,此时我就需要将消息封装为结构体,然后把结构体变量的指针发送给对方,对方即可取出使用,封装的结构体类型则由我们自己来定义。

typedef struct message
{
uint8_t keyValue;
char buf[15];
}Msg;

具体代码:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h" //用到了RTT的打印,代替了串口,要包含这个头文件
#include "string.h" //用到了c库的strcpy函数,所以一定要包含这个头文件
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct message
{
	uint8_t keyValue;
	char buf[15];

}Msg;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
osMessageQId msgqId; //用于存放消息队列ID 句柄
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
uint8_t KEY_Scan(void);
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
    /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
    /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
    /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
    /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* definition and creation of myTask01 */
  osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
  myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

  /* definition and creation of myTask02 */
  osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
  myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* 创建消息队列,创建消息队列的代码也可以放到某个线程函数中 */
  osMessageQDef(msgq, 10, Msg *); 
  msgqId = osMessageCreate(osMessageQ(msgq), NULL);	
  /* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */

	osEvent evt;
	static uint8_t flag1=0;	
	Msg *msgp = NULL;

	for(;;)
	{		
		evt = osMessageGet(msgqId, osWaitForever); //接收消息
		if(evt.status == osEventMessage) 	//判断是否成功收到消息
		{	
			msgp = (Msg *)evt.value.p; 		//将void *转为Msg *
			
			SEGGER_RTT_printf(0,"%s\r\n", msgp->buf);
			if(msgp->keyValue == 1) 			//判断k1按键是否按下
			{
				if(flag1 == 0) 				//点亮LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
					flag1 = 1;
				}
				else if(flag1 == 1)			//熄灭LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
					flag1 = 0;
				}
			}
		}
		else 
		{
			SEGGER_RTT_printf(0,"osMessageGet error\r\n");
		}
		
		osDelay(200);	
	}
  
  
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	osStatus ret = 0;
	Msg msg;
	uint8_t keyValue;//暂存键值
	for(;;)
	{
		keyValue = KEY_Scan();
		if(keyValue != 0) 	//如果按键按下就发送消息
		{
			msg.keyValue = keyValue;//设置按键的键值
			strcpy(msg.buf, "key k1 pressed");
			
			/* 发送消息,这里发送的是指针,一定要将指针强转为uint32_t
			 * 0表示不管发送是否成功都立即返回,不休眠阻塞 */
			ret = osMessagePut(msgqId,(uint32_t)&msg, 0);	
			if(ret != osOK)
			{
				SEGGER_RTT_printf(0,"osMessagePut error\r\n");
			}
		}
		osDelay(200);	//延时200ms
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t KEY_Scan(void)
{
    //没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
    //被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
    //key_up检测到已经返回过一次键值了,不再返回键值
    static uint8_t key_up = 0;	
	
	
    int KEY1 = HAL_GPIO_ReadPin(KEY1_GPIO_Port, GPIO_PIN_3);
    int KEY2 = HAL_GPIO_ReadPin(KEY2_GPIO_Port, GPIO_PIN_4);

    if((key_up == 0) && (KEY1 == 0 || KEY2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
    {
        osDelay(100);                      //去抖动
        key_up = 1;                        //设置标志位,表示按下

        if(KEY1 == 0)return 1; 				//如果k1按下就返回1
        else if(KEY2 == 0)return 2; 		//如果k2按下就返回2
    }
    else if(KEY1 == 1 && KEY2 == 1) key_up = 0; 	 //按键松开,清标志位

    return 0;                              //无按键按下或松开了时就返回0
}

/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

实际上也可以以整形的传递指针,取数据时就通过value中的v取,然后再将整形数强制转为指针后再使用,如果以整形的方式来传递指针的话,以上例子中的部分代码需要改为如下形式:

osMessageQDef(msgq, 10, uint32_t); //uint32_t 表示传递的是整形数
msgp = (Msg *)evt.value.v; //由于传递的是整形数,使用 v 来取数据,然后强转为 Msg *

三、内存池

3.1、内存池的作用

从堆中动态分配空间时我们可以使用malloc函数,但是为了安全起见,RTOS提供了更加安全的内存池这个东西,作用与malloc一样,所以运行RTOS时完全可以使用内存池来代替C库的malloc 函数。

在前面的消息队列的例子中,发送消息的线程会将msg地址发送给对方线程,结构体变量msg为线程函数的局部变量,而且msg里面包含非常占用空间的数组,由于线程函数的栈比较小(一般指定为 128),为了减少对栈空间的消耗,我们完全可以使用内存池来开辟msg的空间

不建议发送局部变量指针的原因还有一个,那就是当函数运行结束后栈会被释放,此时操作的局部变量空间就是非法空间。

3.2、宏

osFeature_Pool(打开内存池的宏)

  • 宏原型:#define osFeature_Pool 1
  • 作用
    为 1:内存池可用
    为 0:内存池不可用
    通过osFeature_Pool宏可以选择内存池的 API 是否可用,默认就是 1,因此内存池是可用的。

osPoolDef(定义内存池结构体的宏)

  • 宏原型
#define osPoolDef(name, no, type) \
const osPoolDef_t os_pool_def_##name = {(no), sizeof(type), NULL}
  • 作用:使用osPoolDef_t类型来定义内存池的结构体变量(数据结构),后续用于创建内存池。
  • 参数
    name:指定名字,结构体变量会加os_pool_def_前缀。
    no:指定内存池的最大块数,内存池是按块来管理的。
    type:指定开辟空间时所用的类型
    例子:在下面

osPool

  • 宏原型:#define osPool(name) &os_pool_def_##name
  • 作用:再指定名字前加上 os_pool_def_前缀并取地址
    例子:

3.3、API

osPoolCreate(创建内存池)

  • 函数原型:osPoolId osPoolCreate(const osPoolDef_t *pool_def)
  • 功能:使用osPoolDef所定义的数据结构来创建一个内存池
  • 参数:指定osPoolDef宏所定义的结构体变量的地址,此时需要使用osPool
  • 返回值:调用成功就返回唯一识别内存池的ID,否者就返回 NULL
  • 例子:

osPoolAlloc(开辟内存空间1)

  • 函数原型:void * osPoolAlloc(osPoolId pool_id)
  • 功能:从pool_id所指向的内存池中开辟空间,大小为osPoolDef第三个参数所指定类型的大小,osPoolAlloc的特点是开辟空间后不会对空间请 0。
  • 参数:
    pool_id:内存池 ID
  • 返回值:调用成功就返回开辟空间的地址(指针),开辟空间失败就返回 NULL
    由于返回类型是void *,因此需要强转为需要的指针类型。
  • 例子:在下面

osPoolCAlloc(开辟内存空间2)

  • 函数原型:void * osPoolCAlloc(osPoolId pool_id)
  • 功能:作用与osPoolAlloc一样,只不过这个函数会对开辟的空间进行清零。
  • 例子:

osPoolFree(释放开辟的空间)

  • 函数原型:osStatus osPoolFree(osPoolId pool_id,void *block)
  • 功能:释放开辟的空间
  • 参数:
    pool_id:内存池 ID
    block:所开辟空间的指针
  • 返回值:osStatus
  • 例子:在下面

案例

修改消息队列的例子,将局部变量msg 改为从通过内存池来开辟空间。

这里只给出被做了改动的StartTask02线程函数的代码,其它代码与消息队列例子的代码完全一样,添加和被改动过的地方会使用===来标记。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h" //用到了RTT的打印,代替了串口,要包含这个头文件
#include "string.h" //用到了c库的strcpy函数,所以一定要包含这个头文件
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct message
{
	uint8_t keyValue;
	char buf[15];

}Msg;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
osMessageQId msgqId; //用于存放消息队列ID 句柄
osPoolId poolId;	//用于存放内存池ID 句柄
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
uint8_t KEY_Scan(void);
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* USER CODE BEGIN RTOS_MUTEX */
	/* add mutexes, ... */
	/* USER CODE END RTOS_MUTEX */

	/* USER CODE BEGIN RTOS_SEMAPHORES */
	/* add semaphores, ... */
	/* USER CODE END RTOS_SEMAPHORES */

	/* USER CODE BEGIN RTOS_TIMERS */
	/* start timers, add new ones, ... */
	/* USER CODE END RTOS_TIMERS */

	/* USER CODE BEGIN RTOS_QUEUES */
	/* add queues, ... */
	/* USER CODE END RTOS_QUEUES */

	/* Create the thread(s) */
	/* definition and creation of myTask01 */
	osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
	myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

	/* definition and creation of myTask02 */
	osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
	myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

	/* USER CODE BEGIN RTOS_THREADS */
	/* 创建消息队列,创建消息队列的代码也可以放到某个线程函数中 */
	osMessageQDef(msgq, 10, Msg *); 
	msgqId = osMessageCreate(osMessageQ(msgq), NULL);	

	/* 创建内存池 */
	osPoolDef(memPl, 8, Msg);	
	poolId = osPoolCreate(osPool(memPl));	
	/* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */

	osEvent evt;
	static uint8_t flag1=0;	
	Msg *msgp = NULL;

	for(;;)
	{		
		evt = osMessageGet(msgqId, osWaitForever); //接收消息
		if(evt.status == osEventMessage) 	//判断是否成功收到消息
		{	
			msgp = (Msg *)evt.value.p; 		//将void *转为Msg *
			
			SEGGER_RTT_printf(0,"%s\r\n", msgp->buf);
			if(msgp->keyValue == 1) 			//判断k1按键是否按下
			{
				if(flag1 == 0) 				//点亮LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
					flag1 = 1;
				}
				else if(flag1 == 1)			//熄灭LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
					flag1 = 0;
				}
			}
		}
		else 
		{
			SEGGER_RTT_printf(0,"osMessageGet error\r\n");
		}
		
		osDelay(200);	
	}
  
  
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	osStatus ret = 0;
	Msg *msgp = NULL;										//========
	uint8_t keyValue;//暂存键值
	
	//使用内存池来开辟空间,msgp指向开辟的空间
	msgp = (Msg *)osPoolCAlloc(poolId); 					//========		
	if(NULL == msgp) 										//========
	{														//========
		SEGGER_RTT_printf(0,"osPoolCAlloc 开辟空间失败\r\n");//========
	} 														//========
	for(;;)
	{
		keyValue = KEY_Scan();
		if(keyValue != 0) 	//如果按键按下就发送消息
		{
			msgp->keyValue = keyValue;//设置按键的键值 (.改为->)//========
			strcpy(msgp->buf, "key k1 pressed");				  //========
			
			/* 发送消息,这里发送的是指针,一定要将指针强转为uint32_t
			 * 0表示不管发送是否成功都立即返回,不休眠阻塞 */
			ret = osMessagePut(msgqId,(uint32_t)msgp, 0);		 //========
			if(ret != osOK)
			{
				SEGGER_RTT_printf(0,"osMessagePut error\r\n");
			}
		}
		osDelay(200);	//延时200ms
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t KEY_Scan(void)
{
    //没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
    //被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
    //key_up检测到已经返回过一次键值了,不再返回键值
    static uint8_t key_up = 0;	
	
	
    int KEY1 = HAL_GPIO_ReadPin(KEY1_GPIO_Port, GPIO_PIN_3);
    int KEY2 = HAL_GPIO_ReadPin(KEY2_GPIO_Port, GPIO_PIN_4);

    if((key_up == 0) && (KEY1 == 0 || KEY2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
    {
        osDelay(100);                      //去抖动
        key_up = 1;                        //设置标志位,表示按下

        if(KEY1 == 0)return 1; 				//如果k1按下就返回1
        else if(KEY2 == 0)return 2; 		//如果k2按下就返回2
    }
    else if(KEY1 == 1 && KEY2 == 1) key_up = 0; 	 //按键松开,清标志位

    return 0;                              //无按键按下或松开了时就返回0
}

/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

 例子代码并没有调用osPoolFree来释放空间,是因为程序在运行的过程中需要一直使用,不需要释放,程序结束时会自动释放,因此不需要调用osPoolFree来主动释放,如果在程序结束之前就要释放空间的话,我们就需要调用osPoolFree来主动释放。

释放举例:

if(osOK != osPoolFree(poolId, msgp))
{
	printf("释放失败\r\n");
}

实际上可以不用每次都重新发msg的地址,只需要发送一次即可,只要对方拿到空间的指针后,两个线程即可以共享操作同一片空间,其中一个线程修改了空间的数据后,另一个线程就从里面读出修改后数据,这其实就是一个共享内存。

四、邮箱队列

在内存池的案例中,我们使用内存池来开辟msg的空间,然后使用消息队列将空间指针发送给另一个线程,这样一来这两个线程就可以共操作同一个空间,以实现数据交换

使用消息队列和内存池这种结合方式时,不仅需要创建消息队列,还要创建内存池,这就会比较麻烦,而邮箱队里可以一步到位,所以邮箱队列就是消息队列和内存池相结合后的产物,查看邮箱队列的源码时你会发现,内部封装的就是消息队列和内存池。

所以,邮箱队列的作用就是开辟一段内存空间,然后将空间指针发送给对方线程

4.1、宏

osFeature_MailQ

  • 宏原型:#define osFeature_MailQ 1
  • 作用
    为 1:表示邮箱队列可用
    为 0:表示邮箱队列不可用
    osFeature_MailQ宏默认为 1,因此邮箱队列可用。

osMailQDef

宏原型

#define osMailQDef(name, queue_sz, type) \
struct os_mailQ_cb *os_mailQ_cb_##name; \
const osMailQDef_t os_mailQ_def_##name = { (queue_sz), sizeof(type), (
&os_mailQ_cb_##name) }
  • 作用:定义两个结构体变量
    使用osMailQDef_t定义邮箱队列的结构体变量,变量名的前缀为os_mailQ_def_。后续需要使用这个变量来创建邮箱队列。
    定义os_mailQ_cb_前缀的结构体指针变量,这个指针变量会用于初始化os_mailQ_def_前缀的结构体变量。
  • 参数
    name:指定名字。
    queue_sz:指定邮箱队列所挂消息上限。
    type:类型,开辟空间时,空间大小就是这个类型的大小。

osMailQ

  • 宏原型:#define osMailQ(name) &os_mailQ_def_##name
  • 作用:给名字添加os_mailQ_def_前缀,构建出结构体变量名并取地址。

4.2、API

osMailCreate(创建邮箱队列)

  • 函数原型:osMailQId osMailCreate(const osMailQDef_t *queue_def,osThreadId thread_id)
  • 功能:创建邮箱队列
  • 参数
    queue_def:指定 osMailQDef 宏所定义结构体变量的地址,需要用到 osMailQ 宏
    thread_id:指定接收消息的那个线程的 ID
  • 返回值:函数调用成功就返回邮箱队列的 ID,否者就返回 NULL

osMailAlloc(在内存池中开辟空间)

  • 函数原型:void *osMailAlloc(osMailQId queue_id, uint32_t millisec)
  • 功能:在内存池中开辟空间,由于内部封装了内存池,所以就是在内存池中开辟空间。
  • 参数
    queue_id:邮箱队列的 ID。
    millisec:指定超时时间。
    0:不管是否成功开辟空间,函数都会立即返回。
  • osWaitForever:如果开辟空间不成功会则一直阻塞,直到成功为止。其它值比如 200:如果 200ms 内没有开辟成功就超时返回
  • 返回值:函数调用成功就返回开辟空间的指针,否者就返回 NULL

osMailCAlloc

  • 函数原型:void * osMailCAlloc(osMailQId queue_id,uint32_t millisec)
    *功能:功能与osMailAlloc相同,只不过会将空间清0。

osMailFree(释放开辟的空间)

  • 函数原型:osStatus osMailFree(osMailQId queue_id, void *mail)
  • 功能:释放开辟的空间
  • 参数
    queue_id:邮箱队列的 ID
    mail:指向空间的指针
  • 返回值:osStatus

osMailPut(发送邮箱消息)

  • 函数原型:osStatus osMailPut(osMailQId queue_id, void *mail)
  • 功能:发送邮箱消息,其实就是将开辟空间的指针发送给对方
  • 参数
    queue_id:邮箱队列的 ID
    mail:开辟空间的指针
  • 返回值:osStatus

osMailGet

  • 函数原型:osEvent osMailGet(osMailQId queue_id, uint32_t millisec)
  • 功能:接收邮箱消息。
  • 参数:
    queue_id:邮箱队列的 I
    millisec:超时时间
    0:不管是否接受成功,都立即返回
  • osWaitForever:如果接收不成功则休眠(阻塞),直到接收到为止
    其它值比如 200:在 200ms 内如果没有接收到就超时返回
  • osEvent
typedef struct  {
  osStatus                 status;     ///< //存放是否得到消息,或者消息接收失败
  union  {
    uint32_t                    v;     ///< message as 32-bit value
    void                       *p;     ///< message or mail as void pointer
    int32_t               signals;     ///< signal flags
  } value;                             ///< event value
  union  {
    osMailQId             mail_id;     ///< mail id obtained by \ref osMailCreate
    osMessageQId       message_id;     ///< message id obtained by \ref osMessageCreate
  } def;                               ///< event definition
} osEvent;
  • osStatus status
    成功接收到邮箱消息时:里面放的是osEventMail,只要检测到里面放的是osEventMail,就表示成功接收到了邮箱消息。
    超时:里面放的是osEventTimeout
    错误:错误码

  • value
    传递邮箱消息时,本质传递的是空间的指针,该指针就放在了value中,此时需要使用p来获取该指针。

  • def
    使用邮箱队列时里面放的是邮箱队列的ID,此时需要使用message_id来获取ID

案例

将消息队列与内存池相结合的案例改为使用邮箱队列。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h" //用到了RTT的打印,代替了串口,要包含这个头文件
#include "string.h" //用到了c库的strcpy函数,所以一定要包含这个头文件
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct message
{
	uint8_t keyValue;
	char buf[15];

}Msg;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
osMailQId mailqId; //用于存放邮箱队列ID 句柄
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
uint8_t KEY_Scan(void);
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* USER CODE BEGIN RTOS_MUTEX */
	/* add mutexes, ... */
	/* USER CODE END RTOS_MUTEX */

	/* USER CODE BEGIN RTOS_SEMAPHORES */
	/* add semaphores, ... */
	/* USER CODE END RTOS_SEMAPHORES */

	/* USER CODE BEGIN RTOS_TIMERS */
	/* start timers, add new ones, ... */
	/* USER CODE END RTOS_TIMERS */

	/* USER CODE BEGIN RTOS_QUEUES */
	/* add queues, ... */
	/* USER CODE END RTOS_QUEUES */

	/* Create the thread(s) */
	/* definition and creation of myTask01 */
	osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
	myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

	/* definition and creation of myTask02 */
	osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
	myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

	/* USER CODE BEGIN RTOS_THREADS */
	/* 创建邮箱队列,创建消息队列的代码也可以放到某个线程函数中 */
	osMailQDef(mailq, 8, Msg *);
	mailqId = osMailCreate(osMailQ(mailq), myTask01Handle);
	/* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */

	osEvent evt;
	static uint8_t flag1=0;	
	Msg *msgp = NULL;

	for(;;)
	{		
		evt = osMailGet(mailqId, osWaitForever); //接收消息
		if(evt.status == osEventMessage) 	//判断是否成功收到消息
		{	
			msgp = (Msg *)evt.value.p; 		//将void *转为Msg *
			
			SEGGER_RTT_printf(0,"%s\r\n", msgp->buf);
			if(msgp->keyValue == 1) 			//判断k1按键是否按下
			{
				if(flag1 == 0) 				//点亮LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);
					flag1 = 1;
				}
				else if(flag1 == 1)			//熄灭LED1
				{
					HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_SET);
					flag1 = 0;
				}
			}
		}
		else 
		{
			SEGGER_RTT_printf(0,"osMessageGet error\r\n");
		}
		
		osDelay(200);	
	} 
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	osStatus ret = 0;
	Msg *msgp = NULL;									
	uint8_t keyValue;//暂存键值
	
	//开辟空间
	msgp = (Msg *)osMailCAlloc(mailqId, osWaitForever); 	//========		
	if(NULL == msgp) 									
	{													
		SEGGER_RTT_printf(0,"osMailCAlloc 开辟空间失败\r\n");//========
	} 														
	for(;;)
	{
		keyValue = KEY_Scan();
		if(keyValue != 0) 	//如果按键按下就发送消息
		{
			msgp->keyValue = keyValue;//设置按键的键值 (.改为->)//========
			strcpy(msgp->buf, "key k1 pressed");				  //========
			
			ret = osMailPut(mailqId, msgp);		 //========
			if(ret != osOK)
			{
				SEGGER_RTT_printf(0,"osMailPut error\r\n");
			}
		}
		osDelay(200);	//延时200ms
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t KEY_Scan(void)
{
    //没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
    //被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
    //key_up检测到已经返回过一次键值了,不再返回键值
    static uint8_t key_up = 0;	
	
	
    int KEY1 = HAL_GPIO_ReadPin(KEY1_GPIO_Port, GPIO_PIN_3);
    int KEY2 = HAL_GPIO_ReadPin(KEY2_GPIO_Port, GPIO_PIN_4);

    if((key_up == 0) && (KEY1 == 0 || KEY2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
    {
        osDelay(100);                      //去抖动
        key_up = 1;                        //设置标志位,表示按下

        if(KEY1 == 0)return 1; 				//如果k1按下就返回1
        else if(KEY2 == 0)return 2; 		//如果k2按下就返回2
    }
    else if(KEY1 == 1 && KEY2 == 1) key_up = 0; 	 //按键松开,清标志位

    return 0;                              //无按键按下或松开了时就返回0
}

/* USER CODE END Application */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

五、互斥锁

5.1、线程切换对资源操作的影响

我们知道线程间会进行切换,这种切换可能会导致一种问题,那就是当多个线程共享操作同一个资源时,有时需要每个线程的操作必须被完整地执行,然后其它线程才能操作资源,但是线程切换往往可能会中断当前线程的操作,然后当其它线程接着操作该资源时,可能就会干扰上一个线程的操作。

5.2、什么是锁,有什么作用

锁就是一个数据结构,用于标记资源是否可以被操作,如果上锁了就不能操作,否者就可以操作,说白了锁可以防止受到线程切换的影响。

多线程操作同一资源时,为了防止线程间切换所带来的影响,需要加锁,而且用的一定是同一把锁,否则就没有加锁效果。
加锁的原理:如下图所示。

5.3、疑问:什么是互斥?

答:就是当我在操作资源时如果没有操作完的话,你就不能操作,你在操作时如果没有操作完的话我不能操作,我们上图的内容来具体理解什么是互斥。互斥锁所实现的就是互斥功能,互斥功能的作用就是防止切换所带来的影响。

假设 A 线程先运行,最开始时是没有上锁的,A 线程加锁成功,然后开始读写文件,如果读写的代码执行了一部分就切换到了其线程上时,比如切换到了 B 线程,此时由于没有运行解锁代码,因此还处于加锁状态,当 B 线程去加锁时由于 A 线程没有解锁,因此会加锁失败,此时就会休眠(阻塞),不会对文件进行读写,B 线程就不会干扰到 A 线程之前读写数据,特别是写,不会在 A 线程写的数据中间插入 B 线程所写入的数据。

由于 B 线程因为加锁失败而进入休眠,此时就会就让出 CPU,然后切换到其它线程上比如 C线程,此时因为同样的原因,C 线程也会加锁失败然后休眠,当再次切换到 A 线程时继续执行被中断的代码,然后解锁,此时再切换到其它线程上时就可以加锁成功。

总之,通过互斥锁就可以让每个线程在操作资源时,保证自己的操作不会因为线程切换而被其它线程所干扰,当然如果如果这种干扰不会任何负面影响的话,此时完全可以不用加锁。

5.4、宏

osMutexDef

  • 宏原型:#define osMutexDef(name) const osMutexDef_t os_mutex_def_##name = { 0 }
  • 作用:使用osMutexDef_t类型定义互斥锁的结构体变量,名字的前缀为os_mutex_def_
  • 参数:指定名字。
osMutexDef(mutex);
等价于
const osMutexDef_t os_mutex_def_mutex = { 0 }

osMutex

  • 宏原型:#define osMutex( name) &os_mutex_def_##name
  • 作用:在名字前面加os_mutex_def_并取地址

5.5、API

osMutexCreate

  • 函数原型:osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
  • 功能:创建一个互斥锁
  • 参数:指定osMutexDef宏所定义结构体变量的地址,此时需要使用osMutex宏* 返回值:函数调用成功就返回唯一识别互斥锁的 ID,否者就返回 NULL

osMutexDelete

  • 函数原型:osStatus osMutexDelete(osMutexId mutex_id)
  • 功能:删除互斥锁,此时会将互斥锁所用的数据结构空间都释放
  • 参数:互斥锁 ID
  • 返回值:osStatus

osMutexWait

  • 函数原型:osStatus osMutexWait(osMutexId mutex_id, uint32_t millisec)
  • 功能:加锁,如果之前就已经上锁了,此时会加锁失败
  • 参数
    mutex_id:互斥锁 ID
    millisec:超时设置
    0:不管加锁否调用成功,函数都会立即返回,这种情况用的很少osWaitForever:如果加锁失败就会永远阻塞,直到加锁成功为止。其它值比如 200:如果 200ms 内没有加锁成功就超时返回。
  • 返回值:osStatus

osMutexRelease

  • 函数原型:osStatus osMutexRelease(osMutexId mutex_id)
  • 功能:解锁
  • 参数:
    mutex_id:互斥锁 ID
  • 返回值:osStatus

举例:

	for(;;)
	{
		osMutexWait(mutexId, osWaitForever);//加锁
		SEGGER_RTT_printf(0, "11111111111\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "22222222222\r\n");
		osDelay(100);				
		SEGGER_RTT_printf(0, "33333333333\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "44444444444\r\n");
		osDelay(100);
		osMutexRelease(mutexId);	//解锁
		osDelay(1000);	
	}

案例

由于我们没有移植文件系统的代码,因此没办法通过操作文件来举例,所以我们就使用RTT打印来举例。先看如下代码:两个线程都向串口打印输出,串口即为共同操作的资源

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
osMutexId mutexId; //用于存放互斥锁的ID
/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* USER CODE BEGIN RTOS_MUTEX */
	/* add mutexes, ... */
	/* USER CODE END RTOS_MUTEX */

	/* USER CODE BEGIN RTOS_SEMAPHORES */
	/* add semaphores, ... */
	/* USER CODE END RTOS_SEMAPHORES */

	/* USER CODE BEGIN RTOS_TIMERS */
	/* start timers, add new ones, ... */
	/* USER CODE END RTOS_TIMERS */

	/* USER CODE BEGIN RTOS_QUEUES */
	/* add queues, ... */
	/* USER CODE END RTOS_QUEUES */

	/* Create the thread(s) */
	/* definition and creation of myTask01 */
	osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
	myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

	/* definition and creation of myTask02 */
	osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
	myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

	/* USER CODE BEGIN RTOS_THREADS */
	/* 创建互斥锁 */
	osMutexDef(mutex);
	mutexId = osMutexCreate(osMutex(mutex));	
	/* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */

	for(;;)
	{
		osMutexWait(mutexId, osWaitForever);//加锁
		SEGGER_RTT_printf(0, "11111111111\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "22222222222\r\n");
		osDelay(100);				
		SEGGER_RTT_printf(0, "33333333333\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "44444444444\r\n");
		osDelay(100);
		osMutexRelease(mutexId);	//解锁
		osDelay(1000);	
	}
  
  
  
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	for(;;)
	{
		osMutexWait(mutexId, osWaitForever);//加锁
		SEGGER_RTT_printf(0, "aaaaaaaaaaa\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "bbbbbbbbbbb\r\n");
		osDelay(100);				
		SEGGER_RTT_printf(0, "ccccccccccc\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "ddddddddddd\r\n");
		osDelay(100);
		osMutexRelease(mutexId);	//解锁
		osDelay(1000);	
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

试验结果

六、信号量

6.1、信号量的作用

信号量分为了二值信号量和多值信号量,二值信号量的作用与互斥锁一样,至于多值信号量的作用后面再介绍。

6.2、宏

osSemaphoreDef

  • 宏原型:
#define osSemaphoreDef (name) const osSemaphoreDef_t os_semaphore_def_##name = { 0 }
  • 作用:使用osSemaphoreDef_t类型来定义信号量的结构体变量,前缀为 os_semaphore_def_
  • 参数:指定名字

osSemaphore

  • 宏原型:#define osSemaphore(name) &os_semaphore_def_##name
  • 作用:给名字添加os_semaphore_def_前缀并取地址

API

osSemaphoreCreate

  • 函数原型:
    osSemaphoreId osSemaphoreCreate(const osSemaphoreDef_t *semaphore_def, int32_t count)
  • 作用:创建信号量
  • 参数
    semaphore_def:指定osSemaphoreDef宏所定义结构体变量的指针,此时需要用到osSemaphore宏。
    count:指定初始资源数,有关什么是资源数后面再介绍。
  • 返回值:函数调用成功就返回唯一识别信号量的 ID,否者就返回 NULL。

osSemaphoreDelete

  • 函数原型:osStatus osSemaphoreDelete(osSemaphoreId semaphore_id)
  • 功能:删除信号量,删除时释放信号量数据结构所用的空间
  • 参数:
    semaphore_id: 信号量 ID
  • 返回值:osStatus

osSemaphoreWait

  • 函数原型:int32_t osSemaphoreWait(osSemaphoreId semaphore_id, uint32_t millisec)
  • 功能:使用资源(加锁),资源数-1,表示已经占用了一个资源
  • 参数
    semaphore_id:信号量 ID
    millisec:超时设置
    0:不管成功与否都立即返回
  • osWaitForever:如果无法获取资源则休眠,直到获取到资源位置。
    其它值比如 200:200ms 内如果没有获取到资源,此时就是超时返回
  • 返回值:函数调用成功就返回减 1 后的资源数,否者就返回-1

osSemaphoreRelease

  • 函数原型:osStatus osSemaphoreRelease(osSemaphoreId semaphore_id)
  • 功能:释放资源,资源时资源数+1,表示多了一个可用资源
  • 参数:信号量 ID
  • 返回值:osStatus

二值信号量

什么是二值信号量

二值信号量的作用就是互斥,所以二值信号量其实也是一个互斥锁。

信号量需要设置资源数,对于二值信号量来说,资源数为就 0 和 1 两个值。

  • 为 1:表示有一个资源可用,占用资源后资源数就-1 变为 0资源可用其实就是表示可以加锁。
  • 为 0:表示资源不可用,此时不可以占用资源,其实就是不可以加锁只有当线程将资源释放后 0 变 1,此时别人才能占用资源(加锁)。释放资源后资源数从 0 变 1,其实就是在解锁。

对于二值信号量来说,最开始时一定要将资源数设置为 1,如果设置为 0 的话将无法占用资源,也就是说无法加锁,由于只有 0、1 这两种状态,所以要么处于加锁状态,要么处于解锁状态,此时二值信号量就是一个互斥锁。

二值信号量案例

将前面互斥锁的案例,改为使用二值信号量来实现互斥。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "SEGGER_RTT.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
osSemaphoreId semId; //用于存放互斥锁的ID
/* USER CODE END Variables */
osThreadId myTask01Handle;
osThreadId myTask02Handle;

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */

void StartTask01(void const * argument);
void StartTask02(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* USER CODE BEGIN RTOS_MUTEX */
	/* add mutexes, ... */
	/* USER CODE END RTOS_MUTEX */

	/* USER CODE BEGIN RTOS_SEMAPHORES */
	/* add semaphores, ... */
	/* USER CODE END RTOS_SEMAPHORES */

	/* USER CODE BEGIN RTOS_TIMERS */
	/* start timers, add new ones, ... */
	/* USER CODE END RTOS_TIMERS */

	/* USER CODE BEGIN RTOS_QUEUES */
	/* add queues, ... */
	/* USER CODE END RTOS_QUEUES */

	/* Create the thread(s) */
	/* definition and creation of myTask01 */
	osThreadDef(myTask01, StartTask01, osPriorityNormal, 0, 128);
	myTask01Handle = osThreadCreate(osThread(myTask01), NULL);

	/* definition and creation of myTask02 */
	osThreadDef(myTask02, StartTask02, osPriorityNormal, 0, 128);
	myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

	/* USER CODE BEGIN RTOS_THREADS */
	/* 创建二值信号量 */
	osSemaphoreDef(sem);
	semId = osSemaphoreCreate(osSemaphore(sem), 1);	
	/* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTask01 */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartTask01 */
void StartTask01(void const * argument)
{
  /* USER CODE BEGIN StartTask01 */
  /* Infinite loop */

	for(;;)
	{
		osSemaphoreWait(semId, osWaitForever); //获取资源,资源数-1(加锁)
		SEGGER_RTT_printf(0, "zhiguoxin01\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "zhiguoxin02\r\n");
		osDelay(100);				
		SEGGER_RTT_printf(0, "zhiguoxin03\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "zhiguoxin04\r\n");
		osDelay(100);
		osSemaphoreRelease(semId); //释放资源,资源数+1(解锁)
		osDelay(1000);	
	}
  
  
  
  /* USER CODE END StartTask01 */
}

/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void const * argument)
{
  /* USER CODE BEGIN StartTask02 */
    /* Infinite loop */	
	for(;;)
	{
		osSemaphoreWait(semId, osWaitForever); //获取资源,资源数-1(加锁)
		SEGGER_RTT_printf(0, "zhiguoxin0a\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "zhiguoxin0b\r\n");
		osDelay(100);				
		SEGGER_RTT_printf(0, "zhiguoxin0c\r\n");
		osDelay(100);
		SEGGER_RTT_printf(0, "zhiguoxin0d\r\n");
		osDelay(100);
		osSemaphoreRelease(semId);//释放资源,资源数+1(解锁)
		osDelay(1000);	
	}
  /* USER CODE END StartTask02 */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
/* USER CODE END Application */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

多值信号量

多值信号量的特点

对于二值信号量来说,二值信号量的值只有 0 和 1 这两个,而多值信号量则有多个,比如将初始时资源数设置为 2 的话,就有 0、1、2 三个值,这就是多值的,当然你也可以将资源数设置为 3、4 等值。

多值信号量的作用

僧多粥少时,想保证固定个数的线程去操作资源时,就可以使用多值信号量,我们这里举例理解,比如我的项目有这样一个需求,总共 5 个线程,但是每次只允许 3 个线程能操作文件,剩余两个线程则不能,至于到底是那三个线程能够操作,就看谁占用到了资源,此时就需要用到多值信号量。

创建信号量时将资源数设置为 3:
第一个线程:占用一个资源后,资源数为 2,资源还有两个可用
第二个线程:占用一个资源后,资源数为 1,资源还有一个可用
第三个线程:占用一个资源后,资源数为 0,资源目前不可用

只有三个线程能操作资源,当其中某个释放了资源,然后资源数+1 后,此时其它没有占用资源的线程则可以占用,总之只能有三个线程能操作资源,其它两个线程会因资源为 0 而休眠,直到有人释放资源为止。

创建信号量时设置的资源数到底代表了什么

代表允许多少个线程去操作资源。

  • 1:每次只允许有一个线程去操作资源,此时为互斥关系,这就是二值信号量
  • 比如 3:每次允许有 3 个线程去操作资源,这就是多值信号量

多值信号量的案例

我们前面在配置时已经使能了“多值信号量”,大家可以自己去创建 5 个线程,然后串口打印,此时可以使用多值信号量来只允许 3 个线程打印,具体代码请自行实现。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-07 10:59:11  更:2021-09-07 11:01:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 8:33:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计