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/>.
29 #include "build/debug.h"
31 #include "drivers/dma.h"
32 #include "drivers/dma_reqmap.h"
33 #include "drivers/io.h"
34 #include "drivers/nvic.h"
35 #include "drivers/rcc.h"
36 #include "drivers/time.h"
37 #include "drivers/timer.h"
38 #include "drivers/system.h"
40 #include "drivers/pwm_output.h"
41 #include "drivers/dshot.h"
42 #include "drivers/dshot_dpwm.h"
43 #include "drivers/dshot_command.h"
44 #include "drivers/pwm_output_dshot_shared.h"
47 * Convert from BF channel to AT32 constants for timer output channels
49 * The AT and ST apis take a different approach to naming channels, so just passing the bf
50 * channel number to the AT calls doesn't work. This function maps between them.
52 * @param bfChannel a channel number as used in timerHardware->channel (1 based)
53 * @param useNChannel indicates that the desired channel should be the complementary output (only available for 1 through 3)
54 * @return an AT32 tmr_channel_select_type constant
55 * XXX what to return for invalid inputs? The tmr_channel_select_type enum doesn't have a suitable value
57 * @see TIM_CH_TO_SELCHANNEL macro
59 tmr_channel_select_type
toCHSelectType(const uint8_t bfChannel
, const bool useNChannel
)
61 tmr_channel_select_type result
= TMR_SELECT_CHANNEL_1
; // XXX I don't like using ch 1 as a default result, but what to do?
64 // Complementary channels only available for 1 through 3
67 result
= TMR_SELECT_CHANNEL_1C
;
70 result
= TMR_SELECT_CHANNEL_2C
;
73 result
= TMR_SELECT_CHANNEL_3C
;
86 result
= TMR_SELECT_CHANNEL_1
;
89 result
= TMR_SELECT_CHANNEL_2
;
92 result
= TMR_SELECT_CHANNEL_3
;
95 result
= TMR_SELECT_CHANNEL_4
;
109 #ifdef USE_DSHOT_TELEMETRY
112 * Enable the timer channels for all motors
114 * Called once for every dshot update if telemetry is being used (not just enabled by #def)
115 * Called from pwm_output_dshot_shared.c pwmTelemetryDecode
117 void dshotEnableChannels(uint8_t motorCount
)
119 for (int i
= 0; i
< motorCount
; i
++) {
120 tmr_primary_mode_select(dmaMotors
[i
].timerHardware
->tim
, TMR_PRIMARY_SEL_COMPARE
);
122 tmr_channel_select_type atCh
= toCHSelectType(dmaMotors
[i
].timerHardware
->channel
, dmaMotors
[i
].output
& TIMER_OUTPUT_N_CHANNEL
);
123 tmr_channel_enable(dmaMotors
[i
].timerHardware
->tim
, atCh
, TRUE
);
127 #endif // USE_DSHOT_TELEMETRY
130 * Set the timer and dma of the specified motor for use as an output
132 * Called from pwmDshotMotorHardwareConfig in this file and also from
133 * pwmTelemetryDecode in src/main/drivers/pwm_output_dshot_shared.c
135 FAST_CODE
void pwmDshotSetDirectionOutput(
136 motorDmaOutput_t
* const motor
137 #ifndef USE_DSHOT_TELEMETRY
138 ,TIM_OCInitTypeDef
*pOcInit
, DMA_InitTypeDef
* pDmaInit
142 #ifdef USE_DSHOT_TELEMETRY
143 TIM_OCInitTypeDef
* pOcInit
= &motor
->ocInitStruct
;
144 DMA_InitTypeDef
* pDmaInit
= &motor
->dmaInitStruct
;
147 const timerHardware_t
* const timerHardware
= motor
->timerHardware
;
148 TIM_TypeDef
*timer
= timerHardware
->tim
;
149 const uint8_t channel
= timerHardware
->channel
; // BF channel index (1 based)
150 const bool useCompOut
= (timerHardware
->output
& TIMER_OUTPUT_N_CHANNEL
) != 0;
152 dmaResource_t
*dmaRef
= motor
->dmaRef
;
154 #if defined(USE_DSHOT_DMAR) && !defined(USE_DSHOT_TELEMETRY)
156 dmaRef
= timerHardware
->dmaTimUPRef
;
162 #ifdef USE_DSHOT_TELEMETRY
163 motor
->isInput
= false;
166 // Disable the preload buffer so that we can write to the comparison registers (CxDT) immediately
167 // This call has the channel -> AT32 channel selector mapping built in
168 timerOCPreloadConfig(timer
, channel
, FALSE
);
170 tmr_channel_enable(timer
, toCHSelectType(channel
, useCompOut
), FALSE
);
172 // The at32 apis do everything except actually put the channel in output mode, so we have to do that ourselves
173 // This is probably a bug in the at32 sdk, so the need for this code may go away in future releases
174 const uint8_t CH_OUTPUT
= 0;
177 timer
->cm1_output_bit
.c1c
= CH_OUTPUT
;
180 timer
->cm1_output_bit
.c2c
= CH_OUTPUT
;
183 timer
->cm2_output_bit
.c3c
= CH_OUTPUT
;
186 timer
->cm2_output_bit
.c4c
= CH_OUTPUT
;
190 timerOCInit(timer
, channel
, pOcInit
);
192 // On ST mcu this would be part of the ocInit struct, but on AT we have to do it seperately
193 { // local scope for variables
194 const bool useNChannel
= false; // tmr_channel_value_set only supports the normal channels
195 const tmr_channel_select_type atChannel
= toCHSelectType(timerHardware
->channel
, useNChannel
);
196 tmr_channel_value_set(timer
, atChannel
, 0);
199 tmr_channel_enable(timer
, toCHSelectType(channel
, useCompOut
), TRUE
);
200 timerOCPreloadConfig(timer
, channel
, TRUE
);
202 pDmaInit
->direction
= DMA_DIR_MEMORY_TO_PERIPHERAL
;
203 xDMA_Init(dmaRef
, pDmaInit
);
205 // Generate an interrupt when the transfer is complete
206 xDMA_ITConfig(dmaRef
, DMA_FDT_INT
, TRUE
);
211 #ifdef USE_DSHOT_TELEMETRY
213 * Set the timer and dma of the specified motor for use as an input
216 static void pwmDshotSetDirectionInput(motorDmaOutput_t
* const motor
)
218 DMA_InitTypeDef
* pDmaInit
= &motor
->dmaInitStruct
;
219 const timerHardware_t
* const timerHardware
= motor
->timerHardware
;
220 TIM_TypeDef
*timer
= timerHardware
->tim
;
221 dmaResource_t
*dmaRef
= motor
->dmaRef
;
225 motor
->isInput
= true;
227 inputStampUs
= micros();
230 tmr_period_buffer_enable(timer
, FALSE
);
232 timer
->pr
= 0xffffffff;
234 tmr_input_channel_init(timer
, &motor
->icInitStruct
, TMR_CHANNEL_INPUT_DIV_1
);
236 pDmaInit
->direction
= DMA_DIR_PERIPHERAL_TO_MEMORY
;
238 xDMA_Init(dmaRef
, pDmaInit
);
240 #endif // USE_DSHOT_TELEMETRY
243 * Start the timers and dma requests to send dshot data to all motors
245 * Called after pwm_output_dshot_shared.c has finished setting up the buffers that represent the dshot packets.
246 * Iterates over all the timers needed (note that there may be less timers than motors since a single timer can run
247 * multiple motors) and enables each one.
249 void pwmCompleteDshotMotorUpdate(void)
251 // If there is a dshot command loaded up, time it correctly with motor update
252 if (!dshotCommandQueueEmpty()) {
253 if (!dshotCommandOutputIsEnabled(dshotPwmDevice
.count
)) {
258 for (int i
= 0; i
< dmaMotorTimerCount
; i
++) { // dmaMotorTimerCount is a global declared in pwm_output_dshot_shared.c
259 #ifdef USE_DSHOT_DMAR
260 // NB burst mode not tested
262 xDMA_SetCurrDataCounter(dmaMotorTimers
[i
].dmaBurstRef
, dmaMotorTimers
[i
].dmaBurstLength
);
263 xDMA_Cmd(dmaMotorTimers
[i
].dmaBurstRef
, TRUE
);
265 // TIM_DMAConfig(dmaMotorTimers[i].timer, TIM_DMABase_CCR1, TIM_DMABurstLength_4Transfers);
266 tmr_dma_control_config(dmaMotorTimers
[i
].timer
, TMR_DMA_TRANSFER_4BYTES
, TMR_CTRL1_ADDRESS
);
268 // XXX TODO - what is the equivalent of TIM_DMA_Update in AT32?
269 // TIM_DMACmd(dmaMotorTimers[i].timer, TIM_DMA_Update, ENABLE);
270 // tmr_dma_request_enable(dmaMotorTimers[i].timer, )
272 #endif // USE_DSHOT_DMAR
274 // I think the counter is reset here to ensure that the first pulse is the correct width,
275 // and maybe also to reduce the chance of an interrupt firing prematurely
276 tmr_counter_value_set(dmaMotorTimers
[i
].timer
, 0);
278 // Allows setting the period with immediate effect
279 tmr_period_buffer_enable(dmaMotorTimers
[i
].timer
, FALSE
);
281 #ifdef USE_DSHOT_TELEMETRY
282 // NB outputPeriod isn't set when telemetry is not #def'd
283 tmr_period_value_set(dmaMotorTimers
[i
].timer
, dmaMotorTimers
[i
].outputPeriod
);
286 tmr_period_buffer_enable(dmaMotorTimers
[i
].timer
, TRUE
);
288 // Ensure that overflow events are enabled. This event may affect both period and duty cycle
289 tmr_overflow_event_disable(dmaMotorTimers
[i
].timer
, FALSE
);
291 // Generate requests from the timer to one or more DMA channels
292 tmr_dma_request_enable(dmaMotorTimers
[i
].timer
, dmaMotorTimers
[i
].timerDmaSources
, TRUE
);
294 dmaMotorTimers
[i
].timerDmaSources
= 0;
300 * Interrupt handler called at the end of each packet
302 * Responsible for switching the dshot direction after sending a dshot command so that we
303 * can receive dshot telemetry. If telemetry is not enabled, disables the dma and request generation.
305 FAST_CODE
static void motor_DMA_IRQHandler(dmaChannelDescriptor_t
*descriptor
)
307 if (DMA_GET_FLAG_STATUS(descriptor
, DMA_IT_TCIF
))
309 motorDmaOutput_t
* const motor
= &dmaMotors
[descriptor
->userParam
];
311 #ifdef USE_DSHOT_TELEMETRY
312 dshotDMAHandlerCycleCounters
.irqAt
= getCycleCounter();
315 // Disable timers and dma
317 #ifdef USE_DSHOT_DMAR
319 xDMA_Cmd(motor
->timerHardware
->dmaTimUPRef
, DISABLE
);
320 TIM_DMACmd(motor
->timerHardware
->tim
, TIM_DMA_Update
, DISABLE
);
323 { // block for the 'else' in the #ifdef above
325 // Important - disable requests before disabling the dma channel, otherwise it's possible to
326 // have a pending request that will fire the moment the dma channel is re-enabled, which
327 // causes fake pulses to be sent at the start of the next packet.
328 // This may be the problem described in the errata 1.10.1. The full work-around sounds a bit
329 // heavyweight, but we should keep it in mind in case it's needed.
331 // How to clear TMR-triggered DAM requests
333 // TMR-induced DMA request cannot be cleared by resetting/setting the corresponding DMA
334 // request enable bit in the TMRx_IDEN register.
336 // Before enabling DMA channel, reset TMR (reset CRM clock of TMR) and initialize TMR to
337 // clear pending DMA requests.
339 // disable request generation
340 tmr_dma_request_enable(motor
->timerHardware
->tim
, motor
->timerDmaSource
, FALSE
);
342 // disable the dma channel, (gets re-enabled in pwm_output_dshot_shared.c from pwmWriteDshotInt)
343 xDMA_Cmd(motor
->dmaRef
, FALSE
);
346 // If we're expecting telem, flip direction and re-enable
347 #ifdef USE_DSHOT_TELEMETRY
348 if (useDshotTelemetry
) {
349 pwmDshotSetDirectionInput(motor
);
350 xDMA_SetCurrDataCounter(motor
->dmaRef
, GCR_TELEMETRY_INPUT_LEN
);
351 xDMA_Cmd(motor
->dmaRef
, TRUE
);
352 tmr_dma_request_enable(motor
->timerHardware
->tim
, motor
->timerDmaSource
, TRUE
);
354 dshotDMAHandlerCycleCounters
.changeDirectionCompletedAt
= getCycleCounter();
358 DMA_CLEAR_FLAG(descriptor
, DMA_IT_TCIF
);
363 bool pwmDshotMotorHardwareConfig(const timerHardware_t
*timerHardware
, uint8_t motorIndex
, uint8_t reorderedMotorIndex
,
364 motorPwmProtocolTypes_e pwmProtocolType
, uint8_t output
)
366 #ifdef USE_DSHOT_TELEMETRY
367 #define OCINIT motor->ocInitStruct
368 #define DMAINIT motor->dmaInitStruct
370 TIM_OCInitTypeDef ocInitStruct
;
371 DMA_InitTypeDef dmaInitStruct
;
372 #define OCINIT ocInitStruct
373 #define DMAINIT dmaInitStruct
376 dmaResource_t
*dmaRef
= NULL
;
377 uint32_t dmaMuxId
= 0;
379 #if defined(USE_DMA_SPEC)
380 const dmaChannelSpec_t
*dmaSpec
= dmaGetChannelSpecByTimer(timerHardware
);
382 if (dmaSpec
!= NULL
) {
383 dmaRef
= dmaSpec
->ref
;
384 dmaMuxId
= dmaSpec
->dmaMuxId
;
386 #else // not defined USE_DMA_SPEC
387 dmaRef
= timerHardware
->dmaRef
;
388 #endif // USE_DMA_SPEC
390 #ifdef USE_DSHOT_DMAR
392 dmaRef
= timerHardware
->dmaTimUPRef
;
394 #endif // USE_DSHOT_DMAR
396 if (dmaRef
== NULL
) {
400 dmaIdentifier_e dmaIdentifier
= dmaGetIdentifier(dmaRef
);
402 bool dmaIsConfigured
= false;
403 #ifdef USE_DSHOT_DMAR
405 const resourceOwner_t
*owner
= dmaGetOwner(dmaIdentifier
);
406 if (owner
->owner
== OWNER_TIMUP
&& owner
->resourceIndex
== timerGetTIMNumber(timerHardware
->tim
)) {
407 dmaIsConfigured
= true;
408 } else if (!dmaAllocate(dmaIdentifier
, OWNER_TIMUP
, timerGetTIMNumber(timerHardware
->tim
))) {
414 if (!dmaAllocate(dmaIdentifier
, OWNER_MOTOR
, RESOURCE_INDEX(reorderedMotorIndex
))) {
419 motorDmaOutput_t
* const motor
= &dmaMotors
[motorIndex
];
420 TIM_TypeDef
*timer
= timerHardware
->tim
;
422 // Boolean configureTimer is always true when different channels of the same timer are processed in sequence,
423 // causing the timer and the associated DMA initialized more than once.
424 // To fix this, getTimerIndex must be expanded to return if a new timer has been requested.
425 // However, since the initialization is idempotent (can be applied multiple times without changing the outcome),
426 // it is left as is in a favor of flash space (for now).
427 const uint8_t timerIndex
= getTimerIndex(timer
);
428 const bool configureTimer
= (timerIndex
== dmaMotorTimerCount
-1);
430 motor
->timer
= &dmaMotorTimers
[timerIndex
];
431 motor
->index
= motorIndex
;
432 motor
->timerHardware
= timerHardware
;
434 const IO_t motorIO
= IOGetByTag(timerHardware
->tag
);
436 uint8_t pupMode
= (output
& TIMER_OUTPUT_INVERTED
) ? GPIO_PULL_DOWN
: GPIO_PULL_UP
;
438 #ifdef USE_DSHOT_TELEMETRY
439 if (useDshotTelemetry
) {
440 output
^= TIMER_OUTPUT_INVERTED
;
444 motor
->iocfg
= IO_CONFIG(GPIO_MODE_MUX
, GPIO_DRIVE_STRENGTH_MODERATE
, GPIO_OUTPUT_PUSH_PULL
, pupMode
);
446 IOConfigGPIOAF(motorIO
, motor
->iocfg
, timerHardware
->alternateFunction
);
448 if (configureTimer
) {
449 RCC_ClockCmd(timerRCC(timer
), ENABLE
);
451 tmr_counter_enable(timer
, FALSE
);
453 uint32_t prescaler
= (uint16_t)(lrintf((float) timerClock(timer
) / getDshotHz(pwmProtocolType
) + 0.01f
) - 1);
454 uint32_t period
= (pwmProtocolType
== PWM_TYPE_PROSHOT1000
? (MOTOR_NIBBLE_LENGTH_PROSHOT
) : MOTOR_BITLENGTH
) - 1;
456 tmr_clock_source_div_set(timer
, TMR_CLOCK_DIV1
);
457 tmr_repetition_counter_set(timer
, 0);
458 tmr_cnt_dir_set(timer
, TMR_COUNT_UP
);
459 tmr_base_init(timer
, period
, prescaler
);
462 tmr_output_config_type
* ocConfig
= &OCINIT
;
463 tmr_output_default_para_init(ocConfig
);
465 ocConfig
->oc_mode
= TMR_OUTPUT_CONTROL_PWM_MODE_A
;
466 if (output
& TIMER_OUTPUT_N_CHANNEL
) {
467 // XXX N channels not yet tested, comments are the stm32 code
468 // OCINIT.TIM_OutputNState = TIM_OutputNState_Enable;
469 ocConfig
->occ_output_state
= TRUE
;
470 // OCINIT.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
471 ocConfig
->occ_idle_state
= FALSE
;
472 // OCINIT.TIM_OCNPolarity = (output & TIMER_OUTPUT_INVERTED) ? TIM_OCNPolarity_Low : TIM_OCNPolarity_High;
473 ocConfig
->occ_polarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OUTPUT_ACTIVE_LOW
: TMR_OUTPUT_ACTIVE_HIGH
;
475 ocConfig
->oc_output_state
= TRUE
;
476 ocConfig
->oc_idle_state
= FALSE
;
477 ocConfig
->oc_polarity
= (output
& TIMER_OUTPUT_INVERTED
) ? TMR_OUTPUT_ACTIVE_LOW
: TMR_OUTPUT_ACTIVE_HIGH
;
480 #ifdef USE_DSHOT_TELEMETRY
481 tmr_input_config_type
* icConfig
= &motor
->icInitStruct
;
482 tmr_input_default_para_init(icConfig
);
483 icConfig
->input_mapped_select
= TMR_CC_CHANNEL_MAPPED_DIRECT
;
484 icConfig
->input_polarity_select
= TMR_INPUT_BOTH_EDGE
;
485 const bool useNChannel
= output
& TIMER_OUTPUT_N_CHANNEL
;
486 icConfig
->input_channel_select
= toCHSelectType(timerHardware
->channel
, useNChannel
);
487 icConfig
->input_filter_value
= 2;
488 #endif // USE_DSHOT_TELEMETRY
490 #ifdef USE_DSHOT_DMAR
492 motor
->timer
->dmaBurstRef
= dmaRef
;
496 motor
->timerDmaSource
= timerDmaSource(timerHardware
->channel
);
498 // clear that bit from timerDmaSources
499 // timerDmaSources can have more than one source set in it if multiple motors share a common timer,
500 motor
->timer
->timerDmaSources
&= ~motor
->timerDmaSource
;
503 xDMA_Cmd(dmaRef
, FALSE
);
506 if (!dmaIsConfigured
) {
507 dmaEnable(dmaIdentifier
);
508 dmaMuxEnable(dmaIdentifier
, dmaMuxId
);
511 dma_default_para_init(&DMAINIT
);
513 #ifdef USE_DSHOT_DMAR
515 motor
->timer
->dmaBurstBuffer
= &dshotBurstDmaBuffer
[timerIndex
][0];
517 DMAINIT
.DMA_Channel
= timerHardware
->dmaTimUPChannel
;
518 DMAINIT
.DMA_Memory0BaseAddr
= (uint32_t)motor
->timer
->dmaBurstBuffer
;
519 DMAINIT
.DMA_DIR
= DMA_DIR_MemoryToPeripheral
;
520 DMAINIT
.DMA_FIFOMode
= DMA_FIFOMode_Enable
;
521 DMAINIT
.DMA_FIFOThreshold
= DMA_FIFOThreshold_Full
;
522 DMAINIT
.DMA_MemoryBurst
= DMA_MemoryBurst_Single
;
523 DMAINIT
.DMA_PeripheralBurst
= DMA_PeripheralBurst_Single
;
525 DMAINIT
.DMA_PeripheralBaseAddr
= (uint32_t)&timerHardware
->tim
->DMAR
;
526 DMAINIT
.DMA_BufferSize
= (pwmProtocolType
== PWM_TYPE_PROSHOT1000
) ? PROSHOT_DMA_BUFFER_SIZE
: DSHOT_DMA_BUFFER_SIZE
; // XXX
527 DMAINIT
.DMA_PeripheralInc
= DMA_PeripheralInc_Disable
;
528 DMAINIT
.DMA_MemoryInc
= DMA_MemoryInc_Enable
;
529 DMAINIT
.DMA_PeripheralDataSize
= DMA_PeripheralDataSize_Word
;
530 DMAINIT
.DMA_MemoryDataSize
= DMA_MemoryDataSize_Word
;
531 DMAINIT
.DMA_Mode
= DMA_Mode_Normal
;
532 DMAINIT
.DMA_Priority
= DMA_Priority_High
;
534 #endif // USE_DSHOT_DMAR
536 motor
->dmaBuffer
= &dshotDmaBuffer
[motorIndex
][0];
537 DMAINIT
.memory_base_addr
= (uint32_t)motor
->dmaBuffer
;
538 DMAINIT
.memory_inc_enable
= TRUE
;
539 DMAINIT
.memory_data_width
= DMA_MEMORY_DATA_WIDTH_WORD
;
540 DMAINIT
.loop_mode_enable
= FALSE
;
541 DMAINIT
.buffer_size
= DSHOT_DMA_BUFFER_SIZE
;
542 DMAINIT
.direction
= DMA_DIR_MEMORY_TO_PERIPHERAL
;
543 DMAINIT
.peripheral_base_addr
= (uint32_t)timerChCCR(timerHardware
); // returns the address of CxDT for timerHardware->channel
544 DMAINIT
.peripheral_inc_enable
= FALSE
;
545 DMAINIT
.peripheral_data_width
= DMA_PERIPHERAL_DATA_WIDTH_WORD
;
546 DMAINIT
.priority
= DMA_PRIORITY_HIGH
;
549 motor
->dmaRef
= dmaRef
;
552 #ifdef USE_DSHOT_TELEMETRY
553 motor
->dshotTelemetryDeadtimeUs
= DSHOT_TELEMETRY_DEADTIME_US
+ 1000000 *
554 (16 * MOTOR_BITLENGTH
) / getDshotHz(pwmProtocolType
);
555 motor
->timer
->outputPeriod
= (pwmProtocolType
== PWM_TYPE_PROSHOT1000
? (MOTOR_NIBBLE_LENGTH_PROSHOT
) : MOTOR_BITLENGTH
) - 1;
556 pwmDshotSetDirectionOutput(motor
);
558 pwmDshotSetDirectionOutput(motor
, &OCINIT
, &DMAINIT
);
561 #ifdef USE_DSHOT_DMAR
563 if (!dmaIsConfigured
) {
564 dmaSetHandler(dmaIdentifier
, motor_DMA_IRQHandler
, NVIC_PRIO_DSHOT_DMA
, motor
->index
);
569 dmaSetHandler(dmaIdentifier
, motor_DMA_IRQHandler
, NVIC_PRIO_DSHOT_DMA
, motor
->index
);
573 const tmr_channel_select_type chSel
= toCHSelectType(timerHardware
->channel
, output
& TIMER_OUTPUT_N_CHANNEL
);
574 tmr_channel_enable(timer
, chSel
, TRUE
);
577 if (configureTimer
) {
578 // Changes to the period register are deferred until the next counter overflow
579 tmr_period_buffer_enable(timer
, TRUE
);
580 tmr_output_enable(timer
, TRUE
);
581 tmr_counter_enable(timer
, TRUE
);
584 #ifdef USE_DSHOT_TELEMETRY
585 if (useDshotTelemetry
) {
586 // avoid high line during startup to prevent bootloader activation
587 *timerChCCR(timerHardware
) = 0xffff;
591 motor
->configured
= true;