ARTICLE DETAIL

资讯详情

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

告别Keil:用VSCode搭建APM32F1编译烧录调试全流程

告别Keil:用VSCode搭建APM32F1编译烧录调试全流程 APM32F103这块板子我原本是用Keil在磕。但Keil的代码补全实在熬人尤其是连续改了几十个结构体字段之后那个红色波浪线和无响应能直接把人搞到心态崩溃。加上家里这台电脑装了WSL之后我愈发想把整个嵌入式编译流程从IDE的黑盒里掏出来看清楚每一步到底发生了什么。于是就有了这一篇——用VScode搭一套APM32F1的完整开发环境走通编译、烧录、调试全流程。这篇文章适合想脱离Keil但还没找到替代方案的嵌入式开发者也适合已经熟悉VScode但第一次碰国产Cortex-M3 MCU的朋友。如果你对为什么别人能用VScode写单片机感到好奇这篇就是给你准备的完整作业。1. 为什么要把APM32F1从Keil搬到VScode1.1 Keil用得好好的折腾什么先别急着骂我瞎折腾。Keil其实并不差至少对老工程师来说装个Pack、建个工程、点一下Download就能跑这套肌肉记忆已经刻进DNA了。但我在APM32F1项目上遇到的痛点很具体一方面是代码提示太弱写结构体成员时经常要翻datasheet确认拼写另一方面工程文件是分散在.uvprojx里面的和Git的diff体验几乎没法看。我用的是APM32F103RBT6这颗芯片是128KB Flash和20KB RAM外设和STM32F1的高度兼容但真要切换开发环境坑并不在芯片本身而在于工具链的适配。再一个现实问题Keil在Windows上调试APM32时虽然可以用CMSIS-DAP或J-Link但它的调试器视图、变量观察窗口都比较封闭想输出自定义日志格式还得靠串口助手。VScode这边不一样任务面板、输出流、JSON配置全是明文规则清晰出了问题能顺着配置一路查下去而不是面对一堆图形界面的灰色按钮。1.2 VScode为嵌入式开发准备了什么VScode本质上是编辑器能把编译和调试交给外部工具链。这个外部化的想法正好和嵌入式工程经常使用的命令行工具链合拍。我们用arm-none-eabi-gcc编译C代码用CMake或Makefile描述工程拓扑用openocd或J-Link实现烧录和调试VScode只负责把这些工具挂在快捷键上、把输出面板收拢在一起、把调试器的断点状态可视化。对APM32F1来说Cortex-M3内核意味着GCC工具链完全支持配合arm-none-eabi-objcopy生成hex和bin文件配合arm-none-eabi-gdb进行源码级调试整套流程在VScode界面上跑起来以后体验比Keil里的黑盒编译要通透得多。而且插件生态里还有C/C IntelliSense能对嵌入式寄存器定义做实时跳转这种效率红利是实打实的。1.3 不适用场景提醒也要说句公道话并不是所有情况下都建议迁移。如果整个团队已经围绕Keil建立了完整的脚本体系和培训流程那换工具链的成本可能高于收益或者你手头的调试器是官方独有协议像某些专用烧录器只提供IDE插件那VScode暂时也替代不了。我的建议是个人项目、学习项目、极客向的验证项目放心折腾产线在跑、所有人都在用Keil、你能不动就不动的项目别为了玩VScode而玩VScode。2. 环境搭建从零装出可用的APM32F1工具箱2.1 工具链选型arm-none-eabi-gcc与make在VScode里编译APM32F1核心工具链是arm-none-eabi-gcc。这套GNU工具链在ARM官网或者各镜像站都能下到安装时记得把arm-none-eabi-gcc的bin目录加入系统PATH。装完以后在终端里执行arm-none-eabi-gcc --version能输出版本信息就说明OK。接下来是构建工具。我建议直接用make配合Makefile管理工程。Windows下可以用MSYS2或者pacman安装make如果不想折腾环境也可以在WSL里跑所有编译命令VScode连接WSL远程开发。这里有一个关键点工具链版本尽量保持一致我早期在macOS上用较老的arm-none-eabi-gcc编译APM32F1结果链接时出现了一些奇怪的符号地址错位换成和维护版本后一切正常。嵌入式开发里编译器版本往往决定了启动文件的ABI细节换来换去非常容易踩到莫名其妙的坑。2.2 VScode插件配置C/C、Cortex-Debug、LinkerScriptVScode插件安装谁都会但关键是怎么配置。我实际使用的插件列表是C/Cms-vscode.cpptools提供代码补全、跳转和编译诊断Cortex-Debugmarus25.cortex-debug专门为Cortex-M内核设计的调试插件配合OpenOCD和J-Link都能用LinkerScript语法提示可选打开.ld链接脚本时高亮能减少语法错误Task Explorer可选快速查看Makefile里的target。装完插件以后还得建立c_cpp_properties.json这里最容易被卡住。我一开始没配置includePath导致寄存器定义和标准外设库的头文件全部爆红跳转也失效。配置如下以你的工程实际路径为准{ configurations: [ { name: APM32F1, includePath: [ ${workspaceFolder}/Core/Inc, ${workspaceFolder}/Drivers/CMSIS/Include, ${workspaceFolder}/Drivers/APM32F1xx_StdPeriph_Driver/Inc, ${workspaceFolder}/Startup ], defines: [ USE_STDPERIPH_DRIVER, APM32F10X_HD ], compilerPath: /usr/bin/arm-none-eabi-gcc, cStandard: c11, intelliSenseMode: linux-gcc-arm } ], version: 4 }defines里的USE_STDPERIPH_DRIVER是标准外设库的开关APM32F10X_HD对应高密度型号。注意APM32F103RBT6属于HD高密度还是MD中密度需要查你自己芯片的Flash大小256KB以上为HD128KB及以下为MD或LD。我用的RBT6是128KB按理说属于MD但实际原厂SDK里仲裁时需要看清启动文件里用的是HD还是MD不同的宏会导致不同的外设映射和中断向量。这是VScode配置里最容易忽略而又致命的一步。2.3 获取APM32F1的支持包和启动文件APM32F1作为极海半导体的Cortex-M3产品官网提供的SDK包一般包含CMSIS目录、标准外设库、启动文件和一个模板工程。拿到SDK以后把以下内容拷到工程里Drivers/CMSIS/Device/APM32F1xx/Include内含寄存器地址定义Drivers/CMSIS/Device/APM32F1xx/Source/startup_apm32f10x_xxx.s根据容量选择启动文件Drivers/APM32F1xx_StdPeriph_Driver标准外设库源码包括GPI/O、RCC、USART等。如果你实在找不到原厂SDK退而求其次用STM32F1的启动文件也能跑但中断向量和时钟初始化可能不完全匹配国产芯片的私有外设。我的建议是恪守一个原则能用原厂启动文件就不要用替代品否则烧进去以后跑飞了你第一个怀疑工具链其实只是启动文件里的SystemInit没配好。3. 工程配置项目结构、链接脚本与Makefile的兼容日记3.1 工程目录规划与CMSIS文件整理一个干净的目录结构能省去后面大量配置时间。我的APM32F103工程最终是这样组织的apm32f103_demo/ │ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ └── system_apm32f1xx.h │ └── Src/ │ ├── main.c │ └── system_apm32f1xx.c │ ├── Drivers/ │ ├── CMSIS/ │ │ ├── Include/ │ │ └── Device/APM32F1xx/ │ │ ├── Include/ │ │ └── Source/ │ └── APM32F1xx_StdPeriph_Driver/ │ ├── Inc/ │ └── Src/ │ ├── Startup/ │ └── startup_apm32f10x_hd.s │ ├── obj/ ├── apm32f103.ld ├── Makefile └── README.md这里有个细节system_apm32f1xx.c里包含了SystemInit函数的实现它会被启动文件调用。如果你从STM32工程拷过来很可能缺失这个文件的APM32特定初始化逻辑导致时钟频率不对。CMSIS目录的Include里放的是基础寄存器定义Device目录里放的是设备特有定义这两个位置都不要放错。3.2 链接脚本(.ld)里必须改的RAM和FLASH地址链接脚本是决定程序能不能稳定运行的基石。我用的是APM32F103RBT6Flash 128KBRAM 20KB。链接脚本中最关键的两行是这样的MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 128K RAM (rwx) : ORIGIN 0x20000000, LENGTH 20K }FLASH的基地址固定为0x08000000这是Cortex-M3内置Flash统一映射地址。RAM基地址为0x20000000长度要和你手上的芯片具体规格一致。如果你写错了LENGTH链接器虽然不会报错但运行时变量堆栈分配会覆盖到不存在的物理地址造成极其隐蔽的数据错乱。我早期用256K的Flash脚本烧写RBT6程序启动正常但只要运行到频繁写变量的逻辑就随机复位排查了一天才发现是链接脚本的Flash长度超了实际容量。堆栈和堆的设置同样重要_heap_size 0x1000; _stack_size 0x400;堆为4KB栈为1KB对RBT6的20KB RAM来说是够用的。如果开了较大缓冲区或者printf浮点支持栈还要适当加大。嵌入式里面的经典错误是栈溢出导致系统跑飞却没有任何预兆。3.3 Makefile的关键编译参数Makefile是整套自动构建的核心。一个针对APM32F1的最小可用Makefile关键部分大概长这样TARGET apm32f103_demo # 芯片架构定义 CPU -mcpucortex-m3 -mthumb # 标准外设库开关 DEFS -D USE_STDPERIPH_DRIVER -D APM32F10X_HD # 源文件收集 C_SOURCES \ Core/Src/main.c \ Drivers/APM32F1xx_StdPeriph_Driver/Src/apm32f1xx_gpio.c \ Drivers/APM32F1xx_StdPeriph_Driver/Src/apm32f1xx_rcc.c \ Startup/startup_apm32f10x_hd.s # 编译参数 CFLAGS $(CPU) $(DEFS) -I Core/Inc -I Drivers/... -O2 -Wall -fno-common # 链接参数 LDFLAGS -T apm32f103.ld $(CPU) --specsnano.specs --specsnosys.specs all: $(TARGET).bin $(TARGET).hex $(TARGET).elf: $(C_SOURCES) arm-none-eabi-gcc $(CFLAGS) $(LDFLAGS) -o $ $^ -lc -lm $(TARGET).bin: $(TARGET).elf arm-none-eabi-objcopy -O binary $ $ $(TARGET).hex: $(TARGET).elf arm-none-eabi-objcopy -O ihex $ $ clean: rm -f $(TARGET).elf $(TARGET).bin $(TARGET).hex这里要重点解释-mcpucortex-m3 -mthumb。APM32F1的内核是Cortex-M3不支持浮点单元也不需要-mfloat-abihard。如果你误加了-mfloat-abihard代码编译出来的浮点运算指令全部非法芯片一执行就会进HardFault。另一个容易被忽略的是--specsnano.specs它引入了优化过的嵌入式C库占用的Flash空间大幅缩水代价是某些标准库函数的处理不那么完整但对单片机场景完全够用。nosys.specs则是提供一套空的系统调用实现避免链接时因为缺少sbrk之类的符号而失败。4. 编译、烧录与调试打通代码到芯片的最后一公里4.1 一键编译并生成hex/bin在VScode里配置好tasks.json以后就可以按CtrlShiftB直接构建。我的tasks.json核心配置是{ version: 2.0.0, tasks: [ { label: build, type: shell, command: make, args: [clean, all], group: { kind: build, isDefault: true }, problemMatcher: [ $gcc ] } ] }problemMatcher设为$gcc编译器的报错信息就能直接显示在VScode的问题窗口里点击即可跳转到对应代码。这就是VScode开发嵌入式很爽的一个点不用切到终端窗口去抓error。编好以后obj/目录下会出现.elf、.bin、.hex三种文件arm-none-eabi-size工具还能输出占用Flash和RAM的具体情况。我平时编译完会执行一次arm-none-eabi-size -A target.elf确认段分配是否符合预期。4.2 OpenOCD与J-Link烧录配置烧录APM32F1有两种常见路线取决于手头调试器。第一种是OpenOCD配合DAP-Link或ST-Link。OpenOCD支持STLINK和CMSIS-DAP协议而APM32F1和STM32F1的内核一致所以target配置可以直接使用stm32f1x.cfg。运行命令openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c program apm32f103_demo.hex verify reset exit这里用到的是OpenOCD的命令编程模式加载配置、烧录hex、校验、复位退出。如果烧录时出现target not in debug state一类错误先检查调试器连接是否稳固再确认芯片已经进入SWD模式——部分开发板需要手动拉高BOOT0才能让芯片处于可编程状态。第二种是J-Link的官方命令行工具。J-Link配合SWD接口烧录更省心命令如下JLinkExe -device STM32F103RB -if SWD -speed 4000 -autoconnect 1进入J-Link终端后执行loadfile apm32f103_demo.hex r gJ-Link的device参数直接写STM32F103RB也能用因为Cortex-M3的SWD协议栈是内核自带的调试器只需识别IDCODE即可。当然你想写APM32F103RB但J-Link软件未必认识这种情况下用STM32F103RB是最稳妥的兼容写法。4.3 VScode调试器的launch.json配置与断点体验调试插件Cortex-Debug连接OpenOCD或J-Link的GDB Server然后在VScode里体验图形化断点、变量监视和寄存器查看。我惯用的launch.json是这样的{ version: 0.2.0, configurations: [ { name: Cortex Debug OpenOCD, cwd: ${workspaceFolder}, executable: ./obj/apm32f103_demo.elf, request: launch, type: cortex-debug, servertype: openocd, configFiles: [ interface/stlink.cfg, target/stm32f1x.cfg ], gdbPath: /usr/bin/arm-none-eabi-gdb, device: STM32F103RB, svdFile: ${workspaceFolder}/APM32F103.svd } ] }svdFile填写的是SVD描述文件它可以让调试器直接映射芯片内部寄存器的名字和位域调试外设时事半功倍。APM32F103的SVD文件在SDK包里一般能找到找不到的话用STM32F103的SVD也基本能跑但寄存器命名可能有些出入。实际调试时我习惯在main()入口设一个断点然后Reset后全速跑到断点接着在RCC_APB2PeriphClockCmd这类外设初始化函数里单步走一遍观察寄存器值的实时变化确认外设时钟是否真正打开——这个能力在Keil里也不是没有但VScode里看浮点数和结构体变量明显更直观。5. 从工程实操中总结的避坑经验与优化思路5.1 最容易踩的坑启动文件与芯片型号不匹配我踩过最痛的坑就是启动文件用错。APM32F103和STM32F103虽然兼容但极海原厂的启动文件里中断向量表在尾部会额外带上一些芯片特定处理。比如某些批次的高密度芯片有多余的外设中断向量表布局稍微不同你拿STM32F103的启动文件硬解程序普遍能启动但一旦触发了没有对应向量处理的中断就会跳到一个空指针位置整个系统变成植物人状态。所以务必对照你的芯片容量选择startup_apm32f10x_ld.s、startup_apm32f10x_md.s还是startup_apm32f10x_hd.s。这个字母还决定了代码里#define APM32F10X_HD之类的宏宏定义直接关联外设库中哪些外设被编译进工程搞错了编译能过运行必定出问题。出事后最有效的手段是先读芯片的IDCODE和Flash大小寄存器确认实际容量再回MCU选型表核对型号编码——别信包装信寄存器。5.2 VScode代码提示失效的排查思路VScode开发C语言项目最大的甜头是IntelliSense但有时候它莫名失效满屏波浪线。我排查过几类成因第一includePath没写全或者相对路径不对尤其是CMSIS的Device路径和标准外设库路径第二defines漏了USE_STDPERIPH_DRIVER或APM32F10X_HD导致条件编译的一部分代码被跳过IntelliSense看不到那些声明第三.vscode/settings.json里如果设置了C_Cpp.default.configurationProvider可能会和c_cpp_properties.json产生冲突需要清掉provider让插件直接读取json。另外一个小技巧是改完c_cpp_properties.json以后按CtrlShiftP执行C/C: Reset IntelliSense Database强制刷新索引。VScode的IntelliSense引擎偶尔会因为工程文件变更而缓存旧数据尤其是新增了源文件或头文件之后不刷新就会一直报错。有一回我新建了一个system_apm32f1xx.c文件IntelliSense里函数跳转全部失效重置数据库以后立刻恢复正常。5.3 多平台工程管理与效率优化思路VScode开发APM32F1的优势之一是可以很方便地在Windows、Linux、macOS之间保持同一个工程。我的做法是所有路径均用相对路径Makefile里用变量拼接路径避免Windows下反斜杠和Linux下斜杠的纠纷。在Windows上如果用WSL做编译VScode的Remote-WSL插件可以让你直接在Linux子系统里编辑代码性能表现和原生Linux一致。效率优化方面还有两个很实用的思路。第一把构建、烧录、调试三个动作分别绑定到自定义快捷键比如CtrlShiftB构建F5调试这样手不离键盘。第二在Makefile里增设flash和debug目标分别调用OpenOCD烧录和启动GDB Server把重复性的命令行操作全部收拢到Task里。写代码时配合Git做版本管理每次改动都可以清晰地看到编译脚本、链接脚本的差异这种全是文本的工程结构确实把嵌入式开发拉回了现代软件工程的舒适区。最后再分享一个小习惯每次换芯片型号的时候我会第一时间去核对三处——启动文件的容量匹配、链接脚本里Flash和RAM的容量、编译宏定义。这三者不一致所有后续努力都会白费。APM32F1在VScode下吃透了以后你再来用任何基于Cortex-M的国产MCU都会觉得无比顺手毕竟背后的GCC、OpenOCD、GDB这些开源工具链都是相通的。
返回列表