MM32F3270

简 介

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。

2.png

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_timer_demo.c 中实现,旨在说明如何在嵌入式多线程环境中使用定时器组功能。

2.1  定时器组

2.1.1  应用程序计时器

快速响应异步外部事件是嵌入式实时应用程序中最重要的功能。但是,其中的许多应用程序还必须按预定的时间间隔执行某些活动。

借助 ThreadX 应用程序计时器,应用程序能够按特定的时间间隔执行应用程序 C 函数。应用程序计时器也可能只过期一次。这种类型的计时器称为“单次计时器”,而重复间隔计时器称为“定期计时器”。

每个应用程序计时器都是一个公用资源。ThreadX 对如何使用应用程序计时器没有任何限制。

2.1.2  计时器间隔

在 ThreadX 中,时间间隔通过定期计时器中断来测量。每个计时器中断称为计时器时钟周期。计时器时钟周期之间的实际时间由应用程序指定,但 10 毫秒是大多数实现的标准时间。定期计时器设置通常位于 tx_initialize_low_level 程序集文件中。

值得一提的是,基础硬件必须能够生成定期中断,应用程序计时器才会正常运行。在某些情况下,处理器具有内置的定期中断功能。如果处理器没有此功能,用户的主板必须包含可生成定期中断的外围设备。

即使没有定期中断源,ThreadX 仍可正常工作。但随后会禁用所有与计时器相关的处理。这包括时间切片、挂起超时和计时器服务。

2.1.3  计时器准确性

计时器过期时间根据时钟周期指定。达到每个计时器时钟周期时,指定到期值将减一。由于应用程序计时器可在计时器中断(或计时器时钟周期)之前启用,因此,实际过期时间可能会提前一个时钟周期。

如果计时器时钟周期速率为 10 毫秒,应用程序计时器可能会提前 10 毫秒过期。与 1 秒计时器相比,这对 10 毫秒计时器更重要。当然,增加计时器中断频率会减少此误差范围。

2.1.4  计时器执行

应用程序计时器按照其激活的顺序执行。例如,如果创建了三个具有相同过期值的计时器并已激活,这些计时器对应的过期函数将保证按它们激活的顺序执行。

2.1.5  创建应用程序计时器

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

2.1.6  运行时应用程序计时器性能信息

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

整个系统的总数:

  • 激活数

  • 停用数

  • 重新激活(定期计时器)

  • expirations

  • 过期调整数

每个应用程序计时器的总数:

  • 激活数

  • 停用数

  • 重新激活(定期计时器)

  • expirations

  • 过期调整数

此信息在运行时通过 tx_timer_performance_info_get 和 tx_timer_performance_system_info_get 服务提供。应用程序计时器性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。

2.1.7  应用程序计时器控制块 TX_TIMER

每个应用程序计时器的特征都可在其控制块中找到。该控制块包含诸如 32 位过期标识值等有用信息。此结构在 tx_api.h 文件中定义。

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

2.1.8  计时器过多

默认情况下,应用程序计时器在优先级为 0 时运行的隐藏系统线程中执行,该线程的优先级通常比任何应用程序线程都高。因此,在应用程序计时器内进行处理应保持最小值。

如果可能,还应尽可能避免使用在每个时钟周期过期的计时器。这种情况可能导致应用程序的开销过大。

如前所述,应用程序计时器在隐藏的系统线程中执行。因此,请不要在应用程序计时器的过期函数内执行任何 ThreadX 服务调用时选择挂起。

2.1.9  相对时间

除了前面所述的应用程序计时器,ThreadX 还提供单个连续递增的 32 位时钟周期计数器。每次发生计时器中断时,时钟周期计数器或时间就会加一。

应用程序可以通过分别调用 tx_time_get 和 tx_time_set 来读取或设置此 32 位计数器。此时钟周期计数器的使用完全由应用程序确定。ThreadX 不在内部使用此计时器。

2.2  Azure ThreadX 定时器组的相关函数

tx_timer_create 创建应用程序计时器

UINT tx_timer_create(
    TX_TIMER *timer_ptr, 
    CHAR *name_ptr,
    VOID (*expiration_function)(ULONG),
    ULONG expiration_input, 
    ULONG initial_ticks,
    ULONG reschedule_ticks, 
    UINT auto_activate);

说明

此服务创建具有指定过期函数和定期的应用程序计时器。

参数

  • timer_ptr:

指向计时器控制块的指针。

  • name_ptr:

指向计时器名称的指针。

  • expiration_function:

在计时器过期时要调用的应用程序函数。

  • expiration_input:

在计时器过期时要传递到过期函数的输入。

  • initial_ticks:

指定计时器过期的初始时钟周期数。合法值的范围为 1 至 0xFFFFFFFF。

  • reschedule_ticks:

指定第一个计时器过期后所有计时器过期的时钟周期数。如果此参数为 0,则计时器是一次性的。否则,对于周期性计时器,合法值的范围为 1 至 0xFFFFFFFF。

备注

一次性计时器过期后,必须通过 tx_timer_change 将其重置,然后才能再次激活。

  • auto_activate:

确定创建期间是否自动激活计时器。如果此值为 TX_AUTO_ACTIVATE (0x01),则激活计时器。否则,如果选择了值 TX_NO_ACTIVATE (0x00),则所创建的计时器处于非活动状态。在这种情况下,随后需要调用 tx_timer_activate 服务来实际启动计时器。

返回值

  • TX_SUCCESS:

(0X00) 成功创建应用程序计时器。

  • TX_TIMER_ERROR:

(0X15) 应用程序计时器指针无效。指针为 NULL 或已创建计时器。

  • TX_TICK_ERROR:

(0x16) 为初始时钟周期提供的值无效(零)。

  • TX_ACTIVATE_ERROR:

(0x17) 选择的激活无效。

  • NX_CALLER_ERROR:

(0x13) 此服务的调用方无效。

示例

TX_TIMER my_timer;
UINT status;

/* Create an application timer that executes
"my_timer_function" after 100 ticks initially and then
after every 25 ticks. This timer is specified to start
immediately! */
status = tx_timer_create(&my_timer,"my_timer_name",
    my_timer_function, 0x1234, 100, 25,
    TX_AUTO_ACTIVATE);

/* If status equals TX_SUCCESS, my_timer_function will
be called 100 timer ticks later and then called every
25 timer ticks. Note that the value 0x1234 is passed to
my_timer_function every time it is called. */

另请参阅

  • tx_timer_activate

  • tx_timer_change

  • tx_timer_deactivate

  • tx_timer_delete

  • tx_timer_info_get

  • tx_timer_performance_info_get

  • tx_timer_performance_system_info_get

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

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_timer_demo.c文件,并添加hardware目录中的led.c、key.c到工程项目中。

3、ThreadX的定时器组应用

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 the ThreadX object control blocks...  */
TX_THREAD               thread_0;

TX_TIMER MyTimer;

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

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

/* Define thread prototypes.  */

void    thread_0_entry(ULONG thread_input);


volatile unsigned int bootloop;


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);

}

void AppModuleCreate(void)
{        
    /*  Create a timer group. */
    tx_timer_create(&MyTimer,
                "My Timer",
                TimerCallback, 
                0,       /* The parameters passed */
                100,     /* Set the initial delay for timer time overflow */
                1000,    /* Set the timer run period after the initial delay */
                TX_AUTO_ACTIVATE);  /* Activate the timer */
}


/*    The callback function for the timer group. */
void TimerCallback(ULONG thread_input)
{

    LED2_TOGGLE();

}

/* 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);

    }
}

3.2  下载与调试

运行程序,板载LED1闪烁,表示当前系统正在运行。

观察LED2间隔1s闪烁。

程序中创建定时器组,设置溢出周期为1000ms,在定时器回调函数中配置LED2引脚翻转,激活定时器。当计时周期到时,LED2引脚翻转,运行现象是LED2间隔1s闪烁,Demo演示成功。

4、小结

Azure RTOS ThreadX提供定时器组能够使应用程序按照特定的时间间隔执行,结合MM32F3270的强大性能,可以实现Azure RTOS广泛的应用场景。

来源:灵动MM32MCU

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

围观 83

简 介 

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)。

围观 44

简 介 

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应用Counting Semaphores的使用,引导用户理解Azure RTOS ThreadX信号量功能。

表 1 适用系列型号

1.png

1、移植应用的准备

1.1  硬件开发板的准备

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

2.png

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_semaphore_demo.c 中实现,旨在说明如何在嵌入式多线程环境中使用信号量,实现任务之间的同步和资源共享机制。

2.1  信号量

2.1.1  统计信号量

ThreadX 提供 32 位计数信号灯,其值范围在 0 到 4,294,967,295 之间。计数信号灯有两个操作:tx_semaphore_get 和 tx_semaphore_put 。执行获取操作会将信号灯数量减一,如果信号灯为 0,获取操作不会成功。获取操作的逆操作是放置操作,该操作会将信号灯数量加一。

每个计数信号灯都是一个公用资源,ThreadX 对如何使用计数信号灯没有任何限制。

计数信号灯通常用于互相排斥,也可将计数信号灯用作事件通知的方法。

2.1.2  互相排斥

互相排斥用于控制线程对某些应用程序区域(也称为关键部分或应用程序资源)的访问 。将信号灯用于互相排斥时,信号灯的“当前计数”表示允许访问的线程总数。在大多数情况下,用于互相排斥的计数信号灯的初始值为 1,这意味着每次只有一个线程可以访问关联的资源。只有 0 或 1 值的计数信号灯通常称为二进制信号灯。

如果使用二进制信号灯,用户必须阻止同一个线程对其已拥有的信号灯执行获取操作。第二个获取操作将失败,并且可能导致调用线程无限期挂起和资源永久不可用。

2.1.3  事件通知

还可以采用生成者-使用者的方式,将计数信号灯用作事件通知。使用者尝试获取计数信号灯,而生成者则在有可用的信息时增加信号灯。此类信号灯的初始值通常为 0,此值不会在生成者为使用者准备好信息之前增加。用于事件通知的信号灯也可能从使用 tx_semaphore_ceiling_put 服务调用中获益。此服务确保信号灯计数值永远不会超过调用中提供的值。

2.1.4  创建计数信号灯

计数信号灯由应用程序线程在初始化期间或运行时创建。信号灯的初始计数在创建过程中指定。应用程序中计数信号灯的数量没有限制。

2.1.5  线程挂起

尝试对当前计数为 0 的信号灯执行获取操作时,应用程序线程可能会挂起。

执行放置操作后,才会执行挂起线程的获取操作并恢复该线程。如果同一计数信号灯上挂起多个线程,这些线程将按照挂起的顺序 (FIFO) 恢复。

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

2.1.6  信号灯放置通知

某些应用程序可能会发现,在放置信号灯时收到通知十分有利。ThreadX 通过 tx_semaphore_put_notify 服务提供此功能。此服务将提供的应用程序通知函数注册到指定的信号灯。只要放置了信号灯,ThreadX 就会调用此应用程序通知函数。应用程序通知函数内的确切处理由应用程序决定;但这通常包括恢复相应的线程以处理新信号灯放置事件。

2.1.7  运行时信号灯性能信息

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

整个系统的总数:

  • 信号灯放置数

  • 信号灯获取数

  • 信号灯获取挂起数

  • 信号灯获取超时数

每个信号灯的总数:

  • 信号灯放置数

  • 信号灯获取数

  • 信号灯获取挂起数

  • 信号灯获取超时数

此信息在运行时通过tx_semaphore_performance_info_get和 tx_semaphore_performance_system_info_get 服务提供。信号灯性能信息在确定应用程序是否正常运行时非常有用。此信息对于优化应用程序也很有用。例如,“信号灯获取超时数”相对较高可能表明其他线程占用资源的时间太长。

2.2  Azure Threadx 信号量的相关函数

tx_semaphore_create 创建计数信号灯

UINT tx_semaphore_create(
    TX_SEMAPHORE *semaphore_ptr,
    CHAR *name_ptr,
    ULONG initial_count);

函数说明

此服务创建用于线程间同步的计数信号灯。初始信号灯计数指定为输入参数。

参数

semaphore_ptr:指向信号灯控制块的指针。

name_ptr:指向信号灯名称的指针。

initial_count:指定此信号灯的初始计数。合法值的范围为 0x00000000 至 0xFFFFFFFF。

返回值

TX_SUCCESS:(0X00) 成功创建信号灯。

TX_SEMAPHORE_ERROR:(0x0C) 信号灯指针无效。指针为 NULL 或已创建信号灯。

NX_CALLER_ERROR:(0x13) 此服务的调用方无效。

示例

TX_SEMAPHORE my_semaphore;
UINT status;

/* Create a counting semaphore whose initial value is 1.
This is typically the technique used to make a binary
semaphore. Binary semaphores are used to provide
protection over a common resource. */
status = tx_semaphore_create(&my_semaphore,
    "my_semaphore_name", 1);

/* If status equals TX_SUCCESS, my_semaphore is ready for
use. */

另请参阅

  • tx_semaphore_ceiling_put

  • tx_semaphore_delete

  • tx_semaphore_get

  • tx_semaphore_info_get

  • tx_semaphore_performance_info_get

  • tx_semaphore_performance_system_info_get

  • tx_semaphore_prioritize

  • tx_semaphore_put

  • tx_semaphore_put_notify

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

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_semaphore_demo.c文件,并添加hardware目录中的led.c、key.c到工程项目中。

8.png

注意:

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

#define USE_SYSTICK_DELAY 0

3、Threadx 的信号量应用

创建如下几个任务:

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

  • K2键按下,发送信号量同步信号。

  • 任务接收到消息后,串口打印。

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 the ThreadX object control blocks...  */
TX_THREAD               thread_0;
TX_THREAD               thread_1;

TX_THREAD               thread_5;

TX_SEMAPHORE Semaphore;

/* 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;

/* Define main entry point.  */



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

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


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

void tx_application_define(void* first_unused_memory)
{
        /* 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);

        /* Create a semaphore for signal synchronization */
        tx_semaphore_create(&Semaphore, "Semaphore", 0);

}


/* 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 200 ticks.  */
        tx_thread_sleep(200);

    }
}

void thread_1_entry(ULONG thread_input)
{
        UINT status;
        /* This thread simply successfully receives the semaphore and starts to print information  */   
    while(1)
    {
        /* Increment the thread counter.  */
        thread_1_counter++;                
        status = tx_semaphore_get(&Semaphore, TX_WAIT_FOREVER);
        if(status == TX_SUCCESS)
        {
        /* Receive the semaphore */
            printf("Synchronization semaphore received\r\n");
        }            
    }
}


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();
        }
        else if(KEY2_PRES == t) {
            LED2_TOGGLE();
            /* K2 is pressed to send a semaphore */
            tx_semaphore_put(&Semaphore);   
        }
        else if(KEY3_PRES == t) {
            LED3_TOGGLE();
        }
        else if(KEY4_PRES == t) {
            LED4_TOGGLE();
        }
        else {
            tx_thread_sleep(10);
        }
    }
}

3.2  下载与调试

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

9.png

创建信号量初始值为 0,用于信号同步。任务5执行按键扫描,当K2按下时通过tx_semaphore_put发送信号量,对计数值执行加1操作。任务1通过tx_semaphore_get用于信号量接收,对计数值执行减1操作,实际运行情况是K2键每按下一次,串口打印一条信息,Demo演示成功。

4、小结

Azure RTOS 使用信号量能够方便地实现任务之间的同步和资源共享机制,结合MM32F3270的强大性能,可以实现Azure RTOS广泛的应用场景。

来源:灵动MM32MCU

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

围观 54

简 介

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应用抢占任务preemption-threshold的使用,引导用户理解Azure RTOS ThreadX抢占任务preemption-threshold功能。

表 1 适用系列型号

“表

1、移植应用的准备

1.1 硬件开发板的准备

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

“使用MM32F3270基于Azure

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/

“使用MM32F3270基于Azure

Azure RTOS ThreadX(源码)

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

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

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

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的EVB-F3270上运行。

此示例在文件 main_preemption_demo.c中实现,旨在说明如何在嵌入式多线程环境中使用抢占任务,实现任务之间的优先级切换与抢占。

2.1 任务优先级与切换的知识

2.1.1 线程优先级

ThreadX 在创建线程时通过分配表示其“优先级”的数值来确定线程的重要性。

ThreadX 的最大优先级数可在 32 到 1024(增量为 32)之间进行配置。

因此:

TX_MAX_PRIORITIES的宏定义设置的数值必须是32的整数倍:

#define TX_MAX_PRIORITIES 32

实际的最大优先级数由 TX_MAX_PRIORITIES 常数在 ThreadX 库的编译过程中确定。设置更多优先级并不会显著增加处理开销。但是,每个包含 32 个优先级的组额外需要 128 字节的 RAM 进行管理。例如,32 个优先级需要 128 字节的 RAM,64 个优先级需要 256 字节的 RAM,而 96 个优先级需要 384 字节的 RAM。

默认情况下,ThreadX 具有 32 个优先级,范围从优先级 0 到优先级 31。数值越小,优先级越高。因此,优先级 0 表示最高优先级,而优先级 (TX_MAX_PRIORITIES-1) 表示最低优先级。

ThreadX没有空闲任务,如果大家创建空闲任务,需要将其设置为最低优先级。

通过协作调度或时间切片,多个线程可以具有相同的优先级。

此外,线程优先级还可以在运行时更改。

2.1.2 线程切换调度

ThreadX 根据线程的优先级来调度线程。优先级最高的就绪线程最先执行。如果有多个具有相同优先级的线程准备就绪,则按照先进先出 (FIFO) 的方式执行。

ThreadX操作系统支持三种调度方式:抢占式调度,时间切片调度和轮询式调度。

在一般的应用中,主要是抢占式调度和时间切片调度,轮询式调度用到的很少。

● 轮循任务调度

ThreadX 支持通过轮循调度处理具有相同优先级的多个线程。此过程通过以协作方式调用 tx_thread_relinquish 来实现。此服务为相同优先级的所有其他就绪线程提供了在 tx_thread_relinquish 调用方再次执行之前执行的机会。

● 时间切片任务调度

“时间切片”是轮循调度的另一种形式。时间片指定线程在不放弃处理器的情况下可以执行的最大计时器时钟周期数(计时器中断)。在 ThreadX 中,时间切片按每个线程提供。线程的时间片在创建时分配,可在运行时修改。当时间片过期时,具有相同优先级的所有其他就绪线程有机会在时间切片线程重新执行之前执行。

当线程挂起、放弃、执行导致抢占的 ThreadX 服务调用或自身经过时间切片后,该线程将获得一个新的线程时间片。

当时间切片的线程被抢占时,该线程将在其剩余的时间片内比具有相同优先级的其他就绪线程更早恢复执行。

● 抢占式任务调度

抢占任务调度是为了支持优先级更高的线程而暂时中断正在执行的线程的过程。每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的API函数。

此过程对正在执行的线程不可见。当更高优先级的线程完成时,控制权将转交回发生抢占的确切位置。这是实时系统中一项非常重要的功能,因为该功能有助于快速响应重要的应用程序事件。

尽管抢占是一项非常重要的功能,但也可能导致各种问题,包括资源不足、开销过大和优先级反转。为了缓解抢占的一些固有问题,ThreadX 提供了一个独特的高级功能,名为抢占阈值 (Preemption-threshold™)。

抢占阈值允许线程指定禁用抢占的优先级上限。优先级高于上限的线程仍可以执行抢占,但不允许优先级低于上限的线程执行抢占。

例如,假设优先级为 20 的线程只与一组优先级介于 15 到 20 之间的线程进行交互。在其关键部分中,优先级为 20 的线程可将其抢占式阀值设置为 15,从而防止该线程和与之交互的所有线程发生抢占。这仍允许(优先级介于 0和 14 之间)真正重要的线程在其关键部分处理中抢占此线程的资源,这会使处理的响应速度更快。

当然,仍有可能通过将其抢占式阀值设置为 0 来为线程禁用所有抢占。此外,可以在运行时更改抢占阈值。

备注:

使用抢占阈值会禁用指定线程的时间切片。

● 优先级继承

ThreadX 还支持其互斥服务内的可选优先级继承。优先级继承允许低优先级线程暂时假设正在等待归较低优先级线程所有的互斥的高优先级线程的优先级。借助此功能,应用程序可以消除中间优先级线程的抢占,从而避免出现不确定的优先级倒置。当然,也可以使用“抢占式阀值”获得类似的结果。

2.1.3 抢占式任务调度

任务优先级需要解决的问题:

● 轮循任务调度

选择线程优先级是多线程处理最重要的方面之一。有时很容易根据感知的线程重要性概念来分配优先级,而不是确定运行时到底需要什么。滥用线程优先级会导致其他线程资源枯竭、产生优先级反转、减少处理带宽,致使应用程序出现难以理解的运行时行为。

如前所述,ThreadX 提供基于优先级的抢占式调度算法。优先级较低的线程只能在没有更高优先级的线程可以执行时才会执行。如果优先级较高的线程始终准备就绪,则不会执行优先级较低的线程。这种情况称为线程资源不足。

大多数线程资源不足的问题都是在调试初期检测到的,可通过确保优先级较高的线程不会连续执行来解决。另外,还可以在应用程序中添加此逻辑,以便逐渐提高资源不足的线程的优先级,直到有机会执行这些线程。

与线程优先级相关的另一个缺陷是“优先级倒置”。当优先级较高的线程由于优先级较低的线程占用其所需资源而挂起时,将会发生优先级倒置。当然,在某些情况下,有必要让两个优先级不同的线程共享一个公用资源。如果这些线程是唯一处于活动状态的线程,优先级倒置时间就与低优先级线程占用资源的时间息息相关。这种情况既具有确定性又非常正常。不过,如果在这种优先级反转的情况,中等优先级的线程变为活动状态,优先级反转时间就不再确定,并且可能导致应用程序失败。

主要有三种不同的方法可防止 ThreadX 中出现不确定的优先级反转。

首先,在设计应用程序优先级选择和运行时行为时,可以采用能够防止出现优先级反转问题的方式。

其次,优先级较低的线程可以利用抢占阈值来阻止中等优先级线程在其与优先级较高的线程共享资源时执行抢占。

最后,使用 ThreadX 互斥对象保护系统资源的线程可以利用可选的互斥优先级继承来消除不确定的优先级反转。

● 优先级开销

要减少多线程处理开销,最容易被忽视的一种方法是减少上下文切换的次数。如前所述,当优先级较高的线程执行优先于正在执行的线程时,则会发生上下文切换。值得一提的是,在发生外部事件(例如中断)和执行线程发出服务调用时,优先级较高的线程可能变为就绪状态。

为了说明线程优先级对上下文切换开销的影响,假设有一个包含三个线程的环境,这些线程分别命名为 thread_1、thread_2 和 thread_3。进一步假定所有线程都处于等待消息的挂起状态。当 thread_1 收到消息后,立即将其转发给 thread_2。随后,thread_2 将此消息转发给 thread_3。Thread_3 只是丢弃此消息。每个线程处理其消息后,则会返回并等待另一个消息。

执行这三个线程所需的处理存在很大的差异,具体取决于其优先级。如果所有线程都具有相同优先级,则会在执行每个线程之前发生一次上下文切换。当每个线程在空消息队列中挂起时,将会发生上下文切换。

但是,如果 thread_2 的优先级高于 thread_1,thread_3 的优先级高于 thread_2,上下文切换的次数将增加一倍。这是因为当其检测到优先级更高的线程现已准备就绪时,会在 tx_queue_send 服务中执行另一次上下文切换。

ThreadX 抢占阈值机制可以避免出现这些额外的上下文切换,并且仍支持前面提到的优先级选择。这是一项重要的功能,因为该功能允许在调度期间使用多个线程优先级,同时避免在线程执行期间出现一些不需要的上下文切换。

这里仅对抢占式调度进行说明。

运行条件:

创建 3 个任务 Task1,Task2 和 Task3。

Task1 的优先级为 1,Task2 的优先级为 2,Task3 的优先级为 3。

“使用MM32F3270基于Azure

没有使用抢占式任务调度,Task1首先开始运行,此时虽然Task2 就绪,只能在等任务1 完全空闲下来或完成任务;Task2 任然在运行,此时虽然Task3也就绪了,同样需要等任务2空闲下来或者完成任务,才能转成运行态,执行程序运行。

而使用任务抢占式调度方式,运行过程变为:

A. 任务 Task1 在运行中,运行过程中由于 Task2 就绪(A点),在抢占式调度器的作用下任务 Task2 抢占Task1 的执行。Task2 进入到运行态,Task1 由运行态进入到就绪态。

B. 任务 Task2 在运行中,运行过程中由于 Task3 就绪(B点),在抢占式调度器的作用下任务 Task3 抢占 Task2的执行。Task3 进入到运行态,Task2 由运行态进入到就绪态。

C. 任务 Task3 运行过程中调用了阻塞式 API 函数(C点),比如 tx_thread_sleep,任务 Task3 被挂起,在抢占式调度器的作用下查找到下一个要执行的最高优先级任务是 Task2,任务 Task2 由就绪态进入到运行态。

D. 任务 Task2 运行完成(完成时结束任务2的抢占)前,任务3的阻塞或Sleep时间已经结束。下一一步继续执行的任务是 Task3,任务 Task3完成后,解除本次抢占。任务1由就绪态进入到运行态,直到运行完成。

2.2 Azure Threadx 任务抢占的相关函数

tx_thread_preemption_change 更改应用程序线程的抢占阈值

UINT tx_thread_preemption_change(
    TX_THREAD *thread_ptr,
    UINT new_threshold, 
    UINT *old_threshold);

函数说明

此服务更改指定线程的抢占阈值。抢占阈值阻止指定线程被等于或小于抢占阈值的线程抢占。

备注:

使用抢占阈值会禁用指定线程的时间切片。

参数

thread_ptr:指向以前创建的应用程序线程的指针。

● new_threshold:新的抢占阈值优先级(0 至 TX_MAX_PRIORITIES-1)。

● old_threshold:指向恢复为上一个抢占阈值的位置的指针。

返回值

● TX_SUCCESS:(0X00) 成功更改抢占阈值。

● TX_THREAD_ERROR:(0X0E) 应用程序线程指针无效。

● TX_THRESH_ERROR:(0x18) 指定的新抢占阈值是无效的线程优先级(值不介于 0 到 TX_MAX_PRIORITIES-1 之间)或大于(优先级更低)当前线程优先级。

● TX_PTR_ERROR:(0x03) 指向之前的抢占阈值存储位置的指针无效。

● NX_CALLER_ERROR:(0x13) 此服务的调用方无效。

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

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

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

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

示例

TX_THREAD my_thread;
UINT my_old_threshold;
UINT status;

/* Disable all preemption of the specified thread. The
current preemption-threshold is returned in
"my_old_threshold". Assume that "my_thread" has
already been created. */

status = tx_thread_preemption_change(&my_thread,
    0, &my_old_threshold);

/* If status equals TX_SUCCESS, the application thread is
non-preemptable by another thread. Note that ISRs are
not prevented by preemption disabling. */

2.3 抢占任务的应用演示

2.3.1 工程目录的建立

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

“使用MM32F3270基于Azure

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

“使用MM32F3270基于Azure

参考sample_threadx.c建立main_preemption_demo.c文件,并添加到工程项目中。

“使用MM32F3270基于Azure

该Demo程序还用到了Led和阻塞时延时,因此还需添加led.c和delay.c。

特别要注意的是需要在led.c中配置USE_SYSTICK_DELAY 为 0。

#define USE_SYSTICK_DELAY 0

2.3.2 创建抢占任务示例

举一个极端点的例子:修改抢占任务阈值为0,以确保不被其他任务抢占。

创建一个待使用的抢占任务,并在任务中修改抢占任务阈值。

void    thread_5_entry(ULONG thread_input)
{

    UINT    status;
    ULONG   actual_flags;
    UINT    old_threshold;
    UINT    i=10;

    /* This thread simply waits for an event in a forever loop.  */
    while(1) {

        // set preemption to 0(the highest, equal to close the thread task interrupt.
        status = tx_thread_preemption_change(&thread_5, THREAD5_PREEMPTION_THRESHOLD_NEW, &old_threshold);

        if(status == TX_SUCCESS)
        {
            //these code show how the tx_thread_preemption_change affect
            //user can add code which can not be interruptted by other thread
            while(i--){
                LED1_TOGGLE();
                DELAY_Ms(100);
            }
            i=10;
            //restore preemption
            tx_thread_preemption_change(&thread_5, THREAD5_PREEMPTION_THRESHOLD, &old_threshold);
        }

        tx_thread_sleep(100);

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

        /* Wait for event flag 0.  */
        status =  tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
                                     &actual_flags, TX_WAIT_FOREVER);

        /* Check status.  */
        if ((status != TX_SUCCESS) || (actual_flags != 0x1))
            break;
    }
}

3、Threadx 的抢占任务应用实现与调试

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"
#define DEMO_STACK_SIZE                 1024

#define THREAD0_PRIORITY                    1
#define THREAD0_PREEMPTION_THRESHOLD        1
#define THREAD5_PRIORITY                    4
#define THREAD5_PREEMPTION_THRESHOLD        4
#define THREAD5_PREEMPTION_THRESHOLD_NEW    0

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



TX_EVENT_FLAGS_GROUP    event_flags_0;

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

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

/* Define thread prototypes.  */
void    thread_0_entry(ULONG thread_input);
void    thread_5_entry(ULONG thread_input);

/* Define main entry point.  */
int main()
{
    LED_Init();
    DELAY_Init(); //can not use systick
    /* Enter the ThreadX kernel.  */
    tx_kernel_enter();
}

/* Define what the initial system looks like.  */
void    tx_application_define(void* first_unused_memory)
{
    /* Create the main thread.  */
    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 5.  This thread simply pends on an event flag which will be set
       by thread_0.  */
    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);

    /* Create the event flags group used by threads 0 and 5.  */
    tx_event_flags_create(&event_flags_0, "event flags 0");
}


/* Define the test threads.  */

void    thread_0_entry(ULONG thread_input)
{

    UINT    status;

    /* This thread simply sits in while-forever-sleep loop.  */
    while(1) {

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

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

        /* Set event flag 0 to wakeup thread 5.  */
        status =  tx_event_flags_set(&event_flags_0, 0x1, TX_OR);

        /* Check status.  */
        if (status != TX_SUCCESS)
            break;
    }
}

void    thread_5_entry(ULONG thread_input)
{

    UINT    status;
    ULONG   actual_flags;
    UINT    old_threshold;
    UINT    i=10;

    /* This thread simply waits for an event in a forever loop.  */
    while(1) {

        // set preemption to 0(the highest, equal to close the thread task interrupt.
        status = tx_thread_preemption_change(&thread_5, THREAD5_PREEMPTION_THRESHOLD_NEW, &old_threshold);

        if(status == TX_SUCCESS)
        {
            //these code show how the tx_thread_preemption_change affect
            //user can add code which can not be interruptted by other thread
            while(i--){
                LED1_TOGGLE();
                DELAY_Ms(100);
            }
            i=10;
            //restore preemption
            tx_thread_preemption_change(&thread_5, THREAD5_PREEMPTION_THRESHOLD, &old_threshold);
        }

        tx_thread_sleep(100);

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

        /* Wait for event flag 0.  */
        status =  tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR,
                                     &actual_flags, TX_WAIT_FOREVER);

        /* Check status.  */
        if ((status != TX_SUCCESS) || (actual_flags != 0x1))
            break;
    }
}

3.2 下载与调试

进入调试模式,可以看到接收抢占任务中闪灯程序运行时,其他高优先级的任务不能从就绪态转为运行态,Demo演示成功。

“使用MM32F3270基于Azure

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

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

“使用MM32F3270基于Azure

但由于任务5中有运行长时间的抢占任务LED0 Toggle的动作,由thread_5_counter于thread_0_counter的巨大差值可以看出:该抢占任务导致Thread0中的发送到Thread5的事件标志,很多未被接收。从而成功的演示的抢占任务的特性。

4、小结

Azure RTOS ThreadX 的使用抢占任务可以方便实现任务优先级切换,避免的任务优先级的缺陷,结合MM32F3270的强大性能,可以实现Azure RTOS的强大的功能。

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

围观 25

简 介

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、移植应用的准备

1.1 硬件开发板的准备

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

“使用MM32F3270基于Azure

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/

“使用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的EVB-F3270上运行。

此示例在文件 main_event_flags_demo.c 中定义,旨在说明如何在嵌入式多线程环境中使用事件标志组,实现任务之间的数据信号传递与中断或任务同步的相互关系。

2.1 Azure Threadx event简介

事件标志是Threadx线程同步的一个强大工具。事件标志可以被任何线程设置或清除,也可以被任何线程检查。线程可以在等待设置某些事件标志组时挂起。

每个事件标志用1 bit表示,事件标志以32个为一组排列,组成一个事件标志组。之后的所有操作都是以组为单位。

“使用MM32F3270基于Azure

线程可以同时对一个组中的所有32个事件标志进行操作。

要设置或清除事件标志组,可以使用tx_event_flags_set服务。

使用tx_event_flags_get服务可以“获取”事件标志组。

获取事件标志时,线程可以等待一个事件标志(一段时间或永久等待),直到该事件标志被另一个线程或中断设置为1(set状态)。

对于嵌入式应用来说,就有点像对一个个的寄存器位进行“&”和“|”操作。

使用事件标志组可以有效的解决中断服务程序和任务之间的同步问题。

“使用MM32F3270基于Azure

2.2 Azure Threadx event的结构体

Event的结构体

typedef struct TX_EVENT_FLAGS_GROUP_STRUCT
{
    /* Define the event flags group ID used for error checking.  */
    ULONG               tx_event_flags_group_id;
    /* Define the event flags group's name.  */
    CHAR                *tx_event_flags_group_name;
    /* Define the actual current event flags in this group. A zero in a
       particular bit indicates the event flag is not set.  */
    ULONG               tx_event_flags_group_current;
    /* Define the reset search flag that is set when an ISR sets flags during
       the search of the suspended threads list.  */
    UINT                tx_event_flags_group_reset_search;
    /* Define the event flags group suspension list head along with a count of
       how many threads are suspended.  */
    struct TX_THREAD_STRUCT
                        *tx_event_flags_group_suspension_list;
    UINT                tx_event_flags_group_suspended_count;
    /* Define the created list next and previous pointers.  */
    struct TX_EVENT_FLAGS_GROUP_STRUCT
                        *tx_event_flags_group_created_next,
                        *tx_event_flags_group_created_previous;
    /* Define the delayed clearing event flags.  */
    ULONG               tx_event_flags_group_delayed_clear;
#ifdef TX_EVENT_FLAGS_ENABLE_PERFORMANCE_INFO
    /* Define the number of event flag sets.  */
    ULONG               tx_event_flags_group_performance_set_count;
    /* Define the number of event flag gets.  */
    ULONG               tx_event_flags_group__performance_get_count;
    /* Define the number of event flag suspensions.  */
    ULONG               tx_event_flags_group___performance_suspension_count;
    /* Define the number of event flag timeouts.  */
    ULONG               tx_event_flags_group____performance_timeout_count;
#endif
#ifndef TX_DISABLE_NOTIFY_CALLBACKS
    /* Define the application callback routine used to notify the application when
       an event flag is set.  */
    VOID                (*tx_event_flags_group_set_notify)(struct TX_EVENT_FLAGS_GROUP_STRUCT *group_ptr);
#endif
    /* Define the port extension in the event flags group control block. This
       is typically defined to whitespace in tx_port.h.  */
    TX_EVENT_FLAGS_GROUP_EXTENSION
} TX_EVENT_FLAGS_GROUP;

Event flags包含成员的含义:

“使用MM32F3270基于Azure

2.3 Azure Threadx event的主要函数与功能

事件标志组的主要函数

“使用MM32F3270基于Azure

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

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

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

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

2.4 事件标志组的应用演示

2.4.1 工程目录的建立

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

“使用MM32F3270基于Azure

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

“使用MM32F3270基于Azure

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

“使用MM32F3270基于Azure

2.4.2 创建事件标志组tx_event_flags_create

声明需要使用的事件标志组变量。

调用tx_event_flags_create, 建立一个事件标志组。

创建一个待使用的事件标志组

#include "tx_api.h"

#define DEMO_STACK_SIZE         1024

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

TX_EVENT_FLAGS_GROUP    event_flags_0;

/* Define what the initial system looks like.  */
void    tx_application_define(void *first_unused_memory)
{
    /* Create the main thread.  */
    tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,  
            thread_0_stack, DEMO_STACK_SIZE, 
            1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
    /* Create thread 5.  This thread simply pends on an event flag which will be set
       by thread_0.  */
    tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,  
            thread_5_stack, DEMO_STACK_SIZE, 
            4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
    /* Create the event flags group used by threads 0 and 5.  */
    tx_event_flags_create(&event_flags_0, "event flags 0");
}

函数原型:

tx_event_flags_create函数原型

UINT  _tx_event_flags_create(TX_EVENT_FLAGS_GROUP *group_ptr, CHAR *name_ptr)

参数:

● group_ptr:指向事件标志组控制块的指针。

● name_ptr:指向事件标志组名称的指针。

返回值:

● TX_SUCCESS:(0X00) 成功创建事件组。

● TX_GROUP_ERROR:(0x06) 事件组指针无效。指针为 NULL 或事件组已创建。

● NX_CALLER_ERROR:(0x13) 此服务的调用方无效。

应用中该实现函数:

tx_event_flags_create(&event_flags_0, "event flags 0");

A. 第1个参数是事件标志组引用的指针,引用声明的事件标注“event_flags_0”指向的地址。

B. 第2个参数是事件标志组的名字,字符串值为" event flags 0"。

2.4.3 创建一个任务用于发送事件标志组

在此建立一个定时发送一个事件标志组的简单任务,发送完成,发送任务计数值加一:

创建一个发送事件标志组的任务

/* Define the test threads.  */
void    thread_0_entry(ULONG thread_input)
{
    UINT status;
    /* This thread simply sits in while-forever-sleep loop.  */
    while(1)
    {
        /* Increment the thread counter.  */
        thread_0_counter++;
        /* Sleep for 10 ticks.  */
        tx_thread_sleep(10);
        /* Set event flag 0 to wakeup thread 5.  */
        status =  tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
        /* Check status.  */
        if (status != TX_SUCCESS)
            break;
    }
}

函数原型:

_tx_event_flags_set函数原型

UINT  _tx_event_flags_set(TX_EVENT_FLAGS_GROUP *group_ptr, ULONG flags_to_set, UINT set_option)

参数:

● group_ptr:指向以前创建的事件标志组控制块的指针。

● flags_to_set:根据所选的 set-option,指定要设置或清除的事件标志。

● set_option:指定将所指定的事件标志与该组的当前事件标志进行“AND”或“OR”运算。以下是有效的选择:

- TX_AND (0x02)

- TX_OR (0x00)

如果选择 TX_AND,则会指定将所指定的事件标志与该组的当前事件标志进行“AND”运算。此选项通常用于清除组中的事件标志。否则,如果指定了 TX_OR,则对所指定的事件标志与该组的当前事件标志进行“OR”运算。

返回值:

● TX_SUCCESS:(0X00) 成功设置事件标志。

● TX_GROUP_ERROR:(0x06) 指向事件标志组的指针无效。

● TX_OPTION_ERROR:(0x08) 指定的 set-option 无效。

应用中该实现函数:

status = tx_event_flags_set(&event_flags_0, 0x1, TX_OR);

A. 第1个参数是事件标志组引用的指针,引用声明的事件标注“event_flags_0”指向的地址。

B. 第2个参数是事件标志组的名字,字符串值为" event flags 0"。

返回值

C. 第3个参数是TX_OR,则对所指定的事件标志与该组的当前事件标志进行“OR”运算。

2.4.4 创建一个任务接收事件标志组

在此建立一个任务,实现接收收事件标志组,接收事件设置值成功,接收任务计数值加一:

创建一个接收事件标志组任务

void    thread_5_entry(ULONG thread_input)
{
    UINT status;
    ULONG actual_flags;
    /* This thread simply waits for an event in a forever loop.  */
    while(1)
    {
        /* Increment the thread counter.  */
        thread_5_counter++;
        /* Wait for event flag 0.  */
        status =  tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR, 
        &actual_flags, TX_WAIT_FOREVER);
        /* Check status.  */
        if ((status != TX_SUCCESS) || (actual_flags != 0x1))
            break;
    }
}

函数原型:

_tx_event_flags_set函数原型

UINT  _tx_event_flags_get(TX_EVENT_FLAGS_GROUP *group_ptr, ULONG requested_flags,
                    UINT get_option, ULONG *actual_flags_ptr, ULONG wait_option)

参数:

● group_ptr:指向以前创建的事件标志组的指针。

● requested_flags:32 位无符号变量,表示请求的事件标志。

● get_option:指定是否需要所有或任何请求的事件标志。以下是有效的选择:

- TX_AND (0x02)

- TX_AND_CLEAR (0x03)

- TX_OR (0x00)

- TX_OR_CLEAR (0x01)

如果选择 TX_AND 或 TX_AND_CLEAR,则会指定所有事件标志在组中都必须存在。如果选择 TX_OR 或 TX_OR_CLEAR,则会指定任何事件标志都符合要求。如果指定 TX_AND_CLEAR 或 TX_OR_CLEAR,则会清除满足请求的事件标志(设置为零)。

● actual_flags_ptr:指向放置检索到的事件标志的位置这一目标的指针。注意,实际获得的标志可能包含没有请求的标志。

● wait_option:定义未设置所选事件标志时服务的行为方式。

等待选项的定义如下:

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

- TX_WAIT_FOREVER 超时值 (0xFFFFFFFF) - 选择 TX_WAIT_FOREVER 会导致发出调用的线程无限期挂起,直到事件标志可用为止。

- 超时值(0x00000001 至 0xFFFFFFFE)- 如果选择一个数值(1 到 0xFFFFFFFE),则会指定在等待事件标志时发出调用的线程保持挂起的最大计时器时钟周期数。

返回值:

● TX_SUCCESS:(0X00) 成功获取事件标志。

● TX_DELETED:(0x01) 线程挂起时删除了事件标志组。

● TX_NO_EVENTS:(0X07) 服务无法在指定的等待时间内获取指定的事件。

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

● TX_GROUP_ERROR:(0x06) 事件标志组指针无效。

● TX_PTR_ERROR:(0x03) 指向实际事件标志的指针无效。

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

● TX_OPTION_ERROR:(0x08) 指定的 get-option 无效。

应用中该实现函数:

status = tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR, &actual_flags, TX_WAIT_FOREVER);

A. 第1个参数是事件标志组引用的指针,引用声明的事件标注“event_flags_0”指向的地址。

B. 第2个参数是根据所选的 get-option,指定要设置或清除的事件标志等于0x01。

C. 第3个参数是TX_OR_CLEAR,则会判断指定任何事件标志都符合要求,符合后并清除。

3、Threadx 的事件标志组应用实现与调试

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 an event flags group.  */

#include "tx_api.h"

#define DEMO_STACK_SIZE         1024

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

TX_EVENT_FLAGS_GROUP    event_flags_0;

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

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

/* Define thread prototypes.  */
void    thread_0_entry(ULONG thread_input);
void    thread_5_entry(ULONG thread_input);

/* Define main entry point.  */
int main()
{
    /* Enter the ThreadX kernel.  */
    tx_kernel_enter();
}

/* Define what the initial system looks like.  */
void    tx_application_define(void *first_unused_memory)
{
    /* Create the main thread.  */
    tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,  
            thread_0_stack, DEMO_STACK_SIZE, 
            1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
    /* Create thread 5.  This thread simply pends on an event flag which will be set
       by thread_0.  */
    tx_thread_create(&thread_5, "thread 5", thread_5_entry, 5,  
            thread_5_stack, DEMO_STACK_SIZE, 
            4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
    /* Create the event flags group used by threads 0 and 5.  */
    tx_event_flags_create(&event_flags_0, "event flags 0");
}

/* Define the test threads.  */
void    thread_0_entry(ULONG thread_input)
{
    UINT status;
    /* This thread simply sits in while-forever-sleep loop.  */
    while(1)
    {
        /* Increment the thread counter.  */
        thread_0_counter++;
        /* Sleep for 10 ticks.  */
        tx_thread_sleep(10);
        /* Set event flag 0 to wakeup thread 5.  */
        status =  tx_event_flags_set(&event_flags_0, 0x1, TX_OR);
        /* Check status.  */
        if (status != TX_SUCCESS)
            break;
    }
}

void    thread_5_entry(ULONG thread_input)
{
    UINT status;
    ULONG actual_flags;
    /* This thread simply waits for an event in a forever loop.  */
    while(1)
    {
        /* Increment the thread counter.  */
        thread_5_counter++;
        /* Wait for event flag 0.  */
        status =  tx_event_flags_get(&event_flags_0, 0x1, TX_OR_CLEAR, 
                                                &actual_flags, TX_WAIT_FOREVER);
        /* Check status.  */
        if ((status != TX_SUCCESS) || (actual_flags != 0x1))
            break;
    }
}

3.2 下载与调试

进入调试模式,可以看到接收事件标志组成功。

“使用MM32F3270基于Azure

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

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

“使用MM32F3270基于Azure

4、小结

这个演示程序中,使用了tx_thread_sleep()函数做了延时,刚好使得发送事件标志组与接收事件标志组一一配对,计数值累加后都是相等的,说明同步成功。

Azure RTOS ThreadX 的使用事件标志组可以方便实现任务对任务或任务对中断的同步,结合MM32F3270的强大性能,可以实现Azure RTOS的各种特色功能。

点击下方链接进行下载:

https://mindmotion.com.cn/download/products/MM32F3270_Event_Flags.zip

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

围观 51

简 介

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)。

围观 134

简 介

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任务切换特性。

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

表 1 适用系列型号

“表

1、移植应用的准备

1.1 硬件开发板的准备

该移植过程中应用的开发板为MM32的EVBoard MB039(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

本次移植过程使用上一个已经移植好的基本样例为起始点,做移植说明。

2、Threadx 的多任务的实现

该章节介绍了多任务的实现的过程和注意事项,该演示程序可在所有MM32F3273G9P的EVBoard MB039上运行。

此示例在文件 main_multi_task.c 中定义,旨在说明如何在嵌入式多线程环境中使用 ThreadX。演示包括初始化硬件外设、4个线程。

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 Azure Threadx的运行过程

通过样例可知,Azure Threadx的主要执行的入口代码如下:

基本系统架构

“使用MM32F3270基于Azure

main 函数

系统完成初始化后,系统将控制权转交给用户提供的 main 函数。此时,应用程序会控制接下来执行的操作。对于大多数应用程序,main 函数只调用 tx_kernel_enter,这是 ThreadX 的入口。但是,应用程序可在进入 ThreadX 之前执行初步处理(通常用于硬件初始化)。

注意:

对 tx_kernel_enter 的调用不会返回结果,因此请勿在此后执行任何处理。

tx_kernel_enter

entry 函数协调各种内部 ThreadX 数据结构的初始化,然后调用应用程序的定义函数 tx_application_define。

当 tx_application_define 返回时,控制权将转交给线程调度循环。这标志着初始化结束。

应用程序定义函数

tx_application_define 函数定义所有初始应用程序线程、队列、信号灯、互斥、事件标志、内存池和计时器。在应用程序的正常操作过程中,还可以在线程中创建和删除系统资源。但是,所有初始应用程序资源都在此处定义。

值得一提的是,tx_application_define 函数只有一个输入参数。“第一个可用”的 RAM 地址是该函数唯一的输入参数。该地址通常用作线程堆栈、队列和内存池的初始运行时内存分配起点。

注意:

初始化完成后,只有正在执行的线程才能创建和删除系统资源(包括其他线程)。因此,在初始化期间必须至少创建一个线程。

中断

在整个初始化过程中,中断处于禁用状态。如果应用程序以某种方式启用中断,则可能出现不可预知的行为。下图显示了从系统重置到特定于应用程序的初始化的整个初始化过程。

“使用MM32F3270基于Azure

线程执行

计划和执行应用程序线程是 ThreadX 最重要的活动。线程通常定义为具有专用用途的半独立程序段。所有线程的组合处理构成了应用程序。

线程在初始化或线程执行期间通过调用 tx_thread_create 来动态创建。创建的线程处于“就绪”或“已挂起”状态。

2.3 Azure Threadx执行程序的建立

打开main_multi_task.c文件,添加#include "led.h"

保留默堆栈空间大小

“使用MM32F3270基于Azure

首先第一步,建立四个线程用于演示四个闪灯线程,以做建立线程的基本展示。

建立四个线程所需的线程优先级(thread priority),线程对象控制块 ( thread object control blocks ),线程累加计数器 ( thread counters ), 线程堆栈块大小 ( thread stacks )

#define  APP_CFG_TASK_LED1_PRIO                            1u
#define  APP_CFG_TASK_LED2_PRIO                           16u
#define  APP_CFG_TASK_LED3_PRIO                           16u
#define  APP_CFG_TASK_LED4_PRIO                            8u

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

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

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

每个演示线程均会递增自己的唯一计数器。可以检查以下计数器以核实演示的操作:

thread_0_counter

thread_1_counter

thread_2_counter

thread_3_counter

在演示执行过程中,每个计数器都应继续增加,其中,thread_0_counter 的增加速度最快, thread_1_counter 的增加速度属于第二快,thread_2_counter和thread_3_counter分列第三和第四;这主要和每次counter累加时sleep时长相关。

线程 0

函数 thread_0_entry 标记线程的入口点。Thread_0 是演示系统中要执行的第一个线程。其处理方式很简单:递增其计数器,休眠 10 个计时器时钟周期,设置GPIO推动LED1亮暗翻转。

后续的线程1,2,3分别操作不同的LED2,LED3,LED4翻转,并等待不同的时间。

Thread_0 优先级被设置为1,是系统中优先级最高的线程。其他的线程分别被设置为16,16,8等不同的优先级。Thread_0的优先级最高,当其请求的休眠过期时,将抢占演示中任何其他正在执行的线程。这个是Azure ThreadX RTOS强大的一个地方。

建立任务代码

void    tx_application_define(void* first_unused_memory)
{
    /* Create threads 0 */
    tx_thread_create(
        &thread_0,              /* 任务控制块地址     */
        "Task 0 led blink 1",   /* 任务名             */
        thread_0_entry,         /* 启动任务函数地址   */
        0,                      /* 传递给任务的参数   */
        thread_0_stack,         /* 堆栈基地址         */
        DEMO_STACK_SIZE,        /* 堆栈空间大小       */
        APP_CFG_TASK_LED1_PRIO, /* 任务优先级         */
        1,                      /* 任务抢占阀值       */
        TX_NO_TIME_SLICE,       /* 不开启时间片       */
        TX_AUTO_START           /* 创建后立即启动     */
    );

    /* Create threads 1 */
    tx_thread_create(
        &thread_1,
        "Task 1 led blink 2",
        thread_1_entry,
        1,
        thread_1_stack,
        DEMO_STACK_SIZE,
        APP_CFG_TASK_LED2_PRIO,
        16,
        TX_NO_TIME_SLICE,
        TX_AUTO_START
    );
    /* Create threads 2. */
    tx_thread_create(
        &thread_2,
        "Task 2 led blink 3",
        thread_2_entry, 2,
        thread_2_stack,
        DEMO_STACK_SIZE,
        APP_CFG_TASK_LED3_PRIO,
        16,
        TX_NO_TIME_SLICE,
        TX_AUTO_START);

    /* Create threads 3 */
    tx_thread_create(
        &thread_3,
        "Task 3 led blink 4",
        thread_3_entry,
        3,
        thread_3_stack,
        DEMO_STACK_SIZE,
        APP_CFG_TASK_LED4_PRIO,
        8,
        TX_NO_TIME_SLICE,
        TX_AUTO_START
    );

}

四个LED翻转的任务

void    thread_0_entry(ULONG thread_input)
{
    while(1) {
        /* Increment the thread counter.  */
        thread_0_counter++;
        LED1_TOGGLE();
        /* Sleep for 10 ticks.  */
        tx_thread_sleep(10);
    }
}

void    thread_1_entry(ULONG thread_input)
{
    while(1) {
        /* Increment the thread counter.  */
        thread_1_counter++;
        LED2_TOGGLE();
        tx_thread_sleep(100);
    }
}

void    thread_2_entry(ULONG thread_input)
{
    while(1) {
        /* Increment the thread counter.  */
        thread_2_counter++;
        LED3_TOGGLE();
        tx_thread_sleep(300);
    }
}

void    thread_3_entry(ULONG thread_input)
{

    while(1) {
        /* Increment the thread counter.  */
        thread_3_counter++;
        LED4_TOGGLE();
        tx_thread_sleep(600);
    }
}

最后添加main函数

main函数代码

int main()
{
    LED_Init();

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

在main()函数中主要执行初步处理(通常用于硬件初始化),在此执行了LED_Init();然后进入tx_kernel_enter()实现系统的任务建立与任务调度。

3、Threadx 的运行与调试

3.1 下载与调试

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

“使用MM32F3270基于Azure

该demo示例,建立了4个线程,程序执行4个LED翻转,并累加Counter值。

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

“使用MM32F3270基于Azure

4、小结

关于使用MM32F3270基于Azure RTOS ThreadX 的最四个小任务就建立,并运行起来,后面再给大家介绍Task相关的抢占任务优先级等Azure RTOS的特色功能。

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

围观 290

简 介

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做基本移植。

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

表 1 适用系列型号

“使用MM32F3270基于Azure

1、移植应用的准备

1.1 硬件开发板的准备

该移植过程中应用的开发板为MM32的EVBoard MB039(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

本次移植过程使用keil模板为例子,做移植说明。

2、Threadx 的移植

该章节介绍了移植的过程和注意事项。

2.1 建立工程目录

建立目标工程文件夹“MM32F3270Project”:

“使用MM32F3270基于Azure

复制Threadx中的“common”目录和“ports”目录下的“Cortex-M3”目录到:

“MM32F3270Project\threadx\common”

“MM32F3270Project\threadx\ports\cortex_m3\keil”

目录。

“使用MM32F3270基于Azure

复制LibSamples_MM32F3270中的“Device”目录到:MM32F3270Project\Device

“使用MM32F3270基于Azure

复制“Samples\LibSamples\GPIO\GPIO_Toggle”目录到“MM32F3270Project”目录。

“使用MM32F3270基于Azure

“使用MM32F3270基于Azure

最后形成如下目标目录:

“使用MM32F3270基于Azure

2.2 添加源码到KEIL工程

打开IOtoggle工程;

“使用MM32F3270基于Azure

它包含基本的LED blink功能;

“使用MM32F3270基于Azure

通过完成编译,选择相应的调试下载工具(ULINK,Jlink,CMSIS-DAP Link,MM32LINK-OB),可以验证EVB正常工作,实现四个LED做500ms/500ms的亮暗闪烁。

验证过后,复制KEILPRJ工程目录为KEILPRJ_Threadx目录;

“使用MM32F3270基于Azure

新建threadX/common分组,添加threadX/common/src下的所有c文件:

“使用MM32F3270基于Azure

新建threadX/ports/mm32f3270/keil分组,选择对应的编译环境。

移植使用的KEIL为5.35,选用KEIL compiler version 5编译器(armcc)。

需要添加 threadX\ports\cortex_m3\keil\src 下的所有 .s 文件:

“使用MM32F3270基于Azure

新建threadX/ports/mm32f3270/keil_s分组,选择对应的汇编文件。

添加threadx\ports\cortex_m3\keil\example_build 下的.s 文件:

tx_initialize_low_level.s:

“使用MM32F3270基于Azure

新建threadX/ports/mm32f3270/demo分组。

添加threadx\ports\cortex_m3\keil\example_build 下的.c 文件:

sample_threadx.c:

“使用MM32F3270基于Azure

因为sample_threadx.c包含main()函数,需要USER 组,以删除USER目录下的main.c文件。

“使用MM32F3270基于Azure

设置编译环境,选择Use default compiler version 5,编译器为(armcc):

“使用MM32F3270基于Azure

设“C/C++”的头文件包含目录:

添加threadx\ports\下的.h文件包含目录:

..\threadx\common\inc

..\threadx\ports\cortex_m3\keil\inc

删除未用到的目录

..\USER

“使用MM32F3270基于Azure

完成源代码添加和头文件目录包含,进行首次编译,

编译报如下错误:

“使用MM32F3270基于Azure

检查适配ThreadX官方的底层适配文件tx_initialize_low_level.s,发现除了必须的函数:_tx_initialize_low_level。该tx_initialize_low_level.s还包含了汇编启动文件startup_mm32f327x_keil.s中的配置代码,包括stack与heap的配置、中断向量表,从而导致了冲突:

“使用MM32F3270基于Azure

因此需要移除tx_initialize_low_level.s中stack与heap的配置、中断向量表相关的代码(参考上图中的代码),因为已经在startup_mm32f327x_keil.s中实现。

同样的原因,需要删除Reset_Handler函数:

“使用MM32F3270基于Azure

及删除__user_initial_stackheap函数:

“使用MM32F3270基于Azure

并修改ThreadX适配文件tx_initialize_low_level.s中SYSTICK配置的参数。

修改SYSCLK频率,配置systick的中断时间间隔为1ms:

“使用MM32F3270基于Azure

并修改tx_initialize_low_level.s中相关代码,以匹配MM32F3270系列的启动代码startup_mm32f327x_keil.s,以下左侧为tx_initialize_low_level.s原始代码,右侧为修改后代码。

“使用MM32F3270基于Azure

修改__tx_vectors为__Vectors

修改|Image$$ZI$$Limit|

“使用MM32F3270基于Azure

添加SysTick_Handler声明到如下代码:

“使用MM32F3270基于Azure

具体参考修改后的.s文件。

再次编译,发现SysTick_Handler有冲突:

“使用MM32F3270基于Azure

原因是系统中的DELAY_Init &DELAY_Ms延时函数使用了SysTick_Handler。

“使用MM32F3270基于Azure

把宏定义:#define USE_SYSTICK_DELAY 1

修改为:#define USE_SYSTICK_DELAY 0

编译通过:

“使用MM32F3270基于Azure

配置好下载Debugger工具(可以使用MM32LINK-OB,JLINK,U-LINK或CMSIS-DAP LINK),下载调试。

3、Threadx 的运行与调试

3.1 下载与调试

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

该demo示例,建立了5个线程,程序都不与芯片的外设相关:

“使用MM32F3270基于Azure

线程只做数值自加,Demo演示了事件(Event),队列(queue),信号量(semaphore)的功能。

“使用MM32F3270基于Azure

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

“使用MM32F3270基于Azure

4、小结

关于使用MM32F3270基于Azure RTOS ThreadX 的最小系统就移植完成,并运行起来,后面再给大家介绍Task相关的配置,信号量,互斥锁等Azure RTOS的特色功能。

提示

可通过下方链接进行下载:
https://mindmotion.com.cn/whir_system/module/extension/Download.aspx?path=/uploadfiles/&name=download%2FMM32F3270Project.zip

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

围观 139

MM32F3270系列的ADC支持注入通道功能,每个ADC模块具有4个注入通道。每个注入通道具有独立的数据寄存器,注入通道具有比规则通道更高的优先级。

规则通道是按照指定的通道顺序进行转换。注入通道是指在规则通道转换过程中,如果突然需要进行其他某个通道的采样,则会在规则通道该次转换完成后停止转换,进行注入通道转换直到转换结束,再继续规则通道转换。

在一些特定场合使用ADC时可能会遇到很多情况,不仅仅简单的按规则通道来采样,有时候需要立刻采样,这就要用到注入通道。

注入通道模式介绍

注入通道工作模式分为自动注入和事件注入两种模式:

a) 自动注入:任意通道工作完成后自动开始注入通道工作;

b) 事件注入:注入事件到来, A/D转换完成当前转换后开始注入通道转换,完成注入通道转换后继续执行任意通道转换。

自动注入转换模式

任意通道配置通道转换完成后,自动开始注入通道转换。如果任意通道是连续扫描模式,需要清除ADST才能停止转换。配置自动注入转换模式应禁止注入事件发生。

“自动注入模式-通道转换时序图"
自动注入模式-通道转换时序图

事件注入工作模式

注入事件下禁止自动注入模式。注入事件发生后(包括软件和触发),如果当前转换正在进行,则完成当前转换后开始注入通道转换。

“任意通道转换时注入事件转换通道时序图"
任意通道转换时注入事件转换通道时序图

在注入通道转换期间有任意通道事件产生,注入转换不会结束,但会保存任意通道事件。完成本次转换序列后,开始任意通道事件的转换。

“注入通道转换时任意事件转换通道发生时序图"
注入通道转换时任意事件转换通道发生时序图

使用触发开始注入通道转换,必须保证触发事件的间隔长与注入序列,注入通道转换期间发生注入事件将被忽略。

触发信号

ADC转换的触发源包括软件触发、定时器和外部事件。

在触发信号产生后,延时N个PCLK2的时钟周期再开始采样。如果是触发扫描模式,只有第一个通道采样被延时,其余通道是在上一个采样结束后立即开始。

设置ADC_ANY_CR寄存器的JTRGEN位可以使用外部事件触发注入通道转换。设置ADC_ANY_CR寄存器的JTRGSEL位可以选择注入通道外部触发源。

具体的外部触发源选择情况,可以参考AD控制寄存器(ADC_ADCR.TRGSEL 或ADC_ANY_CR.JTRGSEL)相关位的描述。外部触发可设置延时控制,具体参考AD控制寄存器(ADC_ADCR.TRGSHIFT 或 ADC_ANY_CR.J TRGSHIFT)相关位的描述。

实验

本次实验使用ADC的注入通道:将ADC1的通道0配置为规则通道模式,通过软件触发,并使用DMA传输数据。将ADC1的通道1配置为注入通道模式,通过外部事件触发,并使用中断,在中断服务子程序中,就可以获取AD值。

开始只有ADC1的通道0进行AD转换,通道1不进行AD转换。当外部事件发生后,通道0当前转换结束后停止,通道1开始进行转换直至结束,通道0继续进行AD转换,经MCU处理将电压数据打印出来。

程序设计

ADC1 配置初始化

void ADC1BasicConfigWithParameter(void)
{
    ADC_InitTypeDef  ADC_InitStruct;
    ADC_TypeDef* ADCn;
    ADCn = ADC1;
    ADC_StructInit(&ADC_InitStruct);

    ADCxClockSet(ADCn, ENABLE);        
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStruct.ADC_PRESCARE = ADC_PCLK2_PRESCARE_16;
    ADC_InitStruct.ADC_Mode = ADC_Mode_Continue;
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;

    ADC_Init(ADCn, &ADC_InitStruct);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 0, ADC_SMPR1_SAMCTL0_240_5);   

    ADC_InjectedSequencerConfig(ADC1, ADC_ANY_CR_JTRGSEL_EXTI12, ADC_ANY_CR_JTRGSHIFT_64);
    ADC_InjectedSequencerLengthConfig(ADC1, ADC_Inject_Seqen_Len1);    
    ADC_InjectedSequencerChannelConfig(ADC1, ADC_InjectedChannel_1, ADC_Channel_1);  

    ADC1->ANYCR |= (1<<4);
    DMAInit();
    ADC_DMACmd(ADCn, ENABLE);
    ADC_Cmd(ADCn, ENABLE);   
}

外部事件初始化

ADC触发源选择外部事件触发方式,配置EXTI_Line12下降沿触发外部中断,通过PB12复用为外部中断/事件线。

void EXTIX_Init(void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2ENR_SYSCFG, ENABLE);

    //Set to pull-up input
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOB, ENABLE);
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_12;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    //GPIOB.12
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOB, EXTI_PinSource12);
    EXTI_StructInit(&EXTI_InitStructure);
    EXTI_InitStructure.EXTI_Line = EXTI_Line12;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Event;
    //Falling edge trigger
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
}

ADC1中断服务函数

外部事件触发ADC注入通道采样,转换完成便会触发ADC中断,在中断服务子程序中获取AD值。

void ADC1_2_IRQHandler(void)
{
    if(RESET != ADC_SREXT_JEOSIF) 
    {
        ADC1->SREXT |= (1 << 20);   //Clear the bit by writing about 1 
        ADCVAL = ADC_GetInjectedCurrentConvertedValue(ADC1);
        Flag_InjectConvert = 1;
        if(RESET != EXTI_GetFlagStatus(EXTI_Line12))  {
            EXTI_ClearITPendingBit(EXTI_Line12);
        }
    }
}

DMA中断服务函数

规则通道使用DMA中断进行数据传输,在DMA中断服务函数中调用ADCFilter()函数对获取的AD值做滤波处理。

void DMA1_Channel1_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC1))  {
        DMA_ClearITPendingBit(DMA1_IT_TC1);
        ADCFilter();
    }
}

主函数

示例中将获取的AD值与3.3V比对,转换为ADC输入通道的电压,并间隔300ms打印。

s32 main(void)
{
    DELAY_Init();
    CONSOLE_Init(115200);
    ADC1_ConfigInit();
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while(1) 
    {
        if(Flag_InjectConvert)
        {
            Flag_InjectConvert = 0;
            fValue = ((float)ADCVAL / 4095) * 3.3; 
            // ADC injection channel
            printf("ADC1_CH_1 = %fV\r\n\n", fValue);
        }   
        if(Flag_ADCFilter) 
        {
            Flag_ADCFilter = 0;
            //Convert the filtered value to voltage  
            ADCVolatge = ((float)ADCFilterValue / 4095) * 3.3;
            //ADC regular channels
            printf("ADC1_CH_0 = %fV\r\n\n", ADCVolatge);
            ADC_SoftwareStartConvCmd(ADC1, ENABLE); 
        }
        DELAY_Ms(300);
    }
}

演示

在开发板上将PA0(ADC1通道0)连接板上GND,将PA1(ADC1通道1)连接板上3.3V。

运行程序,开始时只有ADC1通道0(规则通道)进行AD转换。给PB12一个下降沿信号,此时外部事件触发ADC1通道1(注入通道)进行AD转换,通道0当前转换完成后停止AD转换,通道1进行转换直到结束,通道0进行AD转换。串口调试助手显示如下:

“MM32F3270

各通道输出数据和采样电压一致,与预期相符。在一些ADC应用场合中,通常将注入通道和规则通道混合使用,在规则通道转换的过程中立刻进行注入通道采样,充分发挥ADC外设的特性。

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

围观 179

前面章节中介绍了使用MM32F3270的SDIO外设驱动SD卡,对SD卡识别和简单的数据读写验证,不过像这样直接操作SD卡存储单元,在实际应用中是不现实的。SD卡一般用来存放文件,所以都需要加载文件系统到里面。

FatFs 是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。完全用标准C语言编写,所以具有良好的硬件平台独立性。可以移植到8051、PIC、AVR、SH、Z80、H8、ARM等系列单片机上而只需做简单的修改。它支持FATl2、FATl6和FAT32格式,支持多个存储媒介,具有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。

本章节主要介绍移植FatFs文件系统到SD卡内。

FatFs的特点

1)Windows兼容的FAT文件系统(支持FAT12/FAT16/FAT32)与平台无关,移植简单;

2)代码量少、效率高;

3)多种配置选项;

4)支持多卷(物理驱动器或分区,最多10个卷);

5)多个ANSI/OEM代码页包括DBCS;

6)支持长文件名、 ANSI/OEM 或Unicode;

7)支持RTOS;

8)支持多种扇区大小;

9)只读、最小化的API和I/O缓冲区等。

FatFs源码获取

FatFs文件系统的源码可以从FatFs官网下载:

http://elm-chan.org/fsw/ff/00index_e.html

此地址不仅仅包含资料包下载,还包括文件系统一些知识,包括函数说明,函数调用实例等。

“”SD卡的FatFs文件管理系统"

官网有对FatFs做详细的介绍,感兴趣可以多了解一些。所有版本的FatFs源码的移植步骤都是类似的,我们选择选择其中一个版本下载即可。

FatFs文件结构

解压之后可看到里面有 doc 和src 这两个文件。

“”SD卡的FatFs文件管理系统"

其中doc文件夹里面是一些使用帮助文档,src是FatFs文件系统的源码。

FatFs的源代码主要包含几个文件:

diskio.c、 diskio.h、 ff.c、 ff.h、 integer.h文件。

其中diskio.c 这个文件是文件系统底层和SD驱动的中间接口的实现代码,移植的时候需要改写在diskio.h中声明的那几个函数,代码在ff.c中被调用;diskio.h定义了FatFs用到的宏;ff.c是一般FatFs的代码文件;ff.h是一般FatFs包含的头文件;integer.h是内部基本类型的定义。

option文件夹下是一些可选的外部c文件,包含了多语言支持需要用到的文件和转换函数。

00readme.txt 说明了当前目录下 diskio.c 、 diskio.h、 ff.c、 ff.h、 integer.h 的功能。

FatFs移植步骤

在工程目录下新建FatFs文件夹,并将src文件夹下的文件复制一份至该文件夹。

“”SD卡的FatFs文件管理系统"

“”SD卡的FatFs文件管理系统"

使用KEIL打开工程文件并添加FatFs组件,并将src文件夹下的ff.c、 diskio.c 和 cc936.c 三个文件加入FatFs组件中。

“”SD卡的FatFs文件管理系统"

加入cc936.c文件可以支持简体中文,同时需要把 ffconf.h 中的 _CODE_PAGE 的宏改成 936。

“”SD卡的FatFs文件管理系统"

接着添加FatFs路径到工程选项。

“”SD卡的FatFs文件管理系统"

此时进行编译,会发现提示错误。

编写FatFs接口函数

来看diskio.c文件,注释前面的几个头文件,这里要加入自己的头文件。下面的三个宏定义ATA、MMC、USB也可以改成想要的名称,可以改成SD并定义为0。

“”SD卡的FatFs文件管理系统"

然后将函数disk_status、disk_initialize、disk_read、disk_write里面执行的代码注释或者删除,这里需要添加自己的代码。由于上面改了宏定义,这里switch-case也要做一些修改。

“”SD卡的FatFs文件管理系统"

更改如下:

“”SD卡的FatFs文件管理系统"

对disk_initialize、disk_read、disk_write几个函数也这样更改。

再次编译,发现提示一个关于get_fattime的错误,get_fattime用来获取当前时间,如果不需要,在ffconf.h中的宏定义#define _FS_NORTC改为1关闭,如果需要这个功能,需要在diskio.c里面,实现get_fattime函数,加入如下代码即可。

“”SD卡的FatFs文件管理系统"

然后进行编译,这时错误就没有了。

至此我们已经完成FatFs文件管理系统的移植,不过功能还没有实现,需要在disk_status、disk_initialize、disk_read、disk_writ、disk_ioctl函数中加入执行代码:

设备状态获取

DSTATUS disk_status (
    BYTE pdrv       /* Physical drive number to identify the drive */
)
{
    DSTATUS stat;

    stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);
    return stat;
}

设备初始化

DSTATUS disk_initialize (
    BYTE pdrv               /* Physical drive nmuber to identify the drive */
)
{
    DSTATUS stat = RES_OK;

    if(disk.is_initialized[pdrv] == 0) {
        disk.is_initialized[pdrv] = 1;
        stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
    }
    return stat;
}

读取扇区

DRESULT disk_read (
    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    BYTE* buff,     /* Data buffer to store read data */
    DWORD sector,           /* Sector address in LBA */
    UINT count      /* Number of sectors to read */
)
{
    DRESULT res;

    res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
    return res;
}

扇区写入

DRESULT disk_write (
    BYTE pdrv,      /* Physical drive nmuber to identify the drive */
    const BYTE* buff,   /* Data to be written */
    DWORD sector,       /* Sector address in LBA */
    UINT count          /* Number of sectors to write */
)
{
    DRESULT res;

    res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);
    return res;
}

其他

DRESULT disk_ioctl (
    BYTE pdrv,      /* Physical drive nmuber (0..) */
    BYTE cmd,       /* Control code */
    void* buff      /* Buffer to send/receive control data */
)
{
    DRESULT res;

    res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);
    return res;
}

关联的代码不再进行详细描述,可在MindMotion官网下载MM32F3270 lib_Samples:

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

工程路径如下:

~\MM32F3270_Lib_Samples_V0.90\Demo_app\PlayWave_Demo\SPI_I2S_SDIO_FatFs

可以看到详细的样例与功能操作。

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

围观 1012

页面

订阅 RSS - MM32F3270