尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32+RFID图书管理系统:闭环嵌入式设计与Flash本地存储实现

STM32+RFID图书管理系统:闭环嵌入式设计与Flash本地存储实现 简介本资源是一套基于STM32与RFID技术的物联网图书管理系统完整毕业设计实现面向嵌入式初学者、本科毕设/课设学生及实训项目开发者解决图书借还识别、信息本地化管理与硬件联动控制等典型物联网应用场景问题。压缩包共229个文件含48个C源码如stm32f10x_tim.c、cJSON.c、46个头文件.h、31个Java后端模块、22个编译中间文件.o/.d及Keil工程配置文件.uvprojx/.sct覆盖MCU底层驱动、RFID通信协议解析、串口交互与系统逻辑实现整体包体仅4.45MB轻量易部署。已有194人学习下载资源经实测可直接编译烧录运行附完整工程文件、说明文档及hex固件支持面包板快速搭建验证——无需PCB设计基础按引脚定义连线烧录即可复现全部功能特别适合硬件实践薄弱但需交付成果的学习者高效上手与二次扩展。1. 基于STM32RFID的图书管理嵌入式系统不是Demo是可部署的闭环硬件逻辑你手头那块刚点亮的STM32F103C8T6最小系统板接上MFRC522读卡模块、OLED屏和串口调试器烧进这份源码——三分钟内就能刷出“卡号04 F8 1A 2B书名《嵌入式实时系统设计》”的实时响应。这不是教学演示而是一套完整闭环的物联网终端逻辑RFID标签触发→MCU解析UID→本地Flash查表匹配书目→OLED显示USART透传至PC端日志→支持借还状态双态切换。它绕开了云平台依赖所有业务逻辑固化在片上无RTOS调度开销无网络协议栈拖累连cJSON解析都只用于PC端日志结构化非设备端。适合毕设答辩现场稳定运行也适合作为课设基线——当你需要向导师证明“我真懂中断怎么配、Flash怎么分页写、RFID防冲突怎么调”而不是只会跑个LED流水灯。2. STM32F103底层驱动与RFID通信协议栈实现2.1 硬件资源映射与外设初始化策略本项目采用STM32F103C8T6作为主控其资源约束决定了驱动设计必须精简高效。关键外设分配如下外设模块GPIO引脚功能说明初始化要点SPI1PA5(SCK), PA6(MISO), PA7(MOSI)连接MFRC522时钟频率≤10MHzMFRC522最大支持CPOL0, CPHA0USART1PA9(TX), PA10(RX)调试/PC通信波特率115200无校验位1停止位GPIO输出PB0~PB3OLED控制线DC, RST, CS推挽输出上拉使能EXTI0PA0MFRC522中断引脚下降沿触发优先级设为最高提示PA0作为EXTI0中断源需在stm32f10x_exti.c中显式使能AFIO时钟并配置GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0)。若跳过此步MFRC522的IRQ引脚将无法触发中断导致轮询模式失效——这是新手烧录后“读卡无反应”的最常见原因。初始化代码核心段main.c// 配置SPI1为MFRC522服务 RCC-APB2ENR | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_GPIOAEN | RCC_APB2ENR_GPIOBEN; GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; // PA5/6/7复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // SPI1初始化 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; // 空闲时钟低电平 SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; // 第一个边沿采样 SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 72MHz/418MHz → 实际限频10MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE);2.1.1 MFRC522寄存器操作封装原理MFRC522并非标准SPI从设备其寄存器访问需遵循特定时序地址字节高7位为寄存器地址第0位为RW标志0写1读。驱动层通过MFRC522_WriteRegister()和MFRC522_ReadRegister()封装底层SPI读写关键在于地址掩码处理// 写寄存器addr为实际寄存器地址0x00~0x3F void MFRC522_WriteRegister(uint8_t addr, uint8_t val) { uint8_t tx_buf[2] {addr 0x7E, val}; // 地址低7位RW0 → 0x7E掩码清零bit0 GPIO_ResetBits(GPIOB, GPIO_Pin_3); // CS拉低 SPI_I2S_SendData(SPI1, tx_buf[0]); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, tx_buf[1]); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); GPIO_SetBits(GPIOB, GPIO_Pin_3); // CS拉高 } // 读寄存器返回寄存器当前值 uint8_t MFRC522_ReadRegister(uint8_t addr) { uint8_t tx_buf[2] {(addr 0x7E) | 0x80, 0x00}; // 地址|0x80表示读操作 uint8_t rx_buf[2]; GPIO_ResetBits(GPIOB, GPIO_Pin_3); SPI_I2S_SendData(SPI1, tx_buf[0]); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, tx_buf[1]); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); rx_buf[0] SPI_I2S_ReceiveData(SPI1); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); rx_buf[1] SPI_I2S_ReceiveData(SPI1); GPIO_SetBits(GPIOB, GPIO_Pin_3); return rx_buf[1]; // 第二个字节为读回数据 }注意addr 0x7E确保地址位不干扰RW位| 0x80强制设置读标志。若直接传入addr | 0x80而不做掩码当addr本身bit7为1时如0x80会超出MFRC522有效地址范围0x00-0x3F导致寄存器访问失败。2.2 RFID防冲突与UID解析机制MFRC522支持ISO14443A协议但原始UID4/7字节存在碰撞风险。本项目采用双重防冲突流程先执行PICC_REQA获取卡片存在信号再执行PICC_ANTICOLL获取唯一UID。关键点在于PICC_ANTICOLL返回的UID校验// Anticollision命令返回7字节数据4字节UID 1字节BCC 2字节CRC // BCC UID[0]^UID[1]^UID[2]^UID[3] uint8_t uid[4]; uint8_t status PICC_Anticoll(uid); // 封装函数内部执行Anticollision指令 if (status MI_OK) { uint8_t bcc_calc uid[0] ^ uid[1] ^ uid[2] ^ uid[3]; if (bcc_calc *(uid4)) { // 校验BCC // UID有效进入业务逻辑 BookSearchByUID(uid); // 根据UID查Flash书目表 } else { // BCC校验失败丢弃该UID重新触发Anticollision continue; } }2.2.1 UID到书目映射的Flash存储设计为避免每次读卡都依赖PC端数据库项目将书目信息固化在STM32内部Flash。采用分页存储策略每页1KB起始地址0x0800F000每条记录结构为typedef struct { uint8_t uid[4]; // RFID卡UID char book_name[32]; // 书名ASCII末尾\0 uint8_t status; // 0在馆1已借出 } BookRecord_t;Flash写入前需解锁并擦除整页FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); FLASH_ErasePage(0x0800F000); // 擦除目标页 // 逐字节写入Flash写入最小单位为半字 for (int i 0; i sizeof(BookRecord_t); i 2) { FLASH_ProgramHalfWord(0x0800F000 i, *(uint16_t*)(record_ptr i)); } FLASH_Lock();提示STM32F103 Flash写入需先擦除整页即使只改一条记录且写入后必须调用FLASH_Lock()防止误写。若忘记擦除写入操作将静默失败——此时用ST-Link Utility读取Flash会发现数据未更新。3. USART透传协议与cJSON日志结构化设计3.1 自定义USART帧格式与PC端解析逻辑本项目摒弃AT指令集采用轻量级二进制帧协议实现MCU与PC双向通信帧结构如下字段长度说明SOF1字节起始符0xAACMD1字节命令码0x01读卡成功0x02借书0x03还书LEN1字节数据长度不含SOH/CRCDATAN字节有效载荷UID状态书名ASCIICRC1字节XOR校验和PC端Python示例解析核心import serial import json ser serial.Serial(COM3, 115200, timeout1) buffer bytearray() def parse_frame(data): if len(data) 5: return None if data[0] ! 0xAA: return None cmd, length data[1], data[2] if len(data) ! 4 length 1: return None crc data[-1] calc_crc 0 for b in data[1:-1]: calc_crc ^ b if calc_crc ! crc: return None payload data[3:3length] if cmd 0x01: # 读卡成功 uid payload[:4].hex().upper() status borrowed if payload[4] 1 else available book_name payload[5:].decode(ascii, errorsignore).strip(\x00) return {event: card_read, uid: uid, book: book_name, status: status} return None while True: byte ser.read(1) if byte: buffer.extend(byte) if len(buffer) 5 and buffer[0] 0xAA: frame parse_frame(buffer) if frame: print(json.dumps(frame, ensure_asciiFalse, indent2)) buffer.clear()3.1.1 cJSON在PC端日志生成中的角色定位cJSON库cJSON.c/h仅部署于PC端日志生成环节绝不运行于STM32端。其作用是将解析后的结构化数据转为JSON字符串便于后续导入数据库或前端展示// PC端C代码片段非MCU代码 #include cjson.h ... cJSON *root cJSON_CreateObject(); cJSON_AddStringToObject(root, event, card_read); cJSON_AddStringToObject(root, uid, 04F81A2B); cJSON_AddStringToObject(root, book, 《嵌入式实时系统设计》); cJSON_AddStringToObject(root, status, available); char *json_str cJSON_PrintUnformatted(root); printf(%s\n, json_str); // 输出{event:card_read,uid:04F81A2B,...} cJSON_Delete(root); free(json_str);注意cJSON库体积约20KB远超STM32F103的20KB RAM限制。项目中cJSON.c文件存在于工程目录仅为PC端开发提供参考编译Keil工程时该文件被排除在构建列表外。若误将其加入Keil工程会导致链接失败Error: L6218E: Undefined symbol。3.2 USART中断接收与环形缓冲区实现为避免主循环阻塞RX数据接收采用中断环形缓冲区方案。缓冲区大小设为128字节覆盖最大帧长关键操作#define RX_BUFFER_SIZE 128 volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; volatile uint16_t rx_head 0, rx_tail 0; // USART1中断服务函数 void USART1_IRQHandler(void) { uint8_t ch; if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { ch USART_ReceiveData(USART1); rx_buffer[rx_head] ch; rx_head (rx_head 1) % RX_BUFFER_SIZE; if (rx_head rx_tail) { // 缓冲区满丢弃最早字节 rx_tail (rx_tail 1) % RX_BUFFER_SIZE; } } } // 主循环中解析缓冲区 void USART_ParseBuffer(void) { while (rx_head ! rx_tail) { uint8_t byte rx_buffer[rx_tail]; rx_tail (rx_tail 1) % RX_BUFFER_SIZE; // 将byte送入帧解析状态机 FrameParser_Input(byte); } }3.2.1 帧解析状态机设计状态机共4个状态严格按帧结构流转STATE_IDLE: 等待0xAASTATE_CMD: 接收CMD字节STATE_LEN: 接收LEN字节STATE_DATA: 接收DATACRC校验后触发事件typedef enum { STATE_IDLE, STATE_CMD, STATE_LEN, STATE_DATA } ParseState_t; ParseState_t state STATE_IDLE; uint8_t frame_buf[256]; uint8_t frame_len 0, expected_len 0; void FrameParser_Input(uint8_t byte) { switch(state) { case STATE_IDLE: if (byte 0xAA) { frame_len 0; state STATE_CMD; } break; case STATE_CMD: frame_buf[frame_len] byte; state STATE_LEN; break; case STATE_LEN: expected_len byte; frame_buf[frame_len] byte; state (expected_len 0) ? STATE_DATA : STATE_IDLE; break; case STATE_DATA: frame_buf[frame_len] byte; if (frame_len 4 expected_len 1) { // SOFCMDLENDATACRC ProcessFrame(frame_buf, frame_len); state STATE_IDLE; } break; } }4. OLED人机交互与本地业务逻辑闭环实现4.1 SSD1306驱动与动态界面刷新策略OLED采用SSD1306控制器128×64点阵通过GPIO模拟I2CPB6/SCL, PB7/SDA。为降低CPU占用界面刷新采用差异更新仅当书名或状态变化时重绘对应区域。// OLED坐标系(0,0)为左上角x轴向右y轴向下 // 每行8像素高一行可显示16个ASCII字符8×8字体 void OLED_ShowString(uint8_t line, uint8_t col, char *str) { uint8_t x col * 8; // 列偏移每字符8像素宽 uint8_t y line * 8; // 行偏移每行8像素高 while (*str ! \0) { OLED_ShowChar(x, y, *str); x 8; } } // 状态指示在右上角显示IN LIBRARY或BORROWED void OLED_UpdateStatus(uint8_t status) { OLED_ClearArea(100, 0, 128, 8); // 清除右上角8×28区域 if (status 0) { OLED_ShowString(0, 12, IN LIB); // IN LIBRARY缩写 } else { OLED_ShowString(0, 12, BORRWD); } }4.1.1 图书状态本地维护机制所有借还操作均在MCU端完成状态同步无需等待PC确认读卡成功 → 查询Flash中该UID对应记录 → 显示书名当前状态按下KEY_UP → 执行借书record.status 1→ 重写Flash该记录 → OLED刷新状态按下KEY_DOWN → 执行还书record.status 0→ 重写Flash → OLED刷新Flash写入耗时约20ms期间OLED显示“WRITING...”避免误操作OLED_ShowString(2, 0, WRITING...); // 第2行显示提示 FLASH_Unlock(); FLASH_ErasePage(page_addr); FLASH_ProgramHalfWord(...); FLASH_Lock(); OLED_ClearArea(16, 16, 128, 24); // 清除提示区域 OLED_UpdateStatus(new_status);4.2 按键消抖与多级菜单导航使用独立按键KEY_UP/PB8, KEY_DOWN/PB9, KEY_SET/PA8硬件消抖软件定时器消抖双保险// 定义按键扫描周期为10msSysTick中断中调用 #define KEY_SCAN_PERIOD 10 volatile uint8_t key_state[3] {0}; // 0释放1按下2长按 volatile uint8_t key_press_cnt[3] {0}; void KEY_Scan(void) { static uint8_t last_key[3] {1,1,1}; uint8_t curr_key[3] { GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8), // KEY_UP: 低电平有效 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9), // KEY_DOWN GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) // KEY_SET }; for (int i 0; i 3; i) { if (curr_key[i] ! last_key[i]) { key_press_cnt[i] 0; } else if (curr_key[i] 0) { // 按下状态 if (key_press_cnt[i] 20) { // 持续200ms判定为长按 key_state[i] 2; } else if (key_press_cnt[i] 2) { // 持续20ms判定为短按 key_state[i] 1; } } last_key[i] curr_key[i]; } }4.2.1 三级菜单状态机Level 0主界面: 显示当前书名状态KEY_UP/KEY_DOWN触发借还KEY_SET进入设置Level 1设置菜单: “Add Book” / “Clear All”KEY_UP/KEY_DOWN切换选项KEY_SET确认Level 2添加图书: 输入UID4字节HEXKEY_UP/KEY_DOWN调整数值KEY_SET保存菜单状态流转由menu_state变量控制避免阻塞主循环typedef enum { MENU_MAIN, MENU_SETTING, MENU_ADD_UID } MenuState_t; MenuState_t menu_state MENU_MAIN; uint8_t uid_input[4] {0}; void Menu_HandleKey(void) { switch(menu_state) { case MENU_MAIN: if (key_state[0] 1) BorrowBook(); // KEY_UP if (key_state[1] 1) ReturnBook(); // KEY_DOWN if (key_state[2] 1) menu_state MENU_SETTING; break; case MENU_SETTING: if (key_state[0] 1) AddBookMode(); // KEY_UP进入添加 if (key_state[1] 1) ClearAllBooks(); // KEY_DOWN清空 if (key_state[2] 1) menu_state MENU_MAIN; break; case MENU_ADD_UID: if (key_state[0] 1) AdjustUIDByte(0, 1); // KEY_UP增高位 if (key_state[1] 1) AdjustUIDByte(0, -1); // KEY_DOWN减高位 if (key_state[2] 1) SaveNewBook(uid_input); // KEY_SET保存 break; } }5. Keil工程配置与面包板快速复现指南5.1 Keil MDK-ARM v5工程关键参数设置本项目基于Keil uVision5开发工程配置直接影响Flash写入和中断响应配置项推荐值说明DeviceSTM32F103C8必须与实物芯片一致否则启动文件错误Target → Xtal8.0 MHz外部晶振频率影响SysTick和USART波特率Output → Create HEX File✓生成.hex供ST-Link烧录C/C → DefineUSE_STDPERIPH_DRIVER, STM32F10X_MD启用标准外设库MD系列32KB FlashDebug → Settings → PortST-Link Debugger选择ST-Link V2/V3Utilities → Flash Download → Configure Flash Tools添加STM32F10x High Density Flash算法否则无法烧录Flash提示若使用国产替代芯片如APM32F103需更换Flash算法文件否则烧录时提示“Flash Download failed”。本项目默认适配原厂STM32F103。5.2 面包板连线对照表免PCB方案针对“小白用户不会画PCB”场景提供杜邦线直连方案所有模块均为5V兼容STM32引脚MFRC522引脚OLED引脚说明PA5SCK—SPI时钟PA6MISO—SPI数据输入PA7MOSI—SPI数据输出PA4NSS—MFRC522片选需改代码中CS引脚为PA4PB0—DCOLED数据/命令选择PB1—RSTOLED复位PB3—CSOLED片选PA0IRQ—MFRC522中断输出3.3VVCCVCC供电MFRC522可接3.3V或5VGNDGNDGND共地5.2.1 keilkilll.bat批处理文件作用解析工程根目录下的keilkilll.bat是清理Keil临时文件的脚本内容为echo off del /f /q *.axf del /f /q *.tra del /f /q *.lnp del /f /q *.plg del /f /q *.htm del /f /q *.log del /f /q *.bak del /f /q *.crf del /f /q *.o del /f /q *.d del /f /q *.lst del /f /q *.i del /f /q *.s del /f /q *.dep del /f /q *.uvopt del /f /q *.uvproj del /f /q *.uvprojx del /f /q *.uvoptx del /f /q *.build_log.htm执行该脚本可彻底清除编译残留解决“修改代码后仍运行旧程序”的问题。建议每次更换开发电脑或导入新工程后首次编译前运行一次。5.3 实机验证四步法硬件通电自检上电后OLED显示“STM32 RFID BOOK SYS”无花屏或全白/全黑 → 电源与OLED通信正常RFID基础通信将任意MIFARE Classic卡靠近MFRC522OLED显示“CARD DETECTED” → SPI与MFRC522链路畅通UID读取验证观察OLED是否显示4字节UID如04 F8 1A 2B→ Anticollision流程成功业务闭环测试用已录入UID的卡触发 → 显示书名“IN LIBRARY”按KEY_UP → 显示“BORRWD”且Flash写入成功下次读卡仍显示BORRWD换另一张未录入卡 → OLED显示“NO BOOK FOUND”注意若第3步UID显示为00 00 00 00检查MFRC522的天线连接——常见故障是天线焊盘虚焊或杜邦线接触不良此时用万用表测天线两端应有约20Ω电阻非断路。本文还有配套的精品资源点击获取
返回列表