ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STC89C52与HC-05蓝牙模块串口通信实战:从单字节传输到LCD显示

STC89C52与HC-05蓝牙模块串口通信实战:从单字节传输到LCD显示 简介一份基于STC89C52单片机的蓝牙模块数据传输毕业设计PDF文档面向电子、自动化及计算机相关专业学生尤其适合需要完成无线数据传输类课题的毕业设计者。文档以HC-05主从一体蓝牙模块为无线收发核心结合STC89C52单片机、LCD1602液晶显示和矩阵键盘模块系统阐述了从方案设定、硬件电路设计、软件编程到整机调试的完整流程。资源为单个PDF文件大小约637KB内容紧凑包含系统框图、各模块电路说明、C语言源程序、按键发送与接收操作说明以及相关知识点总结可直接用于课题设计参考和代码逻辑对照。目前已有121人学习下载对快速理解51单片机蓝牙无线传输的实现方法具有实用价值是完成类似嵌入式毕业设计或课程项目的便捷参考资料。1. 两个单片机之间一次只传一个字节的蓝牙设计第一次看到这份设计的代码时最让我意外的是 STC89C52 的串口其实一次只能收发一个字节。很多人觉得这是硬件瓶颈但这个毕业设计恰恰把这个“瓶颈”用到了极致每次按键只发送 0x00~0x09 的单个字节配合蓝牙模块的透传把“一字节无线通信”这件事做到了几乎没有多余逻辑。整个系统由两套完全相同的板卡组成每套都包含 STC89C52、HC-05 蓝牙模块、LCD1602 显示屏和矩阵键盘。两套板卡通过蓝牙配对后任意一侧按下数字键并触发发送另一侧就能在 LCD 上实时显示收到的数字。适用场景很直接智能家居里的短指令无线控制、工业现场的低频遥测、或者作为物联网设备的前置通信验证。新手能从这里看懂串口、蓝牙模块和 LCD 驱动的完整配合有经验的人也能重新审视这种单字节协议在实时性和抗干扰上的取舍。2. STC89C52 串口与 HC-05 蓝牙的配对链路2.1 为什么用 STC89C52 加 HC-05 而不选集成蓝牙的 SoCSTC89C52 是 51 内核里非常典型的一颗芯片内置 UART但没有蓝牙协议栈。HC-05 的作用是把蓝牙协议栈和射频部分全部封装好对外暴露的只是一个串口。这种“单片机 UART 蓝牙透传模块”的组合方式是工程上最稳妥的无线化改造手段——不需要学习复杂的蓝牙协议也不需要重写原有的有线串口程序只要把单片机的 TXD、RXD 分别接到 HC-05 的 RXD、TXD逻辑上就等价于一根“无线延长线”。这套方案适合教学和毕设的原因也很实际STC89C52 的例程资料非常多HC-05 又支持 AT 指令配置主从模式遇到问题时排错路径短。相比 nRF24L01 需要自己处理对码和应答HC-05 的蓝牙 SPP 协议栈把链路管理全部接管了编程负担大大降低。2.2 硬件连接与电平匹配的注意事项硬件连接并不复杂但有一个细节非常容易踩坑HC-05 的 RXD 和很多 51 开发板的 P3.0 口之间电平标准要匹配。HC-05 供电有 5V 和 3.3V 两种版本如果模块是 3.3V 供电它的 RXD 引脚对 5V 高电平可能不识别推荐在单片机的 TXD 到 HC-05 的 RXD 之间串一个 1kΩ 电阻分压或者用两颗电阻搭一个简单的分压网络。单片机引脚HC-05 引脚说明P3.1 (TXD)RXD经 1kΩ 电阻串接避免 5V 高电平损伤模块P3.0 (RXD)TXD模块输出 3.3V 电平51 单片机识别为高电平没有问题GNDGND必须共地否则通信不稳定VCCVCC按模块规格接 3.3V 或 5V连接好以后先不要急着写程序用 USB-TTL 模块把 HC-05 单独接到电脑上打开串口助手发 AT 指令确认模块能响应再接到单片机上进行链路验证。这样可以提前判断是硬件接线问题还是单片机程序问题。2.3 蓝牙配对流程与指示灯判读HC-05 上电后会进入两种状态之一如果之前已经配对过则直接进入透传模式如果未配对则进入 AT 命令模式或者等待配对的状态。指示灯的行为是判断状态的直接依据等待配对时指示灯通常是慢闪配对成功并且进入透传模式后指示灯变为双闪也就是每秒钟闪两次短亮。默认情况下两块 HC-05 模块需要一主一从才能自动配对。如果两块都是从模式它们不会互相连接。解决办法是先把两块模块都接上 USB-TTL在 AT 模式下用指令配置ATROLE0 # 设置当前模块为从模式 ATROLE1 # 设置当前模块为主模式 ATCMODE0 # 固定连接地址开机自动回连 ATBIND从模块蓝牙地址主模块配置ATCMODE0并绑定从模块地址后每次上电会自动发起连接无需手动在手机或其他设备上配对。这种配置方式适合本设计中的两套板卡固定配对场景避免每次开机都要重新按键连接。地址从模块可以通过ATADDR查询。配置完以后主从模块各自断电重启观察指示灯是否进入双闪状态。2.4 串口初始化代码与波特率计算主程序里串口初始化的关键代码集中在main函数开头核心是配置 SCON、TMOD、TH1 和 TL1 这四个寄存器。void main() { SCON 0x50; // 串口方式18位UART允许接收 TMOD 0x20; // 定时器1工作在方式28位自动重装 TH1 0xFD; // 波特率9600初值0xFD TL1 0xFD; // 与TH1相同自动重装时使用 TR1 1; // 启动定时器1 REN 1; // 允许串口接收 SM0 0; SM1 1; // 与SCON0x50等效明确串口方式1 ... }SCON 设置为 0x50二进制即 0101 0000。其中 SM00、SM11 表示串口工作在方式 1也就是 8 位 UART波特率由定时器 1 溢出率决定。REN1 允许接收这是接收数据必须开启的位。TMOD0x20 将定时器 1 设置为方式 2即 8 位自动重装。TH1 和 TL1 的初值由波特率决定当晶振为 11.0592MHz 时波特率 9600 对应的初值是 0xFD。之所以选择 11.0592MHz 晶振是因为这个频率可以被 9600 整除产生的波特率误差几乎为零。如果换成 12MHz 晶振同样的初值会产生约 2% 的误差短时间内一两个字节看不出问题但连续传输时可能会出现偶发乱码。晶振频率目标波特率TH1/TL1 初值实际波特率误差11.0592MHz96000xFD96000%11.0592MHz48000xFA48000%12MHz96000xFD8928约 7%注意上面表格中 12MHz 晶振配 0xFD 时实际波特率约 8928误差较大建议直接把晶振换成 11.0592MHz这是最省事的做法。如果必须用 12MHz 晶振可以尝试 4800 波特率把 TH1 初值设为 0xF3误差会小很多但 HC-05 默认波特率是 9600需要在 AT 模式下把模块波特率也改成 4800保持两边一致。2.5 HC-05 与单片机之间的通信逻辑HC-05 在透传模式下不关心数据内容是什么从串口收到的字节会原封不动地通过蓝牙无线发送给对方对方 HC-05 收到后也从自己的串口输出同样的字节。因此单片机程序里完全没有必要处理任何蓝牙协议只需要在串口中断或者轮询中把 SBUF 里的数据取出来。这个设计里有个值得注意的工程决策发送端只发 0x00 到 0x09 的单字节接收端收到后映射为数字 0 到 9 显示。换成更复杂的字符串通信发送端要多写一个字符串拆包循环接收端要多写一个缓冲区。对于这个应用场景单字节映射省掉了缓冲区越界、粘包、半包等一系列问题稳定性反而更高。等到后续需要扩展字符集再改成多字节帧协议也来得及核心的串口收发逻辑不需要推翻重写。3. LCD1602 显示模块的初始化与写入时序3.1 1602 引脚功能和最小接线方法LCD1602 一共有 16 个引脚但真正控制显示只需要其中的数据引脚和控制引脚。第 1、2 脚是电源第 3 脚是对比度调节用一个 10k 电位器分压接到 V0调整到屏幕出现清晰但不刺眼的字符即可。对比度调太高会出现重影这个在暗光环境下尤其明显。第 4 脚 RS 选择指令寄存器还是数据寄存器第 5 脚 RW 选择读还是写第 6 脚 E 是使能端第 7 到 14 脚是 8 位数据线 D0 到 D7。第 15 和 16 脚是背光正负极直接接 5V 和 GND 就能点亮背光。引脚号符号功能接到单片机1GND电源地GND2VCC5V 电源正极VCC3V0对比度调整10k 电位器中间抽头4RS寄存器选择P2.65RW读写选择P2.5接地可省6E使能端P2.77~14D0~D7数据线P0.0~P0.7需接上拉电阻15LED背光正极VCC16LED-背光负极GND设计中把数据线接在 P0 口需要注意 P0 口是开漏输出内部没有上拉电阻必须外接 4.7k 到 10k 的上拉排阻否则输出高电平时电压上不去LCD 会读不到正确的数据。这一点在焊接和调试时非常容易忽略屏幕只亮不显示字符大概率就是上拉电阻没接或虚焊。3.2 写指令和写数据的时序要求LCD1602 的写时序可以总结为三个步骤先设置 RS 电平决定当前操作是指令还是数据然后通过 P0 口送出数据最后在 E 引脚上产生一个高电平脉冲让 LCD 在负跳变沿锁存数据。RW 引脚在本设计里直接置 0因为程序中只需要写入不需要读取忙标志。void write_lcd_com(uchar com) { rs 0; // RS0选择指令寄存器 P0 com; // 指令字写入P0口 delay(5); // 等待数据稳定 en 1; // E拉高准备锁存 delay(5); // 保持高电平 en 0; // E拉低负跳变沿执行指令 } void write_data(uchar date) { rs 1; // RS1选择数据寄存器 P0 date; // 要显示的字符ASCII码写入P0口 delay(5); en 1; delay(5); en 0; }write_lcd_com和write_data的区别只有一处RS 的电平不同。RS0 时P0 上的字节被解释为指令例如清屏、光标移动、显示开关RS1 时P0 上的字节被解释为字符数据例如 0x30 表示字符 00x41 表示字符 A。E 引脚的高电平脉冲宽度并不是越长越好delay(5) 在 11.0592MHz 晶振下大约产生几十微秒的延时完全满足 LCD1602 的时序要求。这里使用软件延时是为了程序简单不需要精确计时但如果后续扩展为中断驱动显示建议改用定时器延时或者查忙标志避免延时函数阻塞主循环。初始化函数固定调用四组指令0x38 设置 8 位数据接口和两行显示0x0C 开显示不显示光标0x06 写入后光标右移0x01 清屏并让光标归零。有的 LCD 模块对初始化时序更敏感可以在 0x38 之前先延时 15ms再补一次 0x38保证模块上电后稳定进入工作状态。void lcdinit() { en 0; write_lcd_com(0x38); // 8位数据2行显示5x7点阵字符 write_lcd_com(0x0c); // 开显示光标关闭 write_lcd_com(0x06); // 写字符后地址指针加一 write_lcd_com(0x01); // 清屏 }3.3 字符显示地址的换算方法LCD1602 的 DDRAM 地址是连续的但第一行从 0x80 开始第二行从 0xC0 开始。write_lcd_com(0x80 0x40)就是先把地址设为 0xC0即第二行开头。为什么要用 0x80 加上偏移量而不是直接写 0xC0因为 0x80 是设置 DDRAM 地址指令的最高位标志LCD 在解析这个字节时看到最高位为 1就知道这是地址设置指令后面的 7 位才是真正的地址。设计中第一行显示了 8 个字符的接收表第二行显示 8 个字符的发送表。接收端每收到一个字节就把当前位置的数据写入并向后移动一位显示效果就像收到的数字在屏幕上逐个排列出来。这里有个容易出错的地方如果不对地址做边界判断写满 16 个字符后地址继续自增会跳到另一行或者超出有效区域。常见做法是在写入前检查当前列地址当写入位置大于等于 15 时先把地址重置为行首再开始新的一轮接收显示。4. 矩阵键盘扫描与串口数据收发实战4.1 矩阵键盘的扫描原理矩阵键盘把 16 个按键排列成 4 行 4 列用 8 个 I/O 口控制相比独立按键的 16 个 I/O节省了一半的引脚。扫描的基本原理是逐列拉低然后读取行线的状态判断哪一个按键被按下。设计中的代码把 P1 口的高四位作为行线低四位作为列线先拉低第一列再读行线。当某个按键按下时对应的行线会被拉低通过行线和列线的组合就能唯一确定按键位置。扫描过程中最关键的是消抖。按键按下和松开时机械触点会经历 5 到 10ms 的不稳定震动。程序中在首次检测到按键按下后调用 delay(2) 延时然后再次读取确认电平仍然为低才认为按键确实按下。这个二次确认的做法比单纯延时消抖更可靠因为如果第一次是干扰信号第二次读到的电平会恢复为高按键不会被误触发。松开时的检测用while(temp ! 0xf0)循环等待行线恢复高电平防止一次按键被重复处理。4.2 四种典型按键组合与按键编码表在完整的 16 键矩阵中每个按键对应一个独立的扫描码。设计代码片段里只列出了部分按键的 case完整的 16 键编码对应关系如下表P1 口的值为按下时读到的字节。按键编号显示数字P1 扫描码所在行所在列100xEE第1行第1列210xDE第1行第2列320xBE第1行第3列430x7E第1行第4列540xED第2行第1列650xDD第2行第2列760xBD第2行第3列870x7D第2行第4列980xEB第3行第1列1090xDB第3行第2列代码中的num变量保存的是从 switch 语句中解析出的按键编号显示时通过查表table[num]得到对应的字符。查找table[]定义可以看到它存的是 ASCII 字符 0 到 9而不是数字 0 到 9直接写入 LCD 就能显示。这个查表法比直接计算 ASCII 码要直观后续要扩展为显示字母时只需要修改表内容不需要改动显示逻辑。4.3 串口发送数据的触发逻辑发送数据的设计思路是矩阵键盘扫描只负责更新num的值并不立即发送用户通过 LCD 的第二行确认当前选中的数字无误后再按下发送按键触发实际的串口输出。这种“先确认再发送”的交互方式避免了一边按键一边发数据可能造成的误操作。void Key1() { if(Key 0) { // 检测发送按键是否按下 delay(2); // 消抖延时 if(Key 0) { // 二次确认按下 switch(num) { case 0: SBUF 0x00; // 发送数字0对应的字节 while(!TI); // 等待发送完成标志置1 TI 0; // 清除发送中断标志 break; case 1: SBUF 0x01; while(!TI); TI 0; break; // 其余case结构相同发送对应数字的字节 } while(!Key); // 等待按键松开 } } }发送的核心是SBUF 0x00这一句。SBUF 是 STC89C52 的串口数据缓冲寄存器向它写入一个字节后硬件会自动把该字节通过 TXD 引脚移位输出。TI 是发送完成标志位当最后一个停止位发送完成后硬件会把 TI 置为 1。程序里while(!TI)的作用就是在等待发送完成CPU 停在这个循环里直到 TI 为 1 才继续往下执行。注意这里的 TI 不会自动清除必须由软件写 0否则下一次发送时while(!TI)会立即通过导致上一个字节还没发完就覆盖 SBUF。while 循环等待 TI 的做法在波特率 9600 时最多阻塞约 1ms对于按键触发的低频发送场景没有性能问题。4.4 接收数据的两种实现方式原设计中接收部分是在主循环里轮询 RI 标志位判断是否收到了数据。轮询的优点是代码简单直观不需要进入中断上下文缺点是如果主循环中某个函数耗时太长比如 LCD 清屏操作可能错过后续字节。由于本设计一次只接收一个字节接收间隙远远大于主循环的执行时间所以轮询方式完全够用。实际工程中也常用串口中断来处理接收这样可以保证数据到达后立即被处理不会因为主循环阻塞而丢失。中断方式的代码如下void serial_isr() interrupt 4 { uchar rx_byte; if(RI 1) { // 接收中断标志 RI 0; // 必须软件清除否则反复进入中断 rx_byte SBUF; // 读取接收缓冲寄存器 switch(rx_byte) { case 0x00: temp2 0; break; case 0x01: temp2 1; break; // 其余case同理将字节映射为数字 } write_lcd_com(0x80 add); // 显示到当前接收位置 write_data(table[temp2]); // 写入数字字符 add; } }中断服务函数里读取 SBUF 和发送时写入 SBUF 是同一个寄存器地址但编译器不会混淆因为读操作和写操作对应不同的硬件逻辑。读 SBUF 返回的是接收移位寄存器中的内容写 SBUF 则把数据传输到发送移位寄存器。interrupt 4是 Keil C51 编译器规定的串口中断向量号中断服务函数不能有返回值和参数并且内部不准调用可能改变寄存器组的长函数。如果需要在中断中做复杂处理正确做法是先用全局变量保存接收到的字节退出中断后再在主循环中解析和显示。4.5 单字节协议在丢包场景下的表现蓝牙透传相比有线串口多了一个无线链路的环节偶尔出现丢包或者干扰是正常的。单字节协议的优势在于一个字节丢失后最多丢一个数字不会影响后续数据的解析。相比之下如果发送端一次发送 12 34 56 这样的字符串接收端用分隔符或者固定长度来拆包一旦中间丢失一个字符可能导致后续所有包都错位需要重新设计同步机制。这个设计把每个数字独立编码为一个字节就是一个天然的报协议。后续如果需要扩展建议在这个基础上增加帧头帧尾比如用 0xAA 作为帧头0xFE 作为帧尾中间放数据字节。虽然 STC89C52 一次只能发一个字节但接收端可以设计一个简单的状态机把数据存入缓冲数组凑满一帧后再一次性显示。这样既保留了单字节发送的硬件基础又获得了多字节帧的完整性和可扩展性。5. HC-05 蓝牙配对失败与乱码问题的排查技巧5.1 从指示灯判断模块当前状态HC-05 的指示灯状态是整个系统最直接的诊断窗口。上电后指示灯一直快闪说明模块处于 AT 命令模式此时不会进入透传也无法和另一个模块配对。指示灯慢闪说明模块已经进入可配对状态等待主模块发起连接。指示灯每隔一秒左右闪两次说明已经配对成功并进入透传模式此时模块之间可以正常收发数据。如果两块模块都已经进入双闪状态但仍然收不到数据问题大概率不在蓝牙而在单片机串口部分。用 USB-TTL 模块分别监听两块单片机的 TXD 引脚发送端按下按键后在电脑串口助手中应看到单个字节的输出接收端在配对成功后收到的字节也应该能在串口助手中看到。这个方法能把“蓝牙链路是否正常”和“单片机程序是否正确”两个问题隔离开。5.2 配对失败的常见原因和处理步骤两块 HC-05 无法配对最常遇到的问题是两块都是从模式。HC-05 默认角色是从模式如果两块都用默认配置它们永远只能等待外部设备连接不会主动互相连接。解决办法是把其中一块配置为主模式并设置绑定地址。以下是完整的排查顺序# 步骤1确认两块模块都能进入AT模式 # 模块上电前按住模块上的按键再上电指示灯慢闪即为AT模式 ATROLE? # 查询当前角色0为从1为主 # 步骤2将主模块绑定从模块地址 ATROLE1 ATCMODE0 ATBIND98d3:31:xxxxxx # 填入从模块查询到的地址 # 步骤3复位主模块 ATRESET执行完 AT 指令后把主从模块断电重新上电观察指示灯。如果主模块先进入双闪几秒钟后从模块也跟着双闪说明配对成功。如果主模块一直慢闪检查 BIND 地址是否填写正确地址格式是冒号分隔的三段十六进制。另外注意 CMODE0 时模块只连接绑定地址的设备如果地址写错主模块永远不会发起正确的连接请求。临时排查可以把 CMODE 改回 1让主模块连接范围内任意从模块确认硬件正常后再改回固定绑定。5.3 单片机收到乱码的排查方法显示乱码通常不是来自蓝牙链路本身而是两边波特率不一致。HC-05 出厂默认波特率一般是 9600但也可能被之前的操作改成了 38400 或者 115200。单片机这边也需要注意STC89C52 串口方式 1 的波特率由定时器 1 的溢出率计算不同晶振频率对应的 TH1 初值完全不同。排查时先不要连接蓝牙模块直接用 USB-TTL 连接单片机通过串口助手发送一个字节看单片机是否按预期处理。如果这一步通过再把蓝牙接到链路里。乱码还有一个容易忽略的来源是 USB-TTL 模块本身的问题很多便宜的 CH340 模块没有做完整的电平转换高压反灌到 HC-05 的 TXD 引脚导致信号被钳位。排查时优先使用带光耦隔离的模块或者接 1kΩ 限流电阻。5.4 发送频率与实时性的平衡按键触发的发送属于低频操作TI 标志位轮询等待没有任何问题。但如果以后要用传感器定时上报数据比如每 10ms 发一批数据就需要评估发送耗时和采样周期的关系。波特率 9600 时一个字节的总传输时间为 10 位除以 9600约 1.04ms。如果把波特率提高到 38400单字节时间缩短到 0.26ms但对线路质量敏感无线链路中更高的速率意味着相同突发干扰下更容易出错。一个折中方案是分档设计低速档用 9600 保证传输距离和稳定性高速档用 38400 传输批量数据。STC89C52 的最大串口波特率受限于晶振11.0592MHz 下最高可以做到 57600误差也很小。但 HC-05 的蓝牙传输实际吞吐量受限于底层蓝牙协议即使串口设置到 115200实际数据流量也不会线性提升所以这个设计中 9600 波特率是合理的工程选择。本文还有配套的精品资源点击获取
返回列表