1. 项目概述
这个基于STM32的二维码门禁系统是我去年为一个智能小区项目开发的解决方案。当时物业公司提出了三个核心需求:低成本改造现有门禁、支持移动端授权管理、防止二维码被拍照冒用。经过两个月的开发和实测,最终方案在保证安全性的前提下,将传统IC卡门禁的改造成本降低了60%。
整套系统最让我自豪的是它的"三防"特性:防复制(动态二维码)、防截屏(时间戳校验)、防暴力破解(STM32硬件加密)。现在每次路过那个小区,看到居民用手机扫码进出的场景,都会想起调试时熬过的那些夜。下面我就把这套经过实战检验的方案拆解给大家,包括硬件选型思路、核心算法实现和那些只有踩过坑才知道的注意事项。
2. 系统架构设计
2.1 硬件组成框图
系统采用经典的"传感-控制-执行"三层架构:
code复制[手机APP] --WiFi--> [云服务器] --4G-->
[STM32F103C8T6] --RS485--> [电磁锁]
|__GPIO__[蜂鸣器]
|__UART__[LCD屏]
主控选择STM32F103C8T6的三大理由:
- 性价比:零售价仅12元却自带硬件加密功能
- 生态完善:有成熟的二维码解码库(LibQR)移植案例
- 扩展性强:预留的USART接口可对接人脸识别模块
注意:一定要选带硬件加密引擎的型号(如F103/F407),软件加密在门禁场景下存在被嗅探风险。
2.2 核心功能逻辑
系统工作流程包含三个关键状态机:
- 解码验证状态机(50ms周期)
- 网络同步状态机(30分钟周期)
- 异常处理状态机(事件触发)
c复制// 伪代码示例
void QR_Handler(void) {
if(QR_ValidPeriodCheck() == PASS) {
if(QR_Decode() == PASS) {
if(AES_Decrypt() == PASS) {
Door_Open();
Log_Upload();
}
}
}
Error_Handler();
}
3. 关键模块实现
3.1 动态二维码生成算法
为防止二维码被拍照复用,我们设计了时间戳嵌套方案:
code复制有效载荷 = AES128(用户ID + 有效时段 + 随机数)
动态二维码 = Base64(有效载荷) + CRC16校验
在STM32端的验证逻辑:
c复制uint8_t QR_Verify(uint8_t* qr_data) {
uint32_t current_time = RTC_GetTime();
uint32_t qr_time = *(uint32_t*)&qr_data[8];
// 时间窗口设为±3分钟
if(abs(current_time - qr_time) > 180) {
return TIME_OUT;
}
...
}
实测中发现的两个坑:
- 必须用硬件RTC,软件计时器会有累积误差
- 时间同步建议采用NTP over 4G模块(SIM800C)
3.2 低功耗设计技巧
为延长电池供电时的续航(项目要求停电工作8小时):
- 采用状态机轮询替代RTOS(减少上下文切换损耗)
- 动态调整主频:正常模式72MHz → 待机模式8MHz
- 电磁锁驱动改用MOSFET(IRLZ44N比继电器省电60%)
实测电流对比:
| 模式 | 原方案 | 优化后 |
|---|---|---|
| 待机 | 15mA | 3.2mA |
| 扫码处理 | 85mA | 62mA |
| 开锁 | 450mA | 380mA |
4. 开发环境搭建
4.1 工具链配置
推荐使用这套验证过的组合:
- IDE:Keil MDK 5.30(注册机问题自行解决)
- 调试器:J-Link EDU(山寨版会出现校验错误)
- 串口工具:SecureCRT 8.5(配置流控防数据丢失)
关键编译器优化选项:
code复制-Oz -flto -ffunction-sections
4.2 外设驱动移植
二维码扫描头(我用的型号是HX-QR30)需要特别注意:
- 供电时序:先开3.3V,500ms后再开1.8V IO电源
- 图像传输:采用DMA+双缓冲模式(示例代码)
c复制void QR_DMA_Config(void) {
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)QR_Buffer0;
DMA_InitStructure.DMA_BufferSize = QR_BUF_SIZE;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
}
5. 安全机制实现
5.1 防重放攻击方案
采用滚动码+双向验证机制:
- 手机APP生成带序列号的二维码
- 门禁机验证后返回确认信号
- 服务器记录最后有效序列号
mermaid复制sequenceDiagram
participant App
participant Server
participant Device
App->>Server: 请求动态码(序列号N)
Server->>App: 返回AES(信息+N)
App->>Device: 展示二维码
Device->>Server: 验证N>N_last?
Server->>Device: 确认/拒绝
5.2 硬件加密配置
STM32的AES单元需要特殊初始化:
c复制void AES_Config(void) {
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_AES, ENABLE);
AES_DeInit();
AES_InitStructure.AES_Operation = AES_Operation_Encrypt;
AES_InitStructure.AES_Chaining = AES_Chaining_ECB;
AES_InitStructure.AES_KeySize = AES_KeySize_128;
AES_Init(&AES_InitStructure);
// 关键!必须填充密钥寄存器
AES_WriteSubKey(Key);
}
6. 实测问题汇总
6.1 扫码失败排查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 解码成功但验证失败 | RTC电池没电 | 更换CR2032电池 |
| 偶尔识别不出二维码 | 环境光干扰 | 增加光学滤光片 |
| 网络同步经常超时 | SIM卡APN设置错误 | 修改为"CMNET" |
| 开锁后立即重新上锁 | 反馈信号线接触不良 | 改用镀金排针 |
6.2 电磁兼容问题
在首批安装的10台设备中,有3台出现了以下问题:
- 现象:扫码时LCD显示乱码
- 定位:电磁锁动作时电源跌落至4.2V
- 解决方案:
- 在锁电源端并联1000μF电容
- 给STM32增加LC滤波电路
- 改用带缓存的LCD驱动芯片(SSD1306)
7. 项目优化方向
这套系统在实际部署后,我又发现了三个可以改进的点:
-
离线模式增强:当网络中断时,目前会完全拒绝通行。可以改为:
- 白名单用户允许离线验证(需预存密钥)
- 记录离线事件待网络恢复后上传
-
功耗再优化:通过实测发现:
- 扫码头待机电流仍有降低空间(现3.2mA)
- 考虑改用OV7725+软件解码方案
-
防尾随设计:增加:
- 红外对管检测人员通过速度
- 当检测到异常时触发声光报警
最后分享一个只有踩过坑才知道的经验:在批量生产时,一定要先烧录加密选项字节再下载程序,否则会出现部分芯片无法启用硬件加密的情况。我在第一批100套订单里因此返工了23块板子,这个教训值5000块钱。
