ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第七十五章 UCOSII实验2-信号量和邮箱

《STM32H7R7开发指南 V1.1 》第七十五章 UCOSII实验2-信号量和邮箱

原创 嵌入式/内核开发 作者:正点原子 时间:2025-10-31 09:32:29 0 删除 编辑

第七十五章 UCOSII实验2-信号量和邮箱


       上一章,我们学习了如何使用UCOSII,学习了UCOSII的任务调度,但是并没有用到任务间的同步与通信,本章我们将学习两个最基本的任务间通讯方式:信号量和邮箱。

       本章分为如下几个小节:

       75.1 UCOSII信号量和邮箱简介

       75.2 硬件设计

       75.3 程序设计

       75.4 下载验证


        75.1 UCOSII信号量和邮箱简介

       系统中的多个任务在运行时,经常需要互相无冲突地访问同一个共享资源,或者需要互相支持和依赖,甚至有时还要互相加以必要的限制和制约,才保证任务的顺利进行。因此,操作系统必须具有对任务的运行进行协调的能力,从而使任务之间可以无冲突、流畅地同步运行,而不导致灾难性的后果。

       例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态,只有当任务A把打印机释放后,系统才能唤醒任务B,使其获得打印机的使用权。如果这两个任务不这样做,那么会造成极大的混乱。

       任务间的同步依赖于任务间的通信。在UCOSII中,是使用信号量、邮箱(消息邮箱)和消息队列,这些被称作事件的中间环节来实现任务之间的通信的。这里我们仅介绍信号量和邮箱,消息对列将会在下一章介绍。

       事件

       两个任务通过事件进行通讯的示意图如图75.1.1所示:


图75.1.1.1两个任务使用事件进行通信的示意图


       在上图中任务1是发信方,任务2是收信方。任务1负责把信息发送到事件上,这项操作叫做发送事件。任务2通过读取事件操作对事件进行查询:如果有信息则读取,否则等待。读事件操作叫做请求事件。

       为了把描述事件的数据结构统一起来,UCOSII使用叫做事件控制块(ECB)的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含等待任务表在内的所有有关事件的数据,事件控制块结构体定义如下:

typedef struct os_event {
    INT8U    OSEventType;         /* 事件的类型*/
    void    *OSEventPtr;                     /* 消息或消息队列的指针*/
    INT16U   OSEventCnt;                     /* 信号量计数器*/
    OS_PRIO  OSEventGrp;                     /* 等待事件的任务组*/
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE];    /* 任务等待表 */
#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;         /* 事件名 */
#endif
} OS_EVENT;

       信号量

       使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。

       信号量的实质是一个全局计数器的实现机制,释放信号量的任务使得该计数器的值加1,请求到信号量的任务使得该计数器的值减1。如果计数器的值为0,则请求该信号量的任务将挂起等待,直到别的任务释放该信号量。通过这种方式,使得释放信号量的任务可以控制请求信号量的任务的运行。

       信号量的工作原理如图75.1.2所示:


图75.1.2信号量工作原理图


       信号量可以分为两种:一种是二值型信号量,另外一种是N值信号量。

       UCOSII将二值型信号量也称为互斥型信号量,将N值信号量称之为计数型信号量,也就是普通的信号量。

       信号量相关的主要操作有:创建信号量OSSemCreate、请求信号量OSSemPend、释放信号量OSSemPost和删除信号量OSSemDel。后面再对这几个函数进行讲解。

       邮箱

       在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单的办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。消息邮箱的工作情况如图75.1.3所示:


图75.1.3 消息邮箱工作情况图


       从上图可知,只有任务才能请求消息,消息里仅能存放一条消息,如果释放消息的速度比请求消息的速度快,则释放的消息将会丢失。可以通过广播的方式,使得释放的消息传递给所有请求该消息邮箱的任务。如果当前邮箱为空,且有某一任务2正在请求邮箱,则当另一任务1向邮箱中释放消息时,释放的消息将直接发送给任务2,而不用经过邮箱中转。

       在UCOSII中,我们通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。

       与消息邮箱相关的主要操作有:创建邮箱函数OSMboxCreate、向邮箱发送消息函数OSMboxPost、请求邮箱函数OSMboxPend、查询邮箱状态函数OSMboxQuery和删除邮箱函数OSMboxDel。后面再对这几个函数进行讲解。


        75.2 硬件设计


       1. 例程功能

       在UCOSII里面创建6个任务(不包含统计任务和空闲任务):开始任务、LED0任务、LED1任务、触摸屏任务、按键扫描任务和主任务。开始任务用于创建信号量、创建邮箱、初始化统计任务以及其他任务的创建,之后挂起;LED0任务用于DS0控制,提示程序运行状况;LED1任务用于测试信号量,通过请求信号量函数,每得到一个信号量,DS1就亮一下;触摸屏任务用于在屏幕上画图,可以用于测试CPU使用率;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务则通过查询消息邮箱获得键值,并根据键值执行信号量发送(DS1控制)、触摸区域清屏和触摸屏校准等控制。


       2. 硬件资源


       1)LED灯

              LED0 :LED0 – PD14

              LED1:   LED1 – PC0


       2)独立按键  

               KEY0 – PE9

               KEY1 – PE8

              WK_UP – PC13 


       3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)


        75.3 程序设计


       75.3.1 UCOSII相关函数介绍

       信号量函数

       在这里对本实验用到的UCOSII信号量函数进行介绍,相关代码存放在os_sem.c中。


       1. OSSemCreate函数

       创建信号量函数,其声明如下:

OS_EVENT  *OSSemCreate (INT16U cnt)

       l 函数描述:

       用于创建一个信号量。

       l 函数形参:

       cnt是信号量计数器(OSEventCnt)的初始值

       l 函数返回值:

       已创建的信号量的指针


       2. OSSemPend函数

       请求信号量函数,其声明如下:

void  OSSemPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)

       l 函数描述:

       请求信号量。

       l 函数形参:

       pevent:被请求信号量的指针                 

       timeout:等待时限

       perr:错误信息

       OS_ERR_NONE:调用成功,信号量不为零

       OS_ERR_TIMEOUT :信号量没有在指定数目的时钟周期内被设置

       OS_ERR_PEND_ABOUT:取消对信号量的等待

       OS_ERR_EVENT_TYPE:没有传递信号量的指针

       OS_ERR_PEND_ISR:从中断调用该函数时错误

       OS_ERR_PEVENT_NULL:pevent是一个空指针

       OS_ERR_PEND_LOCKED:调度器上锁了

       l 函数返回值:

       无

       l 注意事项:

       为了防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。


       3. OSSemPost函数

       发送信号量函数,其声明如下:

INT8U  OSSemPost (OS_EVENT *pevent)

       l 函数描述:

       用于发送信号量或者称为释放信号量。

       l 函数形参:

       pevent:被请求信号量的指针

       l 函数返回值:

       OS_ERR_NONE:函数调用成功,信号量被成功地设置

       OS_ERR_SEM_OVF:信号量的值溢出

       OS_ERR_EVENT_TYPE:pevent不是指向信号量的指针

       OS_ERR_PEVENT_NULL:pevent是一个空指针


       4.OSSemDel函数

       删除信号量函数,其声明如下:

OS_EVENT  *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *perr)

       l 函数描述:

       用于删除信号量并准备挂起所有任务

       l 函数形参:

       pevent:要删除的信号量指针

       opt:删除条件选项

       OS_DEL_NO_PEND:在没有任务挂起时删除信号量

       OS_DEL_ALWAYS:删除信号量,即使任务正在等待。

       perr:错误信息

       OS_ERR_NONE:函数调用成功,成功删除信号量

       OS_ERR_DEL_ISR:尝试在中断中删除信号量

       OS_ERR_INVALID_OPT:指向一个无效的选项

       OS_ERR_TASK_WAITING:一个或多个任务在等待这个信号量

       OS_ERR_EVENT_TYPE:没有传递一个指向信号量的指针

       OS_ERR_PEVENT_NULL:pevent是一个空指针

       l 函数返回值:

       pevent :存在错误   (OS_EVENT *)0 : 该信号量被成功删除。

       消息邮箱函数

       在这里对本实验用到的UCOSII消息邮箱函数进行介绍,相关代码存放在os_mbox.c中。


       1. OSMboxCreate函数

       创建邮箱函数,其声明如下:

OS_EVENT  *OSMboxCreate (void *pmsg)

       l 函数描述:

       用于创建邮箱函数。

       l 函数形参:

       pmsg:消息的指针

       l 函数返回值:

       消息邮箱的指针

       l 注意事项:

       调用OSMboxCreate前,需先定义msg的初始值。在一般情况下,这个初始值为NULL。但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate中,使得一开始就指向一个邮箱。


       2. OSMboxPost函数

       向邮箱发送消息函数,其声明如下:

INT8U  OSMboxPost (OS_EVENT  *pevent, void *pmsg)

       l 函数描述:

       用于向消息邮箱发送消息。

       l 函数形参:

       pevent:消息邮箱的指针           

       pmsg :消息指针

       l 函数返回值:

       OS_ERR_NONE:消息发送成功

       OS_ERR_MBOX_FULL:不能向满邮箱再发送消息

       OS_ERR_EVENT_TYPE:指定的事件不是消息邮箱类型

       OS_ERR_PEVENT_NULL:不能向不存在的消息邮箱发送消息

       OS_ERR_POST_NULL_PTR:消息缓冲区不能为空


       3. OSMboxPend函数

       请求邮箱函数,其声明如下:

void  *OSMboxPend (OS_EVENT  *pevent, INT32U timeout, INT8U *perr)

       l 函数描述:

       请求消息邮箱,就是等待一个消息传送到消息邮箱或取得一个消息数据

       l 函数形参:

       pevent:消息邮箱的指针       

       timeout:等待时限       

       perr:错误信息

       OS_ERR_NONE:函数调用成功,接收到消息

       OS_ERR_TIMEOUT:未在“超时”时间内接收到消息

       OS_ERR_PEND_ABORT:终止邮箱的等待

       OS_ERR_EVENT_TYPE:无效事件类型

       OS_ERR_PEND_ISR:从ISR中调用该函数,导致任务挂起

       OS_ERR_PEVENT_NULL:pevent是一个空指针

       OS_ERR_PEND_LOCKED:调度器上锁了

       l 函数返回值:

       NULL:未得到消息          !NULL:预期消息的指针


       4. OSMboxQuery函数

       查询邮箱状态函数,其声明如下:

UINT8U  *OSMboxQuery (OS_EVENT  *pevent, OS_MBOX_DATA *p_mbox_data)

       l 函数描述:

       获取消息邮箱的相关信息。

       l 函数形参:

       pevent:消息邮箱的指针         

       p_mbox_data:存放邮箱信息的结构

       l 函数返回值:

       OS_ ERR_NONE:调用成功       

       OS_ERR_EVENT_TYPE:pevent不是指向消息邮箱的指针

       OS_ERR_PEVENT_NULL:不能向不存在的消息邮箱发送消息

       OS_ERR_PDATA_NULL:p_mbox_data是一个空指针

       l 注意事项:

       必须先建立消息邮箱,然后使用


       5.OSMboxDel函数

       删除邮箱函数,其声明如下:

OS_EVENT  *OSMboxDel (OS_EVENT *pevent, INT8U opt, INT8U *perr)

       l 函数描述:

       对一个不再使用的消息邮箱及时删除以释放资源。

       l 函数形参:

       pevent:要删除的邮箱指针

       opt:删除条件选项

       OS_DEL_NO_PEND:在没有任务挂起时删除邮箱

       OS_DEL_ALWAYS:无条件删除邮箱,所有等待该事件的任务转到就绪态。

       perr:错误信息

       OS_ERR_NONE:函数调用成功,成功删除邮箱

       OS_ERR_DEL_ISR:不支持在中断中删除邮箱

       OS_ERR_INVALID_OPT:指向一个无效的选项

       OS_ERR_TASK_WAITING:一个或多个任务在等待这个信号量

       OS_ERR_EVENT_TYPE:没有传递一个指向邮箱的指针

       OS_ERR_PEVENT_NULL:pevent是一个空指针

       l 函数返回值:

       pevent :存在错误  (OS_EVENT *)0 : 该邮箱被成功删除

       程序我们按流程图的设计来实现本节的功能代码。我们通过start_task创建其他任务,包括:

       蜂鸣器任务,成功请求信号量时蜂鸣器响一次,否则阻塞;

       按键扫描函数,根据按键发送不同的消息;

       main_task用于创建定时器任务,并根据获取的队列消息得到按键值并投递给其他任务,控制部分任务运行或挂起,重绘界面等。


       75.3.2 程序解析


       1. uc-os2_demo.c代码

       在uc-os2_demo.c文件下,配置了UCOSII任务及6个任务函数,如下代码所示:

/* START 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define START_TASK_PRIO                 10      /* 开始任务的优先级设置为最低 */
#define START_STK_SIZE                  128     /* 堆栈大小 */
 
OS_STK START_TASK_STK[START_STK_SIZE];          /* 任务堆栈 */
void start_task(void *p_arg);                   /* 任务函数 */
 
 
/* 触摸屏任务 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define TOUCH_TASK_PRIO                 7       /* 优先级设置(越小优先级越高) */
#define TOUCH_STK_SIZE                  128     /* 堆栈大小 */
 
OS_STK TOUCH_TASK_STK[TOUCH_STK_SIZE];          /* 任务堆栈 */
void touch_task(void *p_arg);                   /* 任务函数 */
 
 
/* LED 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED_TASK_PRIO                   6       /* 优先级设置(越小优先级越高) */
#define LED_STK_SIZE                    128     /* 堆栈大小 */
 
OS_STK LED_TASK_STK[LED_STK_SIZE];              /* 任务堆栈 */
void led_task(void *p_arg);                     /* 任务函数 */
 
 
/* 蜂鸣器 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define BEEP_TASK_PRIO                  5       /* 优先级设置(越小优先级越高) */
#define BEEP_STK_SIZE                   128     /* 堆栈大小 */
 
OS_STK BEEP_TASK_STK[BEEP_STK_SIZE];            /* 任务堆栈 */
void beep_task(void *p_arg);                    /* 任务函数 */
 
 
/* 主 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define MAIN_TASK_PRIO                  4       /* 优先级设置(越小优先级越高) */
#define MAIN_STK_SIZE                   512     /* 堆栈大小 */
 
OS_STK MAIN_TASK_STK[MAIN_STK_SIZE];            /* 任务堆栈 */
void main_task(void *p_arg);                    /* 任务函数 */
 
 
/* 按键扫描 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define KEY_TASK_PRIO                   3       /* 优先级设置(越小优先级越高) */
#define KEY_STK_SIZE                    128     /* 堆栈大小 */
 
OS_STK KEY_TASK_STK[KEY_STK_SIZE];              /* 任务堆栈 */
void key_task(void *p_arg);                     /* 任务函数 */
 
/**
 * @brief       start_task
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void start_task(void *p_arg)
{
    OS_CPU_SR cpu_sr = 0;
    CPU_INT32U cnts;
 
    msg_key = OSMboxCreate((void *)0);  /* 创建消息邮箱 */
    sem_beep = OSSemCreate(0);          /* 创建信号量 */
    
    OSStatInit();                       /* 开启统计任务 */
    /* 根据配置的节拍频率配置SysTick */
    cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OS_TICKS_PER_SEC);
    OS_CPU_SysTickInit(cnts);
    
    OS_ENTER_CRITICAL();                /* 进入临界区(关闭中断) */
    
    /* 触摸任务 */
    OSTaskCreateExt((void(*)(void *) )touch_task,
                    (void *          )0,
                    (OS_STK *        )&TOUCH_TASK_STK[TOUCH_STK_SIZE - 1],
                    (INT8U          )TOUCH_TASK_PRIO,
                    (INT16U         )TOUCH_TASK_PRIO,
                    (OS_STK *        )&TOUCH_TASK_STK[0],
                    (INT32U         )TOUCH_STK_SIZE,
                    (void *          )0,
                    (INT16U         )OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
  | OS_TASK_OPT_SAVE_FP);
    /* LED任务 */
    OSTaskCreateExt((void(*)(void *) )led_task,
                    (void *          )0,
                    (OS_STK *        )&LED_TASK_STK[LED_STK_SIZE - 1],
                    (INT8U          )LED_TASK_PRIO,
                    (INT16U         )LED_TASK_PRIO,
                    (OS_STK *        )&LED_TASK_STK[0],
                    (INT32U         )LED_STK_SIZE,
                    (void *          )0,
                    (INT16U         )OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
 | OS_TASK_OPT_SAVE_FP);
    /* 蜂鸣器任务 */
    OSTaskCreateExt((void(*)(void *) )beep_task,
                    (void *          )0,
                    (OS_STK *        )&BEEP_TASK_STK[BEEP_STK_SIZE - 1],
                    (INT8U          )BEEP_TASK_PRIO,
                    (INT16U         )BEEP_TASK_PRIO,
                    (OS_STK *        )&BEEP_TASK_STK[0],
                    (INT32U         )BEEP_STK_SIZE,
                    (void *          )0,
                    (INT16U         )OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
| OS_TASK_OPT_SAVE_FP);
    /* 主任务 */
    OSTaskCreateExt((void(*)(void *) )main_task,
                    (void *          )0,
                    (OS_STK *        )&MAIN_TASK_STK[MAIN_STK_SIZE - 1],
                    (INT8U          )MAIN_TASK_PRIO,
                    (INT16U         )MAIN_TASK_PRIO,
                    (OS_STK *        )&MAIN_TASK_STK[0],
                    (INT32U         )MAIN_STK_SIZE,
                    (void *          )0,
                    (INT16U         )OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
                                   | OS_TASK_OPT_SAVE_FP);
    /* 按键任务 */
    OSTaskCreateExt((void(*)(void *) )key_task,
                    (void *          )0,
                    (OS_STK *        )&KEY_TASK_STK[KEY_STK_SIZE - 1],
                    (INT8U          )KEY_TASK_PRIO,
                    (INT16U         )KEY_TASK_PRIO,
                    (OS_STK *        )&KEY_TASK_STK[0],
                    (INT32U         )KEY_STK_SIZE,
                    (void *          )0,
                    (INT16U         )OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
 | OS_TASK_OPT_SAVE_FP);
    OS_EXIT_CRITICAL();             /* 退出临界区(开中断) */
    OSTaskSuspend(START_TASK_PRIO); /* 挂起开始任务 */
}
/**
 * @brief       LED任务
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void led_task(void *p_arg)
{
    uint8_t t;
 
    while (1)
    {
        t++;
        OSTimeDly(10);
 
        if (t == 8)LED0(1); /* LED0灭 */
 
        if (t == 100)       /* LED0亮 */
        {
            t = 0;
            LED0(0);
        }
    }
}
 
/**
 * @brief       蜂鸣器任务
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void beep_task(void *p_arg)
{
    uint8_t err;
 
    while (1)
    {
        OSSemPend(sem_beep, 0, &err);   /* 请求信号量 */
        BEEP(1);        /* 打开蜂鸣器 */
        OSTimeDly(60);
        BEEP(0);        /* 关闭蜂鸣器 */
        OSTimeDly(940);
    }
}
 
/**
 * @brief       触摸屏任务
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void touch_task(void *p_arg)
{
    uint32_t cpu_sr;
    uint16_t lastpos[2];    /* 最后一次的数据 */
 
    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)
            {
                if (lastpos[0] == 0XFFFF)
                {
                    lastpos[0] = tp_dev.x[0];
                    lastpos[1] = tp_dev.y[0];
                }
 
                OS_ENTER_CRITICAL();    
/* 进入临界段,防止其他任务,打断LCD操作,导致液晶乱序 */
                lcd_draw_bline(lastpos[0], lastpos[1], tp_dev.x[0], 
tp_dev.y[0], 2, RED);   /* 画线 */
                OS_EXIT_CRITICAL();
                lastpos[0] = tp_dev.x[0];
                lastpos[1] = tp_dev.y[0];
            }
        }
        else
        {
            lastpos[0] = 0XFFFF;
            OSTimeDly(10);   /* 没有按键按下的时候 */
        }
    }
}
 
/**
 * @brief       主任务
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void main_task(void *p_arg)
{
    uint32_t key = 0;
    uint8_t err;
    uint8_t semmask = 0;
    uint8_t tcnt = 0;
 
    while (1)
    {
        key = (uint32_t)OSMboxPend(msg_key, 10, &err);
 
        switch (key)
        {
            case KEY0_PRES: /* 控制LED1,并清除触摸区域 */
                LED1_TOGGLE();
                lcd_fill(0, 121, lcddev.width - 1, lcddev.height - 1, WHITE);
                break;
 
            case KEY1_PRES: /* 发送信号量 */
                semmask = 1;
                OSSemPost(sem_beep);
                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_beep->OSEventCnt)    /* 需要显示sem */
        {
            lcd_show_xnum(192, 50, sem_beep->OSEventCnt, 3, 16, 0X80, BLUE); 
            /* 显示信号量的值 */
 
            if (sem_beep->OSEventCnt == 0)semmask = 0;  /* 停止更新 */
        }
 
        if (tcnt == 10) /* 0.6秒更新一次CPU使用率 */
        {
            tcnt = 0;
            lcd_show_xnum(192, 30, OSCPUUsage, 3, 16, 0, BLUE); 
/* 显示CPU使用率 */
        }
 
        tcnt++;
        OSTimeDly(10);
    }
}
 
/**
 * @brief       按键扫描任务
 * @param       p_arg : 传入参数(未用到)
 * @retval      无
 */
void key_task(void *p_arg)
{
    uint32_t key;
 
    while (1)
    {
        key = key_scan(0);
 
        if (key)OSMboxPost(msg_key, (void *)key); /* 发送消息 */
 
        OSTimeDly(10);
    }
}

       该部分代码我们创建了6个任务:start_task、main_task、touch_task、led_task、key_task和beep_task,优先级分别是10、4、7、6、3和5,堆栈都是128。

       该程序的代码流程比上一章复杂了一些,我们创建了消息邮箱msg_key,用于按键任务和主任务之间的数据传输(传递键值),另外创建了信号量sem_beep,用于蜂鸣器任务和主任务之间的通信。


       2. main.c代码

       在main.c文件下,只初始化一些外设,如下代码所示:

int main(void)
{
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 按键初始化 */
    beep_init();                        /* 初始化蜂鸣器 */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
    tp_dev.init();                      /* 触摸屏初始化 */
    
    uc_os2_demo();                      /* 运行uC/OS-II例程 */
}


        75.4 下载验证

       将程序下载到开发板后,通过按KEY0,可以控制LED1的 亮灭。通过按KEY1,可以控制蜂鸣器的发声(连续按下多次后,可以看到蜂鸣器每隔1秒叫一次)。通过按KEY_UP,可以进入校准程序,进行触摸屏校准(注意,电容触摸屏不需要校准,如果是电容屏,按KEY_UP,相当于清屏一次,不会校准)符合我们的预期。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3099306/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764682