chore(elrs): allow future use of unused bits in the "Arming status byte" (#7022)

Co-authored-by: Peter Feerick <peter.feerick@gmail.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael
2026-08-30 18:50:57 +10:00
committed by GitHub
co-authored by Peter Feerick Claude Sonnet 5
parent bfe6cd1e7e
commit 076d80ed9e
2 changed files with 100 additions and 15 deletions
+27 -14
View File
@@ -96,20 +96,20 @@ uint8_t createCrossfireModelIDFrame(uint8_t moduleIdx, uint8_t * frame)
uint8_t createCrossfireChannelsFrame(uint8_t moduleIdx, uint8_t * frame, int16_t * pulses)
{
//
// sends channel data and also communicates commanded armed status in arming mode Switch.
// frame len 24 -> arming mode CH5: module will use channel 5
// frame len 25 -> arming mode Switch: send commanded armed status in extra byte after channel data
//
ModuleData *md = &g_model.moduleData[moduleIdx];
uint8_t armingMode = md->crsf.crsfArmingMode; // 0 = Channel mode, 1 = Switch mode
uint8_t lenAdjust = (armingMode == ARMING_MODE_SWITCH) ? 1 : 0;
// sends channel data and also communicates status information in status byte:
// - arming status in Switch mode (bit 0)
// - arming mode Switch or CH5 (bit 1)
// - bits 2-7 spare
//
uint8_t * buf = frame;
*buf++ = MODULE_ADDRESS;
*buf++ = 24 + lenAdjust; // 1(ID) + 22(channel data) + (+1 extra byte if Switch mode) + 1(CRC)
*buf++ = 25; // 1(ID) + 22(channel data) + 1(extra status byte) + 1(CRC)
uint8_t * crc_start = buf;
*buf++ = CHANNELS_ID;
//
// assemble channel data
//
uint32_t bits = 0;
uint8_t bitsavailable = 0;
for (int i=0; i<CROSSFIRE_CHANNELS_COUNT; i++) {
@@ -122,14 +122,27 @@ uint8_t createCrossfireChannelsFrame(uint8_t moduleIdx, uint8_t * frame, int16_t
bitsavailable -= 8;
}
}
if (armingMode == ARMING_MODE_SWITCH) {
//
// assemble status byte
//
ModuleData *md = &g_model.moduleData[moduleIdx];
if (md->crsf.crsfArmingMode == ARMING_MODE_SWITCH) {
swsrc_t sw = md->crsf.crsfArmingTrigger;
*buf++ = (sw != SWSRC_NONE) && getSwitch(sw, 0); // commanded armed status in Switch mode
*buf = (sw != SWSRC_NONE) && getSwitch(sw, 0); // commanded armed status in Switch mode
} else {
*buf = 0x02; // flag arming mode CH5
}
buf++;
*buf++ = crc8(crc_start, 23 + lenAdjust);
//
// add crc
//
*buf++ = crc8(crc_start, 24);
return buf - frame;
}
+73 -1
View File
@@ -22,12 +22,20 @@
#include "gtest/gtest.h"
#include "gtests.h"
#include "telemetry/telemetry.h"
#include "telemetry/crossfire.h"
#include "crc.h"
#if defined(CROSSFIRE)
uint8_t createCrossfireChannelsFrame(uint8_t moduleIdx, uint8_t * frame, int16_t * pulses);
// Spec-defined part of the frame (0x16 RC Channels Packed): sync byte, type,
// 16 x 11-bit channel packing. Expected bytes computed independently, not
// derived from createCrossfireChannelsFrame() itself.
TEST(Crossfire, createCrossfireChannelsFrame)
{
MODEL_RESET();
int16_t pulsesStart[MAX_TRAINER_CHANNELS];
uint8_t crossfire[CROSSFIRE_FRAME_MAXLEN];
@@ -38,7 +46,71 @@ TEST(Crossfire, createCrossfireChannelsFrame)
createCrossfireChannelsFrame(EXTERNAL_MODULE, crossfire, pulsesStart);
// TODO check
ASSERT_EQ(crossfire[0], MODULE_ADDRESS);
ASSERT_EQ(crossfire[2], CHANNELS_ID);
const uint8_t expectedChannelData[22] = {
0xAD, 0xA0, 0x88, 0x5E, 0xC0, 0x73, 0xA4, 0x56, 0x51, 0x4C, 0x6F,
0xE0, 0x33, 0x22, 0x2B, 0x27, 0x9A, 0x57, 0xF0, 0x1A, 0x99, 0xD5
};
ASSERT_EQ(memcmp(&crossfire[3], expectedChannelData, sizeof(expectedChannelData)), 0);
}
// Status byte after the 0x16 payload is an ExpressLRS extension, not TBS CRSF
// spec (semantics per ExpressLRS's TXModuleEndpoint.cpp / crsf_protocol.h).
// Frame is always 25 bytes (1 ID + 22 channel data + 1 status + 1 CRC);
// bit 0 = commanded armed status (Switch mode only), bit 1 = arming mode is CH5.
TEST(Crossfire, ExpressLRSArmingExtension_CH5Mode)
{
MODEL_RESET();
int16_t pulsesStart[MAX_TRAINER_CHANNELS];
uint8_t crossfire[CROSSFIRE_FRAME_MAXLEN];
memset(crossfire, 0, sizeof(crossfire));
for (int i=0; i<MAX_TRAINER_CHANNELS; i++) {
pulsesStart[i] = -1024 + (2048 / MAX_TRAINER_CHANNELS) * i;
}
g_model.moduleData[EXTERNAL_MODULE].crsf.crsfArmingMode = ARMING_MODE_CH5;
uint8_t len = createCrossfireChannelsFrame(EXTERNAL_MODULE, crossfire, pulsesStart);
ASSERT_EQ(len, 27);
ASSERT_EQ(crossfire[0], MODULE_ADDRESS);
ASSERT_EQ(crossfire[1], 25);
ASSERT_EQ(crossfire[2], CHANNELS_ID);
ASSERT_EQ(crossfire[25], 0x02); // bit 1: arming mode CH5
uint8_t crc = crc8(&crossfire[2], 24);
ASSERT_EQ(crossfire[26], crc);
}
TEST(Crossfire, ExpressLRSArmingExtension_SwitchMode)
{
MODEL_RESET();
int16_t pulsesStart[MAX_TRAINER_CHANNELS];
uint8_t crossfire[CROSSFIRE_FRAME_MAXLEN];
memset(crossfire, 0, sizeof(crossfire));
for (int i=0; i<MAX_TRAINER_CHANNELS; i++) {
pulsesStart[i] = -1024 + (2048 / MAX_TRAINER_CHANNELS) * i;
}
g_model.moduleData[EXTERNAL_MODULE].crsf.crsfArmingMode = ARMING_MODE_SWITCH;
g_model.moduleData[EXTERNAL_MODULE].crsf.crsfArmingTrigger = SWSRC_NONE;
uint8_t len = createCrossfireChannelsFrame(EXTERNAL_MODULE, crossfire, pulsesStart);
ASSERT_EQ(len, 27);
ASSERT_EQ(crossfire[0], MODULE_ADDRESS);
ASSERT_EQ(crossfire[1], 25);
ASSERT_EQ(crossfire[2], CHANNELS_ID);
ASSERT_EQ(crossfire[25], 0); // SWSRC_NONE -> not armed, bit 1 clear (Switch mode)
uint8_t crc = crc8(&crossfire[2], 24);
ASSERT_EQ(crossfire[26], crc);
}
TEST(Crossfire, crc8)