灵动微课堂 (第241讲)|使用MM32F3270基于Azure RTOS动态内存管理的应用
来源:灵动MM32MCU发布时间:2022-12-222178浏览
询问 AI简 介
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 适用系列型号
系列 | 芯片型号 | 开发板 |
MM32F3270 | MM32F3273G9P | EVB-F3270 |
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/

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=f7ab4ff3afa011ec90a79366a52034faactivetab=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 主页上的“下载”按钮来下载存储库的副本。
下载后的仓库代码目录列表如下:

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

本次移植过程使用Azure RTOS原有的sample_threadx.c文件为例,稍作修改,演示动态内存管理功能。
2
ThreadX 动态内存管理的应用
该章节介绍动态内存管理相关知识,演示程序可在MM32F3273G9P的EVB-F3270上运行。此示例在文件 main_malloc_demo.c 中实现,旨在说明如何在嵌入式多线程环境中使用动态内存管理功能。
2.1 动态内存管理
2.1.1 内存块池
在实时应用程序中,采用快速且确定的方式分配内存始终是一项挑战。考虑到这一点,ThreadX 提供了创建和管理多个固定大小的内存块池的功能。
由于内存块池由固定大小的块组成,因此永远不会出现任何碎片问题。当然,碎片会导致出现本质上不确定的行为。此外,分配和释放固定大小内存块所需的时间与简单的链接列表操作所需的时间相当。另外,还可以在可用列表的开头完成内存块分配和取消分配。这可以提供最快的链接列表处理速度,并且有助于将实际的内存块保存在缓存中。
缺乏灵活性是固定大小内存池的主要缺点。池的块大小必须足够大,才能处理其用户最坏情况下的内存需求。当然,如果对同一个池发出了许多大小不同的内存请求,则可能会浪费内存。一种可能的解决方案是创建多个不同的内存块池,这些池包含不同大小的内存块。
每个内存块池都是一个公用资源。ThreadX 对如何使用池没有任何限制。
2.1.1 创建内存块池
内存块池由应用程序线程在初始化期间或运行时创建。应用程序中内存块池的数量没有限制。
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分配固定大小的内存块UINTtx_block_allocate(
TX_BLOCK_POOL*pool_ptr,
VOID**block_ptr,
ULONGwait_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_POOLmy_pool;
unsignedchar*memory_ptr;
UINTstatus;
/*Allocateamemoryblockfrommy_pool.Assumethatthepoolhas
alreadybeencreatedwithacalltotx_block_pool_create.*/
status=tx_block_allocate(my_pool,(VOID**)memory_ptr,
TX_NO_WAIT);
/*IfstatusequalsTX_SUCCESS,memory_ptrcontainstheaddressof
theallocatedblockofmemory.*/
另请参阅
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”:

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

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

注意:
需要在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演示代码
/*Thisisasmalldemoofthehigh-performanceThreadXkernel.Itincludesexamplesofsix
threadsofdifferentpriorities,usingamessagequeue,semaphore,andaneventflagsgroup.*/
#include"tx_api.h"
#include"delay.h"
#include"led.h"
#include"key.h"
#include"uart.h"
#defineDEMO_STACK_SIZE1024
#defineTHREAD0_PRIORITY1
#defineTHREAD0_PREEMPTION_THRESHOLD1
#defineTHREAD1_PRIORITY2
#defineTHREAD1_PREEMPTION_THRESHOLD2
#defineTHREAD5_PRIORITY4
#defineTHREAD5_PREEMPTION_THRESHOLD4
//#defineTHREAD5_PREEMPTION_THRESHOLD_NEW0
#defineBIT_0((ULONG)0x0000001)
#defineBIT_1((ULONG)0x0000002)
#defineBIT_2((ULONG)0x0000004)
#defineBIT_3((ULONG)0x0000008)
#defineBIT_ALL(BIT_0|BIT_1|BIT_2|BIT_3)
/*DefinetheThreadXobjectcontrolblocks...*/
TX_THREADthread_0;
TX_THREADthread_1;
TX_THREADthread_5;
TX_EVENT_FLAGS_GROUPEventGroup;
TX_BLOCK_POOLMyBlock;
TX_BYTE_POOLMyByte;
/*Definethecountersusedinthedemoapplication...*/
ULONGthread_0_counter;
ULONGthread_1_counter;
ULONGthread_5_counter;
/*Definethethreadstacks.*/
UCHARthread_0_stack[DEMO_STACK_SIZE];
UCHARthread_1_stack[DEMO_STACK_SIZE];
UCHARthread_5_stack[DEMO_STACK_SIZE];
/*Definethreadprototypes.*/
voidthread_0_entry(ULONGthread_input);
voidthread_1_entry(ULONGthread_input);
voidthread_5_entry(ULONGthread_input);
volatileunsignedintbootloop;
uint32_tMyBlockBuf[1024];
uint32_tMyByteBuf[1024];
voidSystem_Init(void);
voidAppThreadCreate(void);
voidAppModuleCreate(void);
/*Definemainentrypoint.*/
intmain()
{
System_Init();
/*EntertheThreadXkernel.*/
tx_kernel_enter();
}
/*Definewhattheinitialsystemlookslike.*/
voidtx_application_define(void*first_unused_memory)
{
AppThreadCreate();
AppModuleCreate();
}
voidSystem_Init(void)
{
DELAY_Init();//cannotusesystick
LED_Init();
KEY_Init();
CONSOLE_Init(115200);
printf("!!!Start!!!\r\n");
}
voidAppThreadCreate(void)
{
/*Createthread0.*/
tx_thread_create(
thread_0,
"thread0",
thread_0_entry,
0,
thread_0_stack,
DEMO_STACK_SIZE,
THREAD0_PRIORITY,
THREAD0_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
/*Createthread1.*/
tx_thread_create(
thread_1,
"thread1",
thread_1_entry,
0,
thread_1_stack,
DEMO_STACK_SIZE,
THREAD1_PRIORITY,
THREAD1_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
/*Createthread5.*/
tx_thread_create(
thread_5,
"thread5",
thread_5_entry,
5,
thread_5_stack,
DEMO_STACK_SIZE,
THREAD5_PRIORITY,
THREAD5_PREEMPTION_THRESHOLD,
TX_NO_TIME_SLICE,
TX_AUTO_START);
}
voidAppModuleCreate(void)
{
/*Createsamemoryblockforapplyingforafixed-sizememoryunit*/
tx_block_pool_create(MyBlock,
"MyBlock",
4,/*Thesizeofthememoryunit*/
(VOID*)MyBlockBuf,/*Memoryblockaddress,ensure4-bytealignment*/
sizeof(MyBlockBuf));/*Memoryblocksize,inbytes*/
/*Createamemorypool*/
tx_byte_pool_create(MyByte,
"MyByte",
(VOID*)MyByteBuf,/*Memorypooladdress,ensure4-bytealignment*/
sizeof(MyByteBuf));/*Memorypoolsize*/
/*Createaneventflaggroup*/
tx_event_flags_create(EventGroup,"EventGroupName");
}
/*Definethetestthreads.*/
voidthread_0_entry(ULONGthread_input)
{
/*ThisthreadsimplycontrolsLEDflashingtoindicatethatthesystemisrunning*/
while(1)
{
/*Incrementthethreadcounter.*/
thread_0_counter++;
LED1_TOGGLE();
/*Sleepfor300ticks.*/
tx_thread_sleep(300);
}
}
voidthread_1_entry(ULONGthread_input)
{
UCHAR*BlockPtr;
UCHAR*BytePtr;
ULONGavailable;
ULONGactual_events;
UINTstatus;
while(1)
{
/*Incrementthethreadcounter.*/
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)
{
caseBIT_0:
/*Applyformemoryblocksof4byteseachtime*/
status=tx_block_allocate(MyBlock,
(VOID**)BlockPtr,
TX_NO_WAIT);
if(status==TX_SUCCESS)
{
printf("Succeededinapplyingforamemoryblock.\r\n");
tx_block_pool_info_get(MyBlock,
TX_NULL,
available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("NumberofblocksavailableinMyBlock=%d\r\n",(int)available);
}
break;
caseBIT_1:
status=tx_block_release(BlockPtr);
if(status==TX_SUCCESS)
{
printf("Thememoryblockwasreleasedsuccessfully.\r\n");
tx_block_pool_info_get(MyBlock,
TX_NULL,
available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("NumberofblocksavailableinMyBlock=%d\r\n",(int)available);
}
break;
caseBIT_2:
/*Applyforamemorybytepool,specifying100bytes*/
status=tx_byte_allocate(MyByte,
(VOID**)BytePtr,
100,
TX_NO_WAIT);
if(status==TX_SUCCESS)
{
printf("Succeededinapplyingforthememorybytepool.\r\n");
tx_byte_pool_info_get(MyByte,
TX_NULL,
available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("TheavailableMyBytesize=%dbytes.\r\n",(int)available);
}
break;
caseBIT_3:
status=tx_byte_release(BytePtr);
if(status==TX_SUCCESS)
{
printf("Thememorybytepoolwasreleasedsuccessfully.\r\n");
tx_byte_pool_info_get(MyByte,
TX_NULL,
available,
TX_NULL,
TX_NULL,
TX_NULL,
TX_NULL);
printf("TheavailableMyBytesize=%dbytes.\r\n",(int)available);
}
break;
default:
break;
}
}
}
}
voidthread_5_entry(ULONGthread_input)
{
UCHARt=0;
/*Thisthreadsimplyscanbuttonispressedtosendthesemaphore.*/
while(1)
{
/*Incrementthethreadcounter.*/
thread_5_counter++;
t=KEY_Scan(0);
if(KEY1_PRES==t)
{
//LED1_TOGGLE();
tx_event_flags_set(EventGroup,BIT_0,TX_OR);
}
elseif(KEY2_PRES==t)
{
//LED2_TOGGLE();
tx_event_flags_set(EventGroup,BIT_1,TX_OR);
}
elseif(KEY3_PRES==t)
{
//LED3_TOGGLE();
tx_event_flags_set(EventGroup,BIT_2,TX_OR);
}
elseif(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键,串口打印信息:

创建事件标志组初始化为零,用于任务同步。任务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广泛的应用场景。
往期精彩
第240讲|使用MM32F3270基于Azure RTOS信号量的应用
第232讲 | 使用MM32L0130 SLCD驱动LCD显示
第229讲 | 基于MM32F0140的UDS Bootloader学习笔记
第224讲 | MM32F0140 FlexCAN一致性测试 (2)
第223讲 | MM32F0140 FlexCAN一致性测试 (1)
第222讲 | MM32F0140学习笔记——独立看门狗(IWDG)
第221讲 | 使用MM32F3270基于Azure RTOS抢占任务的应用
第220讲 | 使用MM32F3270基于Azure RTOS事件标志组的应用
第219讲 | 使用MM32F3270基于Azure RTOS消息队列的应用
第218讲 | 使用MM32F3270基于Azure RTOS (ThreadX)的多任务调度
第217讲 | 使用MM32F3270基于Azure RTOS (ThreadX) 的移植
第215讲 | MM32F0140学习笔记——窗口看门狗(WWDG)
第213讲 | MM32F0140学习笔记——FlexCAN 控制器局域网
第212讲 | 工程师笔记——MM32F0040使用总结 (2)
第211讲 | 工程师笔记——MM32F0040使用总结 (1)
第207讲 | MM32F0140学习笔记——时钟系统RCC
第200讲 | 使用MM32F0270 定时器DMA方式输出PWM
关于灵动
上海灵动微电子股份有限公司成立于 2011 年,是中国本土领先的通用 32 位 MCU 产品及解决方案供应商。公司基于 Arm Cortex-M 系列内核开发的 MM32 MCU 产品拥有 F/L/A/SPIN/W 五大系列,目前已量产 200多款型号,累计交付超3亿颗,每年都有近亿台配备了灵动 MM32MCU 的优秀产品交付到客户手中,在本土通用 32 位 MCU 公司中位居前列。

灵动客户涵盖智能工业、汽车电子、通信基建、医疗健康、智慧家电、物联网、个人设备、手机和电脑等应用领域。灵动是中国为数不多的同时获得了 Arm-KEIL、IAR、SEGGER 官方支持的本土 MCU 公司,并建立了独立、完整的通用 MCU 生态体系。灵动始终秉承着“诚信、承诺、创新、合作”的精神,为客户提供从硬件芯片到软件算法、从参考方案到系统设计的全方位支持。

灵动股份
微信号:MindMotion-MMCU


长按识别二维码关注我们

MORE
官网:www.mm32mcu.com
微信公众号:灵动MM32MCU
灵动MM32MCU技术论坛:
bbs.21ic.com/iclist-696-1.html
新闻来源:灵动MM32MCU,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
