#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); }