国民技术 N32 使用 Nimmake 编译指南(详细版)
主线型号:N32G45x / Cortex-M4F
适用系列:N32G45x、N32G43x、N32L43x、N32L40x、N32G03x
构建方式:arm-none-eabi-gcc+ Nimmake(Python 构建脚本)
1. 可行性结论
国民技术 N32 系列基于 ARM Cortex-M 内核,官方 SDK 和 N32Cube 工具已提供完整的 GCC 支持,使用 Nimmake 进行裸机编译完全可行。
官方已提供的 GCC 素材:
| 素材 | 位置 | 说明 |
|---|---|---|
| 启动文件 | Libraries/CMSIS/Device/Nationstech/N32G45x/Source/GCC/startup_n32g45x_gcc.s | ARM 汇编语法,GCC 可用 |
| 链接脚本 | Libraries/CMSIS/Device/Nationstech/N32G45x/Source/GCC/n32g45x_flash.ld | Flash + SRAM 布局 |
| CMSIS 头文件 | Libraries/CMSIS/Device/Nationstech/N32G45x/Include/n32g45x.h | 寄存器定义 |
| 系统初始化 | Libraries/CMSIS/Device/Nationstech/N32G45x/Source/system_n32g45x.c | 时钟配置、SystemInit |
| 标准外设库 | Libraries/N32G45x_StdPeriph_Driver/ | GPIO、USART、SPI、I2C、TIM 等 |
| GCC 示例工程 | Projects/N32G45x_StdPeriph_Examples/ | 部分示例含 GCC/MDK-ARM 双版本 |
N32Cube 支持:N32Cube 图形化工具可生成 Makefile 工程,包含启动文件、链接脚本、外设初始化代码,可作为 Nimmake 工程的素材来源。
2. 为什么用 Nimmake
| 痛点 | Makefile 写法 | Nimmake 写法 |
|---|---|---|
| 芯片参数 | 手动写-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard | CORTEX_M4F_CFG.clone()一行预设 |
| HAL 源文件列表 | 手动 glob 或逐行写,换库就改 | Parties自动扫描src/**/*.c |
| 换型号 | 改 CPU、FPU、链接脚本、启动文件、宏,多处同步 | 只改Chip预设和Parties路径 |
| 头文件路径 | -I逐条写,漏一个就报错 | Parties自动收集inc/ |
| 多目标 | 每个 target 写一套规则 | 多个Program对象即可 |
| 缓存 | 需要手动配ccache | --cache内置支持 |
| 跳转补全 | 无 | --compiledb生成compile_commands.json,VSCode/CLion 可用 |
| 烧录命令 | 手写openocd -f ... | Phony目标封装,nimmake flash |
| 跨平台 | Windows 下路径分隔符、工具链路径问题 | Nimmake 内部处理路径 |
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# Linux:从 xpack-dev-tools 发布页下载解压验证:
arm-none-eabi-gcc--versionarm-none-eabi-size--versionarm-none-eabi-objcopy--versionNimmake 会自动检测工具链,无需手动配置 PATH(但建议加上方便调试)。
3.3 获取 N32 SDK 素材
方式一:从官方 SDK 获取
- 从国民技术官网或 GitHub 下载 N32G45x SDK
- 关注
Libraries/CMSIS/、Libraries/N32G45x_StdPeriph_Driver/、Projects/
方式二:用 N32Cube 生成
- 选择 N32G45x 系列芯片,配置时钟/GPIO/外设
- 生成Makefile工程
- 提取
startup_n32g45x_gcc.s、n32g45x_flash.ld、Core/Inc|Src、Drivers/
3.4 素材自检清单
startup_n32g45x_gcc.s— GCC 版启动文件(不是 Keil/IAR 版)n32g45x_flash.ld— 链接脚本n32g45x.h— 设备头文件system_n32g45x.c— 系统初始化- 标准外设库
.c文件(至少 GPIO + USART 用于测试) arm-none-eabi-gcc可执行
4. 工程目录结构
推荐按重编频率分层:
n32-nimmake/ ├── Nimmake.py # 构建脚本(唯一需要维护的文件) ├── App/ # 应用代码(最常改) │ ├── inc/main.h │ └── src/main.c ├── Device/ # 芯片启动 + 系统初始化(几乎不改) │ ├── startup_n32g45x_gcc.s │ └── system_n32g45x.c ├── Libraries/ # 标准外设库(几乎不改) │ └── N32G45x_StdPeriph_Driver/ │ ├── inc/ (n32g45x.h, n32g45x_gpio.h, n32g45x_usart.h ...) │ └── src/ (n32g45x_gpio.c, n32g45x_usart.c ...) ├── ld/ # 链接脚本 │ └── n32g45x_flash.ld └── tools/ # 烧录脚本(可选) └── openocd_n32.cfg分层理由:App/每次改代码只重编这一层;Device/和Libraries/作为静态库首次编译后缓存;ld/独立存放,换型号时只需替换链接脚本。
5. 完整 Nimmake.py
# Nimmake.pyfromnimmakeimport*# 1. 芯片预设Chip=CORTEX_M4F_CFG.clone()Chip.CPU="cortex-m4"Chip.FPU="fpv4-sp-d16"Chip.ABI="hard"Chip.MACROS=["N32G45X","USE_STDPERIPH_DRIVER"]# 2. 工具链Toolchain=Update(Chip)Refresh()# 3. 编译选项CFLAGS=["-O2","-g","-ffunction-sections","-fdata-sections","-Wall","-Wno-unused-function"]LDFLAGS=["-T"+ROOT+"/ld/n32g45x_flash.ld","-Wl,--gc-sections","-Wl,-Map="+BUILD+"/firmware.map","--specs=nano.specs","--specs=nosys.specs",]# 4. Party:标准外设库(静态库)StdLib=Party("StdLib")StdLib.Sources=Glob("Libraries/N32G45x_StdPeriph_Driver/src/*.c")StdLib.Includes=["Libraries/N32G45x_StdPeriph_Driver/inc","Libraries/CMSIS/Device/Nationstech/N32G45x/Include","Libraries/CMSIS/Core/Include",]StdLib.BuildType="staticlib"# 5. Party:Device(启动文件 + 系统初始化)Device=Party("Device")Device.Sources=["Device/startup_n32g45x_gcc.s","Device/system_n32g45x.c"]Device.Includes=["Libraries/CMSIS/Device/Nationstech/N32G45x/Include","Libraries/CMSIS/Core/Include","Libraries/N32G45x_StdPeriph_Driver/inc",]Device.Dependencies=[StdLib]# 6. Party:App(应用代码)App=Party("App")App.Sources=Glob("App/src/*.c")App.Includes=["App/inc","Libraries/N32G45x_StdPeriph_Driver/inc","Libraries/CMSIS/Device/Nationstech/N32G45x/Include","Libraries/CMSIS/Core/Include",]App.Dependencies=[Device]# 7. ProgramProgram=Prog("firmware")Program.Parties=[App]Program.CFLAGS=CFLAGS Program.LDFLAGS=LDFLAGS# 8. 后处理:生成 bin + 打印体积@AfterBuilddefpost_build(ctx):elf=ctx.Output Run("arm-none-eabi-objcopy -O binary "+elf+" "+elf[:-4]+".bin")Run("arm-none-eabi-objcopy -O ihex "+elf+" "+elf[:-4]+".hex")Run("arm-none-eabi-size "+elf)# 9. 烧录目标@Phonydefflash(ctx):elf=ctx.Program.Output cmd=('openocd -f interface/cmsis-dap.cfg -f target/stm32f4x.cfg ''-c "init" -c "reset halt" ''-c "flash write_image erase " + elf + " -c "reset run" -c "exit"')Run(cmd)@Phonydefgdb(ctx):Run("arm-none-eabi-gdb "+ctx.Program.Output)@Phonydeferase(ctx):cmd=('openocd -f interface/cmsis-dap.cfg -f target/stm32f4x.cfg ''-c "init" -c "reset halt" -c "stm32f4x mass_erase 0" -c "exit"')Run(cmd)DefaultTarget(Program)6. 逐段解析
6.1 芯片预设
N32G45x 是 Cortex-M4F,带单精度 FPU。CORTEX_M4F_CFG已包含-mthumb -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard,无需手写。
6.2 编译宏
N32G45X:型号宏,决定头文件中寄存器定义USE_STDPERIPH_DRIVER:使用标准外设库(若用 HAL 库改为USE_HAL_DRIVER)
6.3 链接脚本
--gc-sections:配合-ffunction-sections -fdata-sections裁剪未使用代码nano.specs:使用 newlib-nano,显著减小体积nosys.specs:提供_sbrk/_write等系统调用空实现
6.4 Party 分层
| Party | 类型 | 说明 |
|---|---|---|
| StdLib | staticlib | 标准外设库,编译为.a,几乎不重编 |
| Device | object | 启动文件 + system_n32g45x.c |
| App | object | 用户代码,每次修改只重编这一层 |
6.5 N32 专属注意点
1. 启动文件必须用 GCC 版:SDK 中可能同时存在startup_n32g45x_keil.s(Keil 语法,GCC 不能用)与startup_n32g45x_gcc.s(GCC 兼容,选这个)。
2._estack与链接脚本:链接脚本中必须有_estack = ORIGIN(RAM) + LENGTH(RAM);,启动文件通过_estack初始化栈指针。RAM 大小与实际芯片不符会直接 HardFault。
3. VTOR 向量表偏移:system_n32g45x.c的SystemInit()会设置 VTOR;做 Bootloader + App 时 App 需重设SCB->VTOR = APP_FLASH_OFFSET;。
7. 常用命令
| 命令 | 说明 |
|---|---|
nimmake | 编译默认目标 |
nimmake --dry-run | 预览命令,不实际执行 |
nimmake --compiledb | 生成compile_commands.json |
nimmake --cache | 启用编译缓存 |
nimmake -n | 仅编译,不链接 |
nimmake -c | 清理构建目录 |
nimmake flash | 烧录固件 |
nimmake erase | 擦除芯片 |
nimmake gdb | 启动 GDB |
8. 烧录方案
8.1 OpenOCD + CMSIS-DAP / NS-Link
N32G45x 与 STM32F407 兼容,多数可用stm32f4x.cfg:
openocd-finterface/cmsis-dap.cfg-ftarget/stm32f4x.cfg\-c"program firmware.elf verify reset exit"若无 N32 专用 target,可用target/stm32f4x.cfg或参考 Flash 控制器寄存器自定义 cfg。
8.2 官方 NSING MCU Download Tool (UART/USB DFU)
NSLink_Download-pCOM3-b115200-ffirmware.bin8.3 J-Link
JLinkExe-deviceN32G45x-ifSWD-speed4000-CommanderScriptflash.jlinkflash.jlink:h/loadbin firmware.bin 0x08000000/r/g/q
9. printf 重定向
使用nano.specs+nosys.specs时,需重写_write才能通过 USART 输出:
// App/src/uart_printf.c#include<stdio.h>#include<unistd.h>#include"n32g45x.h"#include"n32g45x_usart.h"int_write(intfd,char*ptr,intlen){if(fd==STDOUT_FILENO||fd==STDERR_FILENO){for(inti=0;i<len;i++){while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);USART_SendData(USART1,ptr[i]);}returnlen;}return-1;}若用
nosys.specs且不重写_write,printf 会静默丢弃输出,不报错。
10. 型号迁移
| 型号 | 内核 | 预设 | 宏 | Flash | SRAM | 链接脚本 |
|---|---|---|---|---|---|---|
| N32G45x | M4F | CORTEX_M4F_CFG | N32G45X | 512K/1M | 144K | n32g45x_flash.ld |
| N32G43x | M4F | CORTEX_M4F_CFG | N32G43X | 128K | 32K | n32g43x_flash.ld |
| N32L43x | M4 | CORTEX_M4_CFG | N32L43X | 256K | 48K | n32l43x_flash.ld |
| N32G03x | M0 | CORTEX_M0_CFG | N32G03X | 64K | 8K | n32g03x_flash.ld |
迁移时只需修改Nimmake.py中的Chip预设、Chip.MACROS、LDFLAGS的-T链接脚本。
11. Bootloader + App
11.1 Flash 分区示例
| 区域 | 地址 | 大小 | 用途 |
|---|---|---|---|
| Bootloader | 0x08000000 | 32 KB | 引导程序 |
| App | 0x08008000 | 剩余 | 应用程序 |
11.2 App 链接脚本修改
/* n32g45x_app.ld */ MEMORY { FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 480K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 144K }11.3 App 中设置 VTOR
intmain(void){SCB->VTOR=0x08008000;// 偏移向量表SystemInit();// ...}11.4 Nimmake 多目标
Bootloader=Prog("bootloader")Bootloader.Parties=[App_BL]Bootloader.LDFLAGS=["-T"+ROOT+"/ld/n32g45x_boot.ld","-Wl,--gc-sections","--specs=nano.specs","--specs=nosys.specs",]AppFw=Prog("app")AppFw.Parties=[App]AppFw.LDFLAGS=["-T"+ROOT+"/ld/n32g45x_app.ld","-Wl,--gc-sections","--specs=nano.specs","--specs=nosys.specs",]DefaultTargets=[Bootloader,AppFw]12. FreeRTOS 集成
将 FreeRTOS 作为独立 Party,源码目录(croutine.c、event_groups.c、list.c、queue.c、stream_buffer.c、tasks.c、timers.c)+ 对应内核的portable/GCC/ARM_CM4F+portmacro。
FreeRTOS=Party("FreeRTOS")FreeRTOS.Sources=Glob("FreeRTOS/*.c")+\ Glob("FreeRTOS/portable/GCC/ARM_CM4F/*.c")FreeRTOS.Includes=["FreeRTOS/include","FreeRTOS/portable/GCC/ARM_CM4F","FreeRTOSConfig",# FreeRTOSConfig.h]FreeRTOS.Dependencies=[Device]App.Dependencies=[Device,FreeRTOS]中断配合要点:在FreeRTOSConfig.h中把xPortPendSVHandler/xPortSysTickHandler/xPortSVCHandler映射到PendSV_Handler/SysTick_Handler/SV_Handler,并确保 N32 启动文件向量表中的这几个符号不与 FreeRTOS 端口冲突(N32 启动文件里的SysTick_Handler等弱符号会被 FreeRTOS 强符号覆盖)。
13. FAQ
- 启动文件报语法错:误用了 Keil/IAR 版
.s,换成*_gcc.s。 - HAL 与标准库混用冲突:同一工程只保留一套驱动,宏也对应
USE_HAL_DRIVER或USE_STDPERIPH_DRIVER。 - ABI mismatch(hard/soft float):
Chip.ABI、-mfloat-abi与所有预编译库必须一致。 - OpenOCD 缺 flash 算法:改用
stm32f4xtarget 或补 N32 flash 算法。 -T没生效:链接脚本路径拼错或ROOT/BUILD变量未展开,用--dry-run看实际命令。- 上电即 HardFault:
_estack/RAM 大小与型号不符,或栈/堆区被链接脚本压缩。 - printf 无输出:未重写
_write,或 USART 未初始化。 Refresh()漏写:Update(Chip)后未Refresh(),工具链/选项不生效。- 跳转/补全乱码:未生成
compile_commands.json,用nimmake --compiledb。 - 静态库链接顺序错:用
Dependencies声明顺序,不要手写.a顺序。 - 换型号后外设头找不到:同步换
Includes里的 CMSIS Device 目录。
14. 体积分析
arm-none-eabi-size firmware.elf看 text/data/bss。-Wl,-Map=firmware.map生成映射文件,按模块排查大对象。--gc-sections+-ffunction-sections -fdata-sections裁剪未用代码。--specs=nano.specs换 newlib-nano;去掉不用的printf浮点支持(_printf_float_support=0)。- 只启用需要的标准外设
.c,避免整个 StdPeriph 全量链接。
15. 最小可跑 Checklist
- 装好
arm-none-eabi-gcc与nimmake。 - 拿到 GCC 版启动文件
startup_n32g45x_gcc.s。 - 拿到
n32g45x_flash.ld且 RAM/FLASH 大小与型号一致。 - 放入
n32g45x.h、system_n32g45x.c、GPIO 外设库。 - 写好
Nimmake.py(芯片预设 + 工具链 + Party + Program)。 nimmake编译出firmware.elf。nimmake flash或 OpenOCD 烧录。- 上电点亮 LED。
- 串口
printf重定向后确认日志输出。
全部通过后,即可在此基础上扩展 USART、SPI、FreeRTOS、Bootloader 等模块。