Merge pull request #11198 from SteveCEvans/sce_rc2
[betaflight.git] / src / main / telemetry / jetiexbus.c
blob749ee1d836c6dd2dd810ead3f8b6677f84adb641
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 <stdbool.h>
22 #include <stdint.h>
23 #include <string.h>
25 #include "platform.h"
27 #if defined(USE_TELEMETRY_JETIEXBUS)
29 #include "build/build_config.h"
30 #include "build/debug.h"
31 #include "fc/runtime_config.h"
32 #include "config/feature.h"
34 #include "common/utils.h"
35 #include "common/bitarray.h"
37 #include "drivers/serial.h"
38 #include "drivers/serial_uart.h"
39 #include "drivers/time.h"
42 #include "flight/position.h"
43 #include "flight/imu.h"
45 #include "io/serial.h"
46 #include "io/gps.h"
48 #include "rx/rx.h"
49 #include "rx/jetiexbus.h"
51 #include "sensors/battery.h"
52 #include "sensors/sensors.h"
53 #include "sensors/acceleration.h"
55 #include "telemetry/jetiexbus.h"
56 #include "telemetry/telemetry.h"
58 #define EXTEL_DATA_MSG (0x40)
59 #define EXTEL_UNMASK_TYPE (0x3F)
60 #define EXTEL_SYNC_LEN 1
61 #define EXTEL_CRC_LEN 1
62 #define EXTEL_HEADER_LEN 6
63 #define EXTEL_MAX_LEN 26
64 #define EXTEL_OVERHEAD (EXTEL_SYNC_LEN + EXTEL_HEADER_LEN + EXTEL_CRC_LEN)
65 #define EXTEL_MAX_PAYLOAD (EXTEL_MAX_LEN - EXTEL_OVERHEAD)
66 #define EXBUS_MAX_REQUEST_BUFFER_SIZE (EXBUS_OVERHEAD + EXTEL_MAX_LEN)
68 enum exTelHeader_e {
69 EXTEL_HEADER_SYNC = 0,
70 EXTEL_HEADER_TYPE_LEN,
71 EXTEL_HEADER_USN_LB,
72 EXTEL_HEADER_USN_HB,
73 EXTEL_HEADER_LSN_LB,
74 EXTEL_HEADER_LSN_HB,
75 EXTEL_HEADER_RES,
76 EXTEL_HEADER_ID,
77 EXTEL_HEADER_DATA
80 enum {
81 EXBUS_TRANS_ZERO = 0,
82 EXBUS_TRANS_RX_READY,
83 EXBUS_TRANS_RX,
84 EXBUS_TRANS_IS_TX_COMPLETED,
85 EXBUS_TRANS_TX
88 enum exDataType_e {
89 EX_TYPE_6b = 0, // int6_t Data type 6b (-31 ¸31)
90 EX_TYPE_14b = 1, // int14_t Data type 14b (-8191 ¸8191)
91 EX_TYPE_22b = 4, // int22_t Data type 22b (-2097151 ¸2097151)
92 EX_TYPE_DT = 5, // int22_t Special data type – time and date
93 EX_TYPE_30b = 8, // int30_t Data type 30b (-536870911 ¸536870911)
94 EX_TYPE_GPS = 9, // int30_t Special data type – GPS coordinates: lo/hi minute - lo/hi degree.
95 EX_TYPE_DES = 255 // only for devicedescription
98 const uint8_t exDataTypeLen[] = {
99 [EX_TYPE_6b] = 1,
100 [EX_TYPE_14b] = 2,
101 [EX_TYPE_22b] = 3,
102 [EX_TYPE_DT] = 3,
103 [EX_TYPE_30b] = 4,
104 [EX_TYPE_GPS] = 4
107 typedef struct exBusSensor_s {
108 const char *label;
109 const char *unit;
110 const uint8_t exDataType;
111 const uint8_t decimals;
112 } exBusSensor_t;
114 #define DECIMAL_MASK(decimals) (decimals << 5)
116 // list of telemetry messages
117 // after every 15 sensors a new header has to be inserted (e.g. "BF D2")
118 const exBusSensor_t jetiExSensors[] = {
119 {"BF D1", "", EX_TYPE_DES, 0 }, // device descripton
120 {"Voltage", "V", EX_TYPE_22b, DECIMAL_MASK(1)},
121 {"Current", "A", EX_TYPE_22b, DECIMAL_MASK(2)},
122 {"Altitude", "m", EX_TYPE_22b, DECIMAL_MASK(2)},
123 {"Capacity", "mAh", EX_TYPE_22b, DECIMAL_MASK(0)},
124 {"Power", "W", EX_TYPE_22b, DECIMAL_MASK(1)},
125 {"Roll angle", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
126 {"Pitch angle", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
127 {"Heading", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
128 {"Vario", "m/s", EX_TYPE_22b, DECIMAL_MASK(2)},
129 {"GPS Sats", "", EX_TYPE_22b, DECIMAL_MASK(0)},
130 {"GPS Long", "", EX_TYPE_GPS, DECIMAL_MASK(0)},
131 {"GPS Lat", "", EX_TYPE_GPS, DECIMAL_MASK(0)},
132 {"GPS Speed", "m/s", EX_TYPE_22b, DECIMAL_MASK(2)},
133 {"GPS H-Distance", "m", EX_TYPE_22b, DECIMAL_MASK(0)},
134 {"GPS H-Direction", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
135 {"BF D2", "", EX_TYPE_DES, 0 }, // device descripton
136 {"GPS Heading", "\xB0", EX_TYPE_22b, DECIMAL_MASK(1)},
137 {"GPS Altitude", "m", EX_TYPE_22b, DECIMAL_MASK(2)},
138 {"G-Force X", "", EX_TYPE_22b, DECIMAL_MASK(3)},
139 {"G-Force Y", "", EX_TYPE_22b, DECIMAL_MASK(3)},
140 {"G-Force Z", "", EX_TYPE_22b, DECIMAL_MASK(3)}
143 // after every 15 sensors increment the step by 2 (e.g. ...EX_VAL15, EX_VAL16 = 17) to skip the device description
144 enum exSensors_e {
145 EX_VOLTAGE = 1,
146 EX_CURRENT,
147 EX_ALTITUDE,
148 EX_CAPACITY,
149 EX_POWER,
150 EX_ROLL_ANGLE,
151 EX_PITCH_ANGLE,
152 EX_HEADING,
153 EX_VARIO,
154 EX_GPS_SATS,
155 EX_GPS_LONG,
156 EX_GPS_LAT,
157 EX_GPS_SPEED,
158 EX_GPS_DISTANCE_TO_HOME,
159 EX_GPS_DIRECTION_TO_HOME,
160 EX_GPS_HEADING = 17,
161 EX_GPS_ALTITUDE,
162 EX_GFORCE_X,
163 EX_GFORCE_Y,
164 EX_GFORCE_Z
167 union{
168 int32_t vInt;
169 uint16_t vWord[2];
170 char vBytes[4];
171 } exGps;
174 #define JETI_EX_SENSOR_COUNT (ARRAYLEN(jetiExSensors))
176 static uint8_t jetiExBusTelemetryFrame[40];
177 static uint8_t jetiExBusTransceiveState = EXBUS_TRANS_RX;
178 static uint8_t firstActiveSensor = 0;
179 static uint32_t exSensorEnabled = 0;
181 static uint8_t sendJetiExBusTelemetry(uint8_t packetID, uint8_t item);
182 static uint8_t getNextActiveSensor(uint8_t currentSensor);
184 // Jeti Ex Telemetry CRC calculations for a frame
185 uint8_t calcCRC8(uint8_t *pt, uint8_t msgLen)
187 uint8_t crc=0;
188 for (uint8_t mlen = 0; mlen < msgLen; mlen++) {
189 crc ^= pt[mlen];
190 crc = crc ^ (crc << 1) ^ (crc << 2) ^ (0x0e090700 >> ((crc >> 3) & 0x18));
192 return(crc);
195 void enableGpsTelemetry(bool enable)
197 if (enable) {
198 bitArraySet(&exSensorEnabled, EX_GPS_SATS);
199 bitArraySet(&exSensorEnabled, EX_GPS_LONG);
200 bitArraySet(&exSensorEnabled, EX_GPS_LAT);
201 bitArraySet(&exSensorEnabled, EX_GPS_SPEED);
202 bitArraySet(&exSensorEnabled, EX_GPS_DISTANCE_TO_HOME);
203 bitArraySet(&exSensorEnabled, EX_GPS_DIRECTION_TO_HOME);
204 bitArraySet(&exSensorEnabled, EX_GPS_HEADING);
205 bitArraySet(&exSensorEnabled, EX_GPS_ALTITUDE);
206 } else {
207 bitArrayClr(&exSensorEnabled, EX_GPS_SATS);
208 bitArrayClr(&exSensorEnabled, EX_GPS_LONG);
209 bitArrayClr(&exSensorEnabled, EX_GPS_LAT);
210 bitArrayClr(&exSensorEnabled, EX_GPS_SPEED);
211 bitArrayClr(&exSensorEnabled, EX_GPS_DISTANCE_TO_HOME);
212 bitArrayClr(&exSensorEnabled, EX_GPS_DIRECTION_TO_HOME);
213 bitArrayClr(&exSensorEnabled, EX_GPS_HEADING);
214 bitArrayClr(&exSensorEnabled, EX_GPS_ALTITUDE);
219 * -----------------------------------------------
220 * Jeti Ex Bus Telemetry
221 * -----------------------------------------------
223 void initJetiExBusTelemetry(void)
225 // Init Ex Bus Frame header
226 jetiExBusTelemetryFrame[EXBUS_HEADER_SYNC] = 0x3B; // Startbytes
227 jetiExBusTelemetryFrame[EXBUS_HEADER_REQ] = 0x01;
228 jetiExBusTelemetryFrame[EXBUS_HEADER_DATA_ID] = 0x3A; // Ex Telemetry
230 // Init Ex Telemetry header
231 uint8_t *jetiExTelemetryFrame = &jetiExBusTelemetryFrame[EXBUS_HEADER_DATA];
233 jetiExTelemetryFrame[EXTEL_HEADER_SYNC] = 0x9F; // Startbyte
234 jetiExTelemetryFrame[EXTEL_HEADER_USN_LB] = 0x1E; // Serial Number 4 Byte
235 jetiExTelemetryFrame[EXTEL_HEADER_USN_HB] = 0xA4;
236 jetiExTelemetryFrame[EXTEL_HEADER_LSN_LB] = 0x00; // increment by telemetry count (%16) > only 15 values per device possible
237 jetiExTelemetryFrame[EXTEL_HEADER_LSN_HB] = 0x00;
238 jetiExTelemetryFrame[EXTEL_HEADER_RES] = 0x00; // reserved, by default 0x00
240 // Check which sensors are available
241 if (batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE) {
242 bitArraySet(&exSensorEnabled, EX_VOLTAGE);
244 if (batteryConfig()->currentMeterSource != CURRENT_METER_NONE) {
245 bitArraySet(&exSensorEnabled, EX_CURRENT);
247 if ((batteryConfig()->voltageMeterSource != VOLTAGE_METER_NONE) && (batteryConfig()->currentMeterSource != CURRENT_METER_NONE)) {
248 bitArraySet(&exSensorEnabled, EX_POWER);
249 bitArraySet(&exSensorEnabled, EX_CAPACITY);
251 if (sensors(SENSOR_BARO)) {
252 bitArraySet(&exSensorEnabled, EX_ALTITUDE);
253 #ifdef USE_VARIO
254 bitArraySet(&exSensorEnabled, EX_VARIO);
255 #endif
257 if (sensors(SENSOR_ACC)) {
258 bitArraySet(&exSensorEnabled, EX_ROLL_ANGLE);
259 bitArraySet(&exSensorEnabled, EX_PITCH_ANGLE);
260 bitArraySet(&exSensorEnabled, EX_GFORCE_X);
261 bitArraySet(&exSensorEnabled, EX_GFORCE_Y);
262 bitArraySet(&exSensorEnabled, EX_GFORCE_Z);
264 if (sensors(SENSOR_MAG)) {
265 bitArraySet(&exSensorEnabled, EX_HEADING);
268 enableGpsTelemetry(featureIsEnabled(FEATURE_GPS));
270 firstActiveSensor = getNextActiveSensor(0); // find the first active sensor
273 void createExTelemetryTextMessage(uint8_t *exMessage, uint8_t messageID, const exBusSensor_t *sensor)
275 uint8_t labelLength = strlen(sensor->label);
276 uint8_t unitLength = strlen(sensor->unit);
278 exMessage[EXTEL_HEADER_TYPE_LEN] = EXTEL_OVERHEAD + labelLength + unitLength;
279 exMessage[EXTEL_HEADER_LSN_LB] = messageID & 0xF0; // Device ID
280 exMessage[EXTEL_HEADER_ID] = messageID & 0x0F; // Sensor ID (%16)
281 exMessage[EXTEL_HEADER_DATA] = (labelLength << 3) + unitLength;
283 memcpy(&exMessage[EXTEL_HEADER_DATA + 1], sensor->label, labelLength);
284 memcpy(&exMessage[EXTEL_HEADER_DATA + 1 + labelLength], sensor->unit, unitLength);
286 exMessage[exMessage[EXTEL_HEADER_TYPE_LEN] + EXTEL_CRC_LEN] = calcCRC8(&exMessage[EXTEL_HEADER_TYPE_LEN], exMessage[EXTEL_HEADER_TYPE_LEN]);
289 uint32_t calcGpsDDMMmmm(int32_t value, bool isLong)
291 uint32_t absValue = ABS(value);
292 uint16_t deg16 = absValue / GPS_DEGREES_DIVIDER;
293 uint16_t min16 = (absValue - deg16 * GPS_DEGREES_DIVIDER) * 6 / 1000;
295 exGps.vInt = 0;
296 exGps.vWord[0] = min16;
297 exGps.vWord[1] = deg16;
298 exGps.vWord[1] |= isLong ? 0x2000 : 0;
299 exGps.vWord[1] |= (value < 0) ? 0x4000 : 0;
301 return exGps.vInt;
305 int32_t getSensorValue(uint8_t sensor)
307 switch (sensor) {
308 case EX_VOLTAGE:
309 return getLegacyBatteryVoltage();
310 break;
312 case EX_CURRENT:
313 return getAmperage();
314 break;
316 case EX_ALTITUDE:
317 return getEstimatedAltitudeCm();
318 break;
320 case EX_CAPACITY:
321 return getMAhDrawn();
322 break;
324 case EX_POWER:
325 return (getBatteryVoltage() * getAmperage() / 1000);
326 break;
328 case EX_ROLL_ANGLE:
329 return attitude.values.roll;
330 break;
332 case EX_PITCH_ANGLE:
333 return attitude.values.pitch;
334 break;
336 case EX_HEADING:
337 return attitude.values.yaw;
338 break;
340 #ifdef USE_VARIO
341 case EX_VARIO:
342 return getEstimatedVario();
343 break;
344 #endif
346 #ifdef USE_GPS
347 case EX_GPS_SATS:
348 return gpsSol.numSat;
349 break;
351 case EX_GPS_LONG:
352 return calcGpsDDMMmmm(gpsSol.llh.lon, true);
353 break;
355 case EX_GPS_LAT:
356 return calcGpsDDMMmmm(gpsSol.llh.lat, false);
357 break;
359 case EX_GPS_SPEED:
360 return gpsSol.groundSpeed;
361 break;
363 case EX_GPS_DISTANCE_TO_HOME:
364 return GPS_distanceToHome;
365 break;
367 case EX_GPS_DIRECTION_TO_HOME:
368 return GPS_directionToHome;
369 break;
371 case EX_GPS_HEADING:
372 return gpsSol.groundCourse;
373 break;
375 case EX_GPS_ALTITUDE:
376 return gpsSol.llh.altCm;
377 break;
378 #endif
380 #if defined(USE_ACC)
381 case EX_GFORCE_X:
382 return (int16_t)(((float)acc.accADC[0] / acc.dev.acc_1G) * 1000);
383 break;
385 case EX_GFORCE_Y:
386 return (int16_t)(((float)acc.accADC[1] / acc.dev.acc_1G) * 1000);
387 break;
389 case EX_GFORCE_Z:
390 return (int16_t)(((float)acc.accADC[2] / acc.dev.acc_1G) * 1000);
391 break;
392 #endif
394 default:
395 return -1;
399 uint8_t getNextActiveSensor(uint8_t currentSensor)
401 while( ++currentSensor < JETI_EX_SENSOR_COUNT) {
402 if (bitArrayGet(&exSensorEnabled, currentSensor)) {
403 break;
406 if (currentSensor == JETI_EX_SENSOR_COUNT ) {
407 currentSensor = firstActiveSensor;
409 return currentSensor;
412 uint8_t createExTelemetryValueMessage(uint8_t *exMessage, uint8_t item)
414 uint8_t startItem = item;
415 uint8_t sensorItemMaxGroup = (item & 0xF0) + 0x10;
416 uint8_t iCount;
417 uint8_t messageSize;
418 uint32_t sensorValue;
420 exMessage[EXTEL_HEADER_LSN_LB] = item & 0xF0; // Device ID
421 uint8_t *p = &exMessage[EXTEL_HEADER_ID];
423 while (item < sensorItemMaxGroup) {
424 *p++ = ((item & 0x0F) << 4) | jetiExSensors[item].exDataType; // Sensor ID (%16) | EX Data Type
426 sensorValue = getSensorValue(item);
427 iCount = exDataTypeLen[jetiExSensors[item].exDataType];
429 while (iCount > 1) {
430 *p++ = sensorValue;
431 sensorValue = sensorValue >> 8;
432 iCount--;
434 if (jetiExSensors[item].exDataType != EX_TYPE_GPS) {
435 *p++ = (sensorValue & 0x9F) | jetiExSensors[item].decimals;
436 } else {
437 *p++ = sensorValue;
440 item = getNextActiveSensor(item);
442 if (startItem >= item) {
443 break;
446 if ((p - &exMessage[EXTEL_HEADER_ID]) + exDataTypeLen[jetiExSensors[item].exDataType] + 1 >= EXTEL_MAX_PAYLOAD) {
447 break;
450 messageSize = (EXTEL_HEADER_LEN + (p-&exMessage[EXTEL_HEADER_ID]));
451 exMessage[EXTEL_HEADER_TYPE_LEN] = EXTEL_DATA_MSG | messageSize;
452 exMessage[messageSize + EXTEL_CRC_LEN] = calcCRC8(&exMessage[EXTEL_HEADER_TYPE_LEN], messageSize);
454 return item; // return the next item
457 void createExBusMessage(uint8_t *exBusMessage, uint8_t *exMessage, uint8_t packetID)
459 uint16_t crc16;
461 exBusMessage[EXBUS_HEADER_PACKET_ID] = packetID;
462 exBusMessage[EXBUS_HEADER_SUBLEN] = (exMessage[EXTEL_HEADER_TYPE_LEN] & EXTEL_UNMASK_TYPE) + 2; // +2: startbyte & CRC8
463 exBusMessage[EXBUS_HEADER_MSG_LEN] = EXBUS_OVERHEAD + exBusMessage[EXBUS_HEADER_SUBLEN];
465 crc16 = jetiExBusCalcCRC16(exBusMessage, exBusMessage[EXBUS_HEADER_MSG_LEN] - EXBUS_CRC_LEN);
466 exBusMessage[exBusMessage[EXBUS_HEADER_MSG_LEN] - 2] = crc16;
467 exBusMessage[exBusMessage[EXBUS_HEADER_MSG_LEN] - 1] = crc16 >> 8;
470 void checkJetiExBusTelemetryState(void)
472 return;
475 void handleJetiExBusTelemetry(void)
477 static uint16_t framesLost = 0; // only for debug
478 static uint8_t item = 0;
479 uint32_t timeDiff;
481 // Check if we shall reset frame position due to time
482 if (jetiExBusRequestState == EXBUS_STATE_RECEIVED) {
484 // to prevent timing issues from request to answer - max. 4ms
485 timeDiff = micros() - jetiTimeStampRequest;
487 if (timeDiff > 3000) { // include reserved time
488 jetiExBusRequestState = EXBUS_STATE_ZERO;
489 framesLost++;
490 return;
493 if ((jetiExBusRequestFrame[EXBUS_HEADER_DATA_ID] == EXBUS_EX_REQUEST) && (jetiExBusCalcCRC16(jetiExBusRequestFrame, jetiExBusRequestFrame[EXBUS_HEADER_MSG_LEN]) == 0)) {
494 if (serialRxBytesWaiting(jetiExBusPort) == 0) {
495 jetiExBusTransceiveState = EXBUS_TRANS_TX;
496 item = sendJetiExBusTelemetry(jetiExBusRequestFrame[EXBUS_HEADER_PACKET_ID], item);
497 jetiExBusRequestState = EXBUS_STATE_PROCESSED;
498 return;
500 } else {
501 jetiExBusRequestState = EXBUS_STATE_ZERO;
502 return;
506 // check the state if transmit is ready
507 if (jetiExBusTransceiveState == EXBUS_TRANS_IS_TX_COMPLETED) {
508 if (isSerialTransmitBufferEmpty(jetiExBusPort)) {
509 jetiExBusTransceiveState = EXBUS_TRANS_RX;
510 jetiExBusRequestState = EXBUS_STATE_ZERO;
515 uint8_t sendJetiExBusTelemetry(uint8_t packetID, uint8_t item)
517 static uint8_t sensorDescriptionCounter = 0xFF;
518 static uint8_t requestLoop = 0xFF;
519 static bool allSensorsActive = true;
520 uint8_t *jetiExTelemetryFrame = &jetiExBusTelemetryFrame[EXBUS_HEADER_DATA];
522 if (requestLoop) {
523 while( ++sensorDescriptionCounter < JETI_EX_SENSOR_COUNT) {
524 if (bitArrayGet(&exSensorEnabled, sensorDescriptionCounter) || (jetiExSensors[sensorDescriptionCounter].exDataType == EX_TYPE_DES)) {
525 break;
528 if (sensorDescriptionCounter == JETI_EX_SENSOR_COUNT ) {
529 sensorDescriptionCounter = 0;
532 createExTelemetryTextMessage(jetiExTelemetryFrame, sensorDescriptionCounter, &jetiExSensors[sensorDescriptionCounter]);
533 createExBusMessage(jetiExBusTelemetryFrame, jetiExTelemetryFrame, packetID);
534 requestLoop--;
535 if (requestLoop == 0) {
536 item = firstActiveSensor;
537 if (featureIsEnabled(FEATURE_GPS)) {
538 enableGpsTelemetry(false);
539 allSensorsActive = false;
542 } else {
543 item = createExTelemetryValueMessage(jetiExTelemetryFrame, item);
544 createExBusMessage(jetiExBusTelemetryFrame, jetiExTelemetryFrame, packetID);
546 if (!allSensorsActive) {
547 if (sensors(SENSOR_GPS)) {
548 enableGpsTelemetry(true);
549 allSensorsActive = true;
554 serialWriteBuf(jetiExBusPort, jetiExBusTelemetryFrame, jetiExBusTelemetryFrame[EXBUS_HEADER_MSG_LEN]);
555 jetiExBusTransceiveState = EXBUS_TRANS_IS_TX_COMPLETED;
557 return item;
559 #endif