2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
31 #include "pwm_output.h"
33 #include "pwm_mapping.h"
35 void pwmBrushedMotorConfig(const timerHardware_t
*timerHardware
, uint8_t motorIndex
, uint16_t motorPwmRate
, uint16_t idlePulse
);
36 void pwmBrushlessMotorConfig(const timerHardware_t
*timerHardware
, uint8_t motorIndex
, uint16_t motorPwmRate
, uint16_t idlePulse
);
37 void pwmOneshotMotorConfig(const timerHardware_t
*timerHardware
, uint8_t motorIndex
);
38 void pwmServoConfig(const timerHardware_t
*timerHardware
, uint8_t servoIndex
, uint16_t servoPwmRate
, uint16_t servoCenterPulse
);
43 Note: this documentation is only valid for STM32F10x, for STM32F30x please read the code itself.
45 1) multirotor PPM input
47 PWM5..8 used for motors
48 PWM9..10 used for servo or else motors
49 PWM11..14 used for motors
51 2) multirotor PPM input with more servos
53 PWM5..8 used for motors
54 PWM9..10 used for servo or else motors
55 PWM11..14 used for servos
57 2) multirotor PWM input
58 PWM1..8 used for input
59 PWM9..10 used for servo or else motors
60 PWM11..14 used for motors
62 3) airplane / flying wing w/PWM
63 PWM1..8 used for input
64 PWM9 used for motor throttle +PWM10 for 2nd motor
65 PWM11.14 used for servos
67 4) airplane / flying wing with PPM
69 PWM5..8 used for servos
70 PWM9 used for motor throttle +PWM10 for 2nd motor
71 PWM11.14 used for servos
74 const uint16_t * const hardwareMaps
[] = {
81 static pwmIOConfiguration_t pwmIOConfiguration
;
83 pwmIOConfiguration_t
*pwmGetOutputConfiguration(void)
85 return &pwmIOConfiguration
;
88 bool CheckGPIOPin(ioTag_t tag
, GPIO_TypeDef
*gpio
, uint16_t pin
)
90 return IO_GPIOBYTAG(tag
) == gpio
&& IO_PINBYTAG(tag
) == pin
;
93 bool CheckGPIOPinSource(ioTag_t tag
, GPIO_TypeDef
*gpio
, uint16_t pin
)
95 return IO_GPIOBYTAG(tag
) == gpio
&& IO_GPIO_PinSource(IOGetByTag(tag
)) == pin
;
98 pwmIOConfiguration_t
*pwmInit(drv_pwm_config_t
*init
)
100 #ifndef SKIP_RX_PWM_PPM
101 int channelIndex
= 0;
104 memset(&pwmIOConfiguration
, 0, sizeof(pwmIOConfiguration
));
106 // this is pretty hacky shit, but it will do for now. array of 4 config maps, [ multiPWM multiPPM airPWM airPPM ]
109 i
= 2; // switch to air hardware config
110 if (init
->usePPM
|| init
->useSerialRx
)
111 i
++; // next index is for PPM
113 const uint16_t *setup
= hardwareMaps
[i
];
115 for (i
= 0; i
< USABLE_TIMER_CHANNEL_COUNT
&& setup
[i
] != 0xFFFF; i
++) {
116 uint8_t timerIndex
= setup
[i
] & 0x00FF;
117 uint8_t type
= (setup
[i
] & 0xFF00) >> 8;
119 const timerHardware_t
*timerHardwarePtr
= &timerHardware
[timerIndex
];
121 #ifdef OLIMEXINO_UNCUT_LED2_E_JUMPER
122 // PWM2 is connected to LED2 on the board and cannot be connected unless you cut LED2_E
123 if (timerIndex
== PWM2
) {
124 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
131 if (init
->useUART2
&& (timerIndex
== PWM3
|| timerIndex
== PWM4
)) {
132 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
137 #if defined(STM32F303xC) && defined(USE_UART3)
138 // skip UART3 ports (PB10/PB11)
139 if (init
->useUART3
&& (timerHardwarePtr
->tag
== IO_TAG(UART3_TX_PIN
) || timerHardwarePtr
->tag
== IO_TAG(UART3_RX_PIN
))) {
140 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
145 #ifdef SOFTSERIAL_1_TIMER
146 if (init
->useSoftSerial
&& timerHardwarePtr
->tim
== SOFTSERIAL_1_TIMER
) {
147 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
151 #ifdef SOFTSERIAL_2_TIMER
152 if (init
->useSoftSerial
&& timerHardwarePtr
->tim
== SOFTSERIAL_2_TIMER
) {
153 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
159 // skip LED Strip output
160 if (init
->useLEDStrip
) {
161 if (timerHardwarePtr
->tim
== WS2811_TIMER
) {
162 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
165 #if defined(STM32F303xC) && defined(WS2811_PIN)
166 if (timerHardwarePtr
->tag
== IO_TAG(WS2811_PIN
)) {
167 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
176 if (init
->useVbat
&& timerHardwarePtr
->tag
== IO_TAG(VBAT_ADC_PIN
)) {
177 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
183 if (init
->useRSSIADC
&& timerHardwarePtr
->tag
== IO_TAG(RSSI_ADC_PIN
)) {
184 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
189 #ifdef CURRENT_METER_ADC_GPIO
190 if (init
->useCurrentMeterADC
&& timerHardwarePtr
->tag
== IO_TAG(CURRENT_METER_ADC_PIN
)) {
191 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
197 if (init
->useSonar
&&
199 timerHardwarePtr
->tag
== init
->sonarIOConfig
.triggerTag
||
200 timerHardwarePtr
->tag
== init
->sonarIOConfig
.echoTag
202 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
207 // hacks to allow current functionality
208 if (type
== MAP_TO_PWM_INPUT
&& !init
->useParallelPWM
)
211 if (type
== MAP_TO_PPM_INPUT
&& !init
->usePPM
)
215 if (init
->useServos
&& !init
->airplane
) {
217 // remap PWM9+10 as servos
218 if ((timerIndex
== PWM9
|| timerIndex
== PWM10
) && timerHardwarePtr
->tim
== TIM1
)
219 type
= MAP_TO_SERVO_OUTPUT
;
223 // remap outputs 1+2 (PWM2+3) as servos
224 if ((timerIndex
== PWM2
|| timerIndex
== PWM3
) && timerHardwarePtr
->tim
== TIM4
)
225 type
= MAP_TO_SERVO_OUTPUT
;
228 #if defined(COLIBRI_RACE) || defined(LUX_RACE)
229 // remap PWM1+2 as servos
230 if ((timerIndex
== PWM6
|| timerIndex
== PWM7
|| timerIndex
== PWM8
|| timerIndex
== PWM9
) && timerHardwarePtr
->tim
== TIM2
)
231 type
= MAP_TO_SERVO_OUTPUT
;
235 // remap PWM9+10 as servos
236 if ((timerIndex
== PWM9
|| timerIndex
== PWM10
) && timerHardwarePtr
->tim
== TIM1
)
237 type
= MAP_TO_SERVO_OUTPUT
;
241 // remap PWM1+2 as servos
242 if ((timerIndex
== PWM1
|| timerIndex
== PWM2
) && timerHardwarePtr
->tim
== TIM15
)
243 type
= MAP_TO_SERVO_OUTPUT
;
246 #if defined(SPRACINGF3)
247 // remap PWM15+16 as servos
248 if ((timerIndex
== PWM15
|| timerIndex
== PWM16
) && timerHardwarePtr
->tim
== TIM15
)
249 type
= MAP_TO_SERVO_OUTPUT
;
252 #if defined(SPRACINGF3MINI) || defined(OMNIBUS)
253 // remap PWM6+7 as servos
254 if ((timerIndex
== PWM6
|| timerIndex
== PWM7
) && timerHardwarePtr
->tim
== TIM15
)
255 type
= MAP_TO_SERVO_OUTPUT
;
258 #if defined(RCEXPLORERF3)
259 if (timerIndex
== PWM2
)
261 type
= MAP_TO_SERVO_OUTPUT
;
265 #if defined(SPRACINGF3EVO)
266 // remap PWM6+7 as servos
267 if ((timerIndex
== PWM8
|| timerIndex
== PWM9
) && timerHardwarePtr
->tim
== TIM3
)
268 type
= MAP_TO_SERVO_OUTPUT
;
271 #if (defined(STM32F3DISCOVERY) && !defined(CHEBUZZF3))
272 // remap PWM 5+6 or 9+10 as servos - softserial pin pairs require timer ports that use the same timer
273 if (init
->useSoftSerial
) {
274 if (timerIndex
== PWM5
|| timerIndex
== PWM6
)
275 type
= MAP_TO_SERVO_OUTPUT
;
277 if (timerIndex
== PWM9
|| timerIndex
== PWM10
)
278 type
= MAP_TO_SERVO_OUTPUT
;
283 // remap PWM 7+8 as servos
284 if (timerIndex
== PWM7
|| timerIndex
== PWM8
)
285 type
= MAP_TO_SERVO_OUTPUT
;
288 #if defined(SINGULARITY)
289 // remap PWM6+7 as servos
290 if (timerIndex
== PWM6
|| timerIndex
== PWM7
)
291 type
= MAP_TO_SERVO_OUTPUT
;
295 if (init
->useChannelForwarding
&& !init
->airplane
) {
296 #if defined(NAZE) && defined(WS2811_TIMER)
297 // if LED strip is active, PWM5-8 are unavailable, so map AUX1+AUX2 to PWM13+PWM14
298 if (init
->useLEDStrip
) {
299 if (timerIndex
>= PWM13
&& timerIndex
<= PWM14
) {
300 type
= MAP_TO_SERVO_OUTPUT
;
305 #if defined(SPRACINGF3) || defined(NAZE)
306 // remap PWM5..8 as servos when used in extended servo mode
307 if (timerIndex
>= PWM5
&& timerIndex
<= PWM8
)
308 type
= MAP_TO_SERVO_OUTPUT
;
315 // This part of code is unnecessary and can be removed - timer clash is resolved by forcing configuration with the same
316 // timer tick rate - PWM_TIMER_MHZ
318 if (init->useParallelPWM) {
319 // Skip PWM inputs that conflict with timers used outputs.
320 if ((type == MAP_TO_SERVO_OUTPUT || type == MAP_TO_MOTOR_OUTPUT) && (timerHardwarePtr->tim == TIM2 || timerHardwarePtr->tim == TIM3)) {
323 if (type == MAP_TO_PWM_INPUT && timerHardwarePtr->tim == TIM4) {
331 if (type
== MAP_TO_PPM_INPUT
) {
332 #ifndef SKIP_RX_PWM_PPM
334 if (init
->useOneshot
|| isMotorBrushed(init
->motorPwmRate
)) {
335 ppmAvoidPWMTimerClash(timerHardwarePtr
, TIM4
);
338 #if defined(SPARKY) || defined(ALIENFLIGHTF3)
339 if (init
->useOneshot
|| isMotorBrushed(init
->motorPwmRate
)) {
340 ppmAvoidPWMTimerClash(timerHardwarePtr
, TIM2
);
343 ppmInConfig(timerHardwarePtr
);
344 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_PPM
;
345 pwmIOConfiguration
.ppmInputCount
++;
347 addBootlogEvent6(BOOT_EVENT_TIMER_CH_MAPPED
, BOOT_EVENT_FLAGS_NONE
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 0);
349 } else if (type
== MAP_TO_PWM_INPUT
) {
350 #ifndef SKIP_RX_PWM_PPM
351 pwmInConfig(timerHardwarePtr
, channelIndex
);
352 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_PWM
;
353 pwmIOConfiguration
.pwmInputCount
++;
356 addBootlogEvent6(BOOT_EVENT_TIMER_CH_MAPPED
, BOOT_EVENT_FLAGS_NONE
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 1);
358 } else if (type
== MAP_TO_MOTOR_OUTPUT
) {
359 /* Check if we already configured maximum supported number of motors and skip the rest */
360 if (pwmIOConfiguration
.motorCount
>= MAX_MOTORS
) {
361 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 1);
365 #if defined(CC3D) && !defined(CC3D_PPM1)
366 if (init
->useOneshot
|| isMotorBrushed(init
->motorPwmRate
)) {
367 // Skip it if it would cause PPM capture timer to be reconfigured or manually overflowed
368 if (timerHardwarePtr
->tim
== TIM2
) {
369 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
374 if (init
->useOneshot
) {
376 pwmOneshotMotorConfig(timerHardwarePtr
, pwmIOConfiguration
.motorCount
);
377 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_MOTOR
| PWM_PF_OUTPUT_PROTOCOL_ONESHOT
|PWM_PF_OUTPUT_PROTOCOL_PWM
;
379 } else if (isMotorBrushed(init
->motorPwmRate
)) {
381 pwmBrushedMotorConfig(timerHardwarePtr
, pwmIOConfiguration
.motorCount
, init
->motorPwmRate
, init
->idlePulse
);
382 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_MOTOR
| PWM_PF_MOTOR_MODE_BRUSHED
| PWM_PF_OUTPUT_PROTOCOL_PWM
;
386 pwmBrushlessMotorConfig(timerHardwarePtr
, pwmIOConfiguration
.motorCount
, init
->motorPwmRate
, init
->idlePulse
);
387 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_MOTOR
| PWM_PF_OUTPUT_PROTOCOL_PWM
;
390 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].index
= pwmIOConfiguration
.motorCount
;
391 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].timerHardware
= timerHardwarePtr
;
393 pwmIOConfiguration
.motorCount
++;
395 addBootlogEvent6(BOOT_EVENT_TIMER_CH_MAPPED
, BOOT_EVENT_FLAGS_NONE
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 2);
396 } else if (type
== MAP_TO_SERVO_OUTPUT
) {
397 if (pwmIOConfiguration
.servoCount
>= MAX_SERVOS
) {
398 addBootlogEvent6(BOOT_EVENT_TIMER_CH_SKIPPED
, BOOT_EVENT_FLAGS_WARNING
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 2);
403 pwmServoConfig(timerHardwarePtr
, pwmIOConfiguration
.servoCount
, init
->servoPwmRate
, init
->servoCenterPulse
);
405 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].flags
= PWM_PF_SERVO
| PWM_PF_OUTPUT_PROTOCOL_PWM
;
406 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].index
= pwmIOConfiguration
.servoCount
;
407 pwmIOConfiguration
.ioConfigurations
[pwmIOConfiguration
.ioCount
].timerHardware
= timerHardwarePtr
;
409 pwmIOConfiguration
.servoCount
++;
411 addBootlogEvent6(BOOT_EVENT_TIMER_CH_MAPPED
, BOOT_EVENT_FLAGS_NONE
, i
, pwmIOConfiguration
.motorCount
, pwmIOConfiguration
.servoCount
, 3);
417 pwmIOConfiguration
.ioCount
++;
420 return &pwmIOConfiguration
;