Dshot RPM Telemetry Refactoring (#13012)
[betaflight.git] / src / main / telemetry / srxl.c
blob947e39207cf839f81ae177d5da5483ba0ac19fd4
1 /*
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)
8 * any later version.
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/>.
21 #include <math.h>
22 #include <stdbool.h>
23 #include <stdint.h>
24 #include <stdlib.h>
25 #include <string.h>
27 #include "platform.h"
29 #if defined(USE_TELEMETRY_SRXL)
31 #include "build/version.h"
33 #include "cms/cms.h"
35 #include "common/crc.h"
36 #include "common/streambuf.h"
37 #include "common/utils.h"
39 #include "config/config.h"
40 #include "config/feature.h"
42 #include "drivers/dshot.h"
43 #include "drivers/vtx_common.h"
45 #include "fc/rc_controls.h"
46 #include "fc/runtime_config.h"
48 #include "flight/imu.h"
49 #include "flight/mixer.h"
51 #include "io/displayport_srxl.h"
52 #include "io/gps.h"
53 #include "io/serial.h"
54 #include "io/vtx_smartaudio.h"
55 #include "io/vtx_tramp.h"
57 #include "pg/rx.h"
58 #include "pg/motor.h"
60 #include "rx/rx.h"
61 #include "rx/spektrum.h"
62 #include "io/spektrum_vtx_control.h"
63 #include "rx/srxl2.h"
65 #include "sensors/adcinternal.h"
66 #include "sensors/battery.h"
67 #include "sensors/esc_sensor.h"
69 #include "telemetry/telemetry.h"
71 #include "srxl.h"
73 #define SRXL_ADDRESS_FIRST 0xA5
74 #define SRXL_ADDRESS_SECOND 0x80
75 #define SRXL_PACKET_LENGTH 0x15
77 #define SRXL_FRAMETYPE_TELE_QOS 0x7F
78 #define SRXL_FRAMETYPE_TELE_RPM 0x7E
79 #define SRXL_FRAMETYPE_POWERBOX 0x0A
80 #define SRXL_FRAMETYPE_TELE_FP_MAH 0x34
81 #define TELE_DEVICE_VTX 0x0D // Video Transmitter Status
82 #define SRXL_FRAMETYPE_SID 0x00
83 #define SRXL_FRAMETYPE_GPS_LOC 0x16 // GPS Location Data (Eagle Tree)
84 #define SRXL_FRAMETYPE_GPS_STAT 0x17
86 static bool srxlTelemetryEnabled;
87 static bool srxl2 = false;
88 static uint8_t srxlFrame[SRXL_FRAME_SIZE_MAX];
90 static void srxlInitializeFrame(sbuf_t *dst)
92 if (srxl2) {
93 #if defined(USE_SERIALRX_SRXL2)
94 srxl2InitializeFrame(dst);
95 #endif
96 } else {
97 dst->ptr = srxlFrame;
98 dst->end = ARRAYEND(srxlFrame);
100 sbufWriteU8(dst, SRXL_ADDRESS_FIRST);
101 sbufWriteU8(dst, SRXL_ADDRESS_SECOND);
102 sbufWriteU8(dst, SRXL_PACKET_LENGTH);
106 static void srxlFinalize(sbuf_t *dst)
108 if (srxl2) {
109 #if defined(USE_SERIALRX_SRXL2)
110 srxl2FinalizeFrame(dst);
111 #endif
112 } else {
113 crc16_ccitt_sbuf_append(dst, &srxlFrame[3]); // start at byte 3, since CRC does not include device address and packet length
114 sbufSwitchToReader(dst, srxlFrame);
115 // write the telemetry frame to the receiver.
116 srxlRxWriteTelemetryData(sbufPtr(dst), sbufBytesRemaining(dst));
121 SRXL frame has the structure:
122 <0xA5><0x80><Length><16-byte telemetry packet><2 Byte CRC of payload>
123 The <Length> shall be 0x15 (length of the 16-byte telemetry packet + overhead).
127 typedef struct
129 UINT8 identifier; // Source device = 0x7F
130 UINT8 sID; // Secondary ID
131 UINT16 A;
132 UINT16 B;
133 UINT16 L;
134 UINT16 R;
135 UINT16 F;
136 UINT16 H;
137 UINT16 rxVoltage; // Volts, 0.01V increments
138 } STRU_TELE_QOS;
141 #define STRU_TELE_QOS_EMPTY_FIELDS_COUNT 14
142 #define STRU_TELE_QOS_EMPTY_FIELDS_VALUE 0xff
144 bool srxlFrameQos(sbuf_t *dst, timeUs_t currentTimeUs)
146 UNUSED(currentTimeUs);
148 sbufWriteU8(dst, SRXL_FRAMETYPE_TELE_QOS);
149 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
151 sbufFill(dst, STRU_TELE_QOS_EMPTY_FIELDS_VALUE, STRU_TELE_QOS_EMPTY_FIELDS_COUNT); // Clear remainder
153 // Mandatory frame, send it unconditionally.
154 return true;
159 typedef struct
161 UINT8 identifier; // Source device = 0x7E
162 UINT8 sID; // Secondary ID
163 UINT16 microseconds; // microseconds between pulse leading edges
164 UINT16 volts; // 0.01V increments
165 INT16 temperature; // degrees F
166 INT8 dBm_A, // Average signal for A antenna in dBm
167 INT8 dBm_B; // Average signal for B antenna in dBm.
168 // If only 1 antenna, set B = A
169 } STRU_TELE_RPM;
172 #define STRU_TELE_RPM_EMPTY_FIELDS_COUNT 8
173 #define STRU_TELE_RPM_EMPTY_FIELDS_VALUE 0xff
175 #define SPEKTRUM_RPM_UNUSED 0xffff
176 #define SPEKTRUM_TEMP_UNUSED 0x7fff
177 #define MICROSEC_PER_MINUTE 60000000
179 //Original range of 1 - 65534 uSec gives an RPM range of 915 - 60000000rpm, 60MegaRPM
180 #define SPEKTRUM_MIN_RPM 999 // Min RPM to show the user, indicating RPM is really below 999
181 #define SPEKTRUM_MAX_RPM 60000000
183 uint16_t getMotorAveragePeriod(void)
186 #if defined( USE_ESC_SENSOR_TELEMETRY) || defined( USE_DSHOT_TELEMETRY)
187 uint32_t rpm = 0;
188 uint16_t period_us = SPEKTRUM_RPM_UNUSED;
190 #if defined( USE_ESC_SENSOR_TELEMETRY)
191 escSensorData_t *escData = getEscSensorData(ESC_SENSOR_COMBINED);
192 if (escData != NULL) {
193 rpm = escData->rpm;
195 #endif
197 #if defined(USE_DSHOT_TELEMETRY)
198 // Calculate this way when no rpm from esc data
199 if (useDshotTelemetry && rpm == 0) {
200 rpm = lrintf(getDshotRpmAverage());
202 #endif
204 if (rpm > SPEKTRUM_MIN_RPM && rpm < SPEKTRUM_MAX_RPM) {
205 period_us = MICROSEC_PER_MINUTE / rpm; // revs/minute -> microSeconds
206 } else {
207 period_us = MICROSEC_PER_MINUTE / SPEKTRUM_MIN_RPM;
210 return period_us;
211 #else
212 return SPEKTRUM_RPM_UNUSED;
213 #endif
216 bool srxlFrameRpm(sbuf_t *dst, timeUs_t currentTimeUs)
218 int16_t coreTemp = SPEKTRUM_TEMP_UNUSED;
219 #if defined(USE_ADC_INTERNAL)
220 coreTemp = getCoreTemperatureCelsius();
221 coreTemp = coreTemp * 9 / 5 + 32; // C -> F
222 #endif
224 UNUSED(currentTimeUs);
226 sbufWriteU8(dst, SRXL_FRAMETYPE_TELE_RPM);
227 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
228 sbufWriteU16BigEndian(dst, getMotorAveragePeriod()); // pulse leading edges
229 if (telemetryConfig()->report_cell_voltage) {
230 sbufWriteU16BigEndian(dst, getBatteryAverageCellVoltage()); // Cell voltage is in units of 0.01V
231 } else {
232 sbufWriteU16BigEndian(dst, getBatteryVoltage()); // vbat is in units of 0.01V
234 sbufWriteU16BigEndian(dst, coreTemp); // temperature
235 sbufFill(dst, STRU_TELE_RPM_EMPTY_FIELDS_VALUE, STRU_TELE_RPM_EMPTY_FIELDS_COUNT);
237 // Mandatory frame, send it unconditionally.
238 return true;
241 #if defined(USE_GPS)
243 // From Frsky implementation
244 static void GPStoDDDMM_MMMM(int32_t mwiigps, gpsCoordinateDDDMMmmmm_t *result)
246 int32_t absgps, deg, min;
247 absgps = abs(mwiigps);
248 deg = absgps / GPS_DEGREES_DIVIDER;
249 absgps = (absgps - deg * GPS_DEGREES_DIVIDER) * 60; // absgps = Minutes left * 10^7
250 min = absgps / GPS_DEGREES_DIVIDER; // minutes left
251 result->dddmm = deg * 100 + min;
252 result->mmmm = (absgps - min * GPS_DEGREES_DIVIDER) / 1000;
255 // BCD conversion
256 static uint32_t dec2bcd(uint16_t dec)
258 uint32_t result = 0;
259 uint8_t counter = 0;
261 while (dec) {
262 result |= (dec % 10) << counter * 4;
263 counter++;
264 dec /= 10;
266 return result;
270 typedef struct
272 UINT8 identifier; // Source device = 0x16
273 UINT8 sID; // Secondary ID
274 UINT16 altitudeLow; // BCD, meters, format 3.1 (Low order of altitude)
275 UINT32 latitude; // BCD, format 4.4, Degrees * 100 + minutes, less than 100 degrees
276 UINT32 longitude; // BCD, format 4.4 , Degrees * 100 + minutes, flag indicates > 99 degrees
277 UINT16 course; // BCD, 3.1
278 UINT8 HDOP; // BCD, format 1.1
279 UINT8 GPSflags; // see definitions below
280 } STRU_TELE_GPS_LOC;
283 // GPS flags definitions
284 #define GPS_FLAGS_IS_NORTH_BIT 0x01
285 #define GPS_FLAGS_IS_EAST_BIT 0x02
286 #define GPS_FLAGS_LONGITUDE_GREATER_99_BIT 0x04
287 #define GPS_FLAGS_GPS_FIX_VALID_BIT 0x08
288 #define GPS_FLAGS_GPS_DATA_RECEIVED_BIT 0x10
289 #define GPS_FLAGS_3D_FIX_BIT 0x20
290 #define GPS_FLAGS_NEGATIVE_ALT_BIT 0x80
292 bool srxlFrameGpsLoc(sbuf_t *dst, timeUs_t currentTimeUs)
294 UNUSED(currentTimeUs);
295 gpsCoordinateDDDMMmmmm_t coordinate;
296 uint32_t latitudeBcd, longitudeBcd, altitudeLo;
297 uint16_t altitudeLoBcd, groundCourseBcd, hdop;
298 uint8_t hdopBcd, gpsFlags;
300 if (!featureIsEnabled(FEATURE_GPS) || !STATE(GPS_FIX) || gpsSol.numSat < GPS_MIN_SAT_COUNT) {
301 return false;
304 // lattitude
305 GPStoDDDMM_MMMM(gpsSol.llh.lat, &coordinate);
306 latitudeBcd = (dec2bcd(coordinate.dddmm) << 16) | dec2bcd(coordinate.mmmm);
308 // longitude
309 GPStoDDDMM_MMMM(gpsSol.llh.lon, &coordinate);
310 longitudeBcd = (dec2bcd(coordinate.dddmm) << 16) | dec2bcd(coordinate.mmmm);
312 // altitude (low order)
313 altitudeLo = abs(gpsSol.llh.altCm) / 10;
314 altitudeLoBcd = dec2bcd(altitudeLo % 100000);
316 // Ground course
317 groundCourseBcd = dec2bcd(gpsSol.groundCourse);
319 // HDOP
320 hdop = gpsSol.dop.hdop / 10;
321 hdop = (hdop > 99) ? 99 : hdop;
322 hdopBcd = dec2bcd(hdop);
324 // flags
325 gpsFlags = GPS_FLAGS_GPS_DATA_RECEIVED_BIT | GPS_FLAGS_GPS_FIX_VALID_BIT | GPS_FLAGS_3D_FIX_BIT;
326 gpsFlags |= (gpsSol.llh.lat > 0) ? GPS_FLAGS_IS_NORTH_BIT : 0;
327 gpsFlags |= (gpsSol.llh.lon > 0) ? GPS_FLAGS_IS_EAST_BIT : 0;
328 gpsFlags |= (gpsSol.llh.altCm < 0) ? GPS_FLAGS_NEGATIVE_ALT_BIT : 0;
329 gpsFlags |= (gpsSol.llh.lon / GPS_DEGREES_DIVIDER > 99) ? GPS_FLAGS_LONGITUDE_GREATER_99_BIT : 0;
331 // SRXL frame
332 sbufWriteU8(dst, SRXL_FRAMETYPE_GPS_LOC);
333 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
334 sbufWriteU16(dst, altitudeLoBcd);
335 sbufWriteU32(dst, latitudeBcd);
336 sbufWriteU32(dst, longitudeBcd);
337 sbufWriteU16(dst, groundCourseBcd);
338 sbufWriteU8(dst, hdopBcd);
339 sbufWriteU8(dst, gpsFlags);
341 return true;
345 typedef struct
347 UINT8 identifier; // Source device = 0x17
348 UINT8 sID; // Secondary ID
349 UINT16 speed; // BCD, knots, format 3.1
350 UINT32 UTC; // BCD, format HH:MM:SS.S, format 6.1
351 UINT8 numSats; // BCD, 0-99
352 UINT8 altitudeHigh; // BCD, meters, format 2.0 (High bits alt)
353 } STRU_TELE_GPS_STAT;
356 #define STRU_TELE_GPS_STAT_EMPTY_FIELDS_VALUE 0xff
357 #define STRU_TELE_GPS_STAT_EMPTY_FIELDS_COUNT 6
358 #define SPEKTRUM_TIME_UNKNOWN 0xFFFFFFFF
360 bool srxlFrameGpsStat(sbuf_t *dst, timeUs_t currentTimeUs)
362 UNUSED(currentTimeUs);
363 uint32_t timeBcd;
364 uint16_t speedKnotsBcd, speedTmp;
365 uint8_t numSatBcd, altitudeHighBcd;
366 bool timeProvided = false;
368 if (!featureIsEnabled(FEATURE_GPS) || !STATE(GPS_FIX) || gpsSol.numSat < GPS_MIN_SAT_COUNT) {
369 return false;
372 // Number of sats and altitude (high bits)
373 numSatBcd = (gpsSol.numSat > 99) ? dec2bcd(99) : dec2bcd(gpsSol.numSat);
374 altitudeHighBcd = dec2bcd(gpsSol.llh.altCm / 100000);
376 // Speed (knots)
377 speedTmp = gpsSol.groundSpeed * 1944 / 1000;
378 speedKnotsBcd = (speedTmp > 9999) ? dec2bcd(9999) : dec2bcd(speedTmp);
380 #ifdef USE_RTC_TIME
381 dateTime_t dt;
382 // RTC
383 if (rtcHasTime()) {
384 rtcGetDateTime(&dt);
385 timeBcd = dec2bcd(dt.hours);
386 timeBcd = timeBcd << 8;
387 timeBcd = timeBcd | dec2bcd(dt.minutes);
388 timeBcd = timeBcd << 8;
389 timeBcd = timeBcd | dec2bcd(dt.seconds);
390 timeBcd = timeBcd << 4;
391 timeBcd = timeBcd | dec2bcd(dt.millis / 100);
392 timeProvided = true;
394 #endif
395 timeBcd = (timeProvided) ? timeBcd : SPEKTRUM_TIME_UNKNOWN;
397 // SRXL frame
398 sbufWriteU8(dst, SRXL_FRAMETYPE_GPS_STAT);
399 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
400 sbufWriteU16(dst, speedKnotsBcd);
401 sbufWriteU32(dst, timeBcd);
402 sbufWriteU8(dst, numSatBcd);
403 sbufWriteU8(dst, altitudeHighBcd);
404 sbufFill(dst, STRU_TELE_GPS_STAT_EMPTY_FIELDS_VALUE, STRU_TELE_GPS_STAT_EMPTY_FIELDS_COUNT);
406 return true;
409 #endif
412 typedef struct
414 UINT8 identifier; // Source device = 0x34
415 UINT8 sID; // Secondary ID
416 INT16 current_A; // Instantaneous current, 0.1A (0-3276.8A)
417 INT16 chargeUsed_A; // Integrated mAh used, 1mAh (0-32.766Ah)
418 UINT16 temp_A; // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
419 INT16 current_B; // Instantaneous current, 0.1A (0-3276.8A)
420 INT16 chargeUsed_B; // Integrated mAh used, 1mAh (0-32.766Ah)
421 UINT16 temp_B; // Temperature, 0.1C (0-150C, 0x7FFF indicates not populated)
422 UINT16 spare; // Not used
423 } STRU_TELE_FP_MAH;
425 #define STRU_TELE_FP_EMPTY_FIELDS_COUNT 2
426 #define STRU_TELE_FP_EMPTY_FIELDS_VALUE 0xff
428 #define SPEKTRUM_AMPS_UNUSED 0x7fff
429 #define SPEKTRUM_AMPH_UNUSED 0x7fff
431 #define FP_MAH_KEEPALIVE_TIME_OUT 2000000 // 2s
433 bool srxlFrameFlightPackCurrent(sbuf_t *dst, timeUs_t currentTimeUs)
435 uint16_t amps = getAmperage() / 10;
436 uint16_t mah = getMAhDrawn();
437 static uint16_t sentAmps;
438 static uint16_t sentMah;
439 static timeUs_t lastTimeSentFPmAh = 0;
441 timeUs_t keepAlive = currentTimeUs - lastTimeSentFPmAh;
443 if ( amps != sentAmps ||
444 mah != sentMah ||
445 keepAlive > FP_MAH_KEEPALIVE_TIME_OUT ) {
447 sbufWriteU8(dst, SRXL_FRAMETYPE_TELE_FP_MAH);
448 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
449 sbufWriteU16(dst, amps);
450 sbufWriteU16(dst, mah);
451 sbufWriteU16(dst, SPEKTRUM_TEMP_UNUSED); // temp A
452 sbufWriteU16(dst, SPEKTRUM_AMPS_UNUSED); // Amps B
453 sbufWriteU16(dst, SPEKTRUM_AMPH_UNUSED); // mAH B
454 sbufWriteU16(dst, SPEKTRUM_TEMP_UNUSED); // temp B
456 sbufFill(dst, STRU_TELE_FP_EMPTY_FIELDS_VALUE, STRU_TELE_FP_EMPTY_FIELDS_COUNT);
458 sentAmps = amps;
459 sentMah = mah;
460 lastTimeSentFPmAh = currentTimeUs;
461 return true;
463 return false;
466 #if defined(USE_SPEKTRUM_CMS_TELEMETRY) && defined(USE_CMS)
468 // Betaflight CMS using Spektrum Tx telemetry TEXT_GEN sensor as display.
470 #define SPEKTRUM_SRXL_DEVICE_TEXTGEN (0x0C) // Text Generator
471 #define SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS (9) // Text Generator ROWS
472 #define SPEKTRUM_SRXL_DEVICE_TEXTGEN_COLS (13) // Text Generator COLS
475 typedef struct
477 UINT8 identifier;
478 UINT8 sID; // Secondary ID
479 UINT8 lineNumber; // Line number to display (0 = title, 1-8 for general, 254 = Refresh backlight, 255 = Erase all text on screen)
480 char text[13]; // 0-terminated text when < 13 chars
481 } STRU_SPEKTRUM_SRXL_TEXTGEN;
484 #if ( SPEKTRUM_SRXL_TEXTGEN_BUFFER_COLS > SPEKTRUM_SRXL_DEVICE_TEXTGEN_COLS )
485 static char srxlTextBuff[SPEKTRUM_SRXL_TEXTGEN_BUFFER_ROWS][SPEKTRUM_SRXL_TEXTGEN_BUFFER_COLS];
486 static bool lineSent[SPEKTRUM_SRXL_TEXTGEN_BUFFER_ROWS];
487 #else
488 static char srxlTextBuff[SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS][SPEKTRUM_SRXL_DEVICE_TEXTGEN_COLS];
489 static bool lineSent[SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS];
490 #endif
492 //**************************************************************************
493 // API Running in external client task context. E.g. in the CMS task
494 int spektrumTmTextGenPutChar(uint8_t col, uint8_t row, char c)
496 if (row < SPEKTRUM_SRXL_TEXTGEN_BUFFER_ROWS && col < SPEKTRUM_SRXL_TEXTGEN_BUFFER_COLS) {
497 // Only update and force a tm transmision if something has actually changed.
498 if (srxlTextBuff[row][col] != c) {
499 srxlTextBuff[row][col] = c;
500 lineSent[row] = false;
503 return 0;
505 //**************************************************************************
507 bool srxlFrameText(sbuf_t *dst, timeUs_t currentTimeUs)
509 UNUSED(currentTimeUs);
510 static uint8_t lineNo = 0;
511 int lineCount = 0;
513 // Skip already sent lines...
514 while (lineSent[lineNo] &&
515 lineCount < SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS) {
516 lineNo = (lineNo + 1) % SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS;
517 lineCount++;
520 sbufWriteU8(dst, SPEKTRUM_SRXL_DEVICE_TEXTGEN);
521 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
522 sbufWriteU8(dst, lineNo);
523 sbufWriteData(dst, srxlTextBuff[lineNo], SPEKTRUM_SRXL_DEVICE_TEXTGEN_COLS);
525 lineSent[lineNo] = true;
526 lineNo = (lineNo + 1) % SPEKTRUM_SRXL_DEVICE_TEXTGEN_ROWS;
528 // Always send something, Always one user frame after the two mandatory frames
529 // I.e. All of the three frame prep routines QOS, RPM, TEXT should always return true
530 // too keep the "Waltz" sequence intact.
531 return true;
533 #endif
535 #if defined(USE_SPEKTRUM_VTX_TELEMETRY) && defined(USE_SPEKTRUM_VTX_CONTROL) && defined(USE_VTX_COMMON)
537 static uint8_t vtxDeviceType;
539 static void collectVtxTmData(spektrumVtx_t * vtx)
541 const vtxDevice_t *vtxDevice = vtxCommonDevice();
542 vtxDeviceType = vtxCommonGetDeviceType(vtxDevice);
544 // Collect all data from VTX, if VTX is ready
545 unsigned vtxStatus;
546 if (vtxDevice == NULL || !(vtxCommonGetBandAndChannel(vtxDevice, &vtx->band, &vtx->channel) &&
547 vtxCommonGetStatus(vtxDevice, &vtxStatus) &&
548 vtxCommonGetPowerIndex(vtxDevice, &vtx->power)) )
550 vtx->band = 0;
551 vtx->channel = 0;
552 vtx->power = 0;
553 vtx->pitMode = 0;
554 } else {
555 vtx->pitMode = (vtxStatus & VTX_STATUS_PIT_MODE) ? 1 : 0;
558 vtx->powerValue = 0;
559 #ifdef USE_SPEKTRUM_REGION_CODES
560 vtx->region = SpektrumRegion;
561 #else
562 vtx->region = SPEKTRUM_VTX_REGION_NONE;
563 #endif
566 // Reverse lookup, device power index to Spektrum power range index.
567 static void convertVtxPower(spektrumVtx_t * vtx)
569 uint8_t const * powerIndexTable = NULL;
571 vtxCommonLookupPowerValue(vtxCommonDevice(), vtx->power, &vtx->powerValue);
572 switch (vtxDeviceType) {
574 #if defined(USE_VTX_TRAMP)
575 case VTXDEV_TRAMP:
576 powerIndexTable = vtxTrampPi;
577 break;
578 #endif
579 #if defined(USE_VTX_SMARTAUDIO)
580 case VTXDEV_SMARTAUDIO:
581 powerIndexTable = vtxSaPi;
582 break;
583 #endif
584 #if defined(USE_VTX_RTC6705)
585 case VTXDEV_RTC6705:
586 powerIndexTable = vtxRTC6705Pi;
587 break;
588 #endif
590 case VTXDEV_UNKNOWN:
591 case VTXDEV_UNSUPPORTED:
592 default:
593 break;
597 if (powerIndexTable != NULL) {
598 for (int i = 0; i < SPEKTRUM_VTX_POWER_COUNT; i++)
599 if (powerIndexTable[i] >= vtx->power) {
600 vtx->power = i; // Translate device power index to Spektrum power index.
601 break;
606 static void convertVtxTmData(spektrumVtx_t * vtx)
608 // Convert from internal band indexes to Spektrum indexes
609 for (int i = 0; i < SPEKTRUM_VTX_BAND_COUNT; i++) {
610 if (spek2commonBand[i] == vtx->band) {
611 vtx->band = i;
612 break;
616 // De-bump channel no 1 based interally, 0-based in Spektrum.
617 vtx->channel--;
619 // Convert Power index to Spektrum ranges, different per brand.
620 convertVtxPower(vtx);
624 typedef struct
626 UINT8 identifier;
627 UINT8 sID; // Secondary ID
628 UINT8 band; // VTX Band (0 = Fatshark, 1 = Raceband, 2 = E, 3 = B, 4 = A, 5-7 = Reserved)
629 UINT8 channel; // VTX Channel (0-7)
630 UINT8 pit; // Pit/Race mode (0 = Race, 1 = Pit). Race = (normal operating) mode. Pit = (reduced power) mode. When PIT is set, it overrides all other power settings
631 UINT8 power; // VTX Power (0 = Off, 1 = 1mw to 14mW, 2 = 15mW to 25mW, 3 = 26mW to 99mW, 4 = 100mW to 299mW, 5 = 300mW to 600mW, 6 = 601mW+, 7 = manual control)
632 UINT16 powerDec; // VTX Power as a decimal 1mw/unit
633 UINT8 region; // Region (0 = USA, 1 = EU, 0xFF = N/A)
634 UINT8 rfu[7]; // reserved
635 } STRU_TELE_VTX;
638 #define STRU_TELE_VTX_EMPTY_COUNT 7
639 #define STRU_TELE_VTX_EMPTY_VALUE 0xff
641 #define VTX_KEEPALIVE_TIME_OUT 2000000 // uS
643 static bool srxlFrameVTX(sbuf_t *dst, timeUs_t currentTimeUs)
645 static timeUs_t lastTimeSentVtx = 0;
646 static spektrumVtx_t vtxSent;
648 spektrumVtx_t vtx;
649 collectVtxTmData(&vtx);
651 if ((vtxDeviceType != VTXDEV_UNKNOWN) && vtxDeviceType != VTXDEV_UNSUPPORTED) {
652 convertVtxTmData(&vtx);
654 if ((memcmp(&vtxSent, &vtx, sizeof(spektrumVtx_t)) != 0) ||
655 ((currentTimeUs - lastTimeSentVtx) > VTX_KEEPALIVE_TIME_OUT) ) {
656 // Fill in the VTX tm structure
657 sbufWriteU8(dst, TELE_DEVICE_VTX);
658 sbufWriteU8(dst, SRXL_FRAMETYPE_SID);
659 sbufWriteU8(dst, vtx.band);
660 sbufWriteU8(dst, vtx.channel);
661 sbufWriteU8(dst, vtx.pitMode);
662 sbufWriteU8(dst, vtx.power);
663 sbufWriteU16(dst, vtx.powerValue);
664 sbufWriteU8(dst, vtx.region);
666 sbufFill(dst, STRU_TELE_VTX_EMPTY_VALUE, STRU_TELE_VTX_EMPTY_COUNT);
668 memcpy(&vtxSent, &vtx, sizeof(spektrumVtx_t));
669 lastTimeSentVtx = currentTimeUs;
670 return true;
673 return false;
675 #endif // USE_SPEKTRUM_VTX_TELEMETRY && USE_SPEKTRUM_VTX_CONTROL && USE_VTX_COMMON
678 // Schedule array to decide how often each type of frame is sent
679 // The frames are scheduled in sets of 3 frames, 2 mandatory and 1 user frame.
680 // The user frame type is cycled for each set.
681 // Example. QOS, RPM,.CURRENT, QOS, RPM, TEXT. QOS, RPM, CURRENT, etc etc
683 #define SRXL_SCHEDULE_MANDATORY_COUNT 2 // Mandatory QOS and RPM sensors
685 #define SRXL_FP_MAH_COUNT 1
687 #if defined(USE_GPS)
688 #define SRXL_GPS_LOC_COUNT 1
689 #define SRXL_GPS_STAT_COUNT 1
690 #else
691 #define SRXL_GPS_LOC_COUNT 0
692 #define SRXL_GPS_STAT_COUNT 0
693 #endif
695 #if defined(USE_SPEKTRUM_CMS_TELEMETRY) && defined(USE_CMS)
696 #define SRXL_SCHEDULE_CMS_COUNT 1
697 #else
698 #define SRXL_SCHEDULE_CMS_COUNT 0
699 #endif
701 #if defined(USE_SPEKTRUM_VTX_TELEMETRY) && defined(USE_SPEKTRUM_VTX_CONTROL) && defined(USE_VTX_COMMON)
702 #define SRXL_VTX_TM_COUNT 1
703 #else
704 #define SRXL_VTX_TM_COUNT 0
705 #endif
707 #define SRXL_SCHEDULE_USER_COUNT (SRXL_FP_MAH_COUNT + SRXL_SCHEDULE_CMS_COUNT + SRXL_VTX_TM_COUNT + SRXL_GPS_LOC_COUNT + SRXL_GPS_STAT_COUNT)
708 #define SRXL_SCHEDULE_COUNT_MAX (SRXL_SCHEDULE_MANDATORY_COUNT + 1)
709 #define SRXL_TOTAL_COUNT (SRXL_SCHEDULE_MANDATORY_COUNT + SRXL_SCHEDULE_USER_COUNT)
711 typedef bool (*srxlScheduleFnPtr)(sbuf_t *dst, timeUs_t currentTimeUs);
713 const srxlScheduleFnPtr srxlScheduleFuncs[SRXL_TOTAL_COUNT] = {
714 /* must send srxlFrameQos, Rpm and then alternating items of our own */
715 srxlFrameQos,
716 srxlFrameRpm,
717 srxlFrameFlightPackCurrent,
718 #if defined(USE_GPS)
719 srxlFrameGpsStat,
720 srxlFrameGpsLoc,
721 #endif
722 #if defined(USE_SPEKTRUM_VTX_TELEMETRY) && defined(USE_SPEKTRUM_VTX_CONTROL) && defined(USE_VTX_COMMON)
723 srxlFrameVTX,
724 #endif
725 #if defined(USE_SPEKTRUM_CMS_TELEMETRY) && defined(USE_CMS)
726 srxlFrameText,
727 #endif
731 static void processSrxl(timeUs_t currentTimeUs)
733 static uint8_t srxlScheduleIndex = 0;
734 static uint8_t srxlScheduleUserIndex = 0;
736 sbuf_t srxlPayloadBuf;
737 sbuf_t *dst = &srxlPayloadBuf;
738 srxlScheduleFnPtr srxlFnPtr;
740 if (srxlScheduleIndex < SRXL_SCHEDULE_MANDATORY_COUNT) {
741 srxlFnPtr = srxlScheduleFuncs[srxlScheduleIndex];
742 } else {
743 srxlFnPtr = srxlScheduleFuncs[srxlScheduleIndex + srxlScheduleUserIndex];
744 srxlScheduleUserIndex = (srxlScheduleUserIndex + 1) % SRXL_SCHEDULE_USER_COUNT;
746 #if defined(USE_SPEKTRUM_CMS_TELEMETRY) && defined(USE_CMS)
747 // Boost CMS performance by sending nothing else but CMS Text frames when in a CMS menu.
748 // Sideeffect, all other reports are still not sent if user leaves CMS without a proper EXIT.
749 if (cmsInMenu &&
750 (pCurrentDisplay == &srxlDisplayPort)) {
751 srxlFnPtr = srxlFrameText;
753 #endif
757 if (srxlFnPtr) {
758 srxlInitializeFrame(dst);
759 if (srxlFnPtr(dst, currentTimeUs)) {
760 srxlFinalize(dst);
763 srxlScheduleIndex = (srxlScheduleIndex + 1) % SRXL_SCHEDULE_COUNT_MAX;
766 void initSrxlTelemetry(void)
768 // check if there is a serial port open for SRXL telemetry (ie opened by the SRXL RX)
769 // and feature is enabled, if so, set SRXL telemetry enabled
770 if (srxlRxIsActive()) {
771 srxlTelemetryEnabled = true;
772 srxl2 = false;
773 #if defined(USE_SERIALRX_SRXL2)
774 } else if (srxl2RxIsActive()) {
775 srxlTelemetryEnabled = true;
776 srxl2 = true;
777 #endif
778 } else {
779 srxlTelemetryEnabled = false;
780 srxl2 = false;
783 #if defined(USE_SPEKTRUM_CMS_TELEMETRY)
784 if (srxlTelemetryEnabled) {
785 srxlDisplayportRegister();
787 #endif
790 bool checkSrxlTelemetryState(void)
792 return srxlTelemetryEnabled;
796 * Called periodically by the scheduler
798 void handleSrxlTelemetry(timeUs_t currentTimeUs)
800 if (srxl2) {
801 #if defined(USE_SERIALRX_SRXL2)
802 if (srxl2TelemetryRequested()) {
803 processSrxl(currentTimeUs);
805 #endif
806 } else {
807 if (srxlTelemetryBufferEmpty()) {
808 processSrxl(currentTimeUs);
812 #endif