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/>.
27 #ifdef USE_TELEMETRY_CRSF
29 #include "build/atomic.h"
30 #include "build/build_config.h"
31 #include "build/version.h"
35 #include "config/feature.h"
37 #include "config/config.h"
38 #include "common/crc.h"
39 #include "common/maths.h"
40 #include "common/printf.h"
41 #include "common/streambuf.h"
42 #include "common/utils.h"
44 #include "drivers/nvic.h"
46 #include "fc/rc_modes.h"
47 #include "fc/runtime_config.h"
49 #include "flight/imu.h"
50 #include "flight/position.h"
52 #include "io/displayport_crsf.h"
54 #include "io/serial.h"
57 #include "pg/pg_ids.h"
60 #include "rx/crsf_protocol.h"
62 #include "sensors/battery.h"
63 #include "sensors/sensors.h"
65 #include "telemetry/telemetry.h"
66 #include "telemetry/msp_shared.h"
71 #define CRSF_CYCLETIME_US 100000 // 100ms, 10 Hz
72 #define CRSF_DEVICEINFO_VERSION 0x01
73 #define CRSF_DEVICEINFO_PARAMETER_COUNT 0
75 #define CRSF_MSP_BUFFER_SIZE 96
76 #define CRSF_MSP_LENGTH_OFFSET 1
78 static bool crsfTelemetryEnabled
;
79 static bool deviceInfoReplyPending
;
80 static uint8_t crsfFrame
[CRSF_FRAME_SIZE_MAX
];
82 #if defined(USE_MSP_OVER_TELEMETRY)
83 typedef struct mspBuffer_s
{
84 uint8_t bytes
[CRSF_MSP_BUFFER_SIZE
];
88 static mspBuffer_t mspRxBuffer
;
90 #if defined(USE_CRSF_V3)
91 static bool isCrsfV3Running
= false;
93 uint8_t hasPendingReply
:1;
94 uint8_t isNewSpeedValid
:1;
97 uint32_t confirmationTime
;
100 static crsfSpeedControl_s crsfSpeed
= {0};
102 bool checkCrsfCustomizedSpeed(void)
104 return crsfSpeed
.index
< BAUD_COUNT
? true : false;
107 uint32_t getCrsfDesiredSpeed(void)
109 return checkCrsfCustomizedSpeed() ? baudRates
[crsfSpeed
.index
] : CRSF_BAUDRATE
;
112 void setCrsfDefaultSpeed(void)
114 crsfSpeed
.hasPendingReply
= false;
115 crsfSpeed
.isNewSpeedValid
= false;
116 crsfSpeed
.confirmationTime
= 0;
117 crsfSpeed
.index
= BAUD_COUNT
;
118 isCrsfV3Running
= false;
119 crsfRxUpdateBaudrate(getCrsfDesiredSpeed());
123 void initCrsfMspBuffer(void)
128 bool bufferCrsfMspFrame(uint8_t *frameStart
, int frameLength
)
130 if (mspRxBuffer
.len
+ CRSF_MSP_LENGTH_OFFSET
+ frameLength
> CRSF_MSP_BUFFER_SIZE
) {
133 uint8_t *p
= mspRxBuffer
.bytes
+ mspRxBuffer
.len
;
135 memcpy(p
, frameStart
, frameLength
);
136 mspRxBuffer
.len
+= CRSF_MSP_LENGTH_OFFSET
+ frameLength
;
141 bool handleCrsfMspFrameBuffer(uint8_t payloadSize
, mspResponseFnPtr responseFn
)
143 bool requestHandled
= false;
144 if (!mspRxBuffer
.len
) {
149 const int mspFrameLength
= mspRxBuffer
.bytes
[pos
];
150 if (handleMspFrame(&mspRxBuffer
.bytes
[CRSF_MSP_LENGTH_OFFSET
+ pos
], mspFrameLength
, NULL
)) {
151 requestHandled
|= sendMspReply(payloadSize
, responseFn
);
153 pos
+= CRSF_MSP_LENGTH_OFFSET
+ mspFrameLength
;
154 ATOMIC_BLOCK(NVIC_PRIO_SERIALUART1
) {
155 if (pos
>= mspRxBuffer
.len
) {
157 return requestHandled
;
161 return requestHandled
;
165 static void crsfInitializeFrame(sbuf_t
*dst
)
167 dst
->ptr
= crsfFrame
;
168 dst
->end
= ARRAYEND(crsfFrame
);
170 sbufWriteU8(dst
, CRSF_SYNC_BYTE
);
173 static void crsfFinalize(sbuf_t
*dst
)
175 crc8_dvb_s2_sbuf_append(dst
, &crsfFrame
[2]); // start at byte 2, since CRC does not include device address and frame length
176 sbufSwitchToReader(dst
, crsfFrame
);
177 // write the telemetry frame to the receiver.
178 crsfRxWriteTelemetryData(sbufPtr(dst
), sbufBytesRemaining(dst
));
182 CRSF frame has the structure:
183 <Device address> <Frame length> <Type> <Payload> <CRC>
184 Device address: (uint8_t)
185 Frame length: length in bytes including Type (uint8_t)
187 CRC: (uint8_t), crc of <Type> and <Payload>
193 int32_t Latitude ( degree / 10`000`000 )
194 int32_t Longitude (degree / 10`000`000 )
195 uint16_t Groundspeed ( km/h / 10 )
196 uint16_t GPS heading ( degree / 100 )
197 uint16 Altitude ( meter 1000m offset )
198 uint8_t Satellites in use ( counter )
200 void crsfFrameGps(sbuf_t
*dst
)
202 // use sbufWrite since CRC does not include frame length
203 sbufWriteU8(dst
, CRSF_FRAME_GPS_PAYLOAD_SIZE
+ CRSF_FRAME_LENGTH_TYPE_CRC
);
204 sbufWriteU8(dst
, CRSF_FRAMETYPE_GPS
);
205 sbufWriteU32BigEndian(dst
, gpsSol
.llh
.lat
); // CRSF and betaflight use same units for degrees
206 sbufWriteU32BigEndian(dst
, gpsSol
.llh
.lon
);
207 sbufWriteU16BigEndian(dst
, (gpsSol
.groundSpeed
* 36 + 50) / 100); // gpsSol.groundSpeed is in cm/s
208 sbufWriteU16BigEndian(dst
, gpsSol
.groundCourse
* 10); // gpsSol.groundCourse is degrees * 10
209 const uint16_t altitude
= (constrain(getEstimatedAltitudeCm(), 0 * 100, 5000 * 100) / 100) + 1000; // constrain altitude from 0 to 5,000m
210 sbufWriteU16BigEndian(dst
, altitude
);
211 sbufWriteU8(dst
, gpsSol
.numSat
);
217 uint16_t Voltage ( mV * 100 )
218 uint16_t Current ( mA * 100 )
219 uint24_t Fuel ( drawn mAh )
220 uint8_t Battery remaining ( percent )
222 void crsfFrameBatterySensor(sbuf_t
*dst
)
224 // use sbufWrite since CRC does not include frame length
225 sbufWriteU8(dst
, CRSF_FRAME_BATTERY_SENSOR_PAYLOAD_SIZE
+ CRSF_FRAME_LENGTH_TYPE_CRC
);
226 sbufWriteU8(dst
, CRSF_FRAMETYPE_BATTERY_SENSOR
);
227 if (telemetryConfig()->report_cell_voltage
) {
228 sbufWriteU16BigEndian(dst
, (getBatteryAverageCellVoltage() + 5) / 10); // vbat is in units of 0.01V
230 sbufWriteU16BigEndian(dst
, getLegacyBatteryVoltage());
232 sbufWriteU16BigEndian(dst
, getAmperage() / 10);
233 const uint32_t mAhDrawn
= getMAhDrawn();
234 const uint8_t batteryRemainingPercentage
= calculateBatteryPercentageRemaining();
235 sbufWriteU8(dst
, (mAhDrawn
>> 16));
236 sbufWriteU8(dst
, (mAhDrawn
>> 8));
237 sbufWriteU8(dst
, (uint8_t)mAhDrawn
);
238 sbufWriteU8(dst
, batteryRemainingPercentage
);
242 CRSF_ACTIVE_ANTENNA1
= 0,
243 CRSF_ACTIVE_ANTENNA2
= 1
244 } crsfActiveAntenna_e
;
247 CRSF_RF_MODE_4_HZ
= 0,
248 CRSF_RF_MODE_50_HZ
= 1,
249 CRSF_RF_MODE_150_HZ
= 2
253 CRSF_RF_POWER_0_mW
= 0,
254 CRSF_RF_POWER_10_mW
= 1,
255 CRSF_RF_POWER_25_mW
= 2,
256 CRSF_RF_POWER_100_mW
= 3,
257 CRSF_RF_POWER_500_mW
= 4,
258 CRSF_RF_POWER_1000_mW
= 5,
259 CRSF_RF_POWER_2000_mW
= 6
265 int16_t Pitch angle ( rad / 10000 )
266 int16_t Roll angle ( rad / 10000 )
267 int16_t Yaw angle ( rad / 10000 )
270 #define DECIDEGREES_TO_RADIANS10000(angle) ((int16_t)(1000.0f * (angle) * RAD))
272 void crsfFrameAttitude(sbuf_t
*dst
)
274 sbufWriteU8(dst
, CRSF_FRAME_ATTITUDE_PAYLOAD_SIZE
+ CRSF_FRAME_LENGTH_TYPE_CRC
);
275 sbufWriteU8(dst
, CRSF_FRAMETYPE_ATTITUDE
);
276 sbufWriteU16BigEndian(dst
, DECIDEGREES_TO_RADIANS10000(attitude
.values
.pitch
));
277 sbufWriteU16BigEndian(dst
, DECIDEGREES_TO_RADIANS10000(attitude
.values
.roll
));
278 sbufWriteU16BigEndian(dst
, DECIDEGREES_TO_RADIANS10000(attitude
.values
.yaw
));
282 0x21 Flight mode text based
284 char[] Flight mode ( Null terminated string )
286 void crsfFrameFlightMode(sbuf_t
*dst
)
288 // write zero for frame length, since we don't know it yet
289 uint8_t *lengthPtr
= sbufPtr(dst
);
291 sbufWriteU8(dst
, CRSF_FRAMETYPE_FLIGHT_MODE
);
293 // Acro is the default mode
294 const char *flightMode
= "ACRO";
296 // Modes that are only relevant when disarmed
297 if (!ARMING_FLAG(ARMED
) && isArmingDisabled()) {
302 if (!ARMING_FLAG(ARMED
) && featureIsEnabled(FEATURE_GPS
) && (!STATE(GPS_FIX
) || !STATE(GPS_FIX_HOME
))) {
303 flightMode
= "WAIT"; // Waiting for GPS lock
307 // Flight modes in decreasing order of importance
308 if (FLIGHT_MODE(FAILSAFE_MODE
)) {
310 } else if (FLIGHT_MODE(GPS_RESCUE_MODE
)) {
312 } else if (FLIGHT_MODE(PASSTHRU_MODE
)) {
314 } else if (FLIGHT_MODE(ANGLE_MODE
)) {
316 } else if (FLIGHT_MODE(HORIZON_MODE
)) {
318 } else if (airmodeIsEnabled()) {
322 sbufWriteString(dst
, flightMode
);
323 if (!ARMING_FLAG(ARMED
)) {
324 sbufWriteU8(dst
, '*');
326 sbufWriteU8(dst
, '\0'); // zero-terminate string
327 // write in the frame length
328 *lengthPtr
= sbufPtr(dst
) - lengthPtr
;
336 char[] Device Name ( Null terminated string )
340 uint8_t 255 (Max MSP Parameter)
341 uint8_t 0x01 (Parameter version 1)
343 void crsfFrameDeviceInfo(sbuf_t
*dst
) {
346 tfp_sprintf(buff
, "%s %s: %s", FC_FIRMWARE_NAME
, FC_VERSION_STRING
, systemConfig()->boardIdentifier
);
348 uint8_t *lengthPtr
= sbufPtr(dst
);
350 sbufWriteU8(dst
, CRSF_FRAMETYPE_DEVICE_INFO
);
351 sbufWriteU8(dst
, CRSF_ADDRESS_RADIO_TRANSMITTER
);
352 sbufWriteU8(dst
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
353 sbufWriteStringWithZeroTerminator(dst
, buff
);
354 for (unsigned int ii
=0; ii
<12; ii
++) {
355 sbufWriteU8(dst
, 0x00);
357 sbufWriteU8(dst
, CRSF_DEVICEINFO_PARAMETER_COUNT
);
358 sbufWriteU8(dst
, CRSF_DEVICEINFO_VERSION
);
359 *lengthPtr
= sbufPtr(dst
) - lengthPtr
;
363 #if defined(USE_CRSF_V3)
364 void crsfFrameSpeedNegotiationResponse(sbuf_t
*dst
, bool reply
)
367 uint8_t *lengthPtr
= sbufPtr(dst
);
369 sbufWriteU8(dst
, CRSF_FRAMETYPE_COMMAND
);
370 sbufWriteU8(dst
, CRSF_ADDRESS_CRSF_RECEIVER
);
371 sbufWriteU8(dst
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
372 sbufWriteU8(dst
, CRSF_COMMAND_SUBCMD_GENERAL
);
373 sbufWriteU8(dst
, CRSF_COMMAND_SUBCMD_GENERAL_CRSF_SPEED_RESPONSE
);
374 sbufWriteU8(dst
, crsfSpeed
.portID
);
375 sbufWriteU8(dst
, reply
);
376 crc8_poly_0xba_sbuf_append(dst
, &lengthPtr
[1]);
377 *lengthPtr
= sbufPtr(dst
) - lengthPtr
;
380 static void crsfProcessSpeedNegotiationCmd(uint8_t *frameStart
)
383 uint32_t newBaudrate
= frameStart
[2] << 24 | frameStart
[3] << 16 | frameStart
[4] << 8 | frameStart
[5];
385 for (ii
= 0; ii
< BAUD_COUNT
; ++ii
) {
386 if (newBaudrate
== baudRates
[ii
]) {
390 crsfSpeed
.portID
= frameStart
[1];
391 crsfSpeed
.index
= ii
;
394 void crsfScheduleSpeedNegotiationResponse(void)
396 crsfSpeed
.hasPendingReply
= true;
397 crsfSpeed
.isNewSpeedValid
= false;
400 void speedNegotiationProcess(uint32_t currentTime
)
402 if (!featureIsEnabled(FEATURE_TELEMETRY
) && getCrsfDesiredSpeed() == CRSF_BAUDRATE
) {
403 // to notify the RX to fall back to default baud rate by sending device info frame if telemetry is disabled
404 sbuf_t crsfPayloadBuf
;
405 sbuf_t
*dst
= &crsfPayloadBuf
;
406 crsfInitializeFrame(dst
);
407 crsfFrameDeviceInfo(dst
);
409 crsfRxSendTelemetryData();
411 if (crsfSpeed
.hasPendingReply
) {
412 bool found
= crsfSpeed
.index
< BAUD_COUNT
? true : false;
413 sbuf_t crsfSpeedNegotiationBuf
;
414 sbuf_t
*dst
= &crsfSpeedNegotiationBuf
;
415 crsfInitializeFrame(dst
);
416 crsfFrameSpeedNegotiationResponse(dst
, found
);
418 crsfRxSendTelemetryData();
419 crsfSpeed
.hasPendingReply
= false;
420 crsfSpeed
.isNewSpeedValid
= true;
421 crsfSpeed
.confirmationTime
= currentTime
;
423 } else if (crsfSpeed
.isNewSpeedValid
) {
424 if (currentTime
- crsfSpeed
.confirmationTime
>= 4000) {
425 // delay 4ms before applying the new baudrate
426 crsfRxUpdateBaudrate(getCrsfDesiredSpeed());
427 crsfSpeed
.isNewSpeedValid
= false;
428 isCrsfV3Running
= true;
436 #if defined(USE_CRSF_CMS_TELEMETRY)
437 #define CRSF_DISPLAYPORT_MAX_CHUNK_LENGTH 50
438 #define CRSF_DISPLAYPORT_BATCH_MAX 0x3F
439 #define CRSF_DISPLAYPORT_FIRST_CHUNK_MASK 0x80
440 #define CRSF_DISPLAYPORT_LAST_CHUNK_MASK 0x40
441 #define CRSF_DISPLAYPORT_SANITIZE_MASK 0x60
442 #define CRSF_RLE_CHAR_REPEATED_MASK 0x80
443 #define CRSF_RLE_MAX_RUN_LENGTH 256
444 #define CRSF_RLE_BATCH_SIZE 2
446 static uint16_t getRunLength(const void *start
, const void *end
)
448 uint8_t *cursor
= (uint8_t*)start
;
450 size_t runLength
= 0;
451 for (; cursor
!= end
; cursor
++) {
461 static void cRleEncodeStream(sbuf_t
*source
, sbuf_t
*dest
, uint8_t maxDestLen
)
463 const uint8_t *destEnd
= sbufPtr(dest
) + maxDestLen
;
464 while (sbufBytesRemaining(source
) && (sbufPtr(dest
) < destEnd
)) {
465 const uint8_t destRemaining
= destEnd
- sbufPtr(dest
);
466 const uint8_t *srcPtr
= sbufPtr(source
);
467 const uint16_t runLength
= getRunLength(srcPtr
, source
->end
);
470 c
|= CRSF_RLE_CHAR_REPEATED_MASK
;
471 const uint8_t fullBatches
= (runLength
/ CRSF_RLE_MAX_RUN_LENGTH
);
472 const uint8_t remainder
= (runLength
% CRSF_RLE_MAX_RUN_LENGTH
);
473 const uint8_t totalBatches
= fullBatches
+ (remainder
) ? 1 : 0;
474 if (destRemaining
>= totalBatches
* CRSF_RLE_BATCH_SIZE
) {
475 for (unsigned int i
=1; i
<=totalBatches
; i
++) {
476 const uint8_t batchLength
= (i
< totalBatches
) ? CRSF_RLE_MAX_RUN_LENGTH
: remainder
;
477 sbufWriteU8(dest
, c
);
478 sbufWriteU8(dest
, batchLength
);
480 sbufAdvance(source
, runLength
);
484 } else if (destRemaining
>= runLength
) {
485 sbufWriteU8(dest
, c
);
486 sbufAdvance(source
, runLength
);
491 static void crsfFrameDisplayPortChunk(sbuf_t
*dst
, sbuf_t
*src
, uint8_t batchId
, uint8_t idx
)
493 uint8_t *lengthPtr
= sbufPtr(dst
);
495 sbufWriteU8(dst
, CRSF_FRAMETYPE_DISPLAYPORT_CMD
);
496 sbufWriteU8(dst
, CRSF_ADDRESS_RADIO_TRANSMITTER
);
497 sbufWriteU8(dst
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
498 sbufWriteU8(dst
, CRSF_DISPLAYPORT_SUBCMD_UPDATE
);
499 uint8_t *metaPtr
= sbufPtr(dst
);
500 sbufWriteU8(dst
, batchId
);
501 sbufWriteU8(dst
, idx
);
502 cRleEncodeStream(src
, dst
, CRSF_DISPLAYPORT_MAX_CHUNK_LENGTH
);
504 *metaPtr
|= CRSF_DISPLAYPORT_FIRST_CHUNK_MASK
;
506 if (!sbufBytesRemaining(src
)) {
507 *metaPtr
|= CRSF_DISPLAYPORT_LAST_CHUNK_MASK
;
509 *lengthPtr
= sbufPtr(dst
) - lengthPtr
;
512 static void crsfFrameDisplayPortClear(sbuf_t
*dst
)
514 uint8_t *lengthPtr
= sbufPtr(dst
);
515 sbufWriteU8(dst
, CRSF_DISPLAY_PORT_COLS_MAX
+ CRSF_FRAME_LENGTH_EXT_TYPE_CRC
);
516 sbufWriteU8(dst
, CRSF_FRAMETYPE_DISPLAYPORT_CMD
);
517 sbufWriteU8(dst
, CRSF_ADDRESS_RADIO_TRANSMITTER
);
518 sbufWriteU8(dst
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
519 sbufWriteU8(dst
, CRSF_DISPLAYPORT_SUBCMD_CLEAR
);
520 *lengthPtr
= sbufPtr(dst
) - lengthPtr
;
525 // schedule array to decide how often each type of frame is sent
527 CRSF_FRAME_START_INDEX
= 0,
528 CRSF_FRAME_ATTITUDE_INDEX
= CRSF_FRAME_START_INDEX
,
529 CRSF_FRAME_BATTERY_SENSOR_INDEX
,
530 CRSF_FRAME_FLIGHT_MODE_INDEX
,
531 CRSF_FRAME_GPS_INDEX
,
532 CRSF_SCHEDULE_COUNT_MAX
533 } crsfFrameTypeIndex_e
;
535 static uint8_t crsfScheduleCount
;
536 static uint8_t crsfSchedule
[CRSF_SCHEDULE_COUNT_MAX
];
538 #if defined(USE_MSP_OVER_TELEMETRY)
540 static bool mspReplyPending
;
542 void crsfScheduleMspResponse(void)
544 mspReplyPending
= true;
547 void crsfSendMspResponse(uint8_t *payload
)
549 sbuf_t crsfPayloadBuf
;
550 sbuf_t
*dst
= &crsfPayloadBuf
;
552 crsfInitializeFrame(dst
);
553 sbufWriteU8(dst
, CRSF_FRAME_TX_MSP_FRAME_SIZE
+ CRSF_FRAME_LENGTH_EXT_TYPE_CRC
);
554 sbufWriteU8(dst
, CRSF_FRAMETYPE_MSP_RESP
);
555 sbufWriteU8(dst
, CRSF_ADDRESS_RADIO_TRANSMITTER
);
556 sbufWriteU8(dst
, CRSF_ADDRESS_FLIGHT_CONTROLLER
);
557 sbufWriteData(dst
, payload
, CRSF_FRAME_TX_MSP_FRAME_SIZE
);
562 static void processCrsf(void)
564 static uint8_t crsfScheduleIndex
= 0;
566 const uint8_t currentSchedule
= crsfSchedule
[crsfScheduleIndex
];
568 sbuf_t crsfPayloadBuf
;
569 sbuf_t
*dst
= &crsfPayloadBuf
;
571 if (currentSchedule
& BIT(CRSF_FRAME_ATTITUDE_INDEX
)) {
572 crsfInitializeFrame(dst
);
573 crsfFrameAttitude(dst
);
576 if (currentSchedule
& BIT(CRSF_FRAME_BATTERY_SENSOR_INDEX
)) {
577 crsfInitializeFrame(dst
);
578 crsfFrameBatterySensor(dst
);
582 if (currentSchedule
& BIT(CRSF_FRAME_FLIGHT_MODE_INDEX
)) {
583 crsfInitializeFrame(dst
);
584 crsfFrameFlightMode(dst
);
588 if (currentSchedule
& BIT(CRSF_FRAME_GPS_INDEX
)) {
589 crsfInitializeFrame(dst
);
594 crsfScheduleIndex
= (crsfScheduleIndex
+ 1) % crsfScheduleCount
;
597 void crsfScheduleDeviceInfoResponse(void)
599 deviceInfoReplyPending
= true;
603 void initCrsfTelemetry(void)
605 // check if there is a serial port open for CRSF telemetry (ie opened by the CRSF RX)
606 // and feature is enabled, if so, set CRSF telemetry enabled
607 crsfTelemetryEnabled
= crsfRxIsActive();
609 if (!crsfTelemetryEnabled
) {
613 deviceInfoReplyPending
= false;
614 #if defined(USE_MSP_OVER_TELEMETRY)
615 mspReplyPending
= false;
619 if (sensors(SENSOR_ACC
) && telemetryIsSensorEnabled(SENSOR_PITCH
| SENSOR_ROLL
| SENSOR_HEADING
)) {
620 crsfSchedule
[index
++] = BIT(CRSF_FRAME_ATTITUDE_INDEX
);
622 if ((isBatteryVoltageConfigured() && telemetryIsSensorEnabled(SENSOR_VOLTAGE
))
623 || (isAmperageConfigured() && telemetryIsSensorEnabled(SENSOR_CURRENT
| SENSOR_FUEL
))) {
624 crsfSchedule
[index
++] = BIT(CRSF_FRAME_BATTERY_SENSOR_INDEX
);
626 if (telemetryIsSensorEnabled(SENSOR_MODE
)) {
627 crsfSchedule
[index
++] = BIT(CRSF_FRAME_FLIGHT_MODE_INDEX
);
630 if (featureIsEnabled(FEATURE_GPS
)
631 && telemetryIsSensorEnabled(SENSOR_ALTITUDE
| SENSOR_LAT_LONG
| SENSOR_GROUND_SPEED
| SENSOR_HEADING
)) {
632 crsfSchedule
[index
++] = BIT(CRSF_FRAME_GPS_INDEX
);
635 crsfScheduleCount
= (uint8_t)index
;
637 #if defined(USE_CRSF_CMS_TELEMETRY)
638 crsfDisplayportRegister();
642 bool checkCrsfTelemetryState(void)
644 return crsfTelemetryEnabled
;
647 #if defined(USE_CRSF_CMS_TELEMETRY)
648 void crsfProcessDisplayPortCmd(uint8_t *frameStart
)
650 uint8_t cmd
= *frameStart
;
652 case CRSF_DISPLAYPORT_SUBCMD_OPEN
: ;
653 const uint8_t rows
= *(frameStart
+ CRSF_DISPLAYPORT_OPEN_ROWS_OFFSET
);
654 const uint8_t cols
= *(frameStart
+ CRSF_DISPLAYPORT_OPEN_COLS_OFFSET
);
655 crsfDisplayPortSetDimensions(rows
, cols
);
656 crsfDisplayPortMenuOpen();
658 case CRSF_DISPLAYPORT_SUBCMD_CLOSE
:
659 crsfDisplayPortMenuExit();
661 case CRSF_DISPLAYPORT_SUBCMD_POLL
:
662 crsfDisplayPortRefresh();
672 #if defined(USE_CRSF_V3)
673 void crsfProcessCommand(uint8_t *frameStart
)
675 uint8_t cmd
= *frameStart
;
676 uint8_t subCmd
= frameStart
[1];
678 case CRSF_COMMAND_SUBCMD_GENERAL
:
680 case CRSF_COMMAND_SUBCMD_GENERAL_CRSF_SPEED_PROPOSAL
:
681 crsfProcessSpeedNegotiationCmd(&frameStart
[1]);
682 crsfScheduleSpeedNegotiationResponse();
695 * Called periodically by the scheduler
697 void handleCrsfTelemetry(timeUs_t currentTimeUs
)
699 static uint32_t crsfLastCycleTime
;
701 if (!crsfTelemetryEnabled
) {
704 // Give the receiver a chance to send any outstanding telemetry data.
705 // This needs to be done at high frequency, to enable the RX to send the telemetry frame
706 // in between the RX frames.
707 crsfRxSendTelemetryData();
709 // Send ad-hoc response frames as soon as possible
710 #if defined(USE_MSP_OVER_TELEMETRY)
711 if (mspReplyPending
) {
712 mspReplyPending
= handleCrsfMspFrameBuffer(CRSF_FRAME_TX_MSP_FRAME_SIZE
, &crsfSendMspResponse
);
713 crsfLastCycleTime
= currentTimeUs
; // reset telemetry timing due to ad-hoc request
718 if (deviceInfoReplyPending
) {
719 sbuf_t crsfPayloadBuf
;
720 sbuf_t
*dst
= &crsfPayloadBuf
;
721 crsfInitializeFrame(dst
);
722 crsfFrameDeviceInfo(dst
);
724 deviceInfoReplyPending
= false;
725 crsfLastCycleTime
= currentTimeUs
; // reset telemetry timing due to ad-hoc request
729 #if defined(USE_CRSF_CMS_TELEMETRY)
730 if (crsfDisplayPortScreen()->reset
) {
731 crsfDisplayPortScreen()->reset
= false;
732 sbuf_t crsfDisplayPortBuf
;
733 sbuf_t
*dst
= &crsfDisplayPortBuf
;
734 crsfInitializeFrame(dst
);
735 crsfFrameDisplayPortClear(dst
);
737 crsfLastCycleTime
= currentTimeUs
;
740 static uint8_t displayPortBatchId
= 0;
741 if (crsfDisplayPortIsReady() && crsfDisplayPortScreen()->updated
) {
742 crsfDisplayPortScreen()->updated
= false;
743 uint16_t screenSize
= crsfDisplayPortScreen()->rows
* crsfDisplayPortScreen()->cols
;
744 uint8_t *srcStart
= (uint8_t*)crsfDisplayPortScreen()->buffer
;
745 uint8_t *srcEnd
= (uint8_t*)(crsfDisplayPortScreen()->buffer
+ screenSize
);
746 sbuf_t displayPortSbuf
;
747 sbuf_t
*src
= sbufInit(&displayPortSbuf
, srcStart
, srcEnd
);
748 sbuf_t crsfDisplayPortBuf
;
749 sbuf_t
*dst
= &crsfDisplayPortBuf
;
750 displayPortBatchId
= (displayPortBatchId
+ 1) % CRSF_DISPLAYPORT_BATCH_MAX
;
752 while(sbufBytesRemaining(src
)) {
753 crsfInitializeFrame(dst
);
754 crsfFrameDisplayPortChunk(dst
, src
, displayPortBatchId
, i
);
756 crsfRxSendTelemetryData();
759 crsfLastCycleTime
= currentTimeUs
;
764 // Actual telemetry data only needs to be sent at a low frequency, ie 10Hz
765 // Spread out scheduled frames evenly so each frame is sent at the same frequency.
766 if (currentTimeUs
>= crsfLastCycleTime
+ (CRSF_CYCLETIME_US
/ crsfScheduleCount
)) {
767 crsfLastCycleTime
= currentTimeUs
;
772 #if defined(UNIT_TEST)
773 static int crsfFinalizeBuf(sbuf_t
*dst
, uint8_t *frame
)
775 crc8_dvb_s2_sbuf_append(dst
, &crsfFrame
[2]); // start at byte 2, since CRC does not include device address and frame length
776 sbufSwitchToReader(dst
, crsfFrame
);
777 const int frameSize
= sbufBytesRemaining(dst
);
778 for (int ii
= 0; sbufBytesRemaining(dst
); ++ii
) {
779 frame
[ii
] = sbufReadU8(dst
);
784 STATIC_UNIT_TESTED
int getCrsfFrame(uint8_t *frame
, crsfFrameType_e frameType
)
787 sbuf_t
*sbuf
= &crsfFrameBuf
;
789 crsfInitializeFrame(sbuf
);
792 case CRSF_FRAMETYPE_ATTITUDE
:
793 crsfFrameAttitude(sbuf
);
795 case CRSF_FRAMETYPE_BATTERY_SENSOR
:
796 crsfFrameBatterySensor(sbuf
);
798 case CRSF_FRAMETYPE_FLIGHT_MODE
:
799 crsfFrameFlightMode(sbuf
);
802 case CRSF_FRAMETYPE_GPS
:
807 const int frameSize
= crsfFinalizeBuf(sbuf
, frame
);