dshot timer setup
I spent most of my week helping another shop member set up for RP2040 programming, as well as brainstorming and developing a plan for their project for robotic arm control.
I also helped several other students use the laser cutter to create acrylic etchings.
As such my work was relatively limited.
It can generally be summarized as follows:
setup_dshot_timer(TIM1);
setup_dshot_timer(TIM8);
configure_dshot_timer_dma(DMA2_Stream5, TIM1, cmd_ccr_tim1_buf[cmd_ccr_front],
6);
configure_dshot_timer_dma(DMA2_Stream1, TIM8, cmd_ccr_tim8_buf[cmd_ccr_front],
7);
enable_pwm_pins();
// Set DShot pins OSPEEDR value to high [DS8626 Table 50]
GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED8_Msk | GPIO_OSPEEDR_OSPEED9_Msk |
GPIO_OSPEEDR_OSPEED10_Msk | GPIO_OSPEEDR_OSPEED11_Msk);
GPIOA->OSPEEDR |=
(2 << GPIO_OSPEEDR_OSPEED8_Pos) | (2 << GPIO_OSPEEDR_OSPEED9_Pos) |
(2 << GPIO_OSPEEDR_OSPEED10_Pos) | (2 << GPIO_OSPEEDR_OSPEED11_Pos);
GPIOC->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED6_Msk | GPIO_OSPEEDR_OSPEED7_Msk |
GPIO_OSPEEDR_OSPEED8_Msk | GPIO_OSPEEDR_OSPEED9_Msk);
GPIOC->OSPEEDR |=
(2 << GPIO_OSPEEDR_OSPEED6_Pos) | (2 << GPIO_OSPEEDR_OSPEED7_Pos) |
(2 << GPIO_OSPEEDR_OSPEED8_Pos) | (2 << GPIO_OSPEEDR_OSPEED9_Pos);
setup_dshot_timer() and configure_dshot_timer_dma() simply reduce some code duplication and make the main function easier to read.
setup_dshot_timer() configures the registers but does not start the timer, setting it to PWM mode 1 and enabling DMA requests. Interestingly there’s a DMA-specific register that writing to handles burst DMA transfers, so that we can have one transfer that will automatically fill the preload capture compare registers after the timer overflows (the end of a bit).
inline void enable_pwm_pins() {
// Set DShot pins to alternate function mode
// [RM0090 8.3.7 & Figure 26, DS8626 Table 7]
GPIOA->MODER &= ~(GPIO_MODER_MODE8 | GPIO_MODER_MODE9 | GPIO_MODER_MODE10 |
GPIO_MODER_MODE11);
GPIOA->MODER |= (2 << GPIO_MODER_MODE8_Pos) | (2 << GPIO_MODER_MODE9_Pos) |
(2 << GPIO_MODER_MODE10_Pos) | (2 << GPIO_MODER_MODE11_Pos);
GPIOC->MODER &= ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE7 | GPIO_MODER_MODE8 |
GPIO_MODER_MODE9);
GPIOC->MODER |= (2 << GPIO_MODER_MODE6_Pos) | (2 << GPIO_MODER_MODE7_Pos) |
(2 << GPIO_MODER_MODE8_Pos) | (2 << GPIO_MODER_MODE9_Pos);
// Set DShot pins to correct alternate funciton
// [RM0090 8.3.7 & Figure 26, DS8626 Table 7]
GPIOA->AFR[1] &= ~(GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL9 | GPIO_AFRH_AFSEL10 |
GPIO_AFRH_AFSEL11);
GPIOA->AFR[1] |= (1 << GPIO_AFRH_AFSEL8_Pos) | (1 << GPIO_AFRH_AFSEL9_Pos) |
(1 << GPIO_AFRH_AFSEL10_Pos) | (1 << GPIO_AFRH_AFSEL11_Pos);
GPIOC->AFR[0] &= ~(GPIO_AFRL_AFSEL6 | GPIO_AFRL_AFSEL7);
GPIOC->AFR[1] &= ~(GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL9);
GPIOC->AFR[0] |= (3 << GPIO_AFRL_AFSEL6_Pos) | (3 << GPIO_AFRL_AFSEL7_Pos);
GPIOC->AFR[1] |= (3 << GPIO_AFRH_AFSEL8_Pos) | (3 << GPIO_AFRH_AFSEL9_Pos);
}
inline void setup_dshot_timer(TIM_TypeDef *timer) {
timer->CR1 = (0 << TIM_CR1_UDIS_Pos) // Enable timer update event
| TIM_CR1_URS // Set counter overflow as only update sourcce
| (0 << TIM_CR1_DIR_Pos) // Set upcounter
| (0 << TIM_CR1_ARPE_Pos) // Disable auto reload register preload
| (0 << TIM_CR1_CKD_Pos); // Set clock division of 1 (full speed)
timer->DIER = TIM_DIER_UDE; // Enable update DMA request enable
timer->CCMR1 =
(0 << TIM_CCMR1_CC1S_Pos) // Configure at output
| TIM_CCMR1_OC1PE // Enable CCR preload
| (6 << TIM_CCMR1_OC1M_Pos) // Set PWM Mode 1 (active while count < CCR)
| (0 << TIM_CCMR1_CC2S_Pos) | TIM_CCMR1_OC2PE | (6 << TIM_CCMR1_OC2M_Pos);
timer->CCMR2 = (0 << TIM_CCMR2_CC3S_Pos) | TIM_CCMR2_OC3PE |
(6 << TIM_CCMR2_OC3M_Pos) | (0 << TIM_CCMR2_CC4S_Pos) |
TIM_CCMR2_OC4PE | (6 << TIM_CCMR2_OC4M_Pos);
timer->CCER = TIM_CCER_CC1E // Enable capture/compare signal output
| (0 << TIM_CCER_CC1P_Pos) // Set active high polarity
| TIM_CCER_CC2E | (0 << TIM_CCER_CC2P_Pos) | TIM_CCER_CC3E |
(0 << TIM_CCER_CC3P_Pos) | TIM_CCER_CC4E |
(0 << TIM_CCER_CC4P_Pos);
#define CCR1_Offset 0x32u
timer->DCR =
(CCR1_Offset << TIM_DCR_DBA_Pos) // set DMA base address to CCR1 Address
| (4 << TIM_DCR_DBL_Pos); // Set burst length for all for CCR adresses
}
Because of this register we use a single DMA request per timer that handles every bit for all four channels:
inline void configure_dshot_timer_dma(
DMA_Stream_TypeDef *stream,
TIM_TypeDef *timer,
uint32_t timer_ccr_buf[16][4],
uint8_t channel
) {
reset_dma_stream(stream);
// Set destination peripheral pointer to full transfer register
stream->PAR = (uint32_t)&atimer->DMAR;
// Set buffer pointer
stream->M0AR = (uint32_t)timer_ccr_buf;
// Total of 64 transfers (4 channels * 16 bits)
stream->NDTR = 64;
// Enable Direct Mode
stream->FCR = 0;
stream->CR =
(1 << DMA_SxCR_DIR_Pos) // Memory to Peripheral
| DMA_SxCR_MINC // Enable memory increment mode
| (2 << DMA_SxCR_PSIZE_Pos) // Set 32-bit peripheral data size
| (2 << DMA_SxCR_PSIZE_Pos) // Set 32-bit memory data size
| (3 << DMA_SxCR_PL_Pos) // Set very high priority level
| (channel << DMA_SxCR_CHSEL_Pos); // Set channel 6 [RM0090 Table 44]
}
We abstract the PWM pin function mode because we need to switch it back after receiving eRPM data.
inline void enable_pwm_pins() {
// Set DShot pins to alternate function mode
// [RM0090 8.3.7 & Figure 26, DS8626 Table 7]
GPIOA->MODER &= ~(GPIO_MODER_MODE8 | GPIO_MODER_MODE9 | GPIO_MODER_MODE10 |
GPIO_MODER_MODE11);
GPIOA->MODER |= (2 << GPIO_MODER_MODE8_Pos) | (2 << GPIO_MODER_MODE9_Pos) |
(2 << GPIO_MODER_MODE10_Pos) | (2 << GPIO_MODER_MODE11_Pos);
GPIOC->MODER &= ~(GPIO_MODER_MODE6 | GPIO_MODER_MODE7 | GPIO_MODER_MODE8 |
GPIO_MODER_MODE9);
GPIOC->MODER |= (2 << GPIO_MODER_MODE6_Pos) | (2 << GPIO_MODER_MODE7_Pos) |
(2 << GPIO_MODER_MODE8_Pos) | (2 << GPIO_MODER_MODE9_Pos);
// Set DShot pins to correct alternate funciton
// [RM0090 8.3.7 & Figure 26, DS8626 Table 7]
GPIOA->AFR[1] &= ~(GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL9 | GPIO_AFRH_AFSEL10 |
GPIO_AFRH_AFSEL11);
GPIOA->AFR[1] |= (1 << GPIO_AFRH_AFSEL8_Pos) | (1 << GPIO_AFRH_AFSEL9_Pos) |
(1 << GPIO_AFRH_AFSEL10_Pos) | (1 << GPIO_AFRH_AFSEL11_Pos);
GPIOC->AFR[0] &= ~(GPIO_AFRL_AFSEL6 | GPIO_AFRL_AFSEL7);
GPIOC->AFR[1] &= ~(GPIO_AFRH_AFSEL8 | GPIO_AFRH_AFSEL9);
GPIOC->AFR[0] |= (3 << GPIO_AFRL_AFSEL6_Pos) | (3 << GPIO_AFRL_AFSEL7_Pos);
GPIOC->AFR[1] |= (3 << GPIO_AFRH_AFSEL8_Pos) | (3 << GPIO_AFRH_AFSEL9_Pos);
}
I also had to set the OSPEEDR values to high to keep up with the DShot speed.
// Set DShot pins OSPEEDR value to high [DS8626 Table 50]
GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED8_Msk | GPIO_OSPEEDR_OSPEED9_Msk |
GPIO_OSPEEDR_OSPEED10_Msk | GPIO_OSPEEDR_OSPEED11_Msk);
GPIOA->OSPEEDR |=
(2 << GPIO_OSPEEDR_OSPEED8_Pos) | (2 << GPIO_OSPEEDR_OSPEED9_Pos) |
(2 << GPIO_OSPEEDR_OSPEED10_Pos) | (2 << GPIO_OSPEEDR_OSPEED11_Pos);
GPIOC->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED6_Msk | GPIO_OSPEEDR_OSPEED7_Msk |
GPIO_OSPEEDR_OSPEED8_Msk | GPIO_OSPEEDR_OSPEED9_Msk);
GPIOC->OSPEEDR |=
(2 << GPIO_OSPEEDR_OSPEED6_Pos) | (2 << GPIO_OSPEEDR_OSPEED7_Pos) |
(2 << GPIO_OSPEEDR_OSPEED8_Pos) | (2 << GPIO_OSPEEDR_OSPEED9_Pos);
Finally we enable the timers
// Enable timer counters.
TIM1->CR1 |= TIM_CR1_CEN;
TIM8->CR1 |= TIM_CR1_CEN;