Skip to content
Merged
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
4 changes: 4 additions & 0 deletions radio/src/datastructs_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,10 @@ PACK(struct ModuleData {
rx_freq[1] = value >> 8;
}
} afhds3));
NOBACKUP(struct {
uint8_t raw12bits:1;
uint8_t spare1:7 SKIP;
} ghost);
} NAME(mod) FUNC(select_mod_type);

// Helper functions to set both of the rfProto protocol at the same time
Expand Down
4 changes: 4 additions & 0 deletions radio/src/gui/128x64/model_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,10 @@ void menuModelSetup(event_t event)
lcdDrawTextAlignedLeft(y, STR_WARN_BATTVOLTAGE);
putsVolts(lcdLastRightPos, y, getBatteryVoltage(), attr | PREC2 | LEFT);
}
else if (isModuleGhost(moduleIdx)) {
auto & module = g_model.moduleData[moduleIdx];
module.ghost.raw12bits = editCheckBox(module.ghost.raw12bits , MODEL_SETUP_2ND_COLUMN, y, INDENT "Raw 12 bits", attr, event);
}
break;
}

Expand Down
4 changes: 4 additions & 0 deletions radio/src/gui/212x64/model_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,10 @@ void menuModelSetup(event_t event)
lcdDrawTextAlignedLeft(y, STR_WARN_BATTVOLTAGE);
putsVolts(lcdLastRightPos, y, getBatteryVoltage(), attr | PREC2 | LEFT);
}
else if (isModuleGhost(moduleIdx)) {
auto & module = g_model.moduleData[moduleIdx];
module.ghost.raw12bits = editCheckBox(module.ghost.raw12bits , MODEL_SETUP_2ND_COLUMN, y, INDENT "Raw 12 bits", attr, event);
}
}
break;

Expand Down
5 changes: 5 additions & 0 deletions radio/src/gui/colorlcd/model_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,11 @@ class ModuleWindow : public FormGroup {
grid.nextLine();
}

if (isModuleGhost(moduleIdx)) {
new StaticText(this, grid.getLabelSlot(true), "Raw 12 bits", 0, COLOR_THEME_PRIMARY1);
new CheckBox(this, grid.getFieldSlot(), GET_SET_DEFAULT(g_model.moduleData[moduleIdx].ghost.raw12bits));
}

auto par = getParent();
par->moveWindowsTop(top() + 1, adjustHeight());
par->adjustInnerHeight();
Expand Down
2 changes: 2 additions & 0 deletions radio/src/gui/gui_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ inline uint8_t MODULE_OPTION_ROW(uint8_t moduleIdx) {
return TITLE_ROW;
if(isModuleAFHDS3(moduleIdx))
return HIDDEN_ROW;
if(isModuleGhost(moduleIdx))
return 0;
return MULTIMODULE_OPTIONS_ROW(moduleIdx);
}

Expand Down
111 changes: 111 additions & 0 deletions radio/src/lua/api_general.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,113 @@ static int luaCrossfireTelemetryPush(lua_State * L)
}
#endif

#if defined(GHOST)
/*luadoc
@function ghostTelemetryPop()

Pops a received Ghost Telemetry packet from the queue.

@retval nil queue does not contain any (or enough) bytes to form a whole packet

@retval multiple returns 2 values:
* type (number)
* packet (table) data bytes

@status current Introduced in 2.7.0
*/
static int luaGhostTelemetryPop(lua_State * L)
{
if (!luaInputTelemetryFifo) {
luaInputTelemetryFifo = new Fifo<uint8_t, LUA_TELEMETRY_INPUT_FIFO_SIZE>();
if (!luaInputTelemetryFifo) {
return 0;
}
}

uint8_t length = 0, data = 0;
if (luaInputTelemetryFifo->probe(length) && luaInputTelemetryFifo->size() >= uint32_t(length)) {
// length value includes type(1B), payload, crc(1B)
luaInputTelemetryFifo->pop(length);
luaInputTelemetryFifo->pop(data); // type
lua_pushnumber(L, data); // return type
lua_newtable(L);
for (uint8_t i=0; i<length-2; i++) {
luaInputTelemetryFifo->pop(data);
lua_pushinteger(L, i+1);
lua_pushinteger(L, data);
lua_settable(L, -3);
}
return 2;
}

return 0;
}

/*luadoc
@function ghostTelemetryPush()

This functions allows for sending telemetry data toward the Ghost link.

When called without parameters, it will only return the status of the output buffer without sending anything.

@param command command

@param data table of data bytes

@retval boolean data queued in output buffer or not.

@retval nil incorrect telemetry protocol.

@status current Introduced in 2.7.0
*/
static int luaGhostTelemetryPush(lua_State * L)
{
bool sport = (telemetryProtocol == PROTOCOL_TELEMETRY_GHOST);

if (!sport) {
lua_pushnil(L);
return 1;
}

if (lua_gettop(L) == 0) {
lua_pushboolean(L, outputTelemetryBuffer.isAvailable());
}
else if (lua_gettop(L) > TELEMETRY_OUTPUT_BUFFER_SIZE ) {
lua_pushboolean(L, false);
return 1;
}
else if (outputTelemetryBuffer.isAvailable()) {
uint8_t type = luaL_checkunsigned(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
uint8_t length = luaL_len(L, 2); // payload length

if( length > 10 ) { // max 10B payload
lua_pushboolean(L, false);
return 1;
}

// Ghost frames are fixed 14B
outputTelemetryBuffer.pushByte(getGhostModuleAddr()); // addr (1B)
outputTelemetryBuffer.pushByte(12); // len = payload length(10B) + type(1B) + crc(1B)
outputTelemetryBuffer.pushByte(type); // type (1B)
for (int i=0; i<length; i++) { // data, max 10B
lua_rawgeti(L, 2, i+1);
outputTelemetryBuffer.pushByte(luaL_checkunsigned(L, -1));
}
for (int i=0; i<10-length; i++) { // fill zeroes to frame size
outputTelemetryBuffer.pushByte(0);
}
outputTelemetryBuffer.pushByte(crc8(outputTelemetryBuffer.data + 2, 11 )); // Start at type, CRC over type (1B) + payload (10B)
outputTelemetryBuffer.setDestination(TELEMETRY_ENDPOINT_SPORT);
lua_pushboolean(L, true);
}
else {
lua_pushboolean(L, false);
}
return 1;
}
#endif

/*luadoc
@function getFieldInfo(source)

Expand Down Expand Up @@ -2293,6 +2400,10 @@ const luaL_Reg opentxLib[] = {
{ "crossfireTelemetryPop", luaCrossfireTelemetryPop },
{ "crossfireTelemetryPush", luaCrossfireTelemetryPush },
#endif
#if defined(GHOST)
{ "ghostTelemetryPop", luaGhostTelemetryPop },
{ "ghostTelemetryPush", luaGhostTelemetryPush },
#endif
#if defined(MULTIMODULE)
{ "multiBuffer", luaMultiBuffer },
#endif
Expand Down
105 changes: 62 additions & 43 deletions radio/src/pulses/ghost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,59 +24,73 @@
uint8_t createGhostMenuControlFrame(uint8_t * frame, int16_t * pulses)
{
uint8_t * buf = frame;
#if SPORT_MAX_BAUDRATE < 400000
*buf++ = g_eeGeneral.telemetryBaudrate == GHST_TELEMETRY_RATE_400K ? GHST_ADDR_MODULE_SYM : GHST_ADDR_MODULE_ASYM;
#else
*buf++ = GHST_ADDR_MODULE_SYM;
#endif

*buf++ = GHST_UL_RC_CHANS_SIZE;
uint8_t * crc_start = buf;
*buf++ = GHST_UL_MENU_CTRL;
*buf++ = getGhostModuleAddr(); // addr
*buf++ = GHST_UL_RC_CHANS_SIZE; // length
const uint8_t * crc_start = buf;
*buf++ = GHST_UL_MENU_CTRL; // type

// payload
*buf++ = reusableBuffer.ghostMenu.buttonAction; // Joystick states, Up, Down, Left, Right, Press
*buf++ = reusableBuffer.ghostMenu.menuAction; // menu control, open, close, etc.

for (uint8_t i = 0; i < 8; i++)
for (uint8_t i = 0; i < 8; i++) {
*buf++ = 0; // padding to make this the same size as the pulses packet
}

// crc
*buf++ = crc8(crc_start, GHST_UL_RC_CHANS_SIZE - 1);

return buf - frame;
}

// Range for pulses (channels output) is [-1024:+1024]
uint8_t createGhostChannelsFrame(uint8_t * frame, int16_t * pulses)
uint8_t createGhostChannelsFrame(uint8_t * frame, int16_t * pulses, bool raw12bits)
{
static uint8_t lastGhostFrameId = GHST_UL_RC_CHANS_HS4_5TO8;
static uint8_t lastGhostFrameId = 0;
uint8_t ghostUpper4Offset = 0;

switch (lastGhostFrameId) {
case GHST_UL_RC_CHANS_HS4_5TO8:
ghostUpper4Offset = 0;
break;
case GHST_UL_RC_CHANS_HS4_9TO12:
case GHST_UL_RC_CHANS_HS4_12_5TO8:
lastGhostFrameId = raw12bits ? GHST_UL_RC_CHANS_HS4_12_9TO12 : GHST_UL_RC_CHANS_HS4_9TO12;
ghostUpper4Offset = 4;
break;

case GHST_UL_RC_CHANS_HS4_9TO12:
case GHST_UL_RC_CHANS_HS4_12_9TO12:
lastGhostFrameId = raw12bits ? GHST_UL_RC_CHANS_HS4_12_13TO16 : GHST_UL_RC_CHANS_HS4_13TO16;
ghostUpper4Offset = 8;
break;

case GHST_UL_RC_CHANS_HS4_13TO16:
ghostUpper4Offset = 8;
case GHST_UL_RC_CHANS_HS4_12_13TO16:
lastGhostFrameId = raw12bits ? GHST_UL_RC_CHANS_HS4_12_5TO8 : GHST_UL_RC_CHANS_HS4_5TO8;
ghostUpper4Offset = 0;
break;

default: // We don't have known previous state so init
lastGhostFrameId = raw12bits ? GHST_UL_RC_CHANS_HS4_12_5TO8 : GHST_UL_RC_CHANS_HS4_5TO8;
ghostUpper4Offset = 0;
break;
}

uint8_t * buf = frame;
#if SPORT_MAX_BAUDRATE < 400000
*buf++ = g_eeGeneral.telemetryBaudrate == GHST_TELEMETRY_RATE_400K ? GHST_ADDR_MODULE_SYM : GHST_ADDR_MODULE_ASYM;
#else
*buf++ = GHST_ADDR_MODULE_SYM;
#endif
*buf++ = GHST_UL_RC_CHANS_SIZE;
*buf++ = getGhostModuleAddr(); // addr
*buf++ = GHST_UL_RC_CHANS_SIZE; // len
uint8_t * crc_start = buf;
*buf++ = lastGhostFrameId;
*buf++ = lastGhostFrameId; // type

// payload
// first 4 high speed, 12 bit channels (11 relevant bits with openTx)
uint32_t bits = 0;
uint8_t bitsavailable = 0;
for (int i = 0; i < 4; i++) {
uint32_t value = limit(0, GHST_RC_CTR_VAL_12BIT + (((pulses[i] + 2 * PPM_CH_CENTER(i) - 2 * PPM_CENTER) << 3) / 5), 2 * GHST_RC_CTR_VAL_12BIT);
uint32_t value;
if(raw12bits) {
value = limit(0, (1024 + (pulses[i] + 2 * PPM_CH_CENTER(i) - 2 * PPM_CENTER)) << 1, 0xFFF);
} else {
value = limit(0, GHST_RC_CTR_VAL_12BIT + (((pulses[i] + 2 * PPM_CH_CENTER(i) - 2 * PPM_CENTER) << 3) / 5), 2 * GHST_RC_CTR_VAL_12BIT);
}
bits |= value << bitsavailable;
bitsavailable += GHST_CH_BITS_12;
while (bitsavailable >= 8) {
Expand All @@ -89,35 +103,40 @@ uint8_t createGhostChannelsFrame(uint8_t * frame, int16_t * pulses)
// second 4 lower speed, 8 bit channels
for (int i = 4; i < 8; ++i) {
uint8_t channelIndex = i + ghostUpper4Offset;
*buf++ = limit(0, GHST_RC_CTR_VAL_8BIT + (((pulses[channelIndex] + 2 * PPM_CH_CENTER(channelIndex) - 2 * PPM_CENTER) >> 1) / 5), 2 * GHST_RC_CTR_VAL_8BIT);
uint8_t value;
if(raw12bits) {
value = limit(0, 128 + ((pulses[channelIndex] + 2 * PPM_CH_CENTER(channelIndex) - 2 * PPM_CENTER) >> 3), 0xFF);
} else {
value = limit(0, GHST_RC_CTR_VAL_8BIT + (((pulses[channelIndex] + 2 * PPM_CH_CENTER(channelIndex) - 2 * PPM_CENTER) >> 1) / 5), 2 * GHST_RC_CTR_VAL_8BIT);
}
*buf++ = value;
}

// crc
*buf++ = crc8(crc_start, GHST_UL_RC_CHANS_SIZE - 1);

switch (lastGhostFrameId) {
case GHST_UL_RC_CHANS_HS4_5TO8:
lastGhostFrameId = GHST_UL_RC_CHANS_HS4_9TO12;
break;
case GHST_UL_RC_CHANS_HS4_9TO12:
lastGhostFrameId = GHST_UL_RC_CHANS_HS4_13TO16;
break;
case GHST_UL_RC_CHANS_HS4_13TO16:
lastGhostFrameId = GHST_UL_RC_CHANS_HS4_5TO8;
break;
}

return buf - frame;
}

void setupPulsesGhost()
{
if (telemetryProtocol == PROTOCOL_TELEMETRY_GHOST) {
uint8_t * pulses = extmodulePulsesData.ghost.pulses;
if (moduleState[EXTERNAL_MODULE].counter == GHST_MENU_CONTROL)
extmodulePulsesData.ghost.length = createGhostMenuControlFrame(pulses, &channelOutputs[g_model.moduleData[EXTERNAL_MODULE].channelsStart]);
else
extmodulePulsesData.ghost.length = createGhostChannelsFrame(pulses, &channelOutputs[g_model.moduleData[EXTERNAL_MODULE].channelsStart]);

auto &module = g_model.moduleData[EXTERNAL_MODULE];
auto *p_data = &extmodulePulsesData.ghost;

#if defined(LUA)
if (outputTelemetryBuffer.destination == TELEMETRY_ENDPOINT_SPORT) {
memcpy(p_data->pulses, outputTelemetryBuffer.data, outputTelemetryBuffer.size);
p_data->length = outputTelemetryBuffer.size;
outputTelemetryBuffer.reset();
} else
#endif
if (moduleState[EXTERNAL_MODULE].counter == GHST_MENU_CONTROL) {
p_data->length = createGhostMenuControlFrame(p_data->pulses, &channelOutputs[module.channelsStart]);
} else {
p_data->length = createGhostChannelsFrame(p_data->pulses, &channelOutputs[module.channelsStart], module.ghost.raw12bits);
}
moduleState[EXTERNAL_MODULE].counter = GHST_FRAME_CHANNEL;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ bool w_zov_color(void* user, uint8_t* data, uint32_t bitoffs,
}
#endif

uint8_t select_mod_type(void* user, uint8_t* data, uint32_t bitoffs)
static uint8_t select_mod_type(void* user, uint8_t* data, uint32_t bitoffs)
{
data += bitoffs >> 3UL;
data -= offsetof(ModuleData, ppm);
Expand Down
34 changes: 32 additions & 2 deletions radio/src/storage/yaml/yaml_datastructs_funcs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ namespace yaml_conv_220 {
bool w_vbat_max(const YamlNode* node, uint32_t val, yaml_writer_func wf, void* opaque);

uint8_t select_zov(void* user, uint8_t* data, uint32_t bitoffs);
uint8_t select_mod_type(void* user, uint8_t* data, uint32_t bitoffs);
uint8_t select_script_input(void* user, uint8_t* data, uint32_t bitoffs);
uint8_t select_id1(void* user, uint8_t* data, uint32_t bitoffs);
uint8_t select_id2(void* user, uint8_t* data, uint32_t bitoffs);
Expand Down Expand Up @@ -433,7 +432,38 @@ bool w_zov_color(void* user, uint8_t* data, uint32_t bitoffs,

static uint8_t select_mod_type(void* user, uint8_t* data, uint32_t bitoffs)
{
return yaml_conv_220::select_mod_type(user, data, bitoffs);
data += bitoffs >> 3UL;
data -= offsetof(ModuleData, ppm);

ModuleData* mod_data = reinterpret_cast<ModuleData*>(data);
switch (mod_data->type) {
case MODULE_TYPE_NONE:
case MODULE_TYPE_PPM:
case MODULE_TYPE_DSM2:
case MODULE_TYPE_CROSSFIRE:
return 1;
case MODULE_TYPE_MULTIMODULE:
return 2;
case MODULE_TYPE_XJT_PXX1:
case MODULE_TYPE_R9M_PXX1:
case MODULE_TYPE_R9M_LITE_PXX1:
return 3;
case MODULE_TYPE_SBUS:
return 4;
case MODULE_TYPE_ISRM_PXX2:
case MODULE_TYPE_R9M_PXX2:
case MODULE_TYPE_R9M_LITE_PXX2:
case MODULE_TYPE_R9M_LITE_PRO_PXX2:
case MODULE_TYPE_XJT_LITE_PXX2:
return 5;
case MODULE_TYPE_FLYSKY:
if (mod_data->subType == FLYSKY_SUBTYPE_AFHDS2A) return 6;
if (mod_data->subType == FLYSKY_SUBTYPE_AFHDS3) return 7;
break;
case MODULE_TYPE_GHOST:
return 8;
}
return 0;
}

static uint8_t select_script_input(void* user, uint8_t* data, uint32_t bitoffs)
Expand Down
Loading