【STM32F429】第18章 ThreadX消息队列

2021-07-15 11:59:18 浏览数 (1)

第18章 ThreadX消息队列

本章节为大家讲解ThreadX的一个重要的通信机制----消息队列,初学者要熟练掌握,因为消息队列在实际项目中应用较多。

18.1 消息队列

18.2 消息队列任务通知(又称Event Chaining事件链)

18.3 消息队列创建函数tx_queue_create

18.4 消息队列发送函数tx_queue_send

18.5 消息队列接收函数tx_queue_receive

18.6 实验例程

18.7 总结

18.1 消息队列

18.1.1 消息队列的概念及其作用

消息队列就是通过RTOS内核提供的服务,在任务或中断服务程序中将一个消息(注意,ThreadX,RTX5和FreeRTOS消息队列传递的是实际数据,并不是数据地址,RTX4,uCOS-II和uCOS-III是传递的地址)放入到队列。同样,一个或者多个任务可以通过RTOS内核服务从队列中得到消息。通常,先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO),ThreadX的消息队列支持FIFO和LIFO两种数据存取方式。

也许有不理解的初学者会问采用消息队列多麻烦,搞个全局数组不是更简单,其实不然。在裸机编程时,使用全局数组的确比较方便,但是在加上RTOS后就是另一种情况了。相比消息队列,使用全局数组主要有如下四个问题:

  • 使用消息队列可以让RTOS内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
  • 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
  • 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
  • FIFO机制更有利于数据的处理。

18.1.2 ThreadX任务间消息队列的实现

任务间消息队列的实现是指各个任务之间使用消息队列实现任务间的通信。下面我们通过如下的框图来说明一下ThreadX消息队列的实现,让大家有一个形象的认识。

运行条件:

  • 创建消息队列,可以存放10个消息。
  • 创建2个任务Task1和Task2,任务Task1向消息队列放数据,任务Task2从消息队列取数据。
  • ThreadX的消息存取采用FIFO方式。

运行过程主要有以下两种情况:

  • 任务Task1 向消息队列放数据,任务Task2从消息队列取数据,如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况,ThreadX的消息存放函数tx_queue_send支持超时等待,用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。
  • 任务Task1 向消息队列放数据,任务Task2从消息队列取数据,如果放数据的速度慢于取数据的速度,那么会出现消息队列为空的情况,ThreadX的消息获取函数tx_queue_receive支持超时等待,用户可以设置超时等待,直到消息队列中有消息或者设置的超时时间溢出。

18.1.3 ThreadX中断方式消息队列的实现

ThreadX中断方式消息队列的实现是指中断函数和ThreadX任务之间使用消息队列。下面我们通过如下的框图来说明一下ThreadX消息队列的实现,让大家有一个形象的认识。

运行条件:

  • 创建消息队列,可以存放10个消息。
  • 创建1个任务Task1和一个串口接收中断。
  • ThreadX的消息存取采用FIFO方式。

运行过程主要有以下两种情况:

  • 中断服务程序向消息队列放数据,任务Task1从消息队列取数据,如果放数据的速度快于取数据的速度,那么会出现消息队列存放满的情况。由于中断服务程序里面的消息队列发送函数tx_queue_send不支持超时设置,所以超时参数要配置为TX_NO_WAIT。
  • 中断服务程序向消息队列放数据,任务Task1从消息队列取数据,如果放数据的速度慢于取数据的速度,那么会出现消息队列存为空的情况。在ThreadX的任务中可以通过函数tx_queue_receive获取消息,因为此函数可以设置超时等待,直到消息队列中有消息存放或者设置的超时时间溢出。

上面就是一个简单的ThreadX中断方式消息队列通信过程。实际应用中,中断方式的消息机制要注意以下几个问题:

  1. 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
  2. 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
  3. 中断服务程序中调用发送函数,一定要设置超时形参为TX_NO_WAIT
  4. 在ThreadX操作系统中实现中断函数跟裸机编程是一样的。
  • 另外强烈推荐用户将NVIC优先级分组设置为4,即:HAL_NVIC_SetPriorityGrouping (NVIC_PRIORITYGROUP_4);这样中断优先级的管理将非常方便。
  • 用户要在ThreadX多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

18.2 消息队列任务通知(又称Event Chaining事件链)

ThreadX 中的通知功能可用于将各种消息队列“连接”在一起。当单个线程必须处理多个同步事件时,这通常很有用。

例如,应用程序可以为每个对象注册一个通知回调函数,而不是为队列消息、事件标志和信号量挂起单独的线程。当被调用时,应用程序通知例程然后可以恢复单个线程,该线程可以询问每个对象以便查找和处理新事件。

通常,事件链导致更少的线程、更少的开销和更小的 RAM 需求。它还提供了一种高度灵活的机制来处理更复杂系统的同步要求。

18.3 消息队列创建函数tx_queue_create

函数原型:

代码语言:javascript复制
UINT tx_queue_create(
    TX_QUEUE *queue_ptr, 
    CHAR *name_ptr,
    UINT message_size,
    VOID *queue_start, 
    ULONG queue_size);

函数描述:

此函数用于创建消息队列。

  1. 第1个参数是消息队列控制块。
  2. 第2个参数是消息队列名字。
  3. 第3个参数是消息队列每个消息的大小,消息大小范围1-16,每个消息4字节。
  4. 第4个参数是消息队列缓冲地址,必须保证此地址4字节对齐,即此地址对4求余数为0.
  5. 第5个参数是消息缓冲大小,单位字节。
  6. 返回值
    • TX_SUCCESS (0x00) 创建成功。
    • TX_QUEUE_ERROR (0x09) 消息队列控制块无效。
    • TX_PTR_ERROR (0x03)无效的消息队列其实地址。
    • TX_SIZE_ERROR (0x05) 消息队列大小无效。
    • TX_CALLER_ERROR (0x13) 无效的调用

注意事项:

  • 如果第5个参数消息缓冲大小不是第3个参数消息大小的整数倍,多余的部分将清零。

使用举例:

代码语言:javascript复制
uint32_t MessageQueuesBuf1[10]; /* 定义消息队列缓冲1 */

/* 创建消息队列1 */
tx_queue_create(&MessageQueues1, 
        "MessageQueues1", 
        1,                         /* 每次消息队列发送的数据大小,单位32bit,范围1-16 */
         (VOID *)MessageQueuesBuf1, 
    sizeof(MessageQueues1));   /* 消息队列大小,单位字节 */

18.4 消息队列发送函数tx_queue_send

函数原型:

代码语言:javascript复制
UINT tx_queue_send(
    TX_QUEUE *queue_ptr,
    VOID *source_ptr, 
    ULONG wait_option);

函数描述:

此函数用于消息队列发送,将要发送的数据复制到消息队列里面。

1、 第1个参数是消息队列控制块。

2、 第2个参数是要发送的数据地址。

3、 第3个参数是等待选项:

如果消息队列满了,这个形参将派上用场:

  • TX_NO_WAIT (0x00000000),表示不管消息队列是否满,立即返回。如果在定时器组,初始化或中断里面调用,必须要设置成这个参数。
  • TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到消息队列有空间可用。
  • 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、

4、 返回值

  • TX_SUCCESS(0x00)设置成功。
  • TX_GROUP_ERROR(0x06)无效的事件标志组。
  • TX_OPTION_ERROR(0x08)无效设置选项。
  • TX_SUCCESS (0x00) 消息发送成功。
  • TX_DELETED (0x01) 任务挂起阶段,消息队列被删除。
  • TX_QUEUE_FULL (0x0B) 消息队列满,包含等待了指定时间后消息队列依然满。
  • TX_WAIT_ABORTED (0x1A) 消息队列被其它任务,定时器组或者中断服务程序终止。
  • TX_QUEUE_ERROR (0x09) 无效的消息队列控制块。
  • TX_PTR_ERROR (0x03) 无效的发送数据地址。
  • TX_WAIT_ERROR (0x04) 无效调用,主要是在非常任务代码中使用TX_NO_WAIT 以外的形参。比如在中断服务程序里面设置等待发送。

使用举例:

代码语言:javascript复制
typedef struct Msg
{
    uint8_t  ucMessageID;
    uint16_t usData[2];
    uint32_t ulData[2];
}MSG_T;

MSG_T   g_tMsg;                 /* 定义一个结构体用于消息队列数据传递 */

/*
*********************************************************************************************************
*    函 数 名: AppTaskUserIF
*    功能说明: 按键消息处理
*    形    参: thread_input 创建该任务时传递的形参
*    返 回 值: 无
    优 先 级: 4
*********************************************************************************************************
*/
static void AppTaskUserIF(ULONG thread_input)
{
    uint8_t  ucKeyCode;    /* 按键代码 */
    uint32_t SendMessage = 0;
    UINT status;
    MSG_T   *ptMsg;

    (void)thread_input;

    /* 初始化结构体指针 */
    ptMsg = &g_tMsg;

    /* 初始化数组 */
    ptMsg->ucMessageID = 0;
    ptMsg->ulData[0] = 0;
    ptMsg->usData[0] = 0;
    
          
    while(1)
    {        
    ucKeyCode = bsp_GetKey();

    if (ucKeyCode != KEY_NONE)
    {
        switch (ucKeyCode)
        {
        case KEY_DOWN_K1:        /* K1键按打印任务执行情况 */
            DispTaskInfo();
            break;

        case KEY_DOWN_K2:              /* K2键按下, 消息队列发送 */
            SendMessage  ;
            status = tx_queue_send(&MessageQueues1, &SendMessage, 1000);
            if(status == TX_SUCCESS)
            {
            App_Printf("K2键按下,向MessageQueues1发送数据成功rn");
            }
            else
        {
            App_Printf("K2键按下,向MessageQueues1发送数据失败,
即使等待了1000个时钟节拍rn");
            }
            break;

        ase KEY_DOWN_K3:              /* K3键按下, 消息队列发送 */
        ptMsg->ucMessageID  ;
            ptMsg->ulData[0]  ;;
            ptMsg->usData[0]  ;

            status = tx_queue_send(&MessageQueues2, &ptMsg, 1000);
            if(status == TX_SUCCESS)
            {
            App_Printf("K3键按下,向MessageQueues2发送数据成功rn");
        }
            else
            {
            App_Printf("K3键按下,向MessageQueues2发送数据失败,
即使等待了1000个时钟节拍rn");
        }
            break;


            default:                     /* 其他的键值不处理 */
            break;
            }
        }

        tx_thread_sleep(20);
    }
}

18.5 消息队列接收函数tx_queue_receive

函数原型:

代码语言:javascript复制
UINT tx_queue_receive(
    TX_QUEUE *queue_ptr,
    VOID *destination_ptr, 
    ULONG wait_option);

函数描述:

此函数用于消息队列数据获取,将消息队列中的数据复制出来。

1、 第1个参数是消息队列控制块。

2、 第2个参数是从消息队列复制出来数据的存储地址。

3、 第3个参数是等待选项:

如果消息队列空了,这个形参将派上用场:

  • TX_NO_WAIT (0x00000000),表示不管消息队列是否空,立即返回。如果在定时器组,初始化或中断里面调用,必须要设置成这个参数。
  • TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到消息队列有数据。
  • 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、

4、 返回值

  • TX_SUCCESS(0x00)设置成功。
  • TX_GROUP_ERROR(0x06)无效的事件标志组。
  • TX_DELETED (0x01) 任务挂起阶段,消息队列被删除。
  • TX_QUEUE_EMPTY (0x0A) 消息队列空,包含等待了指定时间后消息队列依然空。
  • TX_WAIT_ABORTED (0x1A) 消息队列被其它任务,定时器组或者中断服务程序终止。
  • TX_QUEUE_ERROR (0x09) 无效的消息队列控制块。
  • TX_PTR_ERROR (0x03) 无效的数据存储地址。
  • TX_WAIT_ERROR (0x04) 无效调用,主要是在非常任务代码中使用TX_NO_WAIT 以外的形参。比如在中断服务程序里面设置等待。

注意事项:

  • 可以在初始化,任务,定时器组和中断服务程序里面调用。
  • 一点要保证第2个参数的数据缓冲够存储一个消息队列的消息。

使用举例:

代码语言:javascript复制
/*
*********************************************************************************************************
*    函 数 名: AppTaskMsgPro
*    功能说明: 消息处理,这里用作消息队列MessageQueues1的数据接收。
*    形    参: thread_input 是在创建该任务时传递的形参
*    返 回 值: 无
    优 先 级: 3
*********************************************************************************************************
*/
static void AppTaskMsgPro(ULONG thread_input)
{
    uint32_t RecMessage;
    UINT status;

    while(1)
    {

        status = tx_queue_receive(&MessageQueues1, 
                    &RecMessage,
                    200);

        if(status == TX_SUCCESS)
        {
            /* 成功接收,并通过串口将数据打印出来 */
            printf("接收到消息队列数据RecMessage = %drn", RecMessage);
        }
        else
        {
            /* 超时 */
            bsp_LedToggle(1);
            bsp_LedToggle(4);        
        }
    }
}

18.6 实验例程

配套例子:

V7-3013_ThreadX Message Queues

实验目的:

  1. 学习ThreadX事件标志组

实验内容:

1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况

========================================================

CPU利用率 = 0.89%

任务执行时间 = 0.586484645s

空闲执行时间 = 85.504470575s

中断执行时间 = 0.173225395s

系统总执行时间 = 86.264180615s

=======================================================

任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名

Prio StackSize CurStack MaxStack Taskname

2 4092 303 459 App Task Start

5 4092 167 167 App Msp Pro

4 4092 167 167 App Task UserIF

5 4092 167 167 App Task COM

0 1020 191 191 System Timer Thread

串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。

App Task Start任务 :启动任务,这里用作BSP驱动包处理。

App Task MspPro任务 :消息处理,这里用作消息队列MessageQueues1的数据接收。

App Task UserIF任务 :按键消息处理。

App Task COM任务 :这里用作消息队列MessageQueues2的数据接收。

System Timer Thread任务:系统定时器任务

2、K2键按下,向消息队列MessageQueues1发送数据。

3、K3键按下,向消息队列MessageQueues2发送数据。

4、任务App Task MspPro接收到消息后,串口打印。

5、(1) 凡是用到printf函数的全部通过函数App_Printf实现。

(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。

6、默认上电是通过串口打印信息,如果使用RTT打印信息

(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可

#define Enable_RTTViewer 1

(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。

串口打印信息方式(AC5,AC6和IAR):

波特率 115200,数据位 8,奇偶校验位无,停止位 1

RTT打印信息方式(AC5,AC6和IAR):

Embedded Studio仅支持调试状态RTT打印:

由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。

程序执行框图:

18.7 总结

本章节为大家讲解了任务间的通信和同步机制之一,消息队列,初学者要稍花些时间将其掌握。

0 人点赞