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/>.
32 #include "build/debug.h"
33 #include "build/atomic.h"
35 #include "common/maths.h"
37 #include "config/feature.h"
39 #include "drivers/motor.h"
40 #include "drivers/timer.h"
42 #include "drivers/dshot_command.h"
43 #include "drivers/nvic.h"
45 #include "flight/mixer.h"
50 void dshotInitEndpoints(const motorConfig_t
*motorConfig
, float outputLimit
, float *outputLow
, float *outputHigh
, float *disarm
, float *deadbandMotor3dHigh
, float *deadbandMotor3dLow
)
52 float outputLimitOffset
= DSHOT_RANGE
* (1 - outputLimit
);
53 *disarm
= DSHOT_CMD_MOTOR_STOP
;
54 if (featureIsEnabled(FEATURE_3D
)) {
55 *outputLow
= DSHOT_MIN_THROTTLE
+ getDigitalIdleOffset(motorConfig
) * (DSHOT_3D_FORWARD_MIN_THROTTLE
- 1 - DSHOT_MIN_THROTTLE
);
56 *outputHigh
= DSHOT_MAX_THROTTLE
- outputLimitOffset
/ 2;
57 *deadbandMotor3dHigh
= DSHOT_3D_FORWARD_MIN_THROTTLE
+ getDigitalIdleOffset(motorConfig
) * (DSHOT_MAX_THROTTLE
- DSHOT_3D_FORWARD_MIN_THROTTLE
);
58 *deadbandMotor3dLow
= DSHOT_3D_FORWARD_MIN_THROTTLE
- 1 - outputLimitOffset
/ 2;
60 *outputLow
= DSHOT_MIN_THROTTLE
+ getDigitalIdleOffset(motorConfig
) * DSHOT_RANGE
;
61 *outputHigh
= DSHOT_MAX_THROTTLE
- outputLimitOffset
;
65 float dshotConvertFromExternal(uint16_t externalValue
)
69 externalValue
= constrain(externalValue
, PWM_RANGE_MIN
, PWM_RANGE_MAX
);
71 if (featureIsEnabled(FEATURE_3D
)) {
72 if (externalValue
== PWM_RANGE_MIDDLE
) {
73 motorValue
= DSHOT_CMD_MOTOR_STOP
;
74 } else if (externalValue
< PWM_RANGE_MIDDLE
) {
75 motorValue
= scaleRangef(externalValue
, PWM_RANGE_MIN
, PWM_RANGE_MIDDLE
- 1, DSHOT_3D_FORWARD_MIN_THROTTLE
- 1, DSHOT_MIN_THROTTLE
);
77 motorValue
= scaleRangef(externalValue
, PWM_RANGE_MIDDLE
+ 1, PWM_RANGE_MAX
, DSHOT_3D_FORWARD_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
);
80 motorValue
= (externalValue
== PWM_RANGE_MIN
) ? DSHOT_CMD_MOTOR_STOP
: scaleRangef(externalValue
, PWM_RANGE_MIN
+ 1, PWM_RANGE_MAX
, DSHOT_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
);
86 uint16_t dshotConvertToExternal(float motorValue
)
90 if (featureIsEnabled(FEATURE_3D
)) {
91 if (motorValue
== DSHOT_CMD_MOTOR_STOP
|| motorValue
< DSHOT_MIN_THROTTLE
) {
92 externalValue
= PWM_RANGE_MIDDLE
;
93 } else if (motorValue
<= DSHOT_3D_FORWARD_MIN_THROTTLE
- 1) {
94 externalValue
= scaleRangef(motorValue
, DSHOT_MIN_THROTTLE
, DSHOT_3D_FORWARD_MIN_THROTTLE
- 1, PWM_RANGE_MIDDLE
- 1, PWM_RANGE_MIN
);
96 externalValue
= scaleRangef(motorValue
, DSHOT_3D_FORWARD_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
, PWM_RANGE_MIDDLE
+ 1, PWM_RANGE_MAX
);
99 externalValue
= (motorValue
< DSHOT_MIN_THROTTLE
) ? PWM_RANGE_MIN
: scaleRangef(motorValue
, DSHOT_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
, PWM_RANGE_MIN
+ 1, PWM_RANGE_MAX
);
102 return lrintf(externalValue
);
105 FAST_CODE
uint16_t prepareDshotPacket(dshotProtocolControl_t
*pcb
)
109 ATOMIC_BLOCK(NVIC_PRIO_DSHOT_DMA
) {
110 packet
= (pcb
->value
<< 1) | (pcb
->requestTelemetry
? 1 : 0);
111 pcb
->requestTelemetry
= false; // reset telemetry request to make sure it's triggered only once in a row
116 unsigned csum_data
= packet
;
117 for (int i
= 0; i
< 3; i
++) {
118 csum
^= csum_data
; // xor data by nibbles
122 #ifdef USE_DSHOT_TELEMETRY
123 if (useDshotTelemetry
) {
128 packet
= (packet
<< 4) | csum
;
133 #ifdef USE_DSHOT_TELEMETRY
136 FAST_DATA_ZERO_INIT dshotTelemetryState_t dshotTelemetryState
;
138 static uint32_t dshot_decode_eRPM_telemetry_value(uint16_t value
)
141 if (value
== 0x0fff) {
145 // Convert value to 16 bit from the GCR telemetry format (eeem mmmm mmmm)
146 value
= (value
& 0x01ff) << ((value
& 0xfe00) >> 9);
148 return DSHOT_TELEMETRY_INVALID
;
151 // Convert period to erpm * 100
152 return (1000000 * 60 / 100 + value
/ 2) / value
;
155 static void dshot_decode_telemetry_value(uint8_t motorIndex
, uint32_t *pDecoded
, dshotTelemetryType_t
*pType
)
157 uint16_t value
= dshotTelemetryState
.motorState
[motorIndex
].rawValue
;
159 if (dshotTelemetryState
.motorState
[motorIndex
].telemetryTypes
== DSHOT_NORMAL_TELEMETRY_MASK
) { /* Check DSHOT_TELEMETRY_TYPE_eRPM mask */
160 // Decode eRPM telemetry
161 *pDecoded
= dshot_decode_eRPM_telemetry_value(value
);
163 // Update debug buffer
164 if (motorIndex
< 4) {
165 DEBUG_SET(DEBUG_DSHOT_RPM_TELEMETRY
, motorIndex
, *pDecoded
);
168 // Set telemetry type
169 *pType
= DSHOT_TELEMETRY_TYPE_eRPM
;
171 // Decode Extended DSHOT telemetry
172 switch (value
& 0x0f00) {
175 // Temperature range (in degree Celsius, just like Blheli_32 and KISS)
176 *pDecoded
= value
& 0x00ff;
178 // Set telemetry type
179 *pType
= DSHOT_TELEMETRY_TYPE_TEMPERATURE
;
183 // Voltage range (0-63,75V step 0,25V)
184 *pDecoded
= value
& 0x00ff;
186 // Set telemetry type
187 *pType
= DSHOT_TELEMETRY_TYPE_VOLTAGE
;
191 // Current range (0-255A step 1A)
192 *pDecoded
= value
& 0x00ff;
194 // Set telemetry type
195 *pType
= DSHOT_TELEMETRY_TYPE_CURRENT
;
200 *pDecoded
= value
& 0x00ff;
202 // Set telemetry type
203 *pType
= DSHOT_TELEMETRY_TYPE_DEBUG1
;
208 *pDecoded
= value
& 0x00ff;
210 // Set telemetry type
211 *pType
= DSHOT_TELEMETRY_TYPE_DEBUG2
;
216 *pDecoded
= value
& 0x00ff;
218 // Set telemetry type
219 *pType
= DSHOT_TELEMETRY_TYPE_DEBUG3
;
224 *pDecoded
= value
& 0x00ff;
226 // Set telemetry type
227 *pType
= DSHOT_TELEMETRY_TYPE_STATE_EVENTS
;
232 *pDecoded
= dshot_decode_eRPM_telemetry_value(value
);
234 // Update debug buffer
235 if (motorIndex
< 4) {
236 DEBUG_SET(DEBUG_DSHOT_RPM_TELEMETRY
, motorIndex
, *pDecoded
);
239 // Set telemetry type
240 *pType
= DSHOT_TELEMETRY_TYPE_eRPM
;
247 static void dshotUpdateTelemetryData(uint8_t motorIndex
, dshotTelemetryType_t type
, uint32_t value
)
249 // Update telemetry data
250 dshotTelemetryState
.motorState
[motorIndex
].telemetryData
[type
] = value
;
251 dshotTelemetryState
.motorState
[motorIndex
].telemetryTypes
|= (1 << type
);
254 if ((type
== DSHOT_TELEMETRY_TYPE_TEMPERATURE
) && (value
> dshotTelemetryState
.motorState
[motorIndex
].maxTemp
)) {
255 dshotTelemetryState
.motorState
[motorIndex
].maxTemp
= value
;
259 uint16_t getDshotTelemetry(uint8_t index
)
261 // Process telemetry in case it havenĀ“t been processed yet
262 if (dshotTelemetryState
.rawValueState
== DSHOT_RAW_VALUE_STATE_NOT_PROCESSED
) {
263 const unsigned motorCount
= motorDeviceCount();
264 uint32_t erpmTotal
= 0;
265 uint32_t rpmSamples
= 0;
267 // Decode all telemetry data now to discharge interrupt from this task
268 for (uint8_t k
= 0; k
< motorCount
; k
++) {
269 dshotTelemetryType_t type
;
272 dshot_decode_telemetry_value(k
, &value
, &type
);
274 if (value
!= DSHOT_TELEMETRY_INVALID
) {
275 dshotUpdateTelemetryData(k
, type
, value
);
277 if (type
== DSHOT_TELEMETRY_TYPE_eRPM
) {
285 if (rpmSamples
> 0) {
286 dshotTelemetryState
.averageErpm
= (uint16_t)(erpmTotal
/ rpmSamples
);
289 // Set state to processed
290 dshotTelemetryState
.rawValueState
= DSHOT_RAW_VALUE_STATE_PROCESSED
;
293 return dshotTelemetryState
.motorState
[index
].telemetryData
[DSHOT_TELEMETRY_TYPE_eRPM
];
296 bool isDshotMotorTelemetryActive(uint8_t motorIndex
)
298 return (dshotTelemetryState
.motorState
[motorIndex
].telemetryTypes
& (1 << DSHOT_TELEMETRY_TYPE_eRPM
)) != 0;
301 bool isDshotTelemetryActive(void)
303 const unsigned motorCount
= motorDeviceCount();
305 for (unsigned i
= 0; i
< motorCount
; i
++) {
306 if (!isDshotMotorTelemetryActive(i
)) {
315 void dshotCleanTelemetryData(void)
317 memset(&dshotTelemetryState
, 0, sizeof(dshotTelemetryState
));
320 uint32_t erpmToRpm(uint16_t erpm
)
322 // rpm = (erpm * 100) / (motorConfig()->motorPoleCount / 2)
323 return (erpm
* 200) / motorConfig()->motorPoleCount
;
326 uint32_t getDshotAverageRpm(void)
328 return erpmToRpm(dshotTelemetryState
.averageErpm
);
331 #endif // USE_DSHOT_TELEMETRY
333 #ifdef USE_DSHOT_TELEMETRY_STATS
335 FAST_DATA_ZERO_INIT dshotTelemetryQuality_t dshotTelemetryQuality
[MAX_SUPPORTED_MOTORS
];
337 int16_t getDshotTelemetryMotorInvalidPercent(uint8_t motorIndex
)
339 int16_t invalidPercent
= 0;
341 if (isDshotMotorTelemetryActive(motorIndex
)) {
342 const uint32_t totalCount
= dshotTelemetryQuality
[motorIndex
].packetCountSum
;
343 const uint32_t invalidCount
= dshotTelemetryQuality
[motorIndex
].invalidCountSum
;
344 if (totalCount
> 0) {
345 invalidPercent
= lrintf(invalidCount
* 10000.0f
/ totalCount
);
348 invalidPercent
= 10000; // 100.00%
350 return invalidPercent
;
353 void updateDshotTelemetryQuality(dshotTelemetryQuality_t
*qualityStats
, bool packetValid
, timeMs_t currentTimeMs
)
355 uint8_t statsBucketIndex
= (currentTimeMs
/ DSHOT_TELEMETRY_QUALITY_BUCKET_MS
) % DSHOT_TELEMETRY_QUALITY_BUCKET_COUNT
;
356 if (statsBucketIndex
!= qualityStats
->lastBucketIndex
) {
357 qualityStats
->packetCountSum
-= qualityStats
->packetCountArray
[statsBucketIndex
];
358 qualityStats
->invalidCountSum
-= qualityStats
->invalidCountArray
[statsBucketIndex
];
359 qualityStats
->packetCountArray
[statsBucketIndex
] = 0;
360 qualityStats
->invalidCountArray
[statsBucketIndex
] = 0;
361 qualityStats
->lastBucketIndex
= statsBucketIndex
;
363 qualityStats
->packetCountSum
++;
364 qualityStats
->packetCountArray
[statsBucketIndex
]++;
366 qualityStats
->invalidCountSum
++;
367 qualityStats
->invalidCountArray
[statsBucketIndex
]++;
370 #endif // USE_DSHOT_TELEMETRY_STATS
374 // temporarily here, needs to be moved during refactoring
375 void validateAndfixMotorOutputReordering(uint8_t *array
, const unsigned size
)
377 bool invalid
= false;
379 for (unsigned i
= 0; i
< size
; i
++) {
380 if (array
[i
] >= size
) {
386 int valuesAsIndexes
[size
];
388 for (unsigned i
= 0; i
< size
; i
++) {
389 valuesAsIndexes
[i
] = -1;
393 for (unsigned i
= 0; i
< size
; i
++) {
394 if (-1 != valuesAsIndexes
[array
[i
]]) {
399 valuesAsIndexes
[array
[i
]] = array
[i
];
404 for (unsigned i
= 0; i
< size
; i
++) {