LCOV - code coverage report
Current view: top level - AP_RCProtocol - AP_RCProtocol_FPort2.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 3 107 2.8 %
Date: 2026-09-09 11:02:08 Functions: 1 7 14.3 %

          Line data    Source code
       1             : /*
       2             :  * This file is free software: you can redistribute it and/or modify it
       3             :  * under the terms of the GNU General Public License as published by the
       4             :  * Free Software Foundation, either version 3 of the License, or
       5             :  * (at your option) any later version.
       6             :  *
       7             :  * This file is distributed in the hope that it will be useful, but
       8             :  * WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      10             :  * See the GNU General Public License for more details.
      11             :  *
      12             :  * You should have received a copy of the GNU General Public License along
      13             :  * with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   FRSky FPort2 implementation, with thanks to FrSky for
      17             :   specification
      18             :  */
      19             : 
      20             : #include "AP_RCProtocol_FPort2.h"
      21             : 
      22             : #if AP_RCPROTOCOL_FPORT2_ENABLED
      23             : 
      24             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      25             : #include <AP_Frsky_Telem/AP_Frsky_Telem.h>
      26             : #include <RC_Channel/RC_Channel.h>
      27             : #include <AP_Math/AP_Math.h>
      28             : #include <AP_Math/crc.h>
      29             : #include <stdio.h>
      30             : 
      31             : extern const AP_HAL::HAL& hal;
      32             : 
      33             : #define FRAME_LEN_16 0x18
      34             : #define FRAME_LEN_8  0x0D
      35             : #define FRAME_LEN_24 0x23
      36             : #define FRAME_LEN_DOWNLINK 0x08
      37             : 
      38             : #define FRAME_TYPE_CHANNEL 0xFF
      39             : #define FRAME_TYPE_FC 0x1B
      40             : 
      41             : #define MAX_CHANNELS 24
      42             : 
      43             : #define FLAGS_FAILSAFE_BIT      3
      44             : #define FLAGS_FRAMELOST_BIT     2
      45             : 
      46             : #define CHAN_SCALE_FACTOR1 1000U
      47             : #define CHAN_SCALE_FACTOR2 1600U
      48             : #define CHAN_SCALE_OFFSET 875U
      49             : 
      50             : #define FPORT2_PRIM_NULL 0x00
      51             : #define FPORT2_PRIM_READ 0x30
      52             : #define FPORT2_PRIM_WRITE 0x31
      53             : 
      54             : struct PACKED FPort2_Frame {
      55             :     uint8_t len;
      56             :     uint8_t type;
      57             :     union {
      58             :         uint8_t data[36];
      59             :         struct PACKED {
      60             :             uint8_t prim;
      61             :             uint16_t appid;
      62             :             uint8_t data[4];
      63             :             uint8_t crc;
      64             :         } downlink;
      65             :     };
      66             : };
      67             : 
      68             : static_assert(sizeof(FPort2_Frame) == FPORT2_CONTROL_FRAME_SIZE, "FPort2_Frame incorrect size");
      69             : 
      70             : // constructor
      71        2094 : AP_RCProtocol_FPort2::AP_RCProtocol_FPort2(AP_RCProtocol &_frontend, bool _inverted) :
      72             :     AP_RCProtocol_Backend(_frontend),
      73        2094 :     inverted(_inverted)
      74        2094 : {}
      75             : 
      76             : // decode a full FPort2 control frame
      77           0 : void AP_RCProtocol_FPort2::decode_control(const FPort2_Frame &frame)
      78             : {
      79             :     uint16_t values[MAX_CHANNELS];
      80             : 
      81           0 :     decode_11bit_channels(frame.data, chan_count, values, CHAN_SCALE_FACTOR1, CHAN_SCALE_FACTOR2, CHAN_SCALE_OFFSET);
      82             : 
      83           0 :     const uint8_t b_flags = frame.data[byte_input.control_len-5];
      84           0 :     const uint8_t b_rssi = frame.data[byte_input.control_len-4];
      85             : 
      86           0 :     bool failsafe = ((b_flags & (1 << FLAGS_FAILSAFE_BIT)) != 0);
      87             : 
      88             :     // fport2 rssi 0-50, ardupilot rssi 0-255, scale factor 255/50=5.1
      89           0 :     const uint8_t scaled_rssi = MIN(b_rssi * 5.1f, 255);
      90             : 
      91           0 :     add_input(chan_count, values, failsafe, scaled_rssi);
      92           0 : }
      93             : 
      94             : /*
      95             :   decode a full FPort2 downlink frame
      96             : */
      97           0 : void AP_RCProtocol_FPort2::decode_downlink(const FPort2_Frame &frame)
      98             : {
      99             : #if !APM_BUILD_TYPE(APM_BUILD_iofirmware)
     100             :     /*
     101             :       if we are getting FPORT2 over a UART then we can ask the FrSky
     102             :       telem library for some passthrough data to send back, enabling
     103             :       telemetry on the receiver via the same uart pin as we use for
     104             :       incoming RC frames
     105             :      */
     106             : 
     107           0 :     AP_HAL::UARTDriver *uart = get_UART();
     108           0 :     if (!uart) {
     109           0 :         return;
     110             :     }
     111             : 
     112             :     // no telemetry for 24ch fport2, timing is too tight
     113           0 :     if (chan_count > 16) {
     114           0 :         return;
     115             :     }
     116             : 
     117             : #if HAL_WITH_FRSKY_TELEM_BIDIRECTIONAL
     118             :     // 0x30,0x31 read/write frames
     119             :     // allowed reply prim: 0x00,0x32
     120           0 :     if (frame.downlink.prim == FPORT2_PRIM_READ || frame.downlink.prim == FPORT2_PRIM_WRITE) {
     121           0 :         AP_Frsky_Telem::set_telem_data(frame.downlink.prim, frame.downlink.appid, le32toh_ptr(frame.downlink.data));
     122             :     }
     123             : #endif //HAL_WITH_FRSKY_TELEM_BIDIRECTIONAL
     124             : 
     125             :     /*
     126             :      we're only interested in "flight controller" requests i.e. 0x1B
     127             :      Notes:
     128             :      need to check how to handle multiple sensor IDs (we probably should respond to 0x1E as well)
     129             :      with fport2 we need to account for the IDs we send sensor data from
     130             :      if we respond to multiple sensors like we do for sport we need to make sure GPS data
     131             :      is always sent with the same sensor ID or else OpenTX will see multiple sensor instances (one for each sensor we respond to)
     132             :      (need to double check on OpenTX if fport2 carries sensor IDs up to the OpenTX sensor tables)
     133             :     */
     134           0 :     if (frame.type != FRAME_TYPE_FC) {
     135             :         /*
     136             :          get SPort data from FRSky_Telem
     137             :          when we are not polled for data we prepare telemetry data for the next poll
     138             :          we save the data to a variable so in case we're late we'll
     139             :          send it in the next call, this prevents corruption of status text messages
     140             :         */
     141           0 :         if (!telem_data.available) {
     142             :             uint8_t packet_count;
     143           0 :             if (!AP_Frsky_Telem::get_telem_data(&telem_data.packet, packet_count, 1)) {
     144           0 :                 telem_data.packet.frame = 0x00;
     145           0 :                 telem_data.packet.appid = 0x00;
     146           0 :                 telem_data.packet.data = 0x00;
     147             :             }
     148           0 :             telem_data.available = true;
     149             :         }
     150           0 :         return;
     151             :     }
     152             : 
     153             :     /*
     154             :       check that we haven't been too slow in responding to the new
     155             :       UART data. If we respond too late then we will corrupt the next
     156             :       incoming control frame.
     157             :       16ch fport2: 4.5ms window before next control frame
     158             :       specs require to release the bus at least 1.5ms before next control frame (uplink frame takes 0.851ms)
     159             :      */
     160           0 :     uint64_t tend = uart->receive_time_constraint_us(1);
     161           0 :     uint64_t now = AP_HAL::micros64();
     162           0 :     uint64_t tdelay = now - tend;
     163           0 :     if (tdelay > 2500) {
     164             :         // we've been too slow in responding
     165           0 :         return;
     166             :     }
     167             : 
     168             :     // we initialize to a default null frame
     169           0 :     uint8_t buf[10] {};
     170             : 
     171           0 :     buf[0] = 0x08;
     172           0 :     buf[1] = frame.type;
     173             :     // do not consume telemetry data for invalid downlink frames, i.e. incoming prim == 0x00
     174           0 :     if (frame.downlink.prim != FPORT2_PRIM_NULL) {
     175           0 :         buf[2] = telem_data.packet.frame;
     176           0 :         buf[3] = telem_data.packet.appid & 0xFF;
     177           0 :         buf[4] = telem_data.packet.appid >> 8;
     178           0 :         memcpy(&buf[5], &telem_data.packet.data, 4);
     179             :         // get fresh telem_data in the next call
     180           0 :         telem_data.available = false;
     181             :     }
     182           0 :     buf[9] = crc_sum8_with_carry(&buf[1], 8);
     183             :     
     184           0 :     uart->write(buf, sizeof(buf));
     185             : #endif
     186             : }
     187             : 
     188             : /*
     189             :   process a FPort2 input pulse of the given width
     190             :  */
     191           0 : void AP_RCProtocol_FPort2::process_pulse(uint32_t width_s0, uint32_t width_s1)
     192             : {
     193           0 :     if (have_UART()) {
     194             :         // if we can use a UART we would much prefer to, as it allows
     195             :         // us to send SPORT data out
     196           0 :         return;
     197             :     }
     198           0 :     uint32_t w0 = width_s0;
     199           0 :     uint32_t w1 = width_s1;
     200           0 :     if (inverted) {
     201           0 :         w0 = saved_width;
     202           0 :         w1 = width_s0;
     203           0 :         saved_width = width_s1;
     204             :     }
     205             :     uint8_t b;
     206           0 :     if (ss.process_pulse(w0, w1, b)) {
     207           0 :         _process_byte(ss.get_byte_timestamp_us(), b);
     208             :     }
     209             : }
     210             : 
     211             : // support byte input
     212           0 : void AP_RCProtocol_FPort2::_process_byte(uint32_t timestamp_us, uint8_t b)
     213             : {
     214           0 :     const bool have_frame_gap = (timestamp_us - byte_input.last_byte_us >= 2000U);
     215             : 
     216           0 :     byte_input.last_byte_us = timestamp_us;
     217             : 
     218           0 :     if (have_frame_gap) {
     219             :         // if we have a frame gap then this must be the start of a new
     220             :         // frame
     221           0 :         byte_input.ofs = 0;
     222             :     }
     223             : 
     224           0 :     if (byte_input.ofs == 0) {
     225           0 :         switch (b) {
     226           0 :         case FRAME_LEN_8:
     227           0 :             byte_input.control_len = 16;
     228           0 :             chan_count = 8;
     229           0 :             byte_input.is_downlink = false;
     230           0 :             break;
     231           0 :         case FRAME_LEN_16:
     232           0 :             byte_input.control_len = 27;
     233           0 :             chan_count = 16;
     234           0 :             byte_input.is_downlink = false;
     235           0 :             break;
     236           0 :         case FRAME_LEN_24:
     237           0 :             byte_input.control_len = 38;
     238           0 :             chan_count = 24;
     239           0 :             byte_input.is_downlink = false;
     240           0 :             break;
     241           0 :         case FRAME_LEN_DOWNLINK:
     242           0 :             byte_input.control_len = 10;
     243           0 :             byte_input.is_downlink = true;
     244           0 :             break;
     245           0 :         default:
     246             :             // definately not FPort2, missing header byte
     247           0 :             return;
     248             :         }
     249             :     }
     250             : 
     251           0 :     if (byte_input.ofs == 1) {
     252           0 :         if (!byte_input.is_downlink && b != FRAME_TYPE_CHANNEL) {
     253             :             // not channel data
     254           0 :             byte_input.ofs = 0;
     255           0 :             return;
     256             :         }
     257             :     }
     258             : 
     259           0 :     byte_input.buf[byte_input.ofs++] = b;
     260             : 
     261           0 :     const FPort2_Frame *frame = (const FPort2_Frame *)&byte_input.buf[0];
     262             : 
     263           0 :     if (byte_input.control_len > 2 && byte_input.ofs == byte_input.control_len) {
     264           0 :         if (!byte_input.is_downlink) {
     265           0 :             log_data(AP_RCProtocol::FPORT2, timestamp_us, byte_input.buf, byte_input.ofs);
     266           0 :             if (check_checksum()) {
     267           0 :                 decode_control(*frame);
     268             :             }
     269             :         } else {
     270             :             // downlink packet
     271           0 :             if (check_checksum()) {
     272           0 :                 decode_downlink(*frame);
     273             :             }
     274             :         }
     275           0 :         goto reset;
     276             :     }
     277           0 :     if (byte_input.ofs >= sizeof(byte_input.buf)) {
     278           0 :         goto reset;
     279             :     }
     280           0 :     return;
     281             : 
     282           0 : reset:
     283           0 :     byte_input.ofs = 0;
     284             : }
     285             : 
     286             : // check checksum byte
     287           0 : bool AP_RCProtocol_FPort2::check_checksum(void)
     288             : {
     289           0 :     return crc_sum8_with_carry(&byte_input.buf[1], byte_input.control_len-1) == 0;
     290             : }
     291             : 
     292             : // support byte input
     293           0 : void AP_RCProtocol_FPort2::process_byte(uint8_t b, uint32_t baudrate)
     294             : {
     295           0 :     if (baudrate != 115200) {
     296           0 :         return;
     297             :     }
     298           0 :     _process_byte(AP_HAL::micros(), b);
     299             : }
     300             : 
     301             : #endif  // AP_RCPROTOCOL_FPORT2_ENABLED

Generated by: LCOV version 1.14