LCOV - code coverage report
Current view: top level - AP_RCProtocol - AP_RCProtocol_SRXL2.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 14 166 8.4 %
Date: 2026-09-09 11:02:08 Functions: 5 18 27.8 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   SRXL2 protocol decoder using Horizon Hobby's open source library https://github.com/SpektrumRC/SRXL2
      17             :   Code by Andy Piper
      18             :  */
      19             : 
      20             : #include "AP_RCProtocol_config.h"
      21             : 
      22             : #if AP_RCPROTOCOL_SRXL2_ENABLED
      23             : 
      24             : #include "AP_RCProtocol.h"
      25             : #include "AP_RCProtocol_SRXL2.h"
      26             : #include <AP_Math/AP_Math.h>
      27             : #include <AP_RCTelemetry/AP_Spektrum_Telem.h>
      28             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      29             : #include <AP_HAL/utility/sparse-endian.h>
      30             : #include <AP_VideoTX/AP_VideoTX.h>
      31             : 
      32             : #include "spm_srxl.h"
      33             : 
      34             : extern const AP_HAL::HAL& hal;
      35             : //#define SRXL2_DEBUG
      36             : #ifdef SRXL2_DEBUG
      37             : # define debug(fmt, args...)    hal.console->printf("SRXL2:" fmt "\n", ##args)
      38             : #else
      39             : # define debug(fmt, args...)    do {} while(0)
      40             : #endif
      41             : 
      42             : AP_RCProtocol_SRXL2* AP_RCProtocol_SRXL2::_singleton;
      43             : 
      44        2094 : AP_RCProtocol_SRXL2::AP_RCProtocol_SRXL2(AP_RCProtocol &_frontend) : AP_RCProtocol_Backend(_frontend)
      45             : {
      46             : #if !APM_BUILD_TYPE(APM_BUILD_UNKNOWN)
      47        2094 :     if (_singleton != nullptr) {
      48           0 :         AP_HAL::panic("Duplicate SRXL2 handler");
      49             :     }
      50             : 
      51        2094 :     _singleton = this;
      52             : #else
      53           0 :     if (_singleton == nullptr) {
      54           0 :         _singleton = this;
      55             :     }
      56             : #endif
      57        2094 : }
      58             : 
      59           0 : void AP_RCProtocol_SRXL2::_bootstrap(uint8_t device_id)
      60             : {
      61           0 :     if (_device_id == device_id) {
      62           0 :         return;
      63             :     }
      64             : 
      65             :     // Init the local SRXL device
      66           0 :     if (!srxlInitDevice(device_id, SRXL_DEVICE_PRIORITY, SRXL_DEVICE_INFO, device_id)) {
      67           0 :         AP_HAL::panic("Failed to initialize SRXL2 device");
      68             :     }
      69             : 
      70             :     // Init the SRXL bus: The bus index must always be < SRXL_NUM_OF_BUSES -- in this case, it can only be 0
      71           0 :     if (!srxlInitBus(0, 0, SRXL_SUPPORTED_BAUD_RATES)) {
      72           0 :         AP_HAL::panic("Failed to initialize SRXL2 bus");
      73             :     }
      74             : 
      75           0 :     _device_id = device_id;
      76             : 
      77             :     debug("Bootstrapped as 0x%x", _device_id);
      78             : }
      79             : 
      80         908 : AP_RCProtocol_SRXL2::~AP_RCProtocol_SRXL2() {
      81         454 :     _singleton = nullptr;
      82         908 : }
      83             : 
      84           0 : void AP_RCProtocol_SRXL2::_process_byte(uint32_t timestamp_us, uint8_t byte)
      85             : {
      86           0 :     if (_decode_state == STATE_IDLE) {
      87           0 :         switch (byte) {
      88           0 :         case SPEKTRUM_SRXL_ID:
      89           0 :             _decode_state = STATE_NEW;
      90           0 :             break;
      91           0 :         default:
      92           0 :             _decode_state = STATE_IDLE;
      93           0 :             return;
      94             :         }
      95           0 :         _frame_len_full = 0U;
      96           0 :         _buflen = 0;
      97           0 :         _decode_state_next = STATE_IDLE;
      98             :     }
      99             : 
     100           0 :     switch (_decode_state) {
     101           0 :     case STATE_NEW:  // buffer header byte and prepare for frame reception and decoding
     102           0 :         _buffer[0U]=byte;
     103           0 :         _buflen = 1U;
     104           0 :         _decode_state_next = STATE_COLLECT;
     105           0 :         break;
     106             : 
     107           0 :     case STATE_COLLECT: // receive all bytes. After reception decode frame and provide rc channel information to FMU
     108           0 :         _buffer[_buflen] = byte;
     109           0 :         _buflen++;
     110             : 
     111             :         // need a header to get the length
     112           0 :         if (_buflen < SRXL2_HEADER_LEN) {
     113           0 :             return;
     114             :         }
     115             : 
     116             :         // parse the length
     117           0 :         if (_buflen == SRXL2_HEADER_LEN) {
     118           0 :             _frame_len_full = _buffer[2];
     119             :             // check for garbage frame
     120           0 :             if (_frame_len_full > SRXL2_FRAMELEN_MAX) {
     121           0 :                 _decode_state = STATE_IDLE;
     122           0 :                 _buflen = 0;
     123           0 :                 _frame_len_full = 0;
     124             :             }
     125           0 :             return;
     126             :         }
     127             : 
     128           0 :         if (_buflen > _frame_len_full) {
     129             :             // a logic bug in the state machine, this shouldn't happen
     130           0 :             _decode_state = STATE_IDLE;
     131           0 :             _buflen = 0;
     132           0 :             _frame_len_full = 0;
     133           0 :             return;
     134             :         }
     135             : 
     136           0 :         if (_buflen == _frame_len_full) {
     137           0 :             log_data(AP_RCProtocol::SRXL2, timestamp_us, _buffer, _buflen);
     138             :             // we got a full frame but never handshaked before
     139           0 :             if (!is_bootstrapped()) {
     140           0 :                 if (_buffer[1] == 0x21) {
     141           0 :                     _bootstrap(SRXL_DEVICE_ID);
     142           0 :                     _last_handshake_ms = timestamp_us / 1000;
     143             :                 } else {
     144             :                     // not a handshake frame so reset without initializing the SRXL2 engine
     145           0 :                     _decode_state = STATE_IDLE;
     146           0 :                     _buflen = 0;
     147           0 :                     _frame_len_full = 0;
     148           0 :                     return;
     149             :                 }
     150             :             }
     151             :             // Try to parse SRXL packet -- this internally calls srxlRun() after packet is parsed and resets timeout
     152           0 :             if (srxlParsePacket(0, _buffer, _frame_len_full)) {
     153           0 :                 add_input(ARRAY_SIZE(_channels), _channels, _in_failsafe, _new_rssi);
     154             :             }
     155           0 :             _last_run_ms = AP_HAL::millis();
     156             : 
     157           0 :             _decode_state_next = STATE_IDLE;
     158           0 :             _buflen = 0;
     159             :         } else {
     160           0 :             _decode_state_next = STATE_COLLECT;
     161             :         }
     162           0 :         break;
     163             : 
     164           0 :     default:
     165           0 :         break;
     166             :     }
     167             : 
     168           0 :     _decode_state = _decode_state_next;
     169             : }
     170             : 
     171    36177494 : void AP_RCProtocol_SRXL2::update(void)
     172             : {
     173             :     // on a SPM4650 with telemetry the frame rate is 91Hz equating to around 10ms per frame
     174             :     // however only half of them can return telemetry, so the maximum telemetry rate is 46Hz
     175             :     // also update() is run immediately after check_added_uart() and so in general the delay is < 5ms
     176             :     // to be safe we will only run if the timeout exceeds 50ms
     177    36177494 :     if (_last_run_ms > 0) {
     178           0 :         uint32_t now = AP_HAL::millis();
     179             :         // there have been no updates since we were last called
     180           0 :         const uint32_t delay = now - _last_run_ms;
     181           0 :         if (delay > 50) {
     182           0 :             srxlRun(0, delay);
     183           0 :             _last_run_ms = now;
     184             :         }
     185             :     }
     186    36177494 : }
     187             : 
     188             : // capture SRXL2 encoded values
     189           0 : void AP_RCProtocol_SRXL2::capture_scaled_input(const uint8_t *values_p, bool in_failsafe, int16_t new_rssi)
     190             : {
     191           0 :     _in_failsafe = in_failsafe;
     192             :     // AP rssi: -1 for unknown, 0 for no link, 255 for maximum link
     193             :     // SRXL2 rssi: -ve rssi in dBM, +ve rssi in percentage
     194           0 :     if (new_rssi >= 0) {
     195           0 :         _new_rssi = new_rssi * 255 / 100;
     196             :     }
     197             : 
     198           0 :     for (uint8_t i = 0; i < ARRAY_SIZE(_channels); i++) {
     199             :         /*
     200             :          * Store the decoded channel into the R/C input buffer, taking into
     201             :          * account the different ideas about channel assignement that we have.
     202             :          *
     203             :          * Specifically, the first four channels in rc_channel_data are roll, pitch, thrust, yaw,
     204             :          * but the first four channels from the DSM receiver are thrust, roll, pitch, yaw.
     205             :          */
     206           0 :         uint8_t channel = i;
     207           0 :         switch (channel) {
     208           0 :         case 0:
     209           0 :             channel = 2;
     210           0 :             break;
     211             : 
     212           0 :         case 1:
     213           0 :             channel = 0;
     214           0 :             break;
     215             : 
     216           0 :         case 2:
     217           0 :             channel = 1;
     218           0 :             break;
     219             : 
     220           0 :         default:
     221           0 :             break;
     222             :         }
     223             :         /*
     224             :          * Each channel data value is sent as an unsigned 16-bit value from 0 to 65532 (0xFFFC)
     225             :          * with 32768 (0x8000) representing "Servo Center". The channel value must be bit-shifted
     226             :          * to the right to match the applications's accepted resolution.
     227             :          *
     228             :          * So here we scale to DSMX-2048 and then use our regular Spektrum conversion.
     229             :          */
     230           0 :         const uint16_t v = le16toh_ptr(&values_p[i*2]);
     231           0 :         _channels[channel] = ((int32_t)(v >> 5) * 1194) / 2048 + 903;
     232             :     }
     233           0 : }
     234             : 
     235             : 
     236             : // start bind on DSM satellites
     237           0 : void AP_RCProtocol_SRXL2::start_bind(void)
     238             : {
     239           0 :     srxlEnterBind(DSMX_11MS, true);
     240           0 : }
     241             : 
     242             : // process a byte provided by a uart
     243           0 : void AP_RCProtocol_SRXL2::process_byte(uint8_t byte, uint32_t baudrate)
     244             : {
     245           0 :     if (baudrate != 115200) {
     246           0 :         return;
     247             :     }
     248             : 
     249           0 :     _process_byte(AP_HAL::micros(), byte);
     250             : }
     251             : 
     252             : // handshake
     253           4 : void AP_RCProtocol_SRXL2::process_handshake(uint32_t baudrate)
     254             : {
     255             :     // only bootstrap if only SRXL2 is enabled
     256           4 :     if (baudrate != 115200 || (get_rc_protocols_mask() & ~(1U<<(uint8_t(AP_RCProtocol::SRXL2)+1)))) {
     257           4 :         _handshake_start_ms = 0;
     258           4 :         return;
     259             :     }
     260           0 :     uint32_t now = AP_HAL::millis();
     261             : 
     262             :     // record the time of the first request in this cycle
     263           0 :     if (_handshake_start_ms == 0) {
     264           0 :         _handshake_start_ms = now;
     265             :         // it seems the handshake protocol only sets the baudrate after receiving data
     266             :         // since we are sending data unprompted make sure that the uart is set up correctly
     267           0 :         change_baud_rate(baudrate);
     268             :     }
     269             : 
     270             :     // we have not bootstrapped and attempts to listen first have failed
     271             :     // we should receive a handshake request within the first 250ms
     272           0 :     if (!is_bootstrapped() && now - _handshake_start_ms > 250) {
     273           0 :         _bootstrap(SRXL_DEVICE_ID_BASE_RX);
     274             :     }
     275             : 
     276             :     // certain RXs (e.g. AR620) are "listen-only" - they require the flight controller to initiate
     277             :     // a handshake in order to switch to SRXL2 mode. This requires that we send data on the UART even
     278             :     // if we have not decoded SRXL2 (recently). We try this every 50ms.
     279           0 :     if (now - _handshake_start_ms > 250 && (_last_handshake_ms == 0 || (now - _last_run_ms > 50 && now - _last_handshake_ms > 50))) {
     280           0 :         _in_bootstrap_or_failsafe = true;
     281           0 :         srxlRun(0, 50); // 50 is a magic number at which the handshake protocol is initiated
     282           0 :         _in_bootstrap_or_failsafe = false;
     283           0 :         _last_handshake_ms = now;
     284             :     }
     285             : }
     286             : 
     287             : // send data to the uart
     288           0 : void AP_RCProtocol_SRXL2::send_on_uart(uint8_t* pBuffer, uint8_t length)
     289             : {
     290           0 :     AP_HAL::UARTDriver* uart = get_available_UART();
     291             : 
     292           0 :     if (uart != nullptr && uart->is_initialized()) {
     293             :         // check that we haven't been too slow in responding to the new UART data. If we respond too late then we will
     294             :         // corrupt the next incoming control frame. incoming packets at max 800bits @91Hz @115k baud gives total budget of 11ms 
     295             :         // per packet of which we need 7ms to receive a packet. outgoing packets are 220 bits which require 2ms to send
     296             :         // leaving at most 2ms of delay that can be tolerated
     297           0 :         uint64_t tend = uart->receive_time_constraint_us(1);
     298           0 :         uint64_t now = AP_HAL::micros64();
     299           0 :         uint64_t tdelay = now - tend;
     300           0 :         if (tdelay > 2000 && !_in_bootstrap_or_failsafe) {
     301             :             // we've been too slow in responding
     302           0 :             return;
     303             :         }
     304             :         // debug telemetry packets
     305           0 :         if (pBuffer[1] == 0x80 && pBuffer[4] != 0) {
     306             :             debug("0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x: %s",
     307             :                 pBuffer[0], pBuffer[1], pBuffer[2], pBuffer[3], pBuffer[4], pBuffer[5], pBuffer[6], pBuffer[7], pBuffer[8], pBuffer[9], &pBuffer[7]);
     308             :         }
     309           0 :         uart->write(pBuffer, length);
     310             :     }
     311             : }
     312             : 
     313             : // change the uart baud rate
     314           0 : void AP_RCProtocol_SRXL2::change_baud_rate(uint32_t baudrate)
     315             : {
     316           0 :     AP_HAL::UARTDriver* uart = get_available_UART();
     317           0 :     if (uart != nullptr) {
     318           0 :         uart->begin(baudrate);
     319           0 :         uart->set_flow_control(AP_HAL::UARTDriver::FLOW_CONTROL_DISABLE);
     320           0 :         uart->set_unbuffered_writes(true);
     321             :     }
     322           0 : }
     323             : 
     324             : // SRXL2 library callbacks below
     325             : 
     326             : // User-provided routine to change the baud rate settings on the given UART:
     327             : // uart - the same uint8_t value as the uart parameter passed to srxlInit()
     328             : // baudRate - the actual baud rate (currently either 115200 or 400000)
     329           0 : void srxlChangeBaudRate(uint8_t uart, uint32_t baudRate)
     330             : {
     331           0 :     AP_RCProtocol_SRXL2* srxl2 = AP_RCProtocol_SRXL2::get_singleton();
     332             : 
     333           0 :     if (srxl2 != nullptr) {
     334           0 :         srxl2->change_baud_rate(baudRate);
     335             :     }
     336           0 : }
     337             : 
     338             : // User-provided routine to actually transmit a packet on the given UART:
     339             : // uart - the same uint8_t value as the uart parameter passed to srxlInit()
     340             : // pBuffer - a pointer to an array of uint8_t values to send over the UART
     341             : // length - the number of bytes contained in pBuffer that should be sent
     342           0 : void srxlSendOnUart(uint8_t uart, uint8_t* pBuffer, uint8_t length)
     343             : {
     344           0 :     AP_RCProtocol_SRXL2* srxl2 = AP_RCProtocol_SRXL2::get_singleton();
     345             : 
     346           0 :     if (srxl2 != nullptr) {
     347           0 :         srxl2->send_on_uart(pBuffer, length);
     348             :     }
     349           0 : }
     350             : 
     351             : // User-provided callback routine to fill in the telemetry data to send to the master when requested:
     352             : // pTelemetryData - a pointer to the 16-byte SrxlTelemetryData transmit buffer to populate
     353             : // NOTE: srxlTelemData is available as a global variable, so the memcpy line commented out below
     354             : // could be used if you would prefer to just populate that with the next outgoing telemetry packet.
     355           0 : void srxlFillTelemetry(SrxlTelemetryData* pTelemetryData)
     356             : {
     357             : #if HAL_SPEKTRUM_TELEM_ENABLED && !APM_BUILD_TYPE(APM_BUILD_iofirmware)
     358           0 :     AP_Spektrum_Telem::get_telem_data(pTelemetryData->raw);
     359             : #endif
     360           0 : }
     361             : 
     362             : // User-provided callback routine that is called whenever a control data packet is received:
     363             : // pChannelData - a pointer to the received SrxlChannelData structure for manual parsing
     364             : // isFailsafe - true if channel data is set to failsafe values, else false.
     365             : // this is called from within srxlParsePacket() and before the SRXL2 state machine has been run
     366             : // so be very careful to only do local operations
     367           0 : void srxlReceivedChannelData(SrxlChannelData* pChannelData, bool isFailsafe)
     368             : {
     369           0 :     AP_RCProtocol_SRXL2* srxl2 = AP_RCProtocol_SRXL2::get_singleton();
     370             : 
     371           0 :     if (srxl2 == nullptr) {
     372           0 :         return;
     373             :     }
     374             : 
     375           0 :     if (isFailsafe) {
     376           0 :         srxl2->capture_scaled_input((const uint8_t *)pChannelData->values, true, pChannelData->rssi);
     377             :     } else {
     378           0 :         srxl2->capture_scaled_input((const uint8_t *)srxlChData.values, false, srxlChData.rssi);
     379             :     }
     380             : }
     381             : 
     382             : // User-provided callback routine to handle reception of a bound data report (either requested or unprompted).
     383             : // Return true if you want this bind information set automatically for all other receivers on all SRXL buses.
     384           0 : bool srxlOnBind(SrxlFullID device, SrxlBindData info)
     385             : {
     386           0 :     return true;
     387             : }
     388             : 
     389             : // User-provided callback routine to handle reception of a VTX control packet.
     390           0 : void srxlOnVtx(SrxlVtxData* pVtxData)
     391             : {
     392             : #if AP_VIDEOTX_ENABLED
     393           0 :     AP_RCProtocol_Backend::configure_vtx(pVtxData->band, pVtxData->channel, pVtxData->power, pVtxData->pit);
     394             : #endif
     395           0 : }
     396             : 
     397             : #endif  // AP_RCPROTOCOL_SRXL2_ENABLED

Generated by: LCOV version 1.14