尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CMSIS-5深度解析:从架构分层到工程落地的完整指南

CMSIS-5深度解析:从架构分层到工程落地的完整指南 写这篇文章之前我先说个背景。做嵌入式这几年被问得最多的一个问题是“CMSIS到底是什么我用STM32Cube生成代码里面不就已经有CMSIS了吗为什么还要单独去研究它”答案其实就藏在CMSIS三个字母里Cortex Microcontroller Software Interface StandardCortex微控制器软件接口标准。它不只是几个头文件也不只是某个厂商的SDK而是一整套规范了 ARM Cortex-M 内核、外设、DSP、神经网络部署等环节的软件框架。这篇文章我基于CMSIS-5源码以实际工程的视角做一次深度拆解架构怎么分层、模块怎么协作、工程上怎么治理、以及拿到手之后怎么落地到你自己的项目里。适合正在做ARM平台开发、需要接DSP库或神经网络推理、又或者从裸机过渡到RTOS的工程师参考也适合准备嵌入式的朋友拿来串知识体系。1. 先看全局CMSIS-5到底在解决什么问题1.1 CMSIS家族的演进脉络CMSIS不是从5.0才有的。最早在ARM Cortex-M3刚普及的年代各厂商提供的底层代码各有各的风格导致同一个功能换一颗芯片就要重写一遍驱动非常痛苦。ARM最初推出CMSIS 1.0目的是在“内核”“外设”“中间件”“应用”这几层之间加一道标准接口让上层代码具备可移植性。之后CMSIS经历了从1.0到2.0再到3.0的演进3.x时代已经形成了大家熟悉的Core、DSP、RTOS等模块雏形。真正发生架构级变化的是CMSIS-4它把Device-related的东西进一步划分让厂商可以直接围绕CMSIS-Core扩展补充。进入CMSIS-5之后架构基本定型了Core、DSP、NN、RTOS、Driver、Pack、SVD、DAP等模块各司其职形成了一套完整的、可扩展的嵌入式软件栈。我在实际项目中经常遇到一个误区初学者以为CMSIS是ST或NXP做出来的库。实际上ST、NXP、Nuvoton、Renesas这些厂商都有自己的HAL或驱动库但它们的底层几乎全部建立在CMSIS标准之上。你打开STM32Cube生成工程里面出现的core_cm4.h、system_stm32f4xx.c本质就是CMSIS-Core的具体实现。1.2 CMSIS-5的模块全景CMSIS-5仓库GitHub上的ARM-software/CMSIS_5目录结构很规律顶层按功能划分成几个大目录CMSIS/CoreCMSIS-Core (Cortex-M)提供内核寄存器定义、系统初始化、NVIC、SysTick、MPU、FPU等接口。CMSIS/DAPCMSIS-DAP调试探针固件实现DAPDebug Access Port协议。CMSIS/DriverCMSIS-Driver统一的、面向外设的驱动标准API。CMSIS/DSPCMSIS-DSP优化的DSP函数库。CMSIS/NNCMSIS-NN针对Cortex-M优化的神经网络推理原语。CMSIS/RTOSCMSIS-RTOS API老版本1.x和新的RTOS2接口都在这。CMSIS/PackCMSIS-Pack软件包格式、用CMSIS-Pack工具链打包和分发。CMSIS/SVDSystem View Description描述外设寄存器映射的XML Schema。CMSIS/Utilities辅助脚本和工具。在源码层面CMSIS-5不是一个“可编译的完整固件”而是一个组件集合。每个组件可以被单独引入也可以按需裁剪。这个设计很关键因为嵌入式项目非常讲究资源占用如果不管三七二十一全部添加进去Flash和RAM都会浪费不少。1.3 三层架构的设计思想CMSIS-5的架构建立在经典的分层思想上往细分可以概括为三层第一层内核抽象层。CMSIS-Core定义了从__NVIC_EnableIRQ、SysTick_Config到__DMB、__WFI等内核操作API并将内核对开发者“隐藏”起来使得上层代码不依赖具体芯片寄存器地址。第二层处理器/外设标准化层。包括CMSIS-Driver的统一外设API、CMSIS-SVD的外设描述、以及各芯片厂商基于Core实现的具体设备头文件。第三层应用与中间件层。CMSIS-DSP直接输出计算能力CMSIS-NN提供AI推理能力CMSIS-RTOS2作为RTOS统一接口应用代码在这之上编写就不用担心底层芯片换掉了。之所以这样分层最核心的原因是可移植性和可复用性。举个例子你用CMSIS-DSP库做音频FFT如果代码只依赖arm_cfft_f32这种标准API那么换一款同样带FPU的M4/M7芯片只需要重新编译一遍就可以算法代码零改动。这比直接在寄存器层面操作DSP指令要省心太多了。2. 模块分层深度源码解析2.1 CMSIS-Core嵌入式开发者绕不开的“地基”CMSIS-Core是CMSIS-5所有模块中最基础、最核心的模块。通俗理解它相当于把“芯片的手册”翻译成一套统一的“C语言API”让程序可以跨芯片复用。在源码中CMSIS-Core的典型文件结构包含core_cm0.h、core_cm3.h、core_cm4.h、core_cm7.h、core_cm23.h、core_cm33.h、core_cm35p.h、core_cm55.h等这些是不同Cortex-M内核的设备访问层头文件。cmsis_gcc.h、cmsis_armcc.h、cmsis_armclang.h、cmsis_iccarm.h等这些是不同编译器下的内联函数实现和编译适配。core_cmFunc.h内核特殊寄存器操作函数如__get_PRIMASK、__set_CONTROL等。core_cmInstr.h内核指令封装如__NOP、__WFI、__REV等。我比较推荐大家实际打开core_cm4.h看一眼。乍一看几百行但真正核心的就是三块一是中断与异常相关的定义包括IRQn_Type和NVIC的操作二是特殊寄存器操作的内联函数和intrinsic三是对MPU、FPU、SysTick等结构体的定义。看懂这三个部分你基本就掌握了Cortex-M内核访问的精髓。比如NVIC_EnableIRQ在源码里就是一个函数__STATIC_INLINE void NVIC_EnableIRQ(IRQn_Type IRQn) { if ((int32_t)(IRQn) 0) { NVIC-ISER[(((uint32_t)IRQn) 5UL)] (uint32_t)(1UL (((uint32_t)IRQn) 0x1FUL)); } }逻辑很清楚它把中断号按32位分组对应到ISER寄存器组。这个函数在CMSIS-5中同时适配AC5、AC6、GCC、IAR是因为在头文件里做了大量的预处理判断和编译器差异封装。如果你经常做跨编译器项目建议重点研究一下cmsis_gcc.h与cmsis_armclang.h的区别AC5和AC6的内联汇编语法完全不同CMSIS把这两个路径都抹平了。2.2 CMSIS-DSP与CMSIS-NN把算力吃透CMSIS-DSP是CMSIS-5里最具“含金量”的模块之一。它提供了超过60个函数的优化实现涵盖基础数学加减乘除、绝对值、平方根、矩阵运算、复数运算、快速傅里叶变换FFT、滤波FIR、IIR、Biquad、统计均值、方差、RMS、插值、PID控制等。最值得关注的是CMSIS-DSP针对Cortex-M4/M7等带DSP指令和FPU内核做了专门的优化。以FFT为例如果你在M4上自己写一个浮点FFT可能需要几千个周期使用CMSIS-DSP的arm_cfft_f32几百个周期就能完成同样长度的变换。这正是“原生指令集优化”与“通用C代码”之间的巨大差别。源码中这些优化大多通过内联汇编或intrinsic实现。举个典型的例子CMSIS-DSP里的乘加运算在很多地方会用__SMLAD这类DSP指令通过一条指令完成两次乘法和一次加法。面对大量矩阵或卷积运算时这种优化能直接带来数倍性能提升。CMSIS-NN则在DSP之外更进一步专门为Cortex-M系列实现了一组神经网络原语。它把卷积、池化、全连接、激活函数等算子封装成固定APIconv、pool、softmax等并对int8量化推理做了深度优化。在Cortex-M7或M33这种带DSP/SIMD指令的内核上CMSIS-NN能让原本跑不动的轻量模型跑起来这也是目前很多MCU端AI推理工具链底层的理论依据。如果你要在MCU上做“宠物检测AI模型”或其它视觉识别应用CMSIS-NN是值得优先考虑的底层算子库。不过要提醒CMSIS-NN不能直接加载一个训练好的模型它更像是一组“计算积木”你还需要自己搞定模型解析、内存规划、数据排列与量化参数转换。部署时我通常建议用TensorFlow Lite for Microcontrollers或类似框架作为上层底层算子走CMSIS-NN这样各司其职、开发效率最高。2.3 CMSIS-RTOS2统一RTOS接口的妙处CMSIS-RTOS是嵌入式圈子里讨论度很高的模块因为RTOS是几乎每个中大型嵌入式项目都绕不开的话题。CMSIS-RTOS2定义了一套标准API包括线程创建、消息队列、信号量、互斥量、事件标志、定时器等。它的设计意图很直接你写的应用代码调用osThreadNew、osMessageQueuePut这些API底层跑的是FreeRTOS、RTX5还是其它RTOS应用层不需要关心。源码里RTOS2相关的核心文件是cmsis_os2.h它只是API声明不包含实现。真正的实现由RTOS厂商或第三方提供比如Keil RTX5完全实现了CMSIS-RTOS2FreeRTOS也提供了cmsis_os2.c适配层。这个设计给工程带来的好处非常明显。假设你之前基于FreeRTOS开发后来因为某些原因要切到RTX5如果上层代码全部用CMSIS-RTOS2的API编写切换的时候只需要改底层配置和链接的库应用代码基本可以原样保留。这在长期维护的产品里能省下大量的适配成本。当然直接使用RTOS原生API也有它的价值比如FreeRTOS的xTaskCreate和vTaskDelay有大量网上资料和社区经验排错方便。但如果你的产品需要长期兼容多平台CMSIS-RTOS2这条“中间路径”值得认真考虑。2.4 CMSIS-Driver与CMSIS-Pack被忽视却很重要的部分CMSIS-Driver和CMSIS-Pack在实际项目中存在感不如Core和DSP那么强但它们在“标准驱动接口”与“软件分发”两个维度上意义重大。CMSIS-Driver定义了一组通用的外设API比如CAN_Initialize、SPI_Transmit、USART_Send等。它的价值在于当你需要一个外部组件比如TCP/IP协议栈访问串口、以太网时驱动接口几乎是固定不变的。你切换到不同芯片只需要替换驱动实现上层协议栈的代码不需要大改。虽然很多厂商并没有完整实现CMSIS-Driver但在一些中间件开发、BSP抽象设计时它仍然是一份很好的参考标准。CMSIS-Pack则是ARM推行的一种软件打包格式。简单说一个.pack文件把设备头文件、驱动库、目标配置、示例工程、存放位置等信息都打包起来让IDE能够自动识别、安装、管理。Keil MDK的Pack Installer就是典型应用。从工程治理角度看CMSIS-Pack把“软件的版本”“芯片支持”和“项目依赖”统一管理起来避免把整个SDK全部塞进代码仓库造成混乱。SVDSystem View Description通常不太被应用工程师关注但它对调试工具、代码生成工具的价值很大。SVD文件以XML形式描述了一颗芯片全部外设寄存器的地址、位域和含义。调试器拿到SVD之后就能在调试器里以图形化方式显示各个外设寄存器的名称和状态比单纯看内存十六进制高效得多。基于SVD还可以做外设初始化代码的自动生成。3. 工程治理一个开源项目的“自我修养”3.1 目录结构与命名规范CMSIS-5能够长期稳定维护除了ARM官方投入之外工程治理层面的规范也非常值得学习。先看目录。CMSIS-5仓库顶层用CMSIS/作为唯一大目录下面按模块继续划分。这种结构的好处是每一层职责极其清晰你要用DSP库直接进入CMSIS/DSP/要看Core源码直接进入CMSIS/Core/。这比那种把所有头文件堆在一个include目录里的项目要友好得多。命名规范上CMSIS-5几乎到处都体现了“统一前缀”思想函数名大多以arm_开头DSP和NN库头文件用core_*.h、cmsis_*.h类型定义用__STATIC_INLINE、IRQn_Type这类一致命名。这样带来的直接好处是看代码时凭名称就能判断所属模块和用途范围排查问题的时候省很多事。我自己在团队里推行代码规范时一直把CMSIS当作正面例子统一前缀、统一文件命名、统一注释风格这三条如果做不好代码规模一上来就会变成灾难。3.2 编码规范与文档体系打开CMSIS-5源码会发现一个明显特点多平台编译器适配代码被大量#if defined(__GNUC__)、#if defined(__ICCARM__)等预处理指令包裹。它的边界非常清晰编译器相关逻辑只出现在cmsis_gcc.h、cmsis_armcc.h等独立文件中核心功能头文件不掺入与平台强相关的代码。这种“平台适配隔离”的思路值得所有需要跨IDE、跨编译器开发的团队借鉴。文档方面CMSIS-5的每个模块都配有专门的Doxygen文档。函数注释严格遵循Doxygen格式模块说明页面、接口列表、使用示例齐备。这正是嵌入式开源项目该有的样子代码本身是主体文档用于解释设计意图和使用方法。实际集成时你不需要去网上搜教程直接看Documentation/Doxygen生成的HTML文档即可。3.3 版本管理与发布策略CMSIS-5使用语义化版本管理主版本号、次版本号、修订号各有含义。一般革新性变化会体现在主版本号上功能增强在次版本号上bug修复和兼容性修复在修订号上。这一点读代码时也要注意由于CMSIS涉及的工具链种类多API演进时需要多年兼容性维护策略你不能指望每个版本之间保持完全二进制兼容但尽量保证源码级兼容是ARM一直在做的事。实际开发中如果你的工程要锁定某一个CMSIS版本最稳妥的做法是不要从发行版仓库上随意拉取最新commit而是固定使用某个tag比如v5.9.0并将源码导入自己的项目仓库做一次快照。这样可以避免CMSIS上游更新意外引入的问题影响你的产品。3.4 许可证与合规性CMSIS-5采用Apache License 2.0。这意味着你可以自由使用、修改、分发但需要保留版权声明、修改说明并注意专利授权条款。对于商用嵌入式项目来说Apache 2.0许可总体上比较友好没有强制开源或病毒式传染的要求。不过如果你要直接把CMSIS源代码作为产品一部分进行二进制分发还是建议仔细阅读许可证原文并在产品文档中附上对应的Notice说明。这里有个容易被忽略的细节CMSIS-NN中的部分算子其算法参考了学术界论文并做了实现优化即使协议授权你也要对专利风险保持敏感性。一般消费类产品上问题不大但对技术壁垒要求极高的领域建议公司法务评估后再决定。4. 嵌入式项目选型落地指南4.1 先回答三个问题再决定要不要用在实际项目里引入CMSIS-5之前建议先问自己三个问题第一你的目标芯片是什么内核如果是Cortex-M0/M0那你可能用不到DSP库的SIMD能力如果是M4/M7/M33/M55那么DSP库和NN库有比较明显的性能优势。第二你目前使用的是不是某个厂商IDE比如用Keil MDK或者IAR这些IDE早就内置了对应版本的CMSIS你直接用就好很少需要自己手动下载CMSIS-5源码。第三你的项目需要跨芯片复用吗如果产品线长期限定在单一厂商的单一芯片那么厂商SDK自带的CMSIS版本完全够用如果要做平台化的、可移植的模块那直接维护一份独立的CMSIS-5依赖会更靠谱。之所以强调这三点是因为CMSIS-5的引入方式决定了你后续维护成本。很多项目根本不需要手动“引入CMSIS”因为厂商工具链已经把它处理好了而另一些项目则非常需要“引入CMSIS”比如你要跑CMSIS-DSP或CMSIS-NN但厂商SDK里的版本太旧或没带。4.2 拿到源码后怎么集成到项目如果你的项目确实需要自己维护CMSIS-5源码推荐用Git submodule或者直接vendor一份源码到工程目录里。这里我以vendor形式为例说一下通常的集成步骤从GitHub的ARM-software/CMSIS_5仓库下载指定release比如v5.9.0。在工程里建立Drivers/CMSIS/目录将需要的模块复制进去。通常只需要Core、DSP、NN、RTOS这几个。如果使用Keil MDK可以借助RTE方式添加CMSIS组件如果使用Makefile或CMake需要手动添加头文件路径和源文件路径。对DSP库需要把Source目录下对应编译文件加入工程或者直接使用预编译lib。对NN库建议从Source目录按需添加算子源文件避免全量编译体积过大。一个关键点是DSP库的最佳实践不是把整个Source目录全加进去而是只添加你真正需要用到的函数对应的源文件。比如只用FFT那么可以把TransformFunctions/下的相关.c文件加进去再配合CommonTables/里的相关查表文件。这样编译出来的二进制会更小链接时也不会有那么多无用符号。4.3 DSP库实测一个FFT例程从零跑通这里我分享一个最常见的落地场景在Cortex-M4上使用CMSIS-DSP做FFT。首先保证工程中加入了CMSIS-DSP的Include路径。然后写一个最简单的测试#include arm_math.h #define FFT_LEN 1024 static float32_t input[FFT_LEN * 2]; static float32_t output[FFT_LEN]; void fft_demo(void) { arm_cfft_instance_f32 s; arm_cfft_init_f32(s, FFT_LEN); // 填充输入数据实部在偶数下标虚部在奇数下标 for (int i 0; i FFT_LEN; i) { input[2 * i] arm_sin_f32(2.0f * PI * i / FFT_LEN); input[2 * i 1] 0.0f; } // 执行正向FFT arm_cfft_f32(s, input, 0, 1); // 计算幅值 arm_cmplx_mag_f32(input, output, FFT_LEN); }这段代码在带FPU的M4上很常见。但很多人在这一步踩坑如果MDK工程没有使能FPU或没有定义ARM_MATH_CM4这类宏DSP库可能退回到纯软件运算性能会差很多。所以集成DSP库时你一定要确认项目的浮点单元是开启的并在编译选项里定义正确的宏比如ARM_MATH_CM4、ARM_MATH_CM7或针对M33的ARM_MATH_CM33。另外CMSIS-DSP源码中大量使用了#include arm_math.h而这个头文件内部会根据__FPU_USED、ARM_MATH_CM4等宏决定启用哪些优化分支。如果宏定义不对编译时看起来没错运行结果也可能完全正常但性能和代码尺寸就不是最优的了。4.4 NN库部署要点CMSIS-NN部署比DSP库稍微复杂一些它的核心数据结构围绕int8量化展开。以卷积为例你需要准备好输入数据按NHWC布局排列即按行、按通道、按批次顺序。权重数据通常为int8量化后的一维数组。偏置数据通常为int32。量化参数包括输入scale、输出scale、zero point等。一个典型操作流程是arm_convolve_s8这样的函数。在调用前通常需要先用arm_convolve_wrapper_s8之类的接口或直接按具体场景选择arm_convolve_1x1_s8_fast、arm_convolve_3x3_s8等特化版本。NN库之所以拆分这么细是为了让不同尺寸、不同步长的卷积都能找到最合适的算法规格从而尽可能利用DSP指令和Cache局部性。实操中我建议先用一份已经成熟的开源模型做验证比如用TensorFlow Lite for Microcontrollers的示例模型然后把默认的算子kernel替换成CMSIS-NN。替换方式一般是在TFLM的构建配置里启用CMSIS-NN优化或者直接调用CMSIS-NN API自行实现推理算子。如果从零开始手写需要花的时间会成倍增加。量化这一步尤其容易出问题单独每个算子的数值计算正确但组合起来精度下降明显。通常的解决办法是校准数据集用真实输入统计每个中间层的动态范围再重新计算量化参数。不要简单照搬训练时的量化配置。4.5 从CMSIS-5到CMSIS-6的迁移思路目前CMSIS-5仍是大量项目和IDE的默认依赖但CMSIS-6已经逐步推进。CMSIS-6把原来CMSIS-5里的CMSIS-NN单独抽离出去独立发布和迭代同时对Core接口继续做精简把一些和具体编译工具链强相关的内容收拢到统一层。迁移思路重点是三点第一先看自身代码对CMSIS-Core头文件的依赖程度。如果你的代码大量直接使用core_cm4.h中的寄存器操作从CMSIS-5到6需要重新梳理头文件包含关系。第二CMSIS-DSP的API在核心用户接口层面基本保持一致迁移成本相对低。第三CMSIS-NN的独立化意味着如果你之前使用的是CMSIS-5仓库内的NN库升级到CMSIS-6时需要切换到单独的CMSIS-NN仓库并重新配置库路径。如果当前项目稳定运行我一般不太建议单纯为了新版本而迁移。CMSIS-5的生命周期足够长很多商用产品都在上面跑了好多年。只有当你有明确的新功能需求比如支持新的Cortex-M系列芯片、需要更新的NN算子性能优化、工具链升级要求时再规划迁移。5. 常见问题与排查技巧实录5.1 编译层面的坑问题一重复定义core_cm4.h或system头文件。这是手工添加CMSIS源码时最典型的错误。因为厂商SDK里可能已经自带了一份CMSIS你自己又从GitHub拉了一份导致同一份符号被重复定义。解决办法是去掉项目工程中多余的头文件引用只保留一份CMSIS源码。建议定下明确规则如果使用厂商IDE就用厂商自带的如果使用CMake/Makefile则统一以工程目录下的CMSIS版本为准。问题二编译器版本不兼容。CMSIS-5对ARM Compiler 5和ARM Compiler 6都做了适配但你在使用GCC或IAR时仍然需要留意CMSIS头文件对编译器宏的定义。遇到莫名奇妙的编译错误比如某个intrinsic函数找不到优先检查你使用的编译器版本是否太老或者CMSIS版本是否太新。问题三DSP库使用错误的数学库支持。很多人在M4上跑DSP库算法时发现arm_sin_f32这类函数编译不过原因是工程没有定义对应的DSP库宏或者是库源文件没有正确加入。解决方法是检查arm_math.h中关于ARM_MATH_CM4、__FPU_USED的配置再确保链接时把对应的.c或.lib路径加对了。5.2 性能层面的坑问题一FPU未开启浮点性能差一个数量级。DSP库的很多优化是建立在FPU基础之上的。如果工程没有在启动代码或编译器选项中开启FPU比如M4/M7上的-mfpufpv4-sp-d16CMSIS-DSP会退化为软件模拟浮点速度会非常慢。第一次跑FFT之前先确认__FPU_USED宏已经定义。问题二分页/对齐问题导致NN计算异常。CMSIS-NN对输入/权重数据的地址对齐要求很高通常要求4字节甚至16字节对齐。如果你的内存分配没有采用alignas(16)这类方式在部分内核上可能会产生奇怪的性能下降甚至计算结果错误。排查时先打印关键数组的地址确认是否按16字节对齐。问题三缓存一致性问题。如果你的平台是Cortex-M7带D-CacheDMA和外设访问的共享数据要特别小心缓冲区和Cache一致性问题。CMSIS提供了SCB_InvalidateDCache_by_Addr这类API在DMA发送前、接收后适时调用。很多人在使用CMSIS-DSP做ADC数据采集时发现数据波形不对多半是缓存没有刷干净导致的。5.3 工程集成层面的坑问题一CMSIS-Pack安装失败。使用Keil Pack Installer安装或更新CMSIS-Pack时常见问题是网络原因导致下载中断。解决办法是换用本地方式下载pack文件后双击导入或通过Pack Installer的File Import功能手动导入。问题二厂商库与CMSIS版本功能重叠。STM32 HAL库和CMSIS-Driver在部分功能上有重叠比如串口初始化HAL有HAL_UART_InitCMSIS-Driver有ARM_USART_Initialize。两者并存时要注意不要重复初始化否则可能会引起外设状态冲突。建议一个项目明确主用一套API另一种只做底层适配。问题三特殊中断处理方式不同。CMSIS-Core的函数如NVIC_SetVector可以动态修改中断向量表这在做Bootloader跳转App或运行时修复向量时很常用。但如果你使用了带RTOS的工程中断向量表通常已经被RTOS接管或占用需要配合__disable_irq等操作做好临界区保护。实际调这类问题我的经验是先分清楚“是CMSIS本身的问题”还是“是自己工程配置的问题”不要一出问题就怀疑CMSIS源码有问题。CMSIS在ARM生态里已经过海量产品验证更常见的情况是你对它的理解或工程配置少了一步。最后再分享一个小技巧学习CMSIS-5最好的方式不是在网上看各种二手教程而是直接打开源码和Doxygen文档。你可以在CMSIS/DSP/Examples里找到许多可以直接运行的示例工程在CMSIS/NN/Examples里找到神经网络示例。把它们编译、跑通、改参数、看汇编比读十篇综述都有用。像我当年第一次在STM32F407上点开arm_fft_bin_example把FFT的数据打印出来和Matlab对比一致的那一刻对整个CMSIS的理解才真正落地。希望这篇文章能帮你少走一些弯路。
返回列表