2026 ADI机器控制设计竞赛 - 基于ADMT4000模块设计的多圈密码转盘锁
1. 所选任务介绍
本次项目任务名称为“多圈密码转盘锁”。核心目标是利用高精密多圈绝对值磁编码器 ADMT4000,模拟传统机械保险箱的拨盘解锁机制。与传统单圈角度传感器不同,本任务要求系统能够识别用户手动旋转的“圈数”和“最终角度”,通过预设的复杂序列(如:顺时针转 2 圈至刻度 5,再逆时针转 3 圈至刻度 8 等)来完成解锁。
系统需具备以下关键特性:
- 多维输入识别:同时检测旋转方向、转动圈数(最高 46 圈)及停止位置的刻度值(0-9)。
- 状态保持:具备断电记忆功能,意外断电或重启后,仍能保持当前的锁定/解锁状态及解锁进度。
- 实时反馈:通过串口实时显示操作数据,并可选配 LED 提供视觉反馈。
- 安全逻辑:输入错误立即重置,解锁后大幅度转动自动重新上锁。
2. 项目描述
本项目设计并实现了一套智能电子机械锁系统。系统摒弃了传统的矩阵键盘或触摸输入,转而采用更符合机械美学的“旋转拨盘”交互方式。
用户通过旋转带有磁铁的旋钮,ADMT4000 传感器实时捕捉绝对位置信息。主控芯片 ESP32-S3 对采集的数据进行解算,内部运行一个严谨的状态机,判断用户的旋转方向、转动圈数以及停止位置的刻度值。只有当用户严格按照预设的“方向 - 圈数 - 刻度”序列操作时,系统才会判定解锁成功,并通过WS2812灯和串口消息给予反馈。
若中途操作错误(如方向反了、圈数不够、微调时回退过多),系统将立即重置进度并要求从头开始。此外,项目引入了 非易失性存储(NVS),确保了锁具状态在掉电后不丢失,极大地提升了系统的实用性和安全性,完美复刻了真实保险箱的操作体验。
3. 简短的硬件介绍
本项目硬件架构简洁高效,主要包含以下核心组件:
- 主控模块:ESP32-S3 开发板
- 双核高性能 MCU,内置 Wi-Fi/蓝牙。
- 拥有丰富的 GPIO 资源和完善的 peripherals(如 SPI, NVS, LED Strip 驱动),足以支撑复杂的逻辑运算、浮点计算和多任务处理。
- 角度传感器:ADMT4000 多圈磁编码器
- 核心器件。基于各向异性磁阻(AMR)技术,无需电池即可记录多达 46 圈 的旋转历史。
- 通过 SPI 接口 通信,提供 高精度分辨率和高精度的圈数计数,是本项目实现“密码拨盘”功能的关键。
- 反馈指示:WS2812B 可编程 RGB LED 灯
- 用于直观展示系统状态:
- 蓝色常亮:表示锁定状态。
- 绿色常亮:表示解锁成功。
- 红色闪烁:提示操作错误或重置。
- 黄绿交替:提示步骤完成或微调中。
- 其他组件
- 径向永磁体:安装于旋钮内部,作为传感器的信号源。
- 电源与连接:usb+杜邦线连接。
4. 方案框图 + 设计思路
4.1 方案框图

4.2 设计思路
系统上电后,首先初始化 SPI 总线与 ADMT4000 通信,读取故障寄存器确保传感器工作正常。随后从 NVS 加载上次保存的锁状态(是否已解锁、当前进行到第几步、解锁参考圈数)。
主循环中,程序以约 20ms 的频率轮询 ADMT4000 的绝对角度寄存器(0x03)和单圈角度寄存器(0x05),执行以下逻辑:
- 状态机管理:系统维护一个
current_step变量。对于每一步,定义了两个关键参数:min_turns(最小转动圈数及方向)和target_scale(目标刻度 0-9)。 - 过程检测:
- 圈数阶段:实时计算当前圈数相对于起始点的差值,判断是否满足方向和圈数要求。
- 微调阶段:一旦圈数达标,进入“微调模式”,等待用户将角度对准目标刻度。
- 防误触与重置:在微调过程中,若检测到反向转动超过阈值,视为操作失误,立即调用重置函数清零所有进度。
- 解锁与重锁:
- 所有步骤完成后,标记为解锁状态,记录当前总圈数作为“参考点 (
g_unlock_reference_turns)”。 - 在解锁状态下,若检测到相对于参考点的转动超过半圈(0.5 圈),则自动重新上锁,模拟真实保险箱的关闭机制。
- 所有步骤完成后,标记为解锁状态,记录当前总圈数作为“参考点 (
- 持久化:每完成一步或状态改变,立即写入 NVS 保存,确保断电不丢状态。
5. 原理图、PCB 设计
由于本项目侧重于算法验证与原型开发,硬件连接采用模块化插接方式,未专门设计 PCB。
硬件连接表:
ESP32-S3 GPIO | ADMT4000 引脚 | 功能说明 |
|---|---|---|
GPIO_8 | CS | 片选信号 |
GPIO_15 | MOSI | 主机输出 (Master Out Slave In) |
GPIO_17 | MISO | 主机输入 (Master In Slave Out) |
GPIO_16 | SCK | 时钟信号 |
GPIO_18 | COIL_RESET | 线圈复位控制 |
GPIO_48 | DIN | WS2812 LED 灯数据输入 |
3.3V / GND | VCC / GND | 电源供电 |
这种连接方式灵活便捷,便于调试 SPI 通信时序和排查硬件故障。实际应用中可将其集成至小型 PCB 以缩小体积。
6. 软件流程图 + 调试软件说明 + 关键代码说明

6.1 软件流程简述
- 初始化:启动 NVS,加载历史状态,初始化 SPI 和 LED。
- 数据显示:根据当前状态(锁定/解锁/步骤中)打印提示信息。
- 数据采集:读取 ADMT4000 原始数据,转换为浮点型圈数(Turns)和刻度(Scale 0.0-9.9)。
- 逻辑判断:
- 若已解锁:监测转动幅度,若
|Current - Reference| > 0.5圈,则执行lock_lock()。 - 若锁定中:比对当前转动量与目标步长。
- 圈数未够:持续监测方向和增量,若反向转动过大则重置。
- 圈数已够:进入刻度比对,若误差
< 0.08则进入下一步。
- 若已解锁:监测转动幅度,若
- 状态更新:每完成一步或状态改变,写入 NVS 保存。
- 循环延时:短暂延时后进入下一轮循环。
6.2 调试软件说明
使用 ESP-IDF 自带的 idf.py monitor 串口监视工具(波特率 115200)。
- 动态仪表盘:代码利用 ANSI 转义码实现终端界面的局部刷新。
show_real_time_status函数实时显示“当前步骤”、“已转圈数”、“剩余圈数”、“当前刻度”及“误差值”,提供了类似仪表盘的调试体验,无需滚动查看历史日志。 - 状态日志:关键事件(解锁成功、错误重置、NVS 读写)均通过
ESP_LOGI输出带标签的日志。
6.3 关键代码深度解析
A. 多圈角度到“密码盘刻度”的映射算法
ADMT4000 输出的是连续的浮点圈数(例如 12.345 圈),而传统密码锁需要的是 0-9 的表盘刻度。核心转换函数 get_scale_from_turns 实现了这一逻辑,并引入了软件校准机制。
float get_scale_from_turns(float turns) {
// 1. 应用校准偏移:消除机械安装时的初始角度误差
// g_scale_offset 是通过实验测得的常数,确保物理 0 位对应逻辑 0.0
float corrected_turns = turns - g_scale_offset;
// 2. 提取小数部分:只关心当前在第几圈的什么位置,不关心总圈数
float fraction = corrected_turns - floorf(corrected_turns);
// 3. 处理负数情况:C 语言的取整对于负数行为特殊,需强制归一化到 [0, 1)
if (fraction < 0) fraction += 1.0f;
// 4. 映射到 0-10 刻度:小数部分 * 10 即对应表盘刻度 (0.0 ~ 9.9)
float scale = fraction * 10.0f;
// 5. 边界保护:防止浮点误差导致 scale 等于 10.0
if (scale >= 10.0f) scale -= 10.0f;
if (scale < 0) scale += 10.0f;
return scale;
}
技术亮点:该算法不仅完成了单位转换,还通过 g_scale_offset 变量解决了传感器安装无法绝对对齐物理零点的问题,无需调整硬件即可通过软件修正精度。
B. 基于状态机的密码验证逻辑
解锁过程被建模为一个严谨的状态机,核心在于区分“圈数验证阶段”和“刻度微调阶段”,并通过 g_turns_verified 标志位进行切换。
// 阶段一:圈数与方向验证
if (!g_turns_verified) {
bool direction_ok = false;
// 根据预设密码的正负值判断目标方向 (正:顺时针,负:逆时针)
if (required_turns > 0) {
if (total_delta >= required_turns) direction_ok = true;
} else {
if (total_delta <= required_turns) direction_ok = true;
}
if (direction_ok) {
// 圈数达标,锁定峰值,进入微调模式
g_turns_verified = true;
g_peak_delta = total_delta;
printf("\n[OK] 圈数达标!请微调对准刻度 %.0f...\n", target_scale);
} else {
// 错误检测:若在未达到目标前就向反方向转动超过阈值,视为作弊/误操作
float wrong_dir_threshold = 0.5f;
if ((required_turns > 0 && total_delta < -wrong_dir_threshold) ||
(required_turns < 0 && total_delta > wrong_dir_threshold)) {
reset_lock_progress(); // 立即重置所有进度
}
}
}
// 阶段二:刻度微调与防回退检测
else {
float scale_diff = fabs(current_scale - target_scale);
// 处理刻度跨越 0/10 边界的情况 (如从 9.9 到 0.1)
if (scale_diff > 5.0f) scale_diff = 10.0f - scale_diff;
if (scale_diff <= SCALE_TOLERANCE) {
// 精度满足要求,判定本步成功
g_current_step++;
// ... (进入下一步或解锁)
} else {
// 防回退机制:在微调阶段,如果往回转动超过惩罚阈值,重置
bool is_reversing = false;
if (required_turns > 0) {
if (total_delta < g_peak_delta - REVERSE_PENALTY_THRESHOLD) is_reversing = true;
} else {
if (total_delta > g_peak_delta + REVERSE_PENALTY_THRESHOLD) is_reversing = true;
}
if (is_reversing) {
printf("\n[错误] 检测到回退!重置...\n");
reset_lock_progress();
}
}
}
技术亮点:
- 分段验证:先确认“转够了没”,再确认“停准了没”,符合人类操作保险箱的逻辑。
- 防回退惩罚:引入
REVERSE_PENALTY_THRESHOLD,允许用户手抖微调,但禁止大幅度的反向回转,有效防止暴力试探密码。 - 环形刻度处理:在计算刻度误差时,专门处理了
9.9到0.1的跨零边界情况,确保算法在表盘首尾连接处的正确性。
C. NVS 非易失性存储(断电记忆)
为了实现“断电后状态保持”,项目使用了 ESP32 的 NVS (Non-Volatile Storage) 分区。关键在于保存三个核心变量:unlocked (是否已解锁), step (当前进行到第几步), ref_turns (解锁时的参考圈数)。
void save_lock_state_to_nvs(bool unlocked, int step, float ref_turns) {
nvs_handle_t nvs_handle;
// 以读写模式打开命名空间
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) return;
// 1. 保存布尔状态 (转为 uint8_t)
nvs_set_u8(nvs_handle, NVS_KEY_UNLOCKED, unlocked ? 1 : 0);
// 2. 保存当前步骤索引
nvs_set_u8(nvs_handle, NVS_KEY_STEP, (uint8_t)step);
// 3. 保存浮点数参考圈数 (使用 blob 类型存储 float)
nvs_set_blob(nvs_handle, NVS_KEY_REF_TURNS, &ref_turns, sizeof(float));
// 4. 提交事务,确保数据写入 Flash
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
}
恢复逻辑:在 lock_task 启动初期,系统会调用 load_lock_state_from_nvs。
- 若读取到
unlocked=true,系统直接进入“监控重锁”模式,不再要求输入密码,直到转动幅度超过RELOCK_THRESHOLD(0.5 圈)。 - 若读取到
unlocked=false但step > 0,系统会提示“继续第 X 步操作”,允许用户在断电后从中断处继续解锁,极大提升了用户体验。
D. 自动重锁机制(看门狗逻辑)
在解锁状态下,系统并非永久开放,而是监测相对于解锁时刻的累计转动量。
if (g_is_unlocked) {
// 计算当前圈数与解锁时记录圈数的绝对差值
float total_turn_distance = fabs(current_turns - g_unlock_reference_turns);
// 更新上一次采样值用于瞬时速度计算 (可选)
g_last_unlock_turns = current_turns;
// 核心判断:若累计转动超过半圈 (0.5),判定为关门动作,触发重锁
if (total_turn_distance > RELOCK_THRESHOLD) {
printf("\n[自动锁定] 检测到累计转动%.3f圈 > %.1f圈,锁已重新锁定\n",
total_turn_distance, RELOCK_THRESHOLD);
lock_lock(); // 执行重锁流程:清零状态、保存 NVS、LED 报警
}
}
技术亮点:此逻辑模拟了真实机械锁的“关门即上锁”特性。g_unlock_reference_turns 作为锚点,确保了即使用户在解锁后随意旋转,只要幅度不大(如在开门状态下微调),不会误触发重锁;一旦大幅度旋转(模拟关门),立即安全锁定。
E. 异常处理与系统复位
reset_lock_progress 函数是系统的“安全熔断机制”。当检测到方向错误、回退过多或超时等异常情况时调用。
void reset_lock_progress() {
// 1. 软件状态清零
g_current_step = 0;
g_turns_verified = false;
g_peak_delta = 0.0f;
g_last_turns = -999.0f; // 设为无效值,迫使主循环在下一次重新捕获起始点
// 2. 持久化清零:确保即使此时断电,重启后也是重置状态
save_lock_state_to_nvs(g_is_unlocked, g_current_step, g_unlock_reference_turns);
// 3. 硬件反馈:LED 红色闪烁报警
for (int i = 0; i < 4; i++) {
led_strip_set_pixel(led_strip, 0, 60, 0, 0); // Red
led_strip_refresh(led_strip);
vTaskDelay(150 / portTICK_PERIOD_MS);
led_strip_set_pixel(led_strip, 0, 0, 0, 0); // Off
led_strip_refresh(led_strip);
vTaskDelay(150 / portTICK_PERIOD_MS);
}
}
技术亮点:不仅重置内存变量,还同步更新 Flash 中的 NVS 数据,并配合 LED 视觉反馈,形成了完整的“检测 - 执行 - 反馈”闭环,防止系统在错误状态下卡死。
7. 实物演示及说明

在实际演示中,将磁铁固定在旋钮中心,正对 ADMT4000 传感器表面(间距约 2-5mm)。

- 上电初始:LED 呈蓝色呼吸状,串口显示“系统已锁定,请输入密码”,并列出第一步指令(如:顺时针 1.0 圈 -> 刻度 6)。

- 操作过程:用户顺时针旋转旋钮。串口实时跳动显示“已转:+0.xx 圈”。当圈数超过 1.0 时,提示“圈数达标,请微调”。用户缓慢回旋至刻度 6.0 附近,当误差进入容许范围,LED 变为黄绿交替闪烁,串口提示“第 1 步完成”,并自动进入第 2 步指令。

- 错误测试:若在第一步未完成时反向旋转,LED 立即快速闪烁红色,串口报错“检测到反向转动!重置”,进度归零。
- 解锁成功:完成所有预设步骤后,LED 转为绿色常亮,串口打印“>>> 解锁成功! <<<”。

- 断电测试:在解锁状态下断开电源再重新上电,系统依然保持绿色常亮,并提示“转动超过半圈将自动锁定”,验证了 NVS 存储的有效性。

8. 遇到的难点及解决方法
- 难点一:刻度校准与机械误差
- 问题:理论上的 0 刻度与实际传感器读数存在固定偏差,导致无法精确对准。
- 解决:引入全局变量
g_scale_offset。在调试阶段,先读取传感器在物理 0 位的数值,计算出偏差量填入代码,实现了软件层面的精准校准,无需反复拆装硬件和复位。
- 难点二:抖动与误判
- 问题:用户在接近目标刻度时手抖,导致数值在阈值边缘跳变,引发状态反复切换。
- 解决:增加了
SCALE_TOLERANCE(容差范围,设为 0.08)和REVERSE_PENALTY_THRESHOLD(回退惩罚阈值)。只有当稳定处于容差范围内才判定成功;且允许微小的往复抖动,只有明显的反向大动作才触发重置。
- 难点三:多圈数据的连续性处理
- 问题:ADMT4000 输出的是绝对圈数,但在计算“相对转动量”时,若直接相减可能因噪声产生跳变。
- 解决:在每一步开始时记录
g_step_start_turns作为基准,后续所有计算均基于current_turns - g_step_start_turns的差值,有效隔离了绝对位置噪声的影响。
- 难点四:刻度跨零计算
- 问题:当目标刻度为 0 或 9 附近时,简单的减法会导致误差计算错误(如 9.9 到 0.1 的实际误差很小,但直接相减很大)。
- 解决:在计算
scale_diff时加入逻辑判断:if (scale_diff > 5.0f) scale_diff = 10.0f - scale_diff;,正确处理了圆环刻度的最短路径问题。
9. 对本次活动的心得体会
首先,传感器选型决定上限。ADMT4000 强大的多圈计数能力是实现复杂密码逻辑的基础,如果仅用普通电位器或单圈编码器,该项目将变得复杂。
其次,状态机思维至关重要。面对复杂的交互逻辑(方向、圈数、刻度、断电恢复、自动重锁),采用清晰的状态机模型(锁定、验证圈数、验证刻度、解锁、重锁)让代码结构井然有序,避免了大量的 if-else 嵌套陷阱,提高了代码的可读性和可维护性。
最后,感谢本次竞赛的主办方及 ADI ,提供了如此宝贵的实践平台和像 ADMT4000 这样新颖的元器件支持。