cancel
Showing results for 
Search instead for 
Did you mean: 

Timer 2 output compare mode generating DMA requests without CC match on F411

tergu
Associate II

Hey everyone,

I am outputting a waveform via TIM2 CCR1 stored in an array and transferred via circular DMA access.

The timer is configured for output compare so that whenever a match occurs, a dma request is generated and the next value is fetched from the array. If I make CCR1 larger than ARR no match should occur and no new value should be loaded. The output should keep it's last state. For whatever reason the next values are still loaded into CCR1. I have looked through the datasheet but could not find anything. The code I use to test this is below.

If anyone has an idea on why this is happening please get back to me.

Cheers,

Christian.

  #define array_size 4
  unsigned int array[array_size] = {200, 400, 1000, 600};
 
  //enable clock for Port A
  RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  //set PA0 to AF output
  GPIOA->MODER |= GPIO_MODER_MODER0_1;
  //set AF1
  GPIOA->AFR[0] |= 0x01;
  //set to high speed
  GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR0_0 | GPIO_OSPEEDER_OSPEEDR0_1;
 
  //enable clock for tim2
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
  //set prescaler
  TIM2->PSC = 0;
  //set ARR Value
  TIM2->ARR = 999;
  //set initial CC value
  TIM2->CCR1 = TIM2->ARR >> 1;
  //set CC mode
  TIM2->CCMR1 =  TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1;
  //enable CC Channel
  TIM2->CCER = TIM_CCER_CC1E;
  //enable DMA for CC1
  TIM2->DIER |= TIM_DIER_CC1DE;
  //start timer
  TIM2->CR1 = TIM_CR1_CEN;
 
  //enable clock for DMA1
  RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
  //set peripheral register address
  DMA1_Stream5->PAR = &(TIM2->CCR1);
  //Set memory address
  DMA1_Stream5->M0AR = &(array[0]);
  //set number of items to be transferred
  DMA1_Stream5->NDTR = array_size;
 
  //set channel to 3 (TIM2)
  DMA1_Stream5->CR |= DMA_SxCR_CHSEL_0 | DMA_SxCR_CHSEL_1;
  //Set priority
  DMA1_Stream5->CR |= DMA_SxCR_PL_0 | DMA_SxCR_PL_1;
  //set memory size
  DMA1_Stream5->CR |= DMA_SxCR_MSIZE_1;	//32 bit
  //set peripheral size
  DMA1_Stream5->CR |= DMA_SxCR_PSIZE_1;	//32 bit
  //increment memory pointer
  DMA1_Stream5->CR |= DMA_SxCR_MINC;
  //circular mode
  DMA1_Stream5->CR |= DMA_SxCR_CIRC;
  //direction
  DMA1_Stream5->CR |= DMA_SxCR_DIR_0;
  //clear flags
  DMA1->HIFCR = 0xFFFFFFFF;
  DMA1->LIFCR = 0xFFFFFFFF;
 
  //enable dma controller
  DMA1_Stream5->CR |= DMA_SxCR_EN;

5 REPLIES 5
S.Ma
Principal

Have done something like this in the past, using Standard library. It's a generic Timer code which enables DMA multi-pulse generation. Maybe it will ring a bell to make your code operate:

// In this scheme, the timer is reset, wait for Ch2 rising edge, then starts one pulse mode 
// For example, the output compares can generate pulses or edge which can trigger time shifted ADC, DAC, Analog Switch behaviours
// Using external triggers is easier to probe, hence easier debug, faster move forward job
void EnableOneShotTimerCC_Triggered(Timer_t* Timer, u32 n) {
 
  TIM_DMA_Activate(Timer); // Will sweep through all DMA possible interaction
  
  TIM_SelectOnePulseMode(Timer->TIM, TIM_OPMode_Single); // One Pulse Mode selection
 
  if(n==1)
    TIM_SelectInputTrigger(Timer->TIM, TIM_TS_TI1FP1); // Input Timer Trigged on CH1
  else
    if(n==2) TIM_SelectInputTrigger(Timer->TIM, TIM_TS_TI2FP2); // Input Timer Trigged on CH2
      else
        while(1);
 
  // Slave Mode selection: Trigger Mode (the trigger will set the Timer enable bit)
  TIM_SelectSlaveMode(Timer->TIM, TIM_SlaveMode_Trigger);  
}
 
static void TimerOutputCC_SetDMA(Timer_t* Timer, u32 n) {
  
  DMA_InitTypeDef DMAI;
  u32 DataSize;
  
  if(Timer->Max > 0x0000FFFF)
    DataSize = DMA_PeripheralDataSize_Word;
  else
    DataSize = DMA_PeripheralDataSize_HalfWord;
  
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1 , ENABLE);
 
  DMA_StreamChannelInfo_t* DSCI = Get_TimerDMA_InfoByPPP_n((u32)Timer->TIM, n, DMA_DIR_PeripheralToMemory | DMA_DIR_MemoryToPeripheral);
  DMA_DeInit(DSCI->Stream);//(DMA1_Stream4);
  DMAI.DMA_Channel = DSCI->Channel;//DMA_Channel_5;  
  DMAI.DMA_PeripheralBaseAddr = (u32)Timer->CCR[n];
  DMAI.DMA_Memory0BaseAddr = (u32)Timer->EdgesTableAdr[n];
  DMAI.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  DMAI.DMA_BufferSize = Timer->EdgesSize[n];
  DMAI.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  DMAI.DMA_MemoryInc = DMA_MemoryInc_Enable;
  DMAI.DMA_PeripheralDataSize = DataSize;
  DMAI.DMA_MemoryDataSize = DataSize;
  DMAI.DMA_Mode = DMA_Mode_Circular;
  DMAI.DMA_Priority = DMA_Priority_High;
  DMAI.DMA_FIFOMode = DMA_FIFOMode_Disable;
  DMAI.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  DMAI.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  DMAI.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
 
  DMA_Init(DSCI->Stream/*DMA1_Stream4*/, &DMAI);
}
 
 
 
void SetTimerOutputCC_SingleEdge(Timer_t* Timer, u32 n, u32 Value_lsb) {
 
  Timer->EdgesSize[n] = 0; // Non zero triggers the DMA mode for multiple pulses
  Timer->EdgesTableAdr[n] = 0;
 
  TIM_OCInitTypeDef OC;
  TIM_OCStructInit(&OC);
  
  OC.TIM_OCMode = TIM_OCMode_PWM2;
  OC.TIM_OutputState = TIM_OutputState_Enable;
  OC.TIM_Pulse = Value_lsb;
  OC.TIM_OCPolarity = TIM_OCPolarity_Low;  
  
  TIM_OCnInit(Timer->TIM, n, &OC);
}
 
 
void SetTimerOutputCC_MultiEdges(Timer_t* Timer, u32 n, u32 Adr, u32 Size) {
 
  Timer->EdgesSize[n] = Size; // Non zero triggers the DMA mode for multiple pulses
  Timer->EdgesTableAdr[n] = Adr;
 
  TIM_OCInitTypeDef OC;
  TIM_OCStructInit(&OC);  
  
  OC.TIM_OCMode = TIM_OCMode_Toggle;
  OC.TIM_OutputState = TIM_OutputState_Enable;
  OC.TIM_Pulse = 1;
  OC.TIM_OCPolarity = TIM_OCPolarity_Low;
  TIM_OCnInit(Timer->TIM, n, &OC);
 
  //CC1 only for now
  TimerOutputCC_SetDMA(Timer, n);
  TIM_OCnPreloadConfig(Timer->TIM, n, TIM_OCPreload_Disable);  // Enable preload feature *  // no shadow registers please
}
 
void EnableFreeRunTimer(Timer_t* Timer) {
  
  //u32 n;
  // Simnple timer mode
  if(Timer->EdgesSize[1]<2) { // DMA needed here
    TIM_Cmd(Timer->TIM, ENABLE);      
    return;
  }
  //=======================================================
  // DMA transfer is required here (CC1 hard code for now
  TIM_DMA_Activate(Timer);
  TIM_Cmd(Timer->TIM, ENABLE); // let's run the Timer at last
}

tergu
Associate II

Thanks, but I don't hear any bells ringing.

The code works, and as long as the CC values are below ARR behaves like I would expect it to. After each CC event a dma request is generated and the next value loaded. What I don't get is how can a dma request be generated it I write a value larger than ARR into CC. It seems like an extra dma request is generated at overflow if no CC match occurs within one timer period?

This is feature of the timer, find this at the description of TIMx_SR.CC1IF:

When the contents of TIMx_CCR1 are greater than the contents of TIMx_ARR, the CC1IF bit

goes high on the counter overflow (in upcounting and up/down-counting modes) or underflow

(in downcounting mode)

JW

tergu
Associate II

Thanks for your reply, I think you are right and this is what is happening.

I actually read over that paragraph but I thought that the DMA and Interrupt are independent as they are configured independently too. I looked through the TIM2 to TIM5 section and also quickly through the DMA section to find something that links these two together but could not find anything. They should add a paragraph somewhere saying "whenever a IF is set a DMA request is generated too" and add "CC1IF bit goes high and a dma request is initiated" to the section you mentioned.

I agree that this could be made clearer; but then the whole timers chapter (together with the concept of several timer chapters) is a complete disaster too.

I've been bitten by exactly this "feature" so early in my encounter with the STM32 that I percieve it now just as a minor imperfection in the documentation. Much more and much nastier were to come...

JW