FreeRTOS学习---“信号量”篇

总目录

FreeRTOS学习—“任务”篇
FreeRTOS学习—“消息队列”篇
FreeRTOS学习—“信号量”篇
FreeRTOS学习—“事件组”篇
FreeRTOS学习—“定时器”篇

在“消息队列”篇中,我们曾经埋下一个伏笔,就是说,FreeRTOS中任务见的消息传递及同步,都是通过消息队列,难道通过全局变量不行吗?
答案是可以,不过这样会造成响应不及时,如果用全局变量,只能去轮询,必然会损失一定的时间,才能判断结果
FreeRTOS学习---“信号量”篇_第1张图片

那用信号量行不行啊?
当然行了,只不过,FreeRTOS中信号量的实现,也是通过消息队列做到的
FreeRTOS学习---“信号量”篇_第2张图片

二进制信号量

二值信号量比较直观,类似于红绿灯,创建的时候,默认是红灯的,其他任务都得不到这个信号量的,只有give之后才能变成绿灯,其他任务才能得到信号量,得以运行。(本质上,信号量是用队列实现的)上面是类比,在代码中,创建的时候,初始值是0,give是相当于将计数值设置为1,take后相当于将计数值清0。

demo程序

下面的程序创建了一个二值信号量,例如我们去操作某些硬件,读取I2C之类的,某一时刻,应该是只允许一个任务去操作,无论是读还是写,所以需要用这个信号量去控制一下,并且操作完之后,要及时让出CPU,使得其他任务能够得到信号量。

SemaphoreHandle_t xSemaphore = NULL;

// A task that uses the semaphore.
void WriteSomethingTask( void * pvParameters )
{
	long lValueToSend; 

	/* 该任务会被创建两个实例,所以写入队列的值通过任务入口参数传递 – 这种方式使得每个实例使用不同的值。队列创建时指定其数据单元为long型,所以把入口参数强制转换为数据单元要求的类型 */ 
	lValueToSend = ( long ) pvParameters; 
	for(;;)
	{
		if( xSemaphore != NULL )
		{
			if( xSemaphoreTake(xSemaphore,( TickType_t ) 10 ) == pdTRUE )
			{
				printf("tast:%ld get signal ,keep it 2 seconds\n",lValueToSend);
				{
					//my work
					vTaskDelay(2000 / portTICK_PERIOD_MS); 
				}
				xSemaphoreGive(xSemaphore);
				taskYIELD(); 
			}
			else
			{
			}
		}
	}
}

void app_main(void) 
{ 
	// Semaphore cannot be used before a call to xSemaphoreCreateBinary().
	// This is a macro so pass the variable in directly.
	xSemaphore = xSemaphoreCreateBinary();
	
	if( xSemaphore != NULL )
	{
		xSemaphoreGive(xSemaphore);
		xTaskCreate(WriteSomethingTask, "write1", 1000, ( void * ) 1, 1, NULL ); 
		xTaskCreate(WriteSomethingTask, "write2", 1000, ( void * ) 2, 1, NULL ); 
		xTaskCreate(WriteSomethingTask, "write3", 1000, ( void * ) 3, 1, NULL ); 
	}
}

创建二值信号量

SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数 含义
返回值 NULL 表示没有足够的堆空间分配给信号量而导致创建失败。
非 NULL 值表示信号量创建成功。此返回值应当保存下来,以作为操作此信号量的句柄。

二值信号量减一

portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );
参数 含义
xSemaphore 获取得到的信号量,信号量由定义为 xSemaphoreHandle 类型的变量引用。信号量在使用前必须先创建。
xTicksToWait 阻塞超时时间。任务进入阻塞态以等待信号量有效的最长时间。如果 xTicksToWait 为 0,则 xSemaphoreTake()在信号量无效时会立即返回。阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。常量 portTICK_RATE_MS 可以用来把心跳时间单位转换为毫秒时间单位。如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定 INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制。
返回值 有两个可能的返回值:
1. pdPASS 只有一种情况会返回 pdPASS,那就是成功获得信号量。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待信号量有效。如果在超时到来前信号量变为有效,亦可被成功获取,返回 pdPASS。
2. pdFALSE 未能获得信号量。如果设定了阻塞超时时间(xTicksToWait 非 0),在函数返回之前任务将被转移到阻塞态以等待信号量有效。但直到超时信号量也没有变为有效,所以不会获得信号量,返回 pdFALSE。

下面函数专门用于中断下的信号量获取

portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore, portBASE_TYPE *pxHigherPriorityTaskWoken );
参数 含义
xSemaphore 给出的信号量,信号量由定义为 xSemaphoreHandle 类型的变量引用。信号量在使用前必须先创建。
pxHigherPriorityTaskWoken 对某个信号量而言,可能有不止一个任务处于阻塞态在等待其有效。调用 xSemaphoreGiveFromISR()会让信号量变为有效,所以会让其中一个等待任务切出阻塞态。如果调用 xSemaphoreGiveFromISR()使得一个任务解除阻塞,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么 xSemaphoreGiveFromISR()会在函数内部将 *pxHigherPriorityTaskWoken 设 为pdTRUE。如 果 xSemaphoreGiveFromISR() 将此值设为pdTRUE,则在中断退出前应当进行一次上下文切换。这样才能保证中断直接返回到就绪态任务中优先级最高的任务中。
返回值 有两个可能的返回值:
1. pdPASS xSemaphoreGiveFromISR()调用成功。
2. pdFAIL 如果信号量已经有效,无法给出,则返回 pdFAIL。

二值信号量加一

void xSemaphoreGive( SemaphoreHandle_t xSemaphore )
参数 含义
xSemaphore 目标信号量的句柄。这个句柄即是调用 xSemaphoreCreateBinary()创建该信号量时的返回值。

多值信号量

在中断不频繁的系统中,使用二值信号量没有问题,但是中断频繁发生时,则会有中断丢失的问题。因为中断发生时延迟任务执行,延迟任务执行的过程中,如果又来了两次中断,则只会处理第一次,第二次将会丢失。为此引入多值信号量来处理这个问题。FreeRTOS学习---“信号量”篇_第3张图片

demo

这个demo模拟的是两个任务增加信号量,一个任务处理信号,就类似于接受多个消息,处理多次,然后不丢失记录。

SemaphoreHandle_t xSemaphore = NULL;

// A task that uses the semaphore.
void WriteSomethingTask1( void * pvParameters )
{
	int countme=0;
	for(;;)
	{
		printf("write1 %d\n",countme++);
		xSemaphoreGive(xSemaphore);
		vTaskDelay(1000 / portTICK_PERIOD_MS); 
		taskYIELD(); 
	}
}
// A task that uses the semaphore.
void WriteSomethingTask2( void * pvParameters )
{
	int countme=0;
	for(;;)
	{
		printf("write2 %d\n",countme++);
		xSemaphoreGive(xSemaphore);
		vTaskDelay(1000 / portTICK_PERIOD_MS); 
		taskYIELD(); 
	}
}

// A task that uses the semaphore.
void ReadSomethingTask( void * pvParameters )
{
	int countme=0;
	for(;;)
	{
		if(xSemaphoreTake(xSemaphore,( TickType_t ) 0 ) == pdTRUE )
		{
			printf("read %d\n",countme++);
		}
		else
		{
		}
		vTaskDelay(10 / portTICK_PERIOD_MS); 
		taskYIELD(); 
	}
}


void app_main(void) 
{ 

    xSemaphore = xSemaphoreCreateCounting( 10, 0 );
	
	if( xSemaphore != NULL )
	{
		xTaskCreate(WriteSomethingTask1, "write1", 1000, NULL, 1, NULL ); 
		xTaskCreate(WriteSomethingTask2, "write2", 1000, NULL, 1, NULL ); 
		xTaskCreate(ReadSomethingTask, "read", 1000, NULL, 1, NULL ); 
	}
}

多值信号量创建

SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount )
参数 含义
uxMaxCount 多值信号量最大值。
uxInitialCount 多值信号量初始值。
返回值 NULL 表示没有足够的堆空间分配给信号量而导致创建失败。
非 NULL 值表示信号量创建成功。此返回值应当保存下来,以作为操作此信号量的句柄。

其他操作与二值信号量函数一样。

互斥信号量

互斥量是一种特殊的二值信号量,用于控制在两个或多个任务间访问共享资源。单词MUTEX(互斥量)源于”MUTual EXclusion”。
在用于互斥的场合,互斥量从概念上可看作是与共享资源关联的令牌。一个任务想要合法地访问资源,其必须先成功地得到(Take)该资源对应的令牌(成为令牌持有者)。当令牌持有者完成资源使用,其必须马上归还(Give)令牌。只有归还了令牌,其它任务才可能成功持有,也才可能安全地访问该共享资源。一个任务除非持有了令牌,否则不允许访问共享资源。

如果某资源同时只准一个任务访问,可以用互斥量保护这个资源。这个资源一定是存在的,所以创建互斥量时会先释放一个互斥量,表示这个资源可以使用。任务想访问资源时,先获取互斥量,等使用完资源后,再释放它。也就是说互斥量一旦创建好后,要先获取,后释放,要在同一个任务中获取和释放。这也是互斥量和二进制信号量的一个重要区别,二进制信号量可以在随便一个任务中获取或释放,然后也可以在任意一个任务中释放或获取。互斥量不同于二进制信号量的还有:互斥量具有优先级继承机制,二进制信号量没有,互斥量不可以用于中断服务程序,二进制信号量可以。

简单说就是谁用谁释放。

demo

两个写任务,只有一个能写临界资源,所以用互斥信号量来控制。自己抢自己释放,这就是互斥。

SemaphoreHandle_t xSemaphore = NULL;

void WriteSomethingTask1( void * pvParameters )
{
	for(;;)
	{
		xSemaphoreTake(xSemaphore,portMAX_DELAY);
		printf("WriteSomethingTask1 work\n");
		xSemaphoreGive(xSemaphore);
		vTaskDelay(1000 / portTICK_PERIOD_MS); 
	}

}
void WriteSomethingTask2( void * pvParameters )
{
	for(;;)
	{
		xSemaphoreTake(xSemaphore,portMAX_DELAY);
		printf("WriteSomethingTask2 work\n");
		xSemaphoreGive(xSemaphore);
		vTaskDelay(1000 / portTICK_PERIOD_MS); 
	}

}


void app_main(void) 
{ 

    xSemaphore = xSemaphoreCreateMutex();
	
	if( xSemaphore != NULL )
	{
		xSemaphoreGive(xSemaphore);
		xTaskCreate(WriteSomethingTask1, "write1", 1000, NULL, 1, NULL ); 
		xTaskCreate(WriteSomethingTask2, "write2", 1000, NULL, 1, NULL ); 
	}
}

互斥信号量创建

SemaphoreHandle_t xSemaphoreCreateMutex( void )
参数 含义
返回值 NULL 表示没有足够的堆空间分配给信号量而导致创建失败。
非 NULL 值表示信号量创建成功。此返回值应当保存下来,以作为操作此信号量的句柄。

异常错误

在某次测试中,发现这个看门狗的报错,随机出现了程序重启,这个是因为有任务一直在运行,导致看门狗没有喂养,所以要保证每个任务,都有适当的休息时间。
在这里插入图片描述

结束语

闻听后面的小区有人坠楼身亡了,真是挺惋惜的,大过年的,现场也没有个家人,感觉像是意外,有人说是抑郁症,抑郁症这个名字听起来总感觉很轻微,为啥不叫病呢,起码能让人重视起来啊。哎,前几天的腾讯员工,也是如此。

轻度抑郁症长期表现为有情绪低落、不合群、离群、躯体不适、食欲不振及睡眠障碍。
自我缓解方法有:
平时多学习技术 坚持锻炼、努力多外出交际:
多观察多了解、平时整理好自己的想法写出来、多阅读书籍。

看见没,多学习技术,放在第一行了。老师我一直让你们多学习,是在帮你们远离疾病,那你们多学习,就是在自救。
FreeRTOS学习---“信号量”篇_第4张图片
FreeRTOS学习—“任务”篇
FreeRTOS学习—“消息队列”篇
FreeRTOS学习—“信号量”篇
FreeRTOS学习—“事件组”篇
FreeRTOS学习—“定时器”篇

你可能感兴趣的:(单片机,C语言典型代码,操作系统,FreeRTOS,信号量)