Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions radio/src/gui/gui_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,14 @@ bool isSerialModeAvailable(uint8_t port_nr, int mode)
#endif

#if defined(USB_SERIAL)
// Telemetry input & SBUS trainer on VCP is not yet supported
if (port_nr == SP_VCP &&
(mode == UART_MODE_TELEMETRY || mode == UART_MODE_SBUS_TRAINER))
// Telemetry input on VCP is not yet supported
if (port_nr == SP_VCP && mode == UART_MODE_TELEMETRY)
return false;

// USB CDC carries no line polarity, so the two SBUS trainer modes behave
// identically on VCP. Offer only the one the user knows as plain SBUS:
// normal SBUS is inverted serial, so that is UART_MODE_SBUS_TRAINER_INV.
if (port_nr == SP_VCP && mode == UART_MODE_SBUS_TRAINER)
return false;
#endif

Expand Down
95 changes: 94 additions & 1 deletion radio/src/sbus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

#include "sbus.h"

#include <string.h>

#include "edgetx.h"
#include "timers_driver.h"

Expand All @@ -42,6 +44,13 @@ static bool _sbus_aux_enabled = false;

static void sbusProcessFrame(int16_t* pulses, uint8_t* sbus, uint32_t size);

// The last byte of a frame is 0x00 for Futaba / FrSky, but some
// implementations use it to carry frame flags (0x04 / 0x14 / 0x24).
static inline bool sbusIsEndByte(uint8_t b)
{
return b == SBUS_END_BYTE || b == 0x04 || b == 0x14 || b == 0x24;
}

void sbusSetReceiveCtx(void* ctx, const etx_serial_driver_t* drv)
{
_sbus_ctx = ctx;
Expand Down Expand Up @@ -74,11 +83,95 @@ void sbusFrameReceived(void*)
sbusProcessFrame(trainerInput, frame, received);
}

//
// Byte-stream framer, for ports with no idle-line detection (USB-VCP).
//
// USB CDC gives no frame boundaries: a 25 byte frame may be split over several
// packets, and several frames may arrive in one packet. So frames have to be
// recovered from the stream itself.
//
// Invariant: _sbus_stream_len == 0, or _sbus_stream_buf[0] == SBUS_START_BYTE.
//
static uint8_t _sbus_stream_buf[SBUS_FRAME_SIZE];
static uint8_t _sbus_stream_len = 0;

static const etx_serial_driver_t* _sbus_stream_drv = nullptr;
static void* _sbus_stream_ctx = nullptr;

// Drop the leading byte of a rejected frame and re-sync on the next start byte
// found in what is left. Never drops the whole buffer: a valid frame may well
// have started inside it.
static void sbusStreamResync()
{
uint8_t i = 1;
while (i < _sbus_stream_len && _sbus_stream_buf[i] != SBUS_START_BYTE) i++;

_sbus_stream_len -= i;
if (_sbus_stream_len > 0) {
memmove(_sbus_stream_buf, _sbus_stream_buf + i, _sbus_stream_len);
}
}

void sbusStreamReceiveData(uint8_t* data, uint32_t len)
{
// Trainer mode is not asking for serial input: stay out of the way.
if (!_sbus_aux_enabled) {
_sbus_stream_len = 0;
return;
}

while (len > 0) {
// Hunt for a start byte while no frame is being assembled
if (_sbus_stream_len == 0 && *data != SBUS_START_BYTE) {
data++; len--;
continue;
}

_sbus_stream_buf[_sbus_stream_len++] = *data++;
len--;

if (_sbus_stream_len < SBUS_FRAME_SIZE) continue;

if (sbusIsEndByte(_sbus_stream_buf[SBUS_FRAME_SIZE - 1])) {
// Complete frame. sbusProcessFrame() re-checks it, and resets the
// trainer validity timer if it is accepted.
sbusProcessFrame(trainerInput, _sbus_stream_buf, SBUS_FRAME_SIZE);
_sbus_stream_len = 0;
} else {
sbusStreamResync();
}
}
}

void sbusStreamStart(void* ctx, const etx_serial_driver_t* drv)
{
if (!drv || !drv->setReceiveCb) return;

_sbus_stream_len = 0;
_sbus_stream_drv = drv;
_sbus_stream_ctx = ctx;

drv->setReceiveCb(ctx, sbusStreamReceiveData);
}

void sbusStreamStop()
{
auto drv = _sbus_stream_drv;
auto ctx = _sbus_stream_ctx;

_sbus_stream_drv = nullptr;
_sbus_stream_ctx = nullptr;
_sbus_stream_len = 0;

// Release the RX stream, so the next user of the port gets it
if (drv && drv->setReceiveCb) drv->setReceiveCb(ctx, nullptr);
}

// Range for pulses (ppm input) is [-512:+512]
static void sbusProcessFrame(int16_t* pulses, uint8_t* sbus, uint32_t size)
{
if (size != SBUS_FRAME_SIZE || sbus[0] != SBUS_START_BYTE ||
sbus[SBUS_FRAME_SIZE - 1] != SBUS_END_BYTE) {
!sbusIsEndByte(sbus[SBUS_FRAME_SIZE - 1])) {
return; // not a valid SBUS frame
}
if ((sbus[SBUS_FLAGS_IDX] & (1 << SBUS_FAILSAFE_BIT)) ||
Expand Down
16 changes: 15 additions & 1 deletion radio/src/sbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,21 @@ void sbusSetReceiveCtx(void* ctx, const etx_serial_driver_t* drv);
// SBUS AUX idle callback
void sbusAuxFrameReceived(void* param);

// Enable / disable SBUS AUX
// Enable / disable serial trainer input (both the AUX UART and the USB-VCP
// paths). Driven by TRAINER_MODE_MASTER_SERIAL.
void sbusAuxSetEnabled(bool enabled);

void sbusFrameReceived(void* param);

//
// SBUS byte-stream framer.
//
// For ports that deliver arbitrarily chunked buffers and have no idle-line
// detection to mark frame boundaries (USB-VCP). Attach / detach the driver's
// receive callback, and keep the partial-frame state across chunks.
//
void sbusStreamStart(void* ctx, const etx_serial_driver_t* drv);
void sbusStreamStop();

// Receive callback: feeds a chunk of the byte stream into the framer
void sbusStreamReceiveData(uint8_t* data, uint32_t len);
8 changes: 8 additions & 0 deletions radio/src/serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,15 @@ static void serialSetCallBacks(int mode, void* ctx, const etx_serial_port_t* por
case UART_MODE_SBUS_TRAINER_INV:
sbusSetReceiveCtx(ctx, drv);
if (drv && drv->setIdleCb) {
// Hardware UART: the idle line marks the frame boundaries
drv->setIdleCb(ctx, sbusAuxFrameReceived, nullptr);
} else if (drv && drv->setReceiveCb) {
// No idle-line detection (USB-VCP): recover frames from the byte stream
sbusStreamStart(ctx, drv);
} else {
// De-init (ctx == nullptr, hence drv == nullptr), or a port that can do
// neither. No-op unless the framer is currently attached.
sbusStreamStop();
}
break;

Expand Down
Loading