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/>.
31 #include "common/maths.h"
33 #include "config/feature.h"
35 #include "drivers/dshot.h" // for DSHOT_ constants in initEscEndpoints; may be gone in the future
36 #include "drivers/dshot_bitbang.h"
37 #include "drivers/dshot_dpwm.h"
38 #include "drivers/pwm_output.h" // for PWM_TYPE_* and others
39 #include "drivers/time.h"
41 #include "fc/rc_controls.h" // for flight3DConfig_t
43 #include "sensors/battery.h"
47 static FAST_DATA_ZERO_INIT motorDevice_t
*motorDevice
;
49 static bool motorProtocolEnabled
= false;
50 static bool motorProtocolDshot
= false;
52 void motorShutdown(void)
54 uint32_t shutdownDelayUs
= 1500;
55 motorDevice
->vTable
.shutdown();
56 motorDevice
->enabled
= false;
57 motorDevice
->motorEnableTimeMs
= 0;
58 motorDevice
->initialized
= false;
60 switch (motorConfig()->dev
.motorPwmProtocol
) {
61 case PWM_TYPE_STANDARD
:
62 case PWM_TYPE_ONESHOT125
:
63 case PWM_TYPE_ONESHOT42
:
64 case PWM_TYPE_MULTISHOT
:
65 // Delay 500ms will disarm esc which can prevent motor spin while reboot
66 shutdownDelayUs
+= 500 * 1000;
72 delayMicroseconds(shutdownDelayUs
);
75 void motorWriteAll(float *values
)
78 if (motorDevice
->enabled
) {
79 #ifdef USE_DSHOT_BITBANG
80 if (isDshotBitbangActive(&motorConfig()->dev
)) {
81 // Initialise the output buffers
82 if (motorDevice
->vTable
.updateInit
) {
83 motorDevice
->vTable
.updateInit();
86 // Update the motor data
87 for (int i
= 0; i
< motorDevice
->count
; i
++) {
88 motorDevice
->vTable
.write(i
, values
[i
]);
91 // Don't attempt to write commands to the motors if telemetry is still being received
92 if (motorDevice
->vTable
.telemetryWait
) {
93 (void)motorDevice
->vTable
.telemetryWait();
96 // Trigger the transmission of the motor data
97 motorDevice
->vTable
.updateComplete();
99 // Perform the decode of the last data received
100 // New data will be received once the send of motor data, triggered above, completes
101 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
102 motorDevice
->vTable
.decodeTelemetry();
107 // Perform the decode of the last data received
108 // New data will be received once the send of motor data, triggered above, completes
109 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
110 motorDevice
->vTable
.decodeTelemetry();
113 // Update the motor data
114 for (int i
= 0; i
< motorDevice
->count
; i
++) {
115 motorDevice
->vTable
.write(i
, values
[i
]);
118 // Trigger the transmission of the motor data
119 motorDevice
->vTable
.updateComplete();
128 unsigned motorDeviceCount(void)
130 return motorDevice
->count
;
133 motorVTable_t
*motorGetVTable(void)
135 return &motorDevice
->vTable
;
138 // This is not motor generic anymore; should be moved to analog pwm module
139 static void analogInitEndpoints(const motorConfig_t
*motorConfig
, float outputLimit
, float *outputLow
, float *outputHigh
, float *disarm
, float *deadbandMotor3dHigh
, float *deadbandMotor3dLow
)
141 if (featureIsEnabled(FEATURE_3D
)) {
142 float outputLimitOffset
= (flight3DConfig()->limit3d_high
- flight3DConfig()->limit3d_low
) * (1 - outputLimit
) / 2;
143 *disarm
= flight3DConfig()->neutral3d
;
144 *outputLow
= flight3DConfig()->limit3d_low
+ outputLimitOffset
;
145 *outputHigh
= flight3DConfig()->limit3d_high
- outputLimitOffset
;
146 *deadbandMotor3dHigh
= flight3DConfig()->deadband3d_high
;
147 *deadbandMotor3dLow
= flight3DConfig()->deadband3d_low
;
149 *disarm
= motorConfig
->mincommand
;
150 const float minThrottle
= motorConfig
->mincommand
+ motorConfig
->motorIdle
* 0.1f
;
151 *outputLow
= minThrottle
;
152 *outputHigh
= motorConfig
->maxthrottle
- ((motorConfig
->maxthrottle
- minThrottle
) * (1 - outputLimit
));
156 bool checkMotorProtocolEnabled(const motorDevConfig_t
*motorDevConfig
, bool *isProtocolDshot
)
158 bool enabled
= false;
159 bool isDshot
= false;
161 switch (motorDevConfig
->motorPwmProtocol
) {
162 case PWM_TYPE_STANDARD
:
163 case PWM_TYPE_ONESHOT125
:
164 case PWM_TYPE_ONESHOT42
:
165 case PWM_TYPE_MULTISHOT
:
166 case PWM_TYPE_BRUSHED
:
171 case PWM_TYPE_DSHOT150
:
172 case PWM_TYPE_DSHOT300
:
173 case PWM_TYPE_DSHOT600
:
174 case PWM_TYPE_PROSHOT1000
:
183 if (isProtocolDshot
) {
184 *isProtocolDshot
= isDshot
;
190 static void checkMotorProtocol(const motorDevConfig_t
*motorDevConfig
)
192 motorProtocolEnabled
= checkMotorProtocolEnabled(motorDevConfig
, &motorProtocolDshot
);
195 // End point initialization is called from mixerInit before motorDevInit; can't use vtable...
196 void motorInitEndpoints(const motorConfig_t
*motorConfig
, float outputLimit
, float *outputLow
, float *outputHigh
, float *disarm
, float *deadbandMotor3dHigh
, float *deadbandMotor3dLow
)
198 checkMotorProtocol(&motorConfig
->dev
);
200 if (isMotorProtocolEnabled()) {
201 if (!isMotorProtocolDshot()) {
202 analogInitEndpoints(motorConfig
, outputLimit
, outputLow
, outputHigh
, disarm
, deadbandMotor3dHigh
, deadbandMotor3dLow
);
206 dshotInitEndpoints(motorConfig
, outputLimit
, outputLow
, outputHigh
, disarm
, deadbandMotor3dHigh
, deadbandMotor3dLow
);
212 float motorConvertFromExternal(uint16_t externalValue
)
214 return motorDevice
->vTable
.convertExternalToMotor(externalValue
);
217 uint16_t motorConvertToExternal(float motorValue
)
219 return motorDevice
->vTable
.convertMotorToExternal(motorValue
);
222 void motorPostInit(void)
224 motorDevice
->vTable
.postInit();
227 void motorPostInitNull(void)
231 static bool motorEnableNull(void)
236 static void motorDisableNull(void)
240 static bool motorIsEnabledNull(uint8_t index
)
247 bool motorDecodeTelemetryNull(void)
252 void motorWriteNull(uint8_t index
, float value
)
258 static void motorWriteIntNull(uint8_t index
, uint16_t value
)
264 void motorUpdateCompleteNull(void)
268 static void motorShutdownNull(void)
272 static float motorConvertFromExternalNull(uint16_t value
)
278 static uint16_t motorConvertToExternalNull(float value
)
284 static const motorVTable_t motorNullVTable
= {
285 .postInit
= motorPostInitNull
,
286 .enable
= motorEnableNull
,
287 .disable
= motorDisableNull
,
288 .isMotorEnabled
= motorIsEnabledNull
,
289 .decodeTelemetry
= motorDecodeTelemetryNull
,
290 .write
= motorWriteNull
,
291 .writeInt
= motorWriteIntNull
,
292 .updateComplete
= motorUpdateCompleteNull
,
293 .convertExternalToMotor
= motorConvertFromExternalNull
,
294 .convertMotorToExternal
= motorConvertToExternalNull
,
295 .shutdown
= motorShutdownNull
,
298 static motorDevice_t motorNullDevice
= {
299 .initialized
= false,
303 bool isMotorProtocolEnabled(void)
305 return motorProtocolEnabled
;
308 bool isMotorProtocolDshot(void)
310 return motorProtocolDshot
;
313 bool isMotorProtocolBidirDshot(void)
315 return isMotorProtocolDshot() && useDshotTelemetry
;
318 void motorDevInit(const motorDevConfig_t
*motorDevConfig
, uint16_t idlePulse
, uint8_t motorCount
)
320 memset(motors
, 0, sizeof(motors
));
322 bool useUnsyncedPwm
= motorDevConfig
->useUnsyncedPwm
;
324 if (isMotorProtocolEnabled()) {
325 if (!isMotorProtocolDshot()) {
326 motorDevice
= motorPwmDevInit(motorDevConfig
, idlePulse
, motorCount
, useUnsyncedPwm
);
330 #ifdef USE_DSHOT_BITBANG
331 if (isDshotBitbangActive(motorDevConfig
)) {
332 motorDevice
= dshotBitbangDevInit(motorDevConfig
, motorCount
);
336 motorDevice
= dshotPwmDevInit(motorDevConfig
, idlePulse
, motorCount
, useUnsyncedPwm
);
343 motorDevice
->count
= motorCount
;
344 motorDevice
->initialized
= true;
345 motorDevice
->motorEnableTimeMs
= 0;
346 motorDevice
->enabled
= false;
348 motorNullDevice
.vTable
= motorNullVTable
;
349 motorDevice
= &motorNullDevice
;
353 void motorDisable(void)
355 motorDevice
->vTable
.disable();
356 motorDevice
->enabled
= false;
357 motorDevice
->motorEnableTimeMs
= 0;
360 void motorEnable(void)
362 if (motorDevice
->initialized
&& motorDevice
->vTable
.enable()) {
363 motorDevice
->enabled
= true;
364 motorDevice
->motorEnableTimeMs
= millis();
368 float motorEstimateMaxRpm(void)
370 // Empirical testing found this relationship between estimated max RPM without props attached
371 // (unloaded) and measured max RPM with props attached (loaded), independent from prop size
372 float unloadedMaxRpm
= 0.01f
* getBatteryVoltage() * motorConfig()->kv
;
373 float loadDerating
= -5.44e-6f
* unloadedMaxRpm
+ 0.944f
;
375 return unloadedMaxRpm
* loadDerating
;
378 bool motorIsEnabled(void)
380 return motorDevice
->enabled
;
383 bool motorIsMotorEnabled(uint8_t index
)
385 return motorDevice
->vTable
.isMotorEnabled(index
);
389 timeMs_t
motorGetMotorEnableTimeMs(void)
391 return motorDevice
->motorEnableTimeMs
;
395 #ifdef USE_DSHOT_BITBANG
396 bool isDshotBitbangActive(const motorDevConfig_t
*motorDevConfig
)
398 #if defined(STM32F4) || defined(APM32F4)
399 return motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_ON
||
400 (motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_AUTO
&& motorDevConfig
->useDshotTelemetry
&& motorDevConfig
->motorPwmProtocol
!= PWM_TYPE_PROSHOT1000
);
402 return motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_ON
||
403 (motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_AUTO
&& motorDevConfig
->motorPwmProtocol
!= PWM_TYPE_PROSHOT1000
);