2 * This file is part of Betaflight.
4 * Betaflight is free software. You can redistribute this software
5 * and/or modify this software under the terms of the GNU General
6 * Public License as published by the Free Software Foundation,
7 * either version 3 of the License, or (at your option) any later
10 * Betaflight is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this software.
19 * If not, see <http://www.gnu.org/licenses/>.
31 #include "drivers/io.h"
32 #include "drivers/motor.h"
33 #include "drivers/pwm_output.h"
34 #include "drivers/time.h"
35 #include "drivers/timer.h"
39 FAST_DATA_ZERO_INIT pwmOutputPort_t motors
[MAX_SUPPORTED_MOTORS
];
41 static void pwmOCConfig(TMR_TypeDef
*tim
, uint8_t channel
, uint16_t value
, uint8_t output
)
43 TMR_HandleTypeDef
* Handle
= timerFindTimerHandle(tim
);
44 if (Handle
== NULL
) return;
46 TMR_OC_InitTypeDef TMR_OCInitStructure
;
48 TMR_OCInitStructure
.OCMode
= TMR_OCMODE_PWM1
;
49 TMR_OCInitStructure
.OCIdleState
= TMR_OCIDLESTATE_SET
;
50 TMR_OCInitStructure
.OCPolarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OCPOLARITY_LOW
: TMR_OCPOLARITY_HIGH
;
51 TMR_OCInitStructure
.OCNIdleState
= TMR_OCNIDLESTATE_SET
;
52 TMR_OCInitStructure
.OCNPolarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OCNPOLARITY_LOW
: TMR_OCNPOLARITY_HIGH
;
53 TMR_OCInitStructure
.Pulse
= value
;
54 TMR_OCInitStructure
.OCFastMode
= TMR_OCFAST_DISABLE
;
56 DAL_TMR_PWM_ConfigChannel(Handle
, &TMR_OCInitStructure
, channel
);
59 void pwmOutConfig(timerChannel_t
*channel
, const timerHardware_t
*timerHardware
, uint32_t hz
, uint16_t period
, uint16_t value
, uint8_t inversion
)
61 TMR_HandleTypeDef
* Handle
= timerFindTimerHandle(timerHardware
->tim
);
62 if (Handle
== NULL
) return;
64 configTimeBase(timerHardware
->tim
, period
, hz
);
65 pwmOCConfig(timerHardware
->tim
,
66 timerHardware
->channel
,
68 inversion
? timerHardware
->output
^ TIMER_OUTPUT_INVERTED
: timerHardware
->output
71 if (timerHardware
->output
& TIMER_OUTPUT_N_CHANNEL
)
72 DAL_TMREx_PWMN_Start(Handle
, timerHardware
->channel
);
74 DAL_TMR_PWM_Start(Handle
, timerHardware
->channel
);
75 DAL_TMR_Base_Start(Handle
);
77 channel
->ccr
= timerChCCR(timerHardware
);
79 channel
->tim
= timerHardware
->tim
;
84 static FAST_DATA_ZERO_INIT motorDevice_t motorPwmDevice
;
86 static void pwmWriteUnused(uint8_t index
, float value
)
92 static void pwmWriteStandard(uint8_t index
, float value
)
94 /* TODO: move value to be a number between 0-1 (i.e. percent throttle from mixer) */
95 *motors
[index
].channel
.ccr
= lrintf((value
* motors
[index
].pulseScale
) + motors
[index
].pulseOffset
);
98 void pwmShutdownPulsesForAllMotors(void)
100 for (int index
= 0; index
< motorPwmDevice
.count
; index
++) {
101 // Set the compare register to 0, which stops the output pulsing if the timer overflows
102 if (motors
[index
].channel
.ccr
) {
103 *motors
[index
].channel
.ccr
= 0;
108 void pwmDisableMotors(void)
110 pwmShutdownPulsesForAllMotors();
113 static motorVTable_t motorPwmVTable
;
114 bool pwmEnableMotors(void)
116 /* check motors can be enabled */
117 return (motorPwmVTable
.write
!= &pwmWriteUnused
);
120 bool pwmIsMotorEnabled(uint8_t index
)
122 return motors
[index
].enabled
;
125 static void pwmCompleteOneshotMotorUpdate(void)
127 for (int index
= 0; index
< motorPwmDevice
.count
; index
++) {
128 if (motors
[index
].forceOverflow
) {
129 timerForceOverflow(motors
[index
].channel
.tim
);
131 // Set the compare register to 0, which stops the output pulsing if the timer overflows before the main loop completes again.
132 // This compare register will be set to the output value on the next main loop.
133 *motors
[index
].channel
.ccr
= 0;
137 static float pwmConvertFromExternal(uint16_t externalValue
)
139 return (float)externalValue
;
142 static uint16_t pwmConvertToExternal(float motorValue
)
144 return (uint16_t)motorValue
;
147 static motorVTable_t motorPwmVTable
= {
148 .postInit
= motorPostInitNull
,
149 .enable
= pwmEnableMotors
,
150 .disable
= pwmDisableMotors
,
151 .isMotorEnabled
= pwmIsMotorEnabled
,
152 .shutdown
= pwmShutdownPulsesForAllMotors
,
153 .convertExternalToMotor
= pwmConvertFromExternal
,
154 .convertMotorToExternal
= pwmConvertToExternal
,
157 motorDevice_t
*motorPwmDevInit(const motorDevConfig_t
*motorConfig
, uint16_t idlePulse
, uint8_t motorCount
, bool useUnsyncedPwm
)
159 memset(motors
, 0, sizeof(motors
));
161 motorPwmDevice
.vTable
= motorPwmVTable
;
165 switch (motorConfig
->motorPwmProtocol
) {
167 case PWM_TYPE_ONESHOT125
:
171 case PWM_TYPE_ONESHOT42
:
175 case PWM_TYPE_MULTISHOT
:
179 case PWM_TYPE_BRUSHED
:
181 useUnsyncedPwm
= true;
184 case PWM_TYPE_STANDARD
:
187 useUnsyncedPwm
= true;
192 motorPwmDevice
.vTable
.write
= pwmWriteStandard
;
193 motorPwmDevice
.vTable
.decodeTelemetry
= motorDecodeTelemetryNull
;
194 motorPwmDevice
.vTable
.updateComplete
= useUnsyncedPwm
? motorUpdateCompleteNull
: pwmCompleteOneshotMotorUpdate
;
196 for (int motorIndex
= 0; motorIndex
< MAX_SUPPORTED_MOTORS
&& motorIndex
< motorCount
; motorIndex
++) {
197 const unsigned reorderedMotorIndex
= motorConfig
->motorOutputReordering
[motorIndex
];
198 const ioTag_t tag
= motorConfig
->ioTags
[reorderedMotorIndex
];
199 const timerHardware_t
*timerHardware
= timerAllocate(tag
, OWNER_MOTOR
, RESOURCE_INDEX(reorderedMotorIndex
));
201 if (timerHardware
== NULL
) {
202 /* not enough motors initialised for the mixer or a break in the motors */
203 motorPwmDevice
.vTable
.write
= &pwmWriteUnused
;
204 motorPwmDevice
.vTable
.updateComplete
= motorUpdateCompleteNull
;
205 /* TODO: block arming and add reason system cannot arm */
209 motors
[motorIndex
].io
= IOGetByTag(tag
);
210 IOInit(motors
[motorIndex
].io
, OWNER_MOTOR
, RESOURCE_INDEX(reorderedMotorIndex
));
212 IOConfigGPIOAF(motors
[motorIndex
].io
, IOCFG_AF_PP
, timerHardware
->alternateFunction
);
214 /* standard PWM outputs */
215 // margin of safety is 4 periods when unsynced
216 const unsigned pwmRateHz
= useUnsyncedPwm
? motorConfig
->motorPwmRate
: ceilf(1 / ((sMin
+ sLen
) * 4));
218 const uint32_t clock
= timerClock(timerHardware
->tim
);
219 /* used to find the desired timer frequency for max resolution */
220 const unsigned prescaler
= ((clock
/ pwmRateHz
) + 0xffff) / 0x10000; /* rounding up */
221 const uint32_t hz
= clock
/ prescaler
;
222 const unsigned period
= useUnsyncedPwm
? hz
/ pwmRateHz
: 0xffff;
225 if brushed then it is the entire length of the period.
226 TODO: this can be moved back to periodMin and periodLen
227 once mixer outputs a 0..1 float value.
229 motors
[motorIndex
].pulseScale
= ((motorConfig
->motorPwmProtocol
== PWM_TYPE_BRUSHED
) ? period
: (sLen
* hz
)) / 1000.0f
;
230 motors
[motorIndex
].pulseOffset
= (sMin
* hz
) - (motors
[motorIndex
].pulseScale
* 1000);
232 pwmOutConfig(&motors
[motorIndex
].channel
, timerHardware
, hz
, period
, idlePulse
, motorConfig
->motorPwmInversion
);
234 bool timerAlreadyUsed
= false;
235 for (int i
= 0; i
< motorIndex
; i
++) {
236 if (motors
[i
].channel
.tim
== motors
[motorIndex
].channel
.tim
) {
237 timerAlreadyUsed
= true;
241 motors
[motorIndex
].forceOverflow
= !timerAlreadyUsed
;
242 motors
[motorIndex
].enabled
= true;
245 return &motorPwmDevice
;
248 pwmOutputPort_t
*pwmGetMotors(void)
254 static pwmOutputPort_t servos
[MAX_SUPPORTED_SERVOS
];
256 void pwmWriteServo(uint8_t index
, float value
)
258 if (index
< MAX_SUPPORTED_SERVOS
&& servos
[index
].channel
.ccr
) {
259 *servos
[index
].channel
.ccr
= lrintf(value
);
263 void servoDevInit(const servoDevConfig_t
*servoConfig
)
265 for (uint8_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
266 const ioTag_t tag
= servoConfig
->ioTags
[servoIndex
];
272 servos
[servoIndex
].io
= IOGetByTag(tag
);
274 IOInit(servos
[servoIndex
].io
, OWNER_SERVO
, RESOURCE_INDEX(servoIndex
));
276 const timerHardware_t
*timer
= timerAllocate(tag
, OWNER_SERVO
, RESOURCE_INDEX(servoIndex
));
279 /* flag failure and disable ability to arm */
283 IOConfigGPIOAF(servos
[servoIndex
].io
, IOCFG_AF_PP
, timer
->alternateFunction
);
285 pwmOutConfig(&servos
[servoIndex
].channel
, timer
, PWM_TIMER_1MHZ
, PWM_TIMER_1MHZ
/ servoConfig
->servoPwmRate
, servoConfig
->servoCenterPulse
, 0);
286 servos
[servoIndex
].enabled
= true;
291 #endif // USE_PWM_OUTPUT