我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

HPM6750 RISC-V裸机开发环境搭建与CMake实战

HPM6750 RISC-V裸机开发环境搭建与CMake实战 1. 先别急着敲命令HPM6750 开发环境的整体思路拆解HPM6750 RISC-V 开发这件事难的不是写代码而是把一条从宿主机到目标板的链路完整打通Ubuntu 上的交叉编译器认得这颗芯片的指令集与浮点 ABICMake 能把一堆源文件、启动文件、链接脚本正确组织起来最后产物能被调试器识别、能被烧进板子、能被串口吐出第一个字符。这条链路里任何一环的版本或参数对不上表现出来的现象往往是编译过了但跑飞链接过了但不进 main这类极易让人怀疑人生的故障。我见过不少朋友卡在这一步从别处抄来一份工程cmake ..一执行就报 The C compiler is not able to compile a simple test program然后开始怀疑工具链装坏了、Ubuntu 版本不对、CMake 有 bug一通重装下来问题依旧。实际上这类问题的根因大多非常朴素——CMake 默认试图链接一个可执行文件来验证编译器而裸机环境的工具链没有对应芯片的启动代码和链接脚本链接必然失败。知道了这一层一行set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)就能解决。所以这篇东西我不打算写成一份命令清单而是把每一步为什么这么做、做错了会怎样讲透。内容面向几类人刚从 STM32 那套 Keil/IAR 生态迁过来、想搞明白命令行构建流程的嵌入式开发者手里已经有一块 HPM6750 评估板、想跑通第一个裸机工程的初学者以及需要把个人验证工程升级成多人协作构建体系的工程负责人。全部操作基于 Ubuntu 桌面/服务器版工具链走 RISC-V GNU 交叉编译这一条主流路线构建系统用 CMake调试走 OpenOCD GDB 这套通用组合。文中给出的内存地址、时钟参数有一部分是示意值具体数值请以手上的芯片手册和官方 SDK 为准但方法和取舍逻辑是完全通用的。2. 方案选型为什么是 Ubuntu CMake RISC-V GNU 这套组合2.1 三条能走通的路以及我为什么选第三条给 HPM6750 搭环境现实中有三条路可走。第一条是直接用商业 IDE装完就能点烧录代价是构建过程完全黑盒出问题只能看它给你的那几行日志而且版本升级、CI 集成、多人协作都很难。第二条是用厂商提供的图形化配置工具生成工程本质上还是把一份 CMake 或 Makefile 工程包装起来好处是启动快坏处是配置项一旦超出模板范围就得回头改底层文件。第三条是自己搭一套命令行工具链加 CMake前期配置量大但一旦跑通构建过程完全透明可以塞进持续集成流水线可以用任意编辑器出问题能一路挖到汇编层面。我选第三条。原因很直接HPM6750 是双核 RISC-V、主频几百兆的芯片工程规模一旦上去源文件数量很快过百手工维护 Makefile 的依赖关系是自找麻烦。CMake 的增量构建、目标依赖推导、工具链抽象这几项能力在长期项目里的收益远大于初期配置成本。而 Ubuntu 作为宿主机几乎是默认选择——交叉工具链的预编译包、OpenOCD、Python 脚本依赖在 Debian 系上装起来的阻力最小。2.2 工具链、构建系统、调试器三者职责要分清很多人调不通环境是因为没搞清楚这三样东西各管什么。交叉工具链负责把 C 源码变成 RISC-V 机器码它的关键属性是目标架构-march和 ABI-mabi装错了或者参数不匹配产物在芯片上就执行不了。CMake是一个构建生成器它自己不做编译只负责算出哪些文件需要编译、用什么命令编译、链接时按什么顺序排然后把这些规则交给 Ninja 或 Make。OpenOCD是调试探针的驱动层它把 USB 上的调试器协议翻译成 RISC-V Debug Module 能听懂的命令GDB 再通过 OpenOCD 的 TCP 端口下指令。这三者的边界清楚了排查问题就不会乱。编译报错就盯工具链参数链接报错先看链接脚本和库顺序烧录失败基本是 OpenOCD 配置或 USB 权限程序跑飞则要回头审视启动文件和内存布局。我在实际项目里维护过一张排查表把故障现象—所属层—第一检查点对应起来新人上手时省了大量时间后面第 6 节会完整给出。2.3 目录结构先定好后面少返工工程目录我建议在动手之前就固定下来因为 CMake 的路径一旦散乱后期挪动成本极高。我的习惯是分四层toolchain/放交叉编译工具链的解压目录和 CMake 工具链描述文件sdk/放官方底层驱动与启动文件app/放业务代码build/作为 out-of-source 构建目录永远不提交到版本库。这样做的意义在于把不可变的三方依赖和可变的自有代码彻底分离升级芯片支持包时只需要替换sdk/业务代码一行不动。project-root/ ├── toolchain/ │ ├── riscv32-unknown-elf/ # 交叉工具链 │ └── riscv32-unknown-elf.cmake # CMake 工具链文件 ├── sdk/ │ ├── soc/ # 芯片启动、时钟、寄存器定义 │ ├── drivers/ # 外设驱动 │ └── cmake/ # 厂商提供的构建脚本片段 ├── app/ │ ├── CMakeLists.txt │ └── src/main.c ├── cmake/ │ └── helpers.cmake # 自定义函数转 bin、打印 size └── build/ # 构建产物不提交注意build/目录一定要写进.gitignore。我踩过一个坑——某次把带绝对路径的CMakeCache.txt提交上去同事拉下来后 CMake 一直用缓存的旧工具链路径怎么删都删不干净最后只能整个目录重建。3. 核心细节解析RISC-V 工具链参数与内存布局3.1 架构字符串和 ABI 是全流程的地基-marchrv32imafc和-mabiilp32f这两串字符是整个工程里最不能写错的地方。rv32表示 32 位地址空间i是基础整数指令集m是硬件乘除法没有它一次乘法会被编译器展开成一长串移位加法性能损失巨大a是原子指令做多核同步和中断安全的数据结构时离不开f是单精度浮点c是压缩指令能让代码体积减小两三成对 Flash 容量紧张的板子很有价值。ilp32f里的i是 int、l是 long、p是指针三个都是 32 位最后的f表示浮点参数通过 FPU 寄存器传递。这里最容易翻车的是把ilp32f和ilp32混用某个预编译库是软浮点编译的你的工程是硬浮点链接器会直接报cant link soft-float modules with double-float modules。这种错误信息看起来吓人其实就是 ABI 不一致把所有依赖库的编译参数对齐即可。工具链装完后务必验证一遍看它默认带的是什么配置riscv32-unknown-elf-gcc -v # 关注输出里的 # --with-archrv32imafc --with-abiilp32f如果默认值和你的目标不一致没关系CMake 里显式传参覆盖即可但一定要在编译 C 和汇编时传同一套参数否则同一个目标文件集合里会混进两种 ABI。3.2 内存分区怎么算链接脚本就不会写错HPM6750 的片内存储大致分几块靠近内核的紧耦合指令存储区、紧耦合数据存储区以及更大容量的系统 SRAM还有通过外部总线挂的 NOR Flash 和 SDRAM。这些区域的起始地址和大小必须以手册为准我这里只用一个示意分区说明计算逻辑MEMORY { ILM (rx) : ORIGIN 0x00000000, LENGTH 256K DLM (rw) : ORIGIN 0x00080000, LENGTH 256K AXI_SRAM (rwx) : ORIGIN 0x00100000, LENGTH 512K FLASH (rx) : ORIGIN 0x80000000, LENGTH 4M }写链接脚本之前先做一次容量核算。假设你的代码编译出来.text加.rodata一共 180KB那放在 256KB 的 ILM 里就很紧张中断向量表、启动代码、频繁调用的函数都堆在里面再加上后面功能迭代很容易溢出。我的做法是把热路径中断服务程序、通信协议解析、核心算法显式放进 ILM 段其余代码留在 Flash 里执行通过 XPI 缓存加速。数据同理.data、.bss和堆栈优先放 DLM 或 AXI SRAM把大块的图像、音频缓冲放到 SDRAM。SECTIONS { .text : { KEEP(*(.start)) KEEP(*(.vectors)) *(.text.hot) *(.text*) *(.rodata*) } ILM .data : { *(.data*) } DLM AT FLASH .bss (NOLOAD) : { *(.bss*) *(COMMON) } DLM }这里AT FLASH是关键.data段的初值存在 Flash 里运行时地址在 RAM 中启动代码必须负责把这段数据从 Flash 搬到 RAM再把.bss清零。这段搬运逻辑写在启动汇编里数错一个字节全局变量就是随机值而且这种 bug 只在特定变量上偶发极难定位。所以启动文件里的搬运循环我通常会加一句断言链接进去打开校验确认搬运长度和链接脚本里算出来的一致。3.3 中断向量表和栈顶对齐这两个细节最容易翻车RISC-V 的mtvec寄存器存放中断入口基地址低两位是模式位。用直接模式时基地址必须 4 字节对齐用向量模式时要求更严格的对齐粒度。启动汇编里通常这么写.section .vectors, ax .align 8 .global _vectors _vectors: j default_handler /* 0 */ .align 2 j trap_handler_1 /* 1 */ .align 2 j trap_handler_2 /* 2 */ /* ... 其余入口 */栈顶对齐同样是个隐形雷区。RISC-V 的 ABI 要求栈指针 16 字节对齐如果启动代码里直接把链接脚本导出的_stack_top塞进sp而没有向下取整对齐一旦调用带向量化或浮点运算的函数就会因为未对齐访问触发异常。我一般的写法是la sp, _stack_top andi sp, sp, -16 /* 强制 16 字节对齐 */两行代码省掉可能长达两天的调试。这类看着多余、实际救命的防御性写法我建议都保留代价几乎为零。4. Ubuntu 环境搭建实操从裸系统到能编译4.1 系统依赖一次装齐别等报错再补Ubuntu 20.04 和 22.04 我都在用两者的差异主要在默认 GCC 版本和 Python 版本上对交叉编译本身影响不大。建好系统后先更新源然后把构建依赖一次性装完sudo apt update sudo apt install -y build-essential cmake ninja-build git \ python3 python3-pip pkg-config \ libusb-1.0-0-dev libftdi1-dev \ minicom screen gdb-multiarch这里每一项都不是凑数的。ninja-build比 Make 快大工程增量构建的差距非常明显libusb和libftdi是 OpenOCD 驱动常见调试探针的前提gdb-multiarch是因为 Ubuntu 自带的gdb通常只支持 x86调试 RISC-V 目标会报 Architecture not recognizedminicom用来盯串口输出虽然现在很多人用图形化串口工具但命令行环境里它最省事。关于交叉工具链Ubuntu 官方源里确实有gcc-riscv64-unknown-elf但它默认目标是 64 位配套的 newlib 也往往只有 rv64 版本用它编 rv32 代码虽然在简单场景下能过一旦用到printf浮点格式化这类依赖库的功能就会出问题。所以我推荐直接用工具链发布方提供的 rv32 预编译版本解压即用不污染系统包管理。sudo mkdir -p /opt/riscv sudo tar -xzf riscv32-unknown-elf-gcc-*.tar.gz -C /opt/riscv export PATH/opt/riscv/bin:$PATH把export那行写进~/.bashrc或者单独放一个env.sh团队协作时后者更好——每个人 source 一下就行不会互相覆盖本地配置。4.2 串口和调试器的权限先解决再谈调试Linux 下 USB 设备默认只有 root 能读写普通用户直接跑 OpenOCD 会看到LIBUSB_ERROR_ACCESS。串口同理不在dialout组里打不开/dev/ttyUSB0。# 串口权限 sudo usermod -aG dialout $USER # 调试器 udev 规则 sudo tee /etc/udev/rules.d/99-debugprobe.rules EOF # 按实际调试器的 VID/PID 填写 SUBSYSTEMusb, ATTR{idVendor}0403, ATTR{idProduct}6014, MODE0666, GROUPplugdev EOF sudo udevadm control --reload-rules sudo udevadm trigger改完组权限必须重新登录才生效这一点坑过太多人——他们在当前终端里groups看不到dialout就开始怀疑命令写错了。用lsusb确认调试器 VID/PID别照抄别人规则里的数字不同批次、不同型号的探针 ID 可能完全不同。4.3 验证工具链是否真的可用装完先做一次最小验证别急着上工程。写一个只有main返回的空文件用命令行直接编一遍riscv32-unknown-elf-gcc -marchrv32imafc -mabiilp32f \ -nostdlib -T link.ld -o test.elf test.c riscv32-unknown-elf-objdump -d test.elf | head -30objdump里能看到lui、addi、jal这些 RISC-V 指令说明工具链本身没问题。如果objdump报 cant disassemble for architecture那多半是 binutils 版本太老认不出带c扩展的指令换个新版本即可。5. CMake 构建体系实战把工具链描述写对5.1 toolchain.cmake 的每一行都有讲究CMake 交叉编译的标准入口是CMAKE_TOOLCHAIN_FILE这个文件在第一次配置时被读取之后写进缓存改内容不会自动生效。所以每次改完工具链文件我都会习惯性删掉build/CMakeCache.txt重新配置避免踩到缓存不一致的坑。# toolchain/riscv32-unknown-elf.cmake set(CMAKE_SYSTEM_NAME Generic) set(CMAKE_SYSTEM_PROCESSOR riscv32) set(TOOLCHAIN_PREFIX riscv32-unknown-elf-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g) set(CMAKE_AR ${TOOLCHAIN_PREFIX}ar CACHE FILEPATH ) set(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy CACHE FILEPATH ) set(CMAKE_OBJDUMP ${TOOLCHAIN_PREFIX}objdump CACHE FILEPATH ) set(CMAKE_SIZE ${TOOLCHAIN_PREFIX}size CACHE FILEPATH ) # 关键裸机没有可执行入口不做链接测试 set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) set(ARCH_FLAGS -marchrv32imafc -mabiilp32f -mcmodelmedany) set(CMAKE_C_FLAGS_INIT ${ARCH_FLAGS} -ffunction-sections -fdata-sections) set(CMAKE_ASM_FLAGS_INIT ${ARCH_FLAGS}) set(CMAKE_EXE_LINKER_FLAGS_INIT ${ARCH_FLAGS} -Wl,--gc-sections -nostartfiles) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY这一行的作用是让 CMake 在检测编译器能力时只编成静态库跳过链接环节。前面提到的那个 not able to compile a simple test program 报错九成情况靠这一行解决。-mcmodelmedany也值得说一下。默认的medlow模式要求全局符号落在 ±2GB 的地址窗口内而 HPM6750 的外部 Flash 起始地址往往在 0x80000000 这类高位如果代码和数据分布跨度大链接时就会报 relocation truncated。改成medany用更长指令序列寻址虽然代码体积略增但能覆盖整个地址空间工程里我一般直接用medany省心。5.2 CMakeLists 分层把编译什么和怎么编译分开根目录的CMakeLists.txt只做三件事指定最低版本、声明工程、把子目录加进来。真正的编译选项和源文件清单放在各自子目录里这样换平台、加模块都不用动根文件。cmake_minimum_required(VERSION 3.20) project(hpm6750_demo C ASM) add_subdirectory(sdk) add_subdirectory(app)app/CMakeLists.txt里是业务目标add_executable(${PROJECT_NAME}.elf src/main.c src/startup.S src/system.c ) target_include_directories(${PROJECT_NAME}.elf PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/sdk/include ) target_link_libraries(${PROJECT_NAME}.elf PRIVATE sdk_drivers) target_link_options(${PROJECT_NAME}.elf PRIVATE -T${CMAKE_SOURCE_DIR}/sdk/link/${BOARD}.ld -Wl,-Map${PROJECT_NAME}.map -Wl,--print-memory-usage ) # 生成 bin 和 hex add_custom_command(TARGET ${PROJECT_NAME}.elf POST_BUILD COMMAND ${CMAKE_OBJCOPY} -O binary $TARGET_FILE:${PROJECT_NAME}.elf ${PROJECT_NAME}.bin COMMAND ${CMAKE_OBJCOPY} -O ihex $TARGET_FILE:${PROJECT_NAME}.elf ${PROJECT_NAME}.hex COMMAND ${CMAKE_SIZE} $TARGET_FILE:${PROJECT_NAME}.elf )-Wl,--print-memory-usage这个链接选项我强烈建议加每次构建都会打印各内存区的占用量和百分比Memory region Used Size Region Size %age Used ILM: 124 KB 256 KB 48.44% DLM: 38 KB 256 KB 14.84% FLASH: 176 KB 4 MB 4.29%一眼就能看出哪个区域快满了。曾经有个项目在迭代到后期时 ILM 用量冲到 98%再添一个函数就链接失败好在有这行输出提前一周就把几个大数组挪到了 SDRAM。5.3 编译选项的取舍优化、体积与调试的三角平衡开发阶段我用-Og它的优化幅度低于-O2但不会像-O0那样把变量全放栈上导致单步调试跳来跳去兼顾了可读性和调试体验。发布阶段切-Os优先压体积对 Flash 容量紧张的板子尤其重要。if(CMAKE_BUILD_TYPE STREQUAL Debug) target_compile_options(${PROJECT_NAME}.elf PRIVATE -Og -g3 -gdwarf-2) else() target_compile_options(${PROJECT_NAME}.elf PRIVATE -Os -g) endif()-gdwarf-2而不是默认的 DWARF 4/5是因为部分老版本 GDB 对高版本调试信息格式支持不完整变量显示不出来。这个选择看起来倒退实际用起来省心。另外-flto链接时优化在裸机工程里要慎用它会把跨文件的函数内联导致断点位置错乱而且和--gc-sections配合时偶尔会误删被中断向量表引用的函数。如果要开务必在向量表里用KEEP()或__attribute__((used))保护。浮点库的选择也有讲究。-specsnano.specs能显著减小 newlib 体积但它的printf默认不支持%f需要额外链接浮点格式化支持。调试阶段我通常先不开 nano等功能稳了再切过去逐个确认输出格式没退化。5.4 构建与产物分析每次都要看 size配置和构建命令本身很简单cmake -B build -G Ninja \ -DCMAKE_TOOLCHAIN_FILE../toolchain/riscv32-unknown-elf.cmake \ -DCMAKE_BUILD_TYPEDebug \ -DBOARDhpm6750evk cmake --build build -j$(nproc)构建完成后除了看 size 输出我还会做两件事。一是看 map 文件里的内存分布确认热函数确实落在 ILM 段grep -A 20 Memory Configuration build/app/hpm6750_demo.map二是反汇编入口附近确认启动流程符合预期riscv32-unknown-elf-objdump -d build/app/hpm6750_demo.elf | head -60如果第一条指令不是跳到启动函数而是落在某个数据区那八成是链接脚本里.text段的排序或者ENTRY()指定错了。这种检查花三十秒能避免后面一小时的上板排查。6. 烧录、调试与运行观测6.1 OpenOCD 配置文件的组织方式OpenOCD 的启动参数通常由三部分组成调试器接口配置、目标芯片配置、以及自定义的初始化脚本。openocd -f interface/ftdi/ft2232.cfg \ -f target/riscv.cfg \ -f board/hpm6750.cfg \ -c adapter speed 4000adapter speed这个参数需要根据排线长度和信号质量调。线短、干扰小可以上到 4000kHz 甚至更高下载快如果频繁出现校验失败先降到 1000kHz 试试能稳定就说明是信号完整性问题而不是芯片坏了。我在一块走线较长的自制板上就遇到过这种情况降速后一切正常。烧录 NOR Flash 需要对应的 Flash 驱动算法OpenOCD 通过flash bank命令声明。如果写进去读出来不一致先查flash probe是否识别到正确的容量再查芯片的写保护位有没有被误置。6.2 GDB 调试的启动序列调试裸机程序不能直接gdb ./app.elf需要指定架构并连接远程目标gdb-multiarch build/app/hpm6750_demo.elf (gdb) set architecture riscv:rv32 (gdb) target extended-remote :3333 (gdb) monitor reset halt (gdb) load (gdb) monitor reset halt (gdb) break main (gdb) continue中间两次reset halt不是啰嗦。第一次是为了在干净状态下 load第二次是因为 load 完成后 CPU 状态可能被改变需要重新复位到入口再下断点。漏掉第二次经常出现断点打不上或者程序从奇怪的地方开始跑。如果要调试第二个核需要通过 OpenOCD 的多目标支持切换或者用-c targets查看当前可用目标列表再target切换。多核调试的坑很多建议先让单核跑稳再考虑双核协同。6.3 串口观测与启动日志设计上电后最先看到的东西应该是串口日志。波特率计算别想当然分频值 时钟源 / (16 × 目标波特率) 24,000,000 / (16 × 115,200) ≈ 13.02 → 取 13 实际波特率 24,000,000 / (16 × 13) ≈ 115,384 误差 (115,384 - 115,200) / 115,200 ≈ 0.16%0.16% 的误差远小于 UART 通常能容忍的 2%~3%可以放心用。但如果时钟源是内部 RC 振荡器且精度只有 ±2%叠加上分频误差就可能超限出现乱码。这种情况要么换外部晶振要么降低波特率。启动日志我推荐分级输出最开始几行只打印版本号和时钟频率用最简单的轮询发送不依赖中断和动态内存等系统初始化完成再切到带缓冲的日志模块。这样即使后面初始化失败前面的日志也一定能出来能快速定位卡在哪一步。7. 常见问题速查与踩坑记录7.1 编译链接期问题对照表现象根因处理方式not able to compile a simple test programCMake 尝试链接可执行文件设CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARYcant link soft-float modules with double-floatABI 混用所有依赖统一-mabiilp32frelocation truncated to fit寻址超出 medlow 范围改-mcmodelmedanyundefined reference to _start缺启动文件或未指定入口加入startup.S链接脚本写ENTRY(_start)region ILM overflowed by N bytes内存分区不够看 map 文件把大数组移到 SDRAMundefined reference to printf用了 nano 但没开浮点格式化加-u _printf_float或改回完整 newlib这张表里最值得展开的是最后一条。-specsnano.specs换上之后printf(%f)会静默输出空或者乱值不报错只是结果不对。我第一次遇到时盯着变量值看了半天以为是浮点运算有问题实际是格式化库被裁掉了。所以发布构建里如果用到浮点打印务必在真机上验证输出内容别只看编译通过。7.2 运行期故障的排查顺序真机上跑飞我的排查顺序固定是四步先确认程序确实进了main在入口打断点或在第一行翻转 GPIO再确认栈指针合理读sp看是否落在预期内存区然后确认时钟配置正确用示波器量输出引脚或者读时钟状态寄存器最后才怀疑外设驱动。这个顺序的价值在于从最底层的假设开始验证。很多人一上来就怀疑驱动写错了改了半天发现是时钟根本没配上外设自然一点反应都没有。还有一种经典情况程序在调试器下能跑脱机就不行。这通常是初始化时序问题——调试器连接时会给芯片一定的启动延迟脱机时上电速度更快某个外设还没准备好就被访问了。解决办法是在关键初始化前加延时或者轮询状态位而不是靠for循环空转后者在不同优化等级下延时长度完全不同。7.3 几条只有踩过才知道的经验第一条不要迷信-O0更好调试。-O0下所有变量都在栈上函数调用层次深栈用量反而更大小内存的板子容易出现栈溢出。而且-O0生成的代码量大某些区域直接溢出。-Og是更合理的选择。第二条中断里不要调用printf。这一条大家都知道但实际项目里还是经常出现。表现是串口偶尔卡死或者输出错乱因为printf内部有不可重入的静态状态。真要在中断里输出就准备一个环形缓冲主循环里取出来打印。第三条改链接脚本后一定要全量重编。CMake 对链接脚本的依赖追踪不一定完整改完.ld只做增量构建可能还是用旧的布局现象是改了没效果。我现在的习惯是改链接脚本后直接cmake --build build --clean-first。第四条调试器连接不稳定先怀疑供电。USB 供电不足会导致探针间歇性掉线尤其是板子上外设多、电流大的时候。换个带独立供电的 USB Hub或者给板子单独供电问题往往就消失了。第五条把编译命令导出留档。加-DCMAKE_EXPORT_COMPILE_COMMANDSON生成的compile_commands.json能被各种编辑器读取做代码补全也能在排查某个文件到底用了什么参数时直接查比翻 CMakeLists 快得多。8. 后续可以怎么扩展这套工程跑通第一个点灯工程之后我一般会按这个顺序往上叠先加一个基于系统滴答的软件定时器框架把所有延时和周期任务统一管理再把日志模块做成可分级、可重定向的然后接一个命令行交互通过串口敲命令读写寄存器、查看任务状态。这套东西搭好之后后面调任何外设都有顺手的工具不至于每次都靠点灯猜状态。构建侧可以做的事也不少。加一个cmake --install的安装规则把产物按版本号归档接一个简单的脚本把 size 输出解析出来和历史记录比对代码体积异常增长时自动报警再往前一步把整套构建放进容器镜像里新同事拉下来直接就能编不用再走一遍环境配置。这些投入在项目周期超过三个月的场景下回报都很明显。我自己在这颗芯片上折腾下来的最深体会是环境搭建阶段的每一分钟耐心都会在后面调试阶段以十倍的效率还回来。那些看起来能跑就行的临时配置——随手复制的工具链参数、没对齐的栈指针、没校验的链接脚本——最终都会在某个深夜变成一个难以复现的随机故障。把地基打扎实后面写业务代码才是件愉快的事。
返回列表