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_impl.h"
33 #include "drivers/pwm_output.h"
34 #include "drivers/time.h"
35 #include "drivers/timer.h"
39 static void pwmOCConfig(tmr_type
*tim
, uint8_t channel
, uint16_t value
, uint8_t output
)
41 tmr_output_config_type tmr_OCInitStruct
;
42 tmr_output_default_para_init(&tmr_OCInitStruct
);
43 tmr_OCInitStruct
.oc_mode
= TMR_OUTPUT_CONTROL_PWM_MODE_A
;
45 if (output
& TIMER_OUTPUT_N_CHANNEL
) {
46 tmr_OCInitStruct
.occ_output_state
= TRUE
;
47 tmr_OCInitStruct
.occ_idle_state
= FALSE
;
48 tmr_OCInitStruct
.occ_polarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OUTPUT_ACTIVE_LOW
: TMR_OUTPUT_ACTIVE_HIGH
;
50 tmr_OCInitStruct
.oc_output_state
= TRUE
;
51 tmr_OCInitStruct
.oc_idle_state
= TRUE
;
52 tmr_OCInitStruct
.oc_polarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OUTPUT_ACTIVE_LOW
: TMR_OUTPUT_ACTIVE_HIGH
;
54 tmr_channel_value_set(tim
, (channel
-1)*2, value
);
55 tmr_output_channel_config(tim
,(channel
-1)*2, &tmr_OCInitStruct
);
56 tmr_output_channel_buffer_enable(tim
, ((channel
-1)*2),TRUE
);
59 void pwmOutConfig(timerChannel_t
*channel
, const timerHardware_t
*timerHardware
, uint32_t hz
, uint16_t period
, uint16_t value
, uint8_t inversion
)
61 configTimeBase(timerHardware
->tim
, period
, hz
);
62 pwmOCConfig(timerHardware
->tim
,
63 timerHardware
->channel
,
65 inversion
? timerHardware
->output
^ TIMER_OUTPUT_INVERTED
: timerHardware
->output
68 tmr_output_enable(timerHardware
->tim
, TRUE
);
69 tmr_counter_enable(timerHardware
->tim
, TRUE
);
71 channel
->ccr
= timerChCCR(timerHardware
);
73 channel
->tim
= timerHardware
->tim
;
78 static FAST_DATA_ZERO_INIT motorDevice_t
*pwmMotorDevice
;
80 static void pwmWriteStandard(uint8_t index
, float value
)
82 /* TODO: move value to be a number between 0-1 (i.e. percent throttle from mixer) */
83 *motors
[index
].channel
.ccr
= lrintf((value
* motors
[index
].pulseScale
) + motors
[index
].pulseOffset
);
86 static void pwmShutdownPulsesForAllMotors(void)
88 for (int index
= 0; index
< pwmMotorCount
; index
++) {
89 // Set the compare register to 0, which stops the output pulsing if the timer overflows
90 if (motors
[index
].channel
.ccr
) {
91 *motors
[index
].channel
.ccr
= 0;
96 static void pwmDisableMotors(void)
98 pwmShutdownPulsesForAllMotors();
101 static motorVTable_t motorPwmVTable
;
102 static bool pwmEnableMotors(void)
104 /* check motors can be enabled */
105 return (pwmMotorDevice
->vTable
);
108 static bool pwmIsMotorEnabled(unsigned index
)
110 return motors
[index
].enabled
;
113 static bool useContinuousUpdate
= true;
115 static void pwmCompleteMotorUpdate(void)
117 if (useContinuousUpdate
) {
121 for (int index
= 0; index
< pwmMotorCount
; index
++) {
122 if (motors
[index
].forceOverflow
) {
123 timerForceOverflow(motors
[index
].channel
.tim
);
125 // Set the compare register to 0, which stops the output pulsing if the timer overflows before the main loop completes again.
126 // This compare register will be set to the output value on the next main loop.
127 *motors
[index
].channel
.ccr
= 0;
131 static float pwmConvertFromExternal(uint16_t externalValue
)
133 return (float)externalValue
;
136 static uint16_t pwmConvertToExternal(float motorValue
)
138 return (uint16_t)motorValue
;
141 static motorVTable_t motorPwmVTable
= {
142 .postInit
= motorPostInitNull
,
143 .enable
= pwmEnableMotors
,
144 .disable
= pwmDisableMotors
,
145 .isMotorEnabled
= pwmIsMotorEnabled
,
146 .shutdown
= pwmShutdownPulsesForAllMotors
,
147 .convertExternalToMotor
= pwmConvertFromExternal
,
148 .convertMotorToExternal
= pwmConvertToExternal
,
149 .write
= pwmWriteStandard
,
150 .decodeTelemetry
= motorDecodeTelemetryNull
,
151 .updateComplete
= pwmCompleteMotorUpdate
,
152 .requestTelemetry
= NULL
,
156 bool motorPwmDevInit(motorDevice_t
*device
, const motorDevConfig_t
*motorConfig
, uint16_t idlePulse
)
158 memset(motors
, 0, sizeof(motors
));
164 device
->vTable
= &motorPwmVTable
;
165 pwmMotorDevice
= device
;
166 pwmMotorCount
= device
->count
;
167 useContinuousUpdate
= motorConfig
->useContinuousUpdate
;
171 switch (motorConfig
->motorProtocol
) {
173 case MOTOR_PROTOCOL_ONESHOT125
:
177 case MOTOR_PROTOCOL_ONESHOT42
:
181 case MOTOR_PROTOCOL_MULTISHOT
:
185 case MOTOR_PROTOCOL_BRUSHED
:
187 useContinuousUpdate
= true;
190 case MOTOR_PROTOCOL_PWM
:
193 useContinuousUpdate
= true;
198 for (int motorIndex
= 0; motorIndex
< MAX_SUPPORTED_MOTORS
&& motorIndex
< pwmMotorCount
; motorIndex
++) {
199 const unsigned reorderedMotorIndex
= motorConfig
->motorOutputReordering
[motorIndex
];
200 const ioTag_t tag
= motorConfig
->ioTags
[reorderedMotorIndex
];
201 const timerHardware_t
*timerHardware
= timerAllocate(tag
, OWNER_MOTOR
, RESOURCE_INDEX(reorderedMotorIndex
));
203 if (timerHardware
== NULL
) {
204 /* not enough motors initialised for the mixer or a break in the motors */
205 device
->vTable
= NULL
;
207 /* TODO: block arming and add reason system cannot arm */
211 motors
[motorIndex
].io
= IOGetByTag(tag
);
212 IOInit(motors
[motorIndex
].io
, OWNER_MOTOR
, RESOURCE_INDEX(reorderedMotorIndex
));
214 IOConfigGPIOAF(motors
[motorIndex
].io
, IOCFG_AF_PP
, timerHardware
->alternateFunction
);
216 /* standard PWM outputs */
217 // margin of safety is 4 periods when unsynced
218 const unsigned pwmRateHz
= useContinuousUpdate
? motorConfig
->motorPwmRate
: ceilf(1 / ((sMin
+ sLen
) * 4));
220 const uint32_t clock
= timerClock(timerHardware
->tim
);
221 /* used to find the desired timer frequency for max resolution */
222 const unsigned prescaler
= ((clock
/ pwmRateHz
) + 0xffff) / 0x10000; /* rounding up */
223 const uint32_t hz
= clock
/ prescaler
;
224 const unsigned period
= useContinuousUpdate
? hz
/ pwmRateHz
: 0xffff;
227 if brushed then it is the entire length of the period.
228 TODO: this can be moved back to periodMin and periodLen
229 once mixer outputs a 0..1 float value.
231 motors
[motorIndex
].pulseScale
= ((motorConfig
->motorProtocol
== MOTOR_PROTOCOL_BRUSHED
) ? period
: (sLen
* hz
)) / 1000.0f
;
232 motors
[motorIndex
].pulseOffset
= (sMin
* hz
) - (motors
[motorIndex
].pulseScale
* 1000);
234 pwmOutConfig(&motors
[motorIndex
].channel
, timerHardware
, hz
, period
, idlePulse
, motorConfig
->motorInversion
);
236 bool timerAlreadyUsed
= false;
237 for (int i
= 0; i
< motorIndex
; i
++) {
238 if (motors
[i
].channel
.tim
== motors
[motorIndex
].channel
.tim
) {
239 timerAlreadyUsed
= true;
243 motors
[motorIndex
].forceOverflow
= !timerAlreadyUsed
;
244 motors
[motorIndex
].enabled
= true;
249 pwmOutputPort_t
*pwmGetMotors(void)
255 static pwmOutputPort_t servos
[MAX_SUPPORTED_SERVOS
];
257 void pwmWriteServo(uint8_t index
, float value
)
259 if (index
< MAX_SUPPORTED_SERVOS
&& servos
[index
].channel
.ccr
) {
260 *servos
[index
].channel
.ccr
= lrintf(value
);
264 void servoDevInit(const servoDevConfig_t
*servoConfig
)
266 for (uint8_t servoIndex
= 0; servoIndex
< MAX_SUPPORTED_SERVOS
; servoIndex
++) {
267 const ioTag_t tag
= servoConfig
->ioTags
[servoIndex
];
273 servos
[servoIndex
].io
= IOGetByTag(tag
);
275 IOInit(servos
[servoIndex
].io
, OWNER_SERVO
, RESOURCE_INDEX(servoIndex
));
277 const timerHardware_t
*timer
= timerAllocate(tag
, OWNER_SERVO
, RESOURCE_INDEX(servoIndex
));
280 /* flag failure and disable ability to arm */
284 IOConfigGPIOAF(servos
[servoIndex
].io
, IOCFG_AF_PP
, timer
->alternateFunction
);
286 pwmOutConfig(&servos
[servoIndex
].channel
, timer
, PWM_TIMER_1MHZ
, PWM_TIMER_1MHZ
/ servoConfig
->servoPwmRate
, servoConfig
->servoCenterPulse
, 0);
287 servos
[servoIndex
].enabled
= true;
291 #endif // USE_PWM_OUTPUT