FreeRTOS信号量与任务同步-1二值信号量

信号量是操作系统用来实现资源管理和任务同步的消息机制。FreeRTOS信号量分为二值信号量、计数信号量、互斥信号量和递归互斥信号量。可以将互斥信号量看成一种特殊的二值信号量,但互斥信号量和二值信号量之间还是有一些区别的。

  • 使用目的不同:二值信号量用于同步,可实现任务和任务之间及任务和中断之间的同步。互斥信号量用于互锁,保证在同一时间只有一个任务访问某个资源。
  • 操作方法不同:二值信号量在用于同步时,一般是一个任务(或中断)给出信号,另一个任务获取信号。互斥信号量必须在同一个任务中获取并在同一个任务中给出信号。
  • 使用场合不同:互斥信号量具有优先级继承机制,而二值信号量没有。互斥信号量不能用在中断服务函数中,而二值信号量可以。
  • 创建方法不同:用于创建互斥信号量和用于创建二值信号量的API函数不同,但是获取和给出信号的API函数相同。

1. 二值信号量 

二值信号量相当于只有一个队列项的队列,创建二值信号量与创建队列使用的是同一个函数。二值信号量只关心这个特殊的队列状态,要不为空,要不为满,并不关心队列中存放的是什么消息。
二值信号量主要用于同步,可实现任务和任务之间及任务和中断之间的同步。二值信号量用于实现任务和中断之间同步的工作过程如下。

1.1 任务因请求信号量而阻塞

任务通过xSemaphoreTake()函数试图获取信号量,但此时二值信号量无效,任务进入
阻塞态。

1.2 中断服务函数释放信号量

在任务阻塞过程中,有中断发生,在该中断的中断服务函数中用xSemaphoreGiveFromISR()函数释放了信号量,二值信号量变为有效的。

1.3 任务成功获取任务量

 任务获取二值信号量成功,任务解除阻塞,开始执行任务处理程序

1.4 任务因请求信号量再次进入阻塞态

通常任务函数都是一个大循环,在任务处理完相关事务后,又会再次调用xSemaphoreTake()函数试图再次获取信号量,但此时二值信号量已无效,任务再次进入阻塞态。

2. 创建二值信号量

FreeRTOS 创建二值信号量可使用两个宏:vSemaphoreCreateBinary()和xSemaphoreCreateBinary()。这两个宏最终被队列创建函数xQueueGenericCreate()替换,使用这两个宏创建二值信号量,需要将宏configSUPPORT_DYNAMIC_ALLOCATION设置为1。

2.1 vSemaphoreCreateBinary()

vSemaphoreCreateBinaryO是旧版本的FreeRTOS 使用的二值信号量创建宏,在
semphr.h文件中定义。

	#define vSemaphoreCreateBinary( xSemaphore )																							\
		{																																	\
			( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );	\
			if( ( xSemaphore ) != NULL )																									\
			{																																\
				( void ) xSemaphoreGive( ( xSemaphore ) );																					\
			}																																\
		}

该宏有一个参数xSemaphore,用来保存创建成功的二值信号量的句柄。实际用于创建二值信号量的是队列创建函数xQueueGenericCreate(),创建成功后会返回这个二值信号量的句柄且二值信号量默认有效,创建失败则返回NULL。

 2.2 xSemaphoreCreateBinary()

xSemaphoreCrcateBinary()是新版本的FreeRTOS 使用的二值信号量创建宏,同样是通过调用队列创建函数xQucueGenericCrcate()来创建二值信号量的,只不过创建成功后并没有释放此二值信号量。 

#define xSemaphoreCreateBinary() 
        xQueueGenericCreate( ( UBaseType_t ) 1, 
            semSEMAPHORE_QUEUE_ITEM_LENGTH,                                             queueQUEUE_TYPE_BINARY_SEMAPHORE );

xQueueGenericCreate()函数如果成功创建了二值信号量,则返回这个二值信号量的句
柄,创建失败则返回NULL。

 3. 释放二值信号量

3.1  xSemaphoreGive()

xSemaphoreGive()是一个宏,用于释放二值信号量,最终实现功能的是队列发送函数
xQueueGenericSend()。 

#define xSemaphoreGive( xSemaphore )		
                xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), 
                NULL, 
                semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

该宏有一个参数xSemaphore,指明要释放的信号量句柄。该宏不仅可用于释放二值信号量,还可以释放计数信号量和互斥信号量。释放成功则返回pdTREU,释放失败则返回pdFALSE。

3.2 xSemaphoreGiveFromlSR()

xSemaphoreGiveFromISR()是释放信号量的宏的中断版本,在中断服务函数中使用,同样也是一个宏。该宏可用于释放二值信号量和计数信号量,但不能用于释放互斥信号量最终实现功能的是xQueueGiveFromISR()函数。

#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	
                                xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ),
                                 ( pxHigherPriorityTaskWoken ) )

参数说明如下。

xSemaphore: 信号量句柄。

pxHigherPriorityTaskWoken : 指向一个用于保存调用函数后是否进行任务切换的变量,若执行函数后值为pdTRUE,则要在退出中断服务函数后执行一次任务切换。

返回值:pdTRUE,成功释放信号量;errQUEUE_FULL,在指定阻塞时间内释放信号量失败。

4. 获取信号量 

4.1  xSemaphoreTake()

xSemaphoreTake()是一个宏,用于获取二值信号量。该宏还可用于获取计数信号量和互斥信号量。该宏的定义如下。

#define xSemaphoreTake( xSemaphore, xBlockTime )		
                xQueueSemaphoreTake( ( xSemaphore ),
                 ( xBlockTime ) )

参数说明如下。

xSemaphore: 信号量句柄。

xBlockTime : 任务阻塞超时时间,当信号量无效时,若该值为0则函数立即返回,若该值为portMAXDELAY 则任务将无限期阻塞,但要将宏INCLUDE_vTaskSuspend设置为1。
其他值为任务阻塞的系统时钟节拍。
返回值:pdTRUE,成功获取信号量;pdFALSE,在指定阻塞时间内获取信号量失败。

 4.2 xSemaphoreTakeFromISR()

xSemaphoreTakeFromISR()是获取信号量的宏的中断版本,在中断服务函数中使用,同样也是一个宏。该宏可以用来获取二值信号量和计数信号量,但不能用于获取互斥信号量。最终实现功能的是xQueueReceiveFromISR()函数。

#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	                
                 xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), 
                NULL,
                 ( pxHigherPriorityTaskWoken ) )

参数说明如下。

xSemaphore: 信号量句柄。

pxHigherPriorityTaskWoken : 指向一个用于保存调用函数后是否进行任务切换的变量,若执行函数后值为pdTRUE,则要在退出中断服务函数后执行一次任务切换。

返回值:pdTRUE,成功获取信号量;pdFALSE,在指定阻塞时间内获取信号量失败。

5. 用二值信号量实现任务同步 

本示例通过对二值信号量的操作,实现任务与任务之间及任务与中断之间的同步。本
示例通过appStartTaskO函数创建4个FreeRTOS任务。

  • 任务1的任务函数为Led0Task(),其功能是使LEDO闪烁,优先级为3,通过二值信号量实现任务与任务之间的同步,输出信息到串口。
  • 任务2的任务函数为Led1Task(),其功能是使LED1闪烁,优先级为3,通过二值信号量实现任务与中断之间的同步,输出信息到串口。
  • 任务3是串口守护任务,任务函数为printTask(),优先级为3,其功能是将通过队列传送过来的字符信息在串口上输出,任何时候只有该守护任务能访问串口。
  • 任务4是按键扫描任务,任务函数为keyTask(),优先级为4,其功能是按键扫描,并根据返回的键值执行释放二值信号量、启动定时器等操作。本示例中使用到3个按键,Key1按键用于释放任务与任务之间同步的二值信号量,Key2用于启动TIM2,利用TIM2的更新中断释放任务与中断之间同步的二值信号量,Key3用于停止TIM2。

5.1 配置TIM2 

使用定时器TIM2,利用它的更新中断在中断服务函数中释放二值信号量,实现任务
与中断之间的同步。在库函数环境配置TIM2,使能TIM2全局中断,设置中断抢占优先级为6(系统配置FreeRTOS 能管理的最高中断优先级为5,TIM2的中断抢占优先不能高于5),溢出时间为10ms。

#include "stm32f10x.h"                  // Device header
#include "Timer.h"
#include "appTask.h"
#include "Serial.h"
extern volatile uint8_t uIRQCounter;   //统计中断次数
extern SemaphoreHandle_t binIRQSemaphore;  //任务与中断之间同步二值信号量句柄
BaseType_t xHighPriorityWaskWoken = pdFALSE ; //退出中断后是否进行任务切换


void Timer_Init()
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	TIM_InternalClockConfig(TIM2);//开启内部时钟
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period=100 -1 ;//ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200 -1 ;//PSC
	//定时1秒 也是1HZ  72M/PSC/ARR =1HZ
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//中断使能  更新中断
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除更新中断请求位
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);	
}

void TIM2_IRQHandler(void)
{
	
	if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==SET)
	{
	
		uIRQCounter++;  //中断次数
		
		xSemaphoreGiveFromISR(binIRQSemaphore,&xHighPriorityWaskWoken);
		portYIELD_FROM_ISR(xHighPriorityWaskWoken);
			
	
		TIM_ClearFlag(TIM2,TIM_FLAG_Update);
		
	}

}

5.2 任务函数

static char pcToPrint[80];    //待打印内容缓冲区
xQueueHandle xQueuePrint;			//消息队列句柄
volatile uint8_t uIRQCounter;	//用于统计中断次数

SemaphoreHandle_t binKeySemaphore;  //任务与任务之间同步二值信号量句柄
SemaphoreHandle_t binIRQSemaphore;  //任务与中断之间同步二值信号量句柄




/**********************************************************************
函 数 名:Led0Task
功    能:使LED0闪烁,通过二值信号量实现任务与任务之间的同步,输出信息到串口
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	3
**********************************************************************/
static void Led0Task(void *pvParameters)
{
	
	while(1)
	{
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4)));	//LED0闪烁
		vTaskDelay(pdMS_TO_TICKS(500));//延时500ms
		if(xSemaphoreTake(binKeySemaphore,10) == pdTRUE)
		{
		//生成待打印出信息
		sprintf(pcToPrint,"按键任务通过二值信号量同步任务 1\r\n\r\n");
		//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
		xQueueSendToBack(xQueuePrint,pcToPrint,0);	
		}
	}
}
/**********************************************************************
函 数 名:Led1Task
功		能:使LED1闪烁,通过二值信号量实现任务与中断之间的同步,输出信息到串口
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	3
**********************************************************************/
static void Led1Task(void *pvParameters)
{
	
	
	while(1)
	{
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5)));  //LED1闪烁
		
		vTaskDelay(pdMS_TO_TICKS(500));//延时500ms
		
		//若获取二值信号量成功,则执行一些操作,本示例发送相应信息到串口
		if(xSemaphoreTake(binIRQSemaphore,10) == pdTRUE)
		{
				//生成待打印出信息
				sprintf(pcToPrint,"Tim2中断同步任务2,中断 %3d \r\n\r\n",uIRQCounter);
				//打印信息,所有发送串口的信息不能直接输出,通过队列发送给串口守护任务
				xQueueSendToBack(xQueuePrint,pcToPrint,0);	
		}
	
	}	
}
/**********************************************************************
函 数 名:printTask
功能说明:串口守护任务使用了一个FreeRTOS队列来对串口实现串行化访问,该守护任务是唯一能够直接访问串口的任务。
				  串口守护任务大部分时间都在阻塞态等待队列中有消息到来,当一个消息到达时,
				  串口守护任务仅简单地将接收到的消息发送到串口上,然后又返回阻塞态,继续等待下一条消息的到来。
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	3
**********************************************************************/
void printTask(void *pvParameters)
{
	char pcTowrite[80];  //缓存从队列接受到的数据
	while(1)
	{
		/*当队列为空,即没有字符需要输出时间,阻塞超时时间为portMAX_DELAY,任务将进入无期限等待
		状态,可以不检测队列读取函数的返回值*/
		xQueueReceive(xQueuePrint,pcTowrite,portMAX_DELAY);
		printf("%s",pcTowrite);
	}
}
/**********************************************************************
函 数 名:keyTask
功能说明:按键扫描任务,根据键值执行相应的操作
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级:	4
**********************************************************************/
static void keyTask(void *pvParameters)
{
	uint8_t keyValue = 0;  //存储键值
	
	while(1)
	{
		keyValue =Key_getNum();
		if(keyValue == 1)
		{
			sprintf(pcToPrint,"Key1按键按下,发送同步信号量 .........\r\n\r\n");
			xQueueSendToBack(xQueuePrint,pcToPrint,0);	
			//发送二值信号量给任务1进行同步
			xSemaphoreGive(binKeySemaphore);
		}else if(keyValue == 2)
		{
			//启动TIM2定时器
			TIM_Cmd(TIM2,ENABLE);
			sprintf(pcToPrint,"Key2按键按下,启动TIM2更新中断 .........\r\n\r\n");
			xQueueSendToBack(xQueuePrint,pcToPrint,0);	
			
			
			
			//在TIM2中断中,通过发送二值信号量给任务2进行同步
		}else if(keyValue == 3)
		{
			//关闭TIM2定时器
			TIM_Cmd(TIM2,DISABLE);
			xSemaphoreTake(binIRQSemaphore,10); //在使用计数信号量时注释掉
			sprintf(pcToPrint,"Key3按键按下,关闭TIM2 .........\r\n\r\n");
			xQueueSendToBack(xQueuePrint,pcToPrint,0);	
		}
		vTaskDelay(pdMS_TO_TICKS(100));
	}
}

5.3 信号量及任务创建

static TaskHandle_t Led0TaskHandle = NULL;//任务LED0任务句柄
static TaskHandle_t Led1TaskHandle = NULL;//任务LED1任务句柄
static TaskHandle_t printTaskHandle = NULL;//任务printTask任务句柄
static TaskHandle_t keyTaskHandle = NULL;//按键扫描任务句柄


/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形    参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
	
		/*创建一个长度为2,队列项大小足够容纳待输出字符的队列*/
		xQueuePrint = xQueueCreate(2,sizeof(pcToPrint));
		/*创造2个二值信号量,一个实现任务与任务之间的同步,一个用于实现任务与中断之间的同步*/
		binIRQSemaphore = xSemaphoreCreateBinary();
		binKeySemaphore = xSemaphoreCreateBinary();
	
		if(xQueuePrint && binKeySemaphore && binIRQSemaphore)//如果队列信号量创建成功
		{
				
				taskENTER_CRITICAL();   /*进入临界段,关中断*/
	
				xTaskCreate(Led0Task,"Led0Task",128,NULL,3,&Led0TaskHandle);
				xTaskCreate(Led1Task,"Led1Task",128,NULL,3,&Led1TaskHandle);
				xTaskCreate(printTask,"printTask",128,NULL,3,&printTaskHandle);
				xTaskCreate(keyTask,"keyTask",128,NULL,4,&keyTaskHandle);
				taskEXIT_CRITICAL(); 	/*退出临界段,关中断*/
				vTaskStartScheduler();/*开启调度器*/	
		}
		
}

5.4 下载测试

编译无误下载后,可以看到LED2个灯在闪烁,按Key1,串口打印输出任务同步信息;按Key2按键,启动Tim2,Tim2的更新中断发送二值信号量同步任务2(间隔100ms),串口打印任务与中断的同步信息;按下Key3按键关闭Tim2,停止任务与中断的同步。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值