From b45295bbb4642acd250d95a3837d6a8b7f88a93f Mon Sep 17 00:00:00 2001 From: Hermes Date: Tue, 29 Sep 2026 11:29:34 +0200 Subject: [PATCH] feat(gx12): add Ground Station AUX1 mode --- radio/src/dataconstants.h | 1 + radio/src/groundstation.cpp | 77 ++++++++++++++++++++++++ radio/src/groundstation.h | 6 ++ radio/src/serial.cpp | 10 +++ radio/src/targets/taranis/CMakeLists.txt | 1 + radio/src/translations/i18n/en.h | 1 + radio/src/translations/i18n/pl.h | 1 + 7 files changed, 97 insertions(+) create mode 100644 radio/src/groundstation.cpp create mode 100644 radio/src/groundstation.h diff --git a/radio/src/dataconstants.h b/radio/src/dataconstants.h index b88d73c4b..2827bcfbc 100644 --- a/radio/src/dataconstants.h +++ b/radio/src/dataconstants.h @@ -259,6 +259,7 @@ enum UartModes { UART_MODE_DEBUG, UART_MODE_SPACEMOUSE, UART_MODE_EXT_MODULE, + UART_MODE_GROUND_STATION, UART_MODE_COUNT SKIP, UART_MODE_MAX SKIP = UART_MODE_COUNT-1 }; diff --git a/radio/src/groundstation.cpp b/radio/src/groundstation.cpp new file mode 100644 index 000000000..f6a83f19b --- /dev/null +++ b/radio/src/groundstation.cpp @@ -0,0 +1,77 @@ +#include "edgetx.h" +#include "groundstation.h" +#include "os/timer.h" +#include "tasks/mixer_task.h" + +namespace { +constexpr uint8_t kMagic0 = 'G'; +constexpr uint8_t kMagic1 = 'S'; +constexpr uint8_t kVersion = 1; +constexpr uint8_t kTypeRCChannels = 1; +constexpr uint8_t kChannels = 16; +constexpr uint8_t kFrameSize = 10 + kChannels * 2 + 2; +constexpr uint32_t kPeriodMs = 40; // 25 Hz + +const etx_serial_driver_t *driver = nullptr; +void *driverCtx = nullptr; +timer_handle_t timer = TIMER_INITIALIZER; +uint16_t sequence = 0; + +uint16_t crc16(const uint8_t *data, size_t size) +{ + uint16_t crc = 0xffff; + for (size_t i = 0; i < size; ++i) { + crc ^= uint16_t(data[i]) << 8; + for (uint8_t bit = 0; bit < 8; ++bit) + crc = (crc & 0x8000) ? uint16_t((crc << 1) ^ 0x1021) : uint16_t(crc << 1); + } + return crc; +} + +void sendRCFrame() +{ + if (!driver || !driver->sendByte || !mixerTaskRunning()) return; + + uint8_t frame[kFrameSize] = { kMagic0, kMagic1, kVersion, kTypeRCChannels }; + frame[4] = sequence & 0xff; + frame[5] = sequence >> 8; + const uint32_t timestamp = get_tmr10ms() * 10; + frame[6] = timestamp & 0xff; + frame[7] = (timestamp >> 8) & 0xff; + frame[8] = (timestamp >> 16) & 0xff; + frame[9] = (timestamp >> 24) & 0xff; + + for (uint8_t i = 0; i < kChannels; ++i) { + const uint16_t value = channelOutputs[i]; + frame[10 + i * 2] = value & 0xff; + frame[11 + i * 2] = value >> 8; + } + + const uint16_t crc = crc16(frame, kFrameSize - 2); + frame[kFrameSize - 2] = crc & 0xff; + frame[kFrameSize - 1] = crc >> 8; + for (uint8_t byte : frame) driver->sendByte(driverCtx, byte); + ++sequence; +} + +void timerCb(timer_handle_t *) +{ + // Drain input now so the future profile-control parser has a single owner. + // The baseline intentionally does not act on received bytes yet. + if (driver && driver->getByte) { + uint8_t ignored; + while (driver->getByte(driverCtx, &ignored)) {} + } + sendRCFrame(); +} +} // namespace + +void groundstationSetSerialDriver(void *ctx, const etx_serial_driver_t *drv) +{ + driverCtx = ctx; + driver = drv; + sequence = 0; + if (!timer_is_created(&timer)) + timer_create(&timer, timerCb, "gstation", kPeriodMs, true); + timer_start(&timer); +} diff --git a/radio/src/groundstation.h b/radio/src/groundstation.h new file mode 100644 index 000000000..5b0af2cdd --- /dev/null +++ b/radio/src/groundstation.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include "hal/serial_port.h" + +void groundstationSetSerialDriver(void *ctx, const etx_serial_driver_t *drv); diff --git a/radio/src/serial.cpp b/radio/src/serial.cpp index e6d77b740..4b870ed89 100644 --- a/radio/src/serial.cpp +++ b/radio/src/serial.cpp @@ -37,6 +37,7 @@ #if !defined(BOOT) #include "edgetx.h" + #include "groundstation.h" #include "lua/lua_api.h" #else #include "dataconstants.h" @@ -257,6 +258,10 @@ static void serialSetCallBacks(int mode, void* ctx, const etx_serial_port_t* por break; #endif + case UART_MODE_GROUND_STATION: + groundstationSetSerialDriver(ctx, drv); + break; + #if defined(CONFIGURABLE_MODULE_PORT) and !defined(BOOT) case UART_MODE_EXT_MODULE: if (port && !ctx) { // de-init @@ -354,6 +359,11 @@ static void serialSetupPort(int mode, etx_serial_init& params) break; #endif + case UART_MODE_GROUND_STATION: + params.baudrate = 115200; + params.direction = ETX_Dir_TX_RX; + break; + #endif // BOOT } } diff --git a/radio/src/targets/taranis/CMakeLists.txt b/radio/src/targets/taranis/CMakeLists.txt index d0f720e2d..00abbd2b9 100644 --- a/radio/src/targets/taranis/CMakeLists.txt +++ b/radio/src/targets/taranis/CMakeLists.txt @@ -267,6 +267,7 @@ elseif(PCB STREQUAL X7) set(PXX2 ON) set(ENABLE_SERIAL_PASSTHROUGH ON CACHE BOOL "Enable serial passthrough") set(FLYSKY_GIMBAL YES) + set(SRC ${SRC} groundstation.cpp) if (NOT BLUETOOTH) set(AUX_SERIAL ON) endif() diff --git a/radio/src/translations/i18n/en.h b/radio/src/translations/i18n/en.h index b63eeb8f9..620753bd4 100644 --- a/radio/src/translations/i18n/en.h +++ b/radio/src/translations/i18n/en.h @@ -144,6 +144,7 @@ #define TR_AUX_SERIAL_MODES_9 "Debug" #define TR_AUX_SERIAL_MODES_10 "SpaceMouse" #define TR_AUX_SERIAL_MODES_11 "External module" +#define TR_AUX_SERIAL_MODES_12 "Ground Station" #define TR_SWTYPES_1 "None" #define TR_SWTYPES_2 "Toggle" #define TR_SWTYPES_3 "2POS" diff --git a/radio/src/translations/i18n/pl.h b/radio/src/translations/i18n/pl.h index 6e20bac51..e8e411544 100644 --- a/radio/src/translations/i18n/pl.h +++ b/radio/src/translations/i18n/pl.h @@ -145,6 +145,7 @@ #define TR_AUX_SERIAL_MODES_9 "Debug" #define TR_AUX_SERIAL_MODES_10 "SpaceMouse" #define TR_AUX_SERIAL_MODES_11 "Moduł zewnętrzny" +#define TR_AUX_SERIAL_MODES_12 "Ground Station" #define TR_SWTYPES_1 "Brak" #define TR_SWTYPES_2 "Chwil." #define TR_SWTYPES_3 "2POZ"