电赛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   │
                                    └──────────┘  └──────────┘

整套系统分三部分:

  1. 钥匙端:一块BearPi-Pico H3863,通过星闪广播身份ID,一块STM32最小系统板加DWM1000模块当作标签
  2. 门锁端:一块BearPi-Pico H3863,接收ID + 双UART收测距数据 + OLED显示 + LED/蜂鸣器
  3. UWB锚点:4个STM32+DWM1000模块,分布在不同位置做测距,测完通过串口把距离发给门锁

为什么不用H3863直接驱动DWM1000?一开始我也试过,但H3863的SPI跟DW1000配合时序有问题,我一直没有移植成功。后来改成STM32专门负责UWB测距,通过串口把结果发给H3863处理,效果稳定多了


身份识别:星闪SLE

为什么选星闪?

题目要求"身份识别",传统方案是BLE蓝牙。但BearPi-Pico H3863原生支持星闪SLE(SparkLink Edge),这是华为主推的新一代短距通信技术,比BLE延迟更低、功耗更低

而且SDK里自带SSAP协议栈,开发起来跟BLE GATT很像,上手很快

通信流程

复制代码
钥匙端 (Server)                     门锁端 (Client)
     │                                   │
     │  ◄── 广播 "diansai2_key" ──────  │ 扫描
     │  ── 连接成功 ─────────────────►  │
     │  ── 配对完成 ─────────────────►  │
     │  ── MTU 交换 (520) ────────────► │
     │  ── 服务发现 ──────────────────► │
     │  ── Property Handle ──────────►  │
     │                                   │
     │  ── Notify ID (0x0A) ─────────►  │  每500ms
     │  ── Notify ID ────────────────►  │

钥匙端做Server,注册一个Service(UUID 0x2828)和一个Property(UUID 0x2929),配对完成后每500ms通过Notify推送一次ID。

门锁端做Client,扫描发现钥匙后连接、配对、服务发现,然后等着收Notify

ID校验

钥匙发送的ID是4-bit(0~15),门锁端用拨码开关设置期望ID,收到后比对:

复制代码
if (received_id == expected_id):
    验证通过 -> 允许解锁
else:
    验证失败 -> 保持锁定

OLED上会实时显示接收ID和期望ID,以及PASS/FAIL结果


UWB测距:4锚点DS-TWR

DS-TWR双向测距原理

UWB测距用的是DS-TWR(Double-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、F
  • UART2 收锚点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_pred

Q越大越相信测量值,R越大越相信预测值。0.05和0.1是我反复调试出来的参数

野值剔除

卡尔曼滤波能平滑小噪声,但对大跳变无能为力。所以加了一层野值剔除:

复制代码
if |测量值 - 估计值| > 50cm:
    用估计值替代,连续拒绝计数+1
    if 连续拒绝 > 5次:
        接受新值(强制重置,防止卡死)
else:
    正常卡尔曼更新

50cm阈值和5次限制是经验值,太小会误杀正常值,太大会让噪声通过。

坐标解算

有了4个锚点的滤波后距离,怎么算位置?

用的是差分平方法,不需要解非线性方程,计算量小:

复制代码
x = (d_G² - d_E²) / (2L)     (L=60cm, 2L=120)
y = (d_H² - d_F²) / (2L)

原理很简单:如果G和E距离相等,x=0;如果离G更近,x>0。平方差正好消了距离的常数项

径向距离

坐标x,y算出来后,径向距离:

复制代码
avg = (d_E + d_F + d_G + d_H) / 4
r = (avg - 61.0) / 1.105

61.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字节。直接写在头文件里,编译时写死到Flash


RTOS任务架构

门锁端跑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_cmg_angle等),简单粗暴但有效


踩过的坑

1. H3863直接驱动DW1000时序不稳

一开始想用H3863的SPI直接驱动DW1000,但是SPI程序一直没有移植成功

解决方案:改用STM32专门驱动DW1000,测距完成后串口把距离发给H3863

2. 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 H3863 2 钥匙 + 门锁
STM32F103 + DW1000 5 标签,UWB锚点 E/F/G/H
SSD1306 0.96" OLED 1 门锁显示
LED 红/绿/黄 3 状态指示
有源蜂鸣器 1 事件提示
4位拨码开关 2 ID设置

主控是Hi3863,RISC-V 32bit,240MHz,集成Wi-Fi6/BLE/星闪SLE,606KB SRAM,4MB 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(署名-非商业性使用-相同方式共享)

可以学习、研究、修改、分享,但不能商用,衍生作品也要用同样的许可证


总结

这个项目几个关键设计决策:

  1. 星闪SLE替代BLE:新技术尝鲜,延迟更低,SDK好用
  2. STM32+H3863架构分离:UWB测距交给STM32,H3863专注通信和逻辑
  3. 多级滤波:卡尔曼+野值剔除,两层防护
  4. 差分平方定位:计算量小,不需要解非线性方程
  5. 事件驱动状态机:蜂鸣器只在区域变化时响,不吵

做电赛最大的收获不是最后的结果,而是踩坑的过程。UWB时序、卡尔曼调参、角度环绕、OLED花屏......每个坑都花了不少时间,但解决之后对整个系统的理解会深很多

希望这篇分享对大家有帮助,代码已经开源,欢迎交流


本文项目开源地址:https://gitcode.com/gcw_rVSV2mp6/diansai-C

许可证:CC BY-NC-SA 4.0(禁止商用)

相关推荐
NutShell Wang1 小时前
中国大模型“八周五连发“:开源与低成本重写全球选型
开源·ai agent·vibe coding
凌云拓界2 小时前
NodeVerdict:Node.js 原生诊断数据可视化工具
信息可视化·架构·typescript·开源·node.js·github·bug
不正经学生2 小时前
C语言指针进阶:const 和野指针——给指针加锁,向野指针宣战
c语言·开发语言·c++·算法·c#
云端漫步19872 小时前
HarmonyOS NEXT AI 智能生活助手:AI 日程规划
人工智能·华为·生活·harmonyos
云卷云舒___________3 小时前
MiniMax H3全模态视频模型屠榜、字节跳动Seedance 2.5紧急推出、华为开源盘古2.0 Pro | 8月1日 AI日报
华为·字节跳动·minimax·ai日报·h3·seedance2.5·盘古20pro
多弗朗皮卡丘4 小时前
C语言梦开始的地方3
c语言·开发语言
破晓单片机4 小时前
093、STM32项目分享开源:蓝牙智能晾衣架系统
stm32·单片机·嵌入式硬件·开源
达子6665 小时前
第25章_HarmonyOs开发图解之 电话服务
华为·harmonyos
勤劳X码农5 小时前
2026年配音软件技术选型:7款TTS方案从轻量到API全参数对比
开源