免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

电赛C题:我用星闪+UWB做了一个数字钥匙门锁系统,开源了

电赛C题:我用星闪+UWB做了一个数字钥匙门锁系统,开源了 电赛C题我用星闪UWB做了一个数字钥匙门锁系统开源了前言今年电赛C题要求做一套数字钥匙系统——钥匙能自动识别身份门锁能判断钥匙距离在范围内自动解锁说实话这题不算难但要做得好不容易。身份识别用什么通信方案测距怎么保证精度怎么滤掉噪声区域判断的状态机怎么设计我的方案是星闪SLE做身份识别 4锚点UWB做厘米级定位 卡尔曼滤波平滑数据 三级区域状态机控制门锁最终效果钥匙靠近2米黄灯欢迎1米内绿灯解锁离开自动上锁全程OLED实时显示距离和角度。距离误差均为5cm内角度误差7度内很遗憾今年电赛我们因为一些原因失利了但是还是决定将项目分享出来希望能够帮助到大家代码已开源https://gitcode.com/gcw_rVSV2mp6/diansai-C下面分享一下整体思路和踩过的坑系统架构先放一张整体架构图星闪 SLE 通信 ┌────────────┐ ID 广播 Notify ┌─────────────────────┐ │ 钥匙 KEY │ ◄──────────────────────► │ 门锁 LOCK │ │ (H3863) │ UUID: 0x2828/0x2929 │ (H3863) │ │ │ │ SLE / UART×2 / OLED │ └────────────┘ │ LED×3 / 蜂鸣器 │ └─────────────────────┘ ▲ ▲ UART1 │ │ UART2 ▼ ▼ ┌──────────┐ ┌──────────┐ │ 锚点 E │ │ 锚点 G │ │ 锚点 F │ │ 锚点 H │ │ STM32 │ │ STM32 │ │ DW1000 │ │ DW1000 │ └──────────┘ └──────────┘整套系统分三部分钥匙端一块BearPi-Pico H3863通过星闪广播身份ID一块STM32最小系统板加DWM1000模块当作标签门锁端一块BearPi-Pico H3863接收ID 双UART收测距数据 OLED显示 LED/蜂鸣器UWB锚点4个STM32DWM1000模块分布在不同位置做测距测完通过串口把距离发给门锁为什么不用H3863直接驱动DWM1000一开始我也试过但H3863的SPI跟DW1000配合时序有问题我一直没有移植成功。后来改成STM32专门负责UWB测距通过串口把结果发给H3863处理效果稳定多了身份识别星闪SLE为什么选星闪题目要求身份识别传统方案是BLE蓝牙。但BearPi-Pico H3863原生支持星闪SLESparkLink Edge这是华为主推的新一代短距通信技术比BLE延迟更低、功耗更低而且SDK里自带SSAP协议栈开发起来跟BLE GATT很像上手很快通信流程钥匙端 (Server) 门锁端 (Client) │ │ │ ◄── 广播 diansai2_key ────── │ 扫描 │ ── 连接成功 ─────────────────► │ │ ── 配对完成 ─────────────────► │ │ ── MTU 交换 (520) ────────────► │ │ ── 服务发现 ──────────────────► │ │ ── Property Handle ──────────► │ │ │ │ ── Notify ID (0x0A) ─────────► │ 每500ms │ ── Notify ID ────────────────► │钥匙端做Server注册一个ServiceUUID 0x2828和一个PropertyUUID 0x2929配对完成后每500ms通过Notify推送一次ID。门锁端做Client扫描发现钥匙后连接、配对、服务发现然后等着收NotifyID校验钥匙发送的ID是4-bit0~15门锁端用拨码开关设置期望ID收到后比对if (received_id expected_id): 验证通过 - 允许解锁 else: 验证失败 - 保持锁定OLED上会实时显示接收ID和期望ID以及PASS/FAIL结果UWB测距4锚点DS-TWRDS-TWR双向测距原理UWB测距用的是DS-TWRDouble-Sided Two-Way Ranging算法分三步Tag (标签) Anchor (锚点) │ │ │── Poll 包 ──────────────────────►│ T1 发送, T2 接收 │◄── Response 包 ──────────────────│ T3 发送, T4 接收 │── Final 包 (含T1,T4,T5) ────────►│ T5 发送, T6 接收 │ │ │ 锚点根据6个时间戳计算TOF │飞行时间计算公式Ra T4 - T1 (Round 1) Rb T6 - T3 (Round 2) Da T5 - T4 (Reply 2) Db T3 - T2 (Reply 1) TOF (Ra×Rb - Da×Db) / (Ra Rb Da Db) 距离 TOF × 光速这个公式巧妙地消除了时钟偏移误差不需要双方时钟同步4锚点轮询标签端不是只跟一个锚点测距而是轮询4个锚点E→F→G→H→E…每个锚点独立测距通过串口输出E D:0.56 F D:1.23 G D:0.89 H D:2.34门锁端用两个UART并行接收UART1 收锚点E、FUART2 收锚点G、H锚点布局60cm E ┌──────────┐ G │ │ │ │ 60cm │ │ F └──────────┘ H定位算法从测距到坐标卡尔曼滤波UWB测距有噪声直接用会跳得很厉害。每个锚点独立维护一个卡尔曼滤波器预测: p_pred p_est Q (Q 0.05) 更新: K p_pred / (p_pred R) (R 0.1) x_est x_est K × (测量值 - x_est) p_est (1 - K) × p_predQ越大越相信测量值R越大越相信预测值。0.05和0.1是我反复调试出来的参数野值剔除卡尔曼滤波能平滑小噪声但对大跳变无能为力。所以加了一层野值剔除if |测量值 - 估计值| 50cm: 用估计值替代连续拒绝计数1 if 连续拒绝 5次: 接受新值强制重置防止卡死 else: 正常卡尔曼更新50cm阈值和5次限制是经验值太小会误杀正常值太大会让噪声通过。坐标解算有了4个锚点的滤波后距离怎么算位置用的是差分平方法不需要解非线性方程计算量小x (d_G² - d_E²) / (2L) (L60cm, 2L120) y (d_H² - d_F²) / (2L)原理很简单如果G和E距离相等x0如果离G更近x0。平方差正好消了距离的常数项径向距离坐标x,y算出来后径向距离avg (d_E d_F d_G d_H) / 4 r (avg - 61.0) / 1.10561.0和1.105是实测校准值——在已知距离点采样拟合出来的零偏和缩放系数。不同硬件需要重新标定方位角angle -atan2(y, x) × (180/π) - 125°-125°是锚点布局的安装偏移跟实际摆放方向有关。角度也做了指数平滑α0.3并且处理了360°环绕问题diff new_angle - old_angle if diff 180: diff - 360 if diff -180: diff 360 smooth 0.3 × diff区域状态机距离算好了接下来是区域判断。分三级距离 1m ┌──────────┐ ─────────────► ┌──────────┐ │ SENSING │ │ UNLOCK │ │ (红灯) │ ◄───────────── │ (绿灯) │ └──────────┘ 距离 1m └──────────┘ │ │ 距离 2m ▼ ┌──────────┐ │ WELCOME │ │ (黄灯) │ └──────────┘区域距离LED蜂鸣器解锁区 1.0m绿灯短响100ms欢迎区 2.0m黄灯长响200ms感知区 2.0m红灯-关键细节只有ID验证通过才会解锁。如果ID不匹配不管多近都是红灯锁定。事件驱动蜂鸣器蜂鸣器不是一直响而是区域变化时才响进入解锁区 → 短响离开解锁区 → 短响进入欢迎区 → 长响离开欢迎区 → 长响这样不会太吵但每次状态变化用户都能感知到。OLED显示门锁端有一块0.96寸SSD1306 OLED用软件I2C驱动因为硬件I2C被占用了显示内容扫描中SLE: Scanning... Exp: b0000 Key ID: ---- Zone: ----已连接Key:b1010 Exp:b0000 Ver: PASS Dist:1.23m A:4.5 Zone: WELCOME Event: Enter Welc Hrz:1.53m Status: WELCOME第一行同时显示接收ID和期望ID的二进制第二行PASS/FAIL第三行距离和角度第四行区域第五行事件第六行水平距离第七行状态。6x8字体8行显示信息密度刚刚好字库字库是自己做的6x8点阵95个ASCII字符每个6字节一共570字节。直接写在头文件里编译时写死到FlashRTOS任务架构门锁端跑LiteOS两个并行任务┌─ Diansai2Lock (优先级 17) ──────────┐ │ · SLE Client 扫描/连接/配对 │ │ · 接收 ID DIP 校验 │ │ · OLED 刷新 (200ms) │ │ · LED/蜂鸣器控制 │ │ · 区域判断 状态机 │ └────────────────────────────────────┘ ┌─ Diansai2LockDist (优先级 15) ──────┐ │ · UART1 接收锚点 E/F (115200) │ │ · UART2 接收锚点 G/H (115200) │ │ · 解析 X D:距离 协议 │ │ · 卡尔曼滤波 (每锚点独立) │ │ · 野值剔除 │ │ · 径向距离 方位角计算 │ └────────────────────────────────────┘Lock任务优先级高一点17因为身份识别和状态控制的实时性要求更高。Dist任务优先级15测距数据有一定的容忍度晚几十毫秒没关系。两个任务通过全局变量通信g_distance_cm、g_angle等简单粗暴但有效踩过的坑1. H3863直接驱动DW1000时序不稳一开始想用H3863的SPI直接驱动DW1000但是SPI程序一直没有移植成功解决方案改用STM32专门驱动DW1000测距完成后串口把距离发给H38632. UWB测距跳变DW1000测距偶尔会跳一下比如实际1米突然报5米。卡尔曼滤波能平滑小波动但对这种大跳变没用解决方案加了野值剔除层偏差超过50cm的测量值直接丢弃用上一次的估计值替代。连续丢弃5次后才强制接受新值防止滤波器卡死3. 角度跳变角度在±180°附近会突然跳变比如从179°跳到-179°普通的指数平滑会把这个跳变放大解决方案做平滑前先处理环绕diff new - old if diff 180: diff - 360 if diff -180: diff 360 smooth α × diff硬件清单器件数量角色BearPi-Pico H38632钥匙 门锁STM32F103 DW10005标签UWB锚点 E/F/G/HSSD1306 0.96 OLED1门锁显示LED 红/绿/黄3状态指示有源蜂鸣器1事件提示4位拨码开关2ID设置主控是Hi3863RISC-V 32bit240MHz集成Wi-Fi6/BLE/星闪SLE606KB SRAM4MB Flash。跑LiteOS实时操作系统代码结构diansai2/ ├── README.md # 项目文档 ├── LICENSE # CC BY-NC-SA 4.0 ├── diansai2.h # 公共定义 ├── diansai2_key.c # 钥匙端SLE广播 ID Notify ├── diansai2_lock.c # 门锁端SLE接收 UART测距 卡尔曼 OLED 状态机 ├── diansai2_oled.c/.h # SSD1306软件I2C驱动 ├── diansai2_oled_font.h # 6x8点阵字库 ├── CMakeLists.txt # 构建配置 ├── Kconfig # 引脚配置 └── stm32_uwb/ # STM32 UWB测距固件 ├── 标签/ # Tag固件轮询4锚点 └── 基座/ # Anchor固件测距UART输出开源项目地址https://gitcode.com/gcw_rVSV2mp6/diansai-C硬件开源地址2026电赛C题-小熊派解决方案 - 立创开源硬件平台包含H3863钥匙端门锁端完整固件STM32 UWB标签锚点完整固件含DW1000驱动README文档 项目介绍许可证CC BY-NC-SA 4.0署名-非商业性使用-相同方式共享可以学习、研究、修改、分享但不能商用衍生作品也要用同样的许可证总结这个项目几个关键设计决策星闪SLE替代BLE新技术尝鲜延迟更低SDK好用STM32H3863架构分离UWB测距交给STM32H3863专注通信和逻辑多级滤波卡尔曼野值剔除两层防护差分平方定位计算量小不需要解非线性方程事件驱动状态机蜂鸣器只在区域变化时响不吵做电赛最大的收获不是最后的结果而是踩坑的过程。UWB时序、卡尔曼调参、角度环绕、OLED花屏……每个坑都花了不少时间但解决之后对整个系统的理解会深很多希望这篇分享对大家有帮助代码已经开源欢迎交流本文项目开源地址https://gitcode.com/gcw_rVSV2mp6/diansai-C许可证CC BY-NC-SA 4.0禁止商用
返回列表