You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
515 lines
19 KiB
515 lines
19 KiB
/* |
|
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 |
|
|
|
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) |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <stdint.h> |
|
#include <string.h> |
|
#include "srxl.h" |
|
|
|
|
|
|
|
/* 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 */ |
|
|
|
|
|
static uint16_t max_channels; |
|
|
|
|
|
|
|
|
|
/** |
|
* This function calculates the 16bit crc as used throughout the srxl protocol variants |
|
* |
|
* This function is intended to be called whenever a new byte shall be added to the crc. |
|
* Simply provide the old crc and the new data byte and the function return the new crc value. |
|
* |
|
* To start a new crc calculation for a new srxl frame, provide parameter crc=0 and the first byte of the frame. |
|
* |
|
* @param[in] crc - start value for crc |
|
* @param[in] new_byte - byte that shall be included in crc calculation |
|
* @return calculated crc |
|
*/ |
|
static uint16_t srxl_crc16 (uint16_t crc, uint8_t new_byte) |
|
{ |
|
uint8_t loop; |
|
crc = crc ^ (uint16_t)new_byte << 8; |
|
for(loop = 0; loop < 8; loop++) { |
|
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1); |
|
} |
|
return crc; |
|
} |
|
|
|
|
|
/** |
|
* 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: 800us - 2200us |
|
* @param[out] failsafe_state - true: RC-receiver is in failsafe state, false: RC-receiver is not in failsafe state |
|
* @retval 0 success |
|
*/ |
|
static int srxl_channels_get_v1v2(uint16_t max_values, uint8_t *num_values, uint16_t *values, bool *failsafe_state) |
|
{ |
|
uint8_t loop; |
|
uint32_t channel_raw_value; |
|
|
|
*num_values = (uint8_t)((frame_len_full - 3U)/2U); |
|
*failsafe_state = 0U; /* this protocol version does not support failsafe information */ |
|
|
|
/* 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 */ |
|
} |
|
|
|
/* provide channel data to FMU */ |
|
if ( (uint16_t)*num_values > max_values) { |
|
*num_values = (uint8_t)max_values; |
|
} |
|
memcpy(values, channels, (*num_values)*2); |
|
|
|
return 0; /* for srxl protocol version 1 and 2 it is not expected, that any error happen during decode process */ |
|
} |
|
|
|
|
|
/** |
|
* 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: 800us - 2200us |
|
* @param[out] failsafe_state - true: RC-receiver is in failsafe state, false: RC-receiver is not in failsafe state |
|
* @retval 0 success |
|
*/ |
|
static int srxl_channels_get_v5(uint16_t max_values, uint8_t *num_values, uint16_t *values, bool *failsafe_state) |
|
{ |
|
// 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; |
|
if (b & 0x8000) { |
|
continue; |
|
} |
|
if (c == 12) { |
|
// special handling for channel 12 |
|
// 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]); |
|
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; |
|
} |
|
|
|
// 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 |
|
if (c < SRXL_MAX_CHANNELS) { |
|
v = (((v - 0x400) * 500) / 876) + 1500; |
|
channels[c] = v; |
|
if (c >= max_channels) { |
|
max_channels = c+1; |
|
} |
|
} |
|
|
|
//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); |
|
|
|
// check failsafe bit, this goes low when connection to the |
|
// transmitter is lost |
|
*failsafe_state = ((buffer[1] & 2) == 0); |
|
|
|
// success |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
* 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: 800us - 2200us |
|
* @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 |
|
* @retval 0 success (a decoded packet) |
|
* @retval 1 no packet yet (accumulating) |
|
* @retval 2 unknown packet |
|
* @retval 4 checksum error |
|
*/ |
|
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; |
|
buflen = 0; |
|
return 2; /* protocol version not implemented --> no channel data --> unknown packet */ |
|
} |
|
} |
|
|
|
|
|
/*--------------------------------------------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; |
|
crc_fmu = srxl_crc16(0U,byte); |
|
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 */ |
|
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; |
|
} |
|
buffer[buflen] = byte; |
|
buflen++; |
|
/* CRC not over last 2 frame bytes as these bytes inhabitate the crc */ |
|
if (buflen <= (frame_len_full-2)) { |
|
crc_fmu = srxl_crc16(crc_fmu,byte); |
|
} |
|
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; |
|
} |
|
|
|
|
|
#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[]) |
|
{ |
|
int fd = open(argv[1], O_RDONLY|O_CLOEXEC); |
|
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; |
|
uint16_t values[20]; |
|
bool failsafe_state; |
|
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 |
|
if (select(fd+1, &fds, NULL, NULL, &tv) != 1) { |
|
break; |
|
} |
|
|
|
if (read(fd, &b, 1) != 1) { |
|
break; |
|
} |
|
|
|
if (srxl_decode(micros64(), b, &num_values, values, sizeof(values)/sizeof(values[0]), &failsafe_state) == 0) { |
|
#if 1 |
|
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"); |
|
#endif |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
|
|
#ifdef TEST_BIN_PROGRAM |
|
/* |
|
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; |
|
|
|
|
|
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) { |
|
break; |
|
} |
|
t += 11000; |
|
|
|
for (uint8_t i=0; i<sizeof(u); i++) { |
|
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"); |
|
} |
|
} |
|
} |
|
fclose(f); |
|
} |
|
#endif
|
|
|