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

资讯详情

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

ESP32与STM32芯片唯一标识符(UID)与MAC地址获取全解析

ESP32与STM32芯片唯一标识符(UID)与MAC地址获取全解析 1. 项目概述为什么我们需要芯片的唯一“身份证”在嵌入式开发中尤其是在物联网IoT设备、智能硬件或者需要设备身份认证的场景里我们经常会遇到一个看似简单却至关重要的需求如何让设备证明“我就是我”比如一个基于ESP32的智能插座需要向云端服务器注册服务器必须能唯一地识别它防止被其他设备冒用又或者一个基于STM32的工业控制器其固件授权需要绑定到具体的硬件上确保软件不会被随意复制到其他设备上运行。这时候芯片内置的唯一标识符Unique ID, UID和媒体访问控制地址MAC Address就成了设备的“身份证”和“网络身份证”。这个项目标题“ESP32 | STM32获取芯片MCU唯一标识符、MAC”直指的就是这个核心需求。它不是一个复杂的算法实现而是一项基础但极其重要的底层操作技能。对于ESP32这类高度集成的Wi-Fi/蓝牙SoC以及STM32这类通用的微控制器获取它们的唯一ID和MAC地址的方法、原理和注意事项各有不同。掌握这些方法意味着你为设备赋予了独一无二的身份这是实现设备管理、安全启动、固件加密、动态绑定等高级功能的第一步。很多新手开发者可能会在需要时才临时搜索代码片段但往往忽略了背后的细节比如ID的可靠性、读取方式对功耗的影响、在不同系列芯片上的兼容性等导致后期出现难以排查的“灵异”问题。今天我们就来彻底拆解这个主题不仅告诉你“怎么拿”更要说清楚“为什么这么拿”以及“拿了之后要注意什么”。2. 核心概念解析UID与MAC到底是什么在深入代码之前我们必须先厘清几个核心概念。很多开发者对“唯一标识符”和“MAC地址”的理解是模糊的甚至混为一谈这为后续开发埋下了隐患。2.1 芯片唯一标识符 (Chip Unique ID / UID)芯片唯一标识符通常是一串由芯片制造商在生产过程中一次性写入熔断或固化到芯片硅片上的只读数据。这个ID对于每一颗芯片都是全球唯一的理论上就像人的指纹。它的核心特性包括唯一性这是其最重要的价值。制造商通过复杂的工艺确保在可预见的出货量内两个芯片拥有相同ID的概率极低。只读性通常无法通过软件修改存储在芯片的特定只读存储区域如Flash的特定扇区、OTP区域等。非标准性其长度、格式十六进制、二进制、存储位置和读取方式完全由芯片厂商定义没有统一标准。ESP32的UID和STM32的UID就截然不同。用途主要用于硬件绑定、安全加密作为加密算法的输入因子、设备身份认证、生产追溯等。注意虽然称为“唯一”但在极端罕见的情况下理论上存在碰撞重复的可能。对于超高安全等级的应用有时需要结合其他信息如板载序列号来构成更可靠的唯一身份。2.2 媒体访问控制地址 (MAC Address)MAC地址是一个用于在物理网络段中识别网络设备的地址。对于集成网络功能的芯片如ESP32的Wi-Fi/蓝牙MAC地址是其网络接口的“身份证”。它的特点是标准性遵循IEEE 802标准通常为48位6字节或64位8字节如EU1-64格式。我们常见的是48位格式如24:0A:C4:00:01:02。可配置性虽然芯片出厂时会烧录一个默认的MAC地址通常基于厂商的组织唯一标识符OUI但在许多情况下软件可以对其进行重写或覆盖尤其是在初始化网络协议栈时。ESP32就允许开发者基于基础MAC地址进行自定义。分层性一个多网络接口的芯片如ESP32同时有Wi-Fi Station, Wi-Fi SoftAP, Bluetooth可能会有多个相关的MAC地址它们通常由一个基础MAC地址衍生而来。用途用于局域网LAN内的设备寻址是TCP/IP协议栈底层数据链路层通信的基础。关键区别UID是芯片的身份证与网络功能无关哪怕芯片不联网UID也存在。MAC地址是网络接口的身份证只有具备网络功能的芯片或外设才有。ESP32两者皆有而一个没有网络外设的STM32F103芯片只有UID没有MAC地址除非你外接了以太网或Wi-Fi模块那MAC地址属于那个模块。3. ESP32篇获取UID与MAC的实战与内幕ESP32作为乐鑫推出的明星级物联网芯片其系统高度集成乐鑫的ESP-IDF框架为我们提供了非常便捷的API来获取这些信息。但便捷的背后也有很多值得深究的细节。3.1 获取芯片唯一标识符 (ESP32 UID)ESP32的UID通常指的是其MAC地址本身对于芯片身份而言但更精确的“唯一”标识可以通过读取eFuse中的特定字段获得。eFuse是一次性可编程存储器芯片出厂后某些位被熔断以存储信息。方法一获取基础MAC地址作为芯片ID这是最常用、最直接的方式。ESP32的48位基础MAC地址在出厂时被烧录到eFuse的BLK0EFUSE_BLK0中对于大多数应用这个地址足以作为芯片的唯一标识。#include “esp_system.h” #include “esp_mac.h” void print_chip_id_via_mac() { uint8_t base_mac[6]; // 存储6字节MAC地址 esp_err_t ret esp_efuse_mac_get_default(base_mac); if (ret ESP_OK) { printf(“芯片基础MAC地址 (可作为UID): “); for (int i 0; i 6; i) { printf(“%02x”, base_mac[i]); if (i 5) printf(“:”); } printf(“\n”); } else { printf(“获取MAC地址失败: %s\n”, esp_err_to_name(ret)); } }为什么是esp_efuse_mac_get_default这个API直接读取eFuse中存储的原始出厂MAC。它比esp_read_mac更底层esp_read_mac函数会考虑软件是否通过esp_base_mac_addr_set()设置过自定义MAC返回的是最终使用的MAC。对于需要硬件唯一性的场景直接读eFuse更可靠。方法二读取eFuse中的其他唯一信息对于安全性要求更高的场景可以组合多个eFuse值来生成一个更长的唯一ID。例如读取EFUSE_BLK1如果可用或结合MAC与ESP32_CHIP_ID芯片版本信息。#include “esp_chip_info.h” void print_chip_info() { esp_chip_info_t chip_info; esp_chip_info(chip_info); printf(“芯片型号: ESP32-%s, 核心数: %d, 版本: %d\n”, (chip_info.model CHIP_ESP32) ? “D0WD” : “Unknown”, // 常见型号 chip_info.cores, chip_info.revision); // 芯片版本revision也可以作为唯一性的一部分参考 }实操心得eFuse读取的功耗与速度频繁读取eFuse并不是零成本操作。它比读普通内存慢且会消耗少量能量。虽然对于单次初始化可以忽略不计但切忌在循环中频繁调用。MAC地址的格式获取到的MAC是6字节的二进制数组。如果你需要将其转化为字符串用于HTTP请求头、文件名或数据库存储建议格式化为十六进制字符串且去除冒号例如240ac4000102。这样更紧凑且避免了冒号在某些场景下如URL、文件名可能带来的转义问题。双核情况获取UID/MAC的操作建议放在app_main函数初始化阶段且无需考虑多核任务同步问题因为这些数据是只读的。3.2 获取与操作网络MAC地址ESP32可以有多个MAC地址理解其衍生关系至关重要。1. 获取当前使用的各种MAC地址#include “esp_mac.h” void print_all_macs() { uint8_t mac[6]; // 1. Wi-Fi Station MAC (客户端模式) esp_read_mac(mac, ESP_MAC_WIFI_STA); printf(“Wi-Fi Station MAC: “); print_mac(mac); // 2. Wi-Fi SoftAP MAC (接入点模式) esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP); printf(“Wi-Fi SoftAP MAC: “); print_mac(mac); // 3. Bluetooth MAC esp_read_mac(mac, ESP_MAC_BT); printf(“Bluetooth MAC: “); print_mac(mac); // 4. Ethernet MAC (如果使能了以太网) // esp_read_mac(mac, ESP_MAC_ETH); } void print_mac(uint8_t *mac) { for (int i 0; i 6; i) { printf(“%02x”, mac[i]); if (i 5) printf(“:”); } printf(“\n”); }2. MAC地址的衍生规则ESP-IDF有一个内部的MAC地址衍生规则。基础MAC地址从eFuse读取被用作“种子”。其他MAC地址在此基础上计算得出通常是给基础MAC地址的最后一个字节加上一个偏移量。例如Wi-Fi Station MAC 基础MACWi-Fi SoftAP MAC 基础MAC最后一位 1Bluetooth MAC 基础MAC最后一位 2这样做确保了同一芯片上的不同网络接口拥有唯一且相关的MAC地址。3. 如何设置自定义MAC地址有时出于产品管理或替换损坏芯片的需求我们希望覆盖出厂MAC。#include “esp_wifi.h” void set_custom_wifi_mac() { // 定义一个自定义MAC地址务必确保在局域网内唯一 uint8_t custom_mac[6] {0x24, 0x0A, 0xC4, 0x12, 0x34, 0x56}; esp_err_t ret esp_wifi_set_mac(WIFI_IF_STA, custom_mac); if (ret ! ESP_OK) { printf(“设置STA MAC失败: %s\n”, esp_err_to_name(ret)); } // 注意需要在wifi_init()之后wifi_start()之前调用 }重要警告自定义MAC地址必须谨慎必须确保你设置的地址不会与网络中其他设备冲突。滥用或随机设置可能导致网络混乱。最佳实践是从你拥有的合法OUI地址段中进行分配或者使用基于芯片UID通过哈希算法生成的“伪随机”地址。4. STM32篇深入UID读取与实战应用STM32的UID系统与ESP32完全不同。它没有集成的网络功能因此焦点完全集中在芯片唯一标识符上。STM32的UID存放在芯片内部Flash存储器的特定系统存储区域通过内存映射的方式访问。4.1 标准方法通过参考手册与内存映射读取这是最通用、最可靠的方法。你需要查阅对应STM32系列如F1, F4, H7等的《参考手册》找到“Unique device ID register”章节。UID的地址通常是固定的例如在STM32F1系列中UID起始地址是0x1FFFF7E8。通用读取函数示例 (基于STM32 HAL库)#include “main.h” // 包含你的芯片头文件如stm32f1xx_hal.h void print_stm32_uid() { // 1. 定义UID地址。以下地址以STM32F103C8T6为例其他芯片务必查手册 #define UID_BASE_ADDRESS 0x1FFFF7E8UL #define UID_SIZE_BYTES 12 // STM32F1 UID为96位12字节 // 2. 将地址转换为指针。使用volatile防止编译器优化使用const表明只读。 volatile const uint8_t *uid_ptr (volatile const uint8_t *)UID_BASE_ADDRESS; printf(“STM32 Unique ID (96-bit): “); // 3. 读取并打印。注意STM32是小端字节序(LSB在前)。 // 但UID的“唯一性”是整个数据块我们通常按字节顺序读取即可。 for (int i 0; i UID_SIZE_BYTES; i) { printf(“%02X”, uid_ptr[i]); // 按地址递增读取 // 如果你想按16位或32位读取需要注意对齐和字节序。 // 例如 uint32_t uid_part *(volatile const uint32_t*)(UID_BASE_ADDRESS i*4); } printf(“\n”); // 4. (可选) 将UID转换为一个更方便使用的整数或字符串 // 例如取后64位作为一个长整型ID (假设你的应用不需要完整的96位) uint64_t short_uid 0; for (int i UID_SIZE_BYTES - 8; i UID_SIZE_BYTES; i) { short_uid (short_uid 8) | uid_ptr[i]; // 组合字节 } printf(“截取的后64位UID: %llX\n”, short_uid); }为什么使用volatile const指针volatile告诉编译器这个指针指向的内容可能被硬件改变虽然UID不会变禁止编译器对这个区域的读取操作进行优化如缓存读取值、省略“冗余”读取。对于内存映射的硬件寄存器必须使用volatile。const表明我们不会去写入符合UID只读的特性同时也能让编译器进行一些优化。4.2 使用STM32CubeIDE/HAL库的便捷方法对于使用STM32CubeMX生成代码的项目HAL库提供了一个更便携的宏来获取UID但本质上它还是指向了那个固定的地址。// 在main.c中通常已经由CubeMX定义了芯片相关的头文件 #ifdef STM32F1 #define UID_BASE UID_BASE_F1 // 具体宏名需查看芯片头文件 #endif // 更通用的方法是直接使用HAL库的标识符如果可用 // 有些系列的HAL库定义了 UID_BASE但并非所有。 // 最保险的做法仍然是查阅你所用芯片系列的HAL库头文件例如 // 在 stm32f1xx_hal.h 中搜索 “UID”实操心得地址是硬编码的UID地址是芯片设计时固定的不同STM32系列甚至同系列不同容量型号地址可能不同。F1、F4、L4、H7的UID地址都不一样。每次换芯片型号第一件事就是查《参考手册》确认UID地址这是最容易出错的地方。UID长度不统一STM32F1是96位12字节STM32F4是96位而有些系列可能是128位。代码中的UID_SIZE_BYTES需要根据手册调整。字节序问题虽然我们按字节读取最安全但如果你需要将UID作为一个整体如32位整数数组进行哈希或比较需要注意STM32是小端架构。例如地址0x1FFFF7E8存储的是UID的最低有效字节LSB。在将UID发送到网络通常是大端序或与其他大端系统比较时可能需要转换字节序。UID的稳定性STM32的UID在芯片复位、掉电后保持不变且无法被用户程序擦写。它是硬件级别的标识。4.3 STM32 UID的典型应用场景固件加密与授权// 在固件中预置一个加密密钥或密钥种子该密钥与UID绑定 uint64_t device_uid get_shortened_uid(); // 获取缩短的UID uint32_t secret_key calculate_key(device_uid, master_secret); // 在运行时验证通过UID计算出的密钥是否与预期匹配不匹配则限制功能。生成设备序列号char serial_num[25]; snprintf(serial_num, sizeof(serial_num), “PROD-%012llX”, get_shortened_uid()); // 生成如 “PROD-240AC4123456” 的序列号用于贴标、扫码录入系统。通信加密的盐值在设备与服务器首次握手时将UID作为盐值Salt参与密钥协商增加通信的唯一性和安全性。5. 常见问题与深度排查指南在实际项目中获取UID/MAC看似简单却可能遇到各种意想不到的问题。下面是我从多个项目中总结出的“避坑”清单。5.1 ESP32典型问题问题1获取到的MAC地址全是0xFF或0x00。可能原因eFuse读取错误或者芯片的eFuse区域确实未被正确编程某些早期工程样片或非正规渠道芯片。排查步骤使用espefuse.py工具ESP-IDF自带连接芯片查看eFuse摘要espefuse.py -p PORT summary。检查MAC字段是否正常。确认使用的API是否正确。尝试使用esp_efuse_mac_get_default和esp_read_mac进行对比。如果eFuse中MAC确实为空ESP-IDF在初始化网络时会使用一个软件生成的随机MAC。但这不能作为硬件唯一ID使用。解决方案联系芯片供应商。对于量产产品务必在采购时确认芯片eFuse已编程。问题2Wi-Fi和蓝牙的MAC地址冲突导致连接不稳定。可能原因自定义MAC地址设置不当或者基础MAC衍生规则被破坏导致两个接口MAC地址相同。排查步骤在初始化Wi-Fi和蓝牙之前分别打印它们的MAC地址进行比对。检查代码中是否在esp_wifi_set_mac或esp_bluedroid_init等函数中设置了冲突的地址。解决方案遵循ESP-IDF的MAC地址衍生规则。如果需要自定义确保为Station、SoftAP、BT分别设置唯一且符合OUI规范的地址。问题3UID/MAC在深度睡眠后“变化”了。可能原因这不是UID/MAC变了而是你的程序在深度睡眠唤醒后重新初始化可能因为代码逻辑问题读取到了一个缓存值或错误地址。排查步骤在深度睡眠唤醒后的初始化函数中加入打印UID/MAC的日志与睡眠前对比。确保读取函数在唤醒后能被正确执行且依赖的硬件如eFuse控制器已就绪。解决方案将UID/MAC读取操作放在唤醒后稳定的初始化阶段避免在RTC内存中存储指向易失数据的指针。5.2 STM32典型问题问题1读取UID导致硬件错误HardFault。可能原因地址错误使用了错误的UID基地址访问了非法内存区域。对齐错误试图以32位方式读取一个非4字节对齐的地址虽然UID地址通常是对齐的但误操作可能导致。内存保护在某些高安全系列如TrustZone或配置了MPU内存保护单元的芯片上对系统存储区域的访问可能被禁止。排查步骤核对《参考手册》这是第一步也是最重要的一步。检查指针类型转换是否正确。在调试器中单步执行到读取UID的那行代码观察指针值是否与手册一致。尝试先以8位字节方式读取第一个字节看是否成功。解决方案修正地址确保以字节访问或对齐访问检查并配置MPU或安全状态允许对系统存储区的读取。问题2不同批次的芯片用同样的代码读出的UID格式感觉不一样。可能原因你的代码对UID的“解释”方式不一致。例如有的代码将UID当作一个16进制字符串打印有的将其当作一个整数数组进行哈希。如果打印时格式控制符不对或者组合字节时顺序端序处理不一致就会导致表象不同。排查步骤统一使用最原始的字节数组打印方式进行比较。写一个简单的测试固件只做一件事按字节顺序打印UID。烧录到不同批次的芯片上运行对比输出的原始字节流。解决方案在项目初期就确定UID的使用规范。例如“本项目统一将UID视为一个12字节的大端序数组进行处理和存储”。所有涉及UID的代码读取、发送、比较、哈希都遵循这个规范。问题3UID用于加密种子但设备丢失后无法恢复。可能原因这是产品设计逻辑问题而非技术问题。将UID作为加密的唯一因子意味着固件和芯片必须一一对应。如果芯片物理损坏即使你有备份的固件也无法运行在新的芯片上。解决方案分级消费级接受此限制。设备损坏即报废。在用户协议中说明。企业级/工业级实现一个授权服务器。设备首次上线时将UID上报服务器服务器返回一个激活码或令牌。这样更换芯片后新设备可以重新联系服务器获取授权。UID在这里用作设备身份证明而非加密密钥本身。5.3 通用最佳实践与安全考量不要直接暴露原始UID/MAC尤其是在网络通信或日志中。攻击者可能利用此信息进行设备克隆或追踪。建议对原始ID进行哈希如SHA-256后再使用哈希值即为你的“设备指纹”。本地存储备份对于关键应用可以在设备首次启动时读取UID/MAC计算出一个派生ID并将其存储到非易失存储器如EEPROM、Flash的某个分区中。这样即使主芯片的UID读取电路出现极端问题也有一个备份标识符可用。生产测试环节验证在产品量产烧录和测试工位上增加一个自动读取并记录UID/MAC的步骤。将读取到的ID与产品序列号绑定录入数据库。这为后续的质保、追踪和售后服务提供了数据基础。考虑UID的耗尽问题虽然概率极低但从理论上看任何唯一标识符系统都有耗尽的可能。对于计划出货量巨大的产品数亿台需要了解芯片厂商的UID分配策略和容量评估风险。获取MCU的唯一标识符就像为每一个智能设备办理了一张终身身份证。这张身份证是连接物理世界与数字世界的信任锚点。无论是ESP32还是STM32理解其原理、掌握其方法、规避其陷阱是每一位嵌入式开发者构建可靠、安全、可管理产品的基础能力。希望这篇详尽的拆解能让你下次在调用那几行读取函数时心中更有底气。
返回列表