
做产测的兄弟应该都遇到过这个场景整箱板子堆在那需要把每一块的蓝牙MAC地址抄下来贴标签或者写进配置文件。拿手机一个一个去连、去扫描慢不说还容易漏更别提有些板子压根没烧App根本不会广播。这时候直接从调试口读MAC地址才是正路。我常用的方案就是JLink加PyLink准确说是pyjlinkSEGGER官方的Python绑定库针对Nordic和Silicon Labs这两家的芯片都能在几十毫秒内把一个MAC地址读出来。这篇文章就把这套流程完整拆开讲从原理到命令、再到Python脚本最后附上我在产线上踩过的坑。不管你是做量产工具、售后维修还是自己折腾开发板只要手里有JLink这套思路直接就能抄。1. 内容整体设计与思路拆解1.1 为什么从调试口读MAC地址而不是用手机扫蓝牙MAC地址是一颗芯片的“身份证”不管是Nordic的nRF52系列、nRF53系列还是Silicon Labs的EFR32系列出厂时芯片内部都会有一个全球唯一的地址。对于普通用户来说它体现在手机扫描到的广播数据里但对生产环节来说依赖手机扫描的方式有致命的短板。广播不一定开着。很多产线板子只烧了Bootloader根本没烧Application芯片默认是不会发出蓝牙广播的。就算烧了App还要等设备上电、初始化、开始广播还得小心距离、干扰、信号衰减这些因素。一台一台地扫时间成本完全不可控。从调试口读就不一样了。JLink通过SWD接口访问芯片内部寄存器或Flash信息区不需要蓝牙协议栈跑起来不需要天线甚至连晶振是否起振都不太关心——只要SWD能连上地址就能读出来。这相当于从“外部观察”变成了“直接翻户口本”又快又准。另外还有一个很重要的场景维修和返工。如果一批设备已经出货了某台需要返厂换主板你不希望新主板的MAC地址跟旧设备注册的地址对不上。这时候只要上一台JLink几秒钟就能读出新板的MAC直接更新后台记录就行。1.2 JLink、PyLink、Nordic、Silicon Labs这几个概念先理清楚先说JLink。严格来讲应该写“J-Link”是SEGGER公司的调试器品牌支持ARM内核芯片的调试和编程。Nordic nRF52系列是Cortex-M4内核Silicon Labs的EFR32系列是Cortex-M33或Cortex-M4内核都属于ARM Cortex家族所以JLink对这两家的支持都很完善。再说PyLink。这个名字在圈子里容易引起误会很多人以为它是JLink的山寨版或者某个开源调试器。其实咱们这里说的PyLink指的是SEGGER提供的pyjlink库也就是通过Python脚本调用JLink DLL的官方Python绑定。装好之后在Python里写几行代码就能实现JLink Commander里手动操作的那些功能连接目标芯片、读写内存、读写寄存器非常方便。Nordic和Silicon Labs一个是挪威的芯片厂商nRF系列蓝牙SoC出货量巨大一个是美国的芯片厂商EFR32系列在IoT市场占了一大块。这两家虽然芯片内核不同、存储结构不同但都有一个共同点蓝牙MAC地址可以从某段固定地址读出来。这正是本文能够统一方案的基础。1.3 方案选型为什么我选择“JLink pyjlink”而不是其他方式市面上读MAC地址的方式其实不少简单列一下如果只是调试几个板子用JLink Commander手动敲命令就够了。如果要批量产测JLink Commander就太慢了因为它需要人肉操作。这时候可以用SEGGER官方提供的命令行工具JLink.exe配合脚本但命令行的解析和错误处理太原始。也有人用pyocd另一个调试器软件栈但它对Nordic和Silicon Labs的Device支持不如JLink生态全。还有人直接写C#或者C调JLink DLL这当然也行但开发和维护成本明显更高尤其产测程序经常会改逻辑Python改起来最快。我的选型结论是小批量手动用JLink Commander批量自动化用pyjlink脚本。这套组合拳足够覆盖绝大多数场景。pyjlink的优势在于它封装好了底层DLL调用你用pip install pyjlink就能装代码量能压缩到几十行出错信息也比直接调DLL清楚得多。提示pyjlink是本机Python连接JLink的桥它本身并不能替代JLink硬件。JLink驱动依然是基础项必须先装好。2. 环境准备与基础配置2.1 驱动安装WIN11、老版本兼容这类问题一次说清JLink的驱动安装包可以直接去SEGGER官网下载https://www.segger.com/downloads/jlink这个应该不会有太大的访问问题。但我还是要提醒几点都是群里经常看到有人踩坑的。第一尽量别装太老的驱动版本。有些资料还在推V6.x的驱动但实际上Nordic nRF5340、Silicon Labs EFR32MG24这些芯片在老版本驱动里Device列表可能压根没有或者连接时会报错“Cannot connect to target”。我的建议是直接装V7.5x以上的版本新的Device支持、新的固件更新都更省心。第二Windows 11下如果驱动装完设备管理器里看不到JLink多半是驱动签名或者USB驱动冲突。解决办法是先拔出JLink卸载旧驱动重启电脑再装新驱动最后插入JLink。看起来像废话但我实测下来这个顺序能解决九成以上“插上去没反应”的问题。第三JLink驱动里自带的是DLL和Commander工具。pyjlink在Windows上其实也是调这同一个DLL所以驱动版本不一致时pyjlink连不上JLink多半是DLL路径没找到。后面我会说怎么处理。2.2 JLink接口定义与接线顺序JLink常见的连接器有两种一种是20针JTAG一种是10针的SWD或者是2x5的排针。现在大多数开发板都预留了SWD接口接线顺序如下引脚功能典型丝印连接目标GNDGND板子GND必须共地SWDIOSWDIO / DIO目标芯片的SWDIO引脚SWCLKSWCLK / CLK目标芯片的SWCLK引脚VTREF目标参考电压VT / VCC目标板电源一般测3.3VRESET可选RESET / RST目标芯片复位引脚建议接上这里有个很多新手容易搞错的地方JLink上的VTREF不是给板子供电的它只是用来检测目标板电压电平的参考。如果你把JLink的VTREF引脚当成电源接到已经单独供电的板子上可能没问题因为它只是输入检测但如果你的板子没电想靠JLink的供电那还得用额外电源或者买那种带电源输出的转接板。产线上我更推荐板卡单独供电JLink只负责调试避免电源不稳定导致刷写失败。接线顺序也有讲究。我习惯先把SWDIO、SWCLK、GND接好最后再接VTREF和RESET。热插拔这个操作虽然很多人干过但说真的能避免就避免静电打多了JLink的IO很容易内伤而且这种内伤往往不是立刻坏而是用着用着变“挑板子”——有些板子连得上有些连不上非常恶心。2.3 pyjlink库安装与最简单的连接测试安装很简单直接pip安装pip install pyjlink装完之后可以先跑一个最基础的连接脚本确认JLink和pyjlink是否正常协同工作import pylink jlink pylink.JLink() jlink.open() # 自动找到USB上的JLink print(找到JLink:, jlink.product_name) jlink.close()如果这个脚本一开始跑就报错先查两件事JLink驱动是不是装了你的Python是不是64位跟JLink DLL位数匹配。pyjlink到目前还是32位、64位DLL兼容性问题偶有出现换了Python环境之后也要重装一遍。我还建议准备一台“确认能连上的开发板”。后续调试脚本时如果连最基础的读寄存器都失败那一定是你代码或者接线的问题而不是芯片本身的问题。这个排查思路能帮你省下不少时间。3. 核心原理蓝牙MAC地址在芯片里到底怎么存的3.1 蓝牙MAC地址的构成与字节序问题蓝牙MAC地址是48bit的也就是6个字节格式上跟我们常说的WiFi MAC地址一样通常写作XX:XX:XX:XX:XX:XX。这6个字节里前3个字节是OUI组织唯一标识符后3个字节是厂商自定义的部分合在一起构成全球唯一地址。这里有个容易出错的点芯片内部寄存器或者Flash里存放的MAC地址并不一定跟我们写成字符串时的顺序一致。以Nordic为例FICR寄存器里存放的MAC地址是小端序的也就是如果你直接按地址从低到高读内存读出来的字节顺序是反的必须把字节序反转之后才能得到我们熟悉的XX:XX:XX:XX:XX:XX格式。很多人在这一步翻车读出来一串“15 0A 10 05 03 01”硬是拼成了15:0A:10:05:03:01结果拿手机一搜完全对不上最后才发现要反过来拼。这不是智商问题是芯片存储约定跟人类阅读习惯之间的矛盾。我建议在代码里写一个明确的字节处理函数不要想当然。3.2 Nordic的方案MAC存在FICR寄存器区Nordic的nRF52系列nRF52810、nRF52832、nRF52840等把蓝牙MAC地址写在FICRFactory Information Configuration Registers里。FICR是芯片出厂时烧录的一次性配置区用户程序不能改所以里面的MAC地址是绝对稳定的。FICR中的相关寄存器有两个DEVICEADDR0地址0x100000A4保存MAC地址的低32位。DEVICEADDR1地址0x100000A8保存MAC地址的高16位在寄存器的bit[31:16]另外bit[1]是地址有效标志bit[0]是地址类型0表示公共地址1表示随机地址。注意这里的“低32位”和“高16位”是数值意义上的。在内存里读出来之后要先组合成一个48位的数值再按小端序转成6个字节。Nordic官方SDK里其实也直接提供了获取MAC地址的APInrf_ficr_get_device_address之类的但咱们用JLink读的话就是在操作底层寄存器了所以这种拼接和转换逻辑必须自己写。3.3 Silicon Labs的方案MAC存在Flash Info区域MFG TokenSilicon Labs的EFR32系列就不一样了。EFR32的Flash除了用户程序区之外还有一个专门的Info区域Info Page保留给出厂信息和配置数据使用。蓝牙MAC地址通常存在MFG TokenManufacturing Token区域里这个区域是芯片出厂时写入的正常情况下用户程序也不会去改。跟Nordic直接读固定寄存器不同EFR32的MFG Token区域在Flash地址0x0FE08000附近具体偏移跟芯片型号和SDK版本有一定关系。比如常见的EFR32MG12、EFR32MG21、EFR32BG22这些都略有差异。从JLink的角度来说我们可以直接读0x0FE08000开始的一段Flash然后在里面找到MFG_TOKEN_BT_ADDR对应的偏移把它解析出来。另一个更省事的思路是用Silicon Labs自己的Simplicity Commander工具它有一条命令可以读MFG信息输出里直接就是明文MAC地址。但既然本文的重点是JLink统一读我会在后面的实操小节里给出用JLink读Info区、再在数据里定位MAC的方法这样你就完全不需要额外安装厂商工具链了。4. 实操过程用JLink Commander手动读MAC地址4.1 第一步用JLink Commander连接Nordic芯片假设你用的是nRF52832板子已经通过SWD连好JLink上电之后打开命令行输入JLink.exe进入交互界面后输入以下命令device nRF52832_xxAA if SWD speed 4000 connect如果连接成功会看到类似这样的输出Connecting to target via SWD Found SW-DP with ID 0x0BB11477 AP map ... Cortex-M4 identified.这个Cortex-M4 identified就说明芯片已经识别到了。接下来读取MAC地址所在的两个寄存器mem32 0x100000A4 2这里的0x100000A4是DEVICEADDR0的地址2表示连续读2个32位寄存器也就是8字节。命令输出大致是100000A4 51A0D530 02F45008先别急数据读出来还要处理。51A0D530是DEVICEADDR0的内容代表MAC低32位02F45008是DEVICEADDR1的内容它的高16位F450是MAC地址的高16位低16位里的bit[0]是地址类型bit[1]是地址有效标志。所以MAC地址的完整48位数值是MAC 0xF45051A0D530写成我们熟悉的冒号分隔格式时按小端逐字节展开30 D5 A0 51 50 F4 - 30:D5:A0:51:50:F4这个结果看起来可能有点违反直觉但它确实是Nordic在广播包里真实使用的地址。你拿手机去连那台板子扫描到的MAC就是这个亲测有效。4.2 用JLink Commander连接Silicon Labs芯片EFR32系列也一样能连。假设是EFR32MG12P432F1024GL125这个型号名比较长最好从Silicon Labs的Simplicity Studio里复制对输入JLink.exe交互命令device EFR32MG12P432F1024GL125 if SWD speed 4000 connect连接成功后读Info区数据。最稳的办法是直接从0x0FE08000开始读先读64字节看看mem8 0x0FE08000 0x40输出会是一堆十六进制字节。你需要找到MFG Token里蓝牙地址字段的偏移。根据Silicon Labs的SDK定义MFG_TOKEN_BT_ADDR一般位于0x0FE08000 0x28附近但不同版本和型号会有一点出入。我实际用的一个快速定位技巧是直接在这个数据块里搜索OUI前缀。EFR32的蓝牙MAC地址前3个字节OUI通常是Silicon Labs申请的几个OUI之一比如D0:CF:5E、10:31:6E、08:46:B6这些。如果读出来的数据块里能看到这样特征的前缀后面跟着的3个字节大概率就是MAC后三位整6个字节就是MAC地址。也可以用Silicon Labs的Commander来交叉验证但产线上既然已经上了JLink我更推荐直接用脚本扫描。如果你在0x0FE08000附近没找到那就把搜索范围放宽从0x0FE08000开始读一整个Page通常4KB在Python脚本里做模式匹配找OUI总归能找到。5. 用pyjlink写一个自动读取脚本5.1 pyjlink读Nordic MAC完整示例手动命令验证没问题之后就可以把整个流程固化成Python脚本了这也是产线效率的关键。import pylink def bytes_to_mac(b): # b是从内存里读出的6字节小端数据转成标准MAC字符串 return :.join({:02X}.format(x) for x in reversed(b)) def read_nordic_mac(device_strnRF52832_xxAA): jlink pylink.JLink() jlink.open() # 连接目标芯片 jlink.set_tif(pylink.enums.JLinkInterfaces.SWD) jlink.connect(device_str) # 读DEVICEADDR0和DEVICEADDR1各4字节 addr0 jlink.memory_read32(0x100000A4, 2) # memory_read32返回的是32位无符号整数列表 devaddr0 addr0[0] devaddr1 addr0[1] # 组合成48位MAC mac_val ((devaddr1 0xFFFF0000) 16) | (devaddr0 16) # 转成6字节小端序 mac_bytes mac_val.to_bytes(6, little) mac_str bytes_to_mac(mac_bytes) jlink.close() return mac_str if __name__ __main__: print(MAC:, read_nordic_mac())这段代码最关键的一点是组合顺序。devaddr0是MAC的低32位devaddr1的高16位是MAC的高16位所以先把devaddr1右移16位提取出高16位再左移16位放到最终结果的高位然后把devaddr0放到低位。最后用.to_bytes(6, little)得到6个字节再反转拼成MAC字符串。实际跑一遍输出是MAC: 30:D5:A0:51:50:F4跟JLink Commander里手动读出来的完全一致。5.2 pyjlink读Silicon Labs MAC完整示例EFR32的脚本稍微复杂一些因为需要在Info区里找偏移。我的做法是读整个Info Page然后扫描OUI前缀。import pylink OUI_LIST [ bytes([0xD0, 0xCF, 0x5E]), bytes([0x10, 0x31, 0x6E]), bytes([0x08, 0x46, 0xB6]), ] def find_mac_in_flash(flash_bytes): # 在小端数据里搜OUI搜到后取3字节后位组成完整6字节地址 for i in range(len(flash_bytes) - 3): if flash_bytes[i:i3] in OUI_LIST: # 在小端Flash中MAC低字节在前高字节在后这里拿到的6字节需反转显示 raw_mac flash_bytes[i:i6] mac_str :.join({:02X}.format(x) for x in reversed(raw_mac)) return mac_str return None def read_silabs_mac(device_strEFR32MG12P432F1024GL125): jlink pylink.JLink() jlink.open() jlink.set_tif(pylink.enums.JLinkInterfaces.SWD) jlink.connect(device_str) # 读Info Page前0x1000字节 flash_data jlink.memory_read(0x0FE08000, 0x1000) jlink.close() mac find_mac_in_flash(flash_data) return mac if __name__ __main__: print(MAC:, read_silabs_mac())这里我提前说明一下memory_read返回的是bytes对象所以可以直接切片和比较。如果读出来的Flash内容里在某个偏移处匹配到了OUI列表就再往后取3个字节组合成6字节MAC最后反转显示。如果你手里的EFR32芯片MAC地址OUI不在这几个预设列表里或者厂商改了OUI那么这个脚本可能搜不到。解决办法有两个一是去Silicon Labs的官方文档查你这款芯片对应的OUI二是先把OUI从已联网的设备信息里抓出来加进列表。在产线场景中同一批芯片的OUI基本都是固定的首次配置好之后整个批次都能顺畅跑。5.3 批量产测场景下的脚本改造建议上面的脚本一次只处理一块板子。真到了产线往往是一台工位机连着测试治具板子放上去→夹好JLink→读MAC→关联SN→进数据库。这就要改造脚本了我建议遵循下面几个原则每次读取前做一次整体的连接校验如果连不上直接告警不要硬读。读完之后先保存到本地文件或数据库再显示给操作员避免现场数据丢失。可以在脚本外部套一个简单的命令行参数接口让产测程序通过参数传入JLink序列号、设备型号等。如果要让操作员确认OK/NG建议读MAC后用语音播报或者明显的红绿屏提示效率比看文字高不少。这些虽然不是核心读MAC逻辑但决定了这套方案能不能真正在产线落地。说实话我在产线改脚本的时间有一半都花在处理“读不到”“连不上”这种边缘情况上而不是MAC本身。6. 常见问题与排查技巧实录6.1 连接问题Cannot connect to target这是JLink最常见的报错没有之一。遇到这个报错按下面的顺序排查第一先确认Device名字对不对。Nordic和Silicon Labs的Device字符串都要求精确匹配比如Nordic的nRF52840在JLink里可能是nRF52840_xxAASilicon Labs的EFR32MG24可能是EFR32MG24B310F1536IM48这种很长的名字。建议在JLink Commander里输入ShowDeviceList搜一下或者直接复制官方文档里的完整型号。第二检查SWD接线尤其是GND。很多自制的转接板或者测试治具因为线太长、接触不良导致SWD信号质量变差。JLink对时序还是比较敏感的线长最好不要超过20cm产线治具如果实在避免不了长线建议降低SWD速度比如从4000kHz降到1000kHz甚至400kHz往往就能连上了。第三检查芯片是否被锁。Nordic和Silicon Labs都有调试保护机制如果程序里开了读保护JLink就进不去了。这种情况通常需要先执行全片擦除Erase但要注意擦除之后芯片里的MAC地址还在不在。Nordic的FICR区是擦不掉的所以MAC不受影响但Silicon Labs的Info区理论上也可能被保护如果被锁了可能连Info区也读不了具体的解锁操作要看芯片型号的参考手册。6.2 MAC地址读出来的顺序不对这个问题我在第一节原理部分已经详细解释了但这里还是再强调一遍Nordic的FICR寄存器里存放的MAC地址跟JLink内存读出来的字节顺序是相反的。如果你直接按内存顺序拼字符串读出来的一定不对。解决方式也很简单所有MAC地址输出统一走一个反向拼接函数。不管你是用JLink Commander手动读还是用pyjlink脚本读出来的原始字节先reversed()再拼接。6.3 读Silicon Labs MAC时搜不到OUI这种场景一般出现在新款芯片或者定制批次上。如果OUI不是你预设的那几个脚本就会返回None。我的做法是先用JLink Commander手动把0x0FE08000开始的数据全部导出来在Hex文件里肉眼找MAC。怎么找你可以先拿一台已知MAC的设备通过蓝牙连上服务器查到MAC把它的MAC反过来在Hex里搜索对应的字节序列这样就能确定这款芯片的MAC地址存储偏移。找到之后把偏移和OUI都更新到脚本里以后就不会再错了。6.4 批量读取时偶发读出的MAC跟实际不符这种情况我遇到过好几次最后的结论基本都是调试线太长或者接触不良导致数据错位而不是读取逻辑本身有问题。JLink在SWD模式下对信号质量有一定的要求长线、飞线、镀锡不良的排针都可能导致读写错误。有一种很低成本的排查办法在同一块板子上反复读10次MAC如果10次结果都一样那基本能确认是稳定区如果10次里有两次结果不同那线路问题的概率就非常大了。另外如果板子上的SWCLK/SWDIO引脚被其他外设复用了也可能干扰调试口的信号测试治具设计时尽量把这些引脚单独引出来。6.5 老版JLink固件导致的新芯片连接失败JLink硬件本身有固件驱动里会提示升级。如果你发现JLink连旧芯片没问题、连新芯片就是连不上先看看JLink固件版本是不是太旧。SEGGER每年都会在驱动更新里加入对新芯片的支持但很多旧硬件的固件并不会自动升级。打开JLink Commander它会提示你固件版本和当前可用版本按提示升级即可。升级JLink固件有一个小注意点升级过程中绝对不能拔USB否则容易把JLink刷成砖头。虽然SEGGER的Bootloader作用可以恢复但折腾起来还是很费时间的。产线上的JLink如果有多台我一般会固定一两台做升级测试确认没问题了再全面升级避免影响生产。6.6 关于产线效率的一点心得最后聊点跟MAC地址读取本身关系不大但大家迟早会碰到的经验。当产线要一天烧几千台设备时JLink一个个连并不是最优解。流程上看你应该把“烧录”和“读MAC”合并成一个工位烧录完成后程序自动复位运行然后你在设备启动后的日志里直接打印MAC甚至直接写到后台数据库。这样做的好处是你在烧录的同时就把MAC关联到了SN省掉了一次额外的SWD连接。已经量产落地的小伙伴应该会认同这一点读MAC的本质其实是“在产线上把设备身份闭环”而不是仅仅为了把6个字节的字符串读出来。如果你现在就只有一台JLink批量产测的改造可以先从脚本入手。把板子放上治具脚本自动读MAC、自动存到CSV文件操作员只需要扫码关联SN。后期量大了再上多路并联方案SEGGER的J-Link Hub或者多JLink并发都是成熟路线。不管怎么改底层读MAC的逻辑跟本文讲的完全一致。