2016-10-14 02:07:52 -03:00
/*
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
/*
SRXL protocol decoder , tested against AR7700 SRXL port
Andrew Tridgell , September 2016
2016-11-24 07:27:42 -04:00
Co author : Roman Kirchner , September 2016
- 2016.10 .23 : SRXL variant V1 sucessfully ( Testbench and Pixhawk / MissionPlanner ) tested with RX - 9 - DR M - LINK ( SW v1 .26 )
2016-10-14 02:07:52 -03:00
*/
# include "srxl.h"
2019-09-10 21:11:49 -03:00
# include <AP_Math/crc.h>
2016-10-14 02:07:52 -03:00
2019-09-10 21:11:49 -03:00
# include <stdio.h>
# include <stdint.h>
# include <string.h>
2016-10-14 02:07:52 -03:00
2016-11-24 07:27:42 -04:00
/* SRXL datastream characteristics for all variants */
# define SRXL_MIN_FRAMESPACE_US 8000U /* Minumum space between srxl frames in us (applies to all variants) */
# define SRXL_MAX_CHANNELS 20U /* Maximum number of channels from srxl datastream */
/* decode progress states */
# define STATE_IDLE 0x00U /* do nothing */
# define STATE_NEW 0x01U /* get header of frame + prepare for frame reception + begin new crc cycle */
# define STATE_COLLECT 0x02U /* collect RC channel data from frame + concurrently calc crc over payload data + extract channel information */
/* Variant specific SRXL datastream characteristics */
/* Framelength in byte */
# define SRXL_FRAMELEN_V1 27U /* Framelength with header in byte for: Mpx SRXLv1 or XBUS Mode B */
# define SRXL_FRAMELEN_V2 35U /* Framelength with header in byte for: Mpx SRXLv2 */
# define SRXL_FRAMELEN_V5 18U /* Framelength with header in byte for Spk AR7700 etc. */
# define SRXL_FRAMELEN_MAX 35U /* maximum possible framelengh */
/* Headerbyte */
# define SRXL_HEADER_V1 0xA1U /* Headerbyte for: Mpx SRXLv1 or XBUS Mode B */
# define SRXL_HEADER_V2 0xA2U /* Headerbyte for: Mpx SRXLv2 */
# define SRXL_HEADER_V5 0xA5U /* Headerbyte for: Spk AR7700 etc. */
# define SRXL_HEADER_NOT_IMPL 0xFFU /* Headerbyte for non impemented srxl header*/
static uint8_t buffer [ SRXL_FRAMELEN_MAX ] ; /* buffer for raw srxl frame data in correct order --> buffer[0]=byte0 buffer[1]=byte1 */
static uint8_t buflen ; /* length in number of bytes of received srxl dataframe in buffer */
static uint64_t last_data_us ; /* timespan since last received data in us */
static uint16_t channels [ SRXL_MAX_CHANNELS ] ; /* buffer for extracted RC channel data as pulsewidth in microseconds */
static uint8_t frame_header = 0U ; /* Frame header from SRXL datastream */
static uint8_t frame_len_full = 0U ; /* Length in number of bytes of full srxl datastream */
static uint8_t decode_state = STATE_IDLE ; /* Current state of SRXL frame decoding */
static uint8_t decode_state_next = STATE_IDLE ; /* State of frame decoding thatwill be applied when the next byte from dataframe drops in */
static uint16_t crc_fmu = 0U ; /* CRC calculated over payload from srxl datastream on this machine */
static uint16_t crc_receiver = 0U ; /* CRC extracted from srxl datastream */
2016-10-14 02:07:52 -03:00
static uint16_t max_channels ;
2016-11-24 07:27:42 -04:00
/**
* Get RC channel information as microsecond pulsewidth representation from srxl version 1 and 2
*
* This function extracts RC channel information from srxl dataframe . The function expects the whole dataframe
* in correct order in static array " buffer[SRXL_FRAMELEN_MAX] " . After extracting all RC channel information , the data
* is transferred to " values " array from parameter list . If the pixhawk does not support all channels from srxl datastream ,
* only supported number of channels will be refreshed .
*
* IMPORTANT SAFETY NOTICE : This function shall only be used after CRC has been successful .
*
* Structure of SRXL v1 dataframe - - > 12 channels , 12 Bit per channel
* Byte0 : Header 0xA1
* Byte1 : Bits7 - 4 : Empty Bits3 - 0 : Channel1 MSB
* Byte2 : Bits7 - 0 : Channel1 LSB
* ( . . . . )
* Byte23 : Bits7 - 4 : Empty Bits3 - 0 : Channel12 MSB
* Byte24 : Bits7 - 0 : Channel12 LSB
* Byte25 : CRC16 MSB
* Byte26 : CRC16 LSB
*
* Structure of SRXL v2 dataframe - - > 16 channels , 12 Bit per channel
* Byte0 : Header 0xA2
* Byte1 : Bits7 - 4 : Empty Bits3 - 0 : Channel1 MSB
* Byte2 : Bits7 - 0 : Channel1 LSB
* ( . . . . )
* Byte31 : Bits7 - 4 : Empty Bits3 - 0 : Channel16 MSB
* Byte32 : Bits7 - 0 : Channel16 LSB
* Byte33 : CRC16 MSB
* Byte34 : CRC16 LSB
*
* @ param [ in ] max_values - maximum number of values supported by the pixhawk
* @ param [ out ] num_values - number of RC channels extracted from srxl frame
* @ param [ out ] values - array of RC channels with refreshed information as pulsewidth in microseconds Range : 800u s - 2200u s
* @ param [ out ] failsafe_state - true : RC - receiver is in failsafe state , false : RC - receiver is not in failsafe state
2017-01-08 22:10:37 -04:00
* @ retval 0 success
2016-10-14 02:07:52 -03:00
*/
2016-11-24 07:27:42 -04:00
static int srxl_channels_get_v1v2 ( uint16_t max_values , uint8_t * num_values , uint16_t * values , bool * failsafe_state )
2016-10-14 02:07:52 -03:00
{
2016-11-24 07:27:42 -04:00
uint8_t loop ;
uint32_t channel_raw_value ;
2016-10-14 02:07:52 -03:00
2016-11-24 07:27:42 -04:00
* num_values = ( uint8_t ) ( ( frame_len_full - 3U ) / 2U ) ;
* failsafe_state = 0U ; /* this protocol version does not support failsafe information */
2016-10-14 02:07:52 -03:00
2016-11-24 07:27:42 -04:00
/* get data channel data from frame */
for ( loop = 0U ; loop < * num_values ; loop + + ) {
channel_raw_value = ( ( ( ( uint32_t ) buffer [ loop * 2U + 1U ] ) & 0x0000000FU ) < < 8U ) | ( ( uint32_t ) ( buffer [ loop * 2U + 2U ] ) ) ; /* get 12bit channel raw value from srxl datastream (mask out unused bits with 0x0000000F) */
channels [ loop ] = ( uint16_t ) ( ( ( channel_raw_value * ( uint32_t ) 1400U ) > > 12U ) + ( uint32_t ) 800U ) ; /* convert raw value to servo/esc signal pulsewidth in us */
2016-10-14 02:07:52 -03:00
}
2016-11-24 07:27:42 -04:00
/* provide channel data to FMU */
if ( ( uint16_t ) * num_values > max_values ) {
* num_values = ( uint8_t ) max_values ;
2016-10-14 02:07:52 -03:00
}
2016-11-24 07:27:42 -04:00
memcpy ( values , channels , ( * num_values ) * 2 ) ;
2016-10-14 02:07:52 -03:00
2016-11-24 07:27:42 -04:00
return 0 ; /* for srxl protocol version 1 and 2 it is not expected, that any error happen during decode process */
2016-10-14 02:07:52 -03:00
}
2016-11-24 07:27:42 -04:00
/**
* Get RC channel information as microsecond pulsewidth representation from srxl version 5
*
* This function extracts RC channel information from srxl dataframe . The function expects the whole dataframe
* in correct order in static array " buffer[SRXL_FRAMELEN_MAX] " . After extracting all RC channel information , the data
* is transferred to " values " array from parameter list . If the pixhawk does not support all channels from srxl datastream ,
* only supported number of channels will be refreshed .
*
* IMPORTANT SAFETY NOTICE : This function shall only be used after CRC has been successful .
*
* Structure of SRXL v5 dataframe
* Byte0 : Header 0xA5
* Byte1 - Byte16 : Payload
* Byte17 : CRC16 MSB
* Byte18 : CRC16 LSB
*
* @ param [ in ] max_values - maximum number of values supported by the pixhawk
* @ param [ out ] num_values - number of RC channels extracted from srxl frame
* @ param [ out ] values - array of RC channels with refreshed information as pulsewidth in microseconds Range : 800u s - 2200u s
* @ param [ out ] failsafe_state - true : RC - receiver is in failsafe state , false : RC - receiver is not in failsafe state
2017-01-08 22:10:37 -04:00
* @ retval 0 success
2016-11-24 07:27:42 -04:00
*/
static int srxl_channels_get_v5 ( uint16_t max_values , uint8_t * num_values , uint16_t * values , bool * failsafe_state )
{
2016-10-14 02:07:52 -03:00
// up to 7 channel values per packet. Each channel value is 16
// bits, with 11 bits of data and 4 bits of channel number. The
// top bit indicates a special X-Plus channel
for ( uint8_t i = 0 ; i < 7 ; i + + ) {
uint16_t b = buffer [ i * 2 + 2 ] < < 8 | buffer [ i * 2 + 3 ] ;
uint16_t c = b > > 11 ; // channel number
int32_t v = b & 0x7FF ;
2016-10-15 02:26:13 -03:00
if ( b & 0x8000 ) {
2016-11-24 07:27:42 -04:00
continue ;
2016-10-15 02:26:13 -03:00
}
if ( c = = 12 ) {
2016-11-24 07:27:42 -04:00
// special handling for channel 12
2016-10-15 02:26:13 -03:00
// see http://www.deviationtx.com/forum/protocol-development/2088-18-channels-for-dsm2-dsmx?start=40
//printf("c12: 0x%x %02x %02x\n", (unsigned)(b>>9), (unsigned)buffer[0], (unsigned)buffer[1]);
2016-10-16 04:51:24 -03:00
v = ( b & 0x1FF ) < < 2 ;
c = 10 + ( ( b > > 9 ) & 0x7 ) ;
if ( buffer [ 1 ] & 1 ) {
c + = 4 ;
}
#if 0
printf ( " b=0x%04x v=%u c=%u b[1]=0x%02x \n " ,
( unsigned ) b , ( unsigned ) v , ( unsigned ) c , ( unsigned ) buffer [ 1 ] ) ;
# endif
} else if ( c > 12 ) {
// invalid
v = 0 ;
2016-10-15 02:26:13 -03:00
}
2016-10-14 02:07:52 -03:00
2016-11-24 07:27:42 -04:00
// if channel number if greater than 16 then it is a X-Plus
// channel. We don't yet know how to decode those. There is some information here:
// http://www.deviationtx.com/forum/protocol-development/2088-18-channels-for-dsm2-dsmx?start=40
// but we really need some sample data to confirm
2016-10-14 02:07:52 -03:00
if ( c < SRXL_MAX_CHANNELS ) {
v = ( ( ( v - 0x400 ) * 500 ) / 876 ) + 1500 ;
channels [ c ] = v ;
if ( c > = max_channels ) {
max_channels = c + 1 ;
}
}
2016-10-15 02:26:13 -03:00
2016-10-14 02:07:52 -03:00
//printf("%u:%u ", (unsigned)c, (unsigned)v);
}
//printf("\n");
* num_values = max_channels ;
if ( * num_values > max_values ) {
* num_values = max_values ;
}
memcpy ( values , channels , ( * num_values ) * 2 ) ;
2016-10-15 02:26:13 -03:00
// check failsafe bit, this goes low when connection to the
// transmitter is lost
* failsafe_state = ( ( buffer [ 1 ] & 2 ) = = 0 ) ;
2016-11-24 07:27:42 -04:00
// success
return 0 ;
2016-10-14 02:07:52 -03:00
}
2016-11-24 07:27:42 -04:00
/**
* Decode SRXL frames
*
*
* Structure of all SRXL frames
* Byte [ 0 ] : Header 0xA < VARIANT > - - > Variant specific header . Variant is encoded in bits 3 - 0 of header byte .
* Byte [ 1 ] - Byte [ N - 2 ] : SRXL variant specific payload
* Byte [ N - 1 ] - Byte [ N ] : CRC16 over payload and header
*
* @ param [ in ] timestamp_us - timestamp in microseconds
* @ param [ in ] received byte in microseconds
* @ param [ out ] num_values - number of RC channels extracted from srxl frame
* @ param [ out ] values - array of RC channels with refreshed information as pulsewidth in microseconds Range : 800u s - 2200u s
* @ param [ in ] maximum number of values supported by pixhawk
* @ param [ out ] failsafe_state - true : RC - receiver is in failsafe state , false : RC - receiver is not in failsafe state
2017-01-08 22:10:37 -04:00
* @ retval 0 success ( a decoded packet )
* @ retval 1 no packet yet ( accumulating )
* @ retval 2 unknown packet
* @ retval 4 checksum error
2016-11-24 07:27:42 -04:00
*/
int srxl_decode ( uint64_t timestamp_us , uint8_t byte , uint8_t * num_values , uint16_t * values , uint16_t max_values , bool * failsafe_state )
{
int ret = 1 ;
/*----------------------------------------distinguish different srxl variants at the beginning of each frame---------------------------------------------- */
/* Check if we have a new begin of a frame --> indicators: Time gap in datastream + SRXL header 0xA<VARIANT>*/
if ( ( timestamp_us - last_data_us ) > = SRXL_MIN_FRAMESPACE_US ) {
/* Now detect SRXL variant based on header */
switch ( byte ) {
case SRXL_HEADER_V1 :
frame_len_full = SRXL_FRAMELEN_V1 ;
frame_header = SRXL_HEADER_V1 ;
decode_state = STATE_NEW ;
break ;
case SRXL_HEADER_V2 :
frame_len_full = SRXL_FRAMELEN_V2 ;
frame_header = SRXL_HEADER_V2 ;
decode_state = STATE_NEW ;
break ;
case SRXL_HEADER_V5 :
frame_len_full = SRXL_FRAMELEN_V5 ;
frame_header = SRXL_HEADER_V5 ;
decode_state = STATE_NEW ;
break ;
default :
frame_len_full = 0U ;
frame_header = SRXL_HEADER_NOT_IMPL ;
decode_state = STATE_IDLE ;
2017-01-08 18:08:20 -04:00
buflen = 0 ;
return 2 ; /* protocol version not implemented --> no channel data --> unknown packet */
2016-11-24 07:27:42 -04:00
}
}
/*--------------------------------------------collect all data from stream and decode-------------------------------------------------------*/
switch ( decode_state ) {
case STATE_NEW : /* buffer header byte and prepare for frame reception and decoding */
buffer [ 0U ] = byte ;
2019-04-04 02:32:58 -03:00
crc_fmu = crc_xmodem_update ( 0U , byte ) ;
2016-11-24 07:27:42 -04:00
buflen = 1U ;
decode_state_next = STATE_COLLECT ;
break ;
case STATE_COLLECT : /* receive all bytes. After reception decode frame and provide rc channel information to FMU */
2017-01-08 18:08:20 -04:00
if ( buflen > = frame_len_full ) {
// a logic bug in the state machine, this shouldn't happen
decode_state = STATE_IDLE ;
buflen = 0 ;
frame_len_full = 0 ;
frame_header = SRXL_HEADER_NOT_IMPL ;
return 2 ;
}
2016-11-24 07:27:42 -04:00
buffer [ buflen ] = byte ;
buflen + + ;
/* CRC not over last 2 frame bytes as these bytes inhabitate the crc */
if ( buflen < = ( frame_len_full - 2 ) ) {
2019-04-04 02:32:58 -03:00
crc_fmu = crc_xmodem_update ( crc_fmu , byte ) ;
2016-11-24 07:27:42 -04:00
}
if ( buflen = = frame_len_full ) {
/* CRC check here */
crc_receiver = ( ( uint16_t ) buffer [ buflen - 2 ] < < 8U ) | ( ( uint16_t ) buffer [ buflen - 1 ] ) ;
if ( crc_receiver = = crc_fmu ) {
/* at this point buffer contains all frame data and crc is valid --> extract channel info according to SRXL variant */
switch ( frame_header ) {
case SRXL_HEADER_V1 :
ret = srxl_channels_get_v1v2 ( max_values , num_values , values , failsafe_state ) ;
break ;
case SRXL_HEADER_V2 :
ret = srxl_channels_get_v1v2 ( max_values , num_values , values , failsafe_state ) ;
break ;
case SRXL_HEADER_V5 :
ret = srxl_channels_get_v5 ( max_values , num_values , values , failsafe_state ) ;
break ;
default :
ret = 2 ; /* protocol version not implemented --> no channel data */
break ;
}
}
else {
ret = 4 ; /* CRC fail --> no channel data */
}
decode_state_next = STATE_IDLE ; /* frame data buffering and decoding finished --> statemachine not in use until new header drops is */
}
else {
/* frame not completely received --> frame data buffering still ongoing */
decode_state_next = STATE_COLLECT ;
}
break ;
default :
ret = 1 ; /* STATE_IDLE --> do nothing */
break ;
} /* switch (decode_state) */
decode_state = decode_state_next ;
last_data_us = timestamp_us ;
return ret ;
2016-10-14 02:07:52 -03:00
}
# ifdef TEST_MAIN_PROGRAM
/*
test harness for use under Linux with USB serial adapter
*/
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <time.h>
# include <unistd.h>
# include <stdlib.h>
# include <termios.h>
static uint64_t micros64 ( void )
{
struct timespec ts ;
clock_gettime ( CLOCK_MONOTONIC , & ts ) ;
return 1.0e6 * ( ( ts . tv_sec + ( ts . tv_nsec * 1.0e-9 ) ) ) ;
}
int main ( int argc , const char * argv [ ] )
{
2016-10-30 10:42:48 -03:00
int fd = open ( argv [ 1 ] , O_RDONLY | O_CLOEXEC ) ;
2016-10-14 02:07:52 -03:00
if ( fd = = - 1 ) {
perror ( argv [ 1 ] ) ;
exit ( 1 ) ;
}
struct termios options ;
tcgetattr ( fd , & options ) ;
cfsetispeed ( & options , B115200 ) ;
cfsetospeed ( & options , B115200 ) ;
options . c_cflag & = ~ ( PARENB | CSTOPB | CSIZE ) ;
options . c_cflag | = CS8 ;
options . c_lflag & = ~ ( ICANON | ECHO | ECHOE | ISIG ) ;
options . c_iflag & = ~ ( IXON | IXOFF | IXANY ) ;
options . c_oflag & = ~ OPOST ;
if ( tcsetattr ( fd , TCSANOW , & options ) ! = 0 ) {
perror ( " tcsetattr " ) ;
exit ( 1 ) ;
}
tcflush ( fd , TCIOFLUSH ) ;
while ( true ) {
uint8_t b ;
uint8_t num_values = 0 ;
2016-10-16 04:51:24 -03:00
uint16_t values [ 20 ] ;
2016-10-15 02:26:13 -03:00
bool failsafe_state ;
2016-10-14 02:07:52 -03:00
fd_set fds ;
struct timeval tv ;
FD_ZERO ( & fds ) ;
FD_SET ( fd , & fds ) ;
tv . tv_sec = 1 ;
tv . tv_usec = 0 ;
// check if any bytes are available
2016-11-24 07:27:42 -04:00
if ( select ( fd + 1 , & fds , NULL , NULL , & tv ) ! = 1 ) {
2016-10-14 02:07:52 -03:00
break ;
}
if ( read ( fd , & b , 1 ) ! = 1 ) {
break ;
}
2016-10-16 04:51:24 -03:00
if ( srxl_decode ( micros64 ( ) , b , & num_values , values , sizeof ( values ) / sizeof ( values [ 0 ] ) , & failsafe_state ) = = 0 ) {
2016-10-14 02:07:52 -03:00
# if 1
printf ( " %u: " , num_values ) ;
for ( uint8_t i = 0 ; i < num_values ; i + + ) {
printf ( " %u:%4u " , i + 1 , values [ i ] ) ;
}
2016-10-15 02:26:13 -03:00
printf ( " %s \n " , failsafe_state ? " FAIL " : " OK " ) ;
2016-10-14 02:07:52 -03:00
# endif
}
}
}
# endif
2016-10-16 04:51:24 -03:00
2016-11-24 07:27:42 -04:00
# ifdef TEST_BIN_PROGRAM
2016-10-16 04:51:24 -03:00
/*
test harness for use under Linux with hex dump in a file
*/
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <time.h>
# include <unistd.h>
# include <stdlib.h>
# include <termios.h>
int main ( int argc , const char * argv [ ] )
{
FILE * f = fopen ( argv [ 1 ] , " r " ) ;
if ( ! f ) {
perror ( argv [ 1 ] ) ;
exit ( 1 ) ;
}
uint64_t t = 0 ;
while ( true ) {
uint8_t b ;
uint8_t num_values = 0 ;
uint16_t values [ 20 ] ;
bool failsafe_state ;
2016-11-24 07:27:42 -04:00
uint8_t header ;
if ( fread ( & header , 1 , 1 , f ) ! = 1 ) {
break ;
}
uint8_t frame_size = 0 ;
switch ( header ) {
case SRXL_HEADER_V1 :
frame_size = SRXL_FRAMELEN_V1 ;
break ;
case SRXL_HEADER_V2 :
frame_size = SRXL_FRAMELEN_V2 ;
break ;
case SRXL_HEADER_V5 :
frame_size = SRXL_FRAMELEN_V5 ;
break ;
}
if ( frame_size = = 0 ) {
continue ;
}
uint8_t u [ frame_size ] ;
u [ 0 ] = header ;
if ( fread ( & u [ 1 ] , 1 , sizeof ( u ) - 1 , f ) ! = sizeof ( u ) - 1 ) {
2016-10-16 04:51:24 -03:00
break ;
}
t + = 11000 ;
2016-11-24 07:27:42 -04:00
for ( uint8_t i = 0 ; i < sizeof ( u ) ; i + + ) {
2016-10-16 04:51:24 -03:00
b = u [ i ] ;
if ( srxl_decode ( t , b , & num_values , values , sizeof ( values ) / sizeof ( values [ 0 ] ) , & failsafe_state ) = = 0 ) {
printf ( " %u: " , num_values ) ;
for ( uint8_t i = 0 ; i < num_values ; i + + ) {
printf ( " %u:%4u " , i + 1 , values [ i ] ) ;
}
printf ( " %s \n " , failsafe_state ? " FAIL " : " OK " ) ;
}
}
}
2018-06-19 20:24:38 -03:00
fclose ( f ) ;
2016-10-16 04:51:24 -03:00
}
# endif