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 #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"
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)
69 EXTEL_HEADER_SYNC
= 0,
70 EXTEL_HEADER_TYPE_LEN
,
84 EXBUS_TRANS_IS_TX_COMPLETED
,
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
[] = {
107 typedef struct exBusSensor_s
{
110 const uint8_t exDataType
;
111 const uint8_t decimals
;
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
158 EX_GPS_DISTANCE_TO_HOME
,
159 EX_GPS_DIRECTION_TO_HOME
,
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
)
188 for (uint8_t mlen
= 0; mlen
< msgLen
; mlen
++) {
190 crc
= crc
^ (crc
<< 1) ^ (crc
<< 2) ^ (0x0e090700 >> ((crc
>> 3) & 0x18));
195 void enableGpsTelemetry(bool 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
);
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
);
254 bitArraySet(&exSensorEnabled
, EX_VARIO
);
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;
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;
305 int32_t getSensorValue(uint8_t sensor
)
309 return getLegacyBatteryVoltage();
313 return getAmperage();
317 return getEstimatedAltitudeCm();
321 return getMAhDrawn();
325 return (getBatteryVoltage() * getAmperage() / 1000);
329 return attitude
.values
.roll
;
333 return attitude
.values
.pitch
;
337 return attitude
.values
.yaw
;
342 return getEstimatedVario();
348 return gpsSol
.numSat
;
352 return calcGpsDDMMmmm(gpsSol
.llh
.lon
, true);
356 return calcGpsDDMMmmm(gpsSol
.llh
.lat
, false);
360 return gpsSol
.groundSpeed
;
363 case EX_GPS_DISTANCE_TO_HOME
:
364 return GPS_distanceToHome
;
367 case EX_GPS_DIRECTION_TO_HOME
:
368 return GPS_directionToHome
;
372 return gpsSol
.groundCourse
;
375 case EX_GPS_ALTITUDE
:
376 return gpsSol
.llh
.altCm
;
382 return (int16_t)(((float)acc
.accADC
[0] / acc
.dev
.acc_1G
) * 1000);
386 return (int16_t)(((float)acc
.accADC
[1] / acc
.dev
.acc_1G
) * 1000);
390 return (int16_t)(((float)acc
.accADC
[2] / acc
.dev
.acc_1G
) * 1000);
399 uint8_t getNextActiveSensor(uint8_t currentSensor
)
401 while( ++currentSensor
< JETI_EX_SENSOR_COUNT
) {
402 if (bitArrayGet(&exSensorEnabled
, currentSensor
)) {
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;
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
];
431 sensorValue
= sensorValue
>> 8;
434 if (jetiExSensors
[item
].exDataType
!= EX_TYPE_GPS
) {
435 *p
++ = (sensorValue
& 0x9F) | jetiExSensors
[item
].decimals
;
440 item
= getNextActiveSensor(item
);
442 if (startItem
>= item
) {
446 if ((p
- &exMessage
[EXTEL_HEADER_ID
]) + exDataTypeLen
[jetiExSensors
[item
].exDataType
] + 1 >= EXTEL_MAX_PAYLOAD
) {
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
)
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)
475 void handleJetiExBusTelemetry(void)
477 static uint16_t framesLost
= 0; // only for debug
478 static uint8_t item
= 0;
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
;
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
;
501 jetiExBusRequestState
= EXBUS_STATE_ZERO
;
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
];
523 while( ++sensorDescriptionCounter
< JETI_EX_SENSOR_COUNT
) {
524 if (bitArrayGet(&exSensorEnabled
, sensorDescriptionCounter
) || (jetiExSensors
[sensorDescriptionCounter
].exDataType
== EX_TYPE_DES
)) {
528 if (sensorDescriptionCounter
== JETI_EX_SENSOR_COUNT
) {
529 sensorDescriptionCounter
= 0;
532 createExTelemetryTextMessage(jetiExTelemetryFrame
, sensorDescriptionCounter
, &jetiExSensors
[sensorDescriptionCounter
]);
533 createExBusMessage(jetiExBusTelemetryFrame
, jetiExTelemetryFrame
, packetID
);
535 if (requestLoop
== 0) {
536 item
= firstActiveSensor
;
537 if (featureIsEnabled(FEATURE_GPS
)) {
538 enableGpsTelemetry(false);
539 allSensorsActive
= false;
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
;