2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
25 #ifdef USE_SERIALRX_SRXL2
27 #include "common/crc.h"
28 #include "common/maths.h"
29 #include "common/streambuf.h"
31 #include "drivers/nvic.h"
32 #include "drivers/time.h"
33 #include "drivers/serial.h"
34 #include "drivers/serial_uart.h"
36 #include "io/serial.h"
39 #include "rx/srxl2_types.h"
46 //void cliPrintf(const char *format, ...);
47 //#define DEBUG_PRINTF(format, ...) cliPrintf(format, __VA_ARGS__)
48 #define DEBUG_PRINTF(...) //Temporary until a better debug printf can be included
50 #define DEBUG_PRINTF(...)
55 #define SRXL2_MAX_CHANNELS 32
56 #define SRXL2_FRAME_PERIOD_US 11000 // 5500 for DSMR
57 #define SRXL2_CHANNEL_SHIFT 5
58 #define SRXL2_CHANNEL_CENTER 0x8000
60 #define SRXL2_PORT_BAUDRATE_DEFAULT 115200
61 #define SRXL2_PORT_BAUDRATE_HIGH 400000
62 #define SRXL2_PORT_OPTIONS (SERIAL_STOPBITS_1 | SERIAL_PARITY_NO | SERIAL_BIDIR)
63 #define SRXL2_PORT_MODE MODE_RXTX
65 #define SRXL2_REPLY_QUIESCENCE (2 * 10 * 1000000 / SRXL2_PORT_BAUDRATE_DEFAULT) // 2 * (lastIdleTimestamp - lastReceiveTimestamp). Time taken to send 2 bytes
68 #define SRXL2_MAX_PACKET_LENGTH 80
69 #define SRXL2_DEVICE_ID_BROADCAST 0xFF
71 #define SRXL2_FRAME_TIMEOUT_US 50000
73 #define SRXL2_LISTEN_FOR_ACTIVITY_TIMEOUT_US 50000
74 #define SRXL2_SEND_HANDSHAKE_TIMEOUT_US 50000
75 #define SRXL2_LISTEN_FOR_HANDSHAKE_TIMEOUT_US 200000
77 #define SPEKTRUM_PULSE_OFFSET 988 // Offset value to convert digital data into RC pulse
80 uint8_t raw
[SRXL2_MAX_PACKET_LENGTH
];
85 volatile unsigned len
;
89 static uint8_t unitId
= 0;
90 static uint8_t baudRate
= 0;
92 static Srxl2State state
= Disabled
;
93 static uint32_t timeoutTimestamp
= 0;
94 static uint32_t fullTimeoutTimestamp
= 0;
95 static uint32_t lastValidPacketTimestamp
= 0;
96 static volatile uint32_t lastReceiveTimestamp
= 0;
97 static volatile uint32_t lastIdleTimestamp
= 0;
99 struct rxBuf readBuffer
[2];
100 struct rxBuf
* readBufferPtr
= &readBuffer
[0];
101 struct rxBuf
* processBufferPtr
= &readBuffer
[1];
102 static volatile unsigned readBufferIdx
= 0;
103 static volatile bool transmittingTelemetry
= false;
104 static uint8_t writeBuffer
[SRXL2_MAX_PACKET_LENGTH
];
105 static unsigned writeBufferIdx
= 0;
107 static serialPort_t
*serialPort
;
109 static uint8_t busMasterDeviceId
= 0xFF;
110 static bool telemetryRequested
= false;
112 static uint8_t telemetryFrame
[22];
114 uint8_t globalResult
= 0;
116 /* handshake protocol
117 1. listen for 50ms for serial activity and go to State::Running if found, autobaud may be necessary
118 2. if srxl2_unitId = 0:
119 send a Handshake with destinationDeviceId = 0 every 50ms for at least 200ms
121 listen for Handshake for at least 200ms
122 3. respond to Handshake as currently implemented in process if rePst received
123 4. respond to broadcast Handshake
126 // if 50ms with not activity, go to default baudrate and to step 1
128 bool srxl2ProcessHandshake(const Srxl2Header
* header
)
130 const Srxl2HandshakeSubHeader
* handshake
= (Srxl2HandshakeSubHeader
*)(header
+ 1);
131 if (handshake
->destinationDeviceId
== Broadcast
) {
132 DEBUG_PRINTF("broadcast handshake from %x\r\n", handshake
->sourceDeviceId
);
133 busMasterDeviceId
= handshake
->sourceDeviceId
;
135 if (handshake
->baudSupported
== 1) {
136 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_HIGH
);
137 DEBUG_PRINTF("switching to %d baud\r\n", SRXL2_PORT_BAUDRATE_HIGH
);
146 if (handshake
->destinationDeviceId
!= ((FlightController
<< 4) | unitId
)) {
150 DEBUG_PRINTF("FC handshake from %x\r\n", handshake
->sourceDeviceId
);
152 Srxl2HandshakeFrame response
= {
155 handshake
->destinationDeviceId
,
156 handshake
->sourceDeviceId
,
158 /* baudSupported*/ baudRate
,
164 srxl2RxWriteData(&response
, sizeof(response
));
169 void srxl2ProcessChannelData(const Srxl2ChannelDataHeader
* channelData
, rxRuntimeConfig_t
*rxRuntimeConfig
) {
170 globalResult
= RX_FRAME_COMPLETE
;
172 if (channelData
->rssi
>= 0) {
173 const int rssiPercent
= channelData
->rssi
;
174 lqTrackerSet(rxRuntimeConfig
->lqTracker
, scaleRange(constrain(rssiPercent
, 0, 100), 0, 100, 0, RSSI_MAX_VALUE
));
175 //setRssi(scaleRange(rssiPercent, 0, 100, 0, RSSI_MAX_VALUE), RSSI_SOURCE_RX_PROTOCOL);
178 //If receiver is in a connected state, and a packet is missed, the channel mask will be 0.
179 if (!channelData
->channelMask
.u32
) {
180 globalResult
|= RX_FRAME_FAILSAFE
;
184 const uint16_t *frameChannels
= (const uint16_t *) (channelData
+ 1);
185 uint32_t channelMask
= channelData
->channelMask
.u32
;
186 while (channelMask
) {
187 unsigned idx
= __builtin_ctz (channelMask
);
188 uint32_t mask
= 1 << idx
;
189 rxRuntimeConfig
->channelData
[idx
] = *frameChannels
++;
190 channelMask
&= ~mask
;
193 DEBUG_PRINTF("channel data: %d %d %x\r\n", channelData_header
->rssi
, channelData_header
->frameLosses
, channelData_header
->channelMask
.u32
);
196 bool srxl2ProcessControlData(const Srxl2Header
* header
, rxRuntimeConfig_t
*rxRuntimeConfig
)
198 const Srxl2ControlDataSubHeader
* controlData
= (Srxl2ControlDataSubHeader
*)(header
+ 1);
199 const uint8_t ownId
= (FlightController
<< 4) | unitId
;
200 if (controlData
->replyId
== ownId
) {
201 telemetryRequested
= true;
202 DEBUG_PRINTF("command: %x replyId: %x ownId: %x\r\n", controlData
->command
, controlData
->replyId
, ownId
);
205 switch (controlData
->command
) {
207 srxl2ProcessChannelData((const Srxl2ChannelDataHeader
*) (controlData
+ 1), rxRuntimeConfig
);
210 case FailsafeChannelData
: {
211 globalResult
|= RX_FRAME_FAILSAFE
;
212 //setRssiDirect(0, RSSI_SOURCE_RX_PROTOCOL);
213 lqTrackerSet(rxRuntimeConfig
->lqTracker
, 0);
214 // DEBUG_PRINTF("fs channel data\r\n");
218 #if defined(USE_SPEKTRUM_VTX_CONTROL) && defined(USE_VTX_COMMON)
219 Srxl2VtxData
*vtxData
= (Srxl2VtxData
*)(controlData
+ 1);
220 DEBUG_PRINTF("vtx data\r\n");
221 DEBUG_PRINTF("vtx band: %x\r\n", vtxData
->band
);
222 DEBUG_PRINTF("vtx channel: %x\r\n", vtxData
->channel
);
223 DEBUG_PRINTF("vtx pit: %x\r\n", vtxData
->pit
);
224 DEBUG_PRINTF("vtx power: %x\r\n", vtxData
->power
);
225 DEBUG_PRINTF("vtx powerDec: %x\r\n", vtxData
->powerDec
);
226 DEBUG_PRINTF("vtx region: %x\r\n", vtxData
->region
);
227 // Pack data as it was used before srxl2 to use existing functions.
228 // Get the VTX control bytes in a frame
229 uint32_t vtxControl
= (0xE0 << 24) | (0xE0 << 8) |
230 ((vtxData
->band
& 0x07) << 21) |
231 ((vtxData
->channel
& 0x0F) << 16) |
232 ((vtxData
->pit
& 0x01) << 4) |
233 ((vtxData
->region
& 0x01) << 3) |
234 ((vtxData
->power
& 0x07));
235 spektrumHandleVtxControl(vtxControl
);
243 bool srxl2ProcessPacket(const Srxl2Header
* header
, rxRuntimeConfig_t
*rxRuntimeConfig
)
245 switch (header
->packetType
) {
247 return srxl2ProcessHandshake(header
);
249 return srxl2ProcessControlData(header
, rxRuntimeConfig
);
251 DEBUG_PRINTF("Other packet type, ID: %x \r\n", header
->packetType
);
258 // @note assumes packet is fully there
259 void srxl2Process(rxRuntimeConfig_t
*rxRuntimeConfig
)
261 if (processBufferPtr
->packet
.header
.id
!= SRXL2_ID
|| processBufferPtr
->len
!= processBufferPtr
->packet
.header
.length
) {
262 DEBUG_PRINTF("invalid header id: %x, or length: %x received vs %x expected \r\n", processBufferPtr
->packet
.header
.id
, processBufferPtr
->len
, processBufferPtr
->packet
.header
.length
);
263 globalResult
= RX_FRAME_DROPPED
;
267 const uint16_t calculatedCrc
= crc16_ccitt_update(0, processBufferPtr
->packet
.raw
, processBufferPtr
->packet
.header
.length
);
269 //Invalid if crc non-zero
271 globalResult
= RX_FRAME_DROPPED
;
272 DEBUG_PRINTF("crc mismatch %x\r\n", calculatedCrc
);
276 //Packet is valid only after ID and CRC check out
277 lastValidPacketTimestamp
= micros();
279 if (srxl2ProcessPacket(&processBufferPtr
->packet
.header
, rxRuntimeConfig
)) {
283 DEBUG_PRINTF("could not parse packet: %x\r\n", processBufferPtr
->packet
.header
.packetType
);
284 globalResult
= RX_FRAME_DROPPED
;
288 static void srxl2DataReceive(uint16_t character
, void *data
)
292 lastReceiveTimestamp
= microsISR();
294 //If the buffer len is not reset for whatever reason, disable reception
295 if (readBufferPtr
->len
> 0 || readBufferIdx
>= SRXL2_MAX_PACKET_LENGTH
) {
297 globalResult
= RX_FRAME_DROPPED
;
300 readBufferPtr
->packet
.raw
[readBufferIdx
] = character
;
305 static void srxl2Idle(void)
307 if(transmittingTelemetry
) { // Transmitting telemetry triggers idle interrupt as well. We dont want to change buffers then
308 transmittingTelemetry
= false;
310 else if(readBufferIdx
== 0) { // Packet was invalid
311 readBufferPtr
->len
= 0;
314 lastIdleTimestamp
= microsISR();
315 //Swap read and process buffer pointers
316 if(processBufferPtr
== &readBuffer
[0]) {
317 processBufferPtr
= &readBuffer
[1];
318 readBufferPtr
= &readBuffer
[0];
320 processBufferPtr
= &readBuffer
[0];
321 readBufferPtr
= &readBuffer
[1];
323 processBufferPtr
->len
= readBufferIdx
;
329 static uint8_t srxl2FrameStatus(rxRuntimeConfig_t
*rxRuntimeConfig
)
331 UNUSED(rxRuntimeConfig
);
333 if(serialIsIdle(serialPort
))
338 globalResult
= RX_FRAME_PENDING
;
340 // len should only be set after an idle interrupt (packet reception complete)
341 if (processBufferPtr
!= NULL
&& processBufferPtr
->len
) {
342 srxl2Process(rxRuntimeConfig
);
343 processBufferPtr
->len
= 0;
346 uint8_t result
= globalResult
;
348 const uint32_t now
= micros();
351 case Disabled
: break;
353 case ListenForActivity
: {
355 if (lastValidPacketTimestamp
!= 0) {
356 // as ListenForActivity is done at default baud-rate, we don't need to change anything
357 // @todo if there were non-handshake packets - go to running,
358 // if there were - go to either Send Handshake or Listen For Handshake
360 } else if (cmpTimeUs(lastIdleTimestamp
, lastReceiveTimestamp
) > 0) {
362 uint32_t currentBaud
= serialGetBaudRate(serialPort
);
364 if(currentBaud
== SRXL2_PORT_BAUDRATE_DEFAULT
)
365 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_HIGH
);
367 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_DEFAULT
);
369 } else if (cmpTimeUs(now
, timeoutTimestamp
) >= 0) {
370 // @todo if there was activity - detect baudrate and ListenForHandshake
373 state
= SendHandshake
;
374 timeoutTimestamp
= now
+ SRXL2_SEND_HANDSHAKE_TIMEOUT_US
;
375 fullTimeoutTimestamp
= now
+ SRXL2_LISTEN_FOR_HANDSHAKE_TIMEOUT_US
;
377 state
= ListenForHandshake
;
378 timeoutTimestamp
= now
+ SRXL2_LISTEN_FOR_HANDSHAKE_TIMEOUT_US
;
383 case SendHandshake
: {
384 if (cmpTimeUs(now
, timeoutTimestamp
) >= 0) {
385 // @todo set another timeout for 50ms tries
386 // fill write buffer with handshake frame
387 result
|= RX_FRAME_PROCESSING_REQUIRED
;
390 if (cmpTimeUs(now
, fullTimeoutTimestamp
) >= 0) {
391 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_DEFAULT
);
392 DEBUG_PRINTF("case SendHandshake: switching to %d baud\r\n", SRXL2_PORT_BAUDRATE_DEFAULT
);
393 timeoutTimestamp
= now
+ SRXL2_LISTEN_FOR_ACTIVITY_TIMEOUT_US
;
394 result
= (result
& ~RX_FRAME_PENDING
) | RX_FRAME_FAILSAFE
;
396 state
= ListenForActivity
;
397 lastReceiveTimestamp
= 0;
401 case ListenForHandshake
: {
402 if (cmpTimeUs(now
, timeoutTimestamp
) >= 0) {
403 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_DEFAULT
);
404 DEBUG_PRINTF("case ListenForHandshake: switching to %d baud\r\n", SRXL2_PORT_BAUDRATE_DEFAULT
);
405 timeoutTimestamp
= now
+ SRXL2_LISTEN_FOR_ACTIVITY_TIMEOUT_US
;
406 result
= (result
& ~RX_FRAME_PENDING
) | RX_FRAME_FAILSAFE
;
408 state
= ListenForActivity
;
409 lastReceiveTimestamp
= 0;
414 // frame timed out, reset state
415 if (cmpTimeUs(now
, lastValidPacketTimestamp
) >= SRXL2_FRAME_TIMEOUT_US
) {
416 serialSetBaudRate(serialPort
, SRXL2_PORT_BAUDRATE_DEFAULT
);
417 DEBUG_PRINTF("case Running: switching to %d baud: %d %d\r\n", SRXL2_PORT_BAUDRATE_DEFAULT
, now
, lastValidPacketTimestamp
);
418 timeoutTimestamp
= now
+ SRXL2_LISTEN_FOR_ACTIVITY_TIMEOUT_US
;
419 result
= (result
& ~RX_FRAME_PENDING
) | RX_FRAME_FAILSAFE
;
421 state
= ListenForActivity
;
422 lastReceiveTimestamp
= 0;
423 lastValidPacketTimestamp
= 0;
428 if (writeBufferIdx
) {
429 result
|= RX_FRAME_PROCESSING_REQUIRED
;
435 static bool srxl2ProcessFrame(const rxRuntimeConfig_t
*rxRuntimeConfig
)
437 UNUSED(rxRuntimeConfig
);
439 if (writeBufferIdx
== 0) {
443 const uint32_t now
= micros();
445 if (cmpTimeUs(lastIdleTimestamp
, lastReceiveTimestamp
) > 0) {
446 // time sufficient for at least 2 characters has passed
447 if (cmpTimeUs(now
, lastReceiveTimestamp
) > SRXL2_REPLY_QUIESCENCE
) {
448 transmittingTelemetry
= true;
449 serialWriteBuf(serialPort
, writeBuffer
, writeBufferIdx
);
452 DEBUG_PRINTF("not enough time to send 2 characters passed yet, %d us since last receive, %d required\r\n", now
- lastReceiveTimestamp
, SRXL2_REPLY_QUIESCENCE
);
455 DEBUG_PRINTF("still receiving a frame, %d %d\r\n", lastIdleTimestamp
, lastReceiveTimestamp
);
461 static uint16_t srxl2ReadRawRC(const rxRuntimeConfig_t
*rxRuntimeConfig
, uint8_t channelIdx
)
463 if (channelIdx
>= rxRuntimeConfig
->channelCount
) {
467 return SPEKTRUM_PULSE_OFFSET
+ ((rxRuntimeConfig
->channelData
[channelIdx
] >> SRXL2_CHANNEL_SHIFT
) >> 1);
470 void srxl2RxWriteData(const void *data
, int len
)
472 const uint16_t crc
= crc16_ccitt_update(0, (uint8_t*)data
, len
- 2);
473 ((uint8_t*)data
)[len
-2] = ((uint8_t *) &crc
)[1] & 0xFF;
474 ((uint8_t*)data
)[len
-1] = ((uint8_t *) &crc
)[0] & 0xFF;
476 len
= MIN(len
, (int)sizeof(writeBuffer
));
477 memcpy(writeBuffer
, data
, len
);
478 writeBufferIdx
= len
;
481 bool srxl2RxInit(const rxConfig_t
*rxConfig
, rxRuntimeConfig_t
*rxRuntimeConfig
)
483 static uint16_t channelData
[SRXL2_MAX_CHANNELS
];
484 for (size_t i
= 0; i
< SRXL2_MAX_CHANNELS
; ++i
) {
485 channelData
[i
] = SRXL2_CHANNEL_CENTER
;
488 unitId
= rxConfig
->srxl2_unit_id
;
489 baudRate
= rxConfig
->srxl2_baud_fast
;
491 rxRuntimeConfig
->channelData
= channelData
;
492 rxRuntimeConfig
->channelCount
= SRXL2_MAX_CHANNELS
;
493 rxRuntimeConfig
->rcReadRawFn
= srxl2ReadRawRC
;
494 rxRuntimeConfig
->rcFrameStatusFn
= srxl2FrameStatus
;
495 rxRuntimeConfig
->rcProcessFrameFn
= srxl2ProcessFrame
;
497 const serialPortConfig_t
*portConfig
= findSerialPortConfig(FUNCTION_RX_SERIAL
);
502 portOptions_t options
= SRXL2_PORT_OPTIONS
;
503 if (rxConfig
->serialrx_inverted
) {
504 options
|= SERIAL_INVERTED
;
506 if (rxConfig
->halfDuplex
) {
507 options
|= SERIAL_BIDIR
;
510 serialPort
= openSerialPort(portConfig
->identifier
, FUNCTION_RX_SERIAL
, srxl2DataReceive
,
511 NULL
, SRXL2_PORT_BAUDRATE_DEFAULT
, SRXL2_PORT_MODE
, options
);
517 state
= ListenForActivity
;
518 timeoutTimestamp
= micros() + SRXL2_LISTEN_FOR_ACTIVITY_TIMEOUT_US
;
520 //Looks like this needs to be set in cli config
521 //if (rssiSource == RSSI_SOURCE_NONE) {
522 // rssiSource = RSSI_SOURCE_RX_PROTOCOL;
525 return (bool)serialPort
;
528 bool srxl2RxIsActive(void)
533 bool srxl2TelemetryRequested(void)
535 return telemetryRequested
;
538 void srxl2InitializeFrame(sbuf_t
*dst
)
540 dst
->ptr
= telemetryFrame
;
541 dst
->end
= ARRAYEND(telemetryFrame
);
543 sbufWriteU8(dst
, SRXL2_ID
);
544 sbufWriteU8(dst
, TelemetrySensorData
);
545 sbufWriteU8(dst
, ARRAYLEN(telemetryFrame
));
546 sbufWriteU8(dst
, busMasterDeviceId
);
549 void srxl2FinalizeFrame(sbuf_t
*dst
)
551 sbufSwitchToReader(dst
, telemetryFrame
);
552 // Include 2 additional bytes of length since we're letting the srxl2RxWriteData function add the CRC in
553 srxl2RxWriteData(sbufPtr(dst
), sbufBytesRemaining(dst
) + 2);
554 telemetryRequested
= false;
559 const size_t length
= sizeof(Srxl2BindInfoFrame
);
561 Srxl2BindInfoFrame bind
= {
564 .packetType
= BindInfo
,
568 .request
= EnterBindMode
,
569 .deviceId
= busMasterDeviceId
,
570 .bindType
= DMSX_11ms
,
571 .options
= SRXL_BIND_OPT_TELEM_TX_ENABLE
| SRXL_BIND_OPT_BIND_TX_ENABLE
,
575 srxl2RxWriteData(&bind
, length
);