嵌入式操作系统实验:实验二:信号量与邮箱

本文详细介绍了在嵌入式系统中通过信号量与邮箱进行多任务处理的实验过程,涉及信号量和邮箱的原理、实验步骤,以及LED任务、触摸屏互动和CPU使用率监控的结果。实验强调了信号量在资源管理和任务同步中的关键作用。
摘要由CSDN通过智能技术生成

实验二:信号量与邮箱

1、实验目的

深入理解信号量与邮箱在多任务处理中的应用,掌握其基本原理与使用方法。

2、实验内容

通过阅读和编译运行信号量与邮箱的相关代码,观察实验运行结果。

3、实验原理和实验步骤

实验原理:

1.信号量原理:

信号量是一种用于多任务同步与互斥的机制,通常用于控制对共享资源的访问。信号量的值可以用于表示可用资源的数量。在嵌入式系统中,信号量常被用于解决竞争条件和确保资源的正确共享。当一个任务需要使用某资源时,它会尝试获取信号量;如果信号量的值大于零,任务就可以获得资源并将信号量值减一;如果信号量值为零,任务就会被阻塞,直到有其他任务释放资源。

2.邮箱原理:

邮箱是一种用于在不同任务之间传递消息的通信机制。每个任务可以通过邮箱发送消息给其他任务,并从邮箱接收消息。在嵌入式系统中,邮箱可以用于实现任务之间的信息传递,协调任务的执行顺序,以及处理异步事件。任务通过邮箱发送和接收消息,实现了解耦和通信的目的。

实验步骤:

1.信号量实验步骤:

(1)创建一个共享资源,如共享变量或共享设备。

(2)定义一个信号量,用于控制对共享资源的访问。

(3)编写两个或多个任务,分别尝试获取和释放信号量。

(4)观察任务之间如何协作,确保对共享资源的访问是安全和有序的。

2.邮箱实验步骤:

(1)创建两个或多个任务,它们将通过邮箱进行通信。

(2)定义一个邮箱,用于在任务之间传递消息。

(3)编写发送任务,将消息发送到邮箱。

(4)编写接收任务,从邮箱中接收消息。

(5)观察任务之间的消息传递,确保信息传递是可靠和同步的。

4、问题分析及算法描述

本实验主要是通过按键来控制信号量和邮箱的输送,以此来控制LED任务的闪烁,通过在触摸区画画,改变CPU的使用率,当我们执行清屏校准任务时,触摸屏任务则会被挂起,整体流程图如下:

流程图1 整体流程图

1)首先我们要对主函数进行设置,初始化系统硬件和UCOS-II操作系统,创建了一个启动任务,并启动了操作系统,使得多个任务可以并发运行。

主要代码如下:

int main(void)

 {

  delay_init();       //延时函数初始化

  uart_init(72);

  NVIC_Configuration();  

  LED_Init();    //初始化与LED连接的硬件接口

  LCD_Init();     //初始化LCD

  KEY_Init(); //按键初始化

  tp_dev.init();     //触摸屏初始化

  ucos_load_main_ui(); //加载主界面  

  OSInit();     //初始化UCOSII

  OSTaskCreate(start_task,(void*)0(OS_STK*)&START_TASK_STK[START_STK_SIZE1],START_TASK_PRIO );//创建起始任务

OSStart();

 }

以下是对这段代码的解释:这段代码是一个嵌入式系统中使用了μC/OS-II(Micro C Operating System-II)实时操作系统的 main 函数。下面是对每个函数调用的简要解释:delay_init():延时函数初始化,可能是为了提供在程序中进行一些延时的功能。uart_init(72):串口初始化函数,配置串口通信参数,其中 72 可能表示波特率或其他串口配置参数。NVIC_Configuration():配置中断向量表(Nested Vectored Interrupt Controller),可能用于初始化中断相关设置。LED_Init():LED初始化函数,用于配置与LED连接的硬件接口。

LCD_Init():LCD初始化函数,初始化液晶显示屏相关的硬件接口。KEY_Init():按键初始化函数,用于初始化与按键相关的硬件接口。tp_dev.init():触摸屏初始化函数,初始化触摸屏硬件接口。ucos_load_main_ui():加载主界面函数,可能用于加载嵌入式系统的主界面。OSInit():UCOS-II初始化函数,用于初始化UCOS-II实时操作系统。OSTaskCreate(start_task, (void *)0, (OS_STK *)&START_TASK_STK[START_STK_SIZE-1], START_TASK_PRIO):创建任务函数,其中:start_task 是一个任务函数的指针,表示系统启动时要运行的任务。

(void *)0 是任务的参数,这里设置为 0。(OS_STK *)&START_TASK_STK[START_STK_SIZE-1] 表示任务的栈指针,指向任务栈的顶部。START_TASK_PRIO 是任务的优先级。OSStart():启动UCOS-II实时操作系统,开始多任务调度。

2)开始任务。它创建了两个任务,为信号量与邮箱,同时开启一系列任务,并在最后挂起当前任务。这样,在系统启动后,各个任务会按照其优先级被调度执行。

主要代码如下:

//开始任务

void start_task(void *pdata)

{

  OS_CPU_SR cpu_sr=0;

pdata = pdata;   

msg_key=OSMboxCreate((void*)0); //创建消息邮箱

sem_led1=OSSemCreate(0); //创建信号量     

OSStatInit(); //初始化统计任务.这里会延时1秒钟左右

  OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断)    

  OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO);    

  OSTaskCreate(touch_task,(void *)0,(OS_STK*)&TOUCH_TASK_STK[TOUCH_STK_SIZE-1],TOUCH_TASK_PRIO);      

  OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);      

  OSTaskCreate(main_task,(void *)0,(OS_STK*)&MAIN_TASK_STK[MAIN_STK_SIZE-1],MAIN_TASK_PRIO);      

  OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);      

  OSTaskSuspend(START_TASK_PRIO); //挂起起始任务.

OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断)

}   

下面是对该算法的解释:

OS_CPU_SR cpu_sr=0;:定义一个变量 cpu_sr,用于存储CPU的状态寄存器值。

msg_key=OSMboxCreate((void*)0);:创建一个消息邮箱 msg_key,用于任务之间的消息传递。sem_led1=OSSemCreate(0);:创建一个信号量 sem_led1,初始化为0。OSStatInit();:初始化统计任务,该函数通常用于启动一个统计任务,用于收集有关任务和中断的运行时间等信息。这里可能还包含了一个延时,大约1秒钟左右。OS_ENTER_CRITICAL();:进入临界区,这是一个关中断的操作,确保在执行一些关键操作时不被中断打断。任务创建:OSTaskCreate(led0_task, (void *)0, (OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1], LED0_TASK_PRIO);:创建LED0任务。

OSTaskCreate(touch_task, (void *)0, (OS_STK*)&TOUCH_TASK_STK[TOUCH_STK_SIZE-1], TOUCH_TASK_PRIO);:创建触摸屏任务。

OSTaskCreate(led1_task, (void *)0, (OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1], LED1_TASK_PRIO);:创建LED1任务。

OSTaskCreate(main_task, (void *)0, (OS_STK*)&MAIN_TASK_STK[MAIN_STK_SIZE-1], MAIN_TASK_PRIO);:创建主任务。

OSTaskCreate(key_task, (void *)0, (OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1], KEY_TASK_PRIO);:创建按键任务。

OSTaskSuspend(START_TASK_PRIO);:挂起当前任务(即 start_task 任务)。这是因为一旦所有任务都创建完毕,不再需要 start_task 任务运行。

OS_EXIT_CRITICAL();:退出临界区,允许中断。

流程图2开启任务

3)下面是LED0和LED1的任务。

主要代码如下:

//LED0任务

void led0_task(void *pdata)

{

u8 t;

while(1)

{

t++;

delay_ms(10);

if(t==8)LED0=1; //LED0灭

if(t==100) //LED0亮

{

t=0;

LED0=0;

}

}  

}    

//LED1任务

void led1_task(void *pdata)

{

u8 err;

while(1)

{

OSSemPend(sem_led1,0,&err);

LED1=0;

delay_ms(200);

LED1=1;

delay_ms(800);

}  

}

下面是对这个算法的描述:LED0设置了无符号整数t,当t等于8时,LED0熄灭,等于100时候闪烁。延迟10毫秒,LED1:OSSemPend(sem_led1,0,&err);任务会在这里等待 sem_led1 变为非零。如果 sem_led1 的值为0,任务会一直等待,直到有其他任务或中断释放了 sem_led1,使其值变为非零。等待结束后,err 变量将存储函数执行的错误码。当成功获取信号量后,LED1闪烁,延迟200毫秒后,LED1熄灭,再次延迟800毫秒,以此循环反复。

流程图3 LED任务

4)主任务通过等待按键信息和定时器计数,执行相应的操作,包括发送信号量、清屏、触摸屏校准等,并在LCD屏幕上显示相关信息。

主要代码如下:

void main_task(void *pdata)
{							 
	u32 key=0;	
	u8 err;	
	u8 semmask=0;
	u8 tcnt=0;						 
	while(1)
	{
		key=(u32)OSMboxPend(msg_key,10,&err);   
		switch(key)
		{ 
			case KEY0_PRES://发送信号量
				semmask=1;
				OSSemPost(sem_led1);
				break;
			case KEY1_PRES://清除
				LCD_Fill(0,121,lcddev.width,lcddev.height,WHITE);
				break;
			case WKUP_PRES://校准
				OSTaskSuspend(TOUCH_TASK_PRIO);	//挂起触摸屏任务		 
 				if((tp_dev.touchtype&0X80)==0)TP_Adjust();	   
 				OSTaskResume(TOUCH_TASK_PRIO);	//解挂
				ucos_load_main_ui();			//重新加载主界面		 
				break;
		}
   		if(semmask||sem_led1->OSEventCnt)//需要显示sem		
		{
			POINT_COLOR=BLUE;
			LCD_ShowxNum(192,50,sem_led1->OSEventCnt,3,16,0X80);//显示信号量的值
			if(sem_led1->OSEventCnt==0)semmask=0;	//停止更新
		} 
		if(tcnt==50)//0.5秒更新一次CPU使用率
		{
			tcnt=0;
			POINT_COLOR=BLUE;		  
			LCD_ShowxNum(192,30,OSCPUUsage,3,16,0);	//显示CPU使用率   
		}
		tcnt++;
		delay_ms(10);
	}
}

以下是对这段代码的解释:key=(u32)OSMboxPend(msg_key,10,&err);: 从消息邮箱 msg_key 中获取按键信息,等待时间为10个时钟节拍。获取的按键信息会被存储在 key 变量中。switch(key) { ... }: 根据获取的按键信息执行相应的操作,根据按键值进行分支判断。case KEY0_PRES:: 如果按下了KEY0按键,设置 semmask 为1,并通过 OSSemPost(sem_led1) 发送信号量 sem_led1。case KEY1_PRES:: 如果按下了KEY1按键,用白色填充LCD屏幕的一部分,相当于清屏操作。case WKUP_PRES:: 如果按下了WKUP按键,挂起触摸屏任务,进行触摸屏校准,然后解挂触摸屏任务,并重新加载主界面。if(semmask || sem_led1->OSEventCnt) { ... }: 如果 semmask 为真或者信号量 sem_led1 的计数不为零,表示需要显示信号量信息,然后进行显示操作。显示信号量的值在LCD屏幕上指定位置,蓝色字体。if(tcnt==50) { ... }: 如果计数器 tcnt 达到50,即0.5秒,显示操作系统的CPU使用率(OSCPUUsage)在LCD屏幕上指定位置,蓝色字体。tcnt++;: 计数器递增,用于计时。delay_ms(10);: 延时10毫秒,控制任务执行的速率。

5)对触摸屏任务进行设置,判断触摸屏被按下的时候,在规定的区域进行画图。

主要代码如下:

//触摸屏任务

void touch_task(void *pdata)

{   

while(1)

{

tp_dev.scan(0);  

if(tp_dev.sta&TP_PRES_DOWN) //触摸屏被按下

{

  if(tp_dev.x[0]<lcddev.width&&tp_dev.y[0]<lcddev.height&&tp_dev.y[0]>120)

{    

TP_Draw_Big_Point(tp_dev.x[0],tp_dev.y[0],RED); //画图  

delay_ms(2);    

}

}else delay_ms(10); //没有按键按下的时候

}

}

以下是对这段代码的解释:void touch_task(void *pdata): 定义了一个名为 touch_task 的函数,该函数接受一个 void 类型指针参数 pdata,通常在嵌入式系统中用于传递任务的数据。while(1) { ... }: 无限循环,表示触摸屏任务将一直运行tp_dev.scan(0);: 调用触摸屏设备的 scan 函数,可能是用于扫描触摸屏状态,更新触摸屏的内部数据结构。if(tp_dev.sta & TP_PRES_DOWN) { ... }: 检查触摸屏状态是否被按下。TP_PRES_DOWN 是一个宏定义,表示触摸屏被按下的状态。if(tp_dev.x[0] < lcddev.width && tp_dev.y[0] < lcddev.height && tp_dev.y[0] > 120) { ... }: 检查触摸点的坐标是否在合理范围内,例如在屏幕内且在垂直方向上在120以上。TP_Draw_Big_Point(tp_dev.x[0], tp_dev.y[0], RED);: 如果条件满足,调用 TP_Draw_Big_Point 函数,在触摸点的位置画一个大点,颜色为红色。delay_ms(2);: 引入一个短暂的延时,可能是为了稳定触摸屏的输入。else { delay_ms(10); }: 如果触摸屏没有被按下,引入一个相对较长的延时,减少任务执行的频率,节省系统资源。

5、实验结果

(1) LED任务

实验结果如图4所示:

图4 LED任务实验结果

实验结果描述:上面的运行结果如图所示:首先是初始界面,然后我们按下按键输送信号量后,LED1闪烁过了200毫秒熄灭,过800毫秒再次闪烁,以此反复,而到LED0计数循环到100后,LED0闪烁,延迟10毫秒后又重新开始计数,计数到8,熄灭。以此反复。上图中绿色代表LED0,红色代表LED1。

(2) 触摸屏任务

图5 触摸屏实验结果

实验结果描述:在触摸区进行画画,当我们画入后,我们CPU的使用量也会从0%,慢慢增加,每0.5秒更新一次

(3)清屏校准任务

图6 清屏校准实验结果

实验结果描述:按下KEY1进行到我们的清屏校准界面,挂起我们的触摸屏任务。

6、分析与心得

在进行嵌入式系统中信号量与邮箱的实验过程中,我深刻体会到了它们在多任务处理和通信方面的重要性。以下是我在实验中的一些心得体会:信号量的一大优势是能够方便地管理系统中的资源。通过对资源的分配和释放使用信号量来进行控制,可以有效地避免资源竞争和浪费。在实验中,我学会了如何使用信号量来管理共享资源,确保系统的高效运行。通过这次实验,我更深入地了解了嵌入式系统中信号量和邮箱的原理和应用。这些技术在实际项目中有着广泛的应用,对于构建高效、可靠的嵌入式系统非常关键。

  • 40
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值