一、简介信号量:
概述
信号量是操作系统中重要的一部分,信号量一般用来进行资源管理和任务同步,FreeRTOS中信号量又分为二值信号量、计数型信号量、互斥信号量和递归互斥信号量。不同的信号量其应用场景不同,但有些应用场景是可以互换着使用的。
信号量的特性
信号量这个名字很恰当:
信号:起通知作用
量:还可以用来表示资源的数量
当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
计数型信号量的典型场景是:
计数:事件产生时"give"信号量,让计数值加1;处理事件时要先"take"信号量,就是获得信号量,让计数值减1。
资源管理:要想访问资源需要先"take"信号量,让计数值减1;用完资源后"give"信号量,让计数值加1。
信号量的"give"、"take"双方并不需要相同,可以用于生产者-消费者场合:
生产者为任务A、B,消费者为任务C、D
一开始信号量的计数值为0,如果任务C、D想获得信号量,会有两种结果:
阻塞:买不到东西咱就等等吧,可以定个闹钟(超时时间)
即刻返回失败:不等
任务A、B可以生产资源,就是让信号量的计数值增加1,并且把等待这个资源的顾客唤醒
唤醒谁?谁优先级高就唤醒谁,如果大家优先级一样就唤醒等待时间最长的人
信号量跟队列的对比
二进制信号量跟计数型的唯一差别,就是计数值的最大值被限定为1。
差异列表如下:
队列 | 信号量 |
可以容纳多个数据, 创建队列时有2部分内存: 队列结构体、存储数据的空间 | 只有计数值,无法容纳其他数据。 创建信号量时,只需要分配信号量结构体 |
生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值已经达到最大时返回失败 |
消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 |
两种信号量的对比
信号量的计数值都有限制:限定了最大值。如果最大值被限定为1,那么它就是二进制信号量;如果最大值不是1,它就是计数型信号量。
差别列表如下:
二进制信号量 | 技术型信号量 |
被创建时初始值为0 | 被创建时初始值可以设定 |
其他操作是一样的 | 其他操作是一样的 |
运作机制
二值信号量:
计数信号量:
二值信号量使用案例
在以前,使用DTU模块的时候都是创建一个任务,在while中通过一直循环查询4G模块是否有发送消息过来,当有数据过来的时候再进行处理,这样的方式浪费CPU的资源。最理想的方法就是当没有网络数据的时候网络任务就进入阻塞态,把 CPU 让给其他的任务,当有数据的时候网络任务才去执行。使用了二值信号量,在中断服务函数只需要释放信号量,而在网络处理函数中就一直请求二值信号量,当请求到了,就可以去处理。假如一个中断只通知一个任务的话,可以使用任务通知功能来替代二值信号量,而且使用任务通知的话速度更快,代码量更少。
运行示例图:
二、工程创建:
1.配置RCC时钟
2.配置SYS,将Timebase Source修改为除滴答定时器外的其他定时器。
3.开启FreeRTOS,v1与v2版本不同,一般选用v1即可
4.创建二值信号量Binary Semaphore
Semaphore Name: 信号量名称
Allocation: 分配方式 Dynamic 动态内存创建
Conrol Block Name: 控制块名称
如果想要创建计数信号量Counting Semaphore,必须在 Config parameters 中进行使能。
Semaphore Name: 信号量名称
Count: 计数信号量的最大值
Allocation: 分配方式:Dynamic 动态内存创建
Conrol Block Name: 控制块名称
5.创建任务Task
我们创建两个任务,一个信号量接收任务,一个信号量发送任务。
6.GPIO设置
7.UART串口打印
8.生成代码设置
三、相关API说明
库函数:
二值信号量的创建 :xSemaphoreCreateBinary()
函数原型:
/* 创建一个二进制信号量,返回它的句柄。
* 此函数内部会分配信号量结构体
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
函数功能:通过此函数创建二值信号量,信号量所需的RAM是由FreeRTOS动态分配的。此函数创建好的二值信号量默认是空的,也就是说使用xSemaphoreTake()是获取不到的。
参数:xSemaphore:保存创建成功的二值信号量句柄。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。
创建以一个计数信号量:xSemaphoreCreateCounting()
函数原型:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
函数功能:此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配,真正完成信号量创建的是函数xQueueCreateCountingSemaphore()
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。
信号量的释放 :
1.任务中释放xSemaphoreGive()
BaseType_t xSemaphoreGive( xSemaphore )
函数功能:此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的。
参数:xSemaphore:要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败
2.中断中释放:xSemaphoreGiveFromISR()
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
函数功能:此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数xQueueGiveFromISR()。
参数:
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败
获取信号量
同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都是使用如下这两个函数。
1.任务中获取xSemaphoreTake()
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime)
函数功能:此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成的。
参数:
xSemaphore:要获取的信号量句柄。
xBlockTime: 阻塞时间。
返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
2.中断中获取:xSemaphoreTakeFromISR ()
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
函数功能:此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数xQueueReceiveFromISR ()。
参数:
xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 获取信号量成功。
errQUEUE_FULL: 获取信号量失败
备注:
在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken 设置为pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!
删除信号量:vSemaphoreDelete()
BaseType_t vSemaphoreDelete( xSemaphore )
函数功能:用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。无返回值。
参数:xSemaphore:要删除的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败
hal库:
创建二值信号量:osSemaphoreCreate
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
函数描述:创建一个二值量、并返回一个ID
参数:
semaphore_def: 引用由osSemaphoreDef定义的信号量
count: 信号量数量
返回值:成功返回定时器ID,失败返回0
删除信号量:osSemaphoreDelete
osStatus osSemaphoreDelete (osSemaphoreId semaphore_id)
函数描述:用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。
参数:semaphore_id: 信号量ID
返回值:错误码
释放信号量:osSemaphoreRelease
osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
函数描述:用于释放信号量的宏。释放的信号量对象必须是已经被创建的,可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数 xSemaphoreCreateRecursiveMutex() 创建的递归互斥量。可用在中断服务程序中。
参数:semaphore_id: 信号量ID
返回值:错误
获取信号量:osSemaphoreWait
osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
函数描述:用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。可用在中断服务程序中。
参数:
semaphore_id: 信号量ID
millisec:等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。如果宏 INCLUDE_vTaskSuspend 定义为 1 且形参 xTicksToWait 设置为 portMAX_DELAY ,则任务将一直阻塞在该信号量上(即没有超时时间)
返回值:错误
四、实验示例
二值信号量:
这里我创建了两个任务,释放信号量。一个接收信号量。
SendTask:
void SendTask(void const * argument)
{
/* USER CODE BEGIN SendTask */
osStatus xReturn;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)
{
osDelay(10);//消抖
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)
{
xReturn = osSemaphoreRelease(myBinarySem01Handle);//给出二值信号量
if(osOK == xReturn)
{
printf("release!\r\n");
}
else
{
printf("BinarySem release fail!\r\n");
}
}
}
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)//等待按键释放,防止连续发送
{
osDelay(10);
}
osDelay(100);
}
/* USER CODE END SendTask */
}
ReceiveTask:
void ReceiveTask(void const * argument)
{
/* USER CODE BEGIN ReceiveTask */
osStatus xReturn = osErrorValue;
/* Infinite loop */
for(;;)
{
xReturn = osSemaphoreWait(myBinarySem01Handle,
osWaitForever); /* 等待时间 */
if(osOK == xReturn)
{
printf("BinarySem get!\r\n");
}
}
/* USER CODE END ReceiveTask */
}
计数信号量:
SendTask:
void SendTask(void const * argument)
{
/* USER CODE BEGIN SendTask */
osStatus xReturn;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)
{
osDelay(10);//消抖
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)
{
xReturn = osSemaphoreRelease(CountSemHandle);// 给出计数信号量
if(osOK == xReturn)
{
printf( "Key2 is pressed to release 1 parking space.\n" );
}
else
{
printf( "Key2 is pressed, but there is no parking space to release!\n" );
}
}
}
while(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == 0)//等待按键释放,防止连续发送
{
osDelay(10);
}
osDelay(500);
}
/* USER CODE END SendTask */
}
ReceiveTask:
void ReceiveTask(void const * argument)
{
/* USER CODE BEGIN ReceiveTask */
osStatus xReturn = osErrorValue;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0)
{
osDelay(10);//消抖
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0)
{
xReturn = osSemaphoreWait(CountSemHandle, /* 计数信号量句柄 */
0); /* 等待时间:0 */
if(osOK == xReturn)
{
printf( "Key1 is pressed and successfully applied for parking space.\n" );
}
else
{
printf( "Key1 is pressed. Sorry, the parking lot is full now!\n" );
}
}
}
while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == 0)//等待按键释放,防止连续发送
{
osDelay(10);
}
osDelay(500);
}
/* USER CODE END ReceiveTask */
}