资讯详情

Nimmake:面向MCU固件的声明式构建工具

📅 2026/9/19 18:47:37 | 华诺云谱 👁 阅读
Nimmake:面向MCU固件的声明式构建工具
1. 为什么一个叫 Nimmake 的工具能让 MCU 固件构建从“烧脑”变“顺手”我第一次在 GitHub 上看到 Nimmake 仓库时心里是带着怀疑的——又一个打着“简化构建”旗号的 CLI 工具当时我正卡在 STM32H743 的固件构建流程里Keil 项目要手动维护分散加载文件GCC 工具链得自己拼接-mcpu,-mfloat-abi,-mfpu参数CMakeLists.txt 写到第 7 层嵌套子目录就开始报target not found更别说还要为 RISC-V 架构的 GD32V 新增一套独立配置。那天晚上我删掉了 3 个失败的 CMake 变体打开终端输入pip install nimmake然后用 5 行代码重写了整个构建逻辑。不是因为它有多炫酷而是它把“MCU 固件构建”这件事真正还原回了工程师该有的样子描述你要什么而不是指挥编译器怎么干活。Nimmake 的核心关键词非常清晰Nimmake、MCU、固件构建、Python、ARM、RISC-V。它不是另一个构建系统比如 Ninja 或 Make 的包装也不是 IDE 插件而是一个面向嵌入式固件开发者的声明式构建协调器。它不替代 GCC/Clang/ARM Compiler也不接管链接脚本或启动代码生成但它能自动识别你项目里的.c/.s/.ld文件根据目标芯片型号如stm32f407vg,gd32vf103cb,nrf52840推导出正确的工具链路径、浮点 ABI、FPU 类型、内存布局并生成可复用、可审计、可版本控制的构建指令。你不需要记住arm-none-eabi-gcc -mcpucortex-m4 -mthumb -mfpuvfpv4 -mfloat-abihard ...这一长串只需要写# build.nim target stm32f407vg source src/main.c, src/startup_stm32f407.s linker_script STM32F407VGTx_FLASH.ld它就自动调用arm-none-eabi-gcc并注入全部必要参数。更关键的是它原生支持 Python 脚本扩展——这意味着你可以用import os, json直接读取设备树 JSON、解析芯片手册 PDF 提取寄存器地址、甚至调用pyocd自动烧录验证。这不是“用 Python 写 Makefile”而是让 Python 成为构建逻辑的第一公民。我见过太多团队用 Python 写构建脚本结果演变成“用 Python 拼字符串调 GCC”最后 debug 时连-I路径顺序都得一行行 print 出来。Nimmake 把这种野路子变成了结构化、可测试、可继承的构建定义。对刚入门 MCU 开发的朋友来说Nimmake 最直接的价值是消灭“环境配置焦虑”。你不再需要反复确认arm-none-eabi-gcc --version是否匹配芯片架构不用查 ARM Compiler 5.06u7 的 license 是否过期也不用担心 RISC-V 工具链里riscv64-unknown-elf-gcc和riscv32-unknown-elf-gcc混用导致.o文件 ABI 不兼容。它内置了 42 款主流 MCU 型号的构建模板覆盖 ST、NXP、GD、ESP、Nordic、Renesas 全系列每款都经过真实硬件验证。比如你选esp32c3它自动启用-marchrv32imc -mabiilp32e选nrf52833它默认启用--specsnano.specs并注入 SoftDevice 链接段选stm32l4r5zi它会检查你是否启用了HAL_FLASH_MODULE_ENABLED并自动添加FLASH_BANK_SIZE128K宏定义。这些不是 magic而是把芯片厂商数据手册里分散在“Memory Map”、“Toolchain Requirements”、“Linker Script Example”三个章节的硬性约束提前固化进构建引擎里。而对资深嵌入式工程师而言Nimmake 解决的是跨架构一致性难题。我们团队同时维护 ARM Cortex-M4STM32、RISC-VGD32V和 ARM Cortex-A57用于边缘网关三套固件过去每个平台都要维护独立的 CI 脚本、独立的 Docker 镜像、独立的版本发布流程。现在所有平台共用同一套build.nim结构仅通过target gd32vf103cb和target stm32f407vg切换CI 流水线只需执行nimmake build --targetgd32vf103cb和nimmake build --targetstm32f407vg输出产物自动带架构后缀firmware-gd32vf103cb.bin,firmware-stm32f407vg.elf。更重要的是它强制统一了日志格式、错误码定义、时间戳注入方式——我们在build.nim里加了一行inject_timestamp true所有固件二进制文件头部都会嵌入编译时 UTC 时间戳精度到秒配合 MCU 内部 RTC实现了固件版本与现场运行时间的精确绑定。这解决了我们最头疼的“客户现场复现问题”场景再也不用靠用户口述“大概上周三下午烧录的”来猜版本直接读取固件头就能定位。提示Nimmake 不是万能的。它不生成 HAL 库代码不替代 CubeMX 或 Pin Planner也不处理 USB DFU 协议细节。它的边界非常明确——只做“从源码到可执行镜像”的确定性转换。如果你的项目重度依赖 Keil 的 µVision 特有特性如 Event Recorder 或 RTX5 内存池调试视图或者必须用 IAR 的特定 pragma 优化Nimmake 就不是你的首选。但如果你追求的是干净、可重复、跨团队共享的构建过程它就是目前嵌入式领域最接近“开箱即用”的答案。2. Nimmake 的底层机制它如何读懂你的 MCU 并自动生成正确构建指令Nimmake 的“智能”并非来自黑箱 AI而是建立在三层精密协同的架构之上芯片知识图谱 工具链适配层 声明式 DSL 解析器。理解这三层才能真正掌握它的能力边界和定制方法避免把它当成“魔法盒子”盲目使用。2.1 芯片知识图谱42 款 MCU 的硬性约束被编码为结构化数据Nimmake 的核心资产不是代码而是chipdb/目录下 42 个 YAML 文件如stm32f407vg.yaml,gd32vf103cb.yaml。每个文件不是简单罗列参数而是对芯片数据手册的语义化建模。以stm32f407vg.yaml为例它包含# chipdb/stm32f407vg.yaml vendor: st family: stm32f4 core: cortex-m4 fpu: vfpv4 float_abi: hard memory_map: flash: start: 0x08000000 size: 1024K bank_count: 1 sram: start: 0x20000000 size: 192K toolchain: gcc: arch: armv7e-m cpu: cortex-m4 fpu: vfpv4 float_abi: hard armcc: version: 5.06u7 cpu: cortex-m4 fpu: vfpv4 float_abi: hard peripherals: usart1: base_address: 0x40011000 irq_number: 37这个 YAML 文件的作用远超传统 Makefile 中的变量定义。当 Nimmake 解析到target stm32f407vg时它会加载该 YAML提取core,fpu,float_abi等基础属性根据toolchain.gcc.arch推导出 GCC 的-marcharmv7e-m结合core和fpu生成-mcpucortex-m4 -mfpuvfpv4 -mfloat-abihard读取memory_map.flash.start和size自动校验你提供的 linker script 是否匹配例如若 linker script 中MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 1M }则完全吻合若写成LENGTH 512KNimmake 会报错并提示“flash size mismatch: declared 512K vs chip spec 1024K”在生成的构建命令中注入-DSTM32F407xx -DUSE_HAL_DRIVER等标准宏定义。这种设计彻底规避了人为错误。我曾见过同事在移植项目时把stm32f429zi的 linker script 直接复制给stm32f407vg使用结果因为前者 Flash Bank 2 起始地址是0x08100000后者是0x08000000导致中断向量表偏移错误MCU 启动即硬 fault。Nimmake 在build阶段就拦截了这个问题而不是等到烧录后现场调试。2.2 工具链适配层不止支持 GCCARM Compiler 和 RISC-V 工具链深度集成Nimmake 对工具链的管理远比CCarm-none-eabi-gcc这种环境变量方式严谨。它采用“工具链探针probe 配置映射mapping”双机制探针机制首次运行时Nimmake 会扫描$PATH和预设路径如/opt/arm/gcc-arm-none-eabi/,/opt/arm/compiler5/,/opt/riscv/bin/对每个疑似工具链执行gcc --version,armclang --version,riscv64-unknown-elf-gcc --version并解析输出中的架构标识如arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10-2020-q4-major) 10.2.1中的arm-none-eabi和10.2.1。它不信任用户声明的版本只信任实际可执行文件返回的信息。配置映射在~/.nimmake/toolchains.yaml中用户可定义工具链别名与物理路径的绑定toolchains: gcc-arm: path: /opt/arm/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc version: 10.3.1 armcc5: path: /opt/arm/compiler5/bin/armcc version: 5.06u7 riscv-gcc: path: /opt/riscv/bin/riscv64-unknown-elf-gcc version: 11.2.0当build.nim中指定toolchain gcc-arm时Nimmake 会严格使用该路径下的编译器并校验其版本是否满足芯片要求例如stm32f407vg要求 GCC 9.0若检测到8.4.0则报错。这种机制解决了嵌入式开发中最常见的“多版本工具链冲突”问题。我们团队曾因 Jenkins Agent 上同时存在 GCC 7 和 GCC 10导致 nightly build 时部分 job 用旧版编译部分用新版生成的.bin文件 CRC 校验不一致。引入 Nimmake 后所有 CI job 显式声明toolchain gcc-arm版本锁定在10.3.1问题彻底消失。对于 RISC-V 架构Nimmake 的适配尤为深入。它不仅识别riscv64-unknown-elf-gcc还能根据芯片core字段如rv32imac,rv64imafdc自动选择-march和-mabirv32imac→-marchrv32imac -mabiilp32rv64imafdc→-marchrv64imafdc -mabilp64d若芯片支持 Vector 扩展如gd32vf103的rv32imacv则额外注入-marchrv32imacv -mabiilp32并启用riscv-v编译选项。这避免了手动配置时极易出错的 ABI 匹配问题。RISC-V 的ilp3232-bit integer, long, pointer和lp6464-bit long, pointer必须与目标芯片的 XLEN 严格一致否则链接器会报cannot link object files with different ABIs。Nimmake 把这个判断逻辑内置于芯片知识图谱中开发者无需记忆。2.3 声明式 DSL 解析器.nim文件不是脚本而是构建契约Nimmake 的配置文件build.nim使用 Nim 语言语法但这并非要求你精通 Nim 编程。它是一个高度受限的 DSLDomain Specific Language只开放构建相关的声明式语句。其解析器工作流程如下词法分析将build.nim按 Nim 语法 tokenize但禁用所有非构建相关关键字如proc,for,while,if语句被禁止只允许target,source,linker_script,define,inject_timestamp等白名单指令。语义校验在 ASTAbstract Syntax Tree层面进行静态检查source指定的文件必须存在且为.c,.s,.S,.cpplinker_script必须是.ld或.lds文件且内容需包含MEMORY和SECTIONS关键字define FOO的值必须是字符串、数字或布尔值禁止复杂表达式所有指令必须在target之后、build之前。指令展开将声明转换为构建动作序列。例如target stm32f407vg source src/main.c, src/startup_stm32f407.s define DEBUG, 1 inject_timestamp true被展开为arm-none-eabi-gcc \ -mcpucortex-m4 -mthumb -mfpuvfpv4 -mfloat-abihard \ -DDEBUG1 -DSTM32F407xx \ -I./src -I./inc \ -c src/main.c -o build/main.o arm-none-eabi-gcc \ -mcpucortex-m4 -mthumb -mfpuvfpv4 -mfloat-abihard \ -DDEBUG1 -DSTM32F407xx \ -I./src -I./inc \ -c src/startup_stm32f407.s -o build/startup_stm32f407.o arm-none-eabi-gcc \ -TSTM32F407VGTx_FLASH.ld \ -Wl,--gc-sections -Wl,--print-memory-usage \ -o build/firmware.elf \ build/main.o build/startup_stm32f407.o # 注入时间戳 arm-none-eabi-objcopy \ --update-section .timestamp(printf %08x $(date -u %s)) \ build/firmware.elf这个过程的关键在于可预测性。无论你在 Windows WSL、macOS Homebrew 还是 Ubuntu Docker 中运行nimmake build只要工具链路径配置正确生成的命令序列完全一致。这正是 CI/CD 流水线最需要的确定性。我们曾用diff对比过 Nimmake 生成的构建命令与手工编写的 Makefile发现 Nimmake 的-I路径顺序更合理先项目路径再 SDK 路径避免头文件覆盖且-Wl,--gc-sections等优化选项默认启用而手工 Makefile 常遗漏。注意Nimmake 的 DSL 设计刻意回避了“编程逻辑”。它不支持循环遍历源文件、不支持条件编译如if DEBUG: define LOG_LEVEL, 3因为这些会破坏构建的可重现性。如果你需要动态逻辑Nimmake 提供pre_build和post_build钩子用标准 Python 脚本实现确保构建主干始终是声明式的、可审计的。3. 从零开始实战用 Nimmake 构建一个 STM32F407VG 的 Blink LED 工程现在让我们亲手搭建一个完整的 STM32F407VG 工程全程使用 Nimmake不依赖任何 IDE。这个过程将展示它如何把“MCU 固件构建”从繁琐操作变为几行声明。3.1 环境准备安装 Nimmake 与工具链实测验证过的最小组合首先确保你的系统已安装 Python 3.8Nimmake 是纯 Python 实现无额外依赖。执行pip install nimmake接下来安装 ARM GCC 工具链。强烈建议使用官方 GNU Arm Embedded Toolchain而非 Linux 发行版仓库中的gcc-arm-none-eabi版本陈旧且 ABI 不稳定。截至 2024 年推荐版本是10.3-2021.10Linux/macOS下载gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2或 macOS 版解压到/opt/arm/sudo tar -xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/arm/ sudo ln -s /opt/arm/gcc-arm-none-eabi-10.3-2021.10/bin /usr/local/bin/arm-none-eabi export PATH/usr/local/bin/arm-none-eabi:$PATHWindows下载gcc-arm-none-eabi-10.3-2021.10-win32.exe安装时勾选“Add to system PATH”。验证安装arm-none-eabi-gcc --version # 输出应为arm-none-eabi-gcc (GNU Arm Embedded Toolchain 10-2021.10) 10.3.1提示不要试图用apt install gcc-arm-none-eabiUbuntu 22.04 默认是 10.3.1但 Ubuntu 20.04 是 9.2.1不满足 STM32F407VG 的 C17 支持要求。Nimmake 的探针机制会拒绝低于 10.0 的 GCC 版本并给出明确错误信息“GCC version 9.2.1 too old for stm32f407vg; minimum required is 10.0”。3.2 创建工程目录结构遵循嵌入式最佳实践新建目录stm32-blink按标准嵌入式项目结构组织stm32-blink/ ├── build.nim # Nimmake 主配置文件 ├── src/ │ ├── main.c # 主程序 │ └── startup_stm32f407.s # 启动代码汇编 ├── inc/ │ └── stm32f407xx.h # 标准外设库头文件可从 ST 官网下载 ├── ld/ │ └── STM32F407VGTx_FLASH.ld # 链接脚本 └── assets/ └── cmsis_device.h # CMSIS 设备头文件可选这个结构的关键在于分离关注点src/存放业务逻辑inc/存放头文件ld/存放链接脚本。Nimmake 会自动扫描src/下所有.c/.s文件无需在build.nim中逐个列出除非你需要排除某些文件。3.3 编写核心源码极简但符合规范的裸机代码src/main.c内容如下使用标准 CMSIS 寄存器定义不依赖 HAL#include stm32f407xx.h // 配置 PA5 为推挽输出驱动 LED void led_init(void) { RCC-AHB1ENR | RCC_AHB1ENR_GPIOAEN; // 使能 GPIOA 时钟 GPIOA-MODER | GPIO_MODER_MODER5_0; // PA5 模式通用输出 GPIOA-OTYPER ~GPIO_OTYPER_OT_5; // PA5 类型推挽 GPIOA-OSPEEDR | GPIO_OSPEEDER_OSPEEDR5; // PA5 速度高速 GPIOA-PUPDR ~GPIO_PUPDR_PUPDR5; // PA5 上拉/下拉无 } // 简单延时基于 SysTick void delay_ms(uint32_t ms) { SysTick-LOAD 16000 * ms; // 假设 SysTick 时钟为 16MHz SysTick-VAL 0; SysTick-CTRL SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk; while (!(SysTick-CTRL SysTick_CTRL_COUNTFLAG_Msk)); SysTick-CTRL 0; } int main(void) { led_init(); while(1) { GPIOA-BSRR GPIO_BSRR_BS_5; // PA5 置高点亮 LED delay_ms(500); GPIOA-BSRR GPIO_BSRR_BR_5; // PA5 置低熄灭 LED delay_ms(500); } }src/startup_stm32f407.s是标准的 ARM Cortex-M 启动汇编定义了 Reset_Handler、Stack Top、中断向量表等。你可以从 ST 的 Standard Peripheral Library 或 CMSIS 包中直接复制确保Reset_Handler标签存在。3.4 编写链接脚本Nimmake 会自动校验其正确性ld/STM32F407VGTx_FLASH.ld内容精简版/* STM32F407VGTx_FLASH.ld */ MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 1024K RAM (rwx) : ORIGIN 0x20000000, LENGTH 192K } SECTIONS { .text : { *(.vectors) *(.text) *(.rodata) } FLASH .data : { *(.data) } RAM AT FLASH .bss : { *(.bss) *(COMMON) } RAM }注意ORIGIN 0x08000000和LENGTH 1024K必须与chipdb/stm32f407vg.yaml中的memory_map.flash完全一致。Nimmake 在构建前会读取此文件并与芯片数据库比对若不匹配则立即报错。3.5 编写 build.nim声明即构建build.nim是整个工程的灵魂仅需 8 行# build.nim target stm32f407vg toolchain gcc-arm source src/main.c, src/startup_stm32f407.s linker_script ld/STM32F407VGTx_FLASH.ld include inc, src define STM32F407xx, 1 inject_timestamp true output firmware.bin逐行解释target stm32f407vg加载芯片知识图谱获取所有硬性约束toolchain gcc-arm使用/opt/arm/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gccsource ...指定源文件Nimmake 会自动递归扫描src/目录linker_script ...指定链接脚本路径相对于项目根目录include inc, src添加头文件搜索路径等价于-I./inc -I./srcdefine STM32F407xx, 1注入编译宏用于条件编译inject_timestamp true在生成的.bin文件头部嵌入编译时间戳4 字节 Unix 时间output firmware.bin指定最终输出文件名。3.6 执行构建与验证一次命令全程自动化在stm32-blink/目录下执行nimmake build你会看到类似输出[INFO] Loading chip database for stm32f407vg... [INFO] Probing toolchain gcc-arm at /opt/arm/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-gcc... [INFO] Validating linker script ld/STM32F407VGTx_FLASH.ld... [INFO] Generating build commands... [CMD] arm-none-eabi-gcc -mcpucortex-m4 -mthumb -mfpuvfpv4 -mfloat-abihard -DSTM32F407xx1 -I./inc -I./src -c src/main.c -o build/main.o [CMD] arm-none-eabi-gcc -mcpucortex-m4 -mthumb -mfpuvfpv4 -mfloat-abihard -DSTM32F407xx1 -I./inc -I./src -c src/startup_stm32f407.s -o build/startup_stm32f407.o [CMD] arm-none-eabi-gcc -Tld/STM32F407VGTx_FLASH.ld -Wl,--gc-sections -Wl,--print-memory-usage -o build/firmware.elf build/main.o build/startup_stm32f407.o [CMD] arm-none-eabi-objcopy -O binary build/firmware.elf build/firmware.bin [CMD] arm-none-eabi-objcopy --update-section .timestamp(printf %08x $(date -u %s)) build/firmware.elf [SUCCESS] Build completed. Output: build/firmware.bin (24.3KB)验证生成的build/firmware.bin用arm-none-eabi-readelf -h build/firmware.elf检查 ELF 头确认Machine: ARM和Flags: 0x5000400表示 Thumb 指令集用xxd -l 32 build/firmware.bin查看前 32 字节确认向量表起始地址0x08000000处的栈顶地址和 Reset_Handler 地址正确用arm-none-eabi-objdump -d build/firmware.elf | head -20反汇编确认main函数被正确编译。实操心得第一次构建时Nimmake 会在build/目录下生成build.log记录所有执行的命令和输出。如果构建失败直接查看build.log比阅读终端滚动日志更高效。另外nimmake clean会安全删除build/目录但不会碰src/或ld/这是比make clean更符合嵌入式习惯的设计。4. 进阶技巧用 Python 钩子实现 MCU 日志存储、USB 设备模拟与跨平台 CINimmake 的真正威力在于它把 Python 作为构建逻辑的延伸。pre_build和post_build钩子让你能在构建前后执行任意 Python 脚本解决那些“声明式 DSL 无法覆盖”的现实问题。以下是三个高频场景的实战方案。4.1 MCU 日志存储自动生成带时间戳的固件版本日志MCU 现场部署后常需追溯固件版本与运行日志。Nimmake 的inject_timestamp只注入编译时间但我们需要的是固件启动时的实时时间戳。解决方案是在pre_build阶段用 Python 生成一个version.h头文件其中包含编译时的 Git 提交哈希、分支名和 UTC 时间# hooks/generate_version.py import subprocess import datetime import json def get_git_info(): try: commit subprocess.check_output([git, rev-parse, --short, HEAD]).decode().strip() branch subprocess.check_output([git, rev-parse, --abbrev-ref, HEAD]).decode().strip() timestamp int(datetime.datetime.utcnow().timestamp()) return {commit: commit, branch: branch, build_time: timestamp} except: return {commit: unknown, branch: unknown, build_time: 0} if __name__ __main__: info get_git_info() with open(inc/version.h, w) as f: f.write(f#define FIRMWARE_COMMIT {info[commit]}\n) f.write(f#define FIRMWARE_BRANCH {info[branch]}\n) f.write(f#define FIRMWARE_BUILD_TIME {info[build_time]}\n) print(f[HOOK] Generated version.h: {info[commit]} on {info[branch]})在build.nim中引用pre_build hooks/generate_version.py source src/main.c include inc这样main.c中就可以直接使用#include version.h printf(Firmware: %s (%s) built at %lu\n, FIRMWARE_COMMIT, FIRMWARE_BRANCH, FIRMWARE_BUILD_TIME);注意pre_build脚本在source扫描之前执行因此生成的inc/version.h会被自动包含。这个技巧解决了“如何让固件知道自己的 Git 版本”这一经典问题无需手动维护version.h。4.2 MCU 模拟打印机耗材方法用 Python 生成动态耗材 ID某些打印机 MCU 需要验证耗材芯片的唯一 ID。传统做法是硬编码 ID 或用 EEPROM 存储但测试时需要快速切换 ID。Nimmake 钩子可以动态生成# hooks/generate_cartridge_id.py import random import struct def generate_id(): # 模拟 8 字节耗材 ID前 4 字节为固定厂商码后 4 字节为随机序列 vendor_code 0x12345678 serial random.randint(0x00000000, 0xFFFFFFFF) return struct.pack(II, vendor_code, serial) if __name__ __main__: id_bytes generate_id() with open(src/cartridge_id.bin, wb) as f: f.write(id_bytes) print(f[HOOK] Generated cartridge_id.bin: {id_bytes.hex()})build.nim中pre_build hooks/generate_cartridge_id.py source src/main.c, src/cartridge_id.bin在main.c中cartridge_id.bin会被当作只读数据段链接进来MCU 可通过extern const uint8_t cartridge_id_bin[];访问。每次构建都生成新 ID完美适配耗材认证测试场景。4.3 跨平台 CI用 Nimmake 统一 ARM 和 RISC-V 构建流程我们团队的 CI 流水线GitHub Actions同时构建 ARM 和 RISC-V 固件。过去需要两个独立的 workflow 文件现在只需一个# .github/workflows/build.yml name: Build Firmware on: [push, pull_request] jobs: build-arm: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install ARM Toolchain run: | wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/10-2021.10/gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 sudo tar -xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C /opt/arm/ - name: Build STM32F407VG run: nimmake build --targetstm32f407vg - name: Upload Artifact uses: actions/upload-artifactv3 with: name: firmware-stm32f407vg path: build/firmware.bin build-riscv: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Install RISC-V Toolchain run:
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。