cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F4 DMA and Varying Duty Cycle

haseebnain
Associate II
Posted on June 28, 2016 at 03:09

Hi everyone,

I am wondering how to go about setting up a varying duty cycle on an F4 board using the DMA. This is for a personal project of mine, I have asked around and believe my code does not have any glaring logic issues but it still does not vary the duty cycle of the timer. For the purpose of this project I need to use TIM5 (projecting the output PWM to PA0). I attempted using Channel 6 and Stream 0 but cannot as something is already using PA2 not allowing me to mess with TIM5_CH3.

//initialize structs
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
uint16_t prescalerValue;
//enable GPIOA clock
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//enable TIM5 clock
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
//enable DMA1 clock
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
/* GPIOA Configuration: TIM5 Channel 1 as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM5);
// Stop timer
TIM_Cmd(TIM5, DISABLE);
/* Compute the prescaler value */
prescalerValue = (uint16_t) (SystemCoreClock / 2 / 84000000) - 1;
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 104; 
// 800kHz
TIM_TimeBaseStructure.TIM_Prescaler = prescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM5, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM5, TIM_OCPreload_Enable);
//TIM5->CCR2 = 106;
TIM_Cmd(TIM5, ENABLE);
/* configure DMA */
/* DMA1 Channel Config */
/* DMA1 Stream6 setup*/
DMA_Cmd(DMA1_Stream6, DISABLE); 
// disable DMA channel 6
DMA_DeInit(DMA1_Stream6);
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_Channel = DMA_Channel_6;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TIM5->CCR1);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ledStripDMABuffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = WS2811_DMA_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream6, &DMA_InitStructure);
DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);
DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6); 
// clear DMA1 Channel 6 transfer complete flag
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream6_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_PRIORITY_BASE(NVIC_PRIO_WS2811_DMA);
NVIC_InitStructure.NVIC_IRQChannelSubPriority = NVIC_PRIORITY_SUB(NVIC_PRIO_WS2811_DMA);
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

My desire is to update the duty cycle every time the timer update occurs, the value of that duty cycle should be updated with the value of the

ledStripDMABuffer

. Is there something obvious I am missing? #stm32f4-dma-timer
10 REPLIES 10
Posted on June 28, 2016 at 03:38

If you are going to post hundreds of lines of code make sure it is compilable and demonstrates the failure, otherwise there is little point. Review complete/concise example that I post.

Show the data table. Explain better the PA0 vs PA2 issue you are talking about, it's not making much sense to me. Looks to be a reasonable stab at doing duty cycle modulation on PA0 (TIM5_CH1), but critical elements are missing to draw any conclusions.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
haseebnain
Associate II
Posted on June 28, 2016 at 19:11

Sorry about that, was not sure if my problem had to do with the timer setup or the DMA setup, posted it all just to be safe haha. 

I cannot use PA2 as it is already in use, my only available option was PA0 and TIM5.

When output the signal with the DMA enabled, the output does not vary its duty cycle, rather it changes the entire period of the signal. My desire is to keep the period and just vary the duty cycle. I don't believe that TIM5 is setup incorrectly and think it might be a problem with the DMA initialization but I can't see what that could be.

Once again sorry about the incredibly long post, did not mean to blast too much information at you!

haseebnain
Associate II
Posted on June 28, 2016 at 20:34

I have uploaded images of the oscilloscope, the first image is when a static value of 72 is sent to TIM5->CCR1. The second image is when the DMA is enabled. (The image was too large to upload directly to the forums)

http://imgur.com/a/W7clc

Notice the change in horizontal divisions, the periods of the DMA enabled signal is incredibly large.

Posted on June 29, 2016 at 05:22

It is not so much a matter of length, but a selective cut-n-paste leaving out critical pieces.

This should be viable

// STM32 TIM5 PWM DMA STM32F4 800 KHz - Scope PA0 - sourcer32@gmail.com
// Assuming 168 MHz clock, with APB1 @ 42 MHz
#include ''stm32f4_discovery.h''
#define SAMPLES 4
uint32_t SampleVector[SAMPLES] = { 20, 40, 60, 80 }; // TIM5 32-bit
/**************************************************************************************/
void RCC_Configuration(void)
{
/* DMA1 (TIM5 on APB1) clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
/* TIM5 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
/* GPIOA clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
}
/**************************************************************************************/
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0 TIM5_CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Connect TIM5 pin */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM5); // PA0 TIM5_CH1
}
/**************************************************************************************/
void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
// Per RM0090 TIM5_Update is DMA1, Channel 6, Stream 6 (or Stream 0)
DMA_DeInit(DMA1_Stream6);
DMA_InitStructure.DMA_Channel = DMA_Channel_6;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&TIM5->CCR1;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&SampleVector[0];
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = SAMPLES;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // TIM5 is 32-bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream6, &DMA_InitStructure);
DMA_Cmd(DMA1_Stream6, ENABLE);
}
/**************************************************************************************/
void TIM5_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* Time base configuration */
TIM_InitStruct.TIM_Prescaler = 0; // This will configure the clock to 84 MHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; // Count-up timer mode
TIM_InitStruct.TIM_Period = 105 - 1; // 800 KHz
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStruct.TIM_RepetitionCounter = 0; // Set to 0, not used
TIM_TimeBaseInit(TIM5, &TIM_InitStruct);
TIM_ARRPreloadConfig(TIM5, ENABLE);
/* Output Compare Toggle Mode configuration: Channel2 - so we can actually measure widths */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OC1Init(TIM5, &TIM_OCInitStructure);
/* TIM5 Update DMA Request enable */
TIM_DMACmd(TIM5, TIM_DMA_Update, ENABLE);
/* TIM5 enable counter */
TIM_Cmd(TIM5, ENABLE);
}
/**************************************************************************************/
int main(void)
{
RCC_Configuration();
GPIO_Configuration();
DMA_Configuration();
TIM5_Configuration();
while(1); // Don't want to exit
}
/**************************************************************************************/
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf(''Wrong parameters value: file %s on line %d

'', file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
haseebnain
Associate II
Posted on June 29, 2016 at 22:47

This is an amazing response, thank you Clive!

I ran your code on my board and ended up getting the same type of period manipulation as shown in the images I posted, the waveform was almost identical so I'm not sure what the issue could be. I have some questions though, my understanding was that circular mode would update the DMA with constant information, ie a non-changing buffer. Can an interrupt be issued for a transfer complete in circular mode, simply because my buffer is updated in another file. I attempted to add in a simple IRQHandler to disable the DMA briefly and an enabler to reinitiate the DMA process.

void

DMA1_Stream6_IRQHandler(

void

)
{
if

(DMA_GetFlagStatus(DMA1_Stream6, DMA_FLAG_TCIF6)) {
ws2811LedDataTransferInProgress = 0;
DMA_Cmd(DMA1_Stream6, DISABLE);
TIM_DMACmd(TIM5, TIM_DMA_Update, DISABLE);
DMA_ClearITPendingBit(DMA1_Stream6, DMA_IT_TCIF6);
}

}

void

ws2811LedStripDMAEnable(

void

)
{
DMA_SetCurrDataCounter(DMA1_Stream6, WS2811_DMA_BUFFER_SIZE);

// load number of bytes to be transferred

TIM_SetCounter(TIM5, 0);
DMA_Cmd(DMA1_Stream6, ENABLE);
TIM_DMACmd(TIM5, TIM_DMA_Update, ENABLE);
}

I am also in the process of installing an IDE and trying to get this working on a STM32F4 Discovery Board as well.
Posted on June 30, 2016 at 01:29

Well I did check on a scope here on an STM32F4-DISCO, and definitely modulating duty for 800 KHz.

If I was modulating a long/continuous stream I'd probably use HT and TC interrupts to refill half the buffer (ie replace the half you just finished sending).

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
relics78
Associate
Posted on July 01, 2016 at 20:34

Thanks Clive, for posting this. I have been working with similar code (from the examples provided in the STM32 library) to vary the output PWM duty cycle on channel 1 of TIM1 on the STM32F302R8T6. The code that I am working with also updates the CCR1 register of TIM1 with a new value from memory (via DMA) on each DMA update request. I have a couple of questions that I am looking for advice on:

1. In your posted code, I see that the DMA buffer size is set to 'SAMPLES', which is basically the total number of duty cycle variations required in the output. On each DMA update request from the timer, we would need to transfer one value from 'SampleVector' to CCR1, and keep going (one value at a time) until we turn around, correct? What register setting controls the number of data items transferred (from memory to peripheral) on each DMA update request? Surely we can't have more than one item transferred from 'SampleVector' on each DMA request update, right?

2. If we want a higher output PWM frequency (say 4 Mhz), how can we ensure that the DMA transfer to CCR1 happens fast enough so that the new duty cycle reflects in the next output period?

Any help would be very much appreciated. Thanks!

Posted on July 01, 2016 at 20:56

The DMA is programed in transfer units, byte, half-word (16-bit), or word (32-bit)

One would be serviced for each discrete request from the peripheral.

The ADC doing 4 channels, with a period time trigger, would do bursts of 4 transfers after each trigger, as it would send 4 requests to the DMA unit.

The DMA unit is running on a bus/memory subsystem clocking at 168 MHz (figure F302 at 72 MHz), is suspect it can service a 4 MHz request, and the FIFO could also be enabled so the contention doesn't kill other operation. Typically it is loading a shadow CCRx which gets loaded by the timer into the active register at the Update. I wouldn't preload as that has race hazards written all over it.

Not sure if the timer can do multiple DMA requests, there is some indication it might, I'm also not sure where the rate ceiling is, as I haven't evaluated it, TIM/PWM stuff isn't my bread-n-butter work.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
relics78
Associate
Posted on July 01, 2016 at 22:09

Thanks for the response, Clive. I too was under the impression (based on various examples I saw on how to generate PWM with a varying duty cycle) that on each DMA update request, only one data item is transferred. The reference manual does not seem to be very clear on this though.

Also, regarding generating PWM with a varying duty cycle at higher frequencies, for the STM32F302R8T6 running at 72MHz,  the input clock to TIM1 set to 72Mhz, and the output PWM at 4Mhz, we would have about 72/4 = 18 clock cycles to DMA a new value into CCR1 before the next output period. Does that make sense? Seems like that should be enough time.

I have still been running into issues when I crank up the output frequency to anything above 1MHz. I am trying to vary the duty cycle so that I have one narrow pulse and one wide pulse, in alternation. However, at frequencies above 1Mhz, I see two narrow pulses and two wide pulses in alternation, which seems to indicate that each CCR1 value (duty cycle) is being used twice. I will be debugging this more. In the meantime, any pointers on what could be wrong would be appreciated. Thanks!