STM32F7 Two Timer Channels Share One DMA Channel
I am trying to write a driver that sends DShot commands to an ESC. I am using an STM32F722 MCU. The DShot protocol is similar to addressable LEDs where 1/3 duty cycle represents a 0 and a 2/3 duty cycle represents a 1. The frequency is 600kHz for DShot600.
All of my PWM channels are on TIM2. The problem is that TIM2_CH2 and TIM2_CH4 both share DMA1_Stream6_CH3. TIM2_CH4 also exists on DMA1_Stream7_CH3.
However, when I try to enable outputs on both TIM2_CH2 and TIM2_CH4, the outputs start looking weird (see pic below). If I disable TIM2_CH2, the outputs on the 3 remaining channels look perfect (see pic below).
I think I might have to enable or disable the channels in a certain fashion to ensure they don't run at the same time. However, I need them to both output at the same time. Is this possible?
All relevant code is here. Fair warning, I don't use any HAL or LL libraries so it's a bit lengthy.
main.c
#include "stm32f7xx.h"
#include "pwm.h"
#include "rcc.h"
#include "dwt.h"
#include "usart.h"
#include "drv_dshot.h"
uint16_t motor_value[4] = {2000, 2000, 2000, 2000};
int main(void){
RCC_216MHz_Init();
DWT_Init();
dshot_init();
while(1){
dshot_write(motor_value);
delay(1);
}
}
void DMA1_Stream5_IRQHandler(void){
if(DMA1->HISR & DMA_HISR_TCIF5){
DMA1_Stream5->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream5->CR & DMA_SxCR_EN){}
TIM2->DIER &= ~TIM_DIER_CC1DE;
DMA1->HIFCR |= DMA_HIFCR_CTCIF5;
DMA1_Stream5->NDTR = DSHOT_DMA_BUFFER_SIZE;
}
}
void DMA1_Stream6_IRQHandler(void){
if(DMA1->HISR & DMA_HISR_TCIF6){
DMA1_Stream6->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream6->CR & DMA_SxCR_EN){}
TIM2->DIER &= ~TIM_DIER_CC2DE;
DMA1->HIFCR |= DMA_HIFCR_CTCIF6;
DMA1_Stream6->NDTR = DSHOT_DMA_BUFFER_SIZE;
}
}
void DMA1_Stream1_IRQHandler(void){
if(DMA1->LISR & DMA_LISR_TCIF1){
DMA1_Stream1->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream1->CR & DMA_SxCR_EN){}
TIM2->DIER &= ~TIM_DIER_CC3DE;
DMA1->LIFCR |= DMA_LIFCR_CTCIF1;
DMA1_Stream1->NDTR = DSHOT_DMA_BUFFER_SIZE;
}
}
void DMA1_Stream7_IRQHandler(void){
if(DMA1->HISR & DMA_HISR_TCIF7){
DMA1_Stream7->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream7->CR & DMA_SxCR_EN){}
TIM2->DIER &= ~TIM_DIER_CC4DE;
DMA1->HIFCR |= DMA_HIFCR_CTCIF7;
DMA1_Stream7->NDTR = DSHOT_DMA_BUFFER_SIZE;
}
}
drv_dshot.c
#include "stm32f7xx.h"
#include "stdbool.h"
#include "drv_dshot.h"
static uint32_t motor1_dmabuffer[DSHOT_DMA_BUFFER_SIZE];
static uint32_t motor2_dmabuffer[DSHOT_DMA_BUFFER_SIZE];
static uint32_t motor3_dmabuffer[DSHOT_DMA_BUFFER_SIZE];
static uint32_t motor4_dmabuffer[DSHOT_DMA_BUFFER_SIZE];
static uint16_t dshot_prepare_packet(uint16_t value);
static void dshot_prepare_dmabuffer_all(uint16_t *motor_value);
static void dshot_prepare_dmabuffer(uint32_t *motor_dmabuffer, uint16_t value);
static uint16_t dshot_prepare_packet(uint16_t value);
static void dshot_dma_start(void);
static void dshot_enable_dma_request(void);
void dshot_write(uint16_t *motor_value){
dshot_prepare_dmabuffer_all(motor_value);
dshot_enable_dma_request();
dshot_dma_start();
}
static void dshot_prepare_dmabuffer_all(uint16_t *motor_value){
dshot_prepare_dmabuffer(motor1_dmabuffer, motor_value[0]);
dshot_prepare_dmabuffer(motor2_dmabuffer, motor_value[1]);
DMA1_Stream1->NDTR |= DSHOT_DMA_BUFFER_SIZE;
dshot_prepare_dmabuffer(motor3_dmabuffer, motor_value[2]);
DMA1_Stream6->NDTR |= DSHOT_DMA_BUFFER_SIZE;
dshot_prepare_dmabuffer(motor4_dmabuffer, motor_value[3]);
DMA1_Stream5->NDTR |= DSHOT_DMA_BUFFER_SIZE;
}
static void dshot_prepare_dmabuffer(uint32_t *motor_dmabuffer, uint16_t value)
{
uint16_t packet;
packet = dshot_prepare_packet(value);
for(int i = 0; i < 16; i++)
{
motor_dmabuffer[i] = (packet & 0x8000) ? MOTOR_BIT_1 : MOTOR_BIT_0;
packet <<= 1;
}
motor_dmabuffer[16] = 0;
motor_dmabuffer[17] = 0;
}
static uint16_t dshot_prepare_packet(uint16_t value){
uint16_t packet;
bool dshot_telemetry = false;
packet = (value << 1) | (dshot_telemetry ? 1 : 0);
// compute checksum
unsigned csum = 0;
unsigned csum_data = packet;
for(int i = 0; i < 3; i++)
{
csum ^= csum_data; // xor data by nibbles
csum_data >>= 4;
}
csum &= 0xf;
packet = (packet << 4) | csum;
return packet;
}
static void dshot_dma_start(void){
TIM2->CNT = 0;
TIM2->DIER |= TIM_DIER_CC1DE;
TIM2->DIER |= TIM_DIER_CC2DE;
TIM2->DIER |= TIM_DIER_CC3DE;
TIM2->DIER |= TIM_DIER_CC4DE;
}
static void dshot_enable_dma_request(void){
DMA1_Stream5->CR |= DMA_SxCR_EN;
DMA1_Stream6->CR |= DMA_SxCR_EN;
DMA1_Stream1->CR |= DMA_SxCR_EN;
DMA1_Stream7->CR |= DMA_SxCR_EN;
}
void dshot_init(void){
/////////////////GPIO INIT///////////////////
// enable clock for GPIOA
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// set mode, speed, type, pull, AF
GPIOA->MODER &= ~GPIO_MODER_MODER0;
GPIOA->MODER |= GPIO_MODER_MODER0_1;
GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR0;
GPIOA->OTYPER &= ~GPIO_OTYPER_OT0;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0;
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0;
GPIOA->AFR[0] |= (GPIO_AF1_TIM2 << (4U * 0U));
GPIOA->MODER &= ~GPIO_MODER_MODER1;
GPIOA->MODER |= GPIO_MODER_MODER1_1;
GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR1;
GPIOA->OTYPER &= ~GPIO_OTYPER_OT1;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1;
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL1;
GPIOA->AFR[0] |= (GPIO_AF1_TIM2 << (4U * 1U));
GPIOA->MODER &= ~GPIO_MODER_MODER2;
GPIOA->MODER |= GPIO_MODER_MODER2_1;
GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR2;
GPIOA->OTYPER &= ~GPIO_OTYPER_OT2;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR2;
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL2;
GPIOA->AFR[0] |= (GPIO_AF1_TIM2 << (4U * 2U));
GPIOA->MODER &= ~GPIO_MODER_MODER3;
GPIOA->MODER |= GPIO_MODER_MODER3_1;
GPIOA->OSPEEDR &= ~GPIO_OSPEEDR_OSPEEDR3;
GPIOA->OTYPER &= ~GPIO_OTYPER_OT3;
GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR3;
GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL3;
GPIOA->AFR[0] |= (GPIO_AF1_TIM2 << (4U * 3U));
/////////////////TIMER INIT///////////////////
// enable clock for TIM2
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->CR1 &= ~TIM_CR1_CEN;
// set PSC, AR, clock div, cnt, cnt mode
TIM2->PSC = 0; //(uint16_t)((float) TIMER_CLOCK / 12000000) - 1;
TIM2->ARR = MOTOR_BITLENGTH;
TIM2->CR1 &= ~TIM_CR1_CKD;
TIM2->CR1 &= ~TIM_CR1_DIR;
// set output compare mode
// channel 1
TIM2->CCER &= ~TIM_CCER_CC1E;
TIM2->CCMR1 |= TIM_CCMR1_OC1M_1 |
TIM_CCMR1_OC1M_2;
TIM2->CCER &= ~TIM_CCER_CC1P;
TIM2->CCR1 = 0;
TIM2->CCER |= TIM_CCER_CC1E;
// channel 2
TIM2->CCER &= ~TIM_CCER_CC2E;
TIM2->CCMR1 |= TIM_CCMR1_OC2M_1 |
TIM_CCMR1_OC2M_2;
TIM2->CCER &= ~TIM_CCER_CC2P;
TIM2->CCR2 = 0;
TIM2->CCER |= TIM_CCER_CC2E;
// channel 3
TIM2->CCER &= ~TIM_CCER_CC3E;
TIM2->CCMR2 |= TIM_CCMR2_OC3M_1 |
TIM_CCMR2_OC3M_2;
TIM2->CCER &= ~TIM_CCER_CC3P;
TIM2->CCR3 = 0;
TIM2->CCER |= TIM_CCER_CC3E;
// channel 4
TIM2->CCER &= ~TIM_CCER_CC4E;
TIM2->CCMR2 |= TIM_CCMR2_OC4M_1 |
TIM_CCMR2_OC4M_2;
TIM2->CCER &= ~TIM_CCER_CC4P;
TIM2->CCR4 = 0;
TIM2->CCER |= TIM_CCER_CC4E;
// enable preload
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
TIM2->CCMR1 |= TIM_CCMR1_OC2PE;
TIM2->CCMR2 |= TIM_CCMR2_OC3PE;
TIM2->CCMR2 |= TIM_CCMR2_OC4PE;
//
TIM2->CR1 |= TIM_CR1_ARPE;
// enable the counter
TIM2->CR1 |= TIM_CR1_CEN;
/////////////////DMA INIT///////////////////
/*
* TIM2_CH1 on Stream5_CH3
* TIM2_CH2 on Stream6_CH3
* TIM2_CH3 on Stream1_CH3
* TIM2_CH4 on Stream7_CH3
*/
// reset DMA
RCC->AHB1RSTR |= RCC_AHB1RSTR_DMA1RST;
RCC->AHB1RSTR &= ~RCC_AHB1RSTR_DMA1RST;
// disable DMA stream 5
DMA1_Stream5->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream5->CR & DMA_SxCR_EN){}
DMA1_Stream5->CR = 0;
DMA1_Stream5->NDTR = 0;
DMA1_Stream5->PAR = 0;
DMA1_Stream5->M0AR = 0;
DMA1_Stream5->M1AR = 0;
DMA1_Stream5->FCR = 0x00000021U;
DMA1_Stream5->CR &= ~DMA_SxCR_CHSEL;
DMA1->HIFCR |= 0x00000F40U;
// disable DMA stream 6
DMA1_Stream6->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream6->CR & DMA_SxCR_EN){}
DMA1_Stream6->CR = 0;
DMA1_Stream6->NDTR = 0;
DMA1_Stream6->PAR = 0;
DMA1_Stream6->M0AR = 0;
DMA1_Stream6->M1AR = 0;
DMA1_Stream6->FCR = 0x00000021U;
DMA1_Stream6->CR &= ~DMA_SxCR_CHSEL;
DMA1->HIFCR |= 0x003F0000U;
// disable DMA stream 1
DMA1_Stream1->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream1->CR & DMA_SxCR_EN){}
DMA1_Stream1->CR = 0;
DMA1_Stream1->NDTR = 0;
DMA1_Stream1->PAR = 0;
DMA1_Stream1->M0AR = 0;
DMA1_Stream1->M1AR = 0;
DMA1_Stream1->FCR = 0x00000021U;
DMA1_Stream1->CR &= ~DMA_SxCR_CHSEL;
DMA1->LIFCR |= 0x00000F40U;
// disable DMA stream 7
DMA1_Stream7->CR &= ~DMA_SxCR_EN;
while(DMA1_Stream7->CR & DMA_SxCR_EN){}
DMA1_Stream7->CR = 0;
DMA1_Stream7->NDTR = 0;
DMA1_Stream7->PAR = 0;
DMA1_Stream7->M0AR = 0;
DMA1_Stream7->M1AR = 0;
DMA1_Stream7->FCR = 0x00000021U;
DMA1_Stream7->CR &= ~DMA_SxCR_CHSEL;
DMA1->HIFCR |= 0x0F400000U;
// enable clock
RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
// enable interrupts
NVIC_SetPriority(DMA1_Stream5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
NVIC_EnableIRQ(DMA1_Stream5_IRQn);
NVIC_SetPriority(DMA1_Stream6_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
NVIC_EnableIRQ(DMA1_Stream6_IRQn);
NVIC_SetPriority(DMA1_Stream1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
NVIC_EnableIRQ(DMA1_Stream1_IRQn);
NVIC_SetPriority(DMA1_Stream7_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
NVIC_EnableIRQ(DMA1_Stream7_IRQn);
// motor 4 DMA settings
DMA1_Stream5->CR |= (0x3 << 25U);
DMA1_Stream5->M0AR = (uint32_t)motor4_dmabuffer;
DMA1_Stream5->CR |= DMA_SxCR_DIR_0; // mem to per
DMA1_Stream5->FCR |= DMA_SxFCR_DMDIS; // fifo en
DMA1_Stream5->FCR &= ~DMA_SxFCR_FTH; //1/4 full
DMA1_Stream5->CR &= ~DMA_SxCR_MBURST;
DMA1_Stream5->CR &= ~DMA_SxCR_PBURST;
DMA1_Stream5->PAR = (uint32_t)(&(TIM2->CCR1));
DMA1_Stream5->NDTR = DSHOT_DMA_BUFFER_SIZE;
DMA1_Stream5->CR &= ~DMA_SxCR_PINC;
DMA1_Stream5->CR |= DMA_SxCR_MINC;
DMA1_Stream5->CR |= DMA_SxCR_MSIZE_1;
DMA1_Stream5->CR |= DMA_SxCR_PSIZE_1;
DMA1_Stream5->CR &= ~DMA_SxCR_CIRC;
DMA1_Stream5->CR |= DMA_SxCR_PL_0;
// DMA transfer complete interrupt enable
DMA1_Stream5->CR |= DMA_SxCR_TCIE;
// motor 4 DMA settings
DMA1_Stream6->CR |= (0x3 << 25U);
DMA1_Stream6->M0AR = (uint32_t)motor3_dmabuffer;
DMA1_Stream6->CR |= DMA_SxCR_DIR_0; // mem to per
DMA1_Stream6->FCR |= DMA_SxFCR_DMDIS; // fifo en
DMA1_Stream6->FCR &= ~DMA_SxFCR_FTH; //1/4 full
DMA1_Stream6->CR &= ~DMA_SxCR_MBURST;
DMA1_Stream6->CR &= ~DMA_SxCR_PBURST;
DMA1_Stream6->PAR = (uint32_t)(&(TIM2->CCR2));
DMA1_Stream6->NDTR = DSHOT_DMA_BUFFER_SIZE;
DMA1_Stream6->CR &= ~DMA_SxCR_PINC;
DMA1_Stream6->CR |= DMA_SxCR_MINC;
DMA1_Stream6->CR |= DMA_SxCR_MSIZE_1;
DMA1_Stream6->CR |= DMA_SxCR_PSIZE_1;
DMA1_Stream6->CR &= ~DMA_SxCR_CIRC;
DMA1_Stream6->CR |= DMA_SxCR_PL;
// DMA transfer complete interrupt enable
DMA1_Stream6->CR |= DMA_SxCR_TCIE;
// motor 2 DMA settings
DMA1_Stream1->CR |= (0x3 << 25U);
DMA1_Stream1->M0AR = (uint32_t)motor2_dmabuffer;
DMA1_Stream1->CR |= DMA_SxCR_DIR_0; // mem to per
DMA1_Stream1->FCR |= DMA_SxFCR_DMDIS; // fifo en
DMA1_Stream1->FCR &= ~DMA_SxFCR_FTH; //1/4 full
DMA1_Stream1->CR &= ~DMA_SxCR_MBURST;
DMA1_Stream1->CR &= ~DMA_SxCR_PBURST;
DMA1_Stream1->PAR = (uint32_t)(&(TIM2->CCR3));
DMA1_Stream1->NDTR = DSHOT_DMA_BUFFER_SIZE;
DMA1_Stream1->CR &= ~DMA_SxCR_PINC;
DMA1_Stream1->CR |= DMA_SxCR_MINC;
DMA1_Stream1->CR |= DMA_SxCR_MSIZE_1;
DMA1_Stream1->CR |= DMA_SxCR_PSIZE_1;
DMA1_Stream1->CR &= ~DMA_SxCR_CIRC;
DMA1_Stream1->CR |= DMA_SxCR_PL_0;
// DMA transfer complete interrupt enable
DMA1_Stream1->CR |= DMA_SxCR_TCIE;
// motor 4 DMA settings
DMA1_Stream7->CR |= (0x3 << 25U);
DMA1_Stream7->M0AR = (uint32_t)motor1_dmabuffer;
DMA1_Stream7->CR |= DMA_SxCR_DIR_0; // mem to per
DMA1_Stream7->FCR |= DMA_SxFCR_DMDIS; // fifo en
DMA1_Stream7->FCR &= ~DMA_SxFCR_FTH; //1/4 full
DMA1_Stream7->CR &= ~DMA_SxCR_MBURST;
DMA1_Stream7->CR &= ~DMA_SxCR_PBURST;
DMA1_Stream7->PAR = (uint32_t)(&(TIM2->CCR4));
DMA1_Stream7->NDTR = DSHOT_DMA_BUFFER_SIZE;
DMA1_Stream7->CR &= ~DMA_SxCR_PINC;
DMA1_Stream7->CR |= DMA_SxCR_MINC;
DMA1_Stream7->CR |= DMA_SxCR_MSIZE_1;
DMA1_Stream7->CR |= DMA_SxCR_PSIZE_1;
DMA1_Stream7->CR &= ~DMA_SxCR_CIRC;
DMA1_Stream7->CR |= DMA_SxCR_PL_0;
// DMA transfer complete interrupt enable
DMA1_Stream7->CR |= DMA_SxCR_TCIE;
}
drv_dshot.h
/*
* drv_dshot.h
*
* Created on: Dec 29, 2021
* Author: jeremywolfe
*/
#ifndef DRV_DRV_DSHOT_H_
#define DRV_DRV_DSHOT_H_
/* User Configuration */
// Timer Clock
#define TIMER_CLOCK 108000000 // 100MHz
// MOTOR 1 (PA3) - TIM5 Channel 4, DMA1 Stream 3
#define MOTOR_1_TIM (&htim2)
#define MOTOR_1_TIM_CHANNEL TIM_CHANNEL_4
// MOTOR 2 (PA2) - TIM2 Channel 3, DMA1 Stream 1
#define MOTOR_2_TIM (&htim2)
#define MOTOR_2_TIM_CHANNEL TIM_CHANNEL_3
// MOTOR 3 (PA0) - TIM2 Channel 1, DMA1 Stream 5
#define MOTOR_3_TIM (&htim2)
#define MOTOR_3_TIM_CHANNEL TIM_CHANNEL_1
// MOTOR 4 (PA1) - TIM5 Channel 2, DMA1 Stream 4
#define MOTOR_4_TIM (&htim2)
#define MOTOR_4_TIM_CHANNEL TIM_CHANNEL_2
/* Definition */
#define MHZ_TO_HZ(x) ((x) * 1000000)
#define DSHOT600_HZ MHZ_TO_HZ(12)
#define DSHOT300_HZ MHZ_TO_HZ(6)
#define DSHOT150_HZ MHZ_TO_HZ(3)
#define MOTOR_BIT_0 60
#define MOTOR_BIT_1 120
#define MOTOR_BITLENGTH 180
#define DSHOT_FRAME_SIZE 16
#define DSHOT_DMA_BUFFER_SIZE 18 /* resolution + frame reset (2us) */
#define DSHOT_MIN_THROTTLE 48
#define DSHOT_MAX_THROTTLE 2047
#define DSHOT_RANGE (DSHOT_MAX_THROTTLE - DSHOT_MIN_THROTTLE)
/* Functions */
void dshot_init(void);
void dshot_write(uint16_t *motor_value);
#endif /* DRV_DRV_DSHOT_H_ */
I decided to instead use TIM5 because it shares the exact same pins as TIM2. However, TIM5 has no overlaps on the DMA Request Mapping. After changing the code, it works just as intended.