基于STM32F103移植华为LiteOS—信号量

基于STM32F103移植华为LiteOS—信号量

对于“信号量”这个实现任务间通信机制,想必学过一些操作系统的读者应该都有所了解吧。这里我用比较通俗一点的话给大家讲解华为LiteOS操作系统里头的“信号量”。

一、信号量概念

1、基本概念从专业上的讲解
信号量(Semaphore)是一种实现任务间通信的机制,实现任务之间同步或临界资源的互斥访问。常用于协助一组相互竞争的任务来访问临界资源。
在多任务系统中,各任务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:
• 0,表示没有积累下来的Post操作,且有可能有在此信号量上阻塞的任务。
• 正值,表示有一个或多个Post下来的释放操作。
以同步为目的的信号量和以互斥为目的的信号量在使用有如下不同:
• 用作互斥时,信号量创建后记数是满的,在需要使用临界资源时,先取信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法取到信号量而阻塞,从而保证了临界资源的安全。
• 用作同步时,信号量在创建后被置为空,任务1取信号量而阻塞,任务2在某种条件发生后,释放信号量,于是任务1得以进入READY或RUNNING态,从而达到了两个任务间的同步。
2、通俗一点的讲解


在这里插入图片描述

我们可以将“LiteOS中的信号量”比作为上图所画“饭店中的饭桌”,其中“饭店”=“信号量”、“饭桌=信号量的容量”。当客户对某个饭桌预约或已经用餐了,那么这个饭桌就被标记为“被申请使用中”,也就是图中的红色X,申请使用的饭桌是不会限制使用时间的,当用餐完毕、结账后,这个饭桌是被标记为“空闲状态”,也就是图中的绿色√,此时是可以被客户马上预约或者用餐的。从上可知道LiteOS操作系统拥有“实时响应,实时处理”机制,而那么多个饭桌的管理就相当于信号量在系统中的资源管理了。

3、华为LiteOS开发指南上对信号量的运作机制讲解
在这里插入图片描述
4、华为LiteOS信号量的使用场景
信号量是一种非常灵活的同步方式,可以运用在多种场合中,实现锁、同步、资源计数等功能,也能方便的用于任务与任务,中断与任务的同步中。

二、华为LiteOS中的信号量分为“二值信号量”和“计数信号量”

1、二值信号量
二值信号量可以理解为一个饭店只有一张桌子,该桌子只有(被申请使用和空闲)两种状态,而二值信号量“0”表示申请信号量,“1”表示释放信号量。
在嵌入式操作系统中二值信号量是任务与任务间、 中断与任务间同步的重要手段。可以理解为事件1申请该二值信号量,将事件2阻塞,等待事件1完成,事件1完成后释放该二值信号量,事件2申请该二值信号量,等待事件2完成,事件2完成后释放该二值信号量,然后又到事件1……往返事件的运行达成任务间的同步。
2、计数信号量
计算信号量也就可以理解为一个饭店和它的所有桌子。
顾名思义,计数信号量是用于计数的,在实际的使用中,计数信号量常用于事件计数与资源管理。每当某个事件发生时,任务/中断释放一个信号量(信号量计数值加 1),当处理被事件时(一般在任务中处理),处理任务会取走该信号量(信号量计数值减 1),信号量的计数值则表示还剩余多少个事件没被处理。此外,系统还有很多资源,也可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能访问资源,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。
3、关于华为LiteOS操作系统中的“二值信号量”和“计数信号量”的一些细节
①信号量个数
在这里插入图片描述
上图为LiteOS操作系统中target_config.h配置文件中信号量的程序片段,图中“20”表示最大支持的信号量数量,这个信号量不是指“桌子”,它指的是“饭店”。
也就是如下图所示,能创建的信号量个数最多是LOSCFG_BASE_IPC_SEM_LIMIT个。(无论是二值信号量或计数信号都可以)


在这里插入图片描述

②二值信号量容量和计数信号量容量

在这里插入图片描述
由上图可知在los_sem.ph文件中对二值信号量容量、计数信号量容量有定义。这里二值信号量就不再多说了,在开发中大家可能会遇到申请计数信号量时会按照申请的个数申请,但是释放计数信号量会释放不会按照申请的个数释放同等的个数,而是释放不完。
这里我要说一下,不是释放不完,而是数量太多,计数信号量的最大容量个数是OS_SEM_COUNTING_MAX_COUNT=0xFFFF=65535个,太多了,给人错觉是释放不完。
这里我做了一直图给大家讲解,如下图所示。
在这里插入图片描述
如果大家释放信号量需要不想让它释放不完,实现一些资源管理的功能,那么大家可以修改los_sem.ph文件中定义的OS_SEM_COUNTING_MAX_COUNT变量值,要是使用到多个计数信号量,而且计数信号量创建的容量值不相同,那么我不建议这么做,大家可自行定义一些变量作为标记,防止一直释放计数信号量。


三、亮代码环节

1、计数信号量(模拟停车场)

/* LiteOS头文件 */
#include "los_sys.h "
#include "los_task.ph"
#include "los_queue.h"
#include "los_sem.h"

/* 外设驱动头文件 */
#include "led.h"
#include "usart1.h"
#include "key.h"

/* 定义任务 ID 变量,也叫任务句柄 */
UINT32 Pend_Task_Handle;		//获取信号量
UINT32 Post_Task_Handle;		//释放信号量

/* 定义计算信号量 ID 变量 */
UINT32  CountSem_Handle;
UINT32  CountSem_Handle1;
UINT32  CountSem_Handle2;

/* 函数声明 */
static UINT32 AppTaskCreate(void);
static UINT32 Create_Pend_Task(void);
static UINT32 Create_Post_Task(void);

static void Pend_Task(void);
static void Post_Task(void);


int main(void)
{	
	UINT32 uwRet = LOS_OK; //定义一个任务创建的返回值,默认为创建成功
	
	/*中断优先级分组为 4*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	/* 板载相关初始化 */
	Usart1_Config();				//串口1
	LED_GPIO_Config();			//LED
	Key_Config_parameter();	//按键

	printf("\r\n正点原子战舰开发板-LiteOS-SRAM 动态创建多任务!\r\n");
	
	/* LiteOS 内核初始化 */
	uwRet = LOS_KernelInit();
	if (uwRet != LOS_OK) {
		printf("LiteOS核心初始化失败!任务代码0X%X\r\n",uwRet);
		return LOS_NOK;
	}
	
	uwRet = AppTaskCreate();
	if (uwRet != LOS_OK) {
		printf("AppTaskCreate任务创建失败!任务代码0X%X\r\n",uwRet);
		return LOS_NOK;
	}
	
	/* 开启 LiteOS 任务调度 */
	LOS_Start();
	
	while(1){	//上面任务失败会进入此,LED关闭
		GPIO_SetBits(GPIOB, GPIO_Pin_5);
		GPIO_SetBits(GPIOE,GPIO_Pin_5);
	}
}

/**************************************************************************/
/******************************** 创建任务函数 ********************************/
/**************************************************************************/

/**************************************************************
 * @ 函数名 : AppTaskCreate
 * @ 功能说明: 任务创建,为了方便管理,所有的任务创建函数都可以放在这个函数里面
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 AppTaskCreate(void)
{
	/* 定义一个返回类型变量,初始化为 LOS_OK */
	UINT32 uwRet = LOS_OK;

	/* 创建一个计数信号量,初始化计数值为 5*/
	uwRet = LOS_SemCreate(5,&CountSem_Handle);
	if(uwRet != LOS_OK) {
		printf("CountSem创建失败!失败代码0X%X\r\n",uwRet);
	}
	uwRet = Create_Pend_Task();
	if(uwRet != LOS_OK) {
		printf("Pend_Task create failed!\r\n");
		return uwRet;
	}
	
	uwRet = Create_Post_Task();
	if(uwRet != LOS_OK) {
		printf("Post_Task create failed!\r\n");
		return uwRet;
	}
	
	return LOS_OK;
}

/**************************************************************
 * @ 函数名 : Create_Pend_Task
 * @ 功能说明: Create_Pend_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 Create_Pend_Task(void) {
	UINT32 uwRet = LOS_OK;
	TSK_INIT_PARAM_S task_init_param;
	
	task_init_param.usTaskPrio = 5;
	task_init_param.pcName = "Pend_Task";
	task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Pend_Task;
	task_init_param.uwStackSize = 1024;
	
	uwRet = LOS_TaskCreate(&Pend_Task_Handle,&task_init_param);
	
	return uwRet;
}

/**************************************************************
 * @ 函数名 : Create_Post_Task
 * @ 功能说明: Create_Post_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 Create_Post_Task(void) {
	UINT32 uwRet = LOS_OK;
	TSK_INIT_PARAM_S task_init_param;
	
	task_init_param.usTaskPrio = 4;
	task_init_param.pcName = "Post_Task";
	task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Post_Task;
	task_init_param.uwStackSize = 1024;
	
	uwRet = LOS_TaskCreate(&Post_Task_Handle,&task_init_param);
	
	return uwRet;
}

/**************************************************************************/
/******************************** 任务函数 ********************************/
/**************************************************************************/

/**************************************************************
 * @ 函数名 : Pend_Task
 * @ 功能说明: Pend_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static void Pend_Task(void) {
	UINT32 uwRet = LOS_OK;
	
	while(1) {
		/* 按键KEY0按下,短按 */
		if(KEY_CLASS[KEY0].Key_State_Machine.Key_Event==KEY_CONFIRN_DOWN) {
			/* 获取一个计数信号量,等待时间 0 */
			uwRet = LOS_SemPend(CountSem_Handle,0);
			if(LOS_OK == uwRet) {
				printf("KEY0被按下,成功申请到停车位!\r\n");
			} else {
				printf ("KEY0 被按下,不好意思,现在停车场已满!\r\n");
			}
		}
		LOS_TaskDelay(20);
	}
}

/**************************************************************
 * @ 函数名 : Post_Task
 * @ 功能说明: Post_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static void Post_Task(void) {
	UINT32 uwRet = LOS_OK;
	
	while(1) {
		Read_Key_State();		//读取按键值
		/* 如果按键KEY1按下,短按 */
		if(KEY_CLASS[KEYUP].Key_State_Machine.Key_Event==KEY_CONFIRN_DOWN) {
			/* 释放一个计数信号量 */
			uwRet = LOS_SemPost(CountSem_Handle);	
			if(LOS_OK == uwRet) {
				printf("KEY_UP 被按下,释放 1 个停车位!\r\n");
			} else {
				printf("KEY_UP 被按下,但已无车位可以释放!\r\n");
			}
		}
		LOS_TaskDelay(20); 
	}
}

2、二值信号量(两个任务间进行同步操作)

/* LiteOS头文件 */
#include "los_sys.h "
#include "los_task.ph"
#include "los_queue.h"
#include "los_sem.h"

/* 外设驱动头文件 */
#include "led.h"
#include "usart1.h"
#include "key.h"

/* 定义任务 ID 变量,也叫任务句柄 */
UINT32 Read_Task_Handle;				//获取信号量
UINT32 Write_Task_Handle;		//释放信号量

/* 定义二值信号量 ID 变量 */
UINT32 BinarySem_Handle;

/* 定义全局变量 */
uint8_t ucValue[2] = {0x00,0x000};

/* 函数声明 */
static UINT32 AppTaskCreate(void);
static UINT32 Create_Read_Task(void);
static UINT32 Create_Write_Task(void);

static void Read_Task(void);
static void Write_Task(void);


int main(void)
{	
	UINT32 uwRet = LOS_OK; //定义一个任务创建的返回值,默认为创建成功
	
	/*中断优先级分组为 4*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	/* 板载相关初始化 */
	Usart1_Config();		//串口1
	LED_GPIO_Config();	//LED
	Key_Config_parameter();	//按键

	printf("正点原子战舰开发板-LiteOS-SRAM 动态创建多任务!\r\n\r\n");
	
	/* LiteOS 内核初始化 */
	uwRet = LOS_KernelInit();
	if (uwRet != LOS_OK) {
		printf("LiteOS核心初始化失败!任务代码0X%X\r\n",uwRet);
		return LOS_NOK;
	}
	
	uwRet = AppTaskCreate();
	if (uwRet != LOS_OK) {
		printf("AppTaskCreate任务创建失败!任务代码0X%X\r\n",uwRet);
		return LOS_NOK;
	}
	
	/* 开启 LiteOS 任务调度 */
	LOS_Start();
	
	while(1){	//上面任务失败会进入此,LED关闭
		GPIO_SetBits(GPIOB, GPIO_Pin_5);
		GPIO_SetBits(GPIOE,GPIO_Pin_5);
	}
}

/**************************************************************************/
/******************************** 创建任务函数 ********************************/
/**************************************************************************/

/**************************************************************
 * @ 函数名 : AppTaskCreate
 * @ 功能说明: 任务创建,为了方便管理,所有的任务创建函数都可以放在这个函数里面
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 AppTaskCreate(void)
{
	/* 定义一个返回类型变量,初始化为 LOS_OK */
	UINT32 uwRet = LOS_OK;

	uwRet = LOS_BinarySemCreate(1,&BinarySem_Handle);
	if(uwRet != LOS_OK) {
		printf("BinarySem创建失败!失败代码0X%X\r\n",uwRet);
	}
	
	uwRet = Create_Read_Task();
	if(uwRet != LOS_OK) {
		printf("Read_Task create failed!\r\n");
		return uwRet;
	}
	
	uwRet = Create_Write_Task();
	if(uwRet != LOS_OK) {
		printf("Write_Task create failed!\r\n");
		return uwRet;
	}
	
	return LOS_OK;
}

/**************************************************************
 * @ 函数名 : Create_Read_Task
 * @ 功能说明: Create_Read_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 Create_Read_Task(void) {
	UINT32 uwRet = LOS_OK;
	TSK_INIT_PARAM_S task_init_param;
	
	task_init_param.usTaskPrio = 5;
	task_init_param.pcName = "Read_Task";
	task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Read_Task;
	task_init_param.uwStackSize = 1024;
	
	uwRet = LOS_TaskCreate(&Read_Task_Handle,&task_init_param);
	
	return uwRet;
}

/**************************************************************
 * @ 函数名 : Create_Write_Task
 * @ 功能说明: Create_Write_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static UINT32 Create_Write_Task(void) {
	UINT32 uwRet = LOS_OK;
	TSK_INIT_PARAM_S task_init_param;
	
	task_init_param.usTaskPrio = 4;
	task_init_param.pcName = "Write_Task";
	task_init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)Write_Task;
	task_init_param.uwStackSize = 1024;
	
	uwRet = LOS_TaskCreate(&Write_Task_Handle,&task_init_param);
	
	return uwRet;
}

/**************************************************************************/
/******************************** 任务函数 ********************************/
/**************************************************************************/

/**************************************************************
 * @ 函数名 : Read_Task
 * @ 功能说明: Read_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static void Read_Task(void) {
	UINT32 uwRet = LOS_OK;
	
	while(1) {
		uwRet = LOS_SemPend(BinarySem_Handle,LOS_WAIT_FOREVER);	//申请信号量
		if(uwRet==LOS_OK) {
			printf("\r\nRead_Task Apply Sem Successful!\r\n");
		}
		printf("Read_Task!\r\n");
		printf("ucValue[0] = %d,  ucValue[1] = %d \r\n",ucValue[0],ucValue[1]);		
		if(ucValue[0]==ucValue[1]) {
			printf("Synchronization Successful!\r\n");
		} else {
			printf("Fail!\r\n");
		}
		
		LOS_SemPost(BinarySem_Handle);	//释放信号量
	}
}

/**************************************************************
 * @ 函数名 : Write_Task
 * @ 功能说明: Write_Task任务实现
 * @ 参数 : NULL
 * @ 返回值 : NULL
 *************************************************************/
static void Write_Task(void) {
	UINT32 uwRet = LOS_OK;
	
	while(1) {
		uwRet = LOS_SemPend(BinarySem_Handle,LOS_WAIT_FOREVER);	//申请信号量
		if(uwRet==LOS_OK) {
			printf("\r\nWrite_Task Apply Sem Successful!\r\n");
		}
		printf("Write_Task---1!\r\n");
		ucValue[0]++;
		LOS_TaskDelay(1000);				//任务阻塞
		ucValue[1]++;
		printf("Write_Task---2!\r\n");
		
		LOS_SemPost(BinarySem_Handle);	//释放信号量
		LOS_TaskYield(); //放弃剩余时间片,进行一次任务切换
	}
}

总结

学习东西要有耐心,从根本上去分析,不要盲目追求结果
如下图,野火教程文档只是说出现这种不希望的现象,至于为什么会出现,怎么去去解决却没有提,很多人会做完这个实验就不了了之了。


在这里插入图片描述

有什么问题,欢迎大家一起交流探讨学习,我个人信息详情有我的联系方式,留言也可以。大家一起共同进步。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值