ardupilot/libraries/AP_RCProtocol/spm_srxl.cpp

1431 lines
51 KiB
C++

/*
MIT License
Copyright (c) 2019 Horizon Hobby, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include <string.h>
#include <stdint.h>
#include "spm_srxl.h"
/// LOCAL TYPES AND CONSTANTS ///
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_SPEED)
const uint16_t srxlCRCTable[] =
{
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
#endif
#define SRXL_TELEM_SUPPRESS_MAX (100)
/// PUBLIC VARIABLES ///
SrxlChannelData srxlChData = {0, 0, 0, {0}};
SrxlTelemetryData srxlTelemData = {0};
SrxlVtxData srxlVtxData = {0, 0, 1, 0, 0, 1};
/// LOCAL VARIABLES ///
static SrxlDevice srxlThisDev = {0};
static SrxlBus srxlBus[SRXL_NUM_OF_BUSES];
static bool srxlChDataIsFailsafe = false;
static bool srxlTelemetryPhase = false;
#ifdef SRXL_INCLUDE_MASTER_CODE
static uint32_t srxlFailsafeChMask = 0; // Tracks all active channels for use during failsafe transmission
#endif
static SrxlBindData srxlBindInfo = {0, 0, 0, 0};
static SrxlReceiverStats srxlRx = {0};
static uint16_t srxlTelemSuppressCount = 0;
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
static SrxlFullID srxlFwdPgmDevice = {0, 0}; // Device that should accept Forward Programming connection by default
static uint8_t srxlFwdPgmBuffer[FWD_PGM_MAX_DATA_SIZE] = {0};
static uint8_t srxlFwdPgmBufferLength = 0;
#endif
// Include additional header and externs if using STM32 hardware acceleration
#if(SRXL_CRC_OPTIMIZE_MODE > SRXL_CRC_OPTIMIZE_SIZE)
#ifdef __cplusplus
extern "C"
{
#endif
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HAL)
#if(SRXL_STM_TARGET_FAMILY == SRXL_STM_TARGET_F7)
#include "stm32f7xx_hal.h"
#else
#include "stm32f3xx_hal.h" // Default to F3 if not given
#endif
extern CRC_HandleTypeDef hcrc;
#else
#if(SRXL_STM_TARGET_FAMILY == SRXL_STM_TARGET_F7)
#error "STM32F7 targets not yet supported for register-based STM HW optimization"
#else
#include "stm32f30x.h" // Default to F3 if not given
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif
/// LOCAL HELPER FUNCTIONS ///
// Compute SRXL CRC over packet buffer (assumes length is correctly set)
static uint16_t srxlCrc16(uint8_t* packet)
{
uint16_t crc = 0; // Seed with 0
uint8_t length = packet[2] - 2; // Exclude 2 CRC bytes at end of packet from the length
if(length <= SRXL_MAX_BUFFER_SIZE - 2)
{
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_SIZE)
// Use bitwise method
for(uint8_t i = 0; i < length; ++i)
{
crc = crc ^ ((uint16_t)packet[i] << 8);
for(int b = 0; b < 8; b++)
{
if(crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc = crc << 1;
}
}
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HW)
// Use direct STM32 HW CRC register access
uint8_t* pEnd = &packet[length];
srxlEnterCriticalSection();
#ifdef SRXL_SAVE_HW_CRC_CONTEXT
uint32 savedPOL = CRC->POL;
uint32 savedINIT = CRC->INIT;
uint32 savedCR = CRC->CR;
uint32 savedDR = CRC->DR;
#endif
CRC->POL = 0x1021;
CRC->INIT = 0;
CRC->CR = 0x09; // 16-bit polynomial, no input or output reversal, reset to init of 0
while(packet < pEnd)
*(uint8_t*)(CRC_BASE) = *(packet++);
crc = (uint16_t)CRC->DR;
#ifdef SRXL_SAVE_HW_CRC_CONTEXT
// We have to use this convoluted method to restore things because writing INIT sets DR too
CRC->CR = 0;
CRC->POL = 0x04C11DB7;
CRC->INIT = savedINIT;
CRC->DR = savedINIT;
CRC->POL = savedDR;
CRC->DR = 1;
CRC->CR = savedCR;
CRC->POL = savedPOL;
#endif
srxlExitCriticalSection();
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HAL)
// STM32f3/f7 hardware optimization using the STM32Cube libraries in HAL mode requires the following
// configuration, set in the STM32CubeMX "Pinout & Configuration" tab under "Computing > CRC":
// Basic Parameters
// Default Polynomial State = Disable
// CRC Length = 16-bit
// CRC Generating Polynomial = X12+X5+X0
// Default Init Value State = Disable
// Init Value For CRC Computation = 0
// Advanced Parameters
// Input Data Inversion Mode = None
// Output Data Inversion Mode = Disable
// Input Data Format = Bytes
crc = (uint16_t)HAL_CRC_Calculate(&hcrc, (uint32_t*)packet, length);
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_EXTERNAL)
crc = SRXL_CRC_CALCULATE(packet, length, crc);
#else
// Default to table-lookup method
uint8_t i;
for(i = 0; i < length; ++i)
{
// Get indexed position in lookup table using XOR of current CRC hi byte
uint8_t pos = (uint8_t)((crc >> 8) ^ packet[i]);
// Shift LSB up and XOR with the resulting lookup table entry
crc = (uint16_t)((crc << 8) ^ (uint16_t)(srxlCRCTable[pos]));
}
#endif
}
return crc;
}
// Get the receiver entry for the requested bus and device ID
static inline SrxlRcvrEntry* srxlGetReceiverEntry(uint8_t busIndex, uint8_t deviceID)
{
SrxlRcvrEntry* pRcvr = 0;
uint8_t i;
for(i = 0; i < srxlRx.rcvrCount; ++i)
{
if((srxlRx.rcvr[i].busBits & (1u << busIndex)) && (srxlRx.rcvr[i].deviceID == deviceID))
{
pRcvr = &srxlRx.rcvr[i];
break;
}
}
return pRcvr;
}
// Add a new receiver entry for the given device
static inline SrxlRcvrEntry* srxlAddReceiverEntry(SrxlBus* pBus, SrxlDevEntry devEntry)
{
// Only allow receivers (or flight controllers in certain circumstances) to be added
if(!pBus || devEntry.deviceID < 0x10 || devEntry.deviceID >= 0x40)
return 0;
// If we didn't previously add this receiver, add it now if we have room
SrxlRcvrEntry* pRcvr = srxlGetReceiverEntry(pBus->fullID.busIndex, devEntry.deviceID);
if(!pRcvr)
{
if(srxlRx.rcvrCount >= SRXL_MAX_RCVRS)
return 0;
uint8_t i = srxlRx.rcvrCount++;
pRcvr = &srxlRx.rcvr[i];
pRcvr->deviceID = devEntry.deviceID;
pRcvr->busBits = (1u << pBus->fullID.busIndex);
pRcvr->info = devEntry.info;
// If this receiver is full-range, insert into our sorted list after the other full-range telemetry receivers
if(pRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE)
{
uint8_t n;
for(n = i; n > srxlRx.rcvrSortInsert; --n)
srxlRx.rcvrSorted[n] = srxlRx.rcvrSorted[n - 1];
srxlRx.rcvrSorted[(srxlRx.rcvrSortInsert)++] = pRcvr;
}
// Else just tack onto the end
else
{
srxlRx.rcvrSorted[i] = pRcvr;
}
}
// If this new receiver is a base receiver that supports telemetry or we haven't set a default active telemetry receiver, set it
if(!srxlRx.pTelemRcvr || (pRcvr->deviceID >= 0x20 && pRcvr->deviceID < 0x30 && (pRcvr->info & SRXL_DEVINFO_TELEM_TX_ENABLED)))
{
srxlRx.pTelemRcvr = pRcvr;
}
return pRcvr;
}
// Pick the best receiver to send telemetry on
static inline SrxlRcvrEntry* srxlChooseTelemRcvr(void)
{
// If we only know about one receiver, set it to that
if(srxlRx.rcvrCount == 1)
return srxlRx.rcvrSorted[0];
// If we were previously sending telemetry
if(srxlRx.pTelemRcvr && srxlRx.pTelemRcvr->channelMask)
{
// If the current choice is not full-range
if((srxlRx.pTelemRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE) == 0)
{
// Then see if there is a full-range choice that received channel data to switch to
uint8_t i;
for(i = 0; i < srxlRx.rcvrSortInsert; ++i)
{
if(srxlRx.rcvrSorted[i]->channelMask)
return srxlRx.rcvrSorted[i];
}
}
// Else keep using the current receiver
return srxlRx.pTelemRcvr;
}
// Else just pick the first one that got channel data this past frame
else
{
uint8_t i;
for(i = 0; i < srxlRx.rcvrCount; ++i)
{
if(srxlRx.rcvrSorted[i]->channelMask)
return srxlRx.rcvrSorted[i];
}
}
return 0;
}
// Return pointer to device entry matching the given ID, or NULL if not found
static SrxlDevEntry* srxlGetDeviceEntry(SrxlBus* pBus, uint8_t deviceID)
{
if(pBus)
{
uint8_t i;
for(i = 0; i < pBus->rxDevCount; ++i)
{
if(pBus->rxDev[i].deviceID == deviceID)
return &(pBus->rxDev[i]);
}
}
return 0;
}
// Add an entry to our list of devices found on the SRXL bus (or update an entry if it already exists)
static SrxlDevEntry* srxlAddDeviceEntry(SrxlBus* pBus, SrxlDevEntry devEntry)
{
// Don't allow broadcast or unknown device types to be added
if(!pBus || devEntry.deviceID < 0x10 || devEntry.deviceID > 0xEF)
return 0;
// Limit device priority
if(devEntry.priority > 100)
devEntry.priority = 100;
// Update device entry if it already exists
SrxlDevEntry* retVal = srxlGetDeviceEntry(pBus, devEntry.deviceID);
if(retVal)
{
pBus->rxDevPrioritySum -= retVal->priority;
*retVal = devEntry;
pBus->rxDevPrioritySum += retVal->priority;
}
// Else add to the list if we have room
else if(pBus->rxDevCount < SRXL_MAX_DEVICES)
{
retVal = &(pBus->rxDev[pBus->rxDevCount++]);
*retVal = devEntry;
pBus->rxDevPrioritySum += retVal->priority;
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
// If the new device supports Forward Programming and is a base receiver or flight controller, choose as the default
if(devEntry.info & SRXL_DEVINFO_FWD_PROG_SUPPORT)
{
uint8_t devType = devEntry.deviceID >> 4;
if(devType > (srxlFwdPgmDevice.deviceID >> 4) && devType < SrxlDevType_ESC)
{
srxlFwdPgmDevice.deviceID = devEntry.deviceID;
srxlFwdPgmDevice.busIndex = pBus->fullID.busIndex;
}
}
#endif
// If the new device is a receiver, add to our receiver list
if(retVal->deviceID < 0x30)
{
srxlAddReceiverEntry(pBus, *retVal);
}
}
return retVal;
}
/// PUBLIC FUNCTIONS ///
/**
@brief Initialize common SRXL info for this device
@param deviceID: SRXL Device ID (see section 7.1.1 of SRXL2 Spec)
@param priority: Requested telemetry priority (1-100; typical is 10 per unique message type)
@param info: Device info bits (see SRXL_DEVINFO_XXX bits in spm_srxl.h)
@param uid: Unique 32-bit id to avoid device ID collision during handshake
@return bool: True if device info was successfully initialized
*/
bool srxlInitDevice(uint8_t deviceID, uint8_t priority, uint8_t info, uint32_t uid)
{
if(deviceID < 0x10 || deviceID > 0xEF)
return false;
srxlThisDev.devEntry.deviceID = deviceID;
srxlThisDev.devEntry.info = info;
srxlThisDev.devEntry.priority = priority;
srxlThisDev.devEntry.rfu = 0;
srxlThisDev.uid = uid;
srxlThisDev.vtxProxy = false;
#ifdef SRXL_INCLUDE_MASTER_CODE
// If this device is a receiver, add to our receiver info
if(deviceID < 0x30)
{
srxlInitReceiver(deviceID, info);
}
#endif
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
// If this device is a receiver or flight controller that supports Forward Programming, set as default
if((info & SRXL_DEVINFO_FWD_PROG_SUPPORT) && deviceID < 0x40)
{
srxlFwdPgmDevice.deviceID = deviceID;
srxlFwdPgmDevice.busIndex = 0;
}
#endif
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HW)
// Enable the peripheral clock for the HW CRC engine
RCC->AHBENR |= RCC_AHBENR_CRCEN;
#endif
return true;
}
/**
@brief Initialize bus settings for the given SRXL bus
@param busIndex: Index into srxlBus array of bus entries
@param uart: Number to identify UART to which this SRXL bus should be connected
@param baudSupported: 0 = 115200 baud, 1 = 400000 baud
@return bool: True if SRXL bus was successfully initialized
*/
bool srxlInitBus(uint8_t busIndex, uint8_t uart, uint8_t baudSupported)
{
if(busIndex >= SRXL_NUM_OF_BUSES || !srxlThisDev.devEntry.deviceID)
return false;
SrxlBus* pBus = &srxlBus[busIndex];
pBus->state = SrxlState_ListenOnStartup;
pBus->fullID.deviceID = srxlThisDev.devEntry.deviceID;
pBus->fullID.busIndex = busIndex;
pBus->rxDevCount = 0;
pBus->rxDevPrioritySum = 0;
pBus->requestID = (srxlThisDev.devEntry.deviceID == 0x10) ? 0x11 : 0;
pBus->baudSupported = baudSupported;
pBus->baudRate = SRXL_BAUD_115200;
pBus->frameErrCount = 0;
pBus->uart = uart;
// Default remote receiver is automatically master -- everyone else figures it out during handshake
pBus->master = (srxlThisDev.devEntry.deviceID == 0x10);
pBus->pMasterRcvr = (srxlThisDev.devEntry.deviceID == 0x10) ? &srxlRx.rcvr[0] : 0;
pBus->initialized = true;
return true;
}
/**
@brief See if this device is the bus master on the given bus
@param busIndex: Index into srxlBus array for the desired SRXL bus
@return bool: True if this device is the bus master on the given SRXL bus
*/
bool srxlIsBusMaster(uint8_t busIndex)
{
return (busIndex < SRXL_NUM_OF_BUSES && srxlBus[busIndex].master);
}
/**
@brief Get the current SRXL state machine timeout count for the given bus
@param busIndex: Index into srxlBus array for the desired SRXL bus
@return uint16_t: Timeout count in ms for the given SRXL bus
*/
uint16_t srxlGetTimeoutCount_ms(uint8_t busIndex)
{
return (busIndex < SRXL_NUM_OF_BUSES) ? srxlBus[busIndex].timeoutCount_ms : 0;
}
/**
@brief Get the Device ID of this device on the given bus
@param busIndex: Index into srxlBus array for the desired SRXL bus
@return uint8_t: Device ID of this device on the given SRXL bus
*/
uint8_t srxlGetDeviceID(uint8_t busIndex)
{
return (busIndex < SRXL_NUM_OF_BUSES) ? srxlBus[busIndex].fullID.deviceID : 0;
}
/**
@brief Internal send function called by srxlRun() -- do not call in user code
@param pBus: Pointer to SRXL bus entry for the desired SRXL bus
@param srxlCmd: Specific type of packet to send
@param replyID: Device ID of the device this Send command is targeting
*/
static void srxlSend(SrxlBus* pBus, SRXL_CMD srxlCmd, uint8_t replyID)
{
if(!pBus || !pBus->initialized)
return;
memset(pBus->srxlOut.raw, 0, SRXL_MAX_BUFFER_SIZE);
pBus->srxlOut.header.srxlID = SPEKTRUM_SRXL_ID;
// VTX Data
if(srxlCmd == SRXL_CMD_VTX)
{
pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + sizeof(SrxlVtxData);
pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_VTX;
pBus->srxlOut.control.payload.replyID = replyID;
pBus->srxlOut.control.payload.vtxData = srxlVtxData;
}
#ifdef SRXL_INCLUDE_MASTER_CODE
// Channel Data
else if(srxlCmd == SRXL_CMD_CHANNEL || srxlCmd == SRXL_CMD_CHANNEL_FS)
{
pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
uint32_t channelMask;
if(srxlCmd == SRXL_CMD_CHANNEL)
{
pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_CHANNEL;
pBus->srxlOut.control.payload.replyID = replyID;
channelMask = srxlChData.mask;
}
else // == SRXL_CMD_CHANNEL_FS
{
// In failsafe mode, we dont want a telemetry reply
pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_CHANNEL_FS;
pBus->srxlOut.control.payload.replyID = 0;
channelMask = srxlFailsafeChMask;
}
// Set signal quality info (only a bus master sends this, so assume srxlChData contains the latest values)
pBus->srxlOut.control.payload.channelData.rssi = srxlChData.rssi;
#ifdef SRXL_IS_HUB
pBus->srxlOut.control.payload.channelData.frameLosses = srxlRx.frameLosses;
#else
pBus->srxlOut.control.payload.channelData.frameLosses = srxlRx.rcvr[0].fades;
#endif
uint8_t channelIndex = 0;
uint32_t channelMaskBit = 1;
for(uint8_t i = 0; i < 32; ++i, channelMaskBit <<= 1)
{
if(channelMask & channelMaskBit)
{
pBus->srxlOut.control.payload.channelData.values[channelIndex++] = srxlChData.values[i];
}
}
// Set bits in packet for channels we populated, and clear those mask bits if it was part of a normal channel data command
pBus->srxlOut.control.payload.channelData.mask = channelMask;
if(srxlCmd == SRXL_CMD_CHANNEL)
srxlChData.mask &= ~channelMask;
pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + 7 + (2 * channelIndex);
}
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
// Forward Programming Pass-thru
else if(srxlCmd == SRXL_CMD_FWDPGM)
{
pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + 3 + srxlFwdPgmBufferLength;
pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_FWDPGM;
pBus->srxlOut.control.payload.replyID = replyID;
memcpy(pBus->srxlOut.control.payload.fpData.data, srxlFwdPgmBuffer, srxlFwdPgmBufferLength);
}
#endif // SRXL_INCLUDE_FWD_PGM_CODE
#endif // SRXL_INCLUDE_MASTER_CODE
// RSSI Data
else if(srxlCmd == SRXL_CMD_RSSI)
{
pBus->srxlOut.header.packetType = SRXL_RSSI_ID;
pBus->srxlOut.header.length = sizeof(SrxlRssiPacket);
pBus->srxlOut.rssi.request = SRXL_RSSI_REQ_REQUEST; // TODO: Needs to handle both directions!
pBus->srxlOut.rssi.antennaA = 0; // TODO: Fill in actual data later
pBus->srxlOut.rssi.antennaB = 0;
pBus->srxlOut.rssi.antennaC = 0;
pBus->srxlOut.rssi.antennaD = 0;
}
else if(srxlCmd == SRXL_CMD_HANDSHAKE)
{
pBus->srxlOut.header.packetType = SRXL_HANDSHAKE_ID;
pBus->srxlOut.header.length = sizeof(SrxlHandshakePacket);
pBus->srxlOut.handshake.payload.srcDevID = srxlThisDev.devEntry.deviceID;
pBus->srxlOut.handshake.payload.destDevID = replyID;
pBus->srxlOut.handshake.payload.priority = srxlThisDev.devEntry.priority;
pBus->srxlOut.handshake.payload.baudSupported = pBus->baudSupported;
pBus->srxlOut.handshake.payload.info = srxlThisDev.devEntry.info;
pBus->srxlOut.handshake.payload.uid = srxlThisDev.uid;
}
else if(srxlCmd == SRXL_CMD_TELEMETRY)
{
srxlFillTelemetry(&pBus->srxlOut.telemetry.payload);
pBus->srxlOut.header.packetType = SRXL_TELEM_ID;
pBus->srxlOut.header.length = sizeof(SrxlTelemetryPacket);
// If we successfully received a handshake from the bus master
if(pBus->pMasterRcvr)
{
// If we know that a device on this bus should send it, then target that device
if(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr->busBits & (1u << pBus->fullID.busIndex)))
pBus->srxlOut.telemetry.destDevID = srxlRx.pTelemRcvr->deviceID;
else
pBus->srxlOut.telemetry.destDevID = 0;
}
else
{
// Send 0xFF to tell bus master to re-send the handshake so we know where to direct telemetry in the future
pBus->srxlOut.telemetry.destDevID = 0xFF;
}
#ifdef SRXL_INCLUDE_MASTER_CODE
if(srxlRx.pTelemRcvr && (pBus->srxlOut.telemetry.destDevID == srxlRx.pTelemRcvr->deviceID))
{
// Don't mark telemetry as having been sent if we are sending it ourself over RF
if(pBus->srxlOut.telemetry.destDevID != srxlThisDev.pRcvr->deviceID)
{
srxlTelemetrySent();
// Clear telemetry buffer after sending so we don't repeatedly display old data
// srxlTelemData.sensorID = srxlTelemData.secondaryID = 0;
}
}
#endif
}
else if(srxlCmd == SRXL_CMD_ENTER_BIND)
{
pBus->srxlOut.header.packetType = SRXL_BIND_ID;
pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
pBus->srxlOut.bind.request = SRXL_BIND_REQ_ENTER;
pBus->srxlOut.bind.deviceID = replyID;
pBus->srxlOut.bind.data.type = DSMX_11MS;
pBus->srxlOut.bind.data.options = (replyID != 0xFF) ? SRXL_BIND_OPT_TELEM_TX_ENABLE | SRXL_BIND_OPT_BIND_TX_ENABLE : 0;
pBus->srxlOut.bind.data.guid = 0;
pBus->srxlOut.bind.data.uid = 0;
}
else if(srxlCmd == SRXL_CMD_REQ_BIND)
{
pBus->srxlOut.header.packetType = SRXL_BIND_ID;
pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
pBus->srxlOut.bind.request = SRXL_BIND_REQ_STATUS;
pBus->srxlOut.bind.deviceID = replyID;
memset(&(pBus->srxlOut.bind.data), 0, sizeof(SrxlBindData));
}
else if(srxlCmd == SRXL_CMD_SET_BIND)
{
pBus->srxlOut.header.packetType = SRXL_BIND_ID;
pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
pBus->srxlOut.bind.request = SRXL_BIND_REQ_SET_BIND;
pBus->srxlOut.bind.deviceID = replyID;
pBus->srxlOut.bind.data = srxlBindInfo;
}
else if(srxlCmd == SRXL_CMD_BIND_INFO)
{
pBus->srxlOut.header.packetType = SRXL_BIND_ID;
pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
pBus->srxlOut.bind.request = SRXL_BIND_REQ_BOUND_DATA;
pBus->srxlOut.bind.deviceID = replyID;
pBus->srxlOut.bind.data = srxlBindInfo;
}
// Compute CRC over entire SRXL packet (excluding the 2 CRC bytes at the end)
uint16_t crc = srxlCrc16(pBus->srxlOut.raw);
// Add CRC to packet in big-endian byte order
pBus->srxlOut.raw[pBus->srxlOut.header.length - 2] = (crc >> 8) & 0xFF;
pBus->srxlOut.raw[pBus->srxlOut.header.length - 1] = crc & 0xFF;
// Send the packet out over the assigned UART
srxlSendOnUart(pBus->uart, pBus->srxlOut.raw, pBus->srxlOut.header.length);
}
/**
@brief Parse an SRXL packet received on the given SRXL bus UART
@param busIndex: Index of SRXL bus state information entry in the srxlBus array
@param packet: Pointer to received packet data
@param length: Length in bytes of received packet data
@return bool: True if a valid packet was received, else false
*/
bool srxlParsePacket(uint8_t busIndex, uint8_t* packet, uint8_t length)
{
// Validate parameters
if(busIndex >= SRXL_NUM_OF_BUSES || !packet || length < 5 || length > SRXL_MAX_BUFFER_SIZE)
return false;
// Validate SRXL ID and length
if(packet[0] != SPEKTRUM_SRXL_ID || packet[2] != length)
return false;
// Validate checksum
uint16_t crc = srxlCrc16(packet);
if((((uint16_t)packet[length - 2] << 8) | packet[length - 1]) != crc)
return false;
// Copy packet into our unioned buffer to avoid "strict aliasing" violations
SrxlBus* pBus = &srxlBus[busIndex];
SrxlPacket* pRx = &(pBus->srxlIn);
memcpy(pRx, packet, length);
// Handle restart with ongoing communications -- bump to run state
pBus->timeoutCount_ms = 0; // TODO: Should we clear this even if packet isn't valid?
if(pBus->state < SrxlState_Running && pRx->header.packetType != SRXL_HANDSHAKE_ID)
pBus->state = SrxlState_Running;
// Parse the specific data
switch(pRx->header.packetType)
{
case SRXL_CTRL_ID: // 0xCD
{
SrxlControlData* pCtrlData = &(pRx->control.payload);
// Validate command
if(pCtrlData->cmd > SRXL_CTRL_CMD_FWDPGM)
break;
// VTX
if(pCtrlData->cmd == SRXL_CTRL_CMD_VTX)
{
if(srxlSetVtxData(&pCtrlData->vtxData))
srxlOnVtx(&srxlVtxData);
if(pCtrlData->replyID == pBus->fullID.deviceID || pCtrlData->replyID == 0xFF || srxlThisDev.vtxProxy)
{
// TODO: Should we ack this somehow
}
}
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
// Forward Programming
else if(pCtrlData->cmd == SRXL_CTRL_CMD_FWDPGM)
{
if(pCtrlData->replyID == pBus->fullID.deviceID)
{
memcpy(srxlFwdPgmBuffer, pCtrlData->fpData.data, pRx->header.length - 3 - SRXL_CTRL_BASE_LENGTH);
srxlFwdPgmBufferLength = length;
if(pCtrlData->replyID == srxlFwdPgmDevice.deviceID)
{
// Handle Forward Programming command locally
srxlOnFwdPgm(srxlFwdPgmBuffer, srxlFwdPgmBufferLength);
}
else if(srxlFwdPgmDevice.deviceID && srxlBus[srxlFwdPgmDevice.busIndex].master)
{
// Pass it on through to the next target
srxlBus[srxlFwdPgmDevice.busIndex].txFlags.sendFwdPgmData = 1;
}
}
}
#endif
// Channel Data or Failsafe Data
else
{
bool isFailsafe = (pCtrlData->cmd == SRXL_CTRL_CMD_CHANNEL_FS);
srxlChData.rssi = pCtrlData->channelData.rssi;
srxlChData.frameLosses = pCtrlData->channelData.frameLosses;
if(pBus->pMasterRcvr)
{
if(pCtrlData->channelData.rssi < 0)
{
pBus->pMasterRcvr->rssi_dBm = pCtrlData->channelData.rssi;
pBus->pMasterRcvr->rssiRcvd |= RSSI_RCVD_DBM;
}
else
{
pBus->pMasterRcvr->rssi_Pct = pCtrlData->channelData.rssi;
pBus->pMasterRcvr->rssiRcvd |= RSSI_RCVD_PCT;
}
// If the receiver is sending alternating dBm/%, then use that as phase
if(pBus->pMasterRcvr->rssiRcvd == RSSI_RCVD_BOTH)
{
srxlTelemetryPhase = pCtrlData->channelData.rssi >= 0;
}
pBus->pMasterRcvr->fades = pCtrlData->channelData.frameLosses;
pBus->pMasterRcvr->channelMask = isFailsafe ? 0 : pCtrlData->channelData.mask;
srxlRx.rxBusBits |= pBus->pMasterRcvr->busBits;
}
// Only save received channel values to srxlChData if it's normal channel data or we're in a hold condition
if(!isFailsafe || !srxlRx.lossCountdown)
{
uint8_t channelIndex = 0;
uint32_t channelMaskBit = 1;
uint8_t i;
for(i = 0; i < 32 && channelMaskBit <= pCtrlData->channelData.mask; ++i, channelMaskBit <<= 1)
{
if(pCtrlData->channelData.mask & channelMaskBit)
{
srxlChData.values[i] = pCtrlData->channelData.values[channelIndex++];
srxlChData.mask |= channelMaskBit;
}
}
}
srxlChDataIsFailsafe = isFailsafe; // TODO: Can we still assume this???
srxlReceivedChannelData(&(pCtrlData->channelData), isFailsafe);
// Figure out what type of reply packet to send, if any
if(pCtrlData->replyID == 0)
{
if(pBus->txFlags.enterBind)
{
pBus->state = SrxlState_SendEnterBind;
pBus->txFlags.enterBind = 0;
}
else if(pBus->txFlags.setBindInfo)
{
if(srxlRx.pBindRcvr) // TODO: Double-check this logic
{
pBus->requestID = srxlRx.pBindRcvr->deviceID;
pBus->state = SrxlState_SendSetBindInfo;
}
pBus->txFlags.setBindInfo = 0;
}
else if(pBus->txFlags.broadcastBindInfo)
{
pBus->requestID = 0xFF;
pBus->state = SrxlState_SendSetBindInfo;
pBus->txFlags.broadcastBindInfo = 0;
}
}
else if(pCtrlData->replyID == pBus->fullID.deviceID)
{
pBus->state = SrxlState_SendTelemetry;
}
}
break;
}
case SRXL_HANDSHAKE_ID: // 0x21
{
if(length < sizeof(SrxlHandshakePacket))
return false;
// If this is an unprompted handshake (dest == 0) from a higher device ID, then we're the master
SrxlHandshakeData* pHandshake = &(pRx->handshake.payload);
if((pHandshake->destDevID == 0) && (pHandshake->srcDevID > pBus->fullID.deviceID))
{
// Send a reply immediately to get the slave to shut up
pBus->state = SrxlState_SendHandshake;
pBus->requestID = pHandshake->srcDevID;
pBus->baudSupported = SRXL_SUPPORTED_BAUD_RATES;
srxlRun(busIndex, 0);
pBus->requestID = pBus->fullID.deviceID;
pBus->state = SrxlState_SendHandshake;
pBus->master = true;
pBus->pMasterRcvr = &srxlRx.rcvr[0];
}
// Add this device to our list of discovered devices
SrxlDevEntry newDev = {.deviceID = pHandshake->srcDevID, .priority = pHandshake->priority, .info = pHandshake->info};
srxlAddDeviceEntry(pBus, newDev);
// Bus master needs to track responses and poll next device
if(pBus->master)
{
// Keep track of baud rates supported
pBus->baudSupported &= pHandshake->baudSupported;
// Make sure the state machine is set to poll -- this will eventually reach broadcast address (0xFF)
pBus->state = SrxlState_SendHandshake;
}
// Broadcast handshake sets the agreed upon baud rate for this bus
else if(pHandshake->destDevID == 0xFF)
{
// Get bus master receiver entry (if it's a flight controller, add it now as a receiver on this bus)
if(newDev.deviceID >= 0x30 && newDev.deviceID < 0x40)
{
pBus->pMasterRcvr = srxlAddReceiverEntry(pBus, newDev);
}
else
{
pBus->pMasterRcvr = srxlGetReceiverEntry(busIndex, newDev.deviceID);
}
// Set baud rate and advance to run state
pBus->baudRate = pHandshake->baudSupported;
if(pBus->baudRate & SRXL_BAUD_400000)
{
srxlChangeBaudRate(pBus->uart, 400000); // Only alternate rate supported for now...
}
pBus->state = SrxlState_Running;
}
// Normal Handshake destined for this device should be replied to -- else ignore
else
{
if(pHandshake->destDevID == pBus->fullID.deviceID && pBus->state != SrxlState_SendHandshake)
{
pBus->requestID = pHandshake->srcDevID;
pBus->state = SrxlState_SendHandshake;
}
else
{
pBus->state = SrxlState_ListenForHandshake;
}
}
break;
}
case SRXL_PARAM_ID: // 0x50
{
// TODO: Add later
break;
}
case SRXL_RSSI_ID: // 0x55
{
// TODO: Add later
break;
}
case SRXL_BIND_ID: // 0x41
{
if(length < sizeof(SrxlBindPacket))
return false;
SrxlBindPacket* pBindInfo = &(pRx->bind);
// If this is a bound data report
if(pBindInfo->request == SRXL_BIND_REQ_BOUND_DATA)
{
// Call the user-defined callback -- if returns true, bind all other receivers
SrxlFullID boundID;
boundID.deviceID = pBindInfo->deviceID;
boundID.busIndex = busIndex;
if(srxlOnBind(boundID, pBindInfo->data))
{
// Update the bind info
srxlBindInfo.type = pBindInfo->data.type;
if(pBindInfo->data.options & SRXL_BIND_OPT_TELEM_TX_ENABLE)
{
SrxlRcvrEntry* pNewTelem = srxlGetReceiverEntry(busIndex, pBindInfo->deviceID);
if(pNewTelem || !srxlRx.pTelemRcvr)
srxlRx.pTelemRcvr = pNewTelem;
}
srxlBindInfo.options = 0; // Disable telemetry and Discovery reply when setting other receivers
srxlBindInfo.guid = pBindInfo->data.guid;
srxlBindInfo.uid = pBindInfo->data.uid;
// Try to set bind info for all other receivers on other buses to match it
uint8_t b;
for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
{
if(b == busIndex)
continue;
srxlBus[b].txFlags.broadcastBindInfo = 1;
}
}
}
// If this bind packet is directed at us
else if(pBindInfo->deviceID == pBus->fullID.deviceID || pBindInfo->deviceID == 0xFF)
{
// Check for Enter Bind Mode (only valid if sent to a specific receiver)
if(pBindInfo->request == SRXL_BIND_REQ_ENTER)
{
#ifdef SRXL_INCLUDE_MASTER_CODE
srxlBindInfo.type = pBindInfo->data.type;
srxlBindInfo.options = pBindInfo->data.options;
srxlBindInfo.guid = 0;
srxlBindInfo.uid = 0;
srxlTryToBind(srxlBindInfo);
#endif
}
else if(pBindInfo->request == SRXL_BIND_REQ_STATUS && srxlThisDev.pRcvr)
{
// TODO: Fill in data if we didn't just bind?
pBus->txFlags.reportBindInfo = 1;
}
// Handle set bind info request
else if(pBindInfo->request == SRXL_BIND_REQ_SET_BIND)
{
srxlBindInfo = pBindInfo->data;
#ifdef SRXL_INCLUDE_MASTER_CODE
if(pBus->fullID.deviceID < 0x30)
srxlTryToBind(srxlBindInfo);
#endif
}
}
break;
}
case SRXL_TELEM_ID: // 0x80
{
if(length < sizeof(SrxlTelemetryPacket))
return false;
// NOTE: This data should be sent by exactly one telemetry device in response to a bus master request,
// so it is safe to update the global pTelemRcvr here even though this is a bus-specific function.
SrxlTelemetryPacket* pTelem = &(pRx->telemetry);
memcpy(&srxlTelemData, &pTelem->payload, sizeof(srxlTelemData));
// If the telemetry destination is set to broadcast, that indicates a request to re-handshake
if(pBus->master && pTelem->destDevID == 0xFF)
{
// If the master only found one device, don't poll again -- just tell the requesting device who we are
pBus->requestID = pBus->rxDevCount > 1 ? pBus->fullID.deviceID : 0xFF;
pBus->state = SrxlState_SendHandshake;
}
// If the incoming telemetry is destined for us, then we need to figure out who should send it over RF
else if(pTelem->destDevID == pBus->fullID.deviceID)
{
// This needs different logic for hubs versus endpoints
#ifdef SRXL_IS_HUB
if(srxlRx.pTelemRcvr == 0)
{
srxlRx.pTelemRcvr = srxlChooseTelemRcvr();
}
#else
srxlRx.pTelemRcvr = srxlThisDev.pRcvr;
#endif
// Enable this device's telemetry tx based on whether we are the chosen telemetry receiver
#ifdef SRXL_INCLUDE_MASTER_CODE
srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr == srxlThisDev.pRcvr));
#endif
}
// Else turn off our telemetry and that of any receivers we might reply to via our own telemetry
else
{
srxlRx.pTelemRcvr = 0;
#ifdef SRXL_INCLUDE_MASTER_CODE
srxlSetTelemetryTxEnable(false);
#endif
}
srxlTelemSuppressCount = 0;
#ifdef SRXL_INCLUDE_MASTER_CODE
srxlSuppressInternalTelemetry(&pTelem->payload);
#endif
break;
}
default:
{
break;
}
}
// Run state machine for slave devices after each received packet
if(!pBus->master)
{
srxlRun(busIndex, 0);
}
return true;
}
/**
@brief Run the SRXL state machine after each receive or rx timeout
@param busIndex: Index of SRXL bus state information entry in the srxlBus array
@param timeoutDelta_ms: Number of milliseconds to increment receive timeout if a timeout
occured, or <= 0 to clear timeout count upon packet receive.
*/
void srxlRun(uint8_t busIndex, int16_t timeoutDelta_ms)
{
SrxlBus* pBus = &srxlBus[busIndex];
if(busIndex >= SRXL_NUM_OF_BUSES || !pBus->initialized || pBus->state == SrxlState_Disabled)
return;
// Check receive timeout and advance state if needed
if(timeoutDelta_ms > 0)
{
pBus->timeoutCount_ms += timeoutDelta_ms;
if(pBus->timeoutCount_ms >= 30000)
pBus->timeoutCount_ms = 30000;
if(pBus->timeoutCount_ms >= 50)
{
// After startup delay of 50ms, switch to handshake send or listen based on device unit ID
if(pBus->state == SrxlState_ListenOnStartup)
{
pBus->state = (pBus->fullID.deviceID & 0x0F) ? SrxlState_ListenForHandshake : SrxlState_SendHandshake;
}
// Reset non-master device back to startup conditions if 50ms elapses with no communications
else if(!pBus->master && pBus->state >= SrxlState_Running)
{
srxlChangeBaudRate(pBus->uart, 115200);
pBus->baudRate = SRXL_BAUD_115200;
pBus->timeoutCount_ms = 0;
pBus->requestID = 0; // Change back to 0 to indicate unprompted handshake from slave device
if(pBus->pMasterRcvr)
{
pBus->pMasterRcvr->channelMask = 0;
pBus->pMasterRcvr->fades = 0xFFFF;
pBus->pMasterRcvr->rssi_Pct = 0;
pBus->pMasterRcvr->rssi_dBm = -1;
}
pBus->state = SrxlState_ListenOnStartup;
}
}
}
else
{
pBus->timeoutCount_ms = 0;
}
if(!pBus->master)
{
// Non-master actions for the given state
switch(pBus->state)
{
case SrxlState_SendHandshake:
{
srxlSend(pBus, SRXL_CMD_HANDSHAKE, pBus->requestID);
break;
}
case SrxlState_SendTelemetry:
{
srxlSend(pBus, SRXL_CMD_TELEMETRY, pBus->requestID);
pBus->state = SrxlState_Running;
break;
}
case SrxlState_SendVTX:
{
srxlSend(pBus, SRXL_CMD_VTX, pBus->requestID);
pBus->state = SrxlState_Running;
break;
}
case SrxlState_SendEnterBind:
{
if(srxlRx.pBindRcvr && (srxlRx.pBindRcvr != srxlThisDev.pRcvr))
{
srxlSend(pBus, SRXL_CMD_ENTER_BIND, srxlRx.pBindRcvr->deviceID);
}
else
{
srxlBindInfo.options = 0;
srxlSend(pBus, SRXL_CMD_ENTER_BIND, 0xFF);
}
pBus->state = SrxlState_Running;
break;
}
case SrxlState_SendSetBindInfo:
{
srxlSend(pBus, SRXL_CMD_SET_BIND, pBus->requestID);
pBus->state = SrxlState_Running;
break;
}
case SrxlState_SendBoundDataReport:
{
srxlSend(pBus, SRXL_CMD_BIND_INFO, pBus->fullID.deviceID);
pBus->state = SrxlState_Running;
break;
}
case SrxlState_Running:
default:
{
return;
}
}
}
#ifdef SRXL_INCLUDE_MASTER_CODE
else
{
srxlRunMaster(pBus);
}
#endif // SRXL_INCLUDE_MASTER_CODE
}
/**
@brief Tell the "best" receiver to enter bind mode, either locally or via SRXL command
@param bindType: One of the possible bind status types to use when binding -- NOTE: The transmitter may ignore this
@param broadcast: True if this is a local request that should tell all connected receivers to enter bind
@return bool: True if a receiver was told to enter bind mode; else false
*/
bool srxlEnterBind(uint8_t bindType, bool broadcast)
{
srxlRx.pBindRcvr = 0;
if(broadcast && srxlThisDev.pRcvr)
{
srxlRx.pBindRcvr = srxlThisDev.pRcvr;
}
else if(srxlRx.pTelemRcvr)
{
srxlRx.pBindRcvr = srxlRx.pTelemRcvr;
}
else if(srxlRx.rcvrCount > 0 && srxlRx.rcvrSorted[0]->deviceID < 0x30)
{
srxlRx.pBindRcvr = srxlRx.rcvrSorted[0];
}
if(srxlRx.pBindRcvr)
{
#ifdef SRXL_INCLUDE_MASTER_CODE
// Local bind
if(srxlRx.pBindRcvr == srxlThisDev.pRcvr)
{
srxlBindInfo.type = bindType;
srxlBindInfo.options = SRXL_BIND_OPT_BIND_TX_ENABLE;
srxlBindInfo.guid = 0;
srxlBindInfo.uid = 0;
if(srxlRx.pBindRcvr == srxlRx.pTelemRcvr)
srxlBindInfo.options |= SRXL_BIND_OPT_TELEM_TX_ENABLE;
srxlTryToBind(srxlBindInfo);
if(broadcast)
{
uint8_t b;
for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
{
srxlBus[b].txFlags.enterBind = 1;
}
}
return true;
}
#endif // SRXL_INCLUDE_MASTER_CODE
// Remote bind
uint8_t i;
for(i = 0; i < SRXL_NUM_OF_BUSES; ++i)
{
if((1u << i) & srxlRx.pBindRcvr->busBits)
{
srxlBus[i].txFlags.enterBind = 1;
return true;
}
}
}
return false;
}
/**
@brief Public function to set bind info for the system, either locally or via SRXL commands
@param bindType: Type of bind requested for this receiver or all receivers
@param guid: Transmitter GUID to bind the receiver to
@param uid: Unique ID provided by transmitter upon initial bind (can be 0 if unknown)
@return bool: True if bind info was successfully set for the destination device; else false
*/
bool srxlSetBindInfo(uint8_t bindType, uint64_t guid, uint32_t uid)
{
if(guid == 0)
return false;
// Set bind info, with options defaulted to 0
srxlBindInfo.type = bindType;
srxlBindInfo.options = SRXL_BIND_OPT_US_POWER; // Request US power, with no guarantee it's supported
srxlBindInfo.guid = guid;
srxlBindInfo.uid = uid ? uid : srxlThisDev.uid;
#ifdef SRXL_INCLUDE_MASTER_CODE
// If we are a receiver
if(srxlThisDev.pRcvr)
{
// Bind locally, which will result in no further packets since options == 0
srxlTryToBind(srxlBindInfo);
}
#endif
// Broadcast this bind info on all SRXL buses
uint8_t b;
for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
{
srxlBus[b].txFlags.broadcastBindInfo = 1;
}
return true;
}
/**
@brief Public function to call from the user UART code when a UART frame error occurs
@param busIndex: Index of SRXL bus state information entry in the srxlBus array
*/
void srxlOnFrameError(uint8_t busIndex)
{
SrxlBus* pBus = &srxlBus[busIndex];
if(busIndex >= SRXL_NUM_OF_BUSES || !pBus->initialized)
return;
++(pBus->frameErrCount);
if(pBus->master)
{
// TODO: If master (i.e. remote receiver 0x10), cause break condition to force reset?
return;
}
if(pBus->state == SrxlState_ListenOnStartup || pBus->state == SrxlState_ListenForHandshake)
{
// Wait for multiple frame breaks before trying to change baud rate
if(pBus->frameErrCount < 3)
return;
// Try the next higher baud rate
switch(pBus->baudRate)
{
case SRXL_BAUD_115200:
{
if(pBus->baudSupported & SRXL_BAUD_400000)
{
srxlChangeBaudRate(pBus->uart, 400000);
pBus->baudRate = SRXL_BAUD_400000;
break;
}
FALLTHROUGH;
// else fall thru...
}
case SRXL_BAUD_400000:
default:
{
// TODO: Cause break condition to force reset of everyone? Or just keep cycling?
srxlChangeBaudRate(pBus->uart, 115200);
pBus->baudRate = SRXL_BAUD_115200;
break;
}
}
pBus->frameErrCount = 0;
}
else
{
// TODO: Handle frame error during normal comm -- probably caused by collision on bus?
}
}
SrxlFullID srxlGetTelemetryEndpoint(void)
{
SrxlFullID retVal = {0};
if(srxlRx.pTelemRcvr)
{
retVal.deviceID = srxlRx.pTelemRcvr->deviceID;
retVal.busIndex = srxlRx.pTelemRcvr->busBits;
}
return retVal;
}
bool srxlSetVtxData(SrxlVtxData* pVtxData)
{
if(!pVtxData)
return false;
// Update VTX data, ignoring values marked as unchanged
if(pVtxData->band != 0xFF)
srxlVtxData.band = pVtxData->band;
if(pVtxData->channel != 0xFF)
srxlVtxData.channel = pVtxData->channel;
if(pVtxData->pit != 0xFF)
srxlVtxData.pit = pVtxData->pit;
if(pVtxData->power != 0xFF || pVtxData->powerDec != 0xFFFF)
srxlVtxData.power = pVtxData->power;
if(pVtxData->powerDec != 0xFFFF)
srxlVtxData.powerDec = pVtxData->powerDec;
if(pVtxData->region != 0xFF)
srxlVtxData.region = pVtxData->region;
uint8_t b;
for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
{
srxlBus[b].txFlags.sendVtxData = 1;
}
return true;
}
void srxlSetHoldThreshold(uint8_t countdownReset)
{
srxlRx.lossHoldCount = (countdownReset > 1) ? countdownReset : 45;
}
void srxlClearCommStats(void)
{
srxlRx.holds = 0;
srxlRx.frameLosses = 0;
srxlRx.lossCountdown = srxlRx.lossHoldCount + 1;
}
// Return true on failsafe hold
bool srxlUpdateCommStats(bool isFade)
{
srxlRx.rxBusBits = 0;
if(srxlTelemetryPhase)
{
srxlRx.bestRssi_dBm = -128;
srxlRx.bestRssi_Pct = 0;
}
uint8_t i;
for(i = 0; i < srxlRx.rcvrCount; ++i)
{
if(srxlRx.rcvr[i].channelMask)
{
srxlRx.lossCountdown = srxlRx.lossHoldCount + 1;
if((srxlRx.rcvr[i].rssiRcvd & RSSI_RCVD_DBM) && srxlRx.bestRssi_dBm < srxlRx.rcvr[i].rssi_dBm)
srxlRx.bestRssi_dBm = srxlRx.rcvr[i].rssi_dBm;
if((srxlRx.rcvr[i].rssiRcvd & RSSI_RCVD_PCT) && srxlRx.bestRssi_dBm < srxlRx.rcvr[i].rssi_Pct)
srxlRx.bestRssi_Pct = srxlRx.rcvr[i].rssi_Pct;
}
}
// Set RSSI based on telemetry phase and type of telemetry received
srxlChData.rssi = (srxlTelemetryPhase || srxlChDataIsFailsafe) ? srxlRx.bestRssi_Pct : srxlRx.bestRssi_dBm;
// Update flight log frame losses and holds
if(isFade && srxlRx.lossCountdown)
{
if(--srxlRx.lossCountdown == 0)
{
++srxlRx.holds;
srxlRx.frameLosses -= srxlRx.lossHoldCount;
}
else
{
++srxlRx.frameLosses;
}
}
static uint8_t telemFadeCount = 0;
// If we are allowed to send telemetry by the device downstream (i.e. any slave device)
if(srxlRx.pTelemRcvr)
{
if(srxlRx.pTelemRcvr->channelMask == 0 || (srxlRx.pTelemRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE) == 0)
{
// If our telemetry receiver missed channel data 3 frames in a row, switch
if(++telemFadeCount > 3)
{
srxlRx.pTelemRcvr = srxlChooseTelemRcvr();
#ifdef SRXL_INCLUDE_MASTER_CODE
srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr == srxlThisDev.pRcvr));
#endif
telemFadeCount = 0;
}
}
else
{
telemFadeCount = 0;
}
}
#ifdef SRXL_INCLUDE_MASTER_CODE
// Else check to make sure we're still supposed to suppress telemetry (reset countdown when slave tells us not to send again)
else if(++srxlTelemSuppressCount > SRXL_TELEM_SUPPRESS_MAX)
{
// Enable this device's telemetry tx since we stopped being told not to
srxlRx.pTelemRcvr = srxlThisDev.pRcvr;
srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr);
}
#endif
// Return true while we're in hold condition (failsafe)
return srxlRx.lossCountdown == 0;
}