【FreeRTOS】二值信号量实现线程的同步

【FreeRTOS】二值信号量实现线程的同步

测试环境如下

stm32L431RCT6
MDK keil5
stm32cube + FreeRTOS

一、添加多个任务

1、引脚配置

LED使用的引脚PA8和PB2设置成output

在这里插入图片描述

在这里插入图片描述

将按键引脚PC9设置为Input
在这里插入图片描述

2、选择时钟源,配置时钟

在这里插入图片描述

在这里插入图片描述

3、调试模式和基础源时钟

在这里插入图片描述

在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:

  • HAL的时基,SYS Timebase Source
  • OS的时基(仅在使用OS的情况下才考虑)

而这些 “时基” 该去如何维护,主要分为两种情况考虑:

  • 裸机运行:

可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。

  • 带OS运行:

前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。

在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:在这里插入图片描述

建议不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,如果共用,潜在一定风险。

4、调试串口选择

在这里插入图片描述

需要在NVIC使能串口UART1中断

在这里插入图片描述

5、选择使用FREERTOS以及接口版本

在这里插入图片描述

Cortex微控制器软件接口标准(CMSIS)是独立于供应商的硬件抽象层,用于基于Arm Cortex处理器的微控制器。 CMSIS定义了通用工具接口,并提供一致的设备支持。 CMSIS软件接口简化了软件重用,CMSIS提供了到处理器和外围设备,实时操作系统以及中间件组件的接口。

V1使得软件能够在不同的RTOS系统下工作,为实时操作系统提供通用的API。 V2是在V1基础上的扩展了一些关于 Armv8-M的支持,动态对象,多核系统相关的东西。

6、创建任务

在这里插入图片描述

在这里插入图片描述

7、生成代码

在这里插入图片描述
在这里插入图片描述

8、添加UART串口打印代码

1)在uart.c里面添加printf重定向
/* USER CODE BEGIN 0 */
static uint8_t s_uart1_rxch;
char           g_uart1_rxbuf[256];
uint8_t        g_uart1_bytes;

/* USER CODE END 0 */
………..省略………
/* USER CODE BEGIN 1 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch,FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
	HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xFFFF);
	return ch;
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance==USART1)
	{
		if(g_uart1_bytes<sizeof(g_uart1_rxbuf))
		{
			g_uart1_rxbuf[g_uart1_bytes++]=s_uart1_rxch;
		}
		HAL_UART_Receive_IT(&huart1,&s_uart1_rxch,1);
	}
}

2)使能UART1的中断,在该函数中进行
void MX_UART1_UART_Init(void)
{
。。。。
  /* USER CODE BEGIN USART1_Init 2 */
  //中断接收的每个字节暂时放在s_uart1_rxch
  HAL_UART_Receive_IT(&huart1, &s_uart1_rxch, 1);
  /* USER CODE END USART1_Init 2 */

}

3)在uart.h里面添加头文件和定义

/* USER CODE BEGIN Includes */
#include <stdio.h>

#include <string.h>
/* USER CODE END Includes */


/* USER CODE BEGIN Private defines */
extern char       g_uart1_rxbuf[256];

extern uint8_t    g_uart1_bytes;

#define clear_uart1_rxbuf() do {memset(g_uart1_rxbuf,0,sizeof(g_uart1_rxbuf));\
        g_uart1_bytes=0; } while(0)


/* USER CODE END Private defines */
void MX_USART1_UART_Init(void);

/* USER CODE BEGIN Prototypes */


9、跑马灯跑起来

跑马灯跑起来 在 freertos.c 文件中我们能看到我们创建的任务函数: void Start_RedLed_toggle(void const * argument) 我们需要在任务函数中写跑马灯程序:

在这里插入图片描述

/* USER CODE END Header_Start_RedLed_toggle */
void Start_RedLed_toggle(void const * argument)
{
  /* USER CODE BEGIN Start_RedLed_toggle */
  /* Infinite loop */
  for(;;)
  {

    osDelay(500);
    HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_SET);
    printf("this is RED!\r\n");
    osDelay(500);
    HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_RESET);
    osDelay(500);
  }
  /* USER CODE END Start_RedLed_toggle */
}

/* USER CODE END Header_start_GreenLed_toggle */
void start_GreenLed_toggle(void const * argument)
{
  /* USER CODE BEGIN start_GreenLed_toggle */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
    HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_SET);
    printf("this is Green!\r\n");
    osDelay(1000);
    HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_RESET);
    osDelay(1000);
  }
  /* USER CODE END start_GreenLed_toggle */
}


10、运行测试

然后编译下载 ,可以观察到板子上的LED灯安装程序跑起来了,可以看到RedLed和GreenLed交替闪烁。1秒内红灯闪两次,绿灯闪一次。

在这里插入图片描述

二、实现多任务间的同步

在多任务处理系统中,如果一个任务开始访问资源,但在脱离运行状态之前没有完成其访问,则有可能出错。 如果任务使资源处于不一致状态,则通过任何其他任务或中断访问同一资源可能导致数据损坏或其他类似问题。

对任务之间或任务与中断之间共享的资源的访问必须使用**“互斥”技术进行管理**。 目标是确保一旦一个任务开始访问一个不可重入且不线程安全的共享资源,同一任务就具有对资源的独占访问权限,直到资源返回到一致状态为止。

FreeRTOS提供了几个可以用来实现互斥的特性,但是最好的互斥方法是(只要有可能,因为它不实用)设计应用程序,使资源不被共享,并且每个资源只从一个任务访问。

1、二值信号量

互斥信号是一种特殊类型的二进制信号量,用于控制对两个或多个任务之间共享的资源的访问。

信号量名副其实就是一个信号可以进行任务之前信息的交互,二值信号量通常用于互斥访问或同步。二值信号量就是一个只能保存一个数据的队列,这个队列要么是空要么是有他就只有两种状态。

2、创建信号量Semaphore

在 Timers and Semaphores 进行配置。

1)创建二值信号量Binary Semaphore,

这里比较简单我们只需要设置一下二值信号量的名字即可

在这里插入图片描述

  • Semaphore Name: 信号量名称
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Conrol Block Name: 控制块名称
2)代码编写
1.创建二值信号量

这部分代码cubeMX会自动生成
在这里插入图片描述

3、相关API介绍

(1) osSemaphoreCreate

用于创建一个二值信号量,并返回一个ID。

函数 osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
参数 semaphore_def: 引用由osSemaphoreDef定义的信号量 count: 信号量数量
返回值 成功返回信号量ID,失败返回0
(2)osSemaphoreDelete

用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。

函数 osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
参数 semaphore_id: 信号量ID
返回值 错误码
(3)osSemaphoreRelease

用于释放信号量的宏。释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex() 创建的递归互斥量。可用在中断服务程序中。

函数 osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
参数 semaphore_id: 信号量ID
返回值 错误码
(4)osSemaphoreWait

用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。可用在中断服务程序中。

函数 int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
参数 semaphore_id: 信号量ID millisec:等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。 如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值 错误码

4、二值信号量使用

在 freertos.c 文件中,找到我们创建的两个任务,修改代码。

/* USER CODE END Header_Start_RedLed_toggle */
void Start_RedLed_toggle(void const * argument)
{
  /* USER CODE BEGIN Start_RedLed_toggle */
	osStatus xReturn = osErrorValue;
  /* Infinite loop */
  for(;;)
  {
	xReturn = osSemaphoreWait(myBinarySem01Handle, /* 二值信号量句柄 */
	                               osWaitForever); /* 等待时间 */
	if(osOK == xReturn)
	{
		printf("BinarySem get!\n\n");
		osDelay(500);
		HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_RESET);
		printf("this is RED!\r\n");
		osDelay(500);
		HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_SET);
		osDelay(500);
	}
  }
  /* USER CODE END Start_RedLed_toggle */
}

/* USER CODE END Header_start_GreenLed_toggle */
void start_GreenLed_toggle(void const * argument)
{
  /* USER CODE BEGIN start_GreenLed_toggle */
        osStatus xReturn;
  /* Infinite loop */
  for(;;)
  {

         if(HAL_GPIO_ReadPin(Key1_GPIO_Port, Key1_Pin) == GPIO_PIN_RESET)
         {
                 xReturn = osSemaphoreRelease(myBinarySem01Handle);//给出二值信号量
                 if(osOK == xReturn)
                 {
                         printf("release!\r\n");
                         osDelay(1000);
                         HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_RESET);
                         printf("this is Green!\r\n");
                         osDelay(1000);
                         HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_SET);
                         osDelay(1000);
                  }
                  else
                  {
                     printf("BinarySem release fail!\r\n");
                  }
         }
         osDelay(100);

  }
  /* USER CODE END start_GreenLed_toggle */
}

5、运行测试

红灯尝试获取资源,由于资源是可用的,红灯任务成功的获取资源(红灯闪烁一次),绿灯任务尝试获取资源,但是资源被红灯任务持有,所以尝试失败,绿灯任务进入阻塞状态等待资源,允许红灯再次运行(红灯闪烁一次),按键按下资源被释放,绿灯任务退出阻塞状态,绿灯任务获取资源成功(绿灯闪烁一次)并且之后允许访问资源,当绿灯任务执行完之后也会将资源释放。资源可以再次用于两个任务之间。

在这里插入图片描述

2、计数信号量

在这里插入图片描述

这里需要使用两个按键,按键配置的方法和KEY1一样,实现按下KEY1申请资源,按下KEY2释放资源。

1)创建计数信号量Counting Semaphore

要想使用计数信号量必须在 Config parameters 中把 USE_COUNTING_SEMAPHORES 选择 Enabled 来使能。

用计数信号量实现二值信号量只需将计数信号量的初始值设置为 1 即可。

在这里插入图片描述

添加计数信号量
在这里插入图片描述

  • Semaphore Name: 信号量名称
  • Count: 计数信号量的最大值
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Conrol Block Name: 控制块名称

2)生成代码,计数信号量自动创建。

在这里插入图片描述

3)修改代码

void Start_RedLed_toggle(void const * argument)
{
  /* USER CODE BEGIN Start_RedLed_toggle */
	osStatus xReturn = osErrorValue;
  /* Infinite loop */
  for(;;)
  {
	/*xReturn = osSemaphoreWait(myBinarySem01Handle, // 二 ? 信号量句柄
	                            osWaitForever); // 等待时间
	*/

	if(HAL_GPIO_ReadPin(Key2_GPIO_Port, Key2_Pin) == GPIO_PIN_RESET)
	{
		 xReturn = osSemaphoreWait(myCountingSem01Handle, /* 计数信号量句柄 */
		                                        0); /* 等待时间:0 */

	 if(osOK == xReturn)
	 {
	      printf( "Key2 is pressed and successfully applied for parking space.\r\n" );
	      osDelay(500);
	      HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_RESET);
	      printf("this is RED!\r\n");
	      osDelay(500);
	      HAL_GPIO_WritePin(RedLED_GPIO_Port, RedLED_Pin, GPIO_PIN_SET);
	      osDelay(500);
	 }
	 else
	 {
	      printf( "Key2 is pressed. Sorry, the parking lot is full now!\r\n" );
	 }
	}
  }
  /* USER CODE END Start_RedLed_toggle */
}
void start_GreenLed_toggle(void const * argument)
{
  /* USER CODE BEGIN start_GreenLed_toggle */
	osStatus xReturn;
  /* Infinite loop */
  for(;;)
  {

	 if(HAL_GPIO_ReadPin(Key1_GPIO_Port, Key1_Pin) == GPIO_PIN_RESET)
	 {
		 //xReturn = osSemaphoreRelease(myBinarySem01Handle);//给出二 ? 信号量
		 xReturn = osSemaphoreRelease(myCountingSem01Handle);// 给出计数信号量

	 if(osOK == xReturn)
	 {
		 printf( "Key1 is pressed to release 1 parking space.\r\n" );
		 osDelay(1000);
		 HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_RESET);
		 printf("this is Green!\r\n");
		 osDelay(1000);
		 HAL_GPIO_WritePin(GreenLED_GPIO_Port, GreenLED_Pin, GPIO_PIN_SET);
		 osDelay(1000);
	  }
	  else
	  {
		  printf( "Key1 is pressed, but there is no parking space to release!\r\n" );
	  }
	 }
	 osDelay(100);

  }
  /* USER CODE END start_GreenLed_toggle */
}

4)运行测试

按下key2申请资源,按下key1释放资源,当申请资源数量为5时,再次申请资源会失败,只能等资源释放后才能再次申请。
在这里插入图片描述

参考资料:

配置说明:FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置) - 知乎 (zhihu.com)

二值信号量:(92条消息) FreeRTOS笔记篇:第七章 – 资源管理(互斥锁、二进制信号量、死锁)_墨客Y的博客-CSDN博客_freertos 死锁

创建二值信号量:(92条消息) STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)_Leung_ManWah的博客-CSDN博客_ossemaphorecreate

创建二值信号量:(92条消息) STM32CubeMX学习笔记(30)——FreeRTOS实时操作系统使用(信号量)_Leung_ManWah的博客-CSDN博客_ossemaphorecreate

(92条消息) STM32FreeRTOS二值信号量的基本介绍和操作_花落已飘的博客-CSDN博客

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值