posts/stm32-chipid-verify.md

STM32 实战复盘:一次烧录验证串起的整条工具链

📅 发布日期2026-09-15
STM32 系列 复盘 15 分钟阅读 #STM32#ChipID#Keil#CubeProgrammer#UID

STM32 实战复盘:一次烧录验证串起的整条工具链

新电脑部署完 STM32 开发环境(过程见《换机不翻车:嵌入式开发环境整机迁移复盘》),光"编译通过"还不算完——得拿一块真板子,从编译、烧录到串口输出完整跑一遍才算数。这次用洋桃 1 号开发板(STM32F103C8T6)自带的 48_CHIPID 模块做端到端验证,一路踩了三个坑,也顺手把整条工具链的验证做扎实了。

#一、连接验证:CubeProgrammer 探测链路

烧录之前,先确认"线是通的"。打开 STM32CubeProgrammer v2.23.0,连上 ST-Link 点 Probe,结果全部正常:

探测项结果说明
ST-LINK SN37FF7106...调试器序列号(前缀)
固件V2J37S7ST-LINK 固件版本
目标电压3.24VSWD 供电正常
Device ID0x410STM32F103 中容量(Medium-density)器件
SWD 频率4MHz调试时钟

目标电压 3.24V 说明 SWD 供电正常,Device ID 0x410 是 STM32F103 中容量器件的标准 ID。调试器、驱动、接线、供电这一段全绿——坑都不在连接层,全在后面。

#二、坑一:中文路径让 CubeProgrammer 找不到 hex

第一个坑来得毫无悬念。hex 文件放在中文目录(例如 D:\嵌入式\烧录\)下,CubeProgrammer 加载时直接报错:

txtFile does not exist: 48_CHIPID.hex

文件明明就在那里,路径一个字符不差。这不是 CubeProgrammer 一家的毛病——之前 GNU ld 链接器也不认中文路径,同款问题同款结局:嵌入式工具链里一堆老工具对非 ASCII 路径的支持约等于零。

解法:不跟工具较劲,把 hex 拷到纯 ASCII 路径(比如 C:\stm32_tmp\)再加载烧录,一次通过。

一句话经验:给嵌入式工具链喂的任何文件,路径一律纯 ASCII,能省下无数排查时间。

#三、坑二:改一个字符拿烧录成功的铁证

第二个坑更隐蔽:这块板子出厂就带着旧程序,OLED 显示的内容和"我以为刚烧进去的程序"看起来一模一样。CubeProgrammer 说烧录 OK,可万一烧进去的是旧 hex 呢?板子上看得见的变化,才是能让人睡踏实的证据。

解法:从源码下手,把 OLED 显示的字符串从 "CHIP ID TEST1" 改成 "TEST2",重新编译烧录。烧完 OLED 显示 TEST2——铁证:板子上跑的就是这次新编出来的程序。

编译没有点 GUI,直接用 Keil UV4 命令行:

txtUV4.exe -b demo48_CHIPID.uvproj -j0 -o build.log

-b 批量构建,-j0 不弹 GUI 窗口,-o 把编译输出写进 build.log,编完翻日志确认 error/warning 即可。

这里有个值得复用的工程思路:改一个字符,验证整条链路。完整链条是"源码 → Keil 编译 → hex → CubeProgrammer 烧录 → Flash → 复位运行 → OLED 显示",中间任何一环断了,TEST2 都出不来。挑一个链条末端、肉眼可见、且只可能由新程序产生的信号当验证标志,比盯着工具日志踏实得多。

#四、坑三:GBK 源码用 GB18030 读写保编码

第三个坑在源码本身:洋桃这套例程是 GBK 编码。如果用默认按 UTF-8 处理的编辑器或脚本直接改文件,保存的瞬间中文注释就变乱码,Keil 里打开一团糟。

解法:用脚本改源码时显式按 GB18030 读写(.NET 里 Encoding.GetEncoding("GB18030"),它是 GBK 的超集,向下兼容),改完编码不变,Keil 打开一切正常。

顺带记一笔:博客文件是 UTF-8,洋桃源码是 GBK,两套编码体系并存。处理哪套文件就用哪套编码,工具的默认值只能信一半,动手前先确认。

#五、实验升级:串口每 5 秒打印一次 ChipID

验证通过之后顺手做了个升级:原程序主循环 delay_ms(1000) 每秒打一次 ID,节奏太快不好观察,改成 delay_ms(5000),重编重烧。收串口这次不借助任何串口助手软件,直接用 PowerShell 的 System.IO.Ports.SerialPort 连 COM3、115200:

powershell$p = New-Object IO.Ports.SerialPort COM3,115200,None,8,One
$p.Open()
for(;;){ Start-Sleep -m 200; $p.ReadExisting() }

收到的输出:

txt[4s]  ChipID: 0670FF54 54537851 87152711
[9s]  ChipID: 0670FF54 54537851 87152711
[14s] ChipID: 0670FF54 54537851 87152711

三次输出间隔正好 5 秒,节奏完美;三次 UID 完全一致,稳定可靠。到这里,"写进去的程序"和"跑出来的行为"终于对上了。

#六、ChipID 知识点:96 位 UID 全球唯一

这次实验的主角 ChipID 值得单独记一笔。STM32 的 96 位 UID(Unique Device ID)出厂时固化在芯片里,全球唯一,F103 从固定地址(如 0x1FFFF7E8)连续读 3 个 32 位字就能拿到。四个典型用途:

  • 产品防伪与授权:固件绑定 UID 做授权校验,克隆板跑不起来。
  • 通信地址派生:多节点组网时用 UID 派生地址,省掉人工分配。
  • 加密密钥种子:配合板载私密数据派生密钥,每台设备各不相同。
  • 免贴标序列号:出厂不用人工贴标,读 UID 即可追溯。

还有一个反直觉的点:程序里出现 "chipID error!" 是正常的。代码里硬编码的比对 UID 是杜洋那块板子的(066EFF34 开头),你手里任何一块别的板子 UID 必然不同,比对自然失败——这是演示程序的"特性",不是 bug,换上自己板子的 UID 就不报了。

写完上面这段后,我们顺手把剧情走完了:把比对值改成自己板子的 UID(0x0670FF54 / 0x54537851 / 0x87152711),UV4 重编、CubeProgrammer 烧录、COM3 先开端口再让 ST-Link 复位,抓到开机瞬间——

text[  0s] ChipID: 0670FF54 54537851 87152711
[  1s] chipID OK!
[  1s] ChipID: 0670FF54 54537851 87152711
[  6s] ChipID: 0670FF54 54537851 87152711

这一下完成了防伪逻辑的最小闭环:程序只认原板 UID → 换成自己的 UID → 重新授权通过。把 UID 比对换成密钥校验,就是真实产品里的防克隆方案。另一个细节:抓启动打印要先开串口再复位芯片,顺序反了就只能看到主循环的重复输出。

#七、闭环复盘:整条工具链脚本化可复用

复盘到最后,这台新电脑的 STM32 工具链已经形成完整闭环:

  • 编译:Keil 5.25.3.0 命令行,UV4.exe -b 工程.uvproj -j0 -o build.log。
  • 烧录:CubeProgrammer CLI,hex 从纯 ASCII 路径加载。
  • 验证:PowerShell 串口直收,System.IO.Ports.SerialPort 打印输出。

三步各自独立、全部可脚本化,下次换任何一个工程,改一下工程名和 hex 路径就能整套复用。这次 48_CHIPID 验证的价值也在这里:环境部署的终点不是"软件都装好了",而是"从源码到串口输出,一条命令跑通"。

#八、实战延伸:用 UID 给固件上锁

防抄袭的本质是让固件离开这颗芯片就活不下去。按防护强度从低到高,有四档做法:

档位做法防住谁防不住谁
L1 裸比对if(ID==...) 不匹配就死循环直接拷 hex 到别的板会逆向的人(nop 掉跳转即可)
L2 密钥派生固件不存明文密钥,用 UID 异或/运算出密钥去解密 flash 里的授权块,解错了进残废模式拷贝固件 + 不会逆向的人耐心逆向的人
L3 自校验+读保护L2 基础上,运行时对代码区做 CRC 校验(种子含 UID),并开启 RDP 读保护禁止 SWD 读固件dump 固件者 + patch 校验分支者芯片级攻击(F1 时代 RDP 有 glitch 风险)
L4 外挂安全芯片MCU 与 ATSHA204 类加密芯片做挑战-应答,密钥永不出芯片几乎所有软件攻击成本换来的

L2 的最小骨架(可直接加进 48_CHIPID 工程体会):

cu32 key[3]; // 用 UID 现场派生密钥,固件里不存明文
key[0] = ID[0] ^ 0xA5A53C3C;  // 盐值自定
key[1] = ID[1] ^ 0x5A5AC3C3;
key[2] = ID[2] ^ 0xF0F00D0D;
// 授权块:量产前用上位机按同样算法加密魔数,烧到 flash 固定地址
// 固件上电解密,对不上就进残废模式
if (decrypt_magic(key) == 0x3C3CA5A5) {
        run_normal();
} else {
        while(1); // 残废:外设乱初始化 / 假数据 / 定期死机
}

量产授权流程三步:① 上位机读板子 UID → ② 按约定算法生成加密授权块 → ③ 烧到 flash 约定地址。固件全板通用,授权块每板唯一——这正是"授权烧录器"模式,产线一台工具全搞定。

诚实提醒:F103 是 2007 年的芯片,RDP 读保护有历史漏洞,高价值产品建议换 F4/F7 或外挂安全芯片;学习和中小商用,L2+RDP 已经能挡住 95% 的抄袭者。

#九、相关阅读

#九、更新日志

📝共 3 条记录
日期类型更新内容
2026-09-15新增首次建档:CubeProgrammer 连接验证 → 中文路径/旧程序/GBK 编码三个坑 → 串口 5 秒打印 ChipID → 工具链闭环
2026-09-15追加补完反转:UID 换成自己板子后 chipID OK!,防伪最小闭环;附先开串口再复位的抓取技巧
2026-09-15追加新增第八节:UID 防抄袭四档方案(裸比对/密钥派生/自校验+读保护/安全芯片)、L2 最小代码与量产授权流程