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 motorDevice
->vTable
.shutdown();
55 motorDevice
->enabled
= false;
56 motorDevice
->motorEnableTimeMs
= 0;
57 motorDevice
->initialized
= false;
58 delayMicroseconds(1500);
61 void motorWriteAll(float *values
)
64 if (motorDevice
->enabled
) {
65 #ifdef USE_DSHOT_BITBANG
66 if (useDshotTelemetry
&& isDshotBitbangActive(&motorConfig()->dev
)) {
67 // Initialise the output buffers
68 if (motorDevice
->vTable
.updateInit
) {
69 motorDevice
->vTable
.updateInit();
72 // Update the motor data
73 for (int i
= 0; i
< motorDevice
->count
; i
++) {
74 motorDevice
->vTable
.write(i
, values
[i
]);
77 // Don't attempt to write commands to the motors if telemetry is still being received
78 if (motorDevice
->vTable
.telemetryWait
) {
79 (void)motorDevice
->vTable
.telemetryWait();
82 // Trigger the transmission of the motor data
83 motorDevice
->vTable
.updateComplete();
85 // Perform the decode of the last data received
86 // New data will be received once the send of motor data, triggered above, completes
87 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
88 motorDevice
->vTable
.decodeTelemetry();
93 // Perform the decode of the last data received
94 // New data will be received once the send of motor data, triggered above, completes
95 #if defined(USE_DSHOT) && defined(USE_DSHOT_TELEMETRY)
96 motorDevice
->vTable
.decodeTelemetry();
99 // Update the motor data
100 for (int i
= 0; i
< motorDevice
->count
; i
++) {
101 motorDevice
->vTable
.write(i
, values
[i
]);
104 // Trigger the transmission of the motor data
105 motorDevice
->vTable
.updateComplete();
114 unsigned motorDeviceCount(void)
116 return motorDevice
->count
;
119 motorVTable_t
*motorGetVTable(void)
121 return &motorDevice
->vTable
;
124 // This is not motor generic anymore; should be moved to analog pwm module
125 static void analogInitEndpoints(const motorConfig_t
*motorConfig
, float outputLimit
, float *outputLow
, float *outputHigh
, float *disarm
, float *deadbandMotor3dHigh
, float *deadbandMotor3dLow
)
127 if (featureIsEnabled(FEATURE_3D
)) {
128 float outputLimitOffset
= (flight3DConfig()->limit3d_high
- flight3DConfig()->limit3d_low
) * (1 - outputLimit
) / 2;
129 *disarm
= flight3DConfig()->neutral3d
;
130 *outputLow
= flight3DConfig()->limit3d_low
+ outputLimitOffset
;
131 *outputHigh
= flight3DConfig()->limit3d_high
- outputLimitOffset
;
132 *deadbandMotor3dHigh
= flight3DConfig()->deadband3d_high
;
133 *deadbandMotor3dLow
= flight3DConfig()->deadband3d_low
;
135 *disarm
= motorConfig
->mincommand
;
136 *outputLow
= motorConfig
->minthrottle
;
137 *outputHigh
= motorConfig
->maxthrottle
- ((motorConfig
->maxthrottle
- motorConfig
->minthrottle
) * (1 - outputLimit
));
141 bool checkMotorProtocolEnabled(const motorDevConfig_t
*motorDevConfig
, bool *isProtocolDshot
)
143 bool enabled
= false;
144 bool isDshot
= false;
146 switch (motorDevConfig
->motorPwmProtocol
) {
147 case PWM_TYPE_STANDARD
:
148 case PWM_TYPE_ONESHOT125
:
149 case PWM_TYPE_ONESHOT42
:
150 case PWM_TYPE_MULTISHOT
:
151 case PWM_TYPE_BRUSHED
:
156 case PWM_TYPE_DSHOT150
:
157 case PWM_TYPE_DSHOT300
:
158 case PWM_TYPE_DSHOT600
:
159 case PWM_TYPE_PROSHOT1000
:
168 if (isProtocolDshot
) {
169 *isProtocolDshot
= isDshot
;
175 static void checkMotorProtocol(const motorDevConfig_t
*motorDevConfig
)
177 motorProtocolEnabled
= checkMotorProtocolEnabled(motorDevConfig
, &motorProtocolDshot
);
180 // End point initialization is called from mixerInit before motorDevInit; can't use vtable...
181 void motorInitEndpoints(const motorConfig_t
*motorConfig
, float outputLimit
, float *outputLow
, float *outputHigh
, float *disarm
, float *deadbandMotor3dHigh
, float *deadbandMotor3dLow
)
183 checkMotorProtocol(&motorConfig
->dev
);
185 if (isMotorProtocolEnabled()) {
186 if (!isMotorProtocolDshot()) {
187 analogInitEndpoints(motorConfig
, outputLimit
, outputLow
, outputHigh
, disarm
, deadbandMotor3dHigh
, deadbandMotor3dLow
);
191 dshotInitEndpoints(motorConfig
, outputLimit
, outputLow
, outputHigh
, disarm
, deadbandMotor3dHigh
, deadbandMotor3dLow
);
197 float motorConvertFromExternal(uint16_t externalValue
)
199 return motorDevice
->vTable
.convertExternalToMotor(externalValue
);
202 uint16_t motorConvertToExternal(float motorValue
)
204 return motorDevice
->vTable
.convertMotorToExternal(motorValue
);
207 void motorPostInit(void)
209 motorDevice
->vTable
.postInit();
212 void motorPostInitNull(void)
216 static bool motorEnableNull(void)
221 static void motorDisableNull(void)
225 static bool motorIsEnabledNull(uint8_t index
)
232 bool motorDecodeTelemetryNull(void)
237 void motorWriteNull(uint8_t index
, float value
)
243 static void motorWriteIntNull(uint8_t index
, uint16_t value
)
249 void motorUpdateCompleteNull(void)
253 static void motorShutdownNull(void)
257 static float motorConvertFromExternalNull(uint16_t value
)
263 static uint16_t motorConvertToExternalNull(float value
)
269 static const motorVTable_t motorNullVTable
= {
270 .postInit
= motorPostInitNull
,
271 .enable
= motorEnableNull
,
272 .disable
= motorDisableNull
,
273 .isMotorEnabled
= motorIsEnabledNull
,
274 .decodeTelemetry
= motorDecodeTelemetryNull
,
275 .write
= motorWriteNull
,
276 .writeInt
= motorWriteIntNull
,
277 .updateComplete
= motorUpdateCompleteNull
,
278 .convertExternalToMotor
= motorConvertFromExternalNull
,
279 .convertMotorToExternal
= motorConvertToExternalNull
,
280 .shutdown
= motorShutdownNull
,
283 static motorDevice_t motorNullDevice
= {
284 .initialized
= false,
288 bool isMotorProtocolEnabled(void)
290 return motorProtocolEnabled
;
293 bool isMotorProtocolDshot(void)
295 return motorProtocolDshot
;
298 bool isMotorProtocolBidirDshot(void)
300 return isMotorProtocolDshot() && useDshotTelemetry
;
303 void motorDevInit(const motorDevConfig_t
*motorDevConfig
, uint16_t idlePulse
, uint8_t motorCount
)
305 memset(motors
, 0, sizeof(motors
));
307 bool useUnsyncedPwm
= motorDevConfig
->useUnsyncedPwm
;
309 if (isMotorProtocolEnabled()) {
310 if (!isMotorProtocolDshot()) {
311 motorDevice
= motorPwmDevInit(motorDevConfig
, idlePulse
, motorCount
, useUnsyncedPwm
);
315 #ifdef USE_DSHOT_BITBANG
316 if (isDshotBitbangActive(motorDevConfig
)) {
317 motorDevice
= dshotBitbangDevInit(motorDevConfig
, motorCount
);
321 motorDevice
= dshotPwmDevInit(motorDevConfig
, idlePulse
, motorCount
, useUnsyncedPwm
);
328 motorDevice
->count
= motorCount
;
329 motorDevice
->initialized
= true;
330 motorDevice
->motorEnableTimeMs
= 0;
331 motorDevice
->enabled
= false;
333 motorNullDevice
.vTable
= motorNullVTable
;
334 motorDevice
= &motorNullDevice
;
338 void motorDisable(void)
340 motorDevice
->vTable
.disable();
341 motorDevice
->enabled
= false;
342 motorDevice
->motorEnableTimeMs
= 0;
345 void motorEnable(void)
347 if (motorDevice
->initialized
&& motorDevice
->vTable
.enable()) {
348 motorDevice
->enabled
= true;
349 motorDevice
->motorEnableTimeMs
= millis();
353 float motorEstimateMaxRpm(void)
355 // Empirical testing found this relationship between estimated max RPM without props attached
356 // (unloaded) and measured max RPM with props attached (loaded), independent from prop size
357 float unloadedMaxRpm
= 0.01f
* getBatteryVoltage() * motorConfig()->kv
;
358 float loadDerating
= -5.44e-6f
* unloadedMaxRpm
+ 0.944f
;
360 return unloadedMaxRpm
* loadDerating
;
363 bool motorIsEnabled(void)
365 return motorDevice
->enabled
;
368 bool motorIsMotorEnabled(uint8_t index
)
370 return motorDevice
->vTable
.isMotorEnabled(index
);
374 timeMs_t
motorGetMotorEnableTimeMs(void)
376 return motorDevice
->motorEnableTimeMs
;
380 #ifdef USE_DSHOT_BITBANG
381 bool isDshotBitbangActive(const motorDevConfig_t
*motorDevConfig
)
384 return motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_ON
||
385 (motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_AUTO
&& motorDevConfig
->useDshotTelemetry
&& motorDevConfig
->motorPwmProtocol
!= PWM_TYPE_PROSHOT1000
);
387 return motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_ON
||
388 (motorDevConfig
->useDshotBitbang
== DSHOT_BITBANG_AUTO
&& motorDevConfig
->motorPwmProtocol
!= PWM_TYPE_PROSHOT1000
);
393 float getDigitalIdleOffset(const motorConfig_t
*motorConfig
)
395 return CONVERT_PARAMETER_TO_PERCENT(motorConfig
->digitalIdleOffsetValue
* 0.01f
);