ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ARM MCU边缘AI静态评测:从编译器到内存布局的深度拆解

ARM MCU边缘AI静态评测:从编译器到内存布局的深度拆解 1. 这不是一次常规代码审查为什么 ML-KWS-for-MCU 的静态评测值得花三天拆解ARM 架构在边缘 AI 场景里早已不是“备选方案”而是事实上的工业级标准。当你看到“ML‑KWS‑for‑MCU”这个项目名别急着 clone、make、flash——它背后藏着的不是一段能跑通的 demo而是一套被反复锤炼过的嵌入式机器学习工程范式。我去年在给某国产语音模组做低功耗唤醒词识别升级时第一轮就拿它当 baseline结果发现光是头文件依赖图就画了四页 A3 纸CMSIS-NN 调用链里嵌了三层宏展开而 main.c 里那个看似简单的kws_run()函数实际触发了 7 个编译期断言、3 类内存对齐校验、2 套量化参数校验逻辑。这不是炫技是 MCU 上跑 AI 必须付出的代价。所谓“源码静态评测”绝非 grep cloc 的简单统计。它要回答三个硬问题第一这段代码在 Cortex-M4F 上真实占用多少 Cycle第二它的内存布局是否能在 256KB Flash 64KB SRAM 的典型资源约束下不越界第三当编译器从 ARM Compiler 5 升级到 Armclang 6.18哪些宏定义会失效、哪些 inline assembly 会崩、哪些 CMSIS-DSP 函数调用路径会断裂这些恰恰是热词里反复出现的“arm交叉编译”“arm compiler 5.06 update 7”“stm32cubemx 编译后无 arm 文件夹”等真实故障的根源。你不需要懂汇编指令集全貌但必须清楚__attribute__((aligned(16)))在不同编译器下的行为差异你不必手写 NEON 指令但得知道arm_math.h里arm_fir_f32()和arm_fir_q15()的栈空间消耗为何差出 3 倍。这篇解析就是把这套隐性知识显性化的过程——它不教你如何写第一个 KWS 模型而是告诉你当你的模型在 STM32H743 上跑出 92% 准确率却死在第 37 次唤醒时该从哪一行注释开始读起。适合谁看如果你正用 Keil MDK 或 IAR EW for ARM 9.40.1 开发语音唤醒功能且遇到过“编译通过但烧录后 hardfault”“模型精度达标但功耗超标 300%”“换用新芯片后 CMSIS-NN 报错 unknown architecture”这类问题那你就是目标读者。哪怕你只用 Arduino IDE 写 Blink只要未来打算接入 TinyML 工具链这篇里关于.ld链接脚本段落重排、.data与.bss区域的初始化时机、__init_array_start符号绑定机制的实操细节都会成为你调试时的救命稻草。它不假设你熟悉 Socrates 工具链或 NIC400 总线拓扑但默认你已能看懂startup_stm32h743xx.s里的堆栈指针初始化和SystemInit()调用顺序。现在我们直接切进源码根目录从 CMakeLists.txt 的第一行开始一寸寸剥开这个为 ARM MCU 量身定制的边缘 AI 工程骨架。2. 工程架构全景不是“一个 main.c 加一堆头文件”而是一套分层防御体系2.1 五层架构模型从硬件抽象到模型推理的垂直切片ML-KWS-for-MCU 的目录结构表面平实实则暗藏精密分层。它并非按功能模块如 audio/、model/、utils/粗暴划分而是严格遵循“硬件无关性→平台适配性→算法可移植性→模型可替换性→应用可配置性”的五层递进逻辑。这种设计直接回应了热词中高频出现的“arm和x86区别”“vmware运行arm系统”等跨平台痛点——它确保同一份核心推理代码在 Cortex-M0Keil、Cortex-M7IAR、RISC-VGCC甚至 x86 Linux用于仿真验证上仅需修改最底层的 Platform Abstraction LayerPAL其余四层零改动。Layer 1Hardware Abstraction LayerHAL位于/src/hal/包含hal_adc.c、hal_gpio.c、hal_timer.c。关键不在实现而在接口契约所有函数签名强制返回hal_status_t枚举值仅HAL_OK/HAL_ERROR/HAL_BUSY且禁止任何阻塞式延时HAL_Delay()被彻底移除。取而代之的是hal_timer_start_ms() 回调注册机制。这直接规避了“银河麒麟 ssh 10.3 rpm升级包arm”场景下因内核调度延迟导致的采样抖动问题——在裸机环境timer callback 是唯一可信的时间锚点。Layer 2Platform Abstraction LayerPAL位于/src/pal/是真正的“ARM 架构适配中枢”。这里没有#ifdef __ARM_ARCH_7EM__这类脆弱宏而是采用编译时特征检测pal_cmsis_nn_init()函数内部先调用__get_CPIDR0()读取 CPU ID 寄存器再根据IMPLEMENTER和PARTNUM字段动态加载cmsis_nn_armv7em.o或cmsis_nn_armv8mml.o。这意味着当你把固件从 STM32F407Cortex-M4迁移到 GD32E503Cortex-M33无需改一行代码链接器自动选择对应优化库。热词里“arm compiler 5.06 update 6 (build 750) 下载”之所以重要正是因为旧版 AC5 对__get_CPIDR0()的 inline asm 支持不全会导致 PAL 层 fallback 到通用实现性能损失达 40%。Layer 3Algorithm Abstraction LayerAAL位于/src/aal/核心是aal_kws_engine.c。它定义了aal_kws_config_t结构体将模型输入尺寸、采样率、量化位宽、唤醒词数量等全部参数化。重点在于所有模型相关操作加载权重、执行推理、输出置信度均通过函数指针aal_kws_ops_t实现而非硬编码调用。当你想把 TensorFlow Lite Micro 模型换成 ONNX Runtime Tiny只需实现新的ops结构体并注册AAL 层完全无感。这正是“边缘ai部署”中模型热切换的底层支撑。Layer 4Model Interface LayerMIL位于/models/存放kws_model_quantized.h等头文件。此处采用“头文件即模型”的激进设计权重数据以const int8_t model_weights[] {0x1A, 0x2B, ...}形式内联在头文件中而非二进制 blob。好处是编译期确定内存布局坏处是每次模型更新都要重新编译整个工程。但实测表明在 256KB Flash 限制下这种设计比 runtime 加载节省 12KB 可执行代码空间——因为省去了fread()、memcpy()等 libc 调用开销。热词中“redis arm版本”“mysql arm”强调的轻量化哲学在此得到极致体现。Layer 5Application LayerAPP位于/src/app/app_main.c仅做三件事初始化 HAL/PAL/AAL、启动音频采集 DMA、循环调用aal_kws_run()。所有业务逻辑如唤醒后触发蓝牙广播、LED 呼吸灯效均通过app_callback_t注册与 KWS 引擎彻底解耦。这种设计让“基于arm的微机原理与接口技术”教学案例能无缝复用本项目框架——学生只需修改 callback 函数即可完成从“语音唤醒”到“语音控制继电器”的实验升级。提示不要试图在/src/目录下新增.c文件并直接 include。所有新功能必须注入对应 Layer 的接口否则会破坏架构一致性。我曾见过团队为快速实现串口调试在app_main.c里硬编码printf()结果在启用--specsnano.specs编译时因_write实现缺失导致链接失败——这就是绕过 PAL 层的代价。2.2 构建系统深度解耦CMakeLists.txt 里的 ARM 编译器博弈项目的CMakeLists.txt不是简单罗列源文件而是一场针对 ARM 编译器生态的精密调度。它通过if(CMAKE_SYSTEM_PROCESSOR MATCHES arm.*)检测目标架构但真正的魔法在后续分支if(ARM_COMPILER STREQUAL AC5) # AC5 特定处理禁用 C11 标准强制 -O2 优化 set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} --cpuCortex-M4.fp --fpuvfpv4 --fpmodefast) add_definitions(-DARM_AC5_COMPILER) elseif(ARM_COMPILER STREQUAL ARMCLANG) # Armclang 处理启用 LTO启用 MVE 向量扩展 set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} --targetarm-arm-none-eabi --mcpucortex-m7 --mfloat-abihard) add_definitions(-DARM_ARMCLANG_COMPILER) endif()关键洞察在于它不依赖CMAKE_C_COMPILER的绝对路径而是通过ARM_COMPILER环境变量显式指定编译器类型。这意味着你可以同时维护 AC5 和 Armclang 两套构建脚本而无需修改 CMakeLists.txt。热词中“keil arm compiler 的 missing:compiler version 5编译不了”问题根源常在于 Keil MDK 的armcc.exe路径未正确注入ARM_COMPILER环境变量导致 CMake 误判为 GCC进而启用错误的-march参数。更精妙的是链接脚本的动态生成。/cmake/linker_script.cmake并非提供固定.ld文件而是根据MEMORY_SIZE_KB参数如256实时生成STM32H743VI_FLASH.ld。它精确计算.text、.rodata、.data、.bss四大段的起始地址与长度并在.data段末尾插入__init_array_start和__init_array_end符号——这是 CMSIS-NN 初始化函数自动注册的关键。当热词中出现“linux40 飞腾arm交叉编译”需求时只需复制此 CMake 逻辑将MEMORY_SIZE_KB设为飞腾平台的 SRAM 容量链接脚本即自适应生成。2.3 内存布局的战争从 .ld 文件到实际 RAM 分配的毫米级控制MCU 的内存是寸土寸金的战场。ML-KWS-for-MCU 的STM32H743VI_FLASH.ld文件其价值远超普通链接脚本。它将 64KB SRAM 划分为五个严格隔离的区域区域名起始地址长度用途关键约束SRAM10x2000000032KB.data/.bss/ Stack必须 8-byte 对齐供 CMSIS-NN FFT 使用SRAM20x2002000016KB模型权重缓存必须 16-byte 对齐供 NEON load/storeSRAM30x200300008KB音频环形缓冲区必须 32-byte 对齐DMA 传输要求SRAM40x200320004KB临时计算栈独立于主栈避免 overflowCCMRAM0x10000000256KB模型权重常量区仅读取支持 MPU 保护这个划分不是拍脑袋决定的。实测数据显示当SRAM2长度小于16KBCMSIS-NN 的arm_convolve_s8()函数因无法分配足够工作缓冲区会退化为纯标量实现推理耗时从 12ms 暴增至 47ms。而SRAM3的 32-byte 对齐则直接对应 STM32H7 的 MDMA 控制器对齐要求——若未对齐DMA 传输会丢弃首字节导致音频帧错位唤醒率暴跌至 30%。更隐蔽的是.data段的初始化时机。startup_stm32h743xx.s中SystemInit()执行完毕后__main函数才调用__iar_data_init3()IAR或__libc_init_array()GCC来拷贝.data。但 ML-KWS-for-MCU 在pal_init()中额外插入pal_sram_init()强制在SystemInit()后立即清零SRAM2和SRAM3——因为 CMSIS-NN 的某些函数如arm_softmax_q7()会读取未初始化内存作为临时缓冲区残留数据会导致 softmax 输出异常。这个细节在“arm汇编语言实战:从零到精通”教程里绝不会提及却是实际项目成败的关键。3. 源码静态评测用工具链穿透表象直击 ARM MCU 上的 AI 真实开销3.1 静态分析三板斧cppcheck clang-tidy custom python script静态评测不是走马观花而是用三套工具形成交叉验证闭环Cppcheck 2.12专注内存安全。启用--enablewarning,style,performance,portability特别关注memleak和uninitvar规则。项目中src/aal/aal_kws_engine.c的aal_kws_run()函数Cppcheck 会报告[src/aal/aal_kws_engine.c:142]: (warning) Array input_buffer[160] accessed at index 160, which is out of bounds.这指向一个经典陷阱input_buffer定义为int16_t input_buffer[160]但 CMSIS-NN 的arm_mfcc_init_q15()要求输入长度为 161含预加重系数。原作者用#pragma GCC diagnostic ignored -Warray-bounds压制警告但静态评测必须标记此风险——它在 AC5 编译器下可能引发 stack smash。Clang-Tidy 14.0.6聚焦 ARM 优化。启用clang-analyzer-core,google-readability,llvm-header-guard,misc-no-recursion。关键发现是src/pal/pal_cmsis_nn.c中// 错误示范递归调用 static void pal_cmsis_nn_init_recursive(int level) { if (level 0) pal_cmsis_nn_init_recursive(level-1); // 触发 misc-no-recursion }虽然此函数实际未被调用但 Clang-Tidy 仍标记为高危——在资源受限的 MCU 上任何潜在递归都是灾难。Custom Python Script (static_analyze.py)这才是真正杀手锏。它不分析语法而是解析编译产物运行arm-none-eabi-gcc -S -O2 src/aal/aal_kws_engine.c -o aal_kws_engine.s生成汇编统计ldr/str指令数量反映内存带宽压力计算bl指令跳转深度反映函数调用栈深度提取所有vld1.32/vst1.32指令确认 NEON 向量使用率。实测结果aal_kws_run()函数在-O2下生成 237 条ldr指令其中 189 条访问SRAM2权重区证明内存带宽是主要瓶颈bl指令最大嵌套深度为 5符合 Cortex-M4 的 8-level call stack 安全余量vld1.32出现 42 次证实 CMSIS-NN 的 NEON 优化已生效。这些数字比任何文档都更真实地告诉你当前模型在目标芯片上能否满足 200ms 唤醒延迟。注意static_analyze.py必须使用与目标环境一致的工具链版本。我曾用 Armclang 6.18 生成的汇编去分析 AC5 编译结果导致 NEON 指令统计完全失真——因为 AC5 对vld1.32的指令编码与 Armclang 不同。3.2 量化参数的静态校验为什么q7_t比int8_t更危险项目大量使用q7_tCMSIS 定义的 7-bit 有符号定点数而非标准int8_t。表面看只是 typedef实则暗藏陷阱。静态评测必须检查所有q7_t变量的初始化与运算初始化陷阱q7_t weight[128] {0};是安全的但q7_t weight[128] {127};在 AC5 下会被解释为0x7F而在 Armclang 下可能被截断为0xFF-1导致权重符号反转。静态脚本需扫描所有q7_t数组初始化强制要求显式十六进制赋值{0x7F}。运算溢出CMSIS-NN 的arm_element_sum_q7()函数其内部累加器为 32-bit但输入q7_t相加时若sum 127或 -128会静默饱和。静态评测需定位所有arm_element_sum_q7()调用点反向推导输入数据范围。例如若输入是 MFCC 特征范围 -100 ~ 100则 3 个元素相加就可能饱和必须插入arm_clip_q7()校验。内存对齐强制q7_t数组必须 4-byte 对齐才能被 NEONvld1.32高效加载。静态脚本检查__attribute__((aligned(4)))是否应用于所有q7_t*参数。漏掉一处就会导致vld1.32触发Alignment fault在裸机环境下直接 hardfault。热词中“arm dsp pid工具”“arm socrates 生成nic400”强调的硬件加速在此层面暴露本质NEON 不是万能银弹它要求数据、代码、内存布局三者严丝合缝。一个未对齐的q7_t数组足以让整套 DSP 加速失效。3.3 CMSIS-NN 调用链的穿透式审计从 API 到寄存器的逐层追踪CMSIS-NN 是 ARM 官方为 MCU 优化的神经网络库但它的“黑盒”属性常被低估。静态评测必须穿透其 API直达汇编层以arm_convolve_s8()为例其函数签名arm_status arm_convolve_s8( const q7_t * pSrc, // 输入特征图 uint16_t srcDim, // 输入宽度 const q7_t * pChIn, // 输入通道数 const q7_t * pWeight, // 权重 const q7_t * pBias, // 偏置 const uint16_t chOut, // 输出通道数 const uint16_t chIn, // 输入通道数 const uint16_t dimKernel, // 卷积核尺寸 const uint16_t padding, // 填充 const uint16_t stride, // 步长 const q7_t * pDst, // 输出 const uint16_t dstDim, // 输出宽度 const q15_t * pAcc, // 累加器关键 const uint16_t dimAcc, // 累加器尺寸 const uint16_t outOffset, // 输出偏移 const uint16_t outShift, // 输出移位 const uint16_t activation_min, // 激活最小值 const uint16_t activation_max // 激活最大值 );静态审计发现三个致命细节pAcc参数的双重身份它既是输入存储前一层输出又是输出存储本层卷积结果。但 CMSIS-NN 文档未明确说明pAcc必须与pDst完全分离。实测中若pAcc与pDst指向同一内存块arm_convolve_s8()会在写入pAcc时覆盖尚未读取的pDst数据导致结果全乱。静态脚本必须检查所有调用点确保pAcc和pDst的地址范围无交集。dimAcc的隐式约束dimAcc必须等于chOut * dstDim * dstDim输出特征图总像素数。但 CMSIS-NN 不做运行时校验若传入错误值会越界写入pAcc后续内存。静态评测需从模型配置文件kws_model_quantized.h反向计算理论dimAcc并与调用点参数比对。outShift的编译器陷阱outShift是右移位数用于定点数缩放。但在 AC5 编译器下运算符对负数执行算术右移而 CMSIS-NN 内部期望逻辑右移。静态脚本需扫描所有outShift赋值强制转换为((uint8_t)outShift)避免符号扩展污染。这些细节在“arm soc体系结构”教材里不会讲却是让 CMSIS-NN 从“能跑”变成“稳定跑”的分水岭。一次pAcc重叠就能让唤醒率从 95% 降到 0%。4. 实操过程与核心环节实现从零开始复现评测环境的完整流水线4.1 环境搭建避开 ARM 编译器版本地狱的实操清单搭建可复现的评测环境核心是锁定工具链版本。热词中“arm compiler 5.06 update 7 (build 960)下载”“iar ew for arm 9.40.1”等绝非随意列举而是经过千次编译验证的黄金组合。我的实操清单如下ARM Compiler 5.06 Update 7 (Build 960)下载地址Arm Developer 官网需注册文件名armcc-5.06u7-build-960.exe。安装时必须取消勾选“Add to PATH”改用 CMake 的ARMCC_PATH环境变量指向C:\Program Files\ARM\ARMCC\bin\armcc.exe。原因Windows PATH 中若存在多个armcc.exeCMake 会随机选择导致构建不可重现。IAR EW for ARM 9.40.1下载地址IAR Systems 官网文件名IAR_EWARM_9401_Install.exe。安装后在C:\Program Files\IAR Systems\Embedded Workbench 9.4\arm\bin目录下确认iccarma.exe版本为9.40.1.12345运行iccarma --version。关键设置在 IAR IDE 的Project - Options - C/C Compiler - Code中将Optimization level设为High并勾选Enable function inlining——这是 CMSIS-NN 性能的关键。QEMU for ARM Cortex-M下载qemu-system-arm7.2.0 版本非最新版。新版 QEMU 对 Cortex-M7 的 FPU 模拟有 bug会导致arm_softmax_q7()计算结果偏差。验证命令qemu-system-arm -M stm32h743i-disco -nographic -kernel build/kws.elf应输出KWS initialized, waiting for audio...。Python 3.9.16专用虚拟环境python -m venv ml-kws-env激活后pip install -r requirements.txt含pyelftools0.27、numpy1.21.6、matplotlib3.5.2。特别注意pyelftools必须锁定 0.27 版新版对 ARM ELF 的 section 解析有兼容性问题。实操心得不要用 Chocolatey 或 Scoop 安装 ARM 工具链。我曾用 Scoop 安装的arm-none-eabi-gcc其libgcc.a与 CMSIS-NN 的arm_cortexM7lfsp_math.lib链接时出现undefined reference to __aeabi_idiv错误——因为 Scoop 版本缺少 ARM EABI 的整数除法软实现。坚持从官方渠道下载是避免“stm32cubemx 编译后无 arm 文件夹”类问题的铁律。4.2 静态评测流水线一条命令跑完全部分析的 Bash 脚本将前述工具整合为自动化流水线是保证评测可重复的核心。run_static_analysis.sh脚本如下#!/bin/bash set -e # 任一命令失败即退出 # 1. 清理并生成构建目录 rm -rf build mkdir build cd build # 2. 配置 CMake强制指定 AC5 export ARM_COMPILERAC5 export ARMCC_PATH/c/Program Files/ARM/ARMCC/bin/armcc.exe cmake -G MinGW Makefiles \ -DCMAKE_BUILD_TYPERelease \ -DARM_COMPILERAC5 \ -DARMCC_PATH$ARMCC_PATH \ -DMEMORY_SIZE_KB256 \ .. # 3. 编译并生成汇编 make -j4 arm-none-eabi-gcc -S -O2 ../src/aal/aal_kws_engine.c -o aal_kws_engine.s # 4. 运行 cppcheck cppcheck --enablewarning,style,performance,portability \ --suppressuninitvar:../src/pal/pal_cmsis_nn.c \ --file-filter*.c \ ../src/ cppcheck_report.txt # 5. 运行 clang-tidy clang-tidy -p . \ -checksclang-analyzer-core,google-readability,llvm-header-guard,misc-no-recursion \ ../src/aal/aal_kws_engine.c \ ../src/pal/pal_cmsis_nn.c clang_tidy_report.txt # 6. 运行自定义静态分析 python ../scripts/static_analyze.py \ --asm-file aal_kws_engine.s \ --elf-file kws.elf \ --model-header ../models/kws_model_quantized.h \ --output report.json # 7. 生成最终 PDF 报告 python ../scripts/generate_report.py --input report.json --output kws_static_audit.pdf echo ✅ 静态评测完成报告已生成kws_static_audit.pdf关键技巧--suppressuninitvar:../src/pal/pal_cmsis_nn.c参数不是掩盖问题而是标记已知可控风险。CMSIS-NN 的某些函数如arm_mfcc_init_q15()确实会读取未初始化内存但这是其算法设计使然静态评测需记录而非报错。4.3 内存布局可视化用 Python 解析 ELF生成 RAM 使用热力图静态评测的终极输出不是文字报告而是直观的内存热力图。elf_memory_visualizer.py脚本核心逻辑from pyelftools.elftools.elf.elffile import ELFFile import matplotlib.pyplot as plt import numpy as np def parse_elf_sections(elf_path): with open(elf_path, rb) as f: elf ELFFile(f) sections {} for section in elf.iter_sections(): if section[sh_flags] 0x1: # SHF_ALLOC 标志 sections[section.name] { addr: section[sh_addr], size: section[sh_size], flags: section[sh_flags] } return sections def plot_ram_usage(sections): # 创建 64KB RAM 热力图Y轴地址X轴时间维度此处简化为单帧 ram_map np.zeros(64*1024) # 64KB for name, sec in sections.items(): start sec[addr] - 0x20000000 # 相对于 SRAM1 起始地址 if 0 start len(ram_map): end min(start sec[size], len(ram_map)) ram_map[start:end] 1 if .data in name or .bss in name else 0.5 plt.figure(figsize(12, 6)) plt.imshow(ram_map.reshape(1, -1), cmapRdYlGn, aspectauto) plt.title(SRAM1 Usage Heatmap (0x20000000 - 0x20010000)) plt.xlabel(Address Offset (Bytes)) plt.ylabel(RAM Region) plt.colorbar(labelUsage Intensity (1.0Data/BSS, 0.5Code)) plt.savefig(ram_usage_heatmap.png, dpi300, bbox_inchestight) if __name__ __main__: sections parse_elf_sections(build/kws.elf) plot_ram_usage(sections)生成的热力图清晰显示.data段占据0x20000000-0x200012004.5KB.bss段紧随其后至0x2000280010KB而SRAM20x20020000区域被model_weights占满。当热词中出现“centos7镜像下载教程 arm 架构”需求时此图可直接用于向客户证明我们的固件在 64KB RAM 下仍有 12KB 余量供 OTA 升级使用。5. 常见问题与排查技巧实录那些让工程师熬夜的 ARM MCU 边缘 AI 故障5.1 典型故障速查表从现象到根因的精准映射现象可能根因排查步骤解决方案编译通过烧录后立即 HardFault__main函数未正确调用__libc_init_array()1. 用arm-none-eabi-objdump -d build/kws.elf | grep __main2. 检查__main中是否包含bl __libc_init_array在startup_stm32h743xx.s的Reset_Handler末尾手动添加bl __libc_init_array调用唤醒率忽高忽低70%-95%波动SRAM3音频缓冲区未 32-byte 对齐DMA 丢帧1. 检查hal_audio.c中audio_buffer定义__attribute__((aligned(32))) static int16_t audio_buffer[2048];2. 用arm-none-eabi-nm build/kws.elf | grep audio_buffer确认地址强制添加__attribute__((aligned(32)))并确保audio_buffer地址 %32 0模型精度达标但功耗超标 300%SRAM4临时栈未启用所有计算在主栈进行1. 检查startup_stm32h743xx.s中Stack_Size是否设为0x4001KB2. 检查pal_sram_init()是否调用SCB-VTOR 0x20032000将Stack_Size改为0x200512B并在pal_sram_init()中为SRAM4分配独立栈更换芯片后 CMSIS-NN 报错 unknown architecturepal_cmsis_nn.c中__get_CPIDR0()返回值解析错误1. 在
返回列表