0911 · Microchip ATECC608A · CryptoAuthentication 安全存储芯片

0911 ATECC608A 加密存储芯片
(WCMCU-608模块) 完整技术资料

这颗芯片接到 ESP32-2432S028R(2.8"屏/CYD)上,做了一个中文触摸UI演示:检测芯片、读序列号、Configuration+Lock流程、密钥生成、签名与验证、随机数、SHA-256哈希,一共9个界面状态。这份资料整理了芯片规格、接线方法、安全机制、完整固件代码,以及触摸UI的每个页面含义。固件当前固定运行在 SIMULATE_LOCK=1 模拟模式——界面/交互全部真实,但没有对物理芯片执行任何真实的写配置/Lock操作,详见第5节。

  • 芯片Microchip ATECC608A-MAHDA-S(UDFN8)
  • 接口I2C(本资料用的)/ 单线SWI
  • I2C地址0x60(固定)
  • 主控+屏幕ESP32-2432S028R 2.8" TFT (CYD)
  • 状态⚠ 模拟模式(SIMULATE_LOCK=1),未对实体芯片执行真实Lock
1

总览

Overview

ATECC608A 是 Microchip CryptoAuthentication 系列的硬件加密协处理器 + 安全密钥存储芯片。跟本工作区里其它传感器类项目(BMM150/DPS310)不同,它本身不产生物理量数据,产出的是"证明身份"和"保护密钥"——私钥在芯片内部生成、加密运算在芯片内部完成,正常使用流程里私钥不会以明文形式出现在总线上或主控MCU的内存里。这次把它接到 ESP32-2432S028R 上,做了一个中文触摸UI,把检测、Lock流程、密钥生成、签名验证、随机数、哈希这几个核心功能逐个点亮演示。

ATECC608A

  • 硬件密钥安全存储,最多16个slot
  • ECDSA签名/验签(NIST P256椭圆曲线)
  • SHA-256、HMAC、AES-128
  • 内置随机数发生器(NIST SP 800-90A/B/C)
  • 出厂唯一72-bit序列号,防伪造/防克隆

ESP32-2432S028R(CYD)

  • ESP32-D0WD-V3 双核
  • 板载2.8" ST7789 TFT,240×320,带触摸(XPT2046)
  • CN1扩展口:GND/IO22/IO27/3V3
  • 本项目把CN1当I2C用(SCL/SDA)
  • 触摸只判断"按下没有",不做坐标映射

中文触摸UI

  • 检测/信息/锁定确认/锁定结果 4个基础页
  • 密钥生成/签名验证/随机数/SHA256哈希 4个demo页
  • U8g2_for_TFT_eSPI + 文泉驿点阵字体渲染中文
  • 长按5秒+进度条确认永久锁定,防误触
  • ⚠ 当前固定SIMULATE_LOCK=1,全部demo数据都是模拟值
2

ATECC608A 详解

CryptoAuthentication 安全存储芯片

板载型号 ATECC608A-MAHDA-S(UDFN8封装,"MAH"是UDFN封装的官方标记代号)。板子丝印/淘宝标注为"WCMCU-608"。

项目参数
核心功能硬件密钥安全存储(16 slot) + ECDSA签名/验签(P256) + ECDH密钥协商 + SHA-256/HMAC/AES-128 + 硬件随机数发生器 + 2个高耐久单调计数器
接口标准I2C(最高1MHz) 或 单线SWI(省一个GPIO),本资料用I2C
I2C地址固定 0x60(7-bit,不可改)
供电2.0~5.5V,IO电平1.8~5.5V
功耗等待I/O时约2mA(典型),执行ECC命令时3~14mA,待机约800µA,睡眠典型30nA/最大150nA
工作温度-40℃~+85℃
EEPROM寿命40万次/字节写入;数据保持35℃下30~50年
封装8-pad UDFN 或 8-lead SOIC
唯一序列号出厂焊死的72-bit唯一序列号(带厂商签名,防伪造/防克隆),本资料实测S/N: 0123E19A6949E835EE

Pin功能
1 / 2 / 3NC(不接)
4GND
5SDA
6SCL
7NC(不接)
8VCC

⚠ A版本已知I2C小bug,跟别的I2C设备共用总线要注意

ATECC608A(A版本)有一个已知I2C电路小bug:芯片处于Idle模式、总线频率≤300kHz、且总线上还挂了其他I2C设备时,总线上别的设备产生的数据模式有极小概率被误判成唤醒脉冲,可能干扰正在发给其他设备的数据;B版本(ATECC608B)修了这个问题。本项目用的是A版本。如果以后要跟别的I2C传感器(如SHT41/BH1750等)共用一条总线,建议把总线速率设到400kHz或更高来规避。


出厂默认"未Lock"状态

芯片出厂默认是未Lock状态(未配置)。像随机数、生成密钥、签名这些完整加密功能,需要先写入Configuration Zone并执行Lock命令才能用;但读取芯片是否存在、读序列号(serialNumber())在未Lock状态下就能正常工作,适合做"通不通"的先期测试。详见第5节安全机制。


ATECC608A模块实拍特写,丝印清晰可见型号
实拍 · WCMCU-608模块特写,紫色沉金板,丝印清楚印着ATECC608A型号
3

ESP32 CYD 详解

ESP32-2432S028R · 2.8" 屏

圈内俗称"CYD"(Cheap Yellow Display),主控是ESP32-D0WD-V3双核芯片,板载一块2.8寸ST7789驱动的TFT彩屏(240×320,SPI接口,带XPT2046电阻触摸),还带一个CN1四针扩展口,跟本工作区里的HC-SR04、BMM150、DPS310几个项目共用同一套板子和引脚约定。

项目参数
主控ESP32-D0WD-V3,双核,自带Wi-Fi/蓝牙(本项目未使用)
屏幕2.8" ST7789,240×320,SPI接口,用TFT_eSPI库驱动
触摸XPT2046电阻触摸,本项目只用IRQ引脚判断"有没有按下",不做坐标映射(CS常拉低)
CN1扩展口GND / IO22 / IO27 / 3V3 四针,本资料把 IO22 当SCL、IO27 当SDA 用
烧录方式PlatformIO命令行 pio run --target upload

触摸引脚(跟I2C/屏幕引脚分开)

触摸走的是SPI接口,不占用I2C的IO22/IO27:TOUCH_CS=33TOUCH_IRQ=36。固件里把CS常拉低,只轮询IRQ电平——按下即为低电平,省掉坐标标定这一步,跟本工作区03号项目(BMI160单独测试UI)"点哪都算"的思路一致。

4

接线

仅4根线,I2C模式

只用到4根线,ATECC608A模块接到CYD的CN1扩展口上。

WCMCU-608 (ATECC608A)ESP32 CYD · CN1

VCC3V3
GNDGND
SDAIO27
SCLIO22
I2C总线协议,两根数据线就能通信;芯片在总线上的地址固定是0x60,不需要额外配置
5

安全机制:Configuration 与 Lock

⚠ 一次性、不可逆操作

芯片出厂时是"空白"状态,密钥怎么用、能不能读、能不能改,这些规则都还没定下来。要真正用上它的加密功能,得先写一份配置,再执行一个叫Lock的操作,把这份配置永久锁死——锁死之后配置就再也改不了了,这是芯片故意设计成不可逆的,防的是运行中途配置被人篡改。

⚠ 本项目固件当前固定运行在 SIMULATE_LOCK=1

固件main.ino第27行 #define SIMULATE_LOCK 1:界面、交互、长按5秒的确认流程全部真实,但没有真的调用写配置和锁定这两个函数,也不会真的执行签名/生成密钥/随机数——全部用标了"模拟"字样的假数据展示。屏幕右下角常驻一行黄字"模拟模式 - 未触碰真实芯片"提醒这一点。要真的对物理芯片执行Lock,需要把这个宏改成0并重新烧录,且Lock之后配置永久生效、不能反悔。


官方安全做法:不要自己手写Configuration Zone字节去Lock —— ArduinoECCX08库自带一份Arduino官方验证过的默认TLS配置,是目前最安全可靠的参考做法:

官方Configuration+Lock流程
#include <ArduinoECCX08.h>
#include <utility/ECCX08SelfSignedCert.h>
#include <utility/ECCX08DefaultTLSConfig.h>   // 库自带的默认TLS配置数组

// 确认 ECCX08.locked() 为 false 之后:
ECCX08.writeConfiguration(ECCX08_DEFAULT_TLS_CONFIG);  // 写入官方默认配置
ECCX08.lock();                                          // 永久锁定,不可逆!

// Lock 之后才能:
ECCX08.generatePrivateKey(slot, publicKey);   // 在 slot(0~4)里生成新私钥,返回对应公钥
ECCX08.ecSign(slot, message32bytes, signature);      // 用该私钥签名(输入必须是32字节)
ECCX08.ecdsaVerify(message32bytes, signature, publicKey);  // 验签
ECCX08.random(65535);                          // 生成随机数(Lock后才可用)
6

触摸UI与9个界面状态

中文UI · U8g2_for_TFT_eSPI + 文泉驿字体

屏幕在任意时刻只处于一个"状态",单击/长按5秒驱动状态跳转。以下是每个页面显示什么、代表什么:

页面(状态)屏幕显示代表什么怎么进入/离开
检测中...纯标题,无内容开机瞬间,正在调用ECCX08.begin()探测I2C总线0x60地址开机自动进入,几百毫秒后自动跳下一页
未找到芯片红字+接线提示(SDA=IO27 SCL=IO22)I2C探测失败,多半是接线错长按5秒重启重试
已找到芯片绿字+型号+72位序列号+锁定状态主页/信息页,序列号是出厂唯一防伪编号未锁定时单击进锁定确认页;已锁定单击进密钥生成;长按5秒重新扫描
永久锁定确认"不可撤销"提示+红色进度条二次确认页,长按满5秒才生效,防误触长按满5秒触发Lock(本项目当前为模拟)
锁定成功/失败结果+模拟标注明确告知这次是否真的改了芯片配置单击返回主页
生成密钥槽位号+公钥十六进制芯片内部生成密钥对,私钥永远不出芯片单击下一个;长按5秒回主页
签名与验证签名值+"验证:通过"用密钥对数据签名再验证,身份认证核心用法同上
生成随机数一个数字芯片硬件随机数发生器(TRNG),每次换新数单击换一个新数;长按5秒回主页
SHA-256哈希32字节数据的摘要值硬件SHA-256加速,验证数据完整性同上

已找到芯片信息页实拍
实拍 · "已找到芯片"信息页,绿字型号+序列号
生成密钥demo页实拍
实拍 · "生成密钥"demo页,槽位0+公钥(模拟)

SHA-256哈希demo页实拍
实拍 · "SHA-256哈希"demo页,对32字节数据求哈希,摘要(模拟)十六进制值
7

完整固件代码

PlatformIO · Arduino (ESP32) ✅ 编译/烧录/触摸交互均已验证

用 PlatformIO + Arduino核心,ArduinoECCX08库源码直接拷进工程lib/目录(不依赖联网下载);TFT_eSPI/U8g2/U8g2_for_TFT_eSPI三个库走registry/git URL拉取。

代码在本机的位置

下面这份代码就是工程里实际跑的那份,文件在 06_测试代码\ESP32_ATECC608A_UI演示\src\main.ino,同目录 固件源码解说与屏幕说明.md 有更详细的分段代码解说(含真实行号),配套B站视频"关键代码解读"章节用的就是这份材料。


platformio.ini
[env:esp32dev]
platform = espressif32@6.13.0
board = esp32dev
framework = arduino
board_build.partitions = huge_app.csv
; Arduino-ESP32 核心3.x(ESP-IDF5)在这台机器上链接报错:
;   undefined reference to `esp_bt_controller_mem_release'
; 是工具链BT库版本不匹配的已知问题,跟代码无关。固定用2.0.17这个稳定核心版本绕开。
; (跟 02_中文触摸效率时钟、U1205 BMM150、02601 DPS310 项目踩过的坑是同一个,写法照抄。)
platform_packages =
    framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/releases/download/2.0.17/esp32-2.0.17.zip
upload_port = COM20
monitor_port = COM20
monitor_speed = 115200

; TFT_eSPI版本/引脚定义照抄02_中文触摸效率时钟项目里已验证过的配置。
; U8g2 + U8g2_for_TFT_eSPI:给TFT_eSPI加中文字体支持(文泉驿点阵字库wqy16/wqy12),
; 字库数据直接编译进固件,不用额外生成.vlw字库文件。
lib_deps =
    bodmer/TFT_eSPI @ ^2.5.43
    olikraus/U8g2 @ ^2.35.19
    https://github.com/Bodmer/U8g2_for_TFT_eSPI.git

build_flags =
    -D USER_SETUP_LOADED=1
    -D ST7789_DRIVER=1
    -D TFT_WIDTH=240
    -D TFT_HEIGHT=320
    -D TFT_MOSI=13
    -D TFT_SCLK=14
    -D TFT_CS=15
    -D TFT_DC=2
    -D TFT_RST=-1
    -D TFT_MISO=-1
    -D TFT_BL=21
    -D TFT_BACKLIGHT_ON=HIGH
    -D TFT_INVERSION_OFF=1
    -D SPI_FREQUENCY=40000000
    -D LOAD_GLCD=1
    -D LOAD_FONT2=1
    -D LOAD_FONT4=1
    -D LOAD_FONT6=1
    -D LOAD_FONT7=1
    -D LOAD_FONT8=1
    -D LOAD_GFXFF=1
    -D SMOOTH_FONT=1

src/main.ino
/*
  ESP32-2432S028R(2.8寸屏CYD) + ATECC608A 触摸UI演示(中文版)
  接线:模块 VCC->3.3V,GND->GND,SDA->IO27,SCL->IO22(跟BMM150/DPS310项目同一路I2C排针)
  屏幕/触摸引脚沿用 02_中文触摸效率时钟、03_BMI160单独测试UI 两个项目已验证过的配置。
  中文字体用 U8g2_for_TFT_eSPI + U8g2 自带的文泉驿点阵字体(wqy16/wqy12),
  不用额外生成字库文件,字库数据直接编译进固件。

  交互:
    - 单击:翻页/切换演示内容
    - 长按(HOLD_MS,当前5秒,按住期间屏幕下方有红色进度条):确认"永久Lock"这类
      不可逆操作;主页长按=重新扫描芯片;演示页长按=返回主页

  SIMULATE_LOCK = 1 (当前默认):
    只走UI/状态机流程,不会真的调用 ECCX08.writeConfiguration()/lock(),也不会真的
    执行签名/生成密钥/随机数——全部用标了"模拟"字样的假数据展示,用来先把触摸交互
    和翻页调试顺,不碰物理芯片的真实配置。
    确认UI没问题、且用户明确同意要真的Lock这颗芯片之后,把这个宏改成0再重新烧录,
    才会真的执行 Configuration+Lock 和调用真实的签名/密钥/随机数功能。
*/

#include <SPI.h>
#include <TFT_eSPI.h>
#include <U8g2_for_TFT_eSPI.h>
#include <Wire.h>
#include <ArduinoECCX08.h>

#define SIMULATE_LOCK 1

#define I2C_SDA 27
#define I2C_SCL 22

#define TOUCH_CS 33
#define TOUCH_IRQ 36

#define HOLD_MS 5000UL
#define KEY_SLOT 0

#define SCREEN_W 320
#define SCREEN_H 240

#define COL_BG      TFT_BLACK
#define COL_TEXT    TFT_WHITE
#define COL_DIM     TFT_DARKGREY
#define COL_OK      TFT_GREEN
#define COL_WARN    TFT_RED
#define COL_ACCENT  TFT_YELLOW
#define COL_BAR_BG  0x2104   // 深灰
#define COL_BAR_FG  TFT_RED

TFT_eSPI tft = TFT_eSPI();
U8g2_for_TFT_eSPI u8f;

enum AppState {
  STATE_DETECT,
  STATE_ERROR,
  STATE_INFO,
  STATE_LOCK_CONFIRM,
  STATE_MENU_KEYGEN,
  STATE_MENU_SIGN,
  STATE_MENU_RANDOM,
  STATE_MENU_SHA256,
};

static AppState state = STATE_DETECT;
static bool chipLockedReal = false;   // ECCX08.locked() 的真实结果
static bool simLocked = false;        // 仅SIMULATE_LOCK=1时,软件层面假装"已锁定"
static String serialNumberStr;

static bool isLockedEffective() {
#if SIMULATE_LOCK
  return simLocked;
#else
  return chipLockedReal;
#endif
}

// ---------------- 触摸:只用IRQ判断"有没有摁下",不做坐标映射 ----------------
// XPT2046在CS拉低期间,只要屏幕被摁着,PENIRQ会一直是低电平——足够用来做
// "单击/长按"这种不需要具体坐标的交互,省掉坐标标定这一步(跟03号项目思路一致,
// 那边也是靠"点哪都算",没做坐标映射)。
static void touchInit() {
  pinMode(TOUCH_CS, OUTPUT);
  digitalWrite(TOUCH_CS, LOW);   // 常拉低,让IRQ一直有效
  pinMode(TOUCH_IRQ, INPUT);
}

static bool isTouchedNow() {
  return digitalRead(TOUCH_IRQ) == LOW;
}

// ---------------- 画面绘制小工具 ----------------
// 中文用U8g2的文泉驿点阵字体:wqy16(16px,标题/正文用) + wqy12(12px,提示小字用)。
// 这两个字体是等宽位图字体,一个汉字/一个ASCII字符基本都是"字号"px宽,
// 320屏两边各留12px边距,可用宽度296px,正文最多约18个汉字,够用不会超屏。
static int cursorY;

static void resetCursor(int y) { cursorY = y; }
static void setPrimaryFont()   { u8f.setFont(u8g2_font_wqy16_t_gb2312); }   // 16px
static void setSecondaryFont() { u8f.setFont(u8g2_font_wqy12_t_gb2312); }   // 12px

static void drawHeader(const char *title, uint16_t color) {
  tft.fillScreen(COL_BG);
  u8f.setForegroundColor(color);
  setPrimaryFont();
  u8f.drawUTF8(12, 30, title);
  tft.drawFastHLine(0, 40, SCREEN_W, COL_DIM);
#if SIMULATE_LOCK
  u8f.setForegroundColor(COL_ACCENT);
  setSecondaryFont();
  u8f.drawUTF8(12, SCREEN_H - 8, "模拟模式 - 未触碰真实芯片");
#endif
  resetCursor(62);
}

// tier: 2=正文(默认,16px文泉驿),1=小字提示(12px文泉驿)
static void printLine(const String &text, uint16_t color, uint8_t tier = 2) {
  u8f.setForegroundColor(color);
  if (tier >= 2) setPrimaryFont(); else setSecondaryFont();
  u8f.drawUTF8(12, cursorY, text.c_str());
  cursorY += (tier >= 2) ? 24 : 18;
}

static void printGap(int px) { cursorY += px; }

static void drawProgressBar(uint32_t elapsedMs) {
  const int x = 12, y = SCREEN_H - 46, w = SCREEN_W - 24, h = 16;
  uint32_t clamped = elapsedMs > HOLD_MS ? HOLD_MS : elapsedMs;
  int fillW = (int)((uint32_t)w * clamped / HOLD_MS);
  tft.fillRect(x, y, w, h, COL_BAR_BG);
  if (fillW > 0) tft.fillRect(x, y, fillW, h, COL_BAR_FG);
  tft.drawRect(x, y, w, h, COL_TEXT);
}

static void clearProgressBar() {
  const int x = 12, y = SCREEN_H - 46, w = SCREEN_W - 24, h = 16;
  tft.fillRect(x, y, w, h, COL_BAR_BG);
  tft.drawRect(x, y, w, h, COL_TEXT);
}

// 只转前 len 字节,每 8 字节一行(16个hex字符),用正文字号显示也不会超屏。
static String toHex(const uint8_t *data, int len) {
  String s;
  char buf[3];
  for (int i = 0; i < len; i++) {
    sprintf(buf, "%02X", data[i]);
    s += buf;
  }
  return s;
}

// ---------------- 各页面绘制 ----------------
static void renderDetectPage() {
  drawHeader("检测中...", COL_TEXT);
}

static void renderErrorPage() {
  drawHeader("未找到芯片", COL_WARN);
  printLine("请检查接线:", COL_TEXT);
  printLine("SDA=IO27 SCL=IO22", COL_TEXT);
  printGap(10);
  printLine("长按5秒重试", COL_DIM, 1);
}

static void renderInfoPage() {
  drawHeader("已找到芯片", COL_OK);
  printLine("型号:ATECC608A", COL_TEXT);
  printLine("序列号:", COL_DIM, 1);
  printLine(serialNumberStr, COL_TEXT);
  printGap(6);
  if (isLockedEffective()) {
    printLine("已锁定", COL_OK);
    printGap(6);
    printLine("轻触:查看演示", COL_DIM, 1);
  } else {
    printLine("未锁定(出厂状态)", COL_WARN);
    printGap(6);
    printLine("轻触:进入锁定步骤", COL_DIM, 1);
  }
  printLine("长按5秒:重新扫描", COL_DIM, 1);
}

static void renderLockConfirmPage(uint32_t heldMs) {
  drawHeader("永久锁定确认", COL_WARN);
  printLine("该操作不可撤销!", COL_TEXT);
  printLine("确认后配置永久生效", COL_TEXT);
  printGap(10);
  printLine("长按5秒确认锁定", COL_ACCENT);
  drawProgressBar(heldMs);
}

static void renderLockResultPage(bool ok) {
  drawHeader(ok ? "锁定成功" : "锁定失败", ok ? COL_OK : COL_WARN);
#if SIMULATE_LOCK
  printLine("(模拟,未写入真实配置)", COL_ACCENT);
#else
  printLine(ok ? "配置已写入并锁定" : "写入或锁定失败", COL_TEXT);
#endif
  printGap(10);
  printLine("轻触继续", COL_DIM, 1);
}

static void renderKeygenPage() {
  drawHeader("生成密钥", COL_TEXT);
  printLine("槽位:" + String(KEY_SLOT), COL_TEXT);
  printGap(6);

  uint8_t publicKey[64];
#if SIMULATE_LOCK
  for (int i = 0; i < 64; i++) publicKey[i] = (uint8_t)(0xA5 ^ (i * 7 + 3));
  printLine("公钥(模拟):", COL_ACCENT, 1);
#else
  ECCX08.generatePrivateKey(KEY_SLOT, publicKey);
  printLine("公钥(真实):", COL_ACCENT, 1);
#endif
  printLine(toHex(publicKey, 8), COL_TEXT);
  printLine(toHex(publicKey + 8, 8), COL_TEXT);
  printGap(10);
  printLine("轻触:下一个  长按5秒:主页", COL_DIM, 1);
}

static void renderSignPage() {
  drawHeader("签名与验证", COL_TEXT);

  const byte input[32] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
  };
  byte signature[64];
  byte publicKey[64];
  bool verified;

#if SIMULATE_LOCK
  for (int i = 0; i < 64; i++) signature[i] = (uint8_t)(0x5A ^ (i * 11 + 1));
  verified = true;
  printLine("签名(模拟):", COL_ACCENT, 1);
#else
  ECCX08.generatePublicKey(KEY_SLOT, publicKey);
  ECCX08.ecSign(KEY_SLOT, input, signature);
  verified = ECCX08.ecdsaVerify(input, signature, publicKey);
  printLine("签名(真实):", COL_ACCENT, 1);
#endif
  printLine(toHex(signature, 8), COL_TEXT);
  printLine(toHex(signature + 8, 8), COL_TEXT);
  printGap(6);
  printLine(verified ? "验证:通过" : "验证:失败", verified ? COL_OK : COL_WARN);
  printGap(10);
  printLine("轻触:下一个  长按5秒:主页", COL_DIM, 1);
}

static void renderRandomPage() {
  drawHeader("生成随机数", COL_TEXT);

  long value;
#if SIMULATE_LOCK
  value = random(0, 65535);
  printLine("随机数(模拟):", COL_ACCENT, 1);
#else
  value = ECCX08.random(65535);
  printLine("随机数(真实):", COL_ACCENT, 1);
#endif
  printGap(6);
  printLine(String(value), COL_TEXT);
  printGap(10);
  printLine("轻触:换一个新数", COL_DIM, 1);
  printLine("长按5秒:返回主页", COL_DIM, 1);
}

static void renderSha256Page() {
  drawHeader("SHA-256哈希", COL_TEXT);

  const byte input[32] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
  };
  byte digest[32];

  printLine("对32字节数据求哈希", COL_TEXT, 1);
  printGap(4);
#if SIMULATE_LOCK
  for (int i = 0; i < 32; i++) digest[i] = (uint8_t)(0x3C ^ (i * 13 + 5));
  printLine("摘要(模拟):", COL_ACCENT, 1);
#else
  ECCX08.SHA256(input, sizeof(input), digest);
  printLine("摘要(真实):", COL_ACCENT, 1);
#endif
  printLine(toHex(digest, 8), COL_TEXT);
  printLine(toHex(digest + 8, 8), COL_TEXT);
  printGap(10);
  printLine("轻触:下一个  长按5秒:主页", COL_DIM, 1);
}

static void renderCurrentState() {
  switch (state) {
    case STATE_DETECT:       renderDetectPage(); break;
    case STATE_ERROR:        renderErrorPage(); break;
    case STATE_INFO:         renderInfoPage(); break;
    case STATE_LOCK_CONFIRM: renderLockConfirmPage(0); break;
    case STATE_MENU_KEYGEN:  renderKeygenPage(); break;
    case STATE_MENU_SIGN:    renderSignPage(); break;
    case STATE_MENU_RANDOM:  renderRandomPage(); break;
    case STATE_MENU_SHA256:  renderSha256Page(); break;
  }
}

// ---------------- Lock 执行(模拟或真实,由 SIMULATE_LOCK 决定) ----------------
static bool performLock() {
#if SIMULATE_LOCK
  delay(1200);   // 假装在写配置,凑一个真实感的等待时间
  simLocked = true;
  return true;
#else
  if (!ECCX08.writeConfiguration(ECCX08_DEFAULT_TLS_CONFIG)) return false;
  if (!ECCX08.lock()) return false;
  chipLockedReal = true;
  return true;
#endif
}

// ---------------- 触摸事件 -> 状态机 ----------------
static void onShortTap() {
  switch (state) {
    case STATE_ERROR:
      break; // 只能长按重试,单击不响应
    case STATE_INFO:
      state = isLockedEffective() ? STATE_MENU_KEYGEN : STATE_LOCK_CONFIRM;
      renderCurrentState();
      break;
    case STATE_LOCK_CONFIRM:
      // 没按满5秒的单击视为无效操作,停留在本页(不取消也不确认)
      clearProgressBar();
      break;
    case STATE_MENU_KEYGEN:
      state = STATE_MENU_SIGN;
      renderCurrentState();
      break;
    case STATE_MENU_SIGN:
      state = STATE_MENU_RANDOM;
      renderCurrentState();
      break;
    case STATE_MENU_RANDOM:
      state = STATE_MENU_SHA256;
      renderCurrentState();
      break;
    case STATE_MENU_SHA256:
      state = STATE_MENU_KEYGEN;
      renderCurrentState();
      break;
    default:
      break;
  }
}

static void onLongPressComplete() {
  switch (state) {
    case STATE_ERROR:
      ESP.restart();
      break;
    case STATE_INFO:
      // 在主页长按 = 真的重新扫描一次芯片(软复位),跟"演示页长按=回主页"分开,
      // 两个长按动作不会互相冲突。
      ESP.restart();
      break;
    case STATE_LOCK_CONFIRM: {
      drawHeader("锁定中...", COL_ACCENT);
      printLine("请稍候", COL_TEXT);
      bool ok = performLock();
      renderLockResultPage(ok);
      // 下一次单击会走到 renderCurrentState(),state先切到INFO,
      // 让"轻触继续"点一下之后按当前锁定状态正确分流到菜单。
      state = STATE_INFO;
      break;
    }
    case STATE_MENU_KEYGEN:
    case STATE_MENU_SIGN:
    case STATE_MENU_RANDOM:
    case STATE_MENU_SHA256:
      // 长按回主页,不用整个重启——软件层面的模拟锁定状态也会保留。
      state = STATE_INFO;
      renderCurrentState();
      break;
    default:
      break;
  }
}

void setup() {
  Serial.begin(115200);
  delay(300);

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(COL_BG);

  u8f.begin(tft);
  u8f.setFontMode(1);       // 透明背景,不用每次单独填底色
  u8f.setFontDirection(0);

  touchInit();

  Wire.begin(I2C_SDA, I2C_SCL);

  renderDetectPage();

  if (!ECCX08.begin()) {
    Serial.println("ATECC608A not found");
    state = STATE_ERROR;
    renderCurrentState();
    return;
  }

  serialNumberStr = ECCX08.serialNumber();
  chipLockedReal = ECCX08.locked();

  Serial.print("S/N: ");
  Serial.println(serialNumberStr);
  Serial.print("Locked: ");
  Serial.println(chipLockedReal ? "yes" : "no");

  state = STATE_INFO;
  renderCurrentState();
}

void loop() {
  static uint32_t lastPoll = 0;
  static bool wasDown = false;
  static uint32_t pressStartMs = 0;
  static bool longFired = false;
  static uint32_t lastActionMs = 0;

  uint32_t now = millis();
  if (now - lastPoll < 20) return;
  lastPoll = now;

  bool down = isTouchedNow();

  if (down && !wasDown) {
    pressStartMs = now;
    longFired = false;
  }

  if (down) {
    uint32_t held = now - pressStartMs;
    if ((state == STATE_LOCK_CONFIRM) ||
        (state == STATE_ERROR) ||
        (state == STATE_INFO) ||
        (state == STATE_MENU_KEYGEN) || (state == STATE_MENU_SIGN) ||
        (state == STATE_MENU_RANDOM) || (state == STATE_MENU_SHA256)) {
      if (state == STATE_LOCK_CONFIRM) drawProgressBar(held);
      if (!longFired && held >= HOLD_MS) {
        longFired = true;
        lastActionMs = now;
        onLongPressComplete();
      }
    }
  }

  if (!down && wasDown && !longFired && (now - lastActionMs > 250)) {
    lastActionMs = now;
    onShortTap();
  }

  wasDown = down;
}
8

代码下载

打包好的3份代码,选跟你硬件匹配的下载

下载前先看清楚自己手上是什么板子——Arduino UNO R3和ESP32用的软件工具、代码格式都不一样,选错了打不开。

压缩包适用硬件代码格式用什么软件打开
UNO_R3_ATECC608A_通信测试.zipArduino UNO R3单个.ino文件Arduino IDE,双击.ino直接打开
ESP32_ATECC608A_通信测试.zip普通ESP32(不需要屏幕)PlatformIO工程(文件夹)VS Code + PlatformIO插件,打开整个文件夹
ESP32_ATECC608A_UI演示.zipESP32-2432S028R(2.8"屏/CYD)PlatformIO工程(文件夹)VS Code + PlatformIO插件,打开整个文件夹

两种代码格式的区别

Arduino IDE格式(.ino单文件):用官方Arduino IDE软件直接打开,上传前先在"库管理器"里搜索安装ArduinoECCX08库。PlatformIO工程格式(文件夹):需要VS Code + PlatformIO插件,"打开文件夹"选中整个解压出来的文件夹(不是打开某个单独文件)。两个ESP32压缩包里ArduinoECCX08库源码已经打包进lib/目录,不用再下载;但UI演示工程还用到TFT_eSPI/U8g2/U8g2_for_TFT_eSPI三个屏幕字体库,编译时会自动联网下载,编译这个工程电脑需要能上网


烧录前必看

两个ESP32工程的platformio.ini里写死了upload_port = COM20,改成你电脑实际显示的COM口号(Windows设备管理器能看到)。UI演示代码默认模拟模式(SIMULATE_LOCK=1),屏幕数据都是假的;要真正执行Configuration+Lock,需要自己把宏改成0重新烧录——这一步永久锁死芯片配置,改了不能反悔


9

实拍演示

B站视频制作中

配套B站教程视频(开箱接线→上电检测→Lock流程→密钥生成/签名验证→随机数/SHA256→关键代码解读→总结,共8章)正在制作,成片草稿见 03_制作脚本与中间文件\输出成片.mp4,标题/简介/封面定稿后会更新本节链接。

原始素材

开箱接线、上电检测、Lock长按进度条、密钥生成/签名验证/随机数/SHA256四个demo页的实拍素材在 01_原始素材_手机拍摄\IMG_9931.MOV,口播稿逐段对照见 02_口播稿与脚本\口播稿_ATECC608A教程.md

10

参考资料来源

References
资料说明
ATECC608A Summary DatasheetDS40001977B(Microchip官方,本地存档见04_规格书与Datasheet)
ATECC608A→608B迁移说明AN2237(Microchip官方)
ArduinoECCX08 官方库github.com/arduino-libraries/ArduinoECCX08
Adafruit ATECC608 教程learn.adafruit.com/adafruit-atecc608-breakout
ESP32-WROOM-32SE(内置版)Espressif官方文档 — 官方直接把ATECC608A焊在模组里的方案,跟本项目"外挂独立模块"是两种做法
← 返回全部技术资料 返回首页 进入淘宝店铺 →