【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() 中断服务函数中来维护。
前面提到的 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重定向
static uint8_t s_uart1_rxch;
char g_uart1_rxbuf[256];
uint8_t g_uart1_bytes;
………..省略………
#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)
{
。。。。
HAL_UART_Receive_IT(&huart1, &s_uart1_rxch, 1);
}
3)在uart.h里面添加头文件和定义
#include <stdio.h>
#include <string.h>
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)
void MX_USART1_UART_Init(void);
9、跑马灯跑起来
跑马灯跑起来 在 freertos.c 文件中我们能看到我们创建的任务函数: void Start_RedLed_toggle(void const * argument) 我们需要在任务函数中写跑马灯程序:
void Start_RedLed_toggle(void const * argument)
{
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);
}
}
void start_GreenLed_toggle(void const * argument)
{
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);
}
}
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 文件中,找到我们创建的两个任务,修改代码。
void Start_RedLed_toggle(void const * argument)
{
osStatus xReturn = osErrorValue;
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);
}
}
}
void start_GreenLed_toggle(void const * argument)
{
osStatus xReturn;
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);
}
}
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)
{
osStatus xReturn = osErrorValue;
for(;;)
{
if(HAL_GPIO_ReadPin(Key2_GPIO_Port, Key2_Pin) == GPIO_PIN_RESET)
{
xReturn = osSemaphoreWait(myCountingSem01Handle,
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" );
}
}
}
}
void start_GreenLed_toggle(void const * argument)
{
osStatus xReturn;
for(;;)
{
if(HAL_GPIO_ReadPin(Key1_GPIO_Port, Key1_Pin) == GPIO_PIN_RESET)
{
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);
}
}
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博客
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)