简介:这份资源面向51单片机初学者与嵌入式物联网开发者,聚焦如何借助LoRa模块实现远距离无线串口通信,解决低功耗、长距离数据传输的实践问题。压缩包共30个文件,约47KB,包含发送端与接收端两套Keil工程,主要文件类型有c源码、uvproj与uvopt工程配置、a51启动代码、hex烧录文件及lst、m51等编译中间文件,结构清晰,便于对照学习。目前已有950人学习下载。资源围绕51单片机串口寄存器配置、SPI接口时序、LoRa频率与扩频因子设置、数据收发流程及CRC校验等关键环节展开,读者可借助发送端与接收端示例代码,理解硬件连接、模块初始化、数据发送与接收中断处理,并掌握电源管理、天线选择与代码优化等实用思路,适合快速搭建LoRa远程通信实验环境。
1. 51单片机接上 LoRa 模块之后,远程通信到底该怎么落地
很多人第一次把 51 单片机和 LoRa 模块焊到同一块板子上,通电后串口助手却只看到一堆乱码,或者干脆一个字节都收不到。问题往往不在 LoRa 本身,而在于 51 单片机的串口配置、模块的透明传输模式、以及收发双方的地址与信道是否对齐。这套资源围绕 51 单片机通过 LoRa 实现远程通信展开,核心是串口透传:单片机把要发的数据从 TXD 丢给 LoRa 模块,模块负责空中调制,接收端的 LoRa 模块再把数据从 RXD 吐给另一颗 51。它适合做课设、小型遥测、点对点开关量传输的从业者,也适合想从有线串口过渡到无线的嵌入式新手。下面按硬件接线、串口配置、收发代码、参数对齐、排错顺序拆开讲,每一步都能直接抄。
2. 硬件链路与串口透传原理:先搞清数据从哪来到哪去
2.1 51 单片机与 LoRa 模块的物理连接
常见的 LoRa 模块(如基于 SX1278 的串口型)对外就是四根线:VCC、GND、TXD、RXD。51 单片机这边用一组 UART,比如 STC89C52 的 P3.0(RXD)和 P3.1(TXD)。接线时记住一个反直觉的点:模块的 TXD 要接单片机的 RXD,模块的 RXD 要接单片机的 TXD,交叉接。如果两边都是公头排针,很多人会顺手直连,结果就是收发全无。
供电要单独说。LoRa 模块在发射瞬间电流能冲到 100mA 以上,51 开发板上的 5V 稳压如果同时带数码管和蜂鸣器,很容易在发射时把电压拉低,表现为“偶尔能发、偶尔死机”。我一般会给 LoRa 模块单独一路 3.3V 或 5V(看模块规格),并在 VCC 和 GND 之间并一个 100μF 电解加 0.1μF 瓷片。这个电容不是玄学,是给发射瞬间补电流的。
如果模块是 3.3V 逻辑而 51 是 5V,TXD 从 51 到模块 RXD 之间要加电平转换,最简单的做法是串一个 1kΩ 电阻再并一个 3.3V 稳压管,或者直接用双向电平转换模块。模块 TXD 到 51 RXD 一般可以直连,因为 3.3V 高电平通常能被 51 识别为高。但如果你发现接收数据偶尔错位,先查这一路。
2.2 透明传输模式下数据是怎么走的
串口型 LoRa 模块出厂通常默认透明传输:模块不关心你发的内容,收到串口数据就打包发到空中,空中收到数据就从串口原样吐出。这意味着 51 单片机不需要处理任何 LoRa 协议栈,只需要像用有线串口一样收发。数据路径是:
51 的 SBUF → 模块 RXD → 模块内部 FIFO → 空中 → 对端模块 FIFO → 对端模块 TXD → 对端 51 的 SBUF。
这条链路里,51 只负责 UART 收发,模块负责调制解调。所以调试时可以把模块先当“无线串口线”用:两台电脑各接一个 LoRa 模块,串口助手互发,通了再接单片机。这个顺序能帮你把问题隔离在“模块配置”还是“单片机程序”上。
2.3 为什么 51 的串口配置必须和模块严格一致
51 的 UART 是异步串口,没有时钟线,收发双方靠约定的波特率采样。模块的串口波特率、数据位、停止位、校验位必须和 51 完全一致。常见模块默认 9600、8N1,而 51 用 11.0592MHz 晶振时,9600 的定时器初值是 TH1=0xFD、TL1=0xFD(SMOD=0)。如果你用的是 12MHz 晶振,9600 会有约 8.5% 的误差,短帧还能凑合,长帧必翻车。所以做串口通信的 51 板子,晶振优先选 11.0592MHz,这是血泪经验。
另外,模块还有一组“空中参数”:频率信道、扩频因子、带宽、编码率、同步字。收发双方这些参数必须完全一致,否则串口再对也收不到。透明传输下,这些参数一般用模块厂商的上位机配置一次,掉电保存。配置时注意:改完要重启模块,有些模块不重启不生效。
3. 51 串口收发代码:从寄存器配置到中断收包
3.1 串口初始化与波特率计算
下面这段是 11.0592MHz 晶振、9600、8N1、允许接收的初始化。用定时器 1 做波特率发生器,工作在模式 2(8 位自动重装)。
#include <reg52.h> void UartInit(void) { SCON = 0x50; // 模式1,8位UART,允许接收 REN=1 TMOD &= 0x0F; // 清除定时器1模式位 TMOD |= 0x20; // 定时器1,模式2,8位自动重装 TH1 = 0xFD; // 9600 @ 11.0592MHz, SMOD=0 TL1 = 0xFD; TR1 = 1; // 启动定时器1 ES = 1; // 开串口中断 EA = 1; // 开总中断 }逻辑说明:SCON=0x50 里,SM0SM1=01 是模式 1(8 位可变波特率),REN=1 允许接收。TMOD 的低 4 位保留给定时器 0,高 4 位设成 0x20 让定时器 1 工作在模式 2。TH1/TL1 装初值 0xFD,对应波特率 9600。ES 和 EA 打开中断后,每收到一个字节,RI 置位并进中断。
参数怎么改:如果模块波特率是 115200,11.0592MHz 下 TH1=TL1=0xFF(SMOD=0)或 0xFD(SMOD=1,PCON|=0x80)。更稳妥的做法是查表,不要手算。如果晶振是 12MHz,9600 的初值是 0xF3,但误差大,不建议。
3.2 发送一个字节与发送字符串
发送时把数据写进 SBUF,然后等 TI 置位,软件清零。
void UartSendByte(unsigned char dat) { SBUF = dat; while (!TI); // 等待发送完成 TI = 0; // 手动清零 } void UartSendString(unsigned char *str) { while (*str) { UartSendByte(*str++); } }逻辑说明:SBUF 是发送和接收共用的寄存器地址,写 SBUF 触发发送,读 SBUF 取出接收数据。TI 在发送完成后由硬件置 1,必须软件清零,否则下一次 while(!TI) 会直接跳过。发送字符串就是循环调用单字节发送,直到遇到 '\0'。
注意:如果在中断里也调用了 UartSendByte,而主循环也在调,可能出现 TI 竞争。简单做法是发送统一放主循环,中断只负责收。如果非要在中断里发,发之前先关 ES,发完再开。
3.3 中断接收与环形缓冲
接收如果直接在中断里处理业务,容易丢字节。常见做法是中断只把数据塞进环形缓冲,主循环再取。
#define BUF_SIZE 64 unsigned char rxBuf[BUF_SIZE]; volatile unsigned char rxHead = 0; volatile unsigned char rxTail = 0; void UartISR(void) interrupt 4 { if (RI) { RI = 0; // 必须软件清零 rxBuf[rxHead] = SBUF; rxHead = (rxHead + 1) % BUF_SIZE; } } bit RxAvailable(void) { return (rxHead != rxTail); } unsigned char RxGetByte(void) { unsigned char dat = rxBuf[rxTail]; rxTail = (rxTail + 1) % BUF_SIZE; return dat; }逻辑说明:interrupt 4 是 51 的串口中断号。RI 置位表示收到一个字节,读 SBUF 取走数据后必须软件清 RI。rxHead 和 rxTail 构成环形队列,RxAvailable 判断有没有新数据,RxGetByte 取一个字节并移动尾指针。BUF_SIZE 取 64 够一般遥测帧用,如果帧长超过 64,要么加大缓冲,要么在中断里做帧头判断。
参数说明:rxHead 和 rxTail 用 volatile 修饰,防止编译器优化导致主循环读到旧值。如果主循环处理慢,缓冲会满,此时新数据会覆盖旧数据,表现为“丢包”。解决要么加快主循环,要么加大 BUF_SIZE,要么在缓冲满时做丢弃计数。
3.4 一个最小收发验证流程
把上面拼起来,主循环里做一件事:收到什么就回什么。
void main(void) { unsigned char dat; UartInit(); UartSendString("51 LoRa ready\r\n"); while (1) { if (RxAvailable()) { dat = RxGetByte(); UartSendByte(dat); // 回显 } } }逻辑说明:上电先发一句提示,确认单片机到模块的发送链路通。然后主循环不断检查缓冲,有数据就回显。把两个这样的节点分别接 LoRa 模块,A 节点串口助手发 “A”,B 节点串口助手应该收到 “A”,反之亦然。如果只有本地回显没有远端,问题在模块参数或空中链路。
4. 模块参数对齐与避坑排查:收不到先查这五处
4.1 空中参数不一致导致“串口通、空中不通”
现象:两个模块分别接电脑,串口助手互发,本地能看到自己发的,对端收不到。原因:频率信道、扩频因子、带宽、同步字至少有一项不同。解决:用模块上位机把两个模块的参数读出来逐项对比,重点看信道和同步字。有些模块出厂信道是 0x17,有些是 0x12,不统一。改完记得点“保存”并重启。
4.2 波特率误差导致长帧乱码
现象:短命令能通,一发几十字节的长帧就出现错位、乱码。原因:51 晶振不是 11.0592MHz,或者模块波特率和单片机不一致。解决:换 11.0592MHz 晶振;用示波器或逻辑分析仪抓 51 的 TXD 波形,量一个位宽,反推实际波特率。常见做法是先用 9600 低速验证,通了再往上提。
4.3 发射瞬间复位或死机
现象:一发送就重启,或者发送后程序跑飞。原因:LoRa 发射电流把电源拉低,51 欠压复位。解决:给模块单独供电或加大滤波电容;在模块 VCC 就近并 100μF+0.1μF;如果模块有使能脚,发射前确认使能电平正确。另外,51 的看门狗如果开着,发射等待时间过长可能触发,先关看门狗调试。
4.4 中断里清 RI 顺序写反
现象:接收偶尔丢第一个字节,或者进一次中断收多个。原因:在中断里先读 SBUF 再清 RI,或者清了 RI 又读了一次 SBUF。解决:标准顺序是 if(RI){ RI=0; dat=SBUF; },先清标志再读数据,避免读操作影响标志。这个顺序在 51 上很关键,写反了会出现玄学丢包。
4.5 天线没接或接错
现象:近距离(同一房间)能通,隔一堵墙就断。原因:模块没接天线,或者天线接口类型不匹配(SMA 公头母头搞混),或者天线频段不对(433MHz 模块接了 868MHz 天线)。解决:确认天线频段和模块一致,拧紧接口。没有天线时不要长时间发射,容易损坏功放。测试时至少接一根匹配的弹簧天线。
5. 进阶技巧:用帧头帧尾做可靠收包与现场验证
透明传输下,接收端不知道一帧从哪开始到哪结束。如果 51 主循环处理慢,或者空中偶发干扰,很容易把两帧粘在一起。我一般会在应用层加一个简单帧格式:帧头 0xAA 0x55、长度、载荷、校验和。接收端在环形缓冲里找帧头,找到后按长度取载荷,校验通过才处理。这样即使丢一个字节,也能在下一帧重新同步。
// 发送端组帧示例 void SendFrame(unsigned char *payload, unsigned char len) { unsigned char i, sum = 0; UartSendByte(0xAA); UartSendByte(0x55); UartSendByte(len); for (i = 0; i < len; i++) { UartSendByte(payload[i]); sum += payload[i]; } UartSendByte(sum); // 简单累加和校验 }逻辑说明:帧头两个字节用于同步,len 告诉接收端后面有多少载荷,sum 是载荷累加和。接收端状态机先找 0xAA,再找 0x55,然后读 len,再读 len 个字节,最后读校验和并比对。校验不过就丢弃,重新找帧头。这个格式开销小,51 上跑毫无压力。
参数怎么定:帧头选 0xAA 0x55 是因为二进制里交替出现,不容易和普通数据混淆。如果载荷里本身可能出现 0xAA 0x55,要么做转义,要么换更长的帧头。len 用 1 字节,最大 255,够一般遥测。校验和用累加和最简单,要求高可以换 CRC8,但 51 上算 CRC8 也不慢。
现场验证我习惯分三步:第一步,两个模块接电脑,串口助手互发长帧,确认空中链路稳定;第二步,一个模块接电脑,一个接 51,电脑发 51 收,51 回显,确认 51 收发正常;第三步,两个 51 互发,用串口助手分别看两边。每一步只改一个变量,出了问题立刻能定位。从那以后我每次调无线串口,都强制先做这三步,不再一上来就焊死两颗单片机对调。希望帮到你。
本文还有配套的精品资源,点击获取