拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

极海 APM32 使用 Nimmake 编译指南

极海 APM32 使用 Nimmake 编译指南

极海 APM32 使用 Nimmake 编译指南(详细版)

主线型号:APM32F103 / Cortex-M3,并给出 F4xx(Cortex-M4F)适配
构建方式:arm-none-eabi-gcc+ Nimmake(Python 构建脚本)
适用系列:APM32F10x、APM32F4xx、APM32F0xx、APM32A1xx、APM32E1xx 等支持 GCC/Makefile 的系列


1. 可行性结论

极海半导体(Geehy)APM32 系列基于 Arm Cortex-M0+/M3/M4F 内核,官方提供完整的 SDK、标准外设库、CMSIS 支持、启动文件、系统初始化代码,以及 Keil DFP 设备支持包(内含启动文件、头文件、Flash 算法等)。SDK 中 Eclipse 工程底层即使用 GNU Arm Embedded Toolchain,因此配合 xPack ARM GCC 工具链和 Nimmake 构建脚本,完全可以实现命令行裸机编译。
官方已提供的底层素材:

素材来源说明
启动文件SDK / DFP 包startup_apm32f10x_md.s/startup_apm32f4xx.s(ARM 汇编,GCC 可用)
设备头文件SDK / DFP 包apm32f10x.h/apm32f4xx.h,寄存器定义
系统初始化SDKsystem_apm32f10x.c/system_apm32f4xx.c,含SystemInit()
CMSIS 核心SDKcore_cm3.h/core_cm4.h等
标准外设库SDKGPIO、USART、SPI、I2C、TIM、ADC 等
中间件SDKFreeRTOS、FatFs、LwIP、USB 等
Flash 算法DFP 包.flm文件,用于 J-Link / 烧录器

需要自行补充的素材:

素材说明
GCC 链接脚本(.ld)官方 DFP 主要提供 Keil.sct格式,需自行编写或从 Eclipse 工程中提取.ld
GCC 版启动文件DFP 中的.s为 ARM 汇编语法,GCC 可直接使用,但需确认伪指令兼容
编译宏需根据型号定义,如APM32F10X_MD、USE_STDPERIPH_DRIVER

2. 为什么用 Nimmake

痛点Makefile 写法Nimmake 写法
芯片参数手写-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hardCORTEX_M4F_CFG.clone()一行预设
标准库源文件列表手动 glob 或逐行写,换型号就改Parties自动扫描Libraries/**/src/*.c
换型号改 CPU、链接脚本、启动文件、宏,多处同步只改Chip预设和Parties路径
头文件路径-I逐条写,漏一个就报错Parties自动收集inc/
多目标每个 target 写一套规则多个Program对象即可
缓存需手动配ccache--cache内置支持
跳转补全无--compiledb生成compile_commands.json
烧录命令手写openocd -f ...Phony目标封装,nimmake flash

3. 环境准备

3.1 安装 Nimmake

pipinstallnimmake nimmake--version

3.2 安装 ARM GCC 工具链

推荐使用xPack ARM Embedded GCC:

# macOSbrewinstallxpack-arm-none-eabi-gcc# Windows (Scoop)scoopinstallxpack-arm-none-eabi-gcc

验证:

arm-none-eabi-gcc--versionarm-none-eabi-size--versionarm-none-eabi-objcopy--version

3.3 获取 APM32 SDK 素材

方式一:从极海官网下载 SDK

  1. 访问极海半导体官网(www.geehy.com)
  2. 进入对应芯片页面(如 APM32F103 / APM32F407)
  3. 下载SDK(如APM32F10x_SDK_V1.8.zip或APM32F4xx_SDK)
  4. 解压后得到:
    • Libraries/— 标准外设库、CMSIS、Device
    • Examples/— 示例工程(含 MDK/IAR/Eclipse 三平台)
    • Middlewares/— FreeRTOS、FatFs、LwIP 等中间件

方式二:从 DFP 包中提取

  1. 下载Geehy.APM32F10x_DFP.x.x.x.pack或Geehy.APM32F4xx_DFP.pack
  2. 用解压软件打开.pack文件(本质是 ZIP)
  3. 提取:
    • Device/Include/apm32f10x.h
    • Device/Source/GCC/startup_apm32f10x_md.s(如有 GCC 目录)
    • Device/Source/system_apm32f10x.c

方式三:从 Eclipse 工程中提取 GCC 配置(最省事)

极海 SDK 的 Eclipse 工程基于 GCC 工具链,可直接从中提取.ld链接脚本和编译参数,是最省事的 GCC 兼容来源。

3.4 素材自检清单

  • startup_apm32f10x_md.s— 启动文件(ARM 汇编,GCC 可用)
  • apm32f10x.h— 设备头文件
  • system_apm32f10x.c— 系统初始化
  • core_cm3.h/core_cm4.h— CMSIS 核心头文件
  • 标准外设库Libraries/APM32F10x_StdPeriphDriver/目录
  • 链接脚本apm32f10x_flash.ld(需自行编写,见第 5 章)
  • arm-none-eabi-gcc可执行

4. 工程目录结构

推荐按重编频率分层:

apm32-nimmake/ |-- Nimmake.py # 构建脚本(唯一需要维护的文件) |-- App/ # 应用代码(最常改) | |-- inc/main.h | |-- src/main.c |-- Device/ # 启动 + 系统初始化(几乎不改) | |-- startup_apm32f10x_md.s | |-- system_apm32f10x.c |-- Libraries/ # 标准外设库(几乎不改) | |-- APM32F10x_StdPeriphDriver/ | |-- inc/ (apm32f10x.h / _gpio.h / _usart.h ...) | |-- src/ (apm32f10x_gpio.c / _usart.c ...) |-- CMSIS/ # CMSIS 核心(几乎不改) | |-- Include/core_cm3.h | |-- Device/apm32f10x.h |-- ld/ # 链接脚本 | |-- apm32f10x_flash.ld |-- tools/ # 烧录脚本(可选) |-- openocd_apm32.cfg

5. 链接脚本编写

以下为 APM32F103RCT6(256 KB Flash + 48 KB SRAM)示例:

/* ld/apm32f10x_flash.ld */ ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 48K } _estack = ORIGIN(SRAM) + LENGTH(SRAM); SECTIONS { .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) . = ALIGN(4); } > FLASH .text : { . = ALIGN(4); *(.text) *(.text.*) *(.rodata) *(.rodata.*) . = ALIGN(4); _etext = .; } > FLASH .data : { . = ALIGN(4); _sdata = .; *(.data) *(.data.*) . = ALIGN(4); _edata = .; } > SRAM AT > FLASH _sidata = LOADADDR(.data); .bss : { . = ALIGN(4); _sbss = .; *(.bss) *(.bss.*) *(COMMON) . = ALIGN(4); _ebss = .; } > SRAM . = ALIGN(4); _end = .; }

注意:APM32 Flash 起始地址为0x08000000(与 STM32 一致),不同型号 Flash/SRAM 大小不同,需根据具体芯片调整LENGTH。


6. 完整 Nimmake.py

# Nimmake.pyfromnimmakeimport*# --- 1. 芯片预设 ---Chip=CORTEX_M3_CFG.clone()Chip.CPU="cortex-m3"Chip.FPU=NoneChip.ABI="soft"Chip.MACROS=["APM32F10X_MD",# 型号宏,决定头文件中寄存器定义"USE_STDPERIPH_DRIVER",# 使用标准外设库]# --- 2. 工具链 ---Toolchain=Update(Chip)Refresh()# --- 3. 编译选项 ---CFLAGS=["-O2","-g","-ffunction-sections","-fdata-sections","-Wall","-Wno-unused-function",]LDFLAGS=["-T"+ROOT+"/ld/apm32f10x_flash.ld","-Wl,--gc-sections","-Wl,-Map="+BUILD+"/firmware.map","--specs=nano.specs","--specs=nosys.specs",]# --- 4. Party:标准外设库(静态库) ---StdPeriph=Party("StdPeriph")StdPeriph.Sources=Glob("Libraries/APM32F10x_StdPeriphDriver/src/*.c")StdPeriph.Includes=["Libraries/APM32F10x_StdPeriphDriver/inc","CMSIS/Include","CMSIS/Device",]StdPeriph.BuildType="staticlib"# --- 5. Party:Device(启动文件 + 系统初始化) ---Device=Party("Device")Device.Sources=["Device/startup_apm32f10x_md.s","Device/system_apm32f10x.c",]Device.Includes=["Libraries/APM32F10x_StdPeriphDriver/inc","CMSIS/Include","CMSIS/Device",]Device.Dependencies=[StdPeriph]# --- 6. Party:App(应用代码) ---App=Party("App")App.Sources=Glob("App/src/*.c")App.Includes=["App/inc","Libraries/APM32F10x_StdPeriphDriver/inc","CMSIS/Include","CMSIS/Device",]App.Dependencies=[Device]# --- 7. Program ---Program=Prog("firmware")Program.Parties=[App]Program.CFLAGS=CFLAGS Program.LDFLAGS=LDFLAGS# --- 8. 后处理:生成 bin + 打印体积 ---@AfterBuilddefpost_build(ctx):elf=ctx.Output Run(f"arm-none-eabi-objcopy -O binary{elf}{elf.replace('.elf','.bin')}")Run(f"arm-none-eabi-objcopy -O ihex{elf}{elf.replace('.elf','.hex')}")Run(f"arm-none-eabi-size{elf}")# --- 9. 烧录目标 ---@Phonydefflash(ctx):elf=ctx.Program.Output Run("openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg ""-c init -c 'reset halt' "f"-c 'flash write_image erase{elf}' ""-c 'reset run' -c exit")@Phonydefgdb(ctx):Run(f"arm-none-eabi-gdb{ctx.Program.Output}")@Phonydeferase(ctx):Run("openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfg ""-c init -c 'reset halt' -c 'stm32f1x mass_erase 0' -c exit")DefaultTarget(Program)

7. 逐段解析

7.1 芯片预设

Chip=CORTEX_M3_CFG.clone()Chip.CPU="cortex-m3"Chip.FPU=NoneChip.ABI="soft"

APM32F103 是 Cortex-M3,无 FPU。若使用 APM32F407(M4F),则改为CORTEX_M4F_CFG,FPU = "fpv4-sp-d16",ABI = "hard"。

7.2 编译宏

Chip.MACROS=["APM32F10X_MD",# 型号宏,决定 Flash/SRAM 大小"USE_STDPERIPH_DRIVER",# 启用标准外设库]

APM32F10X_MD表示 Medium Density(128~256KB Flash),小容量用APM32F10X_LD,大容量改为APM32F10X_HD。

7.3 链接脚本

LDFLAGS=["-T"+ROOT+"/ld/apm32f10x_flash.ld","-Wl,--gc-sections","--specs=nano.specs","--specs=nosys.specs",]
  • nano.specs:使用 newlib-nano,显著减小体积。
  • nosys.specs:提供_sbrk/_write等系统调用的空实现。

7.4 APM32 专属注意点

1. SWD 调试接口默认关闭

APM32F103 上电复位后 SWD 口默认关闭,必须通过 Option Byte 编程手动开启,否则首次无法连接调试器。解锁方式:

  • 使用 Keil Flash Download,在Settings -> Flash Download -> Program/erase options中勾选 “Enable SWD” 并点击 “Update”
  • 或使用 J-Link Commander 执行exec SetTargetInterface SWD后再烧录

2. SysTick 时钟源默认不同

APM32 的SysTick->CTRL复位后CLKSOURCE位默认为 0(外部时钟源),而 STM32 默认为 1(内核时钟源)。使用 FreeRTOS 时需在SysTick_Config()之后强制设置:

SysTick->CTRL|=SysTick_CTRL_CLKSOURCE_Msk;// 强制选择内核时钟

3. NVIC 优先级分组

APM32F103 的 NVIC 优先级分组策略默认为 Group 4(仅抢占优先级,无子优先级),而 STM32F103 默认为 Group 2。若未在SystemInit()中调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4),则NVIC_SetPriority()设置的优先级值会被截断。

4._estack与链接脚本

链接脚本中必须定义_estack,启动文件中通过它初始化栈指针。如果 SRAM 大小与实际芯片不符,上电即 HardFault。

5. VTOR 向量表偏移

做 Bootloader + App 时,App 中需重新设置 VTOR:

SCB->VTOR=APP_FLASH_OFFSET;

8. 常用命令

命令说明
nimmake编译默认目标
nimmake --dry-run预览命令,不实际执行
nimmake --compiledb生成compile_commands.json
nimmake --cache启用编译缓存
nimmake -n仅编译,不链接
nimmake -c清理构建目录
nimmake flash烧录固件
nimmake erase擦除芯片
nimmake gdb启动 GDB

9. 烧录方案

9.1 OpenOCD + CMSIS-DAP / J-Link

APM32F103 与 STM32F103 内存布局一致,可直接使用stm32f1x.cfg:

openocd-finterface/cmsis-dap.cfg\-ftarget/stm32f1x.cfg\-c"program firmware.elf verify reset exit"

APM32F407 则使用stm32f4x.cfg。

9.2 PyOCD

APM32F411 Tiny 等板载 CMSIS-DAP 调试器,可使用 PyOCD:

pyocd flash-tapm32f411xx firmware.hex

9.3 J-Link

需先安装极海 Flash 算法(.flm)到 J-Link 安装目录:

JLinkExe-deviceAPM32F103RC-ifSWD-speed4000-CommanderScriptflash.jlink

flash.jlink内容:

h loadbin firmware.bin 0x08000000 r g q

9.4 官方 ISP 工具

极海提供GeehyProg烧录工具和APM32 MCU ISP串口下载工具,支持 UART/USB ISP 模式。将 BOOT0 拉高后上电即可进入 ISP。


10. printf 重定向

使用nosys.specs时,需重写_write才能通过 USART 输出:

// App/src/uart_printf.c#include<stdio.h>#include<unistd.h>#include"apm32f10x.h"#include"apm32f10x_usart.h"int_write(intfd,char*ptr,intlen){if(fd==STDOUT_FILENO||fd==STDERR_FILENO){for(inti=0;i<len;i++){while(USART_ReadStatusFlag(USART1,USART_FLAG_TXBE)==RESET);USART_TxData(USART1,ptr[i]);}returnlen;}return-1;}

若使用nosys.specs且不重写_write,printf 会静默丢弃输出,不会报错。


11. 型号迁移

型号内核预设宏FlashSRAM备注
APM32F030M0+CORTEX_M0PLUS_CFGAPM32F03032K~64K4K无 FPU,ABI=soft,FPU=None
APM32F035M0+CORTEX_M0PLUS_CFGAPM32F03564K8K无 FPU
APM32F103 LDM3CORTEX_M3_CFGAPM32F10X_LD16K~32K10K小容量
APM32F103 MDM3CORTEX_M3_CFGAPM32F10X_MD64K~256K48K中容量,主力型号
APM32F103 HDM3CORTEX_M3_CFGAPM32F10X_HD384K~512K64K大容量
APM32F405M4FCORTEX_M4F_CFGAPM32F405512K~1M192KFPU=fpv4-sp-d16,ABI=hard
APM32F407M4FCORTEX_M4F_CFGAPM32F407512K~1M192K~256K同上
APM32F411M4FCORTEX_M4F_CFGAPM32F411128K~256K96K小封装高性价比
APM32A103M3CORTEX_M3_CFGAPM32A103128K~256K48K车规,引脚兼容 F103

迁移时只需修改Nimmake.py中的三处:

Chip=CORTEX_M4F_CFG.clone()# 1. 换内核预设Chip.CPU="cortex-m4";Chip.FPU="fpv4-sp-d16";Chip.ABI="hard"Chip.MACROS=["APM32F407","USE_STDPERIPH_DRIVER"]# 2. 换宏LDFLAGS=["-T"+ROOT+"/ld/apm32f407_flash.ld",...]# 3. 换链接脚本

M0+ 系列特别注意:无 FPU,必须把FPU = None、ABI = "soft",且链接脚本 Flash/SRAM 很小,注意代码裁剪。


12. Bootloader + App

12.1 Flash 分区示例

区域地址大小用途
Bootloader0x0800000016 KB引导程序
App0x08004000剩余应用程序

12.2 App 链接脚本修改

/* ld/apm32f103_app.ld */ MEMORY { FLASH (rx) : ORIGIN = 0x08004000, LENGTH = 240K SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 48K }

12.3 App 中设置 VTOR 与跳转

// App/main.c 最前intmain(void){SCB->VTOR=0x08004000;// 偏移向量表SystemInit();// ...}// Bootloader 中跳转到 Appvoidjump_to_app(uint32_tapp_addr){uint32_tapp_sp=*(__IOuint32_t*)app_addr;uint32_tapp_pc=*(__IOuint32_t*)(app_addr+4);void(*app_entry)(void)=(void(*)(void))app_pc;__set_MSP(app_sp);app_entry();}

12.4 Nimmake 多目标

Bootloader=Prog("bootloader")Bootloader.Parties=[App_BL]Bootloader.LDFLAGS=["-T"+ROOT+"/ld/apm32f103_boot.ld"]AppFW=Prog("app")AppFW.Parties=[App_Main]AppFW.LDFLAGS=["-T"+ROOT+"/ld/apm32f103_app.ld"]DefaultTarget(Bootloader)# 也可 DefaultTarget([Bootloader, AppFW]) 同时编译

13. FreeRTOS 集成

将 FreeRTOS 作为独立 Party:

FreeRTOS=Party("FreeRTOS")FreeRTOS.Sources=["Middlewares/FreeRTOS/source/croutine.c","Middlewares/FreeRTOS/source/list.c","Middlewares/FreeRTOS/source/queue.c","Middlewares/FreeRTOS/source/tasks.c","Middlewares/FreeRTOS/source/timers.c","Middlewares/FreeRTOS/portable/GCC/ARM_CM3/port.c","Middlewares/FreeRTOS/portable/MemMang/heap_4.c",]FreeRTOS.Includes=["Middlewares/FreeRTOS/include","Middlewares/FreeRTOS/portable/GCC/ARM_CM3",]

踩坑点:

  1. 中断向量重定向:port.c中xPortPendSVHandler/xPortSysTickHandler必须与启动文件中的PendSV_Handler/SysTick_Handler向量名一致。若启动文件用的是xPortPendSVHandler命名,则无需改;否则需在 App 中断入口里转发。
  2. SysTick 时钟源:见 7.4 第 2 点,APM32 必须强制内核时钟。
  3. heap_4 的_sbrk:需在链接脚本里定义_heap_start/_heap_end,或提供_sbrk实现。

14. FAQ

问题原因 / 解决
启动文件报Error: bad register name拷到的是 Keil/IAR 版启动文件,换成 GCC 版startup_*_gcc.s或 Eclipse 工程里的.s
undefined reference to _sbrk用了nano.specs但没加--specs=nosys.specs,或用了 heap_4 未实现_sbrk
printf 无输出但不报错未重写_write(见第 10 章)
上电即 HardFault链接脚本 SRAM_estack与实际芯片不符
-T没生效 / Flash 地址错链接脚本路径用了相对路径且没在工程根目录执行,改用ROOT绝对拼接
调试器连不上(SWD 无法识别)APM32 SWD 默认关闭,需先通过 Option Byte 开启(见 7.4)
用 STM32 cfg 烧录失败 / ID 不认APM32 Device ID 与 STM32 不同,OpenOCD 可能报 unknown id,可加-c "set CHIPID ..."或用官方工具
ABI mismatch 链接告警库和 App 用了不同-mfloat-abi,全局统一ABI
FreeRTOS 任务不切换SysTick 时钟源未设内核时钟;或 PendSV 优先级未设为最低
换 F4xx 后链接报 FPU 相关错忘记改FPU/ABI为 hard
map 文件没生成LDFLAGS 里加-Wl,-Map=...(注意逗号非空格)

15. 体积分析

arm-none-eabi-size firmware.elf# text / data / bss 三段arm-none-eabi-nm-S--size-sort firmware.elf|tail-20# 最大符号

裁剪手段:

  • -ffunction-sections -fdata-sections+-Wl,--gc-sections
  • --specs=nano.specs(newlib-nano)
  • 标准外设库按需编译,只把用到的.c加进Sources
  • -Os优化体积
  • 通过firmware.map定位大对象

16. 最小可跑 Checklist

  1. 装好arm-none-eabi-gcc与pip install nimmake
  2. 拿到 GCC 版启动文件、system_*.c、设备头文件、标准外设库
  3. 编写/提取apm32f10x_flash.ld,确认_estack与 Flash 起始0x08000000
  4. 写好Nimmake.py,设置正确的Chip预设与MACROS
  5. nimmake --dry-run检查编译/链接命令是否符合预期
  6. nimmake编译,生成firmware.elf/.bin/.hex
  7. 确认 Option Byte 已开启 SWD
  8. nimmake flash烧录(或 J-Link / 官方 ISP)
  9. 复位运行,LED 闪 / 串口打印即成功

本指南与《灵动微 MM32》《雅特力 AT32》《国民技术 N32》《华大 HC32》四篇系列保持一致结构,可直接横向对比迁移。

返回列表