在freertos中,信号量的作用大多是用来同步任务的。而信号量又分为4种信号量,分别是二值信号量、计数信号量、互斥量和递归信号量。这次主要是来介绍二值信号量和计数信号量。
二值信号量的就好比一个消息队列,它与消息队列不同之处在于消息队列可以存放很多数据,而二值信号量只能存放一个数据。二值信号量不关心它存放的是什么数据,它只要知道自己有数据还是没数据这两种状态,也可以用0和1来表示。二值信号量为1,说明它有数据,为0说明它没数据,所以这也是二值信号量这个名词的由来。
#include "semphr.h" //头文件
SemaphoreHandle_t BinarySem_Handle; //二值信号量句柄
BinarySem_Handle = xSemaphoreCreateBinary(); //创建函数
void vSemaphoreDelete(BinarySem_Handle); //删除二值信号量函数
以上是二值信号量的创建和删除函数。首先定义头文件和句柄,然后调用xSemaphoreCreateBinary()来创建二值信号。最后一个是删除函数,在参数里写上想要删除的二值信号量的句柄即可。
xSemaphoreGive( BinarySem_Handle ); //释放二值信号量
这是常规的二值信号量释放函数,非常简单,就是把要释放的二值信号量的句柄 进去就好了。但是这个函数只能在任务中使用,不能在中断里使用。
xSemaphoreGiveFromISR(BinarySem_Handle,pdTRUE);
这是中断里的二值信号量函数,第一个参数 是二值信号量的句柄,第二个参数可以给NULL。
xSemaphoreTake(BinarySem_Handle,portMAX_DELAY); //获取信号量
二值信号量的获取函数有两个参数,第一个是二值信号量的句柄,第二个是阻塞时间。阻塞的时间可以设定自己想要的时间,我这里给的是portMax_DELAY。这是freertos的一个宏定义,表示一个最大时间,也就是一直阻塞在这里。 和上面一样,这个获取函数也不能在中断里使用。
xSemaphoreTakeFromISR(BinarySem_Handle,pdTRUE);
这是中断里使用的二值信号量获取函数,第一个参数是二值信号量句柄,第二个参数可以给NULL。
接下来做一个小实验,在stm32c8t6上面设置两个按键key1和key2来分别获取和释放信号量,看看效果。
SemaphoreHandle_t BinarySem_Handle; //二值信号量句柄
TaskHandle_t startTask_handler; //总任务的句柄
TaskHandle_t ledTask_handler; //led的句柄
TaskHandle_t giveTask_handler; //获取信号量的句柄
TaskHandle_t takeTask_handler; //释放信号量的句柄
void startTask(void *arg)
{
BaseType_t xReturn = pdFALSE; //创建接收值
taskENTER_CRITICAL(); //临界区
BinarySem_Handle = xSemaphoreCreateBinary();
vSemaphoreDelete(BinarySem_Handle);
xReturn = xTaskCreate(ledTask,"ledTask",64,NULL,1,&ledTask_handler); //创建led任务
xReturn = xTaskCreate(giveTask,"giveTask",64,NULL,3,&giveTask_handler); //创建发送任务
xReturn = xTaskCreate(takeTask,"takeTask",64,NULL,2,&takeTask_handler); //创建接收任务
if(xReturn == pdTRUE)
{
printf("Task create ok\n");
}
else
{
printf("Task create error\n");
}
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //临界区
}
我这里创建了4个任务,分别是开始任务startTask(void *arg)、led任务、释放函数任务和获取函数任务。开始任务就是用来创建其他3个任务的。
void ledTask(void *arg)
{
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
}
}
led任务主要起到心跳包的作用,让它一直闪烁,用来判断我们的程序释放运行正常。
void giveTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xSemaphoreGive( BinarySem_Handle ); //释放二值信号量
if(xReturn == pdFALSE)
{
printf("give error\n");
}
else
{
printf("give ok\n");
}
}
vTaskDelay(200);
}
}
当key2被按下时,也就是PA1被按下时,就释放信号量。定义一个xReturn变量来接收xSemaphoreGive()函数的返回值,并把状态在串口助手打印出来。最后再加个200ms的延时,不加的话可能会多次释放。
void takeTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY); //获取信号量
if(xReturn == pdTRUE)
{
printf("take ok\n");
}
}
}
}
当key1被按下时,也就是PA0被按下时,就开始获取信号量,并定义一个变量xReturn来接收xSemaphoreTake()函数的返回值。
首先按下复位键,串口第一行打印Task create ok,说明所有任务创建成功。按下key2释放二值信号量,按下key1获取信号量。接着我多次按下key1,串口打印give error 说明释放失败。因为已经没有信号量了,所有无法释放。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "queue.h"
#include "led.h"
#include "stdlib.h"
#include "semphr.h"
SemaphoreHandle_t BinarySem_Handle; //二值信号量句柄
TaskHandle_t startTask_handler; //总任务的句柄
TaskHandle_t ledTask_handler; //led的句柄
TaskHandle_t giveTask_handler; //获取信号量的句柄
TaskHandle_t takeTask_handler; //释放信号量的句柄
void ledTask(void *arg)
{
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
}
}
void giveTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xSemaphoreGive( BinarySem_Handle ); //释放二值信号量
if(xReturn == pdFALSE)
{
printf("give error\n");
}
else
{
printf("give ok\n");
}
}
vTaskDelay(200);
}
}
void takeTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY); //获取信号量
if(xReturn == pdTRUE)
{
printf("take ok\n");
}
}
}
}
void startTask(void *arg)
{
BaseType_t xReturn = pdFALSE; //创建接收值
taskENTER_CRITICAL(); //临界区
BinarySem_Handle = xSemaphoreCreateBinary();
vSemaphoreDelete(BinarySem_Handle);
xReturn = xTaskCreate(ledTask,"ledTask",64,NULL,1,&ledTask_handler); //创建led任务
xReturn = xTaskCreate(giveTask,"giveTask",64,NULL,3,&giveTask_handler); //创建发送任务
xReturn = xTaskCreate(takeTask,"takeTask",64,NULL,2,&takeTask_handler); //创建接收任务
if(xReturn == pdTRUE)
{
printf("Task create ok\n");
}
else
{
printf("Task create error\n");
}
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //临界区
}
int main(void)
{
LED_Init();
usrt1_init(9600);
xTaskCreate(startTask,"startTask",512,NULL,1,&startTask_handler); //创建开始任务
vTaskStartScheduler();
}
计数信号量其实就相当于消息队列,消息队列关心队列里的值,而计数信号量则不关心信号量的值,只关心信号量是否有值。计数信号量和二值信号量的区别是,二值信号量只能释放一次或者获取一次,而计数信号量能多次释放和获取。
#include "semphr.h"
SemaphoreHandle_t CountSem_Handle ; //计数信号量句柄
CountSem_Handle = xSemaphoreCreateCounting(5,3); //创建计数信号量函数
void vSemaphoreDelete(BinarySem_Handle); //删除计数信号量函数
其实,计数信号量和二值信号量的api函数都是相同的,不同就在于它们的创建方法。计数信号量函数xSemaphoreCreateCounting()的有两个参数,第一个参数是计数信号量的计数最大值,第二个参数是计数信号量的初始值。
两个参数我分别给5和3,代表我创建的计数信号量最大值为5,初始计数值为3,也就是从3开始计数。当我每次释放时,初始计数值就会减1,每次获取初始值就会加1。
因为二值信号量的释放函数和获取函数和计数信号量的释放函数和获取函数都是一样的,所以在这里就不多赘述。
我们的实验和二值信号量的实验是一样的,都是通过按下key1、key2来释放和获取信号量,可以看看它们之间的区别。
void giveTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xSemaphoreGive( CountSem_Handle ); //释放二值信号量
if(xReturn == pdFALSE)
{
printf("give error\n");
}
else
{
printf("give ok\n");
}
}
vTaskDelay(200);
}
}
释放函数的代码逻辑和二值信号量的逻辑是一致的,唯一不同的是它们调用的句柄不同。
void takeTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xSemaphoreTake(CountSem_Handle,0); //获取信号量
if(xReturn == pdTRUE)
{
printf("take ok\n");
}
else if(xReturn == NULL)
{
printf("take error\n");
}
}
vTaskDelay(200);
}
}
获取函数的逻辑也是和二值信号量的逻辑一致,不同点就是它们调用的句柄和阻塞时间。我都阻塞时间设置为0代表一按下key1就直接获取,但是执行完逻辑之后要给个延时,防止它一次性获取多次。
首先点击复位,串口打印Task create ok,说明所有任务都创建成功了。第2和第3行是用来测试计数信号量是否能正常运行。
随后我一连获取了4次信号量,可以看到第4次获取失败了,原因是此时的计数信号量已经满了。 然后我又一连释放了7次计数信号量,前5次都是成功的,后面两次都失败了。因为我设置的计数信号量最大计数值为5,我一连释放了7次,第5次释放时已经释放完了,所有才会释放失败。最后再获取再释放就成功了。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "queue.h"
#include "led.h"
#include "stdlib.h"
#include "semphr.h"
SemaphoreHandle_t CountSem_Handle ; //计数信号量句柄
TaskHandle_t startTask_handler; //总任务的句柄
TaskHandle_t ledTask_handler; //led的句柄
TaskHandle_t giveTask_handler; //获取信号量的句柄
TaskHandle_t takeTask_handler; //释放信号量的句柄
void ledTask(void *arg)
{
while(1)
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
vTaskDelay(500);
}
}
void giveTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET)
{
xReturn = xSemaphoreGive( CountSem_Handle ); //释放二值信号量
if(xReturn == pdFALSE)
{
printf("give error\n");
}
else
{
printf("give ok\n");
}
}
vTaskDelay(200);
}
}
void takeTask(void *arg)
{
BaseType_t xReturn = pdFALSE;
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == RESET)
{
xReturn = xSemaphoreTake(CountSem_Handle,0); //获取信号量
if(xReturn == pdTRUE)
{
printf("take ok\n");
}
else if(xReturn == NULL)
{
printf("take error\n");
}
}
vTaskDelay(200);
}
}
void startTask(void *arg)
{
BaseType_t xReturn = pdFALSE; //创建接收值
taskENTER_CRITICAL(); //临界区
CountSem_Handle = xSemaphoreCreateCounting(5,3);
xReturn = xTaskCreate(ledTask,"ledTask",64,NULL,1,&ledTask_handler); //创建led任务
xReturn = xTaskCreate(giveTask,"giveTask",64,NULL,3,&giveTask_handler); //创建发送任务
xReturn = xTaskCreate(takeTask,"takeTask",64,NULL,2,&takeTask_handler); //创建接收任务
if(xReturn == pdTRUE)
{
printf("Task create ok\n");
}
else
{
printf("Task create error\n");
}
vTaskDelete(startTask_handler); //删除开始任务
taskEXIT_CRITICAL(); //临界区
}
int main(void)
{
LED_Init();
usrt1_init(9600);
xTaskCreate(startTask,"startTask",512,NULL,1,&startTask_handler); //创建开始任务
vTaskStartScheduler();
}