本文是FreeRTOS复习笔记的第七节,信号量。
上一篇文章: 【复习笔记】FreeRTOS(六) 队列操作
文章目录
- 一、信号量分类
- 二、二值信号量
- 2.1.实验设计
- 2.2.测试例程
- 2.3.实验效果
- 三、计数信号量
- 3.1.实验设计
- 3.2.测试例程
- 3.3.实验效果
一、信号量分类
信号量是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,其实信号量主要的功能就是实现任务之间的同步与互斥,实现的方式主要就是依靠队列(信号量是特殊的队列)的任务阻塞机制。
队列也可以实现同步与互斥那为什么还要信号量?
因为信号量相比队列更节省空间,因为实现同步与互斥不需要传递数据,所以信号量没有队列后面的环形存储区,信号量主要就是依靠计数值uxMessagesWaiting(在队列中表示队列现有消息个数,在信号量中表示有效信号量个数)。
信号量的本质的都是特殊的队列,信号量只有队列头部,并没有后面的环形存储区,也就是说信号量只负责消息传递,并不传递数据。
信号量分为二值信号量、计数信号量、互斥信号量和递归互斥信号量。
本篇文章主要复习一下二值信号量和计数信号量。
二、二值信号量
二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况。二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步。
二值信号量相关API函数:
函数 | 作用 |
---|---|
xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
2.1.实验设计
实验目的:学会对FreeRTOS 二值信号量的使用
实验设计:将设计两个任务:
task1:用于按键扫描,当检测到按键KEY0被按下时,释放二值信号量
task2:获取二值信号量,当成功获取后打印提示信息
2.2.测试例程
主函数 main.c代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
QueueHandle_t semphore_handle;
void task1_task(void *p); //任务函数
void task2_task(void *p); //任务函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化LED端口
KEY_Init(); //初始化按键
/*动态创建二值信号量*/
semphore_handle = xSemaphoreCreateBinary();
if(semphore_handle==NULL)
{
printf("信号量创建失败!\r\n");
}
xTaskCreate(task1_task,"task1_task",128,NULL,2,NULL); //任务1
xTaskCreate(task2_task,"task2_task",128,NULL,2,NULL); //任务2
vTaskStartScheduler(); //开启任务调度
}
/* 任务一,释放二值信号量 */
void task1_task(void *p)
{
uint8_t key = 0;
BaseType_t err;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
if(semphore_handle != NULL)
{
err = xSemaphoreGive(semphore_handle);
if(err == pdPASS)
{
taskENTER_CRITICAL(); //进入临界区
printf("Semaphore release success!\r\n");
taskEXIT_CRITICAL(); //退出临界区
}
else
{
taskENTER_CRITICAL(); //进入临界区
printf("Semaphore release failure!\r\n");
taskEXIT_CRITICAL(); //退出临界区
}
}
}
vTaskDelay(10);
}
}
/* 任务二,获取二值信号量 */
void task2_task(void *p)
{
uint32_t i = 0;
BaseType_t err;
while(1)
{
err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
taskENTER_CRITICAL(); //进入临界区
printf("Obtaining semaphore success!\r\n");
taskEXIT_CRITICAL(); //退出临界区
}
else
{
taskENTER_CRITICAL(); //进入临界区
printf("Time out:%d\r\n",++i);
taskEXIT_CRITICAL(); //退出临界区
}
}
}
程序上需要注意的是,每次使用printf()函数都添加了临界区保护,因为printf()比较耗时,如果不加临界区保护,打印长字符串的时候,打印到一半就被切换到另一个任务,这样很容易打印乱码。
2.3.实验效果
实验效果如下:
打开串口工具,每次按下按键,串口就会打印 Semaphore release success!代表成功释放了二值信号量,随后立即打印Obtaining semaphore success!代表成功捕获了二值信号量。
三、计数信号量
计数信号量也比较简单,计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
计数型信号量适用场合:
- 事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1) ,这种场合一般在创建时将初始计数值设置为 0 。
- 资源管理:信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1 )才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目。
通俗地说就是,信号量释放:cnt++,信号量被获取:cnt - -。
二值信号量相关API函数:
函数 | 作用 |
---|---|
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
计数型信号量的释放函数和获取函数与二值信号量相同 。
3.1.实验设计
实验目的:学会对FreeRTOS 计数信号量的使用
实验设计:将设计两个任务:
task1:用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量。
task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值。
3.2.测试例程
主函数 main.c代码如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
QueueHandle_t count_semphore_handle;
void task1_task(void *p); //任务函数
void task2_task(void *p); //任务函数
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口
LED_Init(); //初始化LED端口
KEY_Init(); //初始化按键
/* 创建计数型信号量 */
count_semphore_handle = xSemaphoreCreateCounting(100 , 0); //最大计数值设为100,初始值为0
if(count_semphore_handle==NULL)
{
printf("信号量创建失败!\r\n");
}
xTaskCreate(task1_task,"task1_task",128,NULL,2,NULL); //任务1
xTaskCreate(task2_task,"task2_task",128,NULL,2,NULL); //任务2
vTaskStartScheduler(); //开启任务调度
}
/* 任务一,释放计数型信号量 */
void task1_task(void *p)
{
uint8_t key = 0;
while(1)
{
key = KEY_Scan(0);
if(key == WKUP_PRES)
{
if(count_semphore_handle != NULL)
{
xSemaphoreGive(count_semphore_handle); /* 释放信号量 */
}
}
vTaskDelay(10);
}
}
/* 任务二,获取计数型信号量 */
void task2_task(void *p)
{
BaseType_t err;
while(1)
{
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
taskENTER_CRITICAL(); //进入临界区
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
taskEXIT_CRITICAL(); //退出临界区
}
vTaskDelay(1000);
}
}
3.3.实验效果
实验效果如下:
烧录程序,打开串口工具,复位。由于初始值设为0,按键没有按下的时候,串口不会打印任何东西。在1000ms内快速按下按键,就可以看到打印出信号量的计数值。按下按键的频率越快,信号量的计数值越大;没有按键按下的时候,信号量的计数值会自动减少直到0。
如果超过1000ms才按下,信号量的计数值就也会变成0.
本节主要是学习和掌握二值信号量、计数信号量的基本使用。
完整程序放在gitee上:程序下载。