cancel
Showing results for 
Search instead for 
Did you mean: 

DMA for loading PWM values directly

walkingcrane
Associate II

I am attempting to use the DMA to auto-load PWM values for a motor control application. An example very similar to what I want to do is given in the RM0090, section 17.4.20 (for STM32F427). However, I'm missing some detail and can't get the DMA to transfer the values. We are running the controller at 20kHz and I want to update at 80kHz (the motor PWM freq.), so I am trying to do four bursts of three PWM values that are precalculated during the 20kHz loop. My code is below. You can ignore all the FOC motor control stuff; that is known good code from another implementation. Thanks in advance for your help.

#define MOTOR_PWM_STREAM      DMA2_Stream1

#define MOTOR_PWM_DMA_IRQn     TIM1_UP_TIM10_IRQn

#define MOTOR_PWM_CHANNEL      DMA_Channel_6

#define MOTOR_PWM_BURST_SIZE    3

#define MOTOR_PWM_NUM_BURSTS    4

#define MOTOR_PWM_DMA_BUFF_SIZE   (MOTOR_PWM_BURST_SIZE*MOTOR_PWM_NUM_BURSTS)

//DMA to load precalulated PWM values

static const DMA_InitTypeDef svm_DMA_InitStruct = {

  .DMA_Channel      = MOTOR_PWM_CHANNEL,

  .DMA_PeripheralBaseAddr = (uint32_t) &TIM1->DMAR,

  .DMA_Memory0BaseAddr  = (uint32_t) &svm_dma_buf[0],

  .DMA_DIR        = DMA_DIR_MemoryToPeripheral,

  .DMA_BufferSize     = MOTOR_PWM_DMA_BUFF_SIZE,

  .DMA_PeripheralInc   = DMA_PeripheralInc_Enable,

  .DMA_MemoryInc     = DMA_MemoryInc_Enable,

  .DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord, // 16bit data

  .DMA_MemoryDataSize   = DMA_MemoryDataSize_HalfWord,

  .DMA_Mode        = DMA_Mode_Normal,

  .DMA_Priority      = MOTOR_PWM_DMA_CHANNEL_PRI,

  .DMA_FIFOMode      = DMA_FIFOMode_Disable,

  .DMA_FIFOThreshold   = DMA_FIFOThreshold_1QuarterFull,

  .DMA_MemoryBurst    = DMA_MemoryBurst_Single,

  .DMA_PeripheralBurst  = DMA_PeripheralBurst_Single

};

// Initialize the DMA to transfer PWM values into CCRx registers upon TIM1 Update request

static void motor_init_svm_dma(void) {

  MOTOR_PWM_DMA_CLK_ENABLE();

  // DMA Configuration for transferring SVM vectors into PWM registers:

  DMA_Cmd(MOTOR_PWM_STREAM, DISABLE);

  DMA_DeInit(MOTOR_PWM_STREAM);

  DMA_Init(MOTOR_PWM_STREAM, (DMA_InitTypeDef*) &svm_DMA_InitStruct);

// NOTE - I have commented this out because I don't think the interrupt is needed to cause a DMA request - is that correct? Even if I uncomment this, it doesn't work.

//  // Enable the DMA interrupt and set to the highest priority

//  NVIC_Init((NVIC_InitTypeDef*) &svm_NVIC_InitStructure);

  TIM_DMAConfig(MOTOR_PWM_TIMER, TIM_DMABase_CCR1, TIM_DMABurstLength_3Transfers);

  TIM_SelectCCDMA(MOTOR_PWM_TIMER, ENABLE);

}

// Load the next four pwm values based on current position

static void motor_load_pwm_dma(void) {

  // Turn off the update so it doesn't trigger during this routine

  // Also turn off the DMA for now

  TIM_DMACmd(MOTOR_PWM_TIMER, TIM_DMA_Update, DISABLE);

  DMA_Cmd(MOTOR_PWM_STREAM, DISABLE);

// A term to predict the motor physical movement for the next 4 cycles

  int32_t phase_adv = (int32_t)(mot_ctrl_obj.rpm*0.02667F);

  uint32_t i;

  for(i=0; i<4; i++) {

    // Calculate Sin & Cos from desired motor angle

    motor_SineCosine_f(&motor_ctrl, phase_adv*i);

    // Calculate Ialpha, Ibeta, and Id, Iq from Sin, Cos, Ia, Ib

    motor_ControlClarkePark_f(&motor_ctrl);

    // Calculate control values based on motor mode: Vq, Vd & desired angle

    motor_mode_specific_ctrl();

    // Calculate qValpha, qVbeta from Sin, Cos, Vd, Vq

    motor_ControlInversePark_f(&motor_ctrl);

    // Calculate PWM duty cycles from qValpha & qVbeta

    motor_SpaceVectorModCalcs(motor_ctrl.qValpha, motor_ctrl.qVbeta, mot_ctrl_obj.pwm_registers, motor_ctrl.svm_ref_vectors);

    // PWM Output - Mapped based on motor mode

    svm_dma_buf[i*3] = mot_ctrl_obj.pwm_registers[0];

    svm_dma_buf[i*3+1] = mot_ctrl_obj.pwm_registers[1];

    svm_dma_buf[i*3+2] = mot_ctrl_obj.pwm_registers[2];

  }

  // Re-enable timer request and DMA

  TIM_DMACmd(MOTOR_PWM_TIMER, TIM_DMA_Update, ENABLE);

  DMA_Cmd(MOTOR_PWM_STREAM, ENABLE);

}

4 REPLIES 4

Read out and check/post content of the relevant DMA and TIM registers.

I don't understand SPL, or whatever "library" you are using.

JW

> .DMA_PeripheralInc   = DMA_PeripheralInc_Enable,

You don't want this. It's the DCR/DMAR mechanism which moves the "window" across the required registers.

JW

walkingcrane
Associate II

Thanks, JW. I have made this change but still not getting any update. One thing I am wondering about is the mapping of the DMA stream and channel. Per Table 43 in the TRM, I am using DMA2, Stream 1, Channel 6 which should give me access to TIM1_CH1. However, across that channel, for different streams, there are many possible TIM1 registers available. Should I be using, for example, Stream 5, Channel 6 for TIM1_UP? I cannot find a good explanation in the TRM or datahsheet (or Googling) for what these designations mean specifically.

-Jamie

"Stream" is one DMA "machine". Each such "machine" can be triggered by a choice of input triggers (requests); this choice is called Channel. I.e., you can have several Streams in action simultaneously, but only one Channel per stream.

DMA2/Stream1/Channel6 is triggered by TIM1_CH1, i.e. by a capture/compare event on channel 1, subject to enable by setting TIMx_DIER.CC1DE.

> Should I be using, for example, Stream 5, Channel 6 for TIM1_UP

Yes.

_UP stands for the Update event, ie. "timer overflow"; enabled by TIMx_DIER.UDE.

JW