Azure RTOS

简 介 

Azure RTOS ThreadX 是 Microsoft 提供的高级工业级实时操作系统 (RTOS)。它是专门为深度嵌入式实时 IoT 应用程序设计的。Azure RTOS ThreadX 提供高级计划、通信、同步、计时器、内存管理和中断管理功能。此外,Azure RTOS ThreadX 具有许多高级功能,包括 picokernel™ 体系结构、preemption-threshold™ 计划、event-chaining™、执行分析、性能指标和系统事件跟踪。Azure RTOS ThreadX 非常易于使用,适用于要求极其苛刻的嵌入式应用程序。Azure RTOS ThreadX 在各种产品(包括消费者设备、医疗电子设备和工业控制设备)上的部署次数已达数十亿次。

具体的介绍和用户指南可以参考:

https://docs.microsoft.com/zh-cn/azure/rtos/threadx/

在前文描述移植基本内核的基础上,该应用手册描述了MM32F3270系列MCU结合Azure RTOS ThreadX内存池的使用,引导用户理解Azure RTOS ThreadX动态内存管理功能。

表 1 适用系列型号

 1.png

1、移植应用的准备

1.1   硬件开发板的准备

该移植过程中应用的开发板为MM32的EVB-F3270,板载MM32F3273G9P。

EVB-F3270 (MM32F3273G9P) 的简要参数:

  • Arm Cortex-M3 内核

  • 板载 MM32F3273G9P(LQFP144)

  • USB Host / Device、SPI、I2C

  • 4 x Key、4 x LED

  • I2S Speaker

  • TF-Card

  • Ethernet PHY

1.2  软件的准备

库函数和例程(Lib Samples)

该移植过程中应用的 Firmware 分别为 MM32F3270 库函数和例程,下载地址:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

3.png

Azure RTOS ThreadX(源码)

ThreadX 的源代码已经开放,我们可以从 ThreadX 公共源代码存储库获取 Azure RTOS ThreadX,网址为:

https://github.com/azure-rtos/threadx/

具体的商用使用条件参考Azure的许可证说明:

https://www.microsoft.com/en-us/legal/intellectualproperty/tech-licensin...

Microsoft publishes the Azure RTOS source code to GitHub. No license is required to install and use the software for internal development, testing, and evaluation purposes. A license is required to distribute or sell components and devices unless using Azure RTOS licensed hardware.

Azure RTOS 何时需要许可证?

Microsoft 将 Azure RTOS 源代码发布到 GitHub。安装和使用该软件进行内部开发、测试和评估无需许可证。分发或销售组件和设备需要许可证,除非使用 Azure RTOS 许可的硬件。

ThreadX 安装

可以通过将 GitHub 存储库克隆到本地计算机来安装 ThreadX。下面是用于在 PC 上创建 ThreadX 存储库的克隆的典型语法。

shell复制

git clone https://github.com/azure-rtos/threadx

或者,也可以使用 GitHub 主页上的“下载”按钮来下载存储库的副本。

下载后的仓库代码目录列表如下:

4.png

Azure RTOS ThreadX(源码)支持的开发环境

ThreadX 内核提供好了各种主流硬件平台和软件平台的移植文件,以Cortex_M3为例,可以支持以下六种开发环境:

5.png

本次移植过程使用Azure RTOS原有的sample_threadx.c文件为例,稍作修改,演示动态内存管理功能。

2、ThreadX 动态内存管理的应用

该章节介绍动态内存管理相关知识,演示程序可在MM32F3273G9P的EVB-F3270上运行。此示例在文件 main_malloc_demo.c 中实现,旨在说明如何在嵌入式多线程环境中使用动态内存管理功能。

2.1  动态内存管理

2.1.1  内存块池

在实时应用程序中,采用快速且确定的方式分配内存始终是一项挑战。考虑到这一点,ThreadX 提供了创建和管理多个固定大小的内存块池的功能。

由于内存块池由固定大小的块组成,因此永远不会出现任何碎片问题。当然,碎片会导致出现本质上不确定的行为。此外,分配和释放固定大小内存块所需的时间与简单的链接列表操作所需的时间相当。另外,还可以在可用列表的开头完成内存块分配和取消分配。这可以提供最快的链接列表处理速度,并且有助于将实际的内存块保存在缓存中。

缺乏灵活性是固定大小内存池的主要缺点。池的块大小必须足够大,才能处理其用户最坏情况下的内存需求。当然,如果对同一个池发出了许多大小不同的内存请求,则可能会浪费内存。一种可能的解决方案是创建多个不同的内存块池,这些池包含不同大小的内存块。

每个内存块池都是一个公用资源。ThreadX 对如何使用池没有任何限制。

2.1.2  创建内存块池

内存块池由应用程序线程在初始化期间或运行时创建。应用程序中内存块池的数量没有限制。

2.1.3  内存块大小

如前所述,内存块池包含许多固定大小的块。块大小(以字节为单位)在创建池时指定。

ThreadX 为池中的每个内存块增加了少量开销(C 指针的大小)。此外,ThreadX 可能需要填充块大小,从而确保每个内存块的开头能够正确对齐。

2.1.4  池容量

池中的内存块数是在创建过程中提供的内存区域的块大小和总字节数的函数。池容量的计算方法是将块大小(包括填充和指针开销字节)除以提供的内存区域的总字节数。

2.1.5  池的内存区域

如前所述,块池的内存区域在创建时指定。与 ThreadX 中的其他内存区域一样,该区域可以位于目标地址空间的任何位置。

这是一项重要的功能,因为它提供了相当大的灵活性。例如,假设某个通信产品有一个用于 I/O 的高速内存区域。将此内存区域设置为 ThreadX 内存块池,即可轻松对其进行管理。

2.1.6  线程挂起

在等待空池中的内存块时,应用程序线程可能会挂起。当块返回到池时,将为挂起的线程提供此块,并恢复线程。

如果同一内存块池中挂起多个线程,这些线程将按挂起的顺序 (FIFO) 恢复。

不过,如果应用程序在取消线程挂起的块释放调用之前调用 tx_block_pool_prioritize,还可以恢复优先级。块池设置优先级服务将优先级最高的线程置于挂起列表的前面,让所有其他挂起的线程采用相同的 FIFO 顺序。

2.1.7  运行时块池性能信息

ThreadX 提供可选的运行时块池性能信息。如果 ThreadX 库和应用程序是在定义 TX_BLOCK_POOL_ENABLE_PERFORMANCE_INFO 的情况下生成的,ThreadX 会累积以下信息。

整个系统的总数:

  • 已分配的块数

  • 已释放的块数

  • 分配挂起数

  • 分配超时数

每个块池的总数:

  • 已分配的块数

  • 已释放的块数

  • 分配挂起数

  • 分配超时数

此信息在运行时通过 tx_block_pool_performance_info_get和 tx_block_pool_performance_system_info_get服务提供。块池性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。例如,“分配挂起数”相对较高可能表明块池太小。

2.1.8  内存块池控制块 TX_BLOCK_POOL

每个内存块池的特征都可在其控制块中找到。该控制块包含诸如可用的内存块数和内存池块大小等信息。此结构在 tx_api.h文件中定义。

池控制块也可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义该控件块来使其成为全局结构。

2.1.9  覆盖内存块

务必确保已分配内存块的用户不会在其边界之外写入。如果发生这种情况,则会损坏其相邻的内存区域(通常是后续区域)。结果不可预测,且对于应用程序来说通常很严重。

2.1.10  内存字节池

内存字节池由应用程序线程在初始化期间或运行时创建。应用程序中内存字节池的数量没有限制。

2.1.12  池容量

内存字节池中可分配的字节数略小于创建期间指定的字节数。这是因为可用内存区域的管理带来了一些开销。池中的每个可用内存块都需要相当于两个 C 指针的开销。此外,创建的池包含两个块:一个较大的可用块和在内存区域末端永久分配的一个较小的块。这个分配块用于提高分配算法的性能。这样就无需在合并期间持续检查池区域末端。

在运行时,池中的开销通常会增加。如果分配奇数字节数,系统会加以填充,以确保正确对齐下一个内存块。此外,随着池变得更加零碎,开销也会增加。

2.1.13  池的内存区域

内存字节池的内存区域在创建过程中指定。与 ThreadX 中的其他内存区域一样,该区域可以位于目标地址空间的任何位置。这是一项重要的功能,因为它提供了相当大的灵活性。例如,如果目标硬件有高速内存区域和低速内存区域,用户可以通过在每个区域中创建池来管理这两个区域的内存分配。

2.1.14  线程挂起

在等待池中的内存字节时,应用程序线程可能会挂起。当有足够的连续内存可用时,将为已挂起的线程提供其请求的内存,并且恢复线程。

如果同一内存字节池中挂起多个线程,则按这些线程挂起的顺序 (FIFO) 为其提供内存(恢复)。

不过,如果应用程序在信号灯发出取消线程挂起的字节释放调用之前调用 tx_byte_pool_prioritize,还可以恢复优先级。字节池设置优先级服务将最高优先级的线程置于挂起列表的前面,让所有其他挂起的线程采用相同的 FIFO 顺序。

2.1.15  运行时字节池性能信息

ThreadX 提供可选的运行时字节池性能信息。如果 ThreadX 库和应用程序是在定义 TX_BYTE_POOL_ENABLE_PERFORMANCE_INFO 的情况下生成的,ThreadX 会累积以下信息。

整个系统的总数:

  • 分配数

  • 版本

  • 搜索的片段数

  • 合并的片段数

  • 创建的片段数

  • 分配挂起数

  • 分配超时数

每个字节池的总数:

  • 分配数

  • 版本

  • 搜索的片段数

  • 合并的片段数

  • 创建的片段数

  • 分配挂起数

  • 分配超时数

此信息在运行时通过 tx_byte_pool_performance_info_get和 tx_byte_pool_performance_system_info_get 服务提供。字节池性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。例如,“分配挂起数”相对较高可能表明字节池太小。

2.1.16  内存字节池控制块 TX_BYTE_POOL

每个内存字节池的特征都可在其控制块中找到。该控制块包含诸如池中可用的字节数等有用的信息。此结构在 tx_api.h 文件中定义。

池控制块也可以位于内存中的任意位置,但最常见的是通过在任何函数的作用域外部定义该控件块来使其成为全局结构。

2.1.17  非确定性行为

尽管内存字节池提供了最灵活的内存分配,但这些池也受一些非确定性行为的影响。例如,内存字节池可能有 2,000 字节的可用内存,但可能无法满足 1,000 字节的分配请求。这是因为无法保证有多少可用字节是连续的。即使存在 1,000 字节可用块,也不能保证找到此块需要多长时间。完全有可能需要搜索整个内存池来查找这个 1,000 字节块。

由于内存字节池的不确定性行为,通常应避免在需要确定性实时行为的区域中使用内存字节服务。许多应用程序在初始化或运行时配置期间预先分配其所需的内存。

2.1.18  覆盖内存块

务必确保已分配内存的用户不会在其边界之外写入。如果发生这种情况,则会损坏其相邻的内存区域(通常是后续区域)。结果不可预测,且对于程序执行来说通常是灾难性的。

2.2  Azure ThreadX 动态内存管理的相关函数

tx_block_allocate分配固定大小的内存块

UINT tx_block_allocate(
TX_BLOCK_POOL *pool_ptr, 
VOID **block_ptr, 
ULONG wait_option);

说明

此服务从指定的内存池中分配固定大小的内存块。内存块的实际大小是在创建内存池的过程中确定的参数。

  • pool_ptr:指向之前创建的内存块池的指针。

  • block_ptr:指向目标块指针的指针。成功分配时,已分配内存块的地址就位于此参数所指向的位置。

  • wait_option:定义此服务在没有可用的内存块时的行为方式。等待选项的定义如下:

● TX_NO_WAIT (0x00000000)

   如果选择 TX_NO_WAIT,则无论此服务是否成功,都会导致立即从此服务返回 。如果从非线程(例如初始化、计时器或 ISR)调用服务,则这是唯一有效的选项。

● TX_WAIT_FOREVER (0xFFFFFFF)

   选择 TX_WAIT_FOREVER 会导致发出调用的线程无限期挂起,直到内存块可用为止 。

● 超时值(0x00000001 至 0xFFFFFFFE)

          如果选择一个数值(1 到 0xFFFFFFFE),则会指定在等待内存块时发出调用的线程保持挂起的最大计时器时钟周期数。

返回值

  • TX_SUCCESS (0x00) 成功分配内存块。

  • TX_DELETED (0x01) 线程挂起时删除了内存块池。

  • TX_NO_MEMORY (0x10) 服务无法在指定的等待时间内分配内存块。

  • TX_WAIT_ABORTED (0x1A) 挂起状态由其他线程、计时器或 ISR 中止。

  • TX_POOL_ERROR:(0x02) 内存块池指针无效。

  • TX_WAIT_ERROR:(0x04) 从非线程调用时指定了除 TX_NO_WAIT 以外的等待选项。

  • TX_PTR_ERROR:(0x03) 指向目标指针的指针无效。

示例

TX_BLOCK_POOL my_pool;
unsigned char *memory_ptr;

UINT status;

/* Allocate a memory block from my_pool. Assume that the pool has
already been created with a call to tx_block_pool_create. */

status = tx_block_allocate(&my_pool, (VOID **) &memory_ptr,
  TX_NO_WAIT);

/* If status equals TX_SUCCESS, memory_ptr contains the address of
the allocated block of memory. */

另请参阅

  • tx_block_pool_create

  • tx_block_pool_delete

  • tx_block_pool_info_get

  • tx_block_pool_performance_info_get

  • tx_block_pool_performance_system_info_get

  • tx_block_pool_prioritize

  • tx_block_release


  • tx_byte_allocate

  • tx_byte_pool_create

  • tx_byte_pool_delete

  • tx_byte_pool_info_get

  • tx_byte_pool_performance_info_get

  • tx_byte_pool_performance_system_info_get

  • tx_byte_pool_prioritize

  • tx_byte_release

具体函数的中文说明可以参考:

https://docs.microsoft.com/zh-cn/azure/rtos/threadx/chapter4

具体函数的英文说明可以参考:

https://docs.microsoft.com/en-us/azure/rtos/threadx/threadx-smp/chapter4

2.3  动态内存管理的应用演示

2.3.1  工程目录的建立

打开目标工程文件夹“MM32F3270Project”:

6.png

移除原有样例.c 文件sample_threadx.c:

7.png

参考sample_threadx.c建立main_malloc_demo.c文件,并添加hardware目录中的led.c、key.c到工程项目中。

8.png

注意:

需要在delay.c中配置USE_SYSTICK_DELAY 为 0。

#define USE_SYSTICK_DELAY 0

3、ThreadX 的内存管理应用

创建如下几个任务:

  • LED1闪烁指示当前系统运行。

  • 按键(K1、K2、K3、K4)按下,对应的事件标志置位:

  • 获取事件标志,执行处理程序:

● K1按下,从指定的内存块池中申请固定大小的内存块

● K2按下,将以前分配的内存块释放回其关联的内存池

● K3按下,从指定的内存字节池中分配指定的字节数

● K4按下,将以前分配的内存字节数释放回其关联的内存池

● 打印内存池信息

3.1  代码实现

下载调试默认会运行到main()函数,如下为全部实现的代码。

Demo演示代码

/* This is a small demo of the high-performance ThreadX kernel.  It includes examples of six
  threads of different priorities, using a message queue, semaphore, and an event flags group.  */

#include "tx_api.h"
#include "delay.h"
#include "led.h"
#include "key.h"
#include "uart.h"

#define DEMO_STACK_SIZE         1024

#define THREAD0_PRIORITY 1
#define THREAD0_PREEMPTION_THRESHOLD 1
#define THREAD1_PRIORITY 2
#define THREAD1_PREEMPTION_THRESHOLD 2

#define THREAD5_PRIORITY 4
#define THREAD5_PREEMPTION_THRESHOLD 4
//#define THREAD5_PREEMPTION_THRESHOLD_NEW 0


#define BIT_0  ((ULONG)0x0000001) 
#define BIT_1  ((ULONG)0x0000002) 
#define BIT_2  ((ULONG)0x0000004) 
#define BIT_3  ((ULONG)0x0000008) 
#define BIT_ALL (BIT_0|BIT_1|BIT_2|BIT_3)

/* Define the ThreadX object control blocks...  */
TX_THREAD               thread_0;
TX_THREAD               thread_1;

TX_THREAD               thread_5;

TX_EVENT_FLAGS_GROUP  EventGroup;

TX_BLOCK_POOL MyBlock; 
TX_BYTE_POOL MyByte;   

/* Define the counters used in the demo application...  */
ULONG                   thread_0_counter;
ULONG                   thread_1_counter;

ULONG                   thread_5_counter;

/* Define the thread stacks.  */
UCHAR                   thread_0_stack[DEMO_STACK_SIZE];
UCHAR                   thread_1_stack[DEMO_STACK_SIZE];

UCHAR                   thread_5_stack[DEMO_STACK_SIZE];

/* Define thread prototypes.  */

void    thread_0_entry(ULONG thread_input);
void    thread_1_entry(ULONG thread_input);

void    thread_5_entry(ULONG thread_input);


volatile unsigned int bootloop;


uint32_t MyBlockBuf[1024];
uint32_t MyByteBuf[1024];



void System_Init(void);
void AppThreadCreate(void);
void AppModuleCreate(void);


/* Define main entry point.  */

int main()
{
    System_Init();

    /* Enter the ThreadX kernel.  */
    tx_kernel_enter();
}


/* Define what the initial system looks like.  */

void tx_application_define(void* first_unused_memory)
{
    AppThreadCreate();
    AppModuleCreate();

}

void System_Init(void)
{
    DELAY_Init();//can not use systick
    LED_Init();
    KEY_Init();
    CONSOLE_Init(115200);
    printf("!!! Start !!!\r\n");  

}

void AppThreadCreate(void)
{
    /* Create thread 0.  */
    tx_thread_create(
            &thread_0, 
            "thread 0", 
            thread_0_entry, 
            0,
            thread_0_stack, 
            DEMO_STACK_SIZE,
            THREAD0_PRIORITY, 
            THREAD0_PREEMPTION_THRESHOLD, 
            TX_NO_TIME_SLICE, 
            TX_AUTO_START);

    /* Create thread 1.  */
    tx_thread_create(
            &thread_1, 
            "thread 1", 
            thread_1_entry, 
            0,
            thread_1_stack, 
            DEMO_STACK_SIZE,
            THREAD1_PRIORITY, 
            THREAD1_PREEMPTION_THRESHOLD, 
            TX_NO_TIME_SLICE, 
            TX_AUTO_START);


    /* Create thread 5. */
    tx_thread_create(
            &thread_5, 
            "thread 5", 
            thread_5_entry, 
            5,
            thread_5_stack, 
            DEMO_STACK_SIZE,
            THREAD5_PRIORITY, 
            THREAD5_PREEMPTION_THRESHOLD, 
            TX_NO_TIME_SLICE, 
            TX_AUTO_START);

}

void AppModuleCreate(void)
{        
        /* Creates a memory block for applying for a fixed-size memory unit */
        tx_block_pool_create(&MyBlock,
                             "MyBlock",
                             4,                  /* The size of the memory unit */
                             (VOID *)MyBlockBuf,  /* Memory block address, ensure 4-byte alignment */
                                sizeof(MyBlockBuf));/* Memory block size, in bytes */    
        /* Create a memory pool */  
        tx_byte_pool_create(&MyByte, 
                            "MyByte",
                            (VOID *)MyByteBuf,    /* Memory pool address, ensure 4-byte alignment */
                             sizeof(MyByteBuf));    /* Memory pool size */
        /* Create an event flag group */
        tx_event_flags_create(&EventGroup, "EventGroupName");
}


/* Define the test threads.  */
void thread_0_entry(ULONG thread_input)
{
    /* This thread simply controls LED flashing to indicate that the system is running  */
    while(1)
    {
        /* Increment the thread counter.  */
        thread_0_counter++;

        LED1_TOGGLE();

        /* Sleep for 300 ticks.  */
        tx_thread_sleep(300);

    }
}

void thread_1_entry(ULONG thread_input)
{
    UCHAR *BlockPtr;
    UCHAR *BytePtr;
    ULONG available;

    ULONG actual_events;
    UINT status;

    while(1)
    {
    /* Increment the thread counter.  */
        thread_1_counter++;    
        status = tx_event_flags_get(&EventGroup,
                                    BIT_ALL, 
                                    TX_OR_CLEAR,
                                    &actual_events, 
                                    TX_WAIT_FOREVER);
        if(status == TX_SUCCESS)
        {
            switch(actual_events)
            {
                case BIT_0:
                    /* Apply for memory blocks of 4 bytes each time */
                    status = tx_block_allocate(&MyBlock, 
                                               (VOID **)&BlockPtr, 
                                               TX_NO_WAIT);
                    if(status == TX_SUCCESS)
                    {
                        printf("Succeeded in applying for a memory block. \r\n");
                        tx_block_pool_info_get(&MyBlock, 
                                               TX_NULL,
                                               &available, 
                                               TX_NULL,
                                               TX_NULL,
                                               TX_NULL,
                                               TX_NULL);
                        printf("Number of blocks available in MyBlock = %d\r\n", (int)available);
                    }                                    
                    break;
                case BIT_1:
                    status = tx_block_release(BlockPtr);
                    if(status == TX_SUCCESS)
                    {
                        printf("The memory block was released successfully.\r\n");
                        tx_block_pool_info_get(&MyBlock, 
                                               TX_NULL,
                                               &available, 
                                               TX_NULL,
                                               TX_NULL,
                                               TX_NULL,
                                               TX_NULL);
                        printf("Number of blocks available in MyBlock = %d\r\n", (int)available);
                    }                                            
                    break;
                case BIT_2:
                    /* Apply for a memory byte pool, specifying 100 bytes */
                    status = tx_byte_allocate(&MyByte,
                                              (VOID **)&BytePtr,
                                              100, 
                                              TX_NO_WAIT);
                    if(status == TX_SUCCESS)
                    {
                        printf("Succeeded in applying for the memory byte pool.\r\n");
                        tx_byte_pool_info_get(&MyByte, 
                                              TX_NULL,
                                              &available, 
                                              TX_NULL,
                                              TX_NULL, 
                                              TX_NULL,
                                              TX_NULL);                    
                        printf("The available MyByte size = %d bytes. \r\n", (int)available);
                    }                                                            
                    break;                                    
                case BIT_3:
                    status = tx_byte_release(BytePtr);
                    if(status == TX_SUCCESS)
                    {
                        printf("The memory byte pool was released successfully. \r\n");                                                
                        tx_byte_pool_info_get(&MyByte, 
                                              TX_NULL,
                                              &available, 
                                              TX_NULL,
                                              TX_NULL, 
                                              TX_NULL,
                                              TX_NULL);
                        printf("The available MyByte size = %d bytes. \r\n", (int)available);
                    }                                        
                    break;
                default:
                    break;
            }                            

        }    
    }
}


void thread_5_entry(ULONG thread_input)
{  
    UCHAR t = 0;
    /* This thread simply scan button is pressed to send the semaphore.  */
    while(1)
    {    
        /* Increment the thread counter.  */
        thread_5_counter++;         
        t = KEY_Scan(0);
        if(KEY1_PRES == t) 
        {
            //LED1_TOGGLE();
            tx_event_flags_set(&EventGroup, BIT_0, TX_OR);
        }
        else if(KEY2_PRES == t) 
        {
            //LED2_TOGGLE();
            tx_event_flags_set(&EventGroup, BIT_1, TX_OR);  
        }            
        else if(KEY3_PRES == t) 
        {
            //LED3_TOGGLE();
            tx_event_flags_set(&EventGroup, BIT_2, TX_OR);
        }
        else if(KEY4_PRES == t) 
                {
            //LED4_TOGGLE();
            tx_event_flags_set(&EventGroup, BIT_3, TX_OR);
        }
        else 
        {
            tx_thread_sleep(10);
        }
    }
}

3.2  下载与调试

运行程序,板载LED1闪烁。观察串口调试助手,依次按下K1、K2、K3、K4键,串口打印信息:

9.png

创建事件标志组初始化为零,用于任务同步。任务5执行按键扫描,当按键按下时通过tx_event_flags_set设置事件标志。任务1通过tx_event_flags_get用于检索事件标志,执行对应的处理程序,其中K1进行内存块申请,K2进行内存块释放,K3用于内存字节申请,K4用于内存字节释放,观测串口打印信息,Demo演示成功。

按下K3从MyByte内存池中申请分配了100个字节,还有3980字节可用,而按下K4释放后变成了4088字节可用,前后差值为108个字节,为什么多了8个字节?感兴趣的同学可以思考一下,找找答案!

4、小结

Azure RTOS ThreadX提供内存池能够以快速且确定的方式分配内存,结合MM32F3270的强大性能,可以实现Azure RTOS广泛的应用场景。

来源:灵动MM32MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 65

简 介

Azure RTOS ThreadX 是 Microsoft 提供的高级工业级实时操作系统 (RTOS)。它是专门为深度嵌入式实时 IoT 应用程序设计的。Azure RTOS ThreadX 提供高级计划、通信、同步、计时器、内存管理和中断管理功能。此外,Azure RTOS ThreadX 具有许多高级功能,包括 picokernel™ 体系结构、preemption-threshold™ 计划、event-chaining™、执行分析、性能指标和系统事件跟踪。Azure RTOS ThreadX 非常易于使用,适用于要求极其苛刻的嵌入式应用程序。Azure RTOS ThreadX 在各种产品(包括消费者设备、医疗电子设备和工业控制设备)上的部署次数已达数十亿次。

具体的介绍和用户指南可以参考:

https://docs.microsoft.com/zh-cn/azure/rtos/threadx/

在前文描述移植基本内核的基础上,本文描述了如何基于MM32F3270系列MCU结合Azure RTOS ThreadX实现消息队列块的使用,引导用户理解Azure RTOS ThreadX消息队列的功能。

上期样例工程可通过下方链接进行下载:

https://mindmotion.com.cn/whir_system/module/extension/Download.aspx?path=/uploadfiles/&name=download%2FMM32F3270azure02.zip

包括底层启动文件的移植, 系统 Systick 的配置技巧,引导用户理解 Azure RTOS ThreadX 基本应用。

表 1 适用系列型号

“表

1、移植应用的准备

1.1 硬件开发板的准备

该移植过程中应用的开发板为MM32的EV Board(MM32F3273G9P)

“使用MM32F3270基于Azure

EV Board (MM32F3273G9P) 的简要参数:

● Arm Cortex-M3 内核

● 板载 MM32F3273G9P(LQFP144)

● USB Host / Device、SPI、I2C

● 4 x Key、4 x LED

● I2S Speaker

● TF-Card

● Ethernet PHY

1.2 软件的准备

库函数和例程(Lib Samples)

该移植过程中应用的 Firmware 分别为 MM32F3270 库函数和例程,下载地址:

https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f_mainstream/mm32f3270/

“使用MM32F3270基于Azure

Azure RTOS ThreadX(源码)

ThreadX 的源代码已经开放,我们可以从 ThreadX 公共源代码存储库获取 Azure RTOS ThreadX,网址为:https://github.com/azure-rtos/threadx/

具体的商用使用条件参考Azure的许可证说明:

https://www.microsoft.com/en-us/legal/intellectualproperty/tech-licensing/programs?msclkid=f7ab4ff3afa011ec90a79366a52034fa&activetab=pivot1:primaryr11

Microsoft publishes the Azure RTOS source code to GitHub. No license is required to install and use the software for internal development, testing, and evaluation purposes. A license is required to distribute or sell components and devices unless using Azure RTOS licensed hardware.

Azure RTOS 何时需要许可证?

Microsoft 将 Azure RTOS 源代码发布到 GitHub。安装和使用该软件进行内部开发、测试和评估无需许可证。分发或销售组件和设备需要许可证,除非使用 Azure RTOS 许可的硬件。

ThreadX 安装

可以通过将 GitHub 存储库克隆到本地计算机来安装 ThreadX。下面是用于在 PC 上创建 ThreadX 存储库的克隆的典型语法。

shell复制

git clone https://github.com/azure-rtos/threadx

或者,也可以使用 GitHub 主页上的“下载”按钮来下载存储库的副本。

下载后的仓库代码目录列表如下:

“使用MM32F3270基于Azure

Azure RTOS ThreadX(源码)支持的开发环境

ThreadX 内核提供好了各种主流硬件平台和软件平台的移植文件,以Cortex_M3为例,可以支持以下六种开发环境:

“使用MM32F3270基于Azure

本次移植过程使用Azure RTOS原有的sample_threadx.c Samples为例子,稍作修改,演示了消息队列的作用。

2、Threadx 的消息队列的应用

该章节介绍了消息队列应用的实现的过程和注意事项,该演示程序可在 MM32F3273G9P 的 EV Board 上运行。

此示例在文件 main_queue_task.c 中定义,旨在说明如何在嵌入式多线程环境中使用message quenue,实现任务之间的数据信号传递与同步。

2.1 Azure Threadx queue 应用简介

消息队列是RTOS中常用的一种数据通信方式,常用于任务与任务之间或是中断与任务之间的数据传递。在裸机系统中我们通常会使用全局变量的方式进行数据传递,比如在事件发生后在中断中改变数据和设置标志,然后在主循环中轮询不同的标志是否生效来对全局数据执行不同的操作,执行完毕后清除相关标志。但是这种方式需要不断地轮询标志状态,使得CPU的利用率并不高。而使用RTOS的消息队列则具有任务阻塞机制,当没有需要处理的消息时任务挂起等待消息,此时其他任务占用CPU执行其他操作,当有消息放入队列时任务恢复运行进行消息接收和处理。这种消息处理机制相比裸机而言大大地提高了CPU利用率。

ThreadX的消息队列支持“消息置顶通知”功能,也就是可以将消息放在队列的最前面,使得任务可以及时处理某些紧急消息。

ThreadX的消息队列可以传递任意长度的数据,因为它是采用传递数据指针的方式(一些其他RTOS支持传递整体数据内容。Threadx的queue使用指针传递方式优点是执行效率高,缺点是存数据的内存区域如果数据还未及时处理就被覆写了那么就会引发问题;而部分其他RTOS使用整体数据传递方式,其优点是安全不需担心数据覆写致错,缺点是数据量大的话传递数据过程执行时间长导致效率低)。

Threadx支持消息队列进行线程间通信或者从中断程序向线程发送消息队列。

消息队列中消息通常按照先进先出规则传递,同时提供了把消息直接存储到队列头部的API。每个线程可以创建多个消息队列,并且可以使用多个消息队列和多个线程通信。

消息队列不支持拥有者属性,也就是任何线程可以向消息队列发送或接收消息。应用开发者保证消息队列使用的合理性。

“使用MM32F3270基于Azure

消息传递规则

a)任何线程可以向一个消息队列发送或接收消息,消息队列不支持拥有者属性。

b)消息队列支持先进先出规则,函数_tx_queue_send发送消息都队列尾部。

c)消息队列也支持发送消息到消息头部功能,函数_tx_queue_front_send发送消息到消息头部。

d)如果消息队列有挂起的接收线程,发送消息时,可以直接把消息放到接收线程的缓冲中,这可以降低消息传递延时。

e)TX_THREAD线程控制块中tx_additional_suspend_info域用于存储接收线程缓冲区地址。

f)如果消息队列为空,接收线程调用_tx_queue_receive(wait_option不为0)读取消息时,线程会被挂起到队列tx_queue_suspension_list。其它线程发送消息后会恢复挂起的线程。

g)如果消息队列已满,发送线程调用_tx_queue_send(wait_option不为0)发送消息时,线程会被挂起到队列tx_queue_suspension_list。其它线程接收消息时,会恢复挂起的线程。

如下是Threadx Message Queue的属性:

“使用MM32F3270基于Azure

消息队列控制块(QCB)是用来保持运行时消息队列状态的数据结构。

消息队列控制块Queue的结构体

/* Define the queue structure utilized by the application.  */

typedef struct TX_QUEUE_STRUCT
{

    /* Define the queue ID used for error checking.  */
    ULONG               tx_queue_id;

    /* Define the queue's name.  */
    CHAR                *tx_queue_name;

    /* Define the message size that was specified in queue creation.  */
    UINT                tx_queue_message_size;

    /* Define the total number of messages in the queue.  */
    UINT                tx_queue_capacity;

    /* Define the current number of messages enqueued and the available
       queue storage space.  */
    UINT                tx_queue_enqueued;
    UINT                tx_queue_available_storage;

    /* Define pointers that represent the start and end for the queue's
       message area.  */
    ULONG               *tx_queue_start;
    ULONG               *tx_queue_end;

    /* Define the queue read and write pointers.  Send requests use the write
       pointer while receive requests use the read pointer.  */
    ULONG               *tx_queue_read;
    ULONG               *tx_queue_write;

    /* Define the queue suspension list head along with a count of
       how many threads are suspended.  */
    struct TX_THREAD_STRUCT
                        *tx_queue_suspension_list;
    UINT                tx_queue_suspended_count;

    /* Define the created list next and previous pointers.  */
    struct TX_QUEUE_STRUCT
                        *tx_queue_created_next,
                        *tx_queue_created_previous;

#ifdef TX_QUEUE_ENABLE_PERFORMANCE_INFO

    /* Define the number of messages sent to this queue.  */
    ULONG               tx_queue_performance_messages_sent_count;

    /* Define the number of messages received from this queue.  */
    ULONG               tx_queue_performance_messages_received_count;

    /* Define the number of empty suspensions on this queue.  */
    ULONG               tx_queue_performance_empty_suspension_count;

    /* Define the number of full suspensions on this queue.  */
    ULONG               tx_queue_performance_full_suspension_count;

    /* Define the number of full non-suspensions on this queue. These
       messages are rejected with an appropriate error code.  */
    ULONG               tx_queue_performance_full_error_count;

    /* Define the number of queue timeouts.  */
    ULONG               tx_queue_performance_timeout_count;
#endif

#ifndef TX_DISABLE_NOTIFY_CALLBACKS

    /* Define the application callback routine used to notify the application when
       the a message is sent to the queue.  */
    VOID                (*tx_queue_send_notify)(struct TX_QUEUE_STRUCT *queue_ptr);
#endif

    /* Define the port extension in the queue control block. This
       is typically defined to whitespace in tx_port.h.  */
    TX_QUEUE_EXTENSION

} TX_QUEUE;

queue包含主要成员的含义:

“使用MM32F3270基于Azure

消息队列list

系统中所有信号量控制块挂载一个双向链表_tx_queue_created_ptr中,tx_queue_created_next指向下一个消息队列指针,tx_queue_created_previous指向前一个消息队列指针。

“使用MM32F3270基于Azure

消息队列主要的服务函数

“使用MM32F3270基于Azure

2.2 消息队列功能的演示

2.2.1 工程目录的建立

打开目标工程文件夹“MM32F3270Project”:

“使用MM32F3270基于Azure

移除原有样例.c 文件sample_threadx.c:

“使用MM32F3270基于Azure

参考sample_threadx.c建立main_multi_task.c文件,并添加hardware目录中的led.c到工程项目中。

“使用MM32F3270基于Azure

并把HARDWARE\LED目录添加到包含文件目录中:

“使用MM32F3270基于Azure

2.2.2 创建消息队列_tx_queue_create

声明需要使用的消息队列变量,发送消息变量,接收消息变量(计算声明消息大小)。

还要声明消息队列缓冲块。

调用tx_queue_create, 建立消息队列。

创建一个待使用的消息队列

#define DEMO_QUEUE_SIZE         10
ULONG                   thread_1_messages_sent;
ULONG                   thread_2_messages_received;
TX_QUEUE                queue_0;
/* Define the queue area.  */
UCHAR                   queue_0_area[DEMO_QUEUE_SIZE*sizeof(ULONG)];

    /* Create the message queue shared by threads 1 and 2.  */
    tx_queue_create(&queue_0, "queue 0", TX_1_ULONG, \
        queue_0_area, DEMO_QUEUE_SIZE*sizeof(ULONG));

A. 第1个参数是消息队列控制块,引用声明的队列“queue_0”指向的地址。

B. 第2个参数是消息队列名字,字符串值为"queue 0"。

C. 第3个参数是消息队列每个消息的大小,消息大小范围1-16,每个消息4字节。

D. 第4个参数是消息队列缓冲地址,必须保证此地址4字节对齐,即此地址对4求余数为0。

E. 第5个参数是消息缓冲大小,单位字节。

F. 返回值

TX_SUCCESS (0x00) 创建成功。

TX_QUEUE_ERROR (0x09) 消息队列控制块无效。

TX_PTR_ERROR (0x03)无效的消息队列其实地址。

TX_SIZE_ERROR (0x05) 消息队列大小无效。

TX_CALLER_ERROR (0x13) 无效的调用

2.2.3 创建一个发送消息队列的任务

在此建立一个定时发送一个消息队列的简单任务,发送完成,发送计数值加一:

创建一个发送消息队列任务

void    thread_1_entry(ULONG thread_input)
{

UINT    status;


    /* This thread simply sends messages to a queue shared by thread 2.  */
    while(1)
    {

        /* Increment the thread counter.  */
        thread_1_counter++;

        /* Send message to queue 0.  */
        status =  tx_queue_send(&queue_0, &thread_1_messages_sent, TX_WAIT_FOREVER);

        /* Check completion status.  */
        if (status != TX_SUCCESS)
            break;
        tx_thread_sleep(10);
        /* Increment the message sent.  */
        thread_1_messages_sent++;
    }
}

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

● A. 第1个参数是消息队列控制块。

● B. 第2个参数是要发送的数据地址。

● C. 第3个参数是等待选项:

参数:

TX_NO_WAIT (0x00000000),表示不管消息队列是否满,立即返回。

TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到消息队列有消息。

● D. 返回值

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 以外的形参。

2.2.4 创建一个接收消息队列的任务

在此建立一个接收一个消息队列的简单任务,接收消息成功,接收计数值加一:

创建一个接收消息队列任务

 void    thread_2_entry(ULONG thread_input)
{

ULONG   received_message;
UINT    status;

    /* This thread retrieves messages placed on the queue by thread 1.  */
    while(1)
    {

        /* Increment the thread counter.  */
        thread_2_counter++;

        /* Retrieve a message from the queue.  */
        status = tx_queue_receive(&queue_0, &received_message, TX_WAIT_FOREVER);

        /* Check completion status and make sure the message is what we 
           expected.  */
        if ((status != TX_SUCCESS) || (received_message != thread_2_messages_received))
            break;
        tx_thread_sleep(10);
        /* Otherwise, all is okay.  Increment the received message count.  */
        thread_2_messages_received++;
    }
}

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

● A. 第1个参数是消息队列控制块。

● B. 第2个参数是从消息队列取得的数据存储地址。

● C. 第3个参数是等待选项:

参数含义:

TX_NO_WAIT (0x00000000),表示不管消息队列是否空,立即返回。

TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到消息队列有数据。

● D. 返回值

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 以外的形参。

3、Threadx 的运行与调试

3.1 下载与调试

下载调试默认会运行到main()函数。

“使用MM32F3270基于Azure

该demo示例,建立了2个线程,程序执行一个发送队列,另外一个线程接收队列,实现执行Task Counter与发送&接收的Message Counter的累加。

全速运行后,可以看到相关的数值在变化:

“使用MM32F3270基于Azure

这个演示程序中,使用了tx_thread_sleep()函数做了延时,刚好使得发送消息队列与接收队列一一配对,没有出现发送消息满的情况与接收空的情况。有兴趣的话,可以把延时函数注释掉,看看会出现什么情况,并做进一步的调试。

4、小结

Azure RTOS ThreadX 的使用消息队列是非常高效率与方便,结合MM32F3270的强大性能,可以体验Azure RTOS的各种特色功能。

来源:灵动MM32MCU
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 164
订阅 RSS - Azure RTOS