mirror of
http://git.ua1zbe.ru/ua1zbe/LoRa_APRS_Beacon.git
synced 2026-08-11 16:10:31 +00:00
Initial commit: LoRa APRS Beacon для T-Beam V1.2
- Статический маяк с координатами 67.369281N, 32.497063E - Отправка каждые 10 минут + при включении - APRS символ L в чёрном ромбике, позывной UA1ZBE-TS - Дисплей SH1106 1.3 повёрнутый - Мониторинг напряжения батареи в сообщении - Кнопка для ручной отправки - Частота 433.775 MHz, SF12, BW125 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# LoRa_APRS_Beacon
|
||||
|
||||
LoRa APRS Beacon для T-Beam V1.2 (AXP2101) с дисплеем SH1106 1.3. Статический маяк с координатами, отправка каждые 10 минут, мониторинг батареи, кнопка для ручной отправки. Позывной UA1ZBE-TS, частота 433.775 MHz.
|
||||
23
platformio.ini
Normal file
23
platformio.ini
Normal file
@@ -0,0 +1,23 @@
|
||||
[platformio]
|
||||
default_envs = ttgo-t-beam-AXP2101-v1_2
|
||||
|
||||
[env]
|
||||
platform = espressif32 @ 6.7.0
|
||||
framework = arduino
|
||||
lib_ldf_mode = deep+
|
||||
monitor_speed = 115200
|
||||
monitor_filters = esp32_exception_decoder
|
||||
lib_deps =
|
||||
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays @ ^4.5.0
|
||||
bblanchon/ArduinoJson @ 7.0.4
|
||||
lewisxhe/XPowersLib @ 0.2.4
|
||||
sandeepmistry/LoRa @ 0.8.0
|
||||
shaggydog/OneButton @ 1.5.0
|
||||
check_tool = cppcheck
|
||||
check_flags =
|
||||
cppcheck: --suppress=*:*.pio\* --inline-suppr -DCPPCHECK
|
||||
check_skip_packages = yes
|
||||
|
||||
[env:ttgo-t-beam-AXP2101-v1_2]
|
||||
board = ttgo-t-beam
|
||||
build_flags = -Werror -Wall -DTTGO_T_Beam_V1_2
|
||||
72
src/display.cpp
Normal file
72
src/display.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <SH1106Wire.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "display.h"
|
||||
#include "pins.h"
|
||||
|
||||
SH1106Wire display(0x3c, OLED_SDA, OLED_SCL);
|
||||
|
||||
void setup_display() {
|
||||
pinMode(OLED_RST, OUTPUT);
|
||||
digitalWrite(OLED_RST, LOW);
|
||||
delay(20);
|
||||
digitalWrite(OLED_RST, HIGH);
|
||||
|
||||
// Wire уже инициализирован в main.cpp
|
||||
display.init();
|
||||
display.flipScreenVertically();
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
display.setColor(WHITE);
|
||||
display.drawString(0, 0, "LORA APRS BEACON");
|
||||
display.display();
|
||||
}
|
||||
|
||||
void display_toggle(bool toggle) {
|
||||
if (toggle) {
|
||||
display.displayOn();
|
||||
} else {
|
||||
display.displayOff();
|
||||
}
|
||||
}
|
||||
|
||||
void show_display(String line1, int wait) {
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.drawString(0, 0, line1);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, int wait) {
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.drawString(0, 0, line1);
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
display.drawString(0, 18, line2);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, int wait) {
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.drawString(0, 0, line1);
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
display.drawString(0, 18, line2);
|
||||
display.drawString(0, 28, line3);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
|
||||
void show_display(String line1, String line2, String line3, String line4, int wait) {
|
||||
display.clear();
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
display.drawString(0, 0, line1);
|
||||
display.setFont(ArialMT_Plain_10);
|
||||
display.drawString(0, 18, line2);
|
||||
display.drawString(0, 28, line3);
|
||||
display.drawString(0, 38, line4);
|
||||
display.display();
|
||||
delay(wait);
|
||||
}
|
||||
11
src/display.h
Normal file
11
src/display.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef DISPLAY_H_
|
||||
#define DISPLAY_H_
|
||||
|
||||
void setup_display();
|
||||
void display_toggle(bool toggle);
|
||||
void show_display(String line1, int wait = 0);
|
||||
void show_display(String line1, String line2, int wait = 0);
|
||||
void show_display(String line1, String line2, String line3, int wait = 0);
|
||||
void show_display(String line1, String line2, String line3, String line4, int wait = 0);
|
||||
|
||||
#endif
|
||||
227
src/main.cpp
Normal file
227
src/main.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#include <Arduino.h>
|
||||
#include <LoRa.h>
|
||||
#include <OneButton.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "display.h"
|
||||
#include "pins.h"
|
||||
#include "power_management.h"
|
||||
|
||||
#define VERSION "1.0.0"
|
||||
|
||||
// ===== НАСТРОЙКИ =====
|
||||
#define CALLSIGN "UA1ZBE-TS"
|
||||
#define PATH "WIDE1-1"
|
||||
#define MESSAGE "Test 433.775MHz Lora aprs"
|
||||
#define SYMBOL 'L'
|
||||
#define OVERLAY '\\'
|
||||
#define BEACON_INTERVAL_SEC 600 // 10 минут
|
||||
|
||||
// Координаты (статические)
|
||||
#define LAT_DEG 67
|
||||
#define LAT_MIN 22.1569 // 67.369281 = 67° 22.1569'
|
||||
#define LON_DEG 32
|
||||
#define LON_MIN 29.8238 // 32.497063 = 32° 29.8238'
|
||||
#define LAT_DIR 'N'
|
||||
#define LON_DIR 'E'
|
||||
|
||||
// LoRa настройки
|
||||
#define LORA_FREQ 433775000
|
||||
#define LORA_SF 12
|
||||
#define LORA_BW 125000
|
||||
#define LORA_CR 5
|
||||
#define LORA_POWER 20
|
||||
|
||||
// ===== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ =====
|
||||
#ifdef TTGO_T_Beam_V1_2
|
||||
AXP2101 axp;
|
||||
PowerManagement *powerManagement = &axp;
|
||||
#endif
|
||||
|
||||
OneButton userButton = OneButton(BUTTON_PIN, true, true);
|
||||
|
||||
static bool batteryConnected = false;
|
||||
static String batteryVoltage = "";
|
||||
static unsigned long lastBeaconTime = 0;
|
||||
|
||||
// ===== APRS кодирование координат =====
|
||||
String create_lat_aprs() {
|
||||
char buf[20];
|
||||
sprintf(buf, "%02d%05.2f%c", LAT_DEG, LAT_MIN, LAT_DIR);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
String create_lon_aprs() {
|
||||
char buf[20];
|
||||
sprintf(buf, "%03d%05.2f%c", LON_DEG, LON_MIN, LON_DIR);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
String createTimeString(unsigned long seconds) {
|
||||
unsigned long h = (seconds / 3600) % 24;
|
||||
unsigned long m = (seconds / 60) % 60;
|
||||
unsigned long s = seconds % 60;
|
||||
|
||||
char buf[20];
|
||||
sprintf(buf, "%02lu:%02lu:%02lu", h, m, s);
|
||||
return String(buf);
|
||||
}
|
||||
|
||||
// ===== Функция отправки маяка =====
|
||||
void send_beacon() {
|
||||
String lat = create_lat_aprs();
|
||||
String lon = create_lon_aprs();
|
||||
|
||||
// Получаем напряжение батареи
|
||||
String batInfo = "";
|
||||
#ifdef TTGO_T_Beam_V1_2
|
||||
float batV = powerManagement->getBatteryVoltage();
|
||||
if (batV > 3.0) {
|
||||
batInfo = " Bat: " + String(batV, 1) + "V";
|
||||
} else {
|
||||
batInfo = " Bat: USB";
|
||||
}
|
||||
#endif
|
||||
|
||||
String aprsmsg = "!" + lat + String(OVERLAY) + lon + String(SYMBOL) + " " + String(MESSAGE) + batInfo;
|
||||
|
||||
// Заголовок LoRa APRS
|
||||
String lora_header = "<";
|
||||
lora_header += char(0xFF);
|
||||
lora_header += char(0x01);
|
||||
|
||||
// APRS пакет: адрес назначения + адрес источника + путь + данные
|
||||
String packet = String(CALLSIGN) + ">APLT00," + String(PATH) + ":" + aprsmsg;
|
||||
|
||||
Serial.println("===== TX PACKET =====");
|
||||
Serial.println(packet);
|
||||
Serial.println("=====================");
|
||||
Serial.println();
|
||||
|
||||
// LoRa передача
|
||||
LoRa.beginPacket();
|
||||
LoRa.write((const uint8_t *)lora_header.c_str(), lora_header.length());
|
||||
LoRa.write((const uint8_t *)packet.c_str(), packet.length());
|
||||
LoRa.endPacket();
|
||||
|
||||
Serial.println(">>> Packet sent via LoRa");
|
||||
Serial.println();
|
||||
|
||||
show_display("<< TX >>", CALLSIGN, "Sent at: " + createTimeString(millis() / 1000));
|
||||
|
||||
lastBeaconTime = millis();
|
||||
}
|
||||
|
||||
// ===== SETUP =====
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.flush();
|
||||
delay(100);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Serial.println("Booting... " + String(i + 1));
|
||||
delay(500);
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.println("LoRa APRS Beacon v" VERSION);
|
||||
Serial.println("Starting...");
|
||||
|
||||
// Инициализация PMU - ВАЖНО: сначала Wire!
|
||||
#if defined(TTGO_T_Beam_V1_2)
|
||||
Serial.println("Initializing PMU...");
|
||||
Wire.begin(OLED_SDA, OLED_SCL, 100000);
|
||||
delay(100);
|
||||
if (powerManagement->begin(Wire)) {
|
||||
Serial.println("PMU init OK");
|
||||
} else {
|
||||
Serial.println("PMU init FAILED, continuing anyway");
|
||||
}
|
||||
powerManagement->activateLoRa();
|
||||
// Не включаем GPS - он нам не нужен
|
||||
powerManagement->deactivateGPS();
|
||||
powerManagement->activateOLED();
|
||||
powerManagement->activateMeasurement();
|
||||
#endif
|
||||
|
||||
// Выключаем WiFi и BT
|
||||
// WiFi.mode(WIFI_OFF);
|
||||
// btStop();
|
||||
|
||||
// Дисплей
|
||||
Serial.println("Initializing display...");
|
||||
setup_display();
|
||||
show_display("LoRa APRS Beacon", "v" VERSION, CALLSIGN, 2000);
|
||||
|
||||
// LoRa
|
||||
Serial.println("Initializing LoRa...");
|
||||
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
|
||||
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
|
||||
|
||||
if (!LoRa.begin(LORA_FREQ)) {
|
||||
Serial.println("ERROR: LoRa init failed!");
|
||||
show_display("ERROR", "LoRa init failed!", 0);
|
||||
while (true) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
LoRa.setSpreadingFactor(LORA_SF);
|
||||
LoRa.setSignalBandwidth(LORA_BW);
|
||||
LoRa.setCodingRate4(LORA_CR);
|
||||
LoRa.enableCrc();
|
||||
LoRa.setTxPower(LORA_POWER);
|
||||
Serial.printf("LoRa OK - Freq: %d, SF: %d, BW: %d, CR: %d, PWR: %d\n",
|
||||
(int)LORA_FREQ, (int)LORA_SF, (int)LORA_BW, (int)LORA_CR, (int)LORA_POWER);
|
||||
|
||||
show_display("LoRa OK", "Freq: 433.775 MHz", "SF: 12", 2000);
|
||||
|
||||
// Кнопка
|
||||
userButton.attachClick([]() {
|
||||
Serial.println("Button pressed - manual TX");
|
||||
send_beacon();
|
||||
});
|
||||
|
||||
// Отправляем первый маяк сразу при старте
|
||||
Serial.println();
|
||||
Serial.println("==============================");
|
||||
Serial.println(" LoRa APRS Beacon starting");
|
||||
Serial.println(" Callsign: " CALLSIGN);
|
||||
Serial.println(" Interval: 10 min");
|
||||
Serial.println(" Freq: 433.775 MHz");
|
||||
Serial.println("==============================");
|
||||
Serial.println();
|
||||
|
||||
send_beacon();
|
||||
}
|
||||
|
||||
// ===== LOOP =====
|
||||
void loop() {
|
||||
userButton.tick();
|
||||
|
||||
unsigned long now = millis();
|
||||
|
||||
// Проверка интервала маяка (10 минут)
|
||||
if (now - lastBeaconTime >= BEACON_INTERVAL_SEC * 1000UL) {
|
||||
send_beacon();
|
||||
}
|
||||
|
||||
// Обновление дисплея каждую секунду
|
||||
static unsigned long lastDisplayUpdate = 0;
|
||||
if (now - lastDisplayUpdate >= 1000) {
|
||||
lastDisplayUpdate = now;
|
||||
|
||||
unsigned long uptime = now / 1000;
|
||||
unsigned long nextBeacon = (BEACON_INTERVAL_SEC * 1000UL - (now - lastBeaconTime)) / 1000;
|
||||
|
||||
#ifdef TTGO_T_Beam_V1_2
|
||||
batteryConnected = powerManagement->isBatteryConnect();
|
||||
if (batteryConnected) {
|
||||
batteryVoltage = String(powerManagement->getBatteryVoltage(), 2);
|
||||
}
|
||||
#endif
|
||||
|
||||
show_display(CALLSIGN,
|
||||
"Uptime: " + createTimeString(uptime),
|
||||
"Next: ~" + createTimeString(nextBeacon),
|
||||
batteryConnected ? ("Bat: " + batteryVoltage + "V") : "Powered via USB");
|
||||
}
|
||||
}
|
||||
24
src/pins.h
Normal file
24
src/pins.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef PINS_H_
|
||||
#define PINS_H_
|
||||
|
||||
#undef OLED_SDA
|
||||
#undef OLED_SCL
|
||||
#undef OLED_RST
|
||||
|
||||
#define OLED_SDA 21
|
||||
#define OLED_SCL 22
|
||||
#define OLED_RST 16
|
||||
|
||||
#define BUTTON_PIN 38
|
||||
|
||||
#ifdef TTGO_T_Beam_V0_7
|
||||
#define GPS_RX 15
|
||||
#define GPS_TX 12
|
||||
#endif
|
||||
|
||||
#if defined(TTGO_T_Beam_V1_0) || defined(TTGO_T_Beam_V1_2)
|
||||
#define GPS_RX 12
|
||||
#define GPS_TX 34
|
||||
#endif
|
||||
|
||||
#endif
|
||||
98
src/power_management.cpp
Normal file
98
src/power_management.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <XPowersAXP2101.tpp>
|
||||
|
||||
#include "power_management.h"
|
||||
|
||||
AXP2101::AXP2101() {
|
||||
}
|
||||
|
||||
bool AXP2101::begin(TwoWire &port) {
|
||||
_pmu = new XPowersAXP2101(port);
|
||||
if (!_pmu->init()) {
|
||||
delete _pmu;
|
||||
_pmu = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
_pmu->disablePowerOutput(XPOWERS_DCDC2);
|
||||
_pmu->disablePowerOutput(XPOWERS_DCDC3);
|
||||
_pmu->disablePowerOutput(XPOWERS_DCDC4);
|
||||
_pmu->disablePowerOutput(XPOWERS_DCDC5);
|
||||
_pmu->disablePowerOutput(XPOWERS_ALDO1);
|
||||
_pmu->disablePowerOutput(XPOWERS_ALDO4);
|
||||
_pmu->disablePowerOutput(XPOWERS_BLDO1);
|
||||
_pmu->disablePowerOutput(XPOWERS_BLDO2);
|
||||
_pmu->disablePowerOutput(XPOWERS_DLDO1);
|
||||
_pmu->disablePowerOutput(XPOWERS_DLDO2);
|
||||
|
||||
_pmu->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300);
|
||||
_pmu->enablePowerOutput(XPOWERS_VBACKUP);
|
||||
|
||||
_pmu->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||||
_pmu->enablePowerOutput(XPOWERS_ALDO2);
|
||||
|
||||
_pmu->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||||
_pmu->enablePowerOutput(XPOWERS_ALDO3);
|
||||
|
||||
_pmu->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
|
||||
_pmu->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_800MA);
|
||||
_pmu->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||
_pmu->setChargingLedMode(XPOWERS_CHG_LED_CTRL_CHG);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AXP2101::activateLoRa() {
|
||||
if (!_pmu) return;
|
||||
_pmu->enablePowerOutput(XPOWERS_ALDO2);
|
||||
}
|
||||
|
||||
void AXP2101::deactivateLoRa() {
|
||||
if (!_pmu) return;
|
||||
_pmu->disablePowerOutput(XPOWERS_ALDO2);
|
||||
}
|
||||
|
||||
void AXP2101::activateGPS() {
|
||||
if (!_pmu) return;
|
||||
_pmu->enablePowerOutput(XPOWERS_ALDO3);
|
||||
}
|
||||
|
||||
void AXP2101::deactivateGPS() {
|
||||
if (!_pmu) return;
|
||||
_pmu->disablePowerOutput(XPOWERS_ALDO3);
|
||||
}
|
||||
|
||||
void AXP2101::activateOLED() {
|
||||
if (!_pmu) return;
|
||||
_pmu->enablePowerOutput(XPOWERS_DCDC1);
|
||||
}
|
||||
|
||||
void AXP2101::deactivateOLED() {
|
||||
if (!_pmu) return;
|
||||
_pmu->disablePowerOutput(XPOWERS_DCDC1);
|
||||
}
|
||||
|
||||
void AXP2101::activateMeasurement() {
|
||||
if (!_pmu) return;
|
||||
_pmu->enableBattVoltageMeasure();
|
||||
}
|
||||
|
||||
void AXP2101::deactivateMeasurement() {
|
||||
if (!_pmu) return;
|
||||
_pmu->disableBattVoltageMeasure();
|
||||
}
|
||||
|
||||
double AXP2101::getBatteryVoltage() {
|
||||
if (!_pmu) return 0.0;
|
||||
return _pmu->getBattVoltage() / 1000.0;
|
||||
}
|
||||
|
||||
bool AXP2101::isBatteryConnect() {
|
||||
if (!_pmu) return false;
|
||||
return _pmu->isBatteryConnect();
|
||||
}
|
||||
|
||||
bool AXP2101::isCharging() {
|
||||
if (!_pmu) return false;
|
||||
return _pmu->isCharging();
|
||||
}
|
||||
59
src/power_management.h
Normal file
59
src/power_management.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef POWER_MANAGEMENT_H_
|
||||
#define POWER_MANAGEMENT_H_
|
||||
|
||||
#include <Wire.h>
|
||||
#include <XPowersLibInterface.hpp>
|
||||
|
||||
class PowerManagement {
|
||||
public:
|
||||
~PowerManagement() {
|
||||
}
|
||||
|
||||
virtual bool begin(TwoWire &port) = 0;
|
||||
|
||||
virtual void activateLoRa() = 0;
|
||||
virtual void deactivateLoRa() = 0;
|
||||
|
||||
virtual void activateGPS() = 0;
|
||||
virtual void deactivateGPS() = 0;
|
||||
|
||||
virtual void activateOLED() = 0;
|
||||
virtual void deactivateOLED() = 0;
|
||||
|
||||
virtual void activateMeasurement() = 0;
|
||||
virtual void deactivateMeasurement() = 0;
|
||||
|
||||
virtual double getBatteryVoltage() = 0;
|
||||
|
||||
virtual bool isBatteryConnect() = 0;
|
||||
virtual bool isCharging() = 0;
|
||||
|
||||
protected:
|
||||
XPowersLibInterface *_pmu = 0;
|
||||
};
|
||||
|
||||
class AXP2101 : public PowerManagement {
|
||||
public:
|
||||
AXP2101();
|
||||
|
||||
bool begin(TwoWire &port);
|
||||
|
||||
void activateLoRa();
|
||||
void deactivateLoRa();
|
||||
|
||||
void activateGPS();
|
||||
void deactivateGPS();
|
||||
|
||||
void activateOLED();
|
||||
void deactivateOLED();
|
||||
|
||||
void activateMeasurement();
|
||||
void deactivateMeasurement();
|
||||
|
||||
double getBatteryVoltage();
|
||||
|
||||
bool isBatteryConnect();
|
||||
bool isCharging();
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user