简介:面向Nordic 52832低功耗蓝牙SoC开发者的GCC编译环境搭建资料合集,涵盖从工具链安装、环境变量配置到固件编译下载的完整流程,特别适合需要在Windows/Linux下使用开源工具链开发BLE物联网设备的嵌入式工程师。包体共27037个文件,约255.99MB,主要有C/C++头文件与源码、Python辅助脚本、ARM交叉编译库与链接脚本、编译工具可执行程序以及HTML/PDF/文本说明文档,各类型文件相互配套,可直接用于搭建和验证编译环境。目前已有940人学习下载,内容实用性得到一定认可。资料内含gcc-arm-none-eabi交叉编译工具链、MinGW运行环境以及Nordic相关GCC配置示例,并配有详细的使用说明,能帮助初学者少走弯路,快速上手基于GCC的nRF52832开发流程。 如果你点进这篇文章,大概率和我当初是一样的状态:手里有一块nRF52832开发板,官方资料推荐用SEGGER Embedded Studio或者Keil来编译,可你就是想用GCC这套命令行工具链搞事情。原因可能有很多——想在VSCode里写代码、CI服务器上要跑自动化编译、不想装体积庞大的IDE、或者单纯想弄清楚从源码到hex文件之间到底发生了什么。无论你是哪一种,nRF52832用GCC编译这条路完全走得通,而且把环境折腾明白之后,你对链接脚本、编译参数、固件结构的理解会比只会点IDE按钮深一个档次。
这篇文章是我整理的一份资料合集,以当时用的nRF5_SDK_17.1.0为例,从零开始搭建GCC编译环境、理解SDK的Makefile、编译烧录第一个例程,再到VSCode集成和常见坑的排查。内容不追求高深,但保证每个步骤都能复现,适合刚拿到开发板的小白,也适合从IDE转向命令行的老手。
1. 为什么我坚持用GCC编译nRF52832
1.1 官方工具链之外的另一种选择
先对比一下nRF52832常见的开发方案,你就明白GCC这套东西的价值在哪了。
| 方案 | 是否免费 | 平台 | 工程文件 | 适合场景 |
|---|---|---|---|---|
| Keil MDK | 收费(有试用版) | Windows | .uvprojx | 国内很多教程默认用这个,代码提示一般 |
| IAR EWARM | 收费 | Windows | .ewp | 老项目常用,但工程文件很难迁移 |
| SEGGER Embedded Studio | 免费 | Win/macOS/Linux | .emProject | Nordic官方主推,开箱即用但编辑器体验一般 |
| GCC + Makefile | 完全免费 | Win/macOS/Linux | Makefile | 命令行构建、CI集成、VSCode等任意编辑器 |
我的实际感受是:SEGGER Embedded Studio虽然开箱即用,但它把编译参数、链接脚本、软设备地址这些东西都藏在工程配置里了。一旦你想定制,比如换一个软设备版本、改Flash布局、加入自己写的库,SES的图形界面反而成了阻碍。而GCC + Makefile方案里,一切都以文本形式躺在Makefile和链接脚本里,改了什么一目了然,放进Git里也能方便地做版本对比。
还有一个现实场景是编译服务器。我后来做CI流水线时,服务器是Linux无界面环境,总不可能在上面装一个SES吧?而GCC工具链用命令行一条命令就能部署,项目代码拉下来直接make -j4就可以批量出固件。这种能力是IDE方案给不了的。
1.2 GCC的编译流程值得花十分钟了解
很多初学单片机的人有个误区:点一下Build按钮,固件就出来了。但GCC的编译过程其实分成四个阶段,搞清楚这个对排查问题特别有帮助。
第一个阶段是预处理,编译器展开所有#include和#define。第二个阶段是编译,把预处理后的C代码翻译成汇编。第三个阶段是汇编,把汇编翻译成机器指令的目标文件(.o文件)。最后一个阶段是链接,把所有目标文件和库文件合并,按照链接脚本分配地址,生成最终的hex/elf。
在命令行下你随时可以用类似arm-none-eabi-gcc -E main.c -o main.i查看预处理结果,用-S获得汇编,用-c只编译不链接。有人用gcc -c -e -dd -o main.dd main.c这类命令做分步实验,原理都是这套。搞清楚这个顺序后,遇到报错就能快速判断是发生在哪个阶段:找不到头文件是预处理阶段的事,语法错误是编译阶段的事,undefined reference则是链接阶段的事,排查思路完全不一样。
2. 动手之前必须搞懂的背景知识
2.1 nRF52832、SDK和软设备的关系
nRF52832是一颗Cortex-M4F内核的低功耗蓝牙SoC,主频64MHz,512KB Flash和64KB RAM(xxAA版本)。Cortex-M4F的“F”指的是带硬件浮点单元FPU,这直接影响后面GCC编译参数里的浮点选项。
nRF5 SDK是Nordic官方提供的软件开发套件,里面包含了协议栈源码、外设驱动库、例程工程,以及一个很关键的组成部分——SoftDevice软设备。SoftDevice是Nordic预先编译好、以hex文件形式提供的蓝牙协议栈,用户程序编译的时候并不链接它,但地址空间必须给协议栈留出位置。烧录时也分两步:先烧SoftDevice,再烧应用固件。
很多GCC编译问题的根源就出在对SoftDevice和应用的Flash/RAM地址划分不理解上。以nRF52832最常见的s132软设备为例,在SDK 17.1.0里它的hex文件通常位于components/softdevice/s132/hex/目录下,文件名类似于s132_nrf52_7.3.0_softdevice.hex。这个协议栈会被烧录在Flash的最起始位置,占用的地址范围大约从0x00000000到0x00026000,所以应用程序的起始地址就不能是0x0,而是0x00026000。RAM同理,软设备会占用从0x20000000开始的一段RAM(具体大小取决于版本,我用的7.3.0大概是从0x20002608开始才是应用可用的RAM)。这些数值不需要你背,但要懂得看链接脚本。
2.2 工具链由哪些部分组成
一套可用的GCC编译环境不只是装个编译器那么简单,实际由四个部分组成:
- 交叉编译器:
arm-none-eabi-gcc,负责把C代码编译成ARM Cortex-M的机器码。 - 构建工具:
make(GNU Make),负责解析Makefile里的依赖关系并调用编译器。 - 烧录和命令行工具:
nrfjprog,来自Nordic官方出品的nRF Command Line Tools,负责擦除、烧录、读取芯片。 - 调试驱动和调试器:SEGGER J-Link驱动,配合J-Link调试器使用。
由于nRF52832是ARM内核,我们不能直接用PC上的gcc来编译,而必须用针对嵌入式ARM的交叉编译器。注意“交叉”这个词,意思是编译器运行在x86的PC上,但生成的目标代码是ARM架构的。
3. 一步一步搭建GCC编译环境
3.1 安装ARM GCC工具链
从ARM官方下载GNU Arm Embedded Toolchain,也就是arm-none-eabi-gcc。
Windows平台建议下载.exe安装包,安装时勾选把bin目录加入环境变量PATH。不过我个人反而推荐下载zip压缩包,解压到一个固定路径,比如C:\arm-gcc,然后手动把C:\arm-gcc\bin加入PATH。好处是以后想换版本只需要换目录,不用走卸载程序,多版本共存也方便。
Linux平台下载tar.xz包,解压到/opt下,然后编辑~/.bashrc添加:
export PATH=/opt/gcc-arm-none-eabi-10.3-2021.10/bin:$PATH source ~/.bashrc如果用Ubuntu这类有网络的系统,直接sudo apt install gcc-arm-none-eabi也能装,但版本可能偏旧。离线环境则可以从官网下载rpm安装包,用rpm -ivh命令离线安装。装完务必验证版本:
arm-none-eabi-gcc --version如果显示的还是旧版本,多半是PATH里旧版本的路径排在前面,或者shell缓存了旧的命令路径,执行hash -r刷新缓存,再用which arm-none-eabi-gcc确认到底调用的是哪个路径下的编译器。
这里有一个很多人都会踩的坑:电脑上装了多个版本GCC工具链,明明升级了新版,但一运行arm-none-eabi-gcc --version显示的依然是旧版。主要原因就是PATH顺序不对。Windows下打开“环境变量编辑器”检查PATH中不同GCC目录的排列顺序,把新版本放前面;Linux下用which -a arm-none-eabi-gcc查看所有候选路径。
3.2 安装make和nRF命令行工具
Windows上的make安装有点讲究。SDK的Makefile是基于GNU Make的,所以不能用微软自己的nmake。我比较推荐用MSYS2,安装启动后执行pacman -S make,然后终端里用的命令是mingw32-make。如果你更喜欢命令就叫make,可以把C:\msys64\usr\bin里的make.exe复制到任意一个在PATH中的目录下,或者干脆在终端里设置别名。
macOS比较简单,装了Homebrew后brew install make即可,注意GNU make会被安装为gmake,你可以在SDK的Makefile调用时用gmake,或者做个软链接。
nRF命令行工具是搞nRF52板子必备的,从Nordic官网下载对应安装包。Windows装完、Linux解压后,把bin目录加入PATH。验证:
nrfjprog --version这个工具包里其实还带了mergehex等实用软件,后面合并多个hex文件时会用到。
3.3 准备SDK和例程工程
下载nRF5_SDK_17.1.0,解压到一个纯英文路径下,切记路径里不要出现中文和空格,否则Windows下的make很容易出幺蛾子。
SDK目录非常大,刚开始你会觉得无从下手,但只要关注两个地方就行:components目录存放所有驱动和协议栈源码,examples目录存放各种例程。我们以经典的examples/ble_peripheral/ble_app_uart为例,这个例程实现了通过蓝牙透传串口数据的功能,是大多数人的入门选择。
例程目录下有几个子目录,对应不同开发板和软设备组合。nRF52832的开发板通常是pca10040,软设备是s132,所以我们要找的路径就是:
examples/ble_peripheral/ble_app_uart/pca10040/s132/gcc/这个gcc目录里放着Makefile,这就是我们所有编译工作的核心。
4. 用Makefile编译第一个例程
4.1 读懂SDK自带的Makefile
打开pca10040/s132/gcc目录下的Makefile,你会发现内容不算多,SDK把大量的公共逻辑都封装在components/toolchain/gcc/Makefile.common里了。我们只需要关注几个关键变量。
先来看与我之前讲解对应的关键片段(实际上SDK自带的更复杂,这里做了精简说明):
PROJECT_NAME := ble_app_uart TARGETS := nrf52832_xxaa OUTPUT_DIRECTORY := _build SDK_ROOT := $(abspath ../../../../../..) SOFTDEVICE := s132 # 链接脚本 LINKER_SCRIPT := ble_app_uart_gcc_nrf52.ld # 编译参数 CFLAGS += -mcpu=cortex-m4 -mthumb -mabi=aapcs CFLAGS += -mfloat-abi=hard -mfpu=fpv4-sp-d16 CFLAGS += -DNRF52832_XXAA CFLAGS += -DBOARD_PCA10040 CFLAGS += -DS132 CFLAGS += -DSOFTDEVICE_PRESENT CFLAGS += -DSWI_DISABLE # 头文件路径 CFLAGS += -I$(SDK_ROOT)/components/softdevice/s132/headers # ... include $(SDK_ROOT)/components/toolchain/gcc/Makefile.common这里几个变量是重点。TARGETS定义了目标芯片型号,nrf52832_xxaa对应512KB Flash版本的nRF52832。SDK_ROOT是SDK的根目录路径,原厂默认用相对路径,从gcc目录往上跳6级刚好到达SDK根目录,但这个写法要求整个SDK目录结构不能移动。如果你把例程复制到别处,要么修改相对路径,要么直接改成绝对路径,比如SDK_ROOT := /home/user/nRF5_SDK_17.1.0。
编译参数里的-mcpu=cortex-m4 -mthumb告诉编译器目标内核是Cortex-M4且使用Thumb指令集。-mfloat-abi=hard -mfpu=fpv4-sp-d16启用硬件浮点单元,这正好对应我之前说nRF52832是Cortex-M4F这一点。如果你把float-abi设成soft,程序也能跑,但浮点运算会慢很多,而且如果整个工程里有人用了硬浮点函数库,链接时还会报错。
4.2 修改配置并执行编译
打开命令行,切到gcc目录,执行编译:
make -j4-j4表示启用4个并行编译任务,能明显加快速度。如果你还没修改SDK_ROOT,第一次编译大概率会报错找不到Makefile.common,这时候直接把Makefile里的SDK_ROOT := $(abspath ../../../../../..)换成你的SDK绝对路径就行。也可以不改文件,每次编译时临时指定:
make -j4 SDK_ROOT=/你的路径/nRF5_SDK_17.1.0编译成功后,_build目录下会出现这些文件:
nrf52832_xxaa.hex:Intel HEX格式的固件,烧录用这个。nrf52832_xxaa.elf:带调试信息的可执行文件,在线调试要用它。nrf52832_xxaa.map:链接映射文件,里面记录了每个函数、每个变量被分配到哪个地址,排查问题极有用。nrf52832_xxaa.bin:纯二进制镜像,某些烧录工具需要。
查看固件占用的Flash和RAM大小:
arm-none-eabi-size _build/nrf52832_xxaa.elf这个命令会输出text/data/bss三列数值,text加data就是占用的Flash空间,data加bss是运行时占用的RAM空间。迭代开发时我习惯编译完扫一眼这个数值,心里对余量有数。
如果想清理重新编译,执行make clean,想连依赖文件一起清掉就make pristine,后者在我更换编译选项后必用一次,避免一些莫名其妙的增量编译问题。
4.3 编译报错怎么快速定位
命令行编译的错误信息比IDE原始,但定位起来并不难,掌握一个原则:不要看最后一行,要看第一条error之后紧跟的那个文件路径和行号。下面这些是我遇到过最多的错误类型。
找不到头文件,报错是fatal error: xxx.h: No such file or directory。这种情况就是Makefile里-I参数的头文件路径不全,一般是缺少某个SDK组件路径,或者你添加了自定义模块但忘了加include路径。
链接阶段报undefined reference to 'xxx',说明某个函数声明了但没找到实现。最常见的原因是相应的.c文件没有加入编译依赖。在SDK的Makefile里,源文件是通过$(wildcard ...)方式自动收集的,如果你自己添加了源文件目录,需要在SRC_FILES或类似的变量里补上。
还有一种情况是配合静态库时,报一堆找不到符号,那就要检查Makefile里的LIB_FILES变量,要把.a库文件路径和对应的-l参数加对。GCC链接库这件事,本质上就是告诉链接器去哪儿找符号,路径不对符号自然找不到。
5. 烧录、调试与VSCode集成
5.1 烧录分两步:先软设备后应用
编译只是上半场,把固件烧进芯片才算跑通。连接好J-Link和开发板,先烧录SoftDevice。
建议直接用SDK自带的最省事方式,在gcc目录下执行:
make flash-softdevice这个目标会调用nrfjprog,擦除整个芯片,然后把软设备的hex烧进去。如果你想手动操作,对应的命令是:
nrfjprog --program components/softdevice/s132/hex/s132_nrf52_7.3.0_softdevice.hex --chiperase -f nrf52注意--chiperase参数,它会擦除整片Flash。所以如果芯片里已经烧了重要的生产固件,千万别直接执行这个命令,数据会全没。
接下来烧录应用固件:
nrfjprog --program _build/nrf52832_xxaa.hex --sectorerase -f nrf52 nrfjprog --reset -f nrf52这次用的是--sectorerase,只擦除应用所在的扇区,不会动已经烧好的SoftDevice。两步的顺序不能反,如果先烧应用后烧软设备,软设备会把应用覆盖掉。分步流程走通之后,完全可以在Makefile里加一个flash-all目标,一键完成两步烧录。
5.2 VSCode里配好代码提示、编译任务和调试
用VSCode写nRF52代码,体验确实比SES舒服,但需要配置三个文件。
第一个是.vscode/c_cpp_properties.json,负责代码补全和语法检查。关键是让IntelliSense理解我们的编译宏和头文件路径:
{ "configurations": [ { "name": "nRF52", "compilerPath": "C:/arm-gcc/bin/arm-none-eabi-gcc.exe", "defines": [ "NRF52832_XXAA", "BOARD_PCA10040", "S132", "SOFTDEVICE_PRESENT", "CONFIG_GPIO_AS_PINRESET" ], "includePath": [ "${workspaceFolder}/**", "D:/nRF5_SDK_17.1.0/components/**", "D:/nRF5_SDK_17.1.0/modules/**", "D:/nRF5_SDK_17.1.0/integration/nrfx/**" ] } ], "version": 4 }defines里的宏要和Makefile里保持一致,不然会出现在Makefile中编译通过但VSCode里疯狂标红的情况。includePath里的SDK路径按你自己的实际情况改。
第二个是.vscode/tasks.json,把编译变成快捷键触发的任务:
{ "version": "2.0.0", "tasks": [ { "label": "build", "type": "shell", "command": "make", "args": ["-j4"], "options": { "cwd": "${workspaceFolder}/examples/ble_peripheral/ble_app_uart/pca10040/s132/gcc" }, "group": { "kind": "build", "isDefault": true } } ] }第三个是.vscode/launch.json,配合Cortex-Debug插件实现在VSCode里打断点调试:
{ "version": "0.2.0", "configurations": [ { "name": "nRF52 Debug", "type": "cortex-debug", "request": "launch", "servertype": "jlink", "device": "nRF52832_xxAA", "interface": "swd", "executable": "${workspaceFolder}/examples/ble_peripheral/ble_app_uart/pca10040/s132/gcc/_build/nrf52832_xxaa.elf", "runToEntryPoint": "main" } ] }安装好Cortex-Debug插件、确保J-Link驱动已安装,按F5就能进入调试。可以在main函数里打断点,查看变量值,这与之前nrfjprog命令行方式互为补充:命令行处理批量烧录,VSCode处理日常调试。
6. 常见问题与排查技巧实录
6.1 程序烧进去却跑不起来的地址陷阱
这个坑我当年踩得极为深刻。烧录一切正常,但程序上电后没有任何现象,用调试器单步走会发现程序跑飞进HardFault。最后排查出来是SoftDevice版本和链接脚本不匹配。
SDK 17.1.0中同一份例程的gcc目录下可能有多个链接脚本,比如gcc_nrf52.ld和适配某个软设备版本的专用ld。如果你在Makefile里指定的是不匹配s132版本的链接脚本,Flash和RAM起始地址就会算错。程序以错误地址启动,后面所有外设初始化都会出问题。
排查方法也不复杂:烧录后用J-Link读回Flash内存,看看0x00026000附近到底有没有程序。更简单的,用一个官方没改过的例程直接编译烧录,如果官方版本能跑、你的工程跑不了,那就逐一比对Makefile改动,重点检查LINKER_SCRIPT、CFLAGS里的宏定义是否与软设备版本匹配。
6.2 工具链升级后编译还是旧版本
“明明升级了GCC,为什么编译时还是旧版本”这个问题,不止一个人问过我。原因基本就两类。
第一类是PATH顺序问题,前面已经说过,用which arm-none-eabi-gcc确认实际调用的是哪个路径。第二类是Makefile或构建脚本里直接硬编码了旧版本的绝对路径。这种情况常见于从网上拷贝的Makefile,里面写着CC := /opt/gcc-arm-none-eabi-5.4.1/bin/arm-none-eabi-gcc之类的固定路径。建议要么删掉这种硬编码,改成CC ?= arm-none-eabi-gcc让系统PATH决定,要么每次升级后同步修改Makefile中的路径。
升级GCC后还可能遇到另一个问题:新版编译器对C语言标准的实现更严格,以前在旧版本下能编译通过的代码,现在可能报一堆warning甚至error。比如隐式函数声明这类问题,在旧GCC里是warning,新GCC直接给error。这时候先别急着减编译选项,把代码里那些隐患改掉反而是好事。
6.3 J-Link连不上目标芯片
nrfjprog执行时报Could not connect to target,先别怀疑芯片坏掉,按顺序排查:测量开发板供电是否正常,nRF52832是3.3V供电;检查SWDIO、SWCLK、GND三条线是否接对,尤其是GND必须可靠共地;确认J-Link型号和驱动版本别太老旧,老驱动有时不识别较新的nRF52832批次芯片。
还有些情况是芯片被锁死了,通常是因为代码里意外使能了读保护或者调试口复用。这时候用nrfjprog --recover可以解锁,但必须明确这个命令会全片擦除,Flash里的SoftDevice和应用都会消失。解锁后重新走一遍软设备+应用烧录流程就能恢复。
6.4 常见问题速查表
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
make找不到Makefile.common | SDK_ROOT路径不对 | 改成绝对路径,或在make命令行指定SDK_ROOT |
编译报No such file or directory | 头文件路径缺 -I | 对照Makefile检查includePath,补充SDK组件路径 |
链接报undefined reference | 源文件未参与编译或库未链接 | 检查SRC_FILES和LIB_FILES变量 |
| 程序烧录成功但无现象 | 链接脚本地址与SoftDevice不匹配 | 确认LINKER_SCRIPT与软设备版本对应 |
Cannot connect to target | 接线/供电/芯片锁定 | 检查SWD接线和供电,必要时nrfjprog --recover |
| 烧录时报Flash占用溢出 | Flash空间不够 | 检查map文件,裁剪无用功能或启用 -Wl,--gc-sections |
| Windows下make执行异常 | 路径中文/空格或make版本不兼容 | 工程放到纯英文路径,换GNU make版本 |
最后再分享一点个人的折腾心得
这套GCC环境搭好之后,最大的收获并不是“摆脱了IDE”,而是我终于看懂了固件的生成链路。看Makefile让我明白了编译参数如何影响最终产物,看链接脚本让我理解了Flash和RAM的布局逻辑,看map文件让我知道了每个函数被放到了哪里。这种掌控感是用图形界面点Build完全体会不到的。
实际开发中我形成了一套固定习惯:改任何配置前先make clean,编译完就看一眼arm-none-eabi-size的输出,烧录前用nrfjprog --memrd快速验证一下Flash关键地址的数据。这些操作花不了几秒钟,但能在问题暴露前就发现端倪。如果你也想深入nRF52开发,别嫌搭建环境麻烦,这一下午的折腾绝对值得。
本文还有配套的精品资源,点击获取