LoRa APRS实现指南:从TTGO T-Beam到STM32的AX.25报文通路

发布时间:2026/9/16 12:15:56

LoRa APRS实现指南:从TTGO T-Beam到STM32的AX.25报文通路 简介这是一份面向TTGO T-Beam开发板的LoRa APRS项目源码包供无线电爱好者、物联网开发者和嵌入式初学者参考。项目基于LoRa低功耗广域网技术实现APRS位置报告与短消息收发适用于户外追踪、应急通信、远程遥测等场景帮助读者理解从硬件驱动到无线协议栈的完整链路。包内共12个文件以ino主程序、h/cpp源文件为主辅以md/readme安装说明、platformio.ini工程配置和示例图片压缩包仅107KB轻量易读。目前已有260人学习下载。资源中给出了可编译的Arduino工程入口、配置头文件和依赖库说明读者可借助PlatformIO快速烧录到TTGO T-Beam并结合源码学习LoRa调制参数配置、APRS报文封装、GPS定位数据解析等关键实现也可在此基础上扩展双向通信或中继功能是入门LoRa APRS开发的实用参考。1. TTGO T-Beam 上的 LoRa APRS从 ESP32 到 STM32 的报文通路初学者手里那块 TTGO T-Beam 往往先被当作一块带 GPS 的 ESP32 开发板真正让它区别于普通板卡的是板载 SX1276 射频前端。当 LoRa 上要跑的不是裸串口数据而是 APRS 报文时很多人会发现官方例程能发射、能收 RSSI却解不出 AX.25 地址也发不出能被第三方 APRS 客户端识别的信标。这篇文章不站在评测角度只讲从 T-Beam 到 STM32 都适用的 LoRa APRS 实现路径先理清 AX.25 封装与 SX1276 参数绑定再给出发射与接收的最小代码最后落到频偏校准与误码率验证。适合正在入门 LoRa 通信或打算把 APRS 节点迁到 STM32 做工程项目的读者。2. APRS over LoRa 的帧结构AX.25 封装与 SX1276 参数选型2.1 为什么 APRS 走 LoRa 而不是 LoRaWANAPRS 的协议栈是“应用层 链路层”组合链路层是 AX.25。它没有定义物理层传统上跑在 VHF 电台的 AFSK 上LoRa 恰恰只提供物理层并且不强制入网、不强制时间同步。因此在 LoRa 上承载 APRS 的标准做法是把 AX.25 的完整 UI 帧当作一个普通负载直接通过 SX1276 的“原始包模式”又称显式报头模式发射出去。发送端和接收端配置同一组扩频参数即可不需要网关。与之对比LoRaWAN 在物理层之上多了一层 MAC设备必须完成 Join 流程、分片传输和数据速率自适应。对 APRS 这种“每隔几十秒广播一个短信标”的场景LoRaWAN 的入网开销、下行窗口和确认机制不仅多余还会让节点被视为非法设备而拒绝服务。你更想要的是一个完全直连、没有服务器依赖的通道接收方拿到 payload 后自己解 AX.25 地址、自己判断是否需要转发。下表把两条路径的核心差异列清楚需要的能力LoRaWANRaw LoRa AX.25入网/激活必须 OTAA/ABP无需频道规划按区域配置自定义单频确认机制Class A 下行窗口可选APRS 接收端需网关转换原生协议2.2 AX.25 UI 帧从 Flag 到 FCS一个典型 AX.25 UI 帧的字节顺序是Flag (0x7E)目的地址 7 字节源地址 7 字节Control 0x03PID 0xF0信息字段 InfoFCS 两字节结尾 Flag。地址字段 7 字节中的前 6 字节是文本呼号不足 6 位补空格第 7 字节的高位字节包含了 SSID、最后一个地址标记以及用于 HDLC 链路复用的保留位。由于 AX.25 的 LSB 先传写代码时最好按一个字节一个字节拼别直接把字符串指针塞进结构体。FCS 是 CRC-CCITT 的变体初值 0xFFFF逐字节用 0x8408 多项式右移计算计算完毕后按位取反发送时低字节在前。写代码时最常见的 bug 是把多项式写成 0x1021或者忘记取反。下面的函数是可靠版本uint16_t ax25_fcs_update(uint16_t fcs, uint8_t byte) { uint8_t i; for (i 0; i 8; i) { fcs ((fcs ^ byte) 1) ? (fcs 1) ^ 0x8408 : (fcs 1); byte 1; } return fcs; }这段代码每次处理一个字节右移判位决定是否异或多项式正好对应 AX.25 规定的 LSB-first 传输顺序。组装一帧时先把地址、控制字、PID 和 Info 放入缓冲计算两字节 FCS 追加在末尾发送前还要做比特填充遇到连续五个 1 就插入一个 0避免与 Flag 0x7E 混淆。LoRa 的显式报头不会替你完成这一步所以填充逻辑必须自己做或者干脆依赖内容里极少出现连续五个 1 这个事实在工程上做取舍。2.3 APRS 信标的文本封装APRS 的 Info 字段格式不像 AX.25 那么严格但能被通用客户端解析的信标至少包含位置和符号。常见的无地面站版本格式是!DDMM.mmN/DDDMM.mmW$其中!表示这是一个无时间戳定位信标后面依次是纬度、北/南标志、经度、东/西标志末尾的$是气象站符号。若带上高度可在符号后追加/A000000。如果你只需要验证链路也可以先跳过坐标直接发一条带序号和时间的自由文本很多 APRS 接收软件也按“非定位消息”展示。2.4 SX1276 参数与 APRS 的配对选择直连模式下两端必须完全一致的四组参数是扩频因子 SF、信号带宽 BW、编码率 CR 和是否开启 CRC。对 APRS 信标这种短负载我一般建议 SF8/BW125kHz/CR4/5这个组合在 T-Beam 和 STM32 上都能把单帧空中时间控制在 100ms 上下同时保持 -126dBm 量级的灵敏度。下表给出三种常用档位场景SFBWCR灵敏度量级单帧空中时间适用距离实验室近距离验证7125kHz4/5-123dBm约 40ms几百米城市 APRS 节点8125kHz4/5-126dBm约 100ms1-3km远距离固定站12125kHz4/8-136dBm约 1500ms5km 以上SF12 的空中时间会显著变长如果 APRS 广播周期是 60 秒就要对照占空比要求计算实际占比不要无脑拉高。同一组参数下还要设置同步字为 0x12LoRa 默认启用显式报头并把净荷长度设为你要发送的整帧长度STM32 移植时尤其要记得 LoRa 的 FIFO 一次最多 255 字节AX.25 信标一般只有 30-80 字节不会碰到上限但若后续加 digipeater 路径指示就会变紧。3. 在 TTGO T-Beam 上先把 LoRa APRS 信标发出去3.1 引脚映射与 SPI 初始化TTGO T-Beam 有两种常见批次最新主板上 SX1276 与 ESP32 的连接是SCKGPIO5、MISOGPIO19、MOSIGPIO27、NSSGPIO18、RSTGPIO23、DIO0GPIO26。拿到板子后第一件事不是写代码而是用 gpio 测试把 RST 拉低再拉高确认芯片 VDD 正常买来二手板特别容易在焊接时把 NSS 和 DIO0 搞反。Arduino 环境下SPI 用SPI.begin(5, 19, 27, 18)并设置模式 0、主频 400kHz 起调稳定后再提到 4MHz。SPI.begin(SCK_GPIO, MISO_GPIO, MOSI_GPIO, NSS_GPIO); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); sx1276_reset(); // 拉低 RST 延时 10ms 再拉高设置 SPI 模式 0 是因为 SX1276 在 CPOL0/CPHA0 下读取数据频率高时 SPI 时序余量变小若发现在 4MHz 下间歇性寄存器读取错误先降回 1MHz再用示波器看 MISO 的建立时间。reset 之后可以读版本号寄存器0x42T-Beam 上通常返回 0x12SX1278 返回 0x12 或 0x22不同批次略有差异。初始化时要写 4 个关键寄存器块RegOpMode 设为 LoRa Sleep 模式RegModemConfig1 设为 0x74BW125kHz、CR4/5、显式报头RegModemConfig2 设为 0x74SF8、CRC 开启RegModemConfig3 设为 0x04AGC 自动开启。对应关系是 Config1 的 bit6-4 为带宽bit3-1 为编码率bit0 为报头模式0x74 的二进制恰好是0111 0100SF 位于 Config2 的 bit7-4bit3 是发送连续模式位bit2 是 CRC 位写 0x74 意味着 SF8 无连续 CRC 开。3.2 发射一段 AX.25 帧的最小代码先组装帧再写 FIFO最后进发送模式。下面这个发送函数可以直接搬去掉 GPS 依赖信息字段写死一条测试文本void send_aprs_beacon() { uint8_t frame[96], pktLen 0; uint8_t dst[7] {A,P,R,S, , , 0x00}; uint8_t src[7] {B,G,7,A,B,C, 0x00}; frame[pktLen] 0x7E; // 帧头 Flag for (int i 0; i 6; i) frame[pktLen] dst[i]; frame[pktLen] dst[6] | 0x60; // 末地址位 SSID0 for (int i 0; i 6; i) frame[pktLen] src[i]; frame[pktLen] src[6] | 0x61; // 带路径无关位 frame[pktLen] 0x03; // Control: UI frame[pktLen] 0xF0; // PID uint8_t info[] !4725.34N/01905.50E; memcpy(frame pktLen, info, strlen((char*)info)); pktLen strlen((char*)info); uint16_t fcs 0xFFFF; for (int i 1; i pktLen; i) fcs ax25_fcs_update(fcs, frame[i]); fcs ^ 0xFFFF; frame[pktLen] fcs 0xFF; frame[pktLen] fcs 8; // 写入 SX1276 FIFO基地址 0 sx1276_write(REG_FIFO_ADDR_PTR, 0); for (int i 0; i pktLen; i) sx1276_write(REG_FIFO, frame[i]); sx1276_write(REG_PAYLOAD_LENGTH, pktLen); sx1276_write(REG_OP_MODE, 0x83); // LoRa TX // 轮询 DIO0 直到 TxDone 中断标志置位 while ((sx1276_read(REG_IRQ_FLAGS) 0x08) 0); sx1276_write(REG_IRQ_FLAGS, 0x08); }这里有几个容易被忽略的参数dst第七字节写 0x00src第七字节写 0x610x60 的含义是“最后一个地址字节”标记加 SSID00x61 则额外带上“从站路径无关”的 bit0。FCS 计算从帧内第一个字节地址算到 PID 之后不包含开头和结尾的 Flag计算完之后把 FCS 的低字节放在前、高字节放后。发送时 RegOpMode 写 0x83二进制是1000 0011bit7 为 LoRa 长程模式bit1-0 是 11 表示 TX 模式。轮询 DIO0 之前记得先清 IRQ 标志否则上一帧的遗留标志会让你误判发送完成。3.3 空中时间与占空比约束当你的 APRS 信标固定为 34 字节SF8/BW125 下LoRa 物理层的空中时间按 Semtech 公式可算得约 103ms。用下面的 Python 脚本可以快速估算SF 8 BW 125_000 PL 34 CR4 5 # CR 4/5 - 分母写成 5 t_sym 2 ** SF / BW n_pre 8 4.25 payload_sym 8 max(0, (8 * PL - 4 * SF 28 16 - 20)) / (4 * (SF - 2 * 0)) * (CR4 4) airtime (n_pre payload_sym) * t_sym print(fairtime {airtime * 1000:.1f} ms)脚本里16对应 CRC 开启-20对应显式报头。把这个结果乘上报文间隔就是占空比如果每 60 秒发一次占空比约 0.17%每 5 秒一次则到 2%。公共频段合规设计要让占空比尽量低APRS 信标的建议广播周期也不要小于 30 秒否则频道里全是你自己的丢包和重试信息。4. 把 LoRa APRS 搬到 STM32串口调度与状态机收发4.1 驱动层改造的差异处理从 ESP32 的例程思路搬到 STM32 时最大的差异不是寄存器而是时间基线和中断响应方式。ESP32 的 Arduino 例程可以在loop()里轮询 DIO0STM32 上我更建议把 DIO0 接入一个 EXTI 外部中断在中断服务函数里只置标志位主循环按状态机处理收发这样既不会阻塞 LoRa 的接收也能让 GPS 串口中断与射频中断互相独立。SPI 初始化使用 STM32 的硬件 SPI模式仍为 CPOL0、CPHA0MSB first。用 STM32CubeMX 生成立方体工程时先把 SPI 速率设到 1MHz 以下确认能读到 SX1276 的版本号后再逐步提升。注意 SX1276 对片选的依赖很强在 HAL 的HAL_SPI_TransmitReceive前后拉低、拉高 NSS否则一次读写可能串到另一个寄存器的值。避免直接在中断里调用 HAL_SPI 收发标准做法是中断中置位一个volatile uint8_t rx_pending主循环里执行读取。4.2 用串口注入 GPS 坐标并组装信标GPS 模块输出 NMEA 语句其中$GPRMC包含定位状态、经纬度与 UTC 时间。STM32 上常见做法是串口 DMA 接收把整包$GPRMC存入缓冲区等换行符作为一帧接收完成。GPS 模块的默认波特率不统一下表是常用匹配GPS 模块型号默认波特率注意事项Ublox NEO-6M9600T-Beam 板载版本需解锁命令ATGM336H9600常见于国产模块串口转 TTL 调试板115200需按模块手册修改下面的函数从缓冲区中提取纬度和经度字符串并判断是否为有效定位static float gps_lat, gps_lon; int parse_gprmc(char *buf) { // 按逗号分隔字段2是状态字段3是纬度字段5是经度 char *p strstr(buf, $GPRMC); if (!p) return -1; if (strncmp(p 7, ,A,, 3) ! 0) return -1; // A有效定位 char lat_str[16], lon_str[16]; sscanf(p, $GPRMC,,A,%15[^,],N,%15[^,],E, lat_str, lon_str); gps_lat atof(lat_str); gps_lon atof(lon_str); return 0; }这段代码取$GPRMC后第 3 和第 5 个字段分别对应方向为 N 和 E 的坐标。若模块电平是 3.3V串口直接接 STM32 即可5V 模块要在 RX 线上做分压。串口调试时建议把 NMEA 输入和调试信息放在两个不同串口否则打印日志会污染 GPS 数据帧。解析完成之后把经纬度格式化进 APRS 信标的 Info 字段调用第 3 节的发送函数变体把原先写死的info替换成!4725.34N/01905.50E。注意浮点转字符串时要补足前导零4725.34对应“度分”格式不要写成十进制度数很多接收端严格按字符长度解析。4.3 接收侧的状态机从 DIO0 中断到 AX.25 解析接收侧要先配置连续的 RX 模式RegOpMode 写 0x85LoRa RXCONTRegIrqFlagsMask 不要屏蔽 DIO0。当 DIO0 上升沿到来SX1276 的 RX DONE 中断标志置位主循环里可以像下面这样把 FIFO 读出来void lora_rx_handler(void) { uint8_t irq sx1276_read(REG_IRQ_FLAGS); if (irq 0x40) { uint8_t len sx1276_read(REG_RX_NB_BYTES); uint8_t buf[128]; sx1276_write(REG_FIFO_ADDR_PTR, sx1276_read(REG_FIFO_RX_CURRENT_ADDR)); for (uint8_t i 0; i len; i) buf[i] sx1276_read(REG_FIFO); // 校验 FCS去掉首尾 0x7E 后余下部分是地址控制PIDInfo sx1276_write(REG_IRQ_FLAGS, 0x40); ax25_parse(buf, len); } }IRQ 标志 0x40 是 RXDone 位读取 FIFO 后必须清这个位否则 LoRa 不会再进新包。解析 AX.25 时要先把地址字段与源地址分离第 0-6 字节是目的地址第 7-13 字节是源地址第 14 字节是 Control第 15 字节是 PID第 16 字节开始是 Info。很多实现卡在“为什么收到的内容比发送端多了两个字节”——原因是发送时把 FCS 两字节也写入了 payload接收端拿到的整个 AX.25 UI 帧就是 19 字节头部 Info 2 字节 FCS加上结尾 Flag长度刚好对得上。FCS 校验把整帧不含 Flag 和 FCS 本身重算一遍比对帧尾两字节即可若物理层 CRC 错误SX1276 会在 IRQ 里报 CRC_ERR不会触发 RXDone 中断所以你的解析函数只会在物理层 CRC 和链路层 FCS 双重检查都通过后才会被调用。5. 最后一项用频偏校准和误码率验证 T-Beam 与 STM32 的互通5.1 频偏补偿用 SNR 作为校准指示器LoRa 接收端解调对频偏极为敏感。SX1276 的晶振通常为 32MHz1ppm 的晶振偏差在 433MHz 频段约产生 433Hz 偏差SF8/BW125 的解调带宽只有 125kHz看似容忍度不小但当两个节点的晶振误差叠加达到 500Hz 以上时SNR 会肉眼可见地下降。比较实用的校准办法是让发送端循环发信标接收端一边实时读REG_PACKET_SNR0x19一边周期性修改发送端的RegFrf三字节。步进按 61Hz即Frf值的 LSB来调记录下 SNR 最高的频点作为两板共享的基准。在没有频谱仪的情况下可以这样操作先在空间开阔处把两板靠近放置发送端固定一个发送周期接收端在 MCU 侧扫描一组频点例如从基准频率 -3kHz 到 3kHz每次步进 500Hz统计每个频点上收到的包数和平均 SNR。扫描代码可以放在接收端比对完后把最优频点的修正值写进双方配置文件。两个板子校准完后后续换板时先读一下新板晶振的标称 ppm换算成频偏增量写入即可不需要整段重测。5.2 用带序号的广播包统计误码率校验链路稳定性时可以给 APRS 信标的 Info 末尾追加一个两字节序号例如!4725.34N/01905.50E#0127。接收端解析#后的数字记录每个序号是否缺失就能得出这段时间的丢包率。若两端都是 STM32可以在接收端把序号和接收时的 PacketSnr 值一起写入串口形成一张简单的链路质量表序号SNR丢包间隔判断1279.5-正常1289.8-正常1317.22 包可能有遮挡或频偏漂移实际操作时先让发送端固定以 1 秒间隔发 100 包接收端只统计序号连续性和 SNR 分布。低于 90% 甚至低于 80% 时先看空中时间是否正确、功率配置是否生效再看天线端是否接触不良。T-Beam 的 U.FL 连接器非常容易脱落撕掉保护贴纸时经常把射频馈线一起拉掉这是很多人掉进的一个隐蔽坑。还可以顺手加一个技巧在接收端的 FCS 校验失败分支里打一条调试串口记录能区分“物理层没收到”和“收到了但 AX.25 校验失败”。前者多半是频偏、功率或天线问题后者多半是比特填充或帧组装错误。这一信息对调双板互通很有价值尤其是在两板相隔几米却始终收不到完整信标时先看这两类日志再决定是拧天线还是改代码。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/16 12:10:51

Comsol光子晶体能带计算:模式分离与伪模过滤

1. 光子晶体能带计算的核心挑战在光子晶体器件的设计与优化过程中,能带结构计算是最基础也是最重要的环节之一。Comsol作为一款成熟的多物理场仿真软件,其波动光学模块为光子晶体能带分析提供了便捷的求解环境。但在实际计算中,工程师们常常会…

2026/9/16 14:06:10

PHP原生CC防护系统:请求指纹+动态限流+验证码闭环

简介:这是一套面向PHP开发者与Web安全初学者的轻量级CC攻击防护实践源码,聚焦于解决PHP网站在高并发场景下易遭模拟请求式DDoS(即CC攻击)导致服务瘫痪的问题。资源共14个文件,含7个核心PHP脚本(如anti_ddos…

2026/9/16 14:06:10

C51单片机实时频谱分析:滑动DFT定点实现与Keil工程落地

简介:本资源是面向嵌入式开发工程师与数字信号处理学习者的C51平台滑动DFT(Sliding DFT)完整实现方案,聚焦实时频谱分析场景下的轻量级算法落地。针对C51单片机内存与算力受限的特点,资源提供可直接移植的优化代码、原…

2026/9/16 14:06:10

React Native与鸿蒙SVG滤镜跨平台开发实践

1. 项目概述:React Native与鸿蒙的SVG特效融合开发在跨平台移动开发领域,React Native与鸿蒙系统的结合正成为技术新趋势。这次我们要探讨的是如何在这两个平台上实现SVG图形的高级滤镜特效——这不仅是视觉呈现的升级,更是性能优化的重要实践…

2026/9/16 14:06:10

Spring Boot+Vue民宿预订系统实战:从数据模型到并发控制

简介:整套源码基于JavaSpringbootVue搭建,是典型的前后端分离民宿预订管理系统,面向计算机专业毕业生、在校生以及需要快速上手企业级项目的初中级开发者。系统后端由79个Java源文件构成,涉及服务端逻辑处理、数据持久化、业务逻辑…

2026/9/16 14:01:09

机器视觉工控机选型:从产线稳定性到实时控制的四阶筛选法

1. 项目概述:为什么“机器视觉适合用什么工控机”不是个随便选配置的问题干过三年以上机器视觉落地项目的人都知道,现场调试最让人头皮发麻的时刻,往往不是算法调不准,也不是光源打不好,而是——工控机在产线跑满8小时…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/16 0:04:09

PHP源码部署实战:从环境配置到运行情侣游戏全攻略

简介:这是一套面向情侣互动场景的PHP完整源码,集成情侣飞行棋、真心话大冒险、情趣骰子等玩法,并内置完整分销制度,可自定义多种返佣比例,源码完全开源无加密,支持微信无感自动授权登录与第三方授权&#x…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/15 21:31:11

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码