极海 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,寄存器定义 |
| 系统初始化 | SDK | system_apm32f10x.c/system_apm32f4xx.c,含SystemInit() |
| CMSIS 核心 | SDK | core_cm3.h/core_cm4.h等 |
| 标准外设库 | SDK | GPIO、USART、SPI、I2C、TIM、ADC 等 |
| 中间件 | SDK | FreeRTOS、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=hard | CORTEX_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--version3.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--version3.3 获取 APM32 SDK 素材
方式一:从极海官网下载 SDK
- 访问极海半导体官网(www.geehy.com)
- 进入对应芯片页面(如 APM32F103 / APM32F407)
- 下载SDK(如
APM32F10x_SDK_V1.8.zip或APM32F4xx_SDK) - 解压后得到:
Libraries/— 标准外设库、CMSIS、DeviceExamples/— 示例工程(含 MDK/IAR/Eclipse 三平台)Middlewares/— FreeRTOS、FatFs、LwIP 等中间件
方式二:从 DFP 包中提取
- 下载
Geehy.APM32F10x_DFP.x.x.x.pack或Geehy.APM32F4xx_DFP.pack - 用解压软件打开
.pack文件(本质是 ZIP) - 提取:
Device/Include/apm32f10x.hDevice/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.cfg5. 链接脚本编写
以下为 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.hex9.3 J-Link
需先安装极海 Flash 算法(.flm)到 J-Link 安装目录:
JLinkExe-deviceAPM32F103RC-ifSWD-speed4000-CommanderScriptflash.jlinkflash.jlink内容:
h loadbin firmware.bin 0x08000000 r g q9.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. 型号迁移
| 型号 | 内核 | 预设 | 宏 | Flash | SRAM | 备注 |
|---|---|---|---|---|---|---|
| APM32F030 | M0+ | CORTEX_M0PLUS_CFG | APM32F030 | 32K~64K | 4K | 无 FPU,ABI=soft,FPU=None |
| APM32F035 | M0+ | CORTEX_M0PLUS_CFG | APM32F035 | 64K | 8K | 无 FPU |
| APM32F103 LD | M3 | CORTEX_M3_CFG | APM32F10X_LD | 16K~32K | 10K | 小容量 |
| APM32F103 MD | M3 | CORTEX_M3_CFG | APM32F10X_MD | 64K~256K | 48K | 中容量,主力型号 |
| APM32F103 HD | M3 | CORTEX_M3_CFG | APM32F10X_HD | 384K~512K | 64K | 大容量 |
| APM32F405 | M4F | CORTEX_M4F_CFG | APM32F405 | 512K~1M | 192K | FPU=fpv4-sp-d16,ABI=hard |
| APM32F407 | M4F | CORTEX_M4F_CFG | APM32F407 | 512K~1M | 192K~256K | 同上 |
| APM32F411 | M4F | CORTEX_M4F_CFG | APM32F411 | 128K~256K | 96K | 小封装高性价比 |
| APM32A103 | M3 | CORTEX_M3_CFG | APM32A103 | 128K~256K | 48K | 车规,引脚兼容 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 分区示例
| 区域 | 地址 | 大小 | 用途 |
|---|---|---|---|
| Bootloader | 0x08000000 | 16 KB | 引导程序 |
| App | 0x08004000 | 剩余 | 应用程序 |
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",]踩坑点:
- 中断向量重定向:
port.c中xPortPendSVHandler/xPortSysTickHandler必须与启动文件中的PendSV_Handler/SysTick_Handler向量名一致。若启动文件用的是xPortPendSVHandler命名,则无需改;否则需在 App 中断入口里转发。 - SysTick 时钟源:见 7.4 第 2 点,APM32 必须强制内核时钟。
- 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
- 装好
arm-none-eabi-gcc与pip install nimmake - 拿到 GCC 版启动文件、
system_*.c、设备头文件、标准外设库 - 编写/提取
apm32f10x_flash.ld,确认_estack与 Flash 起始0x08000000 - 写好
Nimmake.py,设置正确的Chip预设与MACROS nimmake --dry-run检查编译/链接命令是否符合预期nimmake编译,生成firmware.elf/.bin/.hex- 确认 Option Byte 已开启 SWD
nimmake flash烧录(或 J-Link / 官方 ISP)- 复位运行,LED 闪 / 串口打印即成功
本指南与《灵动微 MM32》《雅特力 AT32》《国民技术 N32》《华大 HC32》四篇系列保持一致结构,可直接横向对比迁移。