基于STC89C52的蓝牙密码锁设计:从AT24C02存储到JDY-31通信的完整实现
2026/9/16 13:59:18 网站建设 项目流程

简介:基于51单片机的蓝牙密码锁完整开发资料包,面向电子爱好者、大学生课程设计与竞赛备赛人群,解决单片机项目从硬件电路到软件调试的闭环需求。资源共72个文件,压缩包约98.91MB,核心包含原理图与PCB文件(SchDoc/PcbDoc/pdf)、Keil工程源码(c/h/hex/uvproj)、视频讲解(mp4)、软件流程图(doc)、系统框图(docx)及元器件清单(xlsx),并内置DS1302时钟、AT24C02存储、DS18B20温度检测和4×4矩阵键盘等模块代码,便于二次开发。功能上支持时间显示、温度读取、密码输入与重设、一键关门,蓝牙模块可实现手机远程控制。已有1914人学习下载,资料目录按原理图、程序、视频、文档等分模块组织,还附有芯片手册、烧录教程和CH340驱动,适合从零开始完整复现或借鉴改进。

1. 蓝牙密码锁不是加个模块那么简单

把机械锁换成密码锁容易,但真正让一套51单片机密码锁从“能开锁”变成“能用”的关键,是外围模块的协同:AT24C02负责掉电保存密码,DS1302维持实时时钟,DS18B20做温度监测,4×4矩阵键盘完成本地输入,蓝牙模块则承担远程开锁的通信链路。这套基于STC89C52的方案,基本就是国内单片机课程设计的“顶配模板”——它覆盖了I²C、SPI类时序、UART串口、外部中断、定时器中断这些51单片机最核心的知识点。适合正在做课程设计、准备电子设计竞赛,或者想把手头开发板扩展成智能家居控制面板的开发者。需要说明的是,这套资料里的程序不是裸机点灯级别,而是多模块分文件协作的完整工程,值得拆开逐行看。

2. 系统架构与模块选型逻辑

2.1 主控选型:为什么是STC89C52而不是STM32

STC89C52是8位8051内核单片机,工作电压5V,拥有8KB Flash、512字节RAM,内置看门狗和EEPROM。放在今天看性能不算强,但在密码锁场景里够用:矩阵键盘扫描、LCD1602显示、UART蓝牙通信都不需要高主频,12MHz晶振下1个机器周期约1μs,完全能覆盖人机交互的实时性要求。选它更现实的原因是生态成熟——STC-ISP烧录工具免外部编程器,CH340 USB转串口模块十几块钱,对课程设计和快速原型验证非常友好。

这套资源里主程序平铺在main.c,外设驱动按照uart.ckey.clcd1602.cds1302.cds18b20.cIIC.cmotor_bujin.c分文件组织。这种按外设切文件的风格是学习阶段最推荐的结构:每个模块的初始化、时序、业务逻辑隔离,排查问题的时候只需要盯一个文件。工程里同时出现hc-sr04.c,说明这套板子还预留了超声波测距的扩展能力,虽然密码锁主逻辑没用到,但硬件上已经做了冗余设计。

2.2 密码存储:AT24C02掉电不丢的关键

AT24C02是2Kb(256字节)的EEPROM,走I²C总线,最大时钟频率400kHz。密码锁必须解决“断电后密码不丢”的问题,51单片机内置ROM是OTP或者Flash,频繁擦写不现实,外挂EEPROM是标准答案。I²C总线上AT24C02的器件地址是0xA0(写)/0xA1(读),由A2、A1、A0引脚电平决定,这套板子上三个引脚接地,所以地址固定。存储规划上,一般把6位密码的ASCII码连续存储,起始地址选0x00,用0x06地址存一个“是否已初始化”的标记位——这样出厂时第一次上电检测到标记位无效,就写入默认密码123456,之后用户修改才覆盖。

// IIC.c 核心读写函数(节选) void IIC_Start(void) { SCL = 1; SDA = 1; delay_us(5); SDA = 0; // SCL高电平期间SDA拉低 -> 起始信号 delay_us(5); SCL = 0; } void IIC_Stop(void) { SCL = 0; SDA = 0; delay_us(5); SCL = 1; delay_us(5); SDA = 1; // SCL高电平期间SDA拉高 -> 停止信号 } bit IIC_WriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SDA = dat & 0x80; // 高位先行 dat <<= 1; SCL = 1; delay_us(5); SCL = 0; // SCL低电平期间允许SDA变化 delay_us(5); } SDA = 1; // 释放总线,等待从机应答 SCL = 1; delay_us(5); if (SDA) { // SDA为高 -> 无应答 SCL = 0; return 0; } SCL = 0; return 1; }

I²C时序的核心是“SCL高电平期间SDA不能变化”这条铁律。起始和停止信号都发生在SCL高电平状态下,而数据传输过程中SDA的变化必须发生在SCL低电平期间。IIC_WriteByte里每次先置SDA再拉高SCL,正是为了满足这个约束。实际调试时如果EEPROM读写偶尔失败,优先用示波器抓SCL和SDA的波形,看应答位是否拉低——if(SDA)这个判断就是用来捕获从机忙或者地址错误导致的无应答场景。

2.3 时钟与温度:DS1302和DS18B20的互补作用

DS1302是Dallas公司的实时时钟芯片,走三线SPI类接口(SCLK、I/O、CE),支持BCD码格式的秒分时日月周年,内置31字节RAM。密码锁接入DS1302的价值不只是显示时间,更重要的是可以做限时密码——比如临时授权密码只在某段时间内有效。这需要系统上电时从DS1302读取当前时间,然后跟密码有效期比较。DS1302的操作时序比I²C更简单,难点在于读写命令字的构造:命令字第7位固定为1,第6位是RAM/CK选择,第5~1位是寄存器地址,第0位是读写方向。

// ds1302.c 读时间函数(节选) unsigned char DS1302_ReadByte(unsigned char addr) { unsigned char i, dat = 0; DS1302_CE = 0; DS1302_SCLK = 0; DS1302_CE = 1; // CE拉高启动通信 for (i = 0; i < 8; i++) { // 写命令字 DS1302_IO = addr & 0x01; addr >>= 1; DS1302_SCLK = 1; DS1302_SCLK = 0; } for (i = 0; i < 8; i++) { // 读数据,LSB先行 dat >>= 1; if (DS1302_IO) dat |= 0x80; DS1302_SCLK = 1; DS1302_SCLK = 0; } DS1302_CE = 0; return dat; }

注意DS1302是LSB先行,跟大多数SPI设备的MSB先行相反,读回来的时间数据是BCD码。比如寄存器0x85(小时)返回0x23,代表23点而不是十进制的23。用的时候必须做BCD转十进制:hour = (bcd >> 4) * 10 + (bcd & 0x0F),否则显示和比较都会错位。DS18B20是单总线(1-Wire)数字温度传感器,测量范围-55℃到+125℃,12位精度下分辨率0.0625℃。它的存在让密码锁能顺带做高温告警——如果检测到环境温度超过阈值,可以联动步进电机把门锁强制打开(火灾场景逃生),或者蜂鸣器报警。单总线协议比I²C和SPI都苛刻,因为只有一根数据线,时序要求精确到微秒级,STC89C52在12MHz下用_nop_()做延时刚好能卡住时序窗口。

3. 矩阵键盘扫描与密码输入逻辑

3.1 4×4矩阵键盘的行列扫描原理

4×4矩阵键盘占用8个IO口(P1或P2口),排成4行4列。扫描策略分“行扫描法”和“反转法”两种。行扫描法的思路是:先把4列全部置高,逐行拉低,然后读列电平,哪一列变低就确定按键坐标。这套资源用的是更高效的反转法:

// key.c 矩阵键盘扫描(反转法) unsigned char Key_Scan(void) { unsigned char row, col, key_value = 0xFF; P1 = 0x0F; // 低4位输入(列),高4位输出高 delay_ms(10); // 消抖 if ((P1 & 0x0F) != 0x0F) { // 有按键按下 col = P1 & 0x0F; // 读取列值 P1 = 0xF0; // 反转:高4位输入(行),低4位输出高 delay_ms(10); row = P1 & 0xF0; // 读取行值 // 根据row和col组合查表得到键值 key_value = Key_Map[row | col]; } return key_value; }

反转法比行扫描少一半的扫描次数,关键在两次方向切换之间要留出IO电平稳定时间。delay_ms(10)同时承担了消抖功能——机械按键按下瞬间会产生约5~10ms的抖动毛刺,软件延时跨过抖动窗口后再读电平,比硬件RC滤波电路省事。注意反转前后P1口的上下拉方向变了:第一次低四位输入高四位输出,第二次反过来,这是矩阵键盘扫描最容易出错的地方,很多新手在这写错导致按键失灵。

3.2 密码输入的状态机设计

密码输入不能写成顺序执行的阻塞逻辑,否则用户在输入过程中系统无法响应蓝牙指令和时钟更新。这套工程里的做法是状态机驱动:空闲态(等待输入)→ 输入态(逐位接收密码)→ 校验态(比较密码)→ 开锁态(驱动电机)。

状态触发条件动作迁移
IDLE上电/复位清空缓冲区,显示待机界面按下任意数字键→INPUT
INPUT数字键按下存入缓冲区,LCD显示*按下确认键→CHECK
CHECK确认键按下读取AT24C02密码并逐位比较匹配→OPEN;不匹配→IDLE
OPEN校验通过步进电机正转开锁,3秒后反转延时结束→IDLE

密码长度默认6位,缓冲区是全局数组password_buf[6],每按一位buf_len++,超过6位直接忽略新按键——这是防止缓冲区溢出的最简单手段。确认键用ENTER,取消键清空当前输入。校验时逐位比较password_buf和从AT24C02读出的存储值,注意存储的是ASCII码还是裸数字,这决定了比较前要不要转换。

3.3 修改密码与掉电保存流程

修改密码的权限控制是这套方案里相对薄弱但常见的地方:按下SET键后直接进入改密模式,输入新密码,再按SET确认,新密码写入AT24C02。没有要求输入旧密码验证,这在实际产品里是安全隐患,但课程设计阶段为了演示方便可以接受。写入EEPROM时要用页写模式,AT24C02一页8字节,6位密码加上标记位刚好在一页内:

// 写密码到AT24C02,地址0x00开始 void EEPROM_WritePassword(unsigned char *pwd) { unsigned char i; IIC_Start(); IIC_WriteByte(0xA0); // 器件地址+写 IIC_WriteByte(0x00); // 起始地址 for (i = 0; i < 6; i++) { IIC_WriteByte(pwd[i]); } IIC_Stop(); delay_ms(10); // EEPROM内部写周期约5ms }

delay_ms(10)不能省——AT24C02完成一次页写需要内部写周期(典型值5ms),这期间芯片不响应任何I²C指令。如果连续快速修改密码,刚发完停止信号立即启动下一次写操作,第二次写会失败。这就是为什么有些人的EEPROM代码“偶尔丢密码”,其实不是数据丢了,是写周期没等够。

4. 蓝牙模块JDY-31的通信协议与指令设计

4.1 JDY-31模块的参数配置与连接方式

JDY-31是国产低功耗蓝牙透传模块,基于TI CC2541芯片,支持BLE 4.0,串口波特率默认9600。它的核心价值是“透传”——手机APP通过蓝牙发送的任何数据,模块原封不动通过串口转发给51单片机,反过来单片机串口发出的数据也会被透传到手机。模块有6个引脚:VCC、GND、TXD、RXD、STATE、EN。和51单片机连接时注意交叉:模块TXD接单片机RXD(P3.0),模块RXD接单片机TXD(P3.1),共地必须连。电平方面JDY-31是3.3V供电,但TXD/RXD电平兼容5V TTL,可以直接和STC89C52连接,不需要电平转换电路——这是选它而不是HC-05(5V供电但逻辑电平不兼容)的原因之一。

模块出厂波特率不一定是9600,需要先用USB转TTL接到电脑上发AT指令确认。JDY-31的AT指令集比HC-05简单得多:

AT # 测试通信,返回OK AT+NAMELockPro # 修改蓝牙名称为LockPro AT+BAUD4 # 设置波特率为9600(4代表9600) AT+PASS5656 # 设置配对密码 AT+ROLE1 # 设置为主模式(1为主,0为从)

发送每条AT指令必须以\r\n结尾,否则模块不识别。调试时最常用的方式是用串口助手打开对应串口,勾选“发送新行”,然后手动输入AT指令逐个验证。设置完波特率或角色后模块会重启,需要等1~2秒再发下一条指令。

4.2 串口中断接收与指令解析

蓝牙通信的核心代码在uart.c里。密码锁需要实时响应手机指令,所以不能在主循环里轮询串口标志位——必须开中断。STC89C52的串口工作在模式1(8位UART,可变波特率),用定时器1作为波特率发生器:

// uart.c 串口初始化,9600波特率@12MHz void UART_Init(void) { SCON = 0x50; // 串口模式1,REN=1允许接收 TMOD &= 0x0F; // T1保持原模式 TMOD |= 0x20; // T1工作在模式2(8位自动重装) TH1 = 0xFD; // 9600波特率重装值@12MHz TL1 = 0xFD; TR1 = 1; // 启动T1 ES = 1; // 打开串口中断 EA = 1; // 打开总中断 } // 串口中断服务函数 void UART_ISR(void) interrupt 4 { unsigned char cmd; if (RI) { // 接收完成标志 RI = 0; // 必须软件清零 cmd = SBUF; // 读取接收数据 if (cmd == '\n') { // 换行符作为指令结束标志 Process_CMD(); // 解析完整指令 cmd_index = 0; // 重置缓冲区 } else { if (cmd_index < 16) // 防溢出 cmd_buf[cmd_index++] = cmd; } } }

RI标志一定要在读取SBUF之前清零还是之后清零?正确做法是先清零再读数据——如果先读SBUF再清零,理论上没问题,但某些增强型51内核在连续快速接收时可能丢字节。指令缓冲区分两个阶段:非换行符时持续接收存入cmd_buf,收到\n认为一条指令结束,调用解析函数。这种“以换行符定界”的协议设计比固定长度更健壮,因为手机APP发来的指令长度天然不固定。

4.3 手机端指令集设计与鉴权逻辑

蓝牙密码锁的指令集要同时覆盖开锁、状态查询、时间同步和温度读取几个场景。设计原则是:单字符指令做起止标记,后续字段用逗号分隔,避免指令二义性:

OPEN#123456 # 开锁指令,带密码 STAT # 查询锁状态(开/关/温度/时间) TIME#231115 # 同步时间:23时11分15秒 TEMP # 读取当前温度

OPEN#123456这条指令的解析逻辑是:先匹配指令头OPEN#,再校验后续6位密码是否正确。密码校验通过后置开锁标志位,主循环检测到标志位后驱动步进电机。这样设计的好处是串口中断里只做数据接收和缓存解析,耗时操作(电机转动、EEPROM读写)全部丢给主循环处理,避免在中断服务函数里占用太长时间导致丢数据。实际测试中,9600波特率下1个字节的接收时间是约1.04ms,如果在中断里执行EEPROM页写(耗5ms),期间到达的5个字节就会全部丢失。这是串口中断编程最常见的坑。

蓝牙模块的通信距离在空旷环境下约10米,隔一道墙大约衰减到5米以内。手机APP用现成的“蓝牙串口”类工具就能配合调试,不需要专门开发APP。但要注意:iOS系统上第三方蓝牙串口APP可靠性参差不齐,建议用Android手机调试,Android对SPP/BLE串口协议支持更开放。

5. 步进电机开锁机构的驱动与控制

5.1 电机选型与驱动方式

这套方案用的是28BYJ-48步进电机,5V驱动,减速比1:64,步距角5.625°/64=0.08789°。选择步进电机而不是普通直流电机,核心原因是步进电机能精确控制转角——开锁需要让锁舌转动固定角度(比如90°或者180°),直流电机需要加限位开关,步进电机只要发固定数量的脉冲就能精确到达位置。28BYJ-48是四相五线电机,用ULN2003达林顿管驱动板驱动。ULN2003内部是7个达林顿对,集电极开路输出,最大500mA电流,刚好匹配28BYJ-48的相电流。

// motor_bujin.c 步进电机驱动(四相八拍) code unsigned char motor_steps[8] = { 0x08, 0x0C, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09 }; void Motor_Run(int angle, bit direction) { unsigned int total_steps; unsigned char i = 0; // 转1圈需要4096个脉冲,按角度换算 total_steps = (unsigned int)(angle * 4096.0 / 360.0); while (total_steps--) { if (direction) P2 = motor_steps[i++ & 0x07]; else P2 = motor_steps[i-- & 0x07]; delay_ms(2); // 转速控制 } }

四相八拍的工作方式是:每次切换只改变一相的开关状态,保证磁场方向平滑过渡,电机运行更平稳、失步风险更低。motor_steps数组里每两个相邻值的汉明距离为1(只差一位),这正是八拍驱动的核心。delay_ms(2)控制转速,延时越短电机越快,但扭矩越小,超过一定速度会失步。28BYJ-48带减速箱,空载转速约15转/分钟,开锁90°大约需要68个脉冲,配合2ms延时约136ms完成动作,人感知上比较顺滑。

5.2 开锁动作的保护机制

开锁机构最怕的是“堵转”——锁舌机械卡死时电机还在持续输出,导致线圈过热烧毁。课程设计阶段不会加电流检测,但可以在软件上做超时保护:电机开始转动后启动定时器,如果在规定时间内(例如3秒)没有收到限位信号,立即停止输出并报警。这需要硬件上配一个微动开关或者霍尔传感器做限位检测,资源清单里没有明确列出来,但工程文件里有hc-sr04.c说明预留了传感器接口。

实际调试时还有一个常见问题:断电后电机位置的“记忆”。步进电机没有位置反馈,断电后重新上电,它不知道锁舌现在在哪个位置。如果默认位置是“开着”的,一上电就去执行关锁,可能把开锁机构顶坏。简单可靠的方案是:上电初始化时不管锁舌处于什么状态,先执行一次“找零”动作——电机慢速向关锁方向转,直到触碰到限位开关。这个逻辑虽然土,但是在没有编码器的情况下最实用的。

6. 程序烧录与CH340驱动的实战排错

烧录这一层的坑,比写代码多得多。STC89C52是STC公司特有的ISP烧录方式,用串口就能烧程序,不需要专门的编程器。流程是:用USB转TTL模块连接单片机UART(TXD-RXD交叉),冷启动烧录(先点下载,再给单片机上电)。stc-isp-15xx-v6.82.exe是STC官方烧录工具,支持老款STC89系列到新款的STC15系列,打开后按以下参数配置:

单片机型号:STC89C52RC 串口号:CH340对应的COM口(设备管理器里查) 最低波特率:2400 最高波特率:9600(早期版本波特率设太高容易失败) 晶振频率:11.0592MHz(匹配2400/9600波特率)

CH340驱动的安装路径是设备管理器里找“端口(COM和LPT)”,如果插上USB转TTL模块没反应,先换USB线——很多USB线只能充电不能传数据。驱动装好后能看到“USB-SERIAL CH340 (COM3)”之类的设备名,如果显示黄色感叹号,在设备上右键更新驱动,手动指定到驱动包所在目录。

烧录失败的排查顺序:

  1. 先确认COM口号选对。拔插模块,看设备管理器里COM号变不变,变了说明识别到了。
  2. 确认单片机供电正常。5V电源接好,板载电源指示灯亮。
  3. 冷启动烧录:STC烧录器点“下载/编程”后,鼠标点上“重新上电”按钮——先断开单片机电源,再接通,这个时序是STC烧录成功率的关键。很多新手在单片机已经上电的状态下点下载,永远等不到“正在检测目标单片机”的提示。
  4. 波特率降级:如果9600一直失败,把最高波特率改为2400,STC89C52在劣质USB转串口线下高速通信容易出错。
  5. 换串口线:CH340芯片的模块通常比PL2303稳定,PL2303老版本驱动在Win10/11下经常蓝屏或掉线。

烧录完成后project.hex文件通过串口写入单片机Flash,代码里的main()函数从地址0x0000开始执行。验证烧录是否成功的快速方法是:打开串口助手,波特率9600,给蓝牙模块发STAT指令,如果能收到返回的状态信息字符串(比如Lock:CLOSED,TEMP:28.5C,TIME:23:11:15),说明串口通路、蓝牙模块、主控程序三端都正常工作。

最后说一个这套方案里最容易被忽略的技巧:蓝牙模块的STATE引脚可以接单片机一个IO口做连接状态检测。手机APP断开蓝牙时,STATE引脚电平会变化,单片机能感知到并在LCD上显示“蓝牙已断开”。这个细节虽然简单,但能让用户交互体验提升一个档次——否则用户在手机上点了断开,锁这边毫无提示,容易造成“以为锁了其实没锁”的误会。在main.c的主循环里加一个if (BLE_STATE_PIN)分支就能实现,工程量很小,但演示效果加分明显。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询