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.
DMA1 Mapping 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). Bad Data Good Data

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.