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应用抢占任务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)。

围观 11

简 介

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

围观 11

简 介

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

围观 19

简 介

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

围观 38

简 介

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

围观 35

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

围观 45

前面章节中介绍了使用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)。

围观 382

SDIO(Secure Digital Input and Output)中文名称:安全数字输入输出,SDIO在SD标准上定义了一种外设接口。SDIO主要有两类应用——可移动和不可移动。可移动设备作为Palm和Windows Mobile的扩展设备,用来增加蓝牙、照相机、GPS和802.11b功能。不可移动设备遵循相同的电气标准,但不要求符合物理标准。某些手机内包含通过SDIO连接CPU的802.11芯片。此举将“珍贵”的I/ O管脚资源用于更重要的功能。

蓝牙、照相机、GPS和802.11b设备有专为它们定义的应用规范。这些应用规范与为PCI和USB设备定义的类规范很相像。它们允许任何宿主设备与任意外设“通话”,只要它们都支持应用规范。

SDIO和SD卡规范间的一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支支持低速I/ O能力。低速卡支持类似调制解调器、条码扫描仪和GPS接受器等应用。对“组合”卡(存储器+ SDIO)而言,全速和4位操作对卡内存储器和SDIO部分都是强制要求的。

MM32F3270系列控制器支持SDIO接口,本文在接下来会对MM32F3270的SDIO进行介绍,并通过实验演示SDIO驱动SD卡。

01 、SDIO 简介

SD/MMC/SDIO 控制器是 AMBA AHB 从外设,用于控制外部 SD/MMC/SDIO 卡,并支持 DSP/MCU的读/写访问。它作为主机与连接的 SD/MMC/SDIO 卡进行通信。所述控制器是基于 AMBA HCLK 域的完全同步设计。

1.1、SDIO 功能框图

“图1
图1 SDIO功能框图

1) AHB 从模式接口:为 32 位 AHB 总线提供接口。

2) FIFO 控制:产生握手信号到 DMA 硬件接口,并控制对外部数据 FIFO(128x32)的读/写访问。

3) 总线接口单元:包括控制寄存器和命令缓冲单元。

4) 多重块控制:控制多块数据的读写。

5) 时钟控制:通用时钟基于寄存器中定义的分频值。

6) 命令通路:从总线接口单元或 irq 响应中加载新的命令,然后发送命令,并接收响应 crc7 检查和 8 个空时钟。

7) 数据通路:发送和接收数据,用 crc16 检查。

8) 缓冲接口:控制对数据 FIFO 的读写控制信号。

1.2、SDIO 功能描述

1) 完全兼容 SD 记忆卡规格 1.0

2) 完全兼容 SD 存储卡规格 1.1(高速)

3) 完全兼容 SD 记忆卡规格 2.0(SDHC)

4) 完全兼容 MMC 系统规格 2.0~4.2

5) 完全兼容 SDIO 存储卡规格 1.1.0

6) 标准的 MMC 模式接口支持

7) 可编程时钟速率

8) 自动命令/响应 CRC 生成/检查

9) 自动数据 CRC 生成/检查

10) 可编程超时检测

11) AMBA 2.0 32 位 AHB 接口

12) 用于 AHB 数据访问的总共外部 128*32 数据 FIFO

13) 32 位 DMA 硬件接口,用于更快的 DMA 访问

14) DMA 接口可以配置为启用/禁用

15) DMA 请求是可配置的

16) 组合中断输出

02 、SD 存储卡

2.1、Read-Write 属性

根据 Read-Write 属性不同可分为两种类型的 SD 存储卡:

1) 可读可写(RW)卡(FLASH, OTP, MTP)。这些卡通常作为空白媒介来售卖,用于海量终端用户的视频、音频或数字图像记录的存储。

2) 只读存储卡(ROM)。这些卡是用固定的数据内容生产出来的,它们通常用作软件、音频、视频等的传播媒介。

2.2、电源电压

根据工作电源电压不同可分为两种类型的 SD 存储卡:

1) 高电压 SD 存储卡,可以在 2.7-3.6V 的电压范围内工作;

2) 双电压 SD 存储卡,可以在 1.6-3.6V 的电压范围内工作。

2.3、卡容量

根据卡容量不同可分为两种类型的 SD 存储卡:

1) 标准容量的 SD 存储卡支持最大 2G 字节的容量。所有版本的物理规格都定义了标准容量 SD 存储卡;

2) 高容量 SD 存储卡支持超过 2G 字节的容量,此版本规范限制容量高达 32GB。高容量 SD 存储卡是物理层规范版本 2.00 中新定义的。

2.4、传输速度

定义了四种速度等级,表示 SD 卡的最低性能:

1) Class0:这类卡不指定性能;
2) Class2:速度不低于 2MB/s;
3) Class4:速度不低于 4MB/s;
4) Class6:速度不低于 6MB/s;

大容量 SD 存储卡应支持速度等级规范,性能大于或等于 2 级。

2.5、总线拓扑

SD 卡系统定义了两种通信协议:SD 和 SPI。主机系统可以选择任意一种。当收到 reset 命令的时候, SD 卡通过主机的信息来决定使用何种模式,并且之后的通讯都会使用相同模式。不推荐多卡槽用共同的总线信号。一个单独的 SD 总线应该连接一个单独的 SD 卡。SD 总线包含下面的信号:

1) CLK:时钟信号;
2) CMD:双向命令/响应信号;
3) DAT0-DAT3:双向数据信号;
4) Vdd, Vss1, Vss2:电源和地信号。

2.6、总线协议

SD 总线通信:

1) Command:命令是一次操作开始的令牌,从主机发送到一个卡片(编址命令)或者连接到主机的所有卡片(广播命令)。命令在 CMD 线上连续传输。

2) Response:响应是从已寻址的卡或从所有连接上的卡发送到主机的令牌,作为对先前接收到指令的应答。响应在 CMD 线上连续传输。

3) Data:数据可以通过 DATA 线双向传输。

卡片寻址通过使用会话地址来实现,会话地址会在初始化阶段分配给卡。SD 总线上的基本交互是命令/响应交互。这种总线交互直接在命令或者响应的结构里面传输他们的信息。此外,某些操作还有数据令牌。SD 卡发送或接收的数据在块(block)中完成。数据块以 CRC 位来保证传输成功。目前有单块和多块操作。

注:多块操作模式在快速写操作时更好一点。多块传输在 CMD 线上产生 stop 命令时结束。主机端可以配置数据传输是单线还是多线。

“SDIO“无响应”和“无数据”操作"
SDIO“无响应”和“无数据”操作

“SDIO(多)数据块读操作"
SDIO(多)数据块读操作

“SDIO连续读操作"
SDIO连续读操作

“SDIO连续写操作"
SDIO连续写操作

03、卡的初始化以及识别过程

初始化进程以命令 ACMD41 作为开始,通过设置工作条件和 OCR 来进行。HCS(HighCapacitySupport)位为 1 表示主机支持高容量 SD 卡。卡通过 OCR 的 busy 位来通知主机 ACMD41 的初始化完成了。busy 位为 0表示卡仍然在初始化;为 1 表示已经完成初始化。主机会重复发送 ACMD41,直到 busy 位被置 1。卡片旨在第一个 ACMD41 的命令时,检查工作条件和 OCR 里面的 HCS 位。当重复 ACMD41 的时候,除了 CMD0,主机不再发其他命令。接着主机会发送命令 ALL_SEND_CID(CMD2),来获得卡的 CID 号。未识别的卡(处于Ready 状态的)发送自己的 CID 作为响应。当卡发送 CID 后,进入卡识别(Identification)状态。之后主机发送 SEND_RELATIVE_ADDR(CMD3)命令要求卡发布新的相对地址(RCA),一旦收到 RCA,卡就会变为等待(Stand-by)状态。主机会重复识别进程,为系统中每个卡循环发送 CMD2 和 CMD3。对于 SDI/O 卡而言,总线被激活后 SDIO 卡主机先发送 IO_SEND_OP_COND(CMD5)命令,得到的响应是卡的工作条件寄存器的内容,之后再同上发送 CMD3 命令,执行后续操作。

“使用MM32F3270的SDIO驱动SD卡"

04、MM32F3270 SDIO驱动SD卡

MM32的SDIO支持SD/MMC/SDIO卡,其中SDIO卡与SD存储卡是有区别的。SDIO卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDIO wifi, SDIO CMOS相机等。SD存储卡就是平时常见的用于存储数据的卡。

本实验中使用的Micro SD卡属于SDSC(标准容量,最大2G)卡。介绍卡的种类是因为SD协议中的命令也支持这三种类型的卡,因此对MM32中的SDIO接口进行初始化后,上电后就要对接入的卡进行检测、分类,这个过程是通过向卡发送一系列不同的命令,根据卡不同的响应来进行分类。

本实验使用MM32F3270的SDIO对SD卡进行读写测试,首先填充一个块大小的存储器,通过写入操作把数据写入到 SD卡内,然后通过读取操作读取数据到另外的存储器,然后再对比存储器内容,判断读写操作是否正确。

实现的大概流程包括:初始化SDIO 外设以及GPIO,配置 SDIO 基本通信环境进入卡识别模式,通过命令处理后获取卡信息及状态。如果是SD卡正常则进行数据传输,接下来就可以进行读、写以及擦除操作,否则打印SD卡错误信息,不再进行后续操作。

硬件设计

实验使用MB-039开发板,主控芯片为MM32F3277G9P,如图是MB-039开发板的SDIO/TF卡接口部分,完整原理图可以通过官网下载。

“使用MM32F3270的SDIO驱动SD卡"

各个信号引脚对应如下:

“使用MM32F3270的SDIO驱动SD卡"

程序设计

根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也较多,在本文中只对部分核心函数介绍其功能,详细代码可到灵动官网下载参考。

SPIO配置初始化

void SDIO_ConfigInit(void)
{
    SDIO_InitTypeDef SDIO_InitStruct;

    SDIO_PIN_GPIO_Config();
    SDIO_Detect_Pin_Config();

    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    SDIO_DeInit();
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, DISABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);

    SDIO_ClockSet(0x2F);
    SDIO_StructInit(&SDIO_InitStruct);

    SDIO_InitStruct.SDIO_OPMSel = SDIO_MMC_CTRL_OPMSel;
    SDIO_InitStruct.SDIO_SelPTSM = SDIO_MMC_CTRL_SelSM;
    SDIO_InitStruct.SDIO_DATWT = SDIO_MMC_CTRL_DATWT;
    SDIO_Init(&SDIO_InitStruct);

    SDIO_CRCConfig(SDIO_MMC_CRCCTL_CMD_CRCEN | SDIO_MMC_CRCCTL_DAT_CRCEN, ENABLE);
}
void show_sdcard_info(void)
{
    switch(SDCardInfo.CardType) {
        case SDIO_STD_CAPACITY_SD_CARD_V1_1:
            printf("Card Type:SDSC V1.1\r\n");
            break;
        case SDIO_STD_CAPACITY_SD_CARD_V2_0:
            printf("Card Type:SDSC V2.0\r\n");
            break;
        case SDIO_HIGH_CAPACITY_SD_CARD:
            printf("Card Type:SDHC V2.0\r\n");
            break;
        case SDIO_MULTIMEDIA_CARD:
            printf("Card Type:MMC Card\r\n");
            break;
    }
    printf("Card ManufacturerID:%d\r\n", SDCardInfo.SD_cid.ManufacturerID); //The manufacturer ID
    printf("Card RCA:%d\r\n", SDCardInfo.RCA);                          //Card relative address
    printf("Card Capacity:%d MB\r\n", (u32)(SDCardInfo.CardCapacity >> 20));
    printf("Card BlockSize:%d\r\n\r\n", SDCardInfo.CardBlockSize);
}

SD卡的初始化主要进行卡识别和卡状态获取,定义SD_Init()如下:

SD_Error SD_Init(void)
{
    u32 clk;
    RCC_ClocksTypeDef bclk;
    u32 targetFreq;
    __IO SD_Error errorstatus = SD_OK;
    u8 clkdiv = 0;
    INTX_DISABLE();


    errorstatus = SD_PowerON();                 //SD Power On
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_InitializeCards();         //Initialize SD Card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_GetCardInfo(&SDCardInfo);  //Get card information
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_SelectDeselect((u32)(SDCardInfo.RCA << 16)); //Select the SD card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_EnableWideBusOperation(1); //4 bit width, if it is an MMC card, you cannot use 4 bit mode
    if ((errorstatus != SD_OK)) {
        if( (SDIO_MULTIMEDIA_CARD == CardType)) {
            if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
                clkdiv = 0; //V1.1/V2.0 card with the maximum setting of 48/4=12Mhz
            }
            else {
                clkdiv = 1; //For other cards such as SDHC, the maximum setting is 48/2=24Mhz
            }
            if(clkdiv != 0) {
                targetFreq = 24000000;

            }
            else {
                targetFreq = 12000000;
            }
            RCC_GetClocksFreq(&bclk);
            clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
            SDIO_ClockSet(clk);

        }
        else {
            __NOP();
        }
    }
    else {
        if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
            clkdiv = 0;
        }
        else {
            clkdiv = 1;
        }
        if(clkdiv != 0) {
            targetFreq = 24000000;

        }
        else {
            targetFreq = 12000000;
        }
        RCC_GetClocksFreq(&bclk);
        clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
        SDIO_ClockSet(clk);

    }
    INTX_ENABLE();
    return errorstatus;
}

1) SD_PowerON()函数用于查询卡的工作电压和时钟控制配置,并返回 SD_Error 类型错误,该函数是整个 SD 识别的关键函数。

2) SD_InitializeCards()函数初始化SD卡,并将其置入就绪状态;

3) SD_GetCardInfo()函数获取SD卡的信息;

4) SD_SelectDeselect()选择SD卡,发送CMD7命令,选择具有相对地址(RCA)的卡作为ADDR;

5) SD_EnableWideBusOperation()配置SDIO数据宽度;

6) SDIO_ClockSet()配置SDIO的时钟频率。

获取SD卡信息及数据的函数定义如下:

void read_sd_card_info(void)
{
    u16 i = 5;
    SD_Error result;
    u32 sd_size;

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(1) {

        result = SD_Init();

        if(result == SD_OK) {
            break;
        }
        printf("SD Card Error!\r\n");

        DELAY_Ms(1);
    }
    show_sdcard_info();
    i = 5;
    while(i--) {
        if(SD_ReadDisk(&vbuf[0], 0, 1) == 0) { 
            printf("UART Sending Data...\r\n");
            printf("SECTOR 0 DATA:\r\n"); 
            for(sd_size = 0; sd_size < 512; sd_size++) {
                printf("%02x ", vbuf[sd_size]);  
            }
            printf("\r\nDATA ENDED\r\n");
            printf("UART Send Data Over!\r\n");
        }
        DELAY_Ms(50);
    }
}

实验演示

在MB-039开发板的SDIO/TF卡槽插入SD卡,运行程序,串口调试助手显示如下:

“使用MM32F3270的SDIO驱动SD卡"

如果SD卡可用,串口调试助手会打印SD卡的信息,包括卡类型、生产ID、RCA、容量和块大小,接着打印扇区0的数据,会连续打印5次该部分内容。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 lib_Samples:

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

工程路径如下:

~MM32F327x_Samples\ LibSamples\SDIO\SDIO_ReadSDCardInfo

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

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

围观 90

音响数据的采集、处理和传输是多媒体技术的重要组成部分。众多的数字音频系统已经进入消费市场,例如数字音频录音带、数字声音处理器。对于设备和生产厂家来说,标准化的信息传输结构可以提高系统的适应性。

I2S(Inter—IC Sound)总线是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。在飞利浦公司的 I2S 标准中,既规定了硬件接口规范,也规定了数字音频数据的格式。

I2S 总线接口有 3 个主要信号,但只能实现数据半双工传输,后来为实现全双工传输有些设备增加了扩展数据引脚。

MM32F3270 系列控制器支持 I2S 总线接口,本章节在接下来会对MM32F3270 I2S进行介绍,并使用MM32F3270和CS4344芯片进行I2S通信来演示播放MP3。

01、I2S 主要特征

1)半双工通信(仅发射机或接收机)

2)主操作或从操作

3)8 位可编程线性预分频器,以达到精确的音频采样频率( 8KHz 到 192KHz)

4)数据格式可以是 16 位、 24 位或 32 位

5)数据包帧固定为 16 位(16 位数据帧)或 32 位(16 位、 24 位、 32 位数据帧)

6)可编程时钟极性(稳定状态)

7)发射模式下的下溢标志(仅从机),接收模式下的上溢标志(主和从机)和接收/发射模式下的帧错误标志(仅从机)

8)用于传输和接收的 32 位寄存器为两个声道分时复用

9)支持 I2S 协议:

– 飞利浦标准
– MSB 对齐标准(左对齐)
– LSB 对齐标准(右对齐)
– PCM 标准(在 16 位信道帧上具有短帧和长帧同步或扩展到 32 位信道帧的 16 位数据帧)

10)数据方向始终是 MSB 优先

11)DMA 传输能力( 32 位宽)

12)可配置输出 MCLK 来驱动外部音频组件,比率固定在 256× FS(其中 FS 为音频采样频率)

02、I2S 总线接口

I2S 与 SPI 共用三个公共管脚:

1)SD:串行数据(映射在 MOSI 管脚上),用于发送或接收两次多路数据通道(仅在半双工模式下)。

2)WS:声道选择(映射在 NSS 引脚上),是 master 中的数据控制信号输出模式和从模式输入。

3)CK:串行时钟(映射在 SCK 引脚上),是主模式下的串行时钟输出以及从机模式下的串行时钟输入。

4)当某些外部设备需要主时钟输入时,可以使用一个附加的管脚输出时钟到音频设备。

5)MCK:驱动时钟(映射在 MISO 引脚上),用于驱动外部音频组件,仅主模式时使用。

03、I2S 数据格式

三线总线处理音频数据的线路必须经过分时复用两个声道:右声道和左声道。但是只有一个 32 位寄存器用于传输或接收。所以由软件依次配置寄存器 TXREG 为每个声道侧的值,或依次读取寄存器 RXREG的数据。总是先发送左声道,然后发送右声道( CHSIDE 对 PCM 协议没有意义)。

数据可采用以下格式发送:

1) 16 位数据打包在 16 位帧中

2) 16 位数据打包在 32 位帧中

3) 24 位数据打包在 32 位帧中

4) 32 位数据打包在 32 位帧中

当使用 32 位帧上发送 16 位数据时,前 16 位(MSB)是有效的位,16 位 LSB 制为 0,无需任何软件操作,通过硬件实现。其他格式相似。

04、通信标准

对于所有数据格式和通信标准,总是先发送最高位( MSB 优先)。I2S 接口支持四种音频标准,可通过配置 SPI_I2S_I2SCFGR 寄存器的 I2SSTD[1:0]和 PCMSYNC 进行切换。

飞利浦标准

对于本标准, WS 信号用于指示正在传输的声道。发射器在 CK 的下降沿锁存数据,接收器并在 CK的上升读取数据。WS 信号也在 CK 的下降沿被锁定。对于这种标准 I2S 格式的信号,无论有多少位有效数据,数据的最高位总是出现在 WS 变化(也就是一帧开始)后的第 2 个 CK 脉冲处。

“飞利浦标准示意图"
飞利浦标准示意图

MSB 对齐标准

对于这个标准,第一个数据在 WS 变化后的第一个沿有效。

“MSB
MSB 对齐标准示意图

LSB 对齐标准

“LSB
LSB 对齐标准示意图

PCM 标准

对于 PCM 标准,不需要使用声道信息。PCM 有两个模式:短帧模式和长帧模式,通过配置SPI_I2S_I2SCFGR 寄存器的 PCMSYNC 位进行切换。在 PCM 模式下,输出信号(WS, SD)在 CK 信号的上升沿进行采样。输入信号(WS, SD)在 CK 下降沿被捕获。注意在主模式下, CK 和 WS 被配置为输出。

“PCM
PCM 标准示意图

05、基于MM32F3270的音频播放实验

CS4344芯片是实现本次实验功能的重要器件之一。CS4344是一种立体声音频数模转换器 (DAC) ,可使用单个 +3.3 V 或 +5 V 电源,仅需要最小的支持电路。该系列线性模拟低通滤波器和自动速度模式检测,当自动选择 2 kHz 和 200 kHz 之间的采样率,使用采样率和主时钟速率方法。

本实验的基本原理是MM32F3270 读取SD卡中的MP3文件,并对其解码得到PCM信号,通过I2S接口将PCM信号传输给CS4344,由CS4344进行DA转换输出模拟信号,再经过TS4871(音频功率放大器)连接到耳机接口,可以接入耳机等音频播放装置。

硬件设计

如图是MB-039的I2S部分,完整原理图可以通过官网下载。

“基于MM32F3270

各个信号引脚对应如下:

“基于MM32F3270

程序设计

根据接口电路配置GPIO初始化

static void I2S3_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    // I2S  MCLK, SD, CK and WS pins configuration
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_6); //I2S WS
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_6);      //I2S CK  I2S_SCK
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_6);     //I2S SD  I2S_DATAOUT  MOSI
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_5);  //I2S MCK I2S_MCLK

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);


    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOC, &GPIO_InitStructure);

    // config as the control I/O for power on or enter standby
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

I2S配置初始化

   static void I2S_Mode_Config(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_AUDIO_FREQ_TypeDef usAudioFreq, SPI_I2S_TRANS_MODE_TypeDef usMode)
{
    I2S_InitTypeDef I2S_InitStructure;

    if ((usMode == I2S_Mode_SlaveTx) && (usMode == I2S_Mode_SlaveRx)) {
        return;
    }
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    SPI_DeInit(SPI3);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_SPI3, ENABLE);
    if (usMode == I2S_Mode_MasterTx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }
    else if (usMode == I2S_Mode_MasterRx) {
        I2S_InitStructure.I2S_Mode = I2S_Mode_MasterRx;
        I2S_InitStructure.I2S_Standard = usStandard;
        I2S_InitStructure.I2S_DataFormat = usWordLen;
        I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
        I2S_InitStructure.I2S_AudioFreq = usAudioFreq;
        I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
        I2S_Init(SPI3, &I2S_InitStructure);
    }

    SPI_DMACmd(SPI3, ENABLE);
    I2S_Cmd(SPI3, ENABLE);
}

(1)I2S_Mode:I2S 模式选择,可选主机发送、主机接收、从机发送以及从机接收模式,它设定SPI_I2S_GCTL寄存器MODE位的值。一般设置 MM32 控制器为主机模式,当播放声音时选择发送模式,当录制声音时选择接收模式。

(2) I2S_Standard:通信标准格式选择,可选 I2S Philips 标准、左对齐标准、右对齐标准、 PCM 短帧标准或 PCM 长帧标准,它设定SPI_I2S_I2SCFGR 寄存器 I2SSTD位和 PCMSYNC位的值。一般设置为 I2S Philips 标准即可。

(3)I2S_DataFormat:数据格式选择,设定有效数据长度和帧长度,可选标准 16bit 格式、扩展16bit(32bit 帧长度) 格式、 24bit 格式和 32bit 格式,它设定 SPI_I2SCFGR 寄存器 DATLEN 位和CHLEN 位的值。对应 16bit 数据长度可选 16bit 或 32bit 帧长度,其他都是 32bit 帧长度。

(4)I2S_MCLKOutput:主时钟输出使能控制,可选使能输出或禁止输出,它设定 SPI_I2SPR 寄存器 MCKOE 位的值。为提高系统性能一般使能主时钟输出。

(5)I2S_AudioFreq:采样频率设置,标准库提供采样采样频率选择,分别为 4KHz、8kHz、 11kHz、12KHz、16kHz、22kHz、32kHz、44kHz、48kHz、96kHz、192kHz 以及默认 2Hz,它设定 SPI_I2S_SPBRG 寄存器的值。

(6)I2S_CPOL:空闲状态的 CK 线电平,可选高电平或低电平,它设定 SPI_I2S_CCTL 寄存器 CPOL位的值。一般设置为低电平即可。

在I2S_StartPlay()函数中调用I2S_Mode_Config()函数,

   void I2S_StartPlay(SPI_I2S_STANDARD_TypeDef usStandard, SPI_I2S_DATAFORMAT_TypeDef usWordLen, SPI_I2S_AUDIO_FREQ_TypeDef usAudioFreq)
{
    // config I2S interface as standard, bit length, frequence ,the Master Tx mode
    I2S_Mode_Config(usStandard, usWordLen, usAudioFreq, I2S_Mode_MasterTx);
    SPI3->GCTL |= 0xF;
}

在PlayMP3FileDemo()函数中调用I2S_StartPlay()函数,并配置传输模式为主机发送I2S_Mode_MasterTx,选择Phillips标准,16位数据长度,采样频率配置为44KHz。

    I2S_StartPlay(I2S_Standard_Phillips, I2S_DataFormat_16b, I2S_AudioFreq_44k);

PlayMP3File()函数是 MP3 播放器的实现函数,定义如下:

   void PlayMP3File(void)
{
    DIR dirs;
    FILINFO finfo;
    FRESULT res;
    static UINT br;

    DELAY_Init();
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    CONSOLE_Init(115200);

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(SD_Init()) {
        printf("SD Card Error!\r\n");
    }
    MX_FATFS_Init();
    f_mount(&SDFatFS, (TCHAR const*)SDPath, 0);

    u8 buff[2] = {0x01, 0x02};
    I2S_TX_DMA_Init(&buff[0], 1);
    DMA_Cmd(DMA2_Channel2, ENABLE);

//  while(1){
    if (f_opendir(&dirs, "") == FR_OK) {              //success to open directory
        while (f_readdir(&dirs, &finfo) == FR_OK) {       //if there is file in this directory
            if (finfo.fattrib & AM_ARC) {
                if(!finfo.fname[0])
                    break;
                printf("\r\n Now Playing:[");
                printf(finfo.fname);
                printf("]\r\n");
                res = f_open(&fsrc, finfo.fname, FA_OPEN_EXISTING | FA_READ);
                SET_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, ENABLE <<DMA_CCR_EN_Pos);

                hMP3Decoder = MP3InitDecoder();

                readPtr = readBuf;
                res = f_read(&fsrc, readBuf, READBUF_SIZE, &br);
                bytesLeft += br;

                buffer_switch = 0;
                while(1) {
                    offset = MP3FindSyncWord(readPtr, bytesLeft); //assume EOF if no sync found
                    if(offset < 0)break;
                    readPtr += offset; //data start point
                    bytesLeft -= offset; //in buffer
                    if(bytesLeft < READBUF_SIZE) {
                        memmove(readBuf, readPtr, bytesLeft);
                        res = f_read(&fsrc, readBuf + bytesLeft, READBUF_SIZE - bytesLeft, &br);
                        if((res) || (br == 0)) break;
                        if(br < READBUF_SIZE - bytesLeft)
                            memset(readBuf + bytesLeft + br, 0, READBUF_SIZE - bytesLeft - br);
                        bytesLeft = READBUF_SIZE;
                        readPtr = readBuf;
                    }
                    MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);

                    if((samprate != mp3FrameInfo.samprate) && (mp3FrameInfo.samprate != 0)) {
//                                               wm8978_CfgAudioIF(I2S_Standard_Phillips, mp3FrameInfo.bitsPerSample, SPI_Mode_Master);
                        //   I2S_StartPlay((SPI_I2S_STANDARD_TypeDef)I2S_Standard_Phillips, (SPI_I2S_DATAFORMAT_TypeDef)mp3FrameInfo.bitsPerSample, (SPI_I2S_AUDIO_FREQ_TypeDef)mp3FrameInfo.samprate);
                        samprate = (SPI_I2S_AUDIO_FREQ_TypeDef) mp3FrameInfo.samprate;
                    }
                    while(1) {
                        if(DMA_GetITStatus(DMA2_IT_TC2) == SET) {
                            DMA_ClearITPendingBit(DMA2_IT_TC2);

                            if(buffer_switch == 0) {
                                Audio_MAL_Play((u32)buffer4, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer3, 0);
                                buffer_switch = 1;
                                break;
                            }
                            else {
                                Audio_MAL_Play((u32)buffer3, BUFF_SIZE);
                                MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, buffer4, 0);
                                buffer_switch = 0;
                                break;
                            }
                        }
                    }

                }
                CLEAR_BIT(SPI3->GCR, SPI_GCR_SPIEN);
                MODIFY_REG(DMA2_Channel2->CCR, DMA_CCR_EN, DISABLE << DMA_CCR_EN_Pos);
                f_close(&fsrc);
                bytesLeft = 0;
            }
        }
    }
    while(1);
//   }

}

MP3文件是经过压缩算法压缩而存在的,为得到 PCM 信号,需要对 MP3 文件进行解码。本实验使用Helix MP3解码器,Helix MP3 解码器的源代码是开源代码,受制于源代码随附文件中描述的许可协议。该算法支持浮点和定点实现,可移植到任意32位定点处理器上运行,提供对 MPEG-1、 MPEG-2 以及 MPEG-2.5 标准的 Layer3 解码,以及支持可变位速率、恒定位速率,以及立体声和单声道音频格式。关于Helix MP3解码器的移植,在本文中不做重点讲述,更多信息可访问网站:

https://datatype.helixcommunity.org/Mp3dec

f_open 函数用于打开文件,如果文件打开失败则直接退出播放。

MP3InitDecoder 函数用于初始化Helix 解码器,分配解码器必须内存空间,如果初始化解码器失败直接退出播放。

f_read 函数从 SD 卡读取 MP3 文件数据,存放在 readBuf缓冲区中, br变量保存实际读取到的数据的字节数。如果读取数据失败则运行 MP3FreeDecoder 函数关闭解码器后退出播放器。

MP3Decode 函数开始对源数据缓冲区中帧数据进行解码,通过函数返回值可判断得到解码状态,如果发生解码错误则执行对应的代码。

读取到文件末尾就退出循环, 此时MP3文件已经完整播放。

实验演示

SD卡中存储有MP3文件,并将SD卡、耳机设备接入MB-039开发板,运行程序,就可以听到音乐播放。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 lib_Samples:

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

工程路径如下:

~MM32F327x_Samples\Demo_app\PlayWave_Demo\SPI_I2S_SDIO_FatFs\MP3_CS4344_Demo

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

下章的题目为《使用MM32F3270 的SDIO 驱动SD卡》讲解通过SDIO外接MicroSD卡的实现。

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

围观 81

灵动微电子全新主流型 MM32F3270 系列 MCU 现已开始批量供货。该系列 MCU 搭载了 Arm® Cortex®-M3 内核,最高主频 120MHz,提供最高 512KB Flash 和 128KB SRAM,并集成了丰富的通信接口、定时器和模拟资源。MM32F3270 系列 MCU 的典型应用包括工业控制、楼宇自动化、消防监控、指纹模块、家电控制、电源管理、打印机、扫描仪、通信转换模块等。

“灵动MM32F3270系列MCU开始批量供货!"

MM32F3270 的主要特点

  • Arm® Cortex®-M3 内核,最高主频 120MHz
  • 高达 512KB Flash和128KB SRAM
  • 外扩存储器接口 FSMC
  • 多达 8 组 UART、3 组 SPI、3 组 I2S、2 组 I2C
  • 1 组 10/100M 以太网控制器
  • 1 组 USB 2.0 FS OTG 控制器和内置 PHY
  • 1 组 CAN 2.0B 控制器
  • 1 组 SDIO接口
  • 2 组高级定时器,每组可输出 4 通道带互补端口的 PWM,支持死区和刹车
  • 2 组 32 位定时器、4 组 16 位定时器
  • 3 组 12-bit 1MSPS SAR ADC,提供多达 21 个外部通道
  • 2 组 12-bit DAC
  • 2 组高速模拟比较器
  • 2.0 – 5.5V 宽压设计,适用于各种电源供电场合
  • 高可靠性:支持高达 ±8000V HBM ESD
  • 提供 -40~85°C 和 -40~105°C 环温选项
  • 引脚兼容 MM32F103 系列

“灵动MM32F3270系列MCU开始批量供货!"

产品供货情况

MM32F3270现已批量供应 LQFP144、LQFP100、LQFP64、LQFP48 和 QFN40 五种可选封装形式,提供 256KB 和 512KB Flash 选项,全系列提供 -40~85°C 和 -40~105°C 产品型号,并配套完整的软件支持和开发板(MB-039)支持,具体选型信息参考数据手册。

有关芯片购买事宜,请洽灵动的销售、官方代理商和方案设计公司。

更多详细信息,请访问 www.mm32mcu.com

MM32F3270 相关技术链接

基于MM32F3270 以太网 Client_Socket使用

点击这里>>>

基于MM32F3270 以太网 Client使用

点击这里>>>

基于MM32F3270 以太网 Server使用

点击这里>>>

基于MM32F3270 以太网 Server_Socket

点击这里>>>

基于MM32F3270 以太网 UDP使用

点击这里>>>

基于MM32F3270 以太网 HTTP使用

点击这里>>>

使用MM32F3270 FSMC驱动TFT-LCD

点击这里>>>

使用MM32F3270 FSMC驱动外部NOR Flash

点击这里>>>

使用MM32F3270 FSMC驱动OLED

点击这里>>>

使用MM32F3270 FSMC驱动SRAM

点击这里>>>

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

围观 23

页面

订阅 RSS - MM32F3270