你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

查看: 4131|回复: 2

【连载】【ALIENTEK 战舰STM32开发板】STM32开发指南--第五十九章 UCOSII实验2-信号量和邮箱

[复制链接]

24

主题

12

回帖

2

蝴蝶豆

初级会员

最后登录
2020-2-27
发表于 2013-5-1 23:07:02 | 显示全部楼层 |阅读模式
  
第五十九章 UCOSII实验2-信号量和邮箱

     上一章,我们学习了如何使用UCOSII,学习了UCOSII的任务调度,但是并没有用到任务间的同步与通信,本章我们将学习两个最基本的任务间通讯方式:信号量和邮箱。本章分为如下几个部分:
59.1 UCOSII信号量和邮箱简介
59.2 硬件设计
59.3 软件设计
59.4 下载验证



59.1UCOSII信号量和邮箱简介
系统中的多个任务在运行时,经常需要互相无冲突地访问同一个共享资源,或者需要互相支持和依赖,甚至有时还要互相加以必要的限制和制约,才保证任务的顺利运行。因此,操作系统必须具有对任务的运行进行协调的能力,从而使任务之间可以无冲突、流畅地同步运行,而不致导致灾难性的后果。
例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态,只有当任务A把打印机释放后,系统才能唤醒任务B使其获得打印机的使用权。如果这两个任务不这样做,那么会造成极大的混乱 。
任务间的同步依赖于任务间的通信。在UCOSII中,是使用信号量、邮箱(消息邮箱)和消息队列这些被称作事件的中间环节来实现任务之间的通信的。本章,我们仅介绍信号量和邮箱,消息队列将会在下一章介绍。
事件
       两个任务通过事件进行通讯的示意图如图59.1.1所示:

图59.1.1 两个任务使用事件进行通信的示意图在图59.1.1中任务1是发信方,任务2是收信方。任务1负责把信息发送到事件上,这项操作叫做发送事件。任务2通过读取事件操作对事件进行查询:如果有信息则读取,否则等待。读事件操作叫做请求事件。
为了把描述事件的数据结构统一起来,UCOSII使用叫做事件控制块(ECB)的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据,事件控制块结构体定义如下:
typedef struct
{
   INT8U  OSEventType;                 //事件的类型
   INT16U OSEventCnt;                   //信号量计数器
   void *OSEventPtr;                        //消息或消息队列的指针
   INT8U  OSEventGrp;                  //等待事件的任务组
   INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;          //事件名
#endif
} OS_EVENT;
信号量
信号量是一类事件。使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量可以分为两种:一种是二值型信号量,另外一种是N值信号量。
二值型信号量好比家里的座机,任何时候,只能有一个人占用。而N值信号量,则好比公共电话亭,可以同时有多个人(N个)使用。
UCOSII将二值型信号量称之为也叫互斥型信号量,将N值信号量称之为计数型信号量,也就是普通的信号量。本章,我们介绍的是普通信号量,互斥型信号量的介绍,请参考《嵌入式实时操作系统UCOSII原理及应用》5.4节。
接下来我们看看在UCOSII中,与信号量相关的几个函数(未全部列出,下同)。
1) 创建信号量函数
在使用信号量之前,我们必须用函数OSSemCreate来创建一个信号量,该函数的原型为:OS_EVENT *OSSemCreate (INT16U cnt)。该函数返回值为已创建的信号量的指针,而参数cnt则是信号量计数器(OSEventCnt)的初始值。
2) 请求信号量函数
任务通过调用函数OSSemPend请求信号量,该函数原型如下:void OSSemPend ( OS_EVENT *pevent, INT16U timeout, INT8U *err)。其中,参数pevent是被请求信号量的指针,timeout为等待时限,err为错误信息。
为防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而 进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
3) 发送信号量函数
任务获得信号量,并在访问共享资源结束以后,必须要释放信号量,释放信号量也叫做发送信号量,发送信号通过OSSemPost函数实现 。OSSemPost 函数在对信号量的计数器操作之前,首先要检查是否还有等待该信号量的任务。如果没有,就把信号量计数器OSEventCnt加一;如果有,则调用调度器OS_Sched( )去运行等待任务中优先级别最高的任务。函数OSSemPost的原型为:INT8U OSSemPost(OS_EVENT *pevent)。其中,pevent为信号量指针,该函数在调用成功后,返回值为OS_ON_ERR,否则会根据具体错误返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4) 删除信号量函数
应用程序如果不需要某个信号量了,那么可以调用函数OSSemDel来删除该信号量,该函数的原型为:OS_EVENT *OSSemDel (OS_EVENT *pevent,INT8U opt, INT8U *err)。其中,pevent为要删除的信号量指针,opt为删除条件选项,err为错误信息。
邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。
在UCOSII中,我们通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。
接下来我们看看在UCOSII中,与消息邮箱相关的几个函数。
1) 创建邮箱函数
创建邮箱通过函数OSMboxCreate实现,该函数原型为:OS_EVENT *OSMboxCreate (void *msg)。函数中的参数msg为消息的指针,函数的返回值为消息邮箱的指针。
调用函数OSMboxCreate需先定义msg的初始值。在一般的情况下,这个初始值为NULL;但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate 中,使之一开始就指向一个邮箱。
2) 向邮箱发送消息函数
任务可以通过调用函数OSMboxPost 向消息邮箱发送消息,这个函数的原型为:INT8U OSMboxPost (OS_EVENT *pevent,void *msg)。其中pevent为消息邮箱的指针,msg为消息指针。
3) 请求邮箱函数
当一个任务请求邮箱时需要调用函数OSMboxPend,这个函数的主要作用就是查看邮箱指针OSEventPtr是否为NULL,如果不是NULL就把邮箱中的消息指针返回给调用函数的任务,同时用OS_NO_ERR通过函数的参数err通知任务获取消息成功;如果邮箱指针OSEventPtr是NULL,则使任务进入等待状态,并引发一次任务调度。
函数OSMboxPend的原型为:void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)。其中pevent为请求邮箱指针,timeout为等待时限,err为错误信息。
4) 查询邮箱状态函数
任务可以通过调用函数OSMboxQuery查询邮箱的当前状态。该函数原型为:INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata)。其中pevent为消息邮箱指针,pdata为存放邮箱信息的结构。
5) 删除邮箱函数
在邮箱不再使用的时候,我们可以通过调用函数OSMboxDel来删除一个邮箱,该函数原型为:OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err)。其中pevent为消息邮箱指针,opt为删除选项,err为错误信息。
  关于UCOSII信号量和邮箱的介绍,就到这里。更详细的介绍,请参考《嵌入式实时操作系统UCOSII原理及应用》第五章。
59.2硬件设计
本节实验功能简介:本章我们在UCOSII里面创建6个任务:开始任务、LED任务、触摸屏任务、蜂鸣器任务、按键扫描任务和主任务,开始任务用于创建信号量、创建邮箱、初始化统计任务以及其他任务的创建,之后挂起;LED任务用于DS0控制,提示程序运行状况;蜂鸣器任务用于测试信号量,是请求信号量函数,每得到一个信号量,蜂鸣器就叫一次;触摸屏任务用于在屏幕上画图,可以用于测试CPU使用率;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务则通过查询消息邮箱获得键值,并根据键值执行DS1控制、信号量发送(蜂鸣器控制)、触摸区域清屏和触摸屏校准等控制。
所要用到的硬件资源如下:
1) 指示灯DS0 、DS1   
2) 4个按键(KEY0/KEY1/KEY2/WK_UP)
3) 蜂鸣器
4) TFTLCD模块
这些,我们在前面的学习中都已经介绍过了。
59.3软件设计
本章,我们在第三十一章实验 (实验26 )的基础上修改。首先,是UCOSII代码的添加,具体方法同上一章一模一样,本章就不再详细介绍了。不过,本章我们将OS_TICKS_PER_SEC设置为500,即UCOSII的时钟节拍为2ms。
在加入UCOSII代码后,我们只需要修改test.c函数了,打开test.c,输入如下代码:
/////////////////////////UCOSII任务设置///////////////////////////////////
//START 任务
#define START_TASK_PRIO                        10           //设置任务优先级
#define START_STK_SIZE                             64           //设置任务堆栈大小
OS_STK START_TASK_STK[START_STK_SIZE];         //任务堆栈    
void start_task(void *pdata);                                            //任务函数
                      
//LED任务
#define LED_TASK_PRIO                          7            //设置任务优先级
#define LED_STK_SIZE                              64           //设置任务堆栈大小
OS_STK LED_TASK_STK[LED_STK_SIZE];                //任务堆栈
void led_task(void *pdata);                                             //任务函数
 
//触摸屏任务
#define TOUCH_TASK_PRIO                           6            //设置任务优先级
#define TOUCH_STK_SIZE                            64           //设置任务堆栈大小
OS_STK TOUCH_TASK_STK[TOUCH_STK_SIZE];       //任务堆栈    
void touch_task(void *pdata);                                         //任务函数
 
//蜂鸣器任务
#define BEEP_TASK_PRIO                        5            //设置任务优先级
#define BEEP_STK_SIZE                               64           //设置任务堆栈大小
OS_STK BEEP_TASK_STK[BEEP_STK_SIZE];            //任务堆栈
void beep_task(void *pdata);                                           //任务函数
 
//主任务
#define MAIN_TASK_PRIO                        4            //设置任务优先级
#define MAIN_STK_SIZE                                     128         //设置任务堆栈大小
OS_STK MAIN_TASK_STK[MAIN_STK_SIZE];           //任务堆栈
void main_task(void *pdata);                                           //任务函数接口
 
//按键扫描任务
#define KEY_TASK_PRIO                          3            //设置任务优先级
#define KEY_STK_SIZE                                64           //设置任务堆栈大小
OS_STK KEY_TASK_STK[KEY_STK_SIZE];                      //任务堆栈
void key_task(void *pdata);                                            //任务函数
//////////////////////////////////////////////////////////////////////////////
OS_EVENT * msg_key;                     //按键邮箱事件块指针
OS_EVENT * sem_beep;             //蜂鸣器信号量指针            
//加载主界面  
void ucos_load_main_ui(void)
{
       LCD_Clear(WHITE);    //清屏
      POINT_COLOR=RED;  //设置字体为红色
       LCD_ShowString(30,10,200,16,16," WarShip STM32");  
       LCD_ShowString(30,30,200,16,16,"UCOSII TEST2");    
       LCD_ShowString(30,50,200,16,16,"ATOM@ALIENTEK");
     LCD_ShowString(30,75,200,16,16,"KEY0S0 KEY_UP:ADJUST");   
     LCD_ShowString(30,95,200,16,16,"KEY1:BEEP  KEY2:CLEAR");
       LCD_ShowString(80,210,200,16,16,"Touch Area");  
       LCD_DrawLine(0,120,lcddev.width,120);
       LCD_DrawLine(0,70,lcddev.width,70);
       LCD_DrawLine(150,0,150,70);
      POINT_COLOR=BLUE;//设置字体为蓝色
      LCD_ShowString(160,30,200,16,16,"CPU:   %");  
     LCD_ShowString(160,50,200,16,16,"SEM:000");     
}                                                                             
int main(void)
{    
      Stm32_Clock_Init(9);           //系统时钟设置
       uart_init(72,9600);             //串口初始化为9600
       delay_init(72);                         //延时初始化
       LED_Init();                        //初始化与LED连接的硬件接口
       LCD_Init();                       //初始化LCD
       usmart_dev.init(72);             //初始化USMART
       BEEP_Init();                       //蜂鸣器初始化    
       KEY_Init();                         //按键初始化
     tp_dev.init();                        //触摸屏初始化
       ucos_load_main_ui();           //加载主界面
      OSInit();                           //初始化UCOSII
      OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE
-1],START_TASK_PRIO );   //创建起始任务
       OSStart();         
}
//开始任务
void start_task(void *pdata)
{
    OS_CPU_SR cpu_sr=0;
       pdata = pdata;                
       msg_key=OSMboxCreate((void*)0);    //创建消息邮箱
       sem_beep=OSSemCreate(0);               //创建信号量                              
       OSStatInit();                                      //初始化统计任务.这里会延时1秒钟左右
      OS_ENTER_CRITICAL();                  //进入临界区(无法被中断打断)   
      OSTaskCreate(led_task,(void *)0,(OS_STK*)&LED_TASK_STK[LED_STK_SIZE-1],
LED_TASK_PRIO);                                           
      OSTaskCreate(touch_task,(void *)0,(OS_STK*)&TOUCH_TASK_STK
[TOUCH_STK_SIZE-1],TOUCH_TASK_PRIO);                                    
      OSTaskCreate(beep_task,(void *)0,(OS_STK*)&BEEP_TASK_STK[BEEP_STK_SIZE-1],
BEEP_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();                     //退出临界区(可以被中断打断)
}      
//LED任务
void led_task(void *pdata)
{
       u8 t;
       while(1)
       {
              t++; delay_ms(10);
              if(t==8)LED0=1;                 //LED0灭
              if(t==100) { t=0;LED0=0;}   //LED0亮            
       }                                                            
}       
//蜂鸣器任务
void beep_task(void *pdata)
{
       u8 err;
       while(1)
       {
              OSSemPend(sem_beep,0,&err);
              BEEP=1;delay_ms(60);
              BEEP=0;delay_ms(940);             
       }                                                            
}
//触摸屏任务
void touch_task(void *pdata)
{          
       while(1)
       {
              tp_dev.scan(0);              
              if(tp_dev.sta&TP_PRES_DOWN)        //触摸屏被按下
              {    
                    if(tp_dev.xOSEventCnt)//需要显示sem       
              {
                     POINT_COLOR=BLUE;
                     LCD_ShowxNum(192,50,sem_beep->OSEventCnt,3,16,0X80);//显示信号量值
                     if(sem_beep->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);
       }
}
//按键扫描任务
void key_task(void *pdata)
{    
       u8 key;                                                          
       while(1)
       {
              key=KEY_Scan(0);  
              if(key)OSMboxPost(msg_key,(void*)key);//发送消息
             delay_ms(10);
       }
}
该部分代码我们创建了6个任务:start_task、led_task、beep_task、touch_task、main_task和key_task,优先级分别是10和7~3,堆栈大小除了main_task是128,其他都是64。
该程序的运行流程就比上一章复杂了一些,我们创建了消息邮箱msg_key,用于按键任务和主任务之间的数据传输(传递键值),另外创建了信号量sem_beep,用于蜂鸣器任务和主任务之间的通信。
本代码中,我们使用了UCOSII提供的CPU统计任务,通过OSStatInit初始化CPU统计任务,然后在主任务中显示CPU使用率。
另外,在主任务中,我们用到了任务的挂起和恢复函数,在执行触摸屏校准的时候,我们必须先将触摸屏任务挂起,待校准完成之后,再恢复触摸屏任务。这是因为触摸屏校准和触摸屏任务都用到了触摸屏和TFTLCD,而这两个东西是不支持多个任务占用的,所以必须采用独占的方式使用,否则可能导致数据错乱。
软件设计部分就为大家介绍到这里。
59.4下载验证
在代码编译成功之后,我们通过下载代码到战舰STM32开发板上,可以看到LCD显示界面如图59.4.1所示:

图59.4.1 初始界面       从图中可以看出,默认状态下,CPU使用率仅为1%。此时通过在触摸区域画图,可以看到CPU使用率飙升(42%),说明触摸屏任务是一个很占CPU的任务;通过按KEY0,可以控制DS1的亮灭;通过按KEY1则可以控制蜂鸣器的发声(连续按下多次后,可以看到蜂鸣每隔1秒叫一次),同时,可以在LCD上面看到信号量的当前值;通过按KEY2,可以清除触摸屏的输入;通过按WK_UP可以进入校准程序,进行触摸屏校准。
 

《STM32开发指南》第五十九章 UCOSII实验2-信号量和邮箱.rar

下载

610.39 KB, 下载次数: 31, 下载积分: ST金币 -1

实验54 UCOSII实验2-信号量和邮箱.rar

下载

253.75 KB, 下载次数: 34, 下载积分: ST金币 -1

<
回复

使用道具 举报

0

主题

15

回帖

0

蝴蝶豆

新手上路

最后登录
2020-4-11
发表于 2015-4-14 12:37:13 | 显示全部楼层
:):):):):):):):):):)
回复 支持 反对

使用道具 举报

93

主题

2449

回帖

4

蝴蝶豆

论坛元老

最后登录
2020-6-28
发表于 2015-4-14 13:06:16 | 显示全部楼层
学习下
回复 支持 反对

使用道具 举报

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版