ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

TTGO T-Beam LoRa APRS实战:从硬件接线到固件调优

TTGO T-Beam LoRa APRS实战:从硬件接线到固件调优

简介:这份资源是围绕TTGO T-Beam LoRa APRS实现的完整开源工程包,面向物联网开发者、业余无线电爱好者以及嵌入式编程学习者,用于在LoRa网络下搭建自动位置报告系统,解决远距离数据传输与移动设备追踪问题。压缩包共12个文件,整体仅107KB,核心包括Arduino主程序、LoRa配置头文件、射频驱动库、安装说明与PlatformIO构建配置,并附带示例图片和readme文档,对硬件接线与依赖库也有说明,便于快速在PlatformIO或Arduino IDE中完成编译烧录。目前已有261人学习或下载,对于刚接触LoRa或APRS协议的读者,也是一份便于对照学习的代码范例。通过该工程可以学习LoRa射频参数的初始化与配置、APRS报文的构造与解析、GPS位置信息的读取与发送,以及低功耗通信策略,适合借鉴其代码结构实现基于STM32/ESP32的远程位置追踪终端;同时目录划分清晰,便于按需提取模块,是动手实践无线通信和嵌入式系统的实用参考。

1. TTGO-T-Beam-LoRa-APRS-master:先别急着刷,把这串名字拆开再说

TTGO-T-Beam-LoRa-APRS-master_LORASTM32_ 这个仓库名很长,拆开其实就三件事:TTGO T-Beam 是那块带 LoRa 射频和 GPS 的 ESP32 开发板,LoRa-APRS 是业余无线电里通过 LoRa 传输 APRS 位置报文的玩法,而 master 和 LORASTM32 分别代表主分支和一条容易被忽略的 STM32 协处理器路径。很多人拿到板子直接烧固件,结果要么收不到信标,要么 GPS 数据进了黑匣子,最后只能把板子扔在抽屉里。写这篇笔记,就是把从硬件确认、固件配置到参数调优的路子完整走一遍,让新手照做能上线,熟手避开我已经踩过的坑。

2. 从硬件开始:T-Beam 上的 LoRa、GPS 和那颗 STM32 到底怎么配合

2.1 认识 TTGO T-Beam:ESP32 主控和 LoRa 前端的布局

T-Beam 这块板子的核心是 ESP32 负责协议、屏幕和网络,LoRa 收发器常用 SX1276,挂在 ESP32 的 SPI 总线上,GPS 模块通过 UART 输出 NMEA 语句。板上还有一颗低功耗 MCU 在这类项目里经常被忽略,也就是标题里 LORASTM32 可能指向的 STM32 角色——它不直接参与 APRS 解析,却能承担 LoRa 调制解调和 GPS 待机唤醒的低功耗控制。

先确认你的板子型号。T-Beam 有多个硬件修订版本,0.6 和 0.7 的引脚定义略有不同,0.8/1.0 又把 GPS 电源控制改到了独立 MOS 管上。我见过有人拿 0.7 的接线图去驱动 1.0 的板子,结果 GPS_UART 的 TX/RX 对不上,串口日志全是奇怪符号。建议先查丝印,再读板子背后的版本号。

常见引脚分配是这样:LoRa 的 SPI 接在 VSPI(GPIO18/19/23/5),复位脚 GPIO14,中断脚 GPIO26;GPS 的 TX/RX 接 GPIO12/15;而 STM32 如果存在,通常通过 I2C 或 UART 与 ESP32 通信,控制 LoRa 的射频频段切换和休眠节电。

2.2 LORASTM32 到底是固件分支还是协处理器方案

我在实战中看到过两种理解。第一种,LORASTM32 是一个针对 STM32 主控的 LoRa APRS 固件分支,因为原始 APRS 固件基于 ESP32 的 WiFi 和蓝牙,功耗偏高,有人把它移植到 STM32 低功耗平台,只保留 LoRa 发射和 GPS 接收。第二种,T-Beam 板子本身留了一颗 STM32,专门做 GPS 数据预处理和 LoRa 定时唤醒,ESP32 只在需要联网上报时才启动。

从仓库名 master_LORASTM32_ 来看,更像是把两条代码路径放在一起对比:master 分支给 ESP32 完整功能,LORASTM32 分支针对低功耗手持追踪器。我的建议是,先跑通 master 再碰低功耗分支,因为前者社区资料多,日志输出直观,适合验证射频链路;后者省电但排错困难,一旦 GPS 位置偏低,你很难判断是 STM32 没唤醒还是 LoRa 寄存器配置错了。

如果你拿到的是带 STM32 的 T-Beam Combo 版本,注意 ESP32 和 STM32 之间的握手信号。常见做法是 ESP32 通过 GPIO 拉低 STM32 的复位脚,启动后读取共享内存或串口帧。这部分代码在仓库里通常藏在 hal 目录下,别只盯着 app 层。

2.3 接线与天线检查:SPI、UART 和射频链路

无论哪个分支,动手前先做三处检查。第一,LoRa 天线座是否牢固。T-Beam 用的是 U.FL 座,原装没有外置天线,只焊了一小截弹簧天线,如果你要接外接天线,需要一根 U.FL 转 SMA 的跳线,注意 SMA 座必须是带母头的射频线,别接成普通杜邦线。

第二,确认 GPS 天线是否带供电。有源天线需要 3.3V 供电,T-Beam 的 GPS 模块通常已经包含偏置,但如果你外接大天线,要把天线的供电脚和信号脚接对,否则串口里只有开头的 GPGGA 语句,后面全静默。

第三,SPI 接线不要凭记忆。我一般用万用表量一下 LoRa 模块的 NSS 和 RESET 脚到 ESP32 GPIO 的通断。经验是 SX1276 的 NSS 对应 GPIO18,SCK 是 GPIO5,MOSI 是 GPIO23,MISO 是 GPIO19。如果顺序错了,写入寄存器没有报错但射频完全无声,这是最玄学的地方。

2.4 用两个命令确认 LoRa 模块是否活了

烧固件前先跑一遍寄存器读写测试。可以用一个最小 ESP32 程序,把 LoRa 的版本寄存器 0x42 读出来,SX1276 正常返回 0x12,如果读到 0xFF 说明 SPI 通道没通或模块供电异常。代码很简单:

#include "driver/spi_master.h" #include "esp_log.h" void lora_read_version(void) { // 读取 SX1276 版本寄存器,地址 0x42 uint8_t addr = 0x42; uint8_t val = 0; spi_transaction_t t = { .tx_buffer = &addr, .rx_buffer = &val, .length = 16, .rxlength = 8, }; spi_device_transmit(handle, &t); ESP_LOGI("LORA", "Version: 0x%02X", val); }

这段逻辑是发送 0x42 地址并同时接收 8 位返回值,SPI 全双工模式下,读寄存器就是先写地址字节再读数据。参数要注意:传输长度是 16 位,因为地址和读数据在一次事务里完成,rxlength 设为 8 位只取第二个字节。如果返回 0xFF,大概率是 MISO 没接好或模块没上电。

3. 烧录 APRS 固件:从串口驱动到首次开机的完整流程

3.1 编译环境怎么选:ESP-IDF 还是 Arduino

TTGO-T-Beam-LoRa-APRS-master 这个项目我最早用 PlatformIO + Arduino 框架编译,后来发现要改 GPS 低功耗逻辑还是得回到 ESP-IDF。Arduino 的好处是串口监视器直接显示 USB 转串口输出,APRS 报文解析的库又多,适合新手快速看效果。ESP-IDF 的好处是内存管理透明,能拿到更详细的错误堆栈,适合后续做 LoRa 网关和深度休眠。

我的选择是:先装 PlatformIO,创建 board 选择 tbeam,framework 选 Arduino,这样同时兼顾易用性和工程组织。项目里有一个 platformio.ini,里面定义了 tbeam 的环境,烧录脚本默认串口是 /dev/ttyUSB0,Windows 上是 COMx,这个后面要按自己系统改。编译前确认你有 USB 驱动,CP2102 或 CP2104 的桥接芯片,缺驱动只会显示一串问号。

3.2 获取项目并准备 config 文件

仓库拿下来后别急着 build,先把 src/config.h 复制成 config.h.orig,再编辑模板。核心参数是电台呼号、SSID、APRS 路径和频率。比如你的呼号是 BG7ABC,移动站 SSID 通常用 -9,路径可以设 WIDE1-1,WIDE2-1,这在城市里够用,但如果附近没有 iGate,就要改成直接发给本地网关的路径。

// 每一项都要改,别用默认值 const char *APRS_CALL = "BG7ABC-9"; // 呼号-SSID const char *APRS_PATH = "WIDE1-1,WIDE2-1"; // 路径 uint32_t APRS_FREQ = 434.040E6; // 覆盖当地 LoRa APRS 频点

配置里的频率单位是 Hz,浮点 E6 后缀表示兆赫。很多人只改呼号,忘了改频点,结果在 433.775MHz 上发了一整天,本地网关根本收不到。注意不同地区的 LoRa APRS 频段可能不同,有的用 433.775MHz,有的用 434.040MHz,这个要查当地规定,我不在这展开。

3.3 串口日志里读什么:启动阶段的关键三行

烧录成功后,打开串口监视器,波特率设 115200。下面三行最关键:

LORA: init OK (SX1276 ver 0x12) GPS: Waiting fix APRS: Beacon interval 300s

第一行说明 SPI 和 LoRa 寄存器初始化成功;第二行表示 GPS 等待首次定位;第三行是信标间隔。如果第二行一直停在 waiting,说明 GPS 天线有问题或串口波特率不对,T-Beam 的 GPS 模块默认 9600,有些扩展板是 115200,需要在 config.h 里改 GPS_BAND 参数。这个阶段别急着往高处跑,先放到窗户边静置十分钟,让 GPS 完成冷启动。

3.4 首次上报:用第二个接收端验证发射

没有对端设备时,最蠢的验证方式是拿手机听 LoRa 噪声,低效又不准。我一般用第二块 T-Beam 或一个 USB 转 LoRa 模块,设为相同频率和调制参数,看能否收到 RSSI 中断。LoRa 是扩频通信,听起来像噪声,但接收端日志会出现 RSSI 变化。你可以临时把接收端的串口助手指向射频前端,每次发射时 RSSI 会突然抬升:

RX: preamble detected, RSSI -78 dBm

如果 RSSI 显示超过 -60dBm,说明发射链路基本正常;如果完全无反应,检查频率和扩频因子是不是被固件默认值改掉了。这一步能区分是硬件故障还是配置错误,非常关键。

4. 参数与边界:LoRa 调制、GPS 上报节奏和低功耗的权衡

4.1 LoRa 调制参数:带宽和扩频因子不是随便调

APRS over LoRa 最常见的一套参数是:带宽 125kHz,扩频因子 SF7,编码率 4/5。这套配置在 434MHz 频段的理论速率约 5.5kbps,一个标准 APRS 位置报文 20-30 字节,算上前导码和报头,单帧传输时间约 200ms。如果距离远,可以调成带宽 62.5kHz、SF9,灵敏度能提高约 6-8dB,但单帧时间翻两倍,还要考虑占空比限制。

#define LORA_BANDWIDTH 0x08 // 125 kHz #define LORA_SF 0x07 // 对应 SF7 #define LORA_CR 0x01 // 4/5 编码率

这些值直接映射到 SX1276 的 RegModemConfig1 和 RegModemConfig2 寄存器。带宽字段用 4 位表示,0x08 是 125kHz,如果改成 0x09 就是 250kHz,灵敏度会变差;扩频因子对应 4 位值,7 表示 SF7。低功耗追踪场景里,有人为了省电降低空中唤醒时间,会把 SF 调到 12,但不适合 APRS 数据量,因为一帧可能超过 LoRa 单包最大长度限制。

4.2 GPS 上报节奏:不是越快越好

APRS 信标间隔要根据移动速度和电池容量折中。T-Beam 原装 2000mAh 电池,如果每 30 秒发一次,电流会一直维持 80-100mA,待机不到一天;我实际用下来,徒步场景间隔 300 秒比较合理,骑行可以 60 秒。在 config.h 里改:

uint32_t APRS_BEACON_INTERVAL = 300; // 单位秒

另一注意点是 GPS 冷启动会先输出一串无效位置,如果你代码在 fix 有效前就开始组帧,会把 NaN 坐标发到空中,后续网关解码直接失败。项目里通常有 GPS_FIX_TIMEOUT 参数,我习惯设为 120,表示两分钟内没有有效定位就不发信标,避免污染共享信道。

4.3 低功耗:ESP32 休眠与 LoRa 唤醒的矛盾

T-Beam 的功耗大头在 ESP32 WiFi 和 GPS 模块,LoRa 本身待机只有几微安。想要长时间追踪,必须让 ESP32 进入 deep sleep,用定时器唤醒,在唤醒后短暂开启 GPS 和 LoRa 发射,然后继续睡。仓库里 LORASTM32 分支的核心改动就在这里:用 STM32 管理定时唤醒,而不是让 ESP32 一直跑。

如果你在 master 分支也要做低功耗,先把外设电源管脚拉低,GPS 模块有独立的 LDO 控制,ESP32 的 GPIO4 在部分版本控制 GPS 供电。开启 deep sleep 前要保存 RTC 内存里的状态,否则唤醒后变量全清零。下面这段是典型结构:

esp_sleep_enable_timer_wakeup(beacon_interval * 1000 * 1000); esp_deep_sleep_start(); // 进入休眠,GPS 断电 // 唤醒后重新初始化 GPS 和 LoRa gpio_set_level(GPIO_NUM_4, 1); // 给 GPS 重新上电

注意 deep sleep 唤醒后固件会从入口重新执行,不是回到 sleep 后的下一行。这个设计让不少人翻过车:变量重新初始化,所以你必须在开头判断是冷启动还是唤醒,否则信标计数和 GPS 校准数据全丢。我一般用一个 RTC 标志位,记录上次休眠时间,唤醒后计算剩余周期。

4.4 边界参数参考表

下面这组参数来自我的实际测试环境,城市街道测试距离 2-3 公里,开阔水面 8 公里。你可以对照调整。

参数城市推荐值远距离推荐值说明
带宽125 kHz62.5 kHz更窄带宽更慢但更灵敏
扩频因子SF7SF9SF12 只适合短报文
编码率4/54/8抗干扰强但速率低
发射功率17 dBm20 dBm超过芯片限值损坏射频
信标间隔300 s600 s远距离站要省频谱

5. 常见问题与排查:为什么信号差、解码乱、GPS 不动

5.1 现象:只能发送不能接收

发送有 RSSI 变化但解不出任何对端数据,这是 APRS LoRa 项目里最常问的问题。先确认频率和调制参数完全一致,LoRa 的带宽、SF、CR 有一位不同就对不上。然后是接收端有没有设置同步字,SX1276 默认接收所有同步字,但项目里常设为 0x12,如果对端用 0x34 就会全丢。解决方法是把两侧同步字统一,并在代码里暴露这个参数。

再一个是天线方向性。T-Beam 的弹簧天线在垂直时辐射最好,平放时会有明显凹陷,发射端和接收端都立起来能显著提高接收率。我遇到过发射端在左裤袋,接收端握在右手边,距离不到 500 米却丢包,把板子竖着固定后立刻稳定。

5.2 现象:GPS 固定后位置一直不更新

GPS 显示有 3D fix,但 APRS 报文里坐标还是旧值。问题常见于固件用了一种被称为"锁定位置"的优化:为了省电,在首次 fix 后把 GPS 模块关机,只靠惯性估算位置,没有惯性传感器时位置就不动了。解决方法是设置 GPS 更新周期,强制每次信标前重新读一次串口。

while (!is_valid_gps(gps_data)) { // 等待 GGA 语句,最多 10 秒 vTaskDelay(1000 / portTICK_PERIOD_MS); } update_gps_position(gps_data);

如果循环一直出不去,说明 GPS 模块已经休眠或天线脱落。量一下 GPS 天线供电脚对地电压,如果在 3V 左右但没有波纹,模块往往是在老位置上缓存了最后一种错误状态,断电重启比复位指令更有效。

5.3 现象:收到报文乱码或 CRC 错误

网关日志提示 CRC mismatch,但发射端寄存器读回数据显示已经正常发送。最玄幻的是 LoRa 的隐式表头配置。APRS 报文通常用显式表头,包含长度、CRC 信息,如果发射端用隐式表头,接收端不知道包长,会从错误位置解出 CRC。排查方法是用逻辑分析仪抓 SPI,看发射端写入 RegModemConfig1 的 bit2 是 0 还是 1。

另一原因是 GPS 字符串里含换行符,组帧时没有清理尾部 \r\n,导致 payload 长度超过 APRS 协议限制。解决是在组帧前裁掉字符串末尾的空白字符,并强制解析为标准 AX.25 格式,避免直接把 NMEA 句子塞进 LoRa 帧里。

5.4 现象:距离一远就掉线

近距离(几百米)正常,超过 1 公里就丢包,先排除发射功率被限制。T-Beam 的 SX1276 max out 是 20 dBm,但很多固件默认 17 dBm,代码里检查setTxPower是否在初始化后调用。另一个因素是接收端灵敏度——如果你用网关接收,网关的 LoRa 配置是否和节点一致。

再检查天线接头。U.FL 座的插头非常容易虚接,尤其在大风天气晃动后。我曾经追查一周,发现是天线的同轴屏蔽层在插头处断开,休眠后板子在包里碰撞又偶尔接触上,打开日志看 RSSI 像心电图一样起伏。用频谱仪或另一台接收机对比,最直接。

5.5 现象:LORASTM32 协处理器导致 ESP32 启动崩溃

带 STM32 的板子会出现 ESP32 启动后反复打印错误并重启,原因是 ESP32 在初始化时向 STM32 发送配置帧,但 STM32 固件区是空的。项目 master 分支默认不启用 STM32,应该把相关宏注释掉;LORASTM32 分支则需要先给 STM32 烧录配套固件。

排查步骤按顺序来:先断开 STM32 的复位脚,让 ESP32 独立启动,看是否稳定;再查两个芯片之间的 UART 波特率是否一致,常见是 115200 和 9600 不匹配导致 STM32 回了垃圾数据,ESP32 解析失败进入复位循环。最后检查共享 GPIO 是否冲突,比如 T-Beam 的 GPS PPS 引脚和 STM32 中断共用了同一引脚,需要改项目里的 pin mapping。

6. 把验证做透:用第二块 T-Beam 当 iGate,解析自己的 LoRa APRS 报文

6.1 搭建最小接收验证环境

我习惯手边留一块只烧接收固件的 T-Beam,连到电脑跑,当作临时的 iGate 验证节点。发送端配置好呼号和位置,接收端串口输出原始 LoRa 帧,配合解析脚本,能立刻确认端到端链路。接收端固件可以很简单,只做包接收和打印:

# 用 pyserial 读取 LoRa 接收模块输出 import serial ser = serial.Serial('/dev/ttyUSB1', 115200, timeout=1) while True: line = ser.readline() if b'APRS' in line: print(line.decode('utf-8', errors='ignore'))

这段脚本没有做 AX.25 解析,只是为了确认 payload 是否可读。如果你看到非 ASCII 字符,说明 LoRa payload 里混入了二进制段,需要在上位机实现 AX.25 解码。看到类似BG7ABC-9>APRS,WIDE1-1:!3546.23N/12206.12W-的字符串,说明整条链路已经通了。

6.2 解读一条完整 APRS 位置报文

报文格式是> 分隔符前是源地址和路径,!后是位置格式。3546.23N表示北纬 35 度 46.23 分,12206.12W表示西经 122 度 06.12 分,最后的-是符号表,表示位置精度类型。如果!后第一个字符不是数字而是斜杠,代表带附加符号的格式,解析方式不同。

实战里,我会把收到的位置直接扔进地图服务,但注意 APRS 坐标没有高度信息,除非用扩展格式。对于追踪用途,把 NMEA 转换成 APRS 格式时,小数点位过于冗余,只保留两位小数就够了,再多了会增大 LoRa 帧长度,降低传输速度。

6.3 我的收尾习惯:日志按天归档,天线每次换环境测一次驻波

这块板子玩到后期,习惯比技巧更重要。我每次调完参数会在日志第一行写清日期、频率、功率和天线类型,否则三个月后回看串口日志,完全不知道当时用的是什么配置。另外要记得 LoRa 是半双工,发射时不能接收,如果你做 iGate 同时又要发信标,需要避开自己的发射时间,用轮转机制否则会吞掉自己的包。

最后把自己踩过的坑收一句:别迷信默认参数,别为了省电把 GPS 更新周期调得过长,更别忽略天线接头。希望这篇笔记能帮你把 T-Beam 上的 LoRa APRS 玩稳,少走我走过的那圈弯路。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进