尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F030调试必看:RDP读保护、CPUID与Bootloader版本详解

STM32F030调试必看:RDP读保护、CPUID与Bootloader版本详解 做嵌入式调试时最怕的不是代码写不出来而是手里的芯片突然变得“不听话”。前阵子我调一批STM32F030C8T6明明同一型号有的板子能连上调试器有的却死活连不上还有几片读出来的Flash内容完全对不上。排查到最后问题全出在三个不起眼的参数上RDP读保护等级、芯片的CPUID也就是唯一ID和版本ID、以及出厂Bootloader版本。这三个参数一个决定你能不能随便读内部程序一个告诉你这颗芯片到底是什么来路另一个决定芯片内部引导程序和你的升级代码能不能配合。这篇文章就从这三个参数入手把它们的原理、读取方法、常见坑点一次讲清楚适合做固件保护、产线烧录、设备返修和固件升级开发的工程师参考刚入门的朋友也可以照着操作。1. 三个参数到底是什么为什么要一起看很多朋友刚接触STM32F030C8T6时只关心GPIO怎么点灯、串口怎么收发对芯片自身的“身份信息”并不敏感。可真到量产或者做安全保护时这三个参数就成了绕不开的关口。它们分别对应芯片的“锁”、“身份证”和“出厂引导程序”。1.1 RDP读保护固件安全的第一道门RDP的全称是Read Protection中文叫读保护它是STM32芯片内部Flash保护机制的重要组成部分。对于F0系列读保护等级由芯片选项字节Option Bytes中的RDP字节决定选项字节的起始地址是0x1FFFF800其中第一个字节就是RDP值。通过读取这个字节我们可以判断当前芯片处于什么保护状态RDP值等于0xA5时芯片处于Level 0也就是无读保护调试器可以直接读取和修改Flash。RDP值是其他任意非0xCC值比如0x55、0x00芯片处于Level 1此时通过SWD或JTAG读取Flash内容会被禁止代码一般也无法从RAM中读出Flash数据。RDP值等于0xCC时芯片处于Level 2这是最高级别的保护不仅禁止外部读取还会永久禁用调试接口这个时候想让调试器重新连上芯片都无法做到只能把它当一次性器件使用。为什么需要关心RDP因为很多开发者在调试阶段习惯把芯片裸奔着用RDP一直处于Level 0等到产品要出货或者遇到返修时才发现别人拿个ST-Link就能把固件拖走。反过来也有不少人在不知情的情况下拿到RDP Level 1的芯片用调试器一连接就报错误以为芯片坏了。所以RDP是判断“这颗芯片能不能随意读”的第一指标。1.2 CPUID与唯一ID芯片的身份证CPUID这个词在不同语境下有不同含义。ARM内核有一个标准的CPUID寄存器地址在0xE000ED00用于识别内核型号和版本。但在STM32的日常调试中我们通常更关心两组数据一组是芯片的器件IDDEV_ID和修订版本号REV_ID存放在DBGMCU_IDCODE寄存器地址是0x40015800另一组是STM32出厂时烧录的96位唯一ID分布在三个连续地址上。STM32F030C8T6的DEV_ID一般能读到0x440具体以参考手册RM0360为准REV_ID则反映芯片的硅片版本。这两个值如果能和包装标签对上基本可以确认芯片型号和批次。而96位唯一ID存放在0x1FFFF7AC、0x1FFFF7B0、0x1FFFF7B4这三个32位地址中内容包含晶圆坐标、批次号等信息每颗芯片都不一样。这套ID在真实项目里非常有用。比如做设备绑定把唯一ID和软件授权码绑定防止一台设备的固件被复制到另一台再比如出现个别芯片硬件bug时可以靠REV_ID判断哪一批芯片需要规避某些问题。所以读取CPUID系列信息并不是“学术操作”而是很实际的工程需求。1.3 Bootloader版本出厂内置的升级引导STM32F0系列的Flash最底部有一段系统存储区System Memory出厂时芯片已经固化了一段Bootloader程序。通过把BOOT0引脚拉高并复位芯片就会从这段系统存储区启动运行内置Bootloader然后我们就能用USART、SPI或I2C等接口给芯片升级固件。不同芯片批次的出厂Bootloader版本可能不同而版本决定了它支持哪些命令、采用什么协议细节。尤其是做自研升级软件时如果Bootloader版本太老某些命令比如扩展擦除可能不支持或者返回数据格式有差异直接按新版本协议去发指令就会失败。所以读取Bootloader版本号是开发升级工具时必不可少的一步。这里要特别注意Bootloader版本和芯片的固件版本是两回事。固件版本是你自己写的应用程序的版本而Bootloader版本是芯片出厂自带的属于ROM里的固定程序用户不能随便改写。2. 最直接的读取方法用STM32CubeProgrammer加ST-Link如果手头有ST-Link或J-Link这类调试器读取RDP和CPUID信息是最快的。这个方法不需要写代码只要连好线、打开软件点几下鼠标就能看到结果。2.1 接线准备先把目标板和调试器连好。STM32F030C8T6支持SWD接口接线只有四根SWDIO、SWCLK、GND、VDD。ST-Link的SWDIO对应PA13SWCLK对应PA14这两根引脚在芯片上都有复用功能调试时不需要额外配置。连接时要注意几点。第一VDD必须和调试器的参考电压一致F030C8T6的工作电压是2.4V到3.6V常见接3.3V。第二SWDIO和SWCLK引脚上不建议加过长的飞线否则高速通信时容易出错实在线长就降低调试时钟频率。第三目标板最好独立供电调试器供电能力有限不要让调试器同时给板子大电流外设喂电。连接完成后打开STM32CubeProgrammer选择ST-LINK点击Connect。如果连接成功主界面会直接显示芯片型号、Device ID、Revision ID、UID等内容。这些信息就是从DBGMCU_IDCODE和唯一ID地址读出来的。2.2 界面上的实际信息怎么看STM32CubeProgrammer连接成功后在左侧的“Device information”面板里你能看到类似下面的信息Device ID显示0x440或类似值对应STM32F030系列。Revision ID显示芯片的修订版本比如0x2000。UID显示96位十六进制字符串。Flash size显示Flash容量比如64KB。RAM size显示8KB。这些数据读出来后大家可以直接截图存档方便后来和不良品对比。需要读RDP时点击左侧“Option Bytes”选项卡在“Read protection”区域能看到当前级别是Level 0、Level 1还是Level 2。如果显示Level 1说明芯片已经开了读保护量产板子在出厂前经常是Level 1状态。不过STM32CubeProgrammer这个窗口不会显示Bootloader版本号因为Bootloader版本需要进入系统内存启动模式后才能读出来它不在常规调试接口的寄存器里。想要知道版本号要靠后面讲的UART方式或者通过发送AN3155命令来获取。2.3 不想开软件自己写代码直接读寄存器有时候我们需要在量产治具上批量读取这三类信息靠人工开STM32CubeProgrammer不太现实。这时可以直接在STM32上用代码读取对应地址把结果通过串口打出来。以STM32F030C8T6为例核心逻辑大概是这样的#include main.h uint32_t uid0, uid1, uid2; uint32_t dev_id, rev_id; uint32_t rdp_level; uid0 *(volatile uint32_t *)0x1FFFF7AC; uid1 *(volatile uint32_t *)0x1FFFF7B0; uid2 *(volatile uint32_t *)0x1FFFF7B4; dev_id *(volatile uint32_t *)0x40015800 0x00000FFF; rev_id *(volatile uint32_t *)0x40015800 16; rdp_level *(volatile uint32_t *)0x1FFFF800 0xFF;这段代码的原理其实很简单ST公司把重要信息放在固定的地址上我们直接用指针去读。读出来之后再根据RDP值判断保护级别if (rdp_level 0xA5) { // 无读保护 Level 0 } else if (rdp_level 0xCC) { // Level 2最高保护 } else { // Level 1常见保护状态 }需要注意读RDP地址时0x1FFFF800是选项字节的起始地址它并不是一个普通的Flash地址而是Option Bytes映射区域。只要在芯片正常供电状态下直接读这个地址就可以拿到RDP值不需要额外初始化外设。实际调试时我一般会在程序里加一个串口输出把这几个值一起打印出来非常直观。3. 无调试器方案通过UART系统Bootloader读取调试器不是什么时候都方便。产线上经常没有ST-Link或者调试器接口被外壳挡住只有串口引出。这时就轮到系统Bootloader登场了。STM32F030C8T6出厂时内置了USART Bootloader我们完全可以用一个USB转串口模块把芯片切到系统内存启动模式通过协议命令读出前述参数。3.1 让芯片进入系统Bootloader要让STM32F030C8T6进入出厂Bootloader模式需要在复位时把BOOT0引脚设置为高电平。对于F0系列BOOT1引脚在部分封装上也可用于启动选择但F030C8T6的启动方式比较简单BOOT0为低时从主Flash启动BOOT0为高时从系统存储器启动。实际接线是这样把BOOT0接到3.3V然后把复位引脚拉低再释放或者直接重新上电芯片就会进入Bootloader。此时系统Bootloader会在定义的串口引脚上等待主机发送命令。对于F030C8T6最常用的调试串口是USART1对应PA9为TX、PA10为RX。把USB转串口的TX接到PA10RX接到PA9注意要交叉连接然后再连接GND。上电后Bootloader默认支持自动波特率检测所以把PC端串口设成115200bps、8数据位、偶校验、1停止位8E1通常都能识别。注意不要把校验位设成无校验否则Bootloader很可能不响应。3.2 AN3155协议速览与Get命令STM32的出厂Bootloader遵循一个公开协议文档号是AN3155全称是“STM32 microcontroller USART bootloader”。它规定主机和Bootloader之间通过一串命令通信每个命令由命令字节和它的取反字节组成Bootloader收到后会回复ACK0x79表示正确或者NACK0x1F表示命令错误。我们最关心的命令是Get命令也就是0x00。主机发送0x00 FF之后Bootloader如果支持该命令会回复ACK然后返回两个字节的Bootloader版本号比如0x31 0xCE这类组合其中0x31是版本号0xCE是补码0xCE等于0x31的按位取反。除此之外还会返回一个支持命令列表最后再回复一个ACK。用Python写一个最简单的读取脚本代码量可以很少import serial ser serial.Serial( portCOM5, baudrate115200, bytesizeserial.EIGHTBITS, parityserial.PARITY_EVEN, stopbitsserial.STOPBITS_ONE, timeout0.5 ) # 发送 Get 命令 0x00后接补码 0xFF cmd bytes([0x00, 0xFF]) ser.write(cmd) resp ser.read(16) print(resp.hex())如果一切正常你会看到类似79 31 CE 00 FF 00 FF 00 FF 00 FF 00 FF 00 FF 79这样的返回。第一个79是ACK31是Bootloader版本号CE是31的补码后面的字节串是命令掩码列表最后79表示回包结束。3.3 实际交互Log与分析我自己实际跑过一段这样的日志完整过程是这样的主机发送00 FF从机返回79 31 CE 00 FF 02 FD 11 EE 21 DE 31 CE 44 BB 63 9C 79拆开看的话0x79ACK。0x31Bootloader版本号解读为十六进制0x31对应版本V3.1。0xCE0x31的取反用于校验这个版本号没传错。0x00、0x02、0x11、0x21、0x31、0x44、0x63这些是Bootloader支持的命令列表比如0x00是Get、0x02是GetID、0x11是Read Memory、0x21是Write Memory、0x31是Erase、0x44是Extended Erase。对这个回包不熟悉的读者可能会被里面一堆重复的FF搞晕其实它就是补码填充不用过于纠结。关键是抓住版本号、支持命令掩码两个信息就够用了。注意这个过程中PC端串口工具要关闭“DTR/RTS自动复位”功能否则串口模块在发送数据时可能自动把芯片复位导致Bootloader还没维持住就跳到别的模式。使用Python脚本时也要确保串口的DTR和RTS没有被默认打开。4. 实操中容易踩的坑前面讲的都是顺利情况实际做项目时我几乎每次都会在不同的地方栽跟头。这里挑几个最常见的问题分享出来帮大家少走弯路。4.1 RDP Level 1导致SWD连不上典型现象是调试器能识别到芯片ID但读取Flash时报错或者每次点下载都提示“Cannot access target memory”。这类问题十有八九是芯片处于RDP Level 1读保护状态。用STM32CubeProgrammer连接时虽然能读ID但读Flash和下载操作会被禁止。解决方法是切到Option Bytes选项卡把Read Protection从Level 1改回Level 0点击Apply后软件会执行一次全片擦除然后解除保护。这里要特别提醒解除Level 1保护时芯片Flash会被擦除程序数据会全部丢失。所以如果是返修板优先尝试用串口Bootloader先备份Flash内容实在备份不了再考虑解除保护。有些工程师一看到连不上就急着解锁结果把可以恢复的固件给抹掉了这种损失本来是可以避免的。Level 2的情况就更极端一旦设置为Level 2调试接口被永久锁死想通过RDP改回Level 0是不可能的芯片基本就变成了一次性烧录器。对于F0这种低成本MCU这种情况通常只能报废芯片因此量产时千万不要随便把RDP设成Level 2。4.2 UART Bootloader不响应用USB转串口连接系统Bootloader时经常遇到发命令过去没有回复的现象。排查思路可以按下面顺序来检查BOOT0引脚是否稳定在高电平。BOOT0不是靠软件控制的而是硬件引脚电平某些开发板上BOOT0有跳线帽或拨码开关一定要确认复位后仍然是高电平。检查接线是否交叉。USB转串口的TX要接STM32的RXPA10RX要接STM32的TXPA9。接反是所有串口调试里出现频率最高的错误。检查串口参数。AN3155要求偶校验8位1停止位如果设置成NONE校验Bootloader可能直接不回包。检查是否有其他软件占用串口或者串口模块的TXD/RXD电平是否匹配。现在是TTL电平模块直接接没问题但如果中间加了RS232转接板就需要12V左右电平别一概而论。别忘了共地。USB转串口和目标板之间必须连GND否则串口数据会因为参考地不同而乱码。如果这些都检查完还是没反应可以试一下用示波器看PA10或者串口模块TX引脚有没有数据波形没有波形就先查PC端发送有波形但芯片不回再查芯片供电和BOOT0。4.3 UID读出来全0xFF或全0x00正常F030C8T6读取UID时三个32位地址都会有其特定的非零值至少晶圆坐标和批次号不会是全零全FF。如果你读出来全0xFF多半是芯片供电不稳或者SWD连接线接触不良数据线上拉到无效电平。全0x00则要警惕买到克隆芯片有些低成本的兼容芯片并没有厂家的唯一ID功能读出来的值要么是空的要么固定不变。识别克隆芯片在量产采购中非常重要。我遇到过一批号称全新原装的F030C8T6DEV_ID倒是能读到0x440但REV_ID和UID的组合看起来很不正常而且工作温度范围内稳定性差很多。这种芯片后期返修率不低所以做批量选型时一定不要只测功能顺手把UID和REV_ID读一遍存档后面真出问题也好溯源。4.4 常见问题速查表现象可能原因排查方向SWD连接后Flash读取失败RDP Level 1或Level 2读选项字节RDP值需要时解除保护SWD完全无法连接接线错误、供电异常、芯片进入休眠检查SWDIO/SWCLK/VDD/GND检查复位UART Bootloader不回包BOOT0没拉高、串口参数错误确认硬件电平改用8E1参数Bootloader Version读不出来协议命令发错、串口干扰确认0x00后跟补码0xFF启动自动波特率UID全0xFFSWD连接不稳定、芯片未上电检查供电和线序DEV_ID读不到0x440芯片型号不对板、Clone芯片对比参考手册核对丝印解锁RDP后程序丢失Level 1解锁会触发全片擦除解锁前先串口备份或确认不影响5. 这些参数在量产、返修和OTA中的实际用法如果只是调试时看一眼这些参数那其实很简单。但真正让这些参数值钱的地方是在量产、返修和OTA升级这些实战场景里怎么用起来。5.1 用UID做设备绑定和防克隆很多做IOT设备的工程师喜欢把授权信息和设备UID绑定防止用户把固件复制到另一台设备上。比如在出厂测试时先把当前设备的96位UID读出来再结合一些密钥算法生成激活码写入Flash。设备运行时固件每次启动都读一遍UID和存储的激活码做校验如果对不上就拒绝运行。这是我能想到的最简单、成本最低的防克隆做法。STM32F030C8T6的UID地址是公开的攻击者也能读但它毕竟让做盗版的成本高了不少一般小批量山寨项目不会有人去专门绕过这套机制。关键是UID一定要在量产测试时多读几遍防止接触不良导致读到错误值。5.2 RDP等级的设置与升级策略产品开发阶段RDP保持Level 0最方便因为调试器随便连。但产品正式生产后建议至少设置成Level 1这样别人拿到量产板用调试器没法直接读取Flash保护固件不被轻易拷贝。这里有个平衡问题RDP Level 1虽然防了别人也防了自己。后续返修时如果想通过SWD直接升级固件就会遇到Flash读不了的问题。所以很多量产项目会采用“串口Bootloader RDP Level 1”的组合把升级入口留在自研的Bootloader里用户程序通过串口协议更新应用程序同时RDP保护Level 1防止调试器直接读Flash。这样既保护固件又保留了升级通道。至于Level 2除非你是想彻底封死所有调试和升级通道否则不要在产品代码里加这个设置。一旦设置Level 2任何调试器、任何Bootloader都拿它没办法连你自己都救不回来。量产时如果误操作烧了Level 2只能报废芯片。5.3 Bootloader版本和OTA升级的兼容性做OTA升级之前一定要先确认出厂Bootloader版本。不同版本的Bootloader可能影响命令集合和Flash擦写方式。比如老版本只支持页擦除而大容量Flash需要扩展擦除命令再比如新版Bootloader可能固定了某些串口参数导致你按旧的协议去连接时无法同步。我实际见过的问题是这样的某批次F030C8T6的Bootloader版本是V3.1升级程序时使用扩展擦除命令一切正常。但换了另一个批次芯片版本变成V3.0结果扩展擦除命令一直返回NACK升级卡死在擦除阶段。后来查了AN3155才发现V3.0对某些命令掩码的支持和V3.1并不完全一致需要根据版本号走不同的命令分支。所以自研升级软件里最好加一个“版本识别”步骤上电后先发Get命令解析Bootloader版本如果版本太低就提示“该芯片不支持当前升级协议”避免用户在设备上无脑点升级结果卡一半把设备变砖。这不是危言耸听在产线和售后现场这种问题会非常头疼。5.4 自动化产测的小思路如果你在做批量生产可以用这些参数搭一套纯串口的自动化产测流程。比如电脑通过串口连接芯片的BOOT0引脚切换逻辑自动进入系统Bootloader然后发送Get命令读取Bootloader版本、GetID命令读取芯片ID、Read Memory命令读取UID全部记录下来后再让芯片跳转到用户程序跑自检。整个过程不需要SWD调试器一套USB转串口就能完成成本很低。这个方案的好处是产测数据和芯片一一对应每一台设备都能追溯到用的什么芯片、刷的什么固件、Bootloader版本是多少。后续如果某个批次的设备出问题直接看产测记录就能缩小范围。我在产测中实际跑过几千片子用Python脚本批量发命令一台设备大概两三秒就能完成信息读取和校验。相比每台都接ST-Link效率提升非常明显。6. 一点个人经验分享做嵌入式这些年我越来越体会到芯片厂给的这些隐藏参数不是摆设而是关键时刻救命的工具。RDP保护、CPUID、Bootloader版本平时写功能代码时可能三个月都用不上一次可一旦产品进入量产或者遇到返修这三个参数能帮你省下大量排查时间。我自己踩过最大的坑就是RDP Level 1。当时有一批设备做了加密保护把RDP设到了Level 1后来用户反馈设备无法开机我想通过SWD看现场日志结果Flash读不了又不敢直接解锁怕丢日志最后只能靠串口Bootloader一点一点往外拉数据费了很大劲。从那以后我所有的量产项目都会在出厂前打印并保存一份RDP、UID、Bootloader版本记录后期排查问题时直接对着记录看心里有底。如果你刚接触STM32F030C8T6建议你也养成这个习惯拿到芯片先写个工具把DEV_ID、REV_ID、UID、RDP值、Bootloader版本扫一遍保存好存档。这个习惯不复杂但真到关键时刻你会发现它比任何调试技巧都管用。
返回列表