2018-12-26 07:17 AM
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;
2018-12-26 08:43 AM
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
}
2018-12-26 09:38 AM
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?
2018-12-26 11:47 PM
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
2018-12-27 01:13 AM
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.
2018-12-27 03:23 PM
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