嵌入式开发这行干久了,你会发现一个很有意思的现象:很多人能写出一手漂亮的业务逻辑代码,但一碰到"从代码到板子跑起来"这一段就卡壳。编译报错看不懂、烧录连不上、仿真跑不通,三板斧下来直接懵圈。我自己带过不少新人,也帮朋友救过不少"变砖"的板子,说到底,嵌入式MCU软件编译烧录仿真流程这条链路,是每个嵌入式工程师必须跨过去的基本功。它不像写应用层代码那样有丰富的报错提示和调试工具,很多时候你面对的就是一块沉默的芯片和一根SWD线,能不能跑起来全靠你对整个流程的理解深度。
这篇文章我想把MCU开发中"编译→烧录→仿真"这条完整链路掰开揉碎讲清楚。不管你是刚入门的电子专业学生,还是从纯软件开发转过来的工程师,或者是做了几年但一直用IDE一键下载、没深究过底层机制的从业者,都能从里面找到对自己有用的东西。我会从工具链选型讲到编译原理的关键环节,从SWD协议讲到烧录失败的排查思路,从仿真器配置讲到在线调试的实战技巧,尽量做到既讲清楚"为什么",也给出可以直接抄的操作步骤。
1. 整条工具链的设计思路与选型逻辑
1.1 为什么嵌入式编译和普通PC编译不是一回事
很多人第一次接触嵌入式编译会有一个疑问:我在电脑上写C语言,gcc一下就能跑,为什么到了MCU上就这么多讲究?核心原因在于交叉编译这个概念。你的开发主机是x86或者ARM64架构,而目标MCU可能是Cortex-M0、M3、M4、RISC-V或者别的什么内核,指令集完全不同。所以你需要一套在主机上运行、但生成目标芯片机器码的编译器,这就是交叉编译工具链。
以最常见的ARM Cortex-M系列为例,工具链通常是arm-none-eabi-gcc这一套。arm表示目标架构,none表示没有操作系统(裸机),eabi是嵌入式应用二进制接口。这套工具链里包含的不只是编译器,还有汇编器as、链接器ld、二进制转换工具objcopy、反汇编工具objdump、大小分析工具size等等。理解这一点很关键,因为后面排查编译问题时,你经常需要单独调用这些工具来看中间产物。
和PC编译另一个大区别是链接脚本。PC上程序链接由操作系统加载器负责,你基本不用管内存布局。但MCU上没有操作系统,代码放在Flash的哪个地址、变量放在RAM的哪个区域、中断向量表放在哪里,全靠一个.ld链接脚本文件来指定。这个文件写错了,程序要么跑不起来,要么跑着跑着就HardFault。我见过太多人从别人那里拷贝工程,结果链接脚本里的Flash起始地址和自己的芯片对不上,烧进去就是一片死寂。
1.2 编译工具链的几种主流选择
目前嵌入式MCU开发工具链大致分三个流派,各有各的适用场景。
第一派是IDE集成派,代表就是Keil MDK和IAR EWARM。这类工具把编译器、链接器、调试器、烧录器全部打包在一个图形界面里,点一下Build就编译,点一下Download就烧录。优点是上手快,对新手友好,芯片厂商的支持包(Device Family Pack)装好就能用。缺点是编译器是私有的,License要花钱,而且工程文件是二进制格式,做版本管理和CI/CD很麻烦。Keil用的ARMCC/ARMCLANG编译器,IAR用自己的ICCARM,和开源的GCC在语法细节、优化行为上都有差异。
第二派是开源命令行派,核心是GCC ARM Embedded工具链加上Makefile或CMake构建系统。这套方案在Linux和macOS上体验最好,Windows下可以用MSYS2或者WSL。优点是免费、可脚本化、易于集成到CI流水线,工程文件是纯文本,Git管理友好。缺点是配置门槛高,链接脚本、启动文件、编译选项都得自己搞明白。现在很多芯片厂商(比如ST、乐鑫、Nordic)都提供了基于CMake的SDK,把这套流程封装得比较好了。
第三派是厂商SDK派,比如ESP-IDF、STM32CubeIDE、Nordic nRF Connect SDK。这类工具基于开源工具链做了深度定制,把芯片特有的配置(时钟树、外设初始化、分区表)都集成进来了。用起来比裸GCC舒服,又比Keil灵活。我个人现在做新项目基本优先选这类方案,除非客户强制要求用Keil。
选型的时候我的建议是:学习阶段用Keil或STM32CubeIDE快速建立感性认识,做正式项目尤其是需要团队协作和自动化构建的,尽早转到CMake+GCC这套。不要被IDE惯坏了,命令行能力是嵌入式工程师的硬实力。
1.3 烧录器和调试器的关系
新手最容易混淆的就是烧录器和调试器。简单说,调试器一定能烧录,烧录器不一定能调试。像ST-Link、J-Link、DAPLink这些,既是调试器也是烧录器,它们通过SWD或JTAG接口和芯片通信,既能下载程序也能单步调试。而像一些专用的量产烧录器,只负责把固件写进Flash,不具备调试功能。
SWD(Serial Wire Debug)是目前ARM Cortex-M芯片最主流的调试接口,只需要两根信号线:SWCLK和SWDIO,加上电源和地,一共四根线就能工作。相比JTAG的五线制,SWD引脚更少、速度也够用,所以现在绝大多数小板子都只引出SWD。理解SWD的通信机制对排查烧录问题特别有帮助,后面我会专门讲。
2. 编译环节的核心细节与实操要点
2.1 从源码到可执行文件的四个阶段
编译这个词其实是个笼统说法,严格来讲从.c文件到可以烧进芯片的.bin或.hex,中间经过了四个阶段,每个阶段都可能出问题。
预处理阶段,编译器处理所有#开头的指令,展开宏定义、插入头文件内容、处理条件编译。这个阶段最常见的坑是头文件路径没配对,报fatal error: xxx.h: No such file or directory。还有一种隐蔽的问题是宏定义冲突,比如两个头文件都定义了同一个宏但值不一样,预处理后代码行为和你预期完全不同。
编译阶段,把预处理后的C代码翻译成汇编代码。这个阶段报的错通常是语法错误、类型不匹配、未声明变量。GCC的报错信息比Keil要详细,会告诉你具体哪一行、什么类型的错误。我建议养成看警告的习惯,-Wall -Wextra打开,很多潜在的bug在编译阶段就能发现。
汇编阶段,把汇编代码翻译成机器码,生成目标文件.o。这个阶段一般不会报错,除非你手写了汇编或者内联汇编有语法问题。
链接阶段,把所有.o文件和库文件合并,按照链接脚本的规则分配地址,生成最终的.elf文件。这个阶段最典型的错误是undefined reference to xxx,意思是某个函数声明了但没找到实现。还有一种错误是内存溢出,.text段放不下或者.bss段超出RAM大小,链接器会报region FLASH overflowed之类的信息。
理解这四个阶段的意义在于:当编译报错时,你能快速判断问题出在哪个环节。头文件问题看预处理,语法问题看编译,符号找不到看链接,内存不够看链接脚本和map文件。
2.2 链接脚本和启动文件的关键作用
链接脚本是很多人忽略但又极其重要的东西。它定义了芯片的内存布局,告诉链接器哪些地址是Flash、哪些是RAM、代码和数据分别放在哪里。一个典型的Cortex-M链接脚本长这样:
MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K } SECTIONS { .text : { *(.isr_vector) *(.text) *(.rodata) } > FLASH .data : { *(.data) } > RAM AT > FLASH .bss : { *(.bss) } > RAM }这里有几个关键点。.isr_vector是中断向量表,必须放在Flash的最开头,因为Cortex-M内核复位后会从这个地址读取栈指针和复位向量。.data段是已初始化的全局变量,它的初始值存在Flash里,运行时需要拷贝到RAM,所以链接脚本里写的是> RAM AT > FLASH。.bss段是未初始化的全局变量,运行时清零即可,不占Flash空间。
启动文件startup_xxx.s配合链接脚本工作,它做的事情包括:设置初始栈指针、初始化.data段(从Flash拷贝到RAM)、清零.bss段、调用SystemInit配置时钟、最后跳转到main函数。如果你换了芯片但启动文件没换,或者链接脚本的地址和实际芯片不符,程序就会在启动阶段挂掉,表现就是烧录成功但没有任何反应。
提示:拿到一个新芯片,第一件事是确认Flash和RAM的起始地址与大小,直接查数据手册的Memory Map章节,不要凭经验猜。STM32F103和STM32F407的Flash起始地址都是0x08000000,但RAM大小和地址可能不同。
2.3 编译优化等级的选择与陷阱
GCC提供了-O0到-O3以及-Os几个优化等级。新手经常纠结用哪个,我的经验是这样:
-O0不优化,生成的代码和源码一一对应,调试体验最好,但代码体积大、运行慢。开发调试阶段用这个。-O1做基本优化,体积和速度平衡。-O2更激进的优化,可能会改变代码执行顺序,单步调试时会出现"跳来跳去"的现象。-O3最激进,可能做循环展开、函数内联,代码体积反而可能变大。-Os专门优化体积,适合Flash紧张的芯片。
这里有个大坑:优化等级会影响volatile关键字之外的内存访问行为。比如你写了一个延时循环for(int i=0;i<1000;i++);,在-O2下编译器可能直接把这个循环优化掉,因为i没有被使用。解决办法是把循环变量声明为volatile,或者用__NOP()指令。另一个坑是调试时变量被优化没了,你在watch窗口看不到值,这时候要么降优化等级,要么把变量声明为volatile。
我个人的习惯是:开发阶段-O0 -g3,发布阶段-Os或-O2,并且发布前一定要在优化后的版本上完整测试一遍,因为优化可能暴露一些在-O0下被掩盖的时序问题或未定义行为。
2.4 编译产物的格式与转换
编译链接完成后,你得到的是.elf文件,这是带调试信息的可执行文件。但烧录器通常需要的是.bin或.hex格式。
.bin是纯二进制,只包含机器码,没有地址信息,烧录时必须指定起始地址。.hex是Intel HEX格式,每行包含地址、数据和校验和,烧录器能自动识别地址。还有一种是Motorola S-record格式,也就是.s19文件,常见于一些老牌芯片厂商的工具链。
从.elf转换的命令通常是:
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex转换完之后,我强烈建议用arm-none-eabi-size看一下各段的大小:
arm-none-eabi-size firmware.elf输出会显示text、data、bss三个段的大小。text是代码和只读数据,data是已初始化变量(占Flash也占RAM),bss是未初始化变量(只占RAM)。如果text+data接近Flash容量,就该考虑优化代码或者换更大Flash的芯片了。
3. 烧录流程的完整实操与SWD协议解析
3.1 SWD协议的工作原理
SWD协议是ARM专门为Cortex系列设计的调试接口,它只有两根线:SWCLK时钟线和SWDIO双向数据线。通信是半双工的,主机(调试器)和从机(目标芯片)分时复用SWDIO线。
一次完整的SWD通信包括三个阶段:请求阶段主机发送8位请求包,包含APnDP位(选择访问Access Port还是Debug Port)、RnW位(读还是写)、地址位和校验位。应答阶段目标芯片返回3位应答,OK表示成功,WAIT表示需要重试,FAULT表示出错。数据阶段根据读写方向传输32位数据,如果是写操作还有奇偶校验位。
理解这个协议对排查问题很有帮助。比如你遇到"SWD连接失败",可能的原因包括:SWCLK和SWDIO接反了、目标芯片没供电、复位引脚被拉低、芯片进入了低功耗模式关闭了调试接口、或者SWD引脚被复用成了普通GPIO。排查的时候用示波器看SWCLK有没有波形,是最直接的判断方法。
还有一个常见问题是SWD速度设置过高。J-Link默认可能跑在4MHz甚至更高,但如果你用的是劣质杜邦线或者板子走线很长,高速下信号完整性差,就会连接不稳定。这时候把速度降到1MHz甚至500kHz,往往就能连上。我调试新板子的时候习惯先用低速连接,确认稳定后再往上调。
3.2 主流烧录工具的使用方法
OpenOCD是开源界的万能烧录工具,支持几乎所有主流调试器和芯片。它的工作方式是读取配置文件,加载对应的调试器驱动和目标芯片描述,然后提供GDB Server或者直接执行烧录命令。
一个典型的OpenOCD烧录命令长这样:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \ -c "program firmware.elf verify reset exit"interface/stlink.cfg指定调试器类型,target/stm32f1x.cfg指定目标芯片,program命令完成烧录、校验、复位、退出。OpenOCD的配置文件在安装目录的scripts文件夹下,用之前先确认你的芯片有没有对应的cfg文件。
J-Flash是SEGGER家的烧录工具,配合J-Link使用。它的优势是烧录速度快、支持量产模式、可以生成独立的烧录工程。用J-Flash的时候要注意选择正确的芯片型号和Flash算法,选错了会报"Flash download failed"。
STM32CubeProgrammer是ST官方的工具,支持ST-Link、UART、USB DFU、SPI等多种烧录方式。它的图形界面比较友好,命令行模式也支持脚本化。用ST-Link烧录STM32的时候,如果遇到"No STM32 target found",先检查BOOT0引脚状态和复位电路。
esptool是乐鑫ESP系列专用的烧录工具,通过串口烧录。ESP32的烧录需要把GPIO0拉低进入下载模式,然后复位。用esptool的命令:
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 \ write_flash 0x1000 bootloader.bin \ 0x8000 partitions.bin \ 0x10000 app.bin每个bin文件的烧录地址不能搞错,bootloader在0x1000,分区表在0x8000,应用在0x10000,这是ESP32的标准布局。
3.3 烧录失败的排查思路
烧录失败是嵌入式开发的高频问题,我总结了一套排查流程,基本能覆盖90%的情况。
第一步,检查硬件连接。SWD四根线(VCC、GND、SWCLK、SWDIO)是否接对,有没有虚焊。用万用表量一下目标板供电是否正常,3.3V还是1.8V要和调试器匹配。有些调试器不支持1.8V目标,需要电平转换。
第二步,检查复位和启动模式。有些芯片在复位期间调试接口不可用,需要配置调试器在复位后连接。STM32的BOOT0引脚如果拉高,芯片会从系统存储器启动,这时候烧录的是Bootloader而不是你的程序。ESP32需要GPIO0拉低才能进入下载模式。
第三步,降低SWD速度。前面说过,高速下信号完整性差会导致连接失败。把速度降到500kHz试试。
第四步,检查芯片是否被读保护。有些芯片出厂或者被误操作设置了读保护(RDP),这时候调试器连不上也烧不进去。需要用专门的解锁命令清除保护,但注意解锁会擦除整个Flash。
第五步,检查Flash算法。烧录器需要知道目标芯片的Flash怎么擦除、怎么写入,这就是Flash算法。如果算法文件和芯片不匹配,会报"Flash download failed"。Keil和J-Flash都需要手动选择或添加Flash算法。
下面这张表是我整理的常见烧录错误和对应排查方向:
| 错误现象 | 可能原因 | 排查方向 |
|---|---|---|
| No target connected | 供电、接线、复位 | 量电压、查SWD线序、看复位引脚 |
| Flash download failed | Flash算法不匹配 | 确认芯片型号、更新算法文件 |
| Target not halted | 芯片在运行或低功耗 | 配置复位后连接、唤醒芯片 |
| Read protection active | 读保护开启 | 执行解锁、擦除全片 |
| Verify failed | 烧录数据校验错 | 降速、检查Flash质量、重烧 |
| Cannot access memory | 地址越界或时钟问题 | 检查链接脚本、确认时钟配置 |
3.4 量产烧录的注意事项
如果你做的是量产项目,烧录环节有几个额外的坑要注意。一是烧录速度,量产时每块板子省几秒钟,一千块就是几个小时。J-Link的量产模式可以做到很快,但前提是Flash算法优化得好。二是烧录一致性,要确保每块板子烧的固件完全一样,最好用校验和或者哈希值比对。三是烧录治具,量产用的烧录治具要保证接触可靠,pogo pin用久了会氧化,导致接触不良。四是固件版本管理,量产固件一定要有版本号和Git commit记录,出了问题能追溯。
我见过一个案例,客户量产了一批板子,测试时发现有几块功能异常,查了半天发现是烧录治具的某个pogo pin接触不良,导致Flash写入不完整。后来加了烧录后的校验步骤才解决。所以量产烧录一定要有verify环节,不能图快省掉。
4. 仿真调试的实战技巧与问题排查
4.1 在线仿真和离线仿真的区别
嵌入式领域的"仿真"有两个含义,新手容易搞混。在线仿真指的是通过调试器连接真实芯片,进行单步调试、断点、变量查看等操作,本质上是在真实硬件上调试。离线仿真指的是用软件模拟芯片行为,比如Wokwi、Proteus、QEMU这些平台,不需要真实硬件就能跑代码。
在线仿真是嵌入式开发的主力调试手段。通过SWD接口,调试器可以控制芯片暂停、单步执行、读写内存和寄存器、设置硬件断点。硬件断点的数量有限,Cortex-M3/M4通常支持6个,M0只有4个。断点用完了就只能用软件断点或者printf调试。
离线仿真适合学习阶段和算法验证。Wokwi是个不错的在线平台,支持Arduino、ESP32、STM32等,可以在浏览器里搭电路、写代码、看串口输出。Proteus更强大,能仿真模拟电路和数字电路混合系统。但离线仿真有个根本局限:它无法完全模拟真实硬件的时序、电气特性和外设行为,所以最终还是要上真板子验证。
4.2 GDB调试的常用命令
不管用什么IDE,底层调试引擎基本都是GDB。掌握GDB命令能让你在命令行环境下也能高效调试。
连接OpenOCD的GDB Server:
arm-none-eabi-gdb firmware.elf (gdb) target remote localhost:3333 (gdb) monitor reset halt (gdb) load (gdb) continue常用命令包括:break main在main函数设断点,break file.c:100在指定文件行号设断点,info breakpoints查看所有断点,delete删除断点,step单步进入,next单步跳过,finish执行到函数返回,print variable打印变量值,x/16xw 0x20000000以十六进制查看内存,info registers查看寄存器,backtrace查看调用栈。
调试HardFault的时候,GDB特别有用。当程序进入HardFault,先backtrace看调用栈,然后查看SCB->CFSR、SCB->HFSR、SCB->BFAR这些寄存器,能定位到具体的错误类型。比如CFSR的IMPRECISERR位表示不精确的总线错误,PRECISERR表示精确的总线错误,UNDEFINSTR表示未定义指令。
4.3 常见仿真调试问题与解决
问题一:断点打不上。可能是断点数量超了,或者代码在Flash里但调试器没有正确配置Flash断点。解决办法是减少断点数量,或者用__BKPT()指令手动插入断点。
问题二:单步调试时程序跑飞。这通常是因为中断在调试期间触发了。可以在调试时关闭全局中断,或者配置调试器在中断时暂停。另外优化等级过高也会导致单步行为异常,调试时用-O0。
问题三:变量值显示不对。如果变量被优化了,GDB可能显示optimized out。把变量声明为volatile,或者降低优化等级。还有一种情况是变量在寄存器里而不是内存里,GDB读的是内存值,自然不对。
问题四:程序在-O0下正常,-O2下异常。这是典型的未定义行为或者时序问题。常见原因包括:未初始化的变量、数组越界、中断和主循环共享变量没加volatile、延时循环被优化掉。排查方法是逐步提高优化等级,定位到出问题的代码段。
问题五:仿真器连接不稳定,频繁断开。检查USB线质量、SWD线长度、目标板供电。有些便宜的ST-Link克隆版在高速下不稳定,换原版或者降速使用。
4.4 调试技巧与经验分享
分享几个我多年调试总结的技巧。第一,善用printf和SWO。Cortex-M3/M4支持SWO(Serial Wire Output),可以通过SWD线输出调试信息,不占用串口。配置好ITM寄存器后,用ITM_SendChar函数就能输出,速度比串口快很多。第二,用GPIO翻转做时间测量。在关键代码段前后翻转一个GPIO,用示波器看波形,能精确测量执行时间。第三,保存现场。程序崩溃时,把关键寄存器和内存dump出来,事后分析。第四,二分法定位。程序出问题时,通过注释代码或者加断点,逐步缩小问题范围。
还有一个容易被忽略的点:调试器本身的固件版本。ST-Link、J-Link的固件版本太老可能导致兼容性问题,定期更新固件能避免很多莫名其妙的连接问题。但注意,J-Link克隆版更新固件可能会变砖,原版才建议更新。
5. 从开发到量产的完整流程梳理
5.1 开发阶段的工具链搭建
一个规范的嵌入式项目,工具链搭建应该包括:交叉编译工具链(GCC ARM Embedded或厂商定制版)、构建系统(Make或CMake)、调试器驱动(OpenOCD或厂商工具)、版本控制(Git)、CI/CD(可选,GitLab CI或GitHub Actions)。
我推荐的项目结构是这样的:
project/ ├── src/ # 源代码 ├── inc/ # 头文件 ├── lib/ # 第三方库 ├── startup/ # 启动文件 ├── linker/ # 链接脚本 ├── build/ # 构建输出 ├── tools/ # 烧录和调试脚本 ├── CMakeLists.txt └── README.md用CMake管理构建的好处是跨平台、可脚本化、易于集成。一个最小的CMake配置:
cmake_minimum_required(VERSION 3.20) project(firmware C ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_C_COMPILER arm-none-eabi-gcc) set(CMAKE_ASM_COMPILER arm-none-eabi-gcc) add_executable(firmware.elf src/main.c src/system.c startup/startup_stm32f103.s ) target_link_options(firmware.elf PRIVATE -T${CMAKE_SOURCE_DIR}/linker/stm32f103.ld -Wl,-Map=firmware.map --specs=nano.specs --specs=nosys.specs )--specs=nano.specs使用精简版C库,节省Flash空间。--specs=nosys.specs表示没有系统调用,裸机环境必须加。
5.2 烧录脚本的自动化
手动烧录效率低还容易出错,我习惯把烧录命令写成脚本。一个基于OpenOCD的烧录脚本:
#!/bin/bash set -e ELF_FILE="build/firmware.elf" OPENOCD_CFG="-f interface/stlink.cfg -f target/stm32f1x.cfg" if [ ! -f "$ELF_FILE" ]; then echo "Error: $ELF_FILE not found" exit 1 fi openocd $OPENOCD_CFG -c "program $ELF_FILE verify reset exit" echo "Flash done"配合Makefile使用:
flash: openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \ -c "program build/firmware.elf verify reset exit" debug: openocd -f interface/stlink.cfg -f target/stm32f1x.cfg & arm-none-eabi-gdb build/firmware.elf \ -ex "target remote localhost:3333"这样make flash就能一键烧录,make debug就能启动调试会话。
5.3 版本管理与固件追溯
嵌入式项目的版本管理有几个特殊点。一是二进制文件不要进Git,.elf、.bin、.hex这些构建产物应该放在.gitignore里。二是链接脚本和启动文件要进Git,这些是源码的一部分。三是固件版本号要嵌入代码,方便运行时查询。我通常会在代码里定义一个版本结构体:
const struct { uint8_t major; uint8_t minor; uint8_t patch; char git_commit[8]; char build_date[16]; } firmware_version = { .major = 1, .minor = 2, .patch = 3, .git_commit = "abc12345", .build_date = __DATE__, };__DATE__是编译器内置宏,编译时自动填入日期。git_commit可以用构建脚本自动生成,把当前Git commit的前8位写进去。这样出问题时,通过串口或者调试器读出这个结构体,就能知道板子上跑的是哪个版本。
5.4 常见问题速查与避坑清单
最后整理一份速查表,把整个流程中的高频问题和解决方向汇总一下:
| 阶段 | 问题 | 解决方向 |
|---|---|---|
| 编译 | 头文件找不到 | 检查include路径、确认文件存在 |
| 编译 | 未定义引用 | 检查库链接、确认函数实现存在 |
| 编译 | 内存溢出 | 优化代码、检查链接脚本、换芯片 |
| 烧录 | 连接失败 | 查供电、接线、降速、复位模式 |
| 烧录 | 校验失败 | 降速、检查Flash、重烧 |
| 烧录 | 读保护 | 解锁、全片擦除 |
| 仿真 | 断点无效 | 减少断点、检查优化等级 |
| 仿真 | 变量显示异常 | 加volatile、降优化等级 |
| 仿真 | 程序跑飞 | 检查中断、看HardFault寄存器 |
| 运行 | 上电不启动 | 查启动文件、链接脚本、时钟配置 |
几个我踩过的坑特别提醒一下。第一,不要用太长的杜邦线连SWD,超过20厘米信号质量就明显下降,最好用排线或者PCB上的调试座。第二,烧录前先擦除,有些芯片不擦除直接写会出问题,OpenOCD的program命令默认会擦除,但有些工具需要手动加擦除步骤。第三,注意芯片的读保护状态,新买的芯片可能是空的,但二手芯片或者别人用过的可能设了保护。第四,调试完记得断开调试器再上电测试,有些调试器会拉住复位线或者影响启动。
这套流程我用了很多年,从STM32到ESP32,从裸机到RTOS,基本都适用。工具在变,芯片在变,但编译、烧录、仿真这条链路的底层逻辑是相通的。把这条链路吃透,你面对任何新芯片都能快速上手。