1. 项目概述
这个基于STM32和RFID技术的员工打卡门禁系统,是我在电子科技大学本科毕业设计期间完成的一个嵌入式综合项目。它完美融合了硬件设计、嵌入式开发和物联网应用三大技术领域,实现了企业门禁管理的自动化与智能化。
系统采用STM32F103C8T6作为主控芯片,搭配RC522 RFID读写模块,通过读取员工卡片的唯一ID信息进行身份验证。验证成功后,系统会驱动电磁锁开启门禁,同时将打卡记录存储到本地SD卡中,并可通过串口上传至PC端管理软件。整套系统包含完整的硬件电路设计、嵌入式固件开发以及上位机数据管理功能,是一个典型的物联网终端应用案例。
提示:这个项目虽然作为毕业设计开发,但其完整性和实用性已经达到了商用级别。我在开发过程中特别注重系统的稳定性和抗干扰能力,所有电路设计都经过严格测试,确保在工业环境下也能可靠运行。
2. 系统架构设计
2.1 硬件组成框图
整个系统的硬件架构可以分为以下几个核心模块:
-
主控单元:STM32F103C8T6最小系统
- 72MHz Cortex-M3内核
- 64KB Flash + 20KB SRAM
- 丰富的外设接口(SPI/I2C/USART等)
-
RFID识别模块:RC522芯片方案
- 工作频率:13.56MHz
- 最大识别距离:5cm
- 支持ISO14443A协议
-
门禁执行机构:
- 12V电磁锁
- 继电器驱动电路
- 状态指示灯(红/绿LED)
-
数据存储模块:
- MicroSD卡(TF卡)接口
- FAT32文件系统支持
-
通信接口:
- USB转串口(CH340G)
- 预留WiFi/4G模块接口
-
电源管理:
- 12V DC输入
- LM2596降压至5V
- AMS1117-3.3V为MCU供电
2.2 软件架构设计
系统软件采用分层架构设计,主要分为以下几个层次:
-
硬件驱动层:
- HAL库硬件抽象
- SPI驱动(RC522)
- USART驱动
- GPIO控制
-
中间件层:
- FatFs文件系统
- RFID协议栈
- 实时时钟管理
-
应用逻辑层:
- 门禁控制状态机
- 用户管理模块
- 记录存储服务
-
通信协议层:
- 自定义串口协议
- 数据校验机制
- 错误恢复处理
3. 核心硬件设计详解
3.1 STM32最小系统设计
主控电路采用STM32F103C8T6这款性价比极高的Cortex-M3芯片,其最小系统设计要点包括:
-
时钟电路:
- 8MHz外部晶振+22pF负载电容
- 32.768kHz RTC晶振(可选)
-
复位电路:
- 10kΩ上拉电阻
- 100nF电容实现上电复位
- 手动复位按钮
-
调试接口:
- SWD四线调试接口
- 引出USART1用于打印调试信息
-
电源滤波:
- 每个电源引脚放置100nF去耦电容
- 模拟电源单独滤波(10μF+100nF)
注意:STM32的VDDA和VSSA必须连接,即使不使用ADC功能。我在第一版设计中忽略了这点,导致芯片工作不稳定。
3.2 RFID模块接口设计
RC522模块通过SPI接口与STM32通信,硬件连接要点:
code复制STM32 RC522
PA4 <-> SDA(CS)
PA5 <-> SCK
PA6 <-> MISO
PA7 <-> MOSI
GND <-> GND
3.3V <-> 3.3V
NRST <-> RST
关键设计考虑:
- SPI时钟不宜过高,实测8MHz以下最稳定
- 天线匹配电路需要精确调整,影响读卡距离
- 模块与天线之间距离尽量缩短
3.3 门禁驱动电路设计
电磁锁驱动电路采用继电器方案,主要考虑因素:
-
继电器选型:
- 线圈电压:5V
- 触点容量:10A/250VAC
- 型号:SRD-05VDC-SL-C
-
驱动电路:
- NPN三极管(如S8050)驱动
- 续流二极管(1N4007)保护
- LED状态指示
-
电源设计:
- 电磁锁单独12V供电
- 与控制系统电源隔离
- 大容量滤波电容(1000μF)
4. 嵌入式软件实现
4.1 开发环境搭建
-
工具链配置:
- Keil MDK-ARM V5
- STM32CubeMX初始化代码生成
- ST-Link V2调试器
-
工程结构:
code复制/Project
/Core # 核心外设驱动
/Drivers # HAL库文件
/Middlewares # FatFs等中间件
/Application # 业务逻辑代码
/User # 主程序入口
- 关键库依赖:
- STM32 HAL库
- RC522驱动库(移植修改版)
- FatFs R0.12b
4.2 RFID驱动实现
RC522驱动主要功能函数:
c复制// 初始化函数
void RFID_Init(void) {
HAL_GPIO_WritePin(RFID_CS_GPIO_Port, RFID_CS_Pin, GPIO_PIN_SET);
SPI1_Init();
RC522_Reset();
RC522_WriteRegister(0x2A, 0x8D); // 定时器设置
RC522_WriteRegister(0x2B, 0x3E);
RC522_WriteRegister(0x2D, 30);
RC522_AntennaOn(); // 开启天线
}
// 寻卡函数
uint8_t RFID_FindCard(uint8_t* id) {
uint8_t status;
status = RC522_Request(PICC_REQIDL, id);
if(status == MI_OK) {
status = RC522_Anticoll(id);
}
return status;
}
// 验证卡片
uint8_t RFID_CheckCard(uint8_t* id) {
uint8_t i;
for(i=0; i<user_count; i++) {
if(memcmp(id, user_list[i].uid, 4)==0) {
return 1; // 验证通过
}
}
return 0; // 验证失败
}
4.3 门禁控制逻辑
系统采用状态机设计模式,主要状态包括:
-
待机状态:
- 红灯常亮
- 周期性寻卡(约100ms一次)
- 低功耗模式(可选)
-
识别状态:
- 检测到卡片后黄灯闪烁
- 读取卡片UID
- 查询数据库
-
通过状态:
- 绿灯亮
- 继电器吸合2秒
- 记录打卡信息
-
拒绝状态:
- 红灯闪烁3次
- 蜂鸣器报警
- 记录非法尝试
状态转换逻辑代码示例:
c复制void DoorControl_FSM(void) {
static uint8_t state = STANDBY_STATE;
uint8_t card_id[4];
switch(state) {
case STANDBY_STATE:
if(RFID_FindCard(card_id) == MI_OK) {
state = VERIFY_STATE;
LED_Yellow_Blink(3);
}
break;
case VERIFY_STATE:
if(RFID_CheckCard(card_id)) {
state = GRANT_STATE;
Door_Open();
Log_Record(card_id, 1);
} else {
state = DENY_STATE;
Log_Record(card_id, 0);
}
break;
case GRANT_STATE:
if(Door_Timeout()) {
state = STANDBY_STATE;
}
break;
case DENY_STATE:
if(Alarm_Timeout()) {
state = STANDBY_STATE;
}
break;
}
}
5. 数据存储与管理
5.1 SD卡存储实现
采用FatFs文件系统管理打卡记录,关键实现步骤:
- 初始化序列:
c复制FATFS fs;
FIL file;
FRESULT res;
res = f_mount(&fs, "", 1); // 挂载文件系统
if(res != FR_OK) {
Error_Handler();
}
res = f_open(&file, "log.csv", FA_OPEN_ALWAYS | FA_WRITE);
if(res != FR_OK) {
Error_Handler();
}
f_lseek(&file, f_size(&file)); // 定位到文件末尾
- 记录格式设计:
code复制时间戳,卡号(HEX),验证结果,备注
2023-05-20 08:30:15,5A3B8C7D,1,正常打卡
2023-05-20 12:01:30,5A3B8C7D,1,午休返回
2023-05-20 13:45:22,9F2E1D4C,0,非法卡片
- 写入函数实现:
c复制void Log_Record(uint8_t* id, uint8_t result) {
char buffer[64];
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sprintf(buffer, "%04d-%02d-%02d %02d:%02d:%02d,%02X%02X%02X%02X,%d,\n",
sDate.Year+2000, sDate.Month, sDate.Date,
sTime.Hours, sTime.Minutes, sTime.Seconds,
id[0], id[1], id[2], id[3], result);
UINT bw;
f_write(&file, buffer, strlen(buffer), &bw);
f_sync(&file);
}
5.2 上位机通信协议
系统定义了一套简单的串口协议用于与PC端通信:
- 协议帧格式:
code复制[HEAD][LEN][CMD][DATA][CRC][TAIL]
0xAA 1B 1B N 2B 0x55
- 主要命令字:
- 0x01: 上传单条记录
- 0x02: 请求同步所有记录
- 0x03: 添加新用户
- 0x04: 删除用户
- 0x05: 系统时间同步
- 数据交互示例:
c复制// 发送记录示例
void Send_Record(uint8_t* id, uint8_t result) {
uint8_t frame[20];
uint16_t crc;
frame[0] = 0xAA; // HEAD
frame[1] = 0x0E; // LEN
frame[2] = 0x01; // CMD
memcpy(&frame[3], id, 4); // DATA
frame[7] = result;
// 添加时间戳...
crc = CRC16_Calculate(frame, 8);
frame[8] = crc >> 8;
frame[9] = crc & 0xFF;
frame[10] = 0x55; // TAIL
HAL_UART_Transmit(&huart1, frame, 11, 100);
}
6. 系统优化与调试
6.1 读卡性能优化
在实际测试中发现读卡成功率受以下因素影响较大:
-
天线调谐:
- 使用网络分析仪测量天线谐振频率
- 调整匹配电容C1、C2(典型值27pF)
- 目标谐振点:13.56MHz±100kHz
-
软件防冲突:
c复制uint8_t RFID_Anticoll(uint8_t* id) {
uint8_t status;
uint8_t i;
uint8_t check = 0;
RC522_WriteRegister(BitFramingReg, 0x00);
status = RC522_ToCard(PCD_TRANSCEIVE, 0x93, 0x20, id, &i);
if(status == MI_OK) {
for(i=0; i<4; i++) {
check ^= id[i];
}
if(check != 0) {
status = MI_ERR;
}
}
RC522_WriteRegister(CollReg, 0x00);
return status;
}
- 环境干扰处理:
- 增加金属屏蔽罩
- 调整天线与金属物体的距离(>5cm)
- 软件上增加重试机制(3次重试)
6.2 低功耗设计
针对电池供电场景的优化措施:
-
硬件层面:
- 选用低功耗LDO(如TPS78233)
- 未使用外设时钟关闭
- 电磁锁采用脉冲驱动方式
-
软件层面:
c复制void Enter_LowPowerMode(void) {
HAL_GPIO_WritePin(RFID_PWR_GPIO_Port, RFID_PWR_Pin, GPIO_PIN_RESET);
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 5, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
SystemClock_Config(); // 唤醒后重新配置时钟
HAL_GPIO_WritePin(RFID_PWR_GPIO_Port, RFID_PWR_Pin, GPIO_PIN_SET);
}
- 实测功耗数据:
- 运行模式:25mA @3.3V
- 待机模式:3.5mA
- 休眠模式:120μA
7. 常见问题与解决方案
7.1 硬件相关问题
-
RC522读卡距离短:
- 检查天线匹配电路
- 确认电源电压稳定(3.3V±5%)
- 调整天线形状和位置
-
电磁锁无法保持:
- 检查继电器触点是否烧蚀
- 测量锁体电阻(正常约30Ω)
- 确保电源功率足够(12V/1A以上)
-
SD卡识别失败:
- 检查SPI模式(需SD模式)
- 确认卡格式化为FAT32
- 降低SPI时钟速度(初期用400kHz)
7.2 软件相关问题
- FatFs文件系统挂载失败:
c复制FRESULT res = f_mount(&fs, "", 1);
if(res != FR_OK) {
// 常见错误处理:
if(res == FR_NO_FILESYSTEM) {
// 需要格式化
MKFS_PARM opt = {FM_FAT32, 0, 0, 0, 0};
f_mkfs("", &opt, work, sizeof(work));
}
}
-
RFID频繁误识别:
- 增加防冲突算法
- 软件滤波(连续3次识别相同才确认)
- 调整RF场强度(修改TxControlReg)
-
系统死机问题:
- 启用看门狗(IWDG)
- 堆栈空间检查(启动文件调整)
- 关键操作加异常捕获
7.3 开发调试技巧
- 日志调试法:
c复制#define DEBUG_LOG(fmt, ...) \
do { \
printf("[%s] " fmt "\n", __TIME__, ##__VA_ARGS__); \
} while(0)
// 使用示例
DEBUG_LOG("Card detected: %02X%02X%02X%02X", id[0],id[1],id[2],id[3]);
- 内存使用监控:
c复制void Print_MemUsage(void) {
extern int _end;
extern int _estack;
extern int __Stack_Size;
uint32_t stack_used = (uint32_t)&_estack - __get_MSP();
uint32_t heap_used = (uint32_t)sbrk(0) - (uint32_t)&_end;
printf("Stack used: %lu/%lu\n", stack_used, (uint32_t)&__Stack_Size);
printf("Heap used: %lu bytes\n", heap_used);
}
- 性能分析技巧:
c复制#define PROFILING_START() uint32_t start_time = DWT->CYCCNT
#define PROFILING_END(tag) \
do { \
uint32_t cycles = DWT->CYCCNT - start_time; \
printf("[%s] cycles: %lu, us: %.2f\n", \
tag, cycles, cycles/72.0); \
} while(0)
// 使用前需启用DWT
void DWT_Init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
