cancel
Showing results for 
Search instead for 
Did you mean: 

Variable frequency pulse train for pulse transformer application

arre24
Associate II
Posted on August 22, 2012 at 17:22

Hi

The objective is to create a

variable frequency

pulse train with as little CPU intervention as possible.

This was quite straightforward with the TMS320 TI MCU. It had a feature called ''PWM chopper'' where it was possible to configure frequency, pulse width, duty cycle and number of pulses.

To achieve the same kind of functionality with the STM32F4, the idea that first came to mind was to use the Advanced Control Timer & DMA peripheral.

A table which consists of the timer periods that define the pulse train characteristics is setup in RAM. The have the DMA transfer these values to the timer compare register using memory increment mode. The timer output pin is configured to toggle at every compare match. 

Also, it is desired to have control of the time interval between each consecutive pulse train. Therefore a timer capture/compare interrupt is enabled. In the interrupt, the pulse train starting time (first positive flank) is defined as:

Current time (time when the interrupt occurs) + A settable delay

The time when the next interrupt occurs is also settable.

The questions are:

1) Is the method described above possible with the STM32F4?

2) Are there any other ways to get the same result?
53 REPLIES 53
Posted on August 22, 2012 at 18:53

Changing the PWM pulse width using DMA is a simple task, programming multiple registers and changing the frequency, not so much.

You could change settings on an update interrupt, would tend to limit frequency (several 100 KHz perhaps), and be lagging.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
arre24
Associate II
Posted on August 23, 2012 at 00:51

I agree that programming multiple registers is not necessary. Only one timer compare register should be enough.

Below is a description where the pulse train consists of two pulses. The table in RAM therefore has four elements with time values that define the pulse widths. The DMA is setup to transfer four data items using memory increment mode. FIFO mode is disabled.

The timer output pin is set to toggle at every compare match

                _____       ____                                             _____         ____

                |         |      |        |                                            |         |        |        |

                |         |      |        |                                            |         |        |        |

________|         |___|        |______________________|         |____|        |____

                \                          \                                          /

                 \                           \                                       /

                   \                           \                                    /

         First positive flank            Pulse train interval time

The problem I encounter is that the DMA stops working when in the timer capture/compare interrupt, a new time to output the pulse train is set.

The strange thing is that if the interrupt is disabled, I get the characteristic that I want. But the downside is that the time interval between each pulse train is fixed.

I would really like to avoid the solution where an interrupt is generated at every positive and negative flank.

harry_rostovtsev
Associate II
Posted on August 23, 2012 at 19:35

Why is changing the PWM frequency so hard? I mean, it is, but it really shouldn't be. I'm actually trying to do something very similar to this now and failing. My goal is to transfer a ramp up/down table via DMA into the timer so I can accel/decel a motor. This requires changing the frequency of PWM but keeping the duty cycle at 50% (approximately, as the motor driver is actually fairly forgiving about this). I found an application note from ST that does just this:

http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&ved=0CDQQFjAB&url=http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/APPLICATION_NOTE/CD002077pdf&ei=JWk2UI30J6S62wXthoHADw&usg=AFQjCNHfi428awx6fEoIuMHry2RYB7cTPg&sig2=7H0WSB9FSgNeLE1WchXXWA

. Below is the relevant code:

void Stepper_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOA Configuration:TIM2 Channel1 in Output */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* ---------------------------------------------------------------
TIM2 Configuration: Output Compare Toggle Mode:
--------------------------------------------------------------- */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period =60000;
TIM_TimeBaseStructure.TIM_Prescaler = 2;
TIM_OCInitStructure.TIM_Pulse = 0; 
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
/* Output Compare Toggle Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode =TIM_OCMode_Toggle; 
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE);
/* TIM enable counter */
TIM_Cmd(TIM2, ENABLE);
/* -------------------------------------------------------------------
DMA1 configuration 
---------------------------------------------------------------------- */
/* DMA1 channel2 configuration ----------------------------------*/
DMA_DeInit(DMA1_Channel2);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM2_ARR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SRC_Buffer_INC;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = BufferSize;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel2, &DMA_InitStructure);
/* Enable DMA1 Channel2 Transfer Complete interrupt */
DMA_ITConfig(DMA1_Channel2, DMA_IT_TC, ENABLE);
/* Enable DMA1 Channel2 */
DMA_Cmd(DMA1_Channel2, ENABLE);
/* Enable TIM2 DMA update request */
TIM_DMACmd(TIM2,TIM_DMA_Update, ENABLE);
}

Notice that they are loading the ARR (Not CCR) register using DMA. They do this on the STM32F1xx series and I'm using STM32F2xx but I can't for the life of me get this to work. I just get a steady pulse instead and the DMA interrupt never fires.
Posted on August 23, 2012 at 20:17

The problem is changing TWO aspects of the signal in an atomic way isn't practical with DMA as the registers are too sparse.

You could change the PULSE WIDTH or FREQUENCY, pick ONE

Using interrupts, you're limited by the saturation point of the processor.

The TIM on the STM32 is a jack of all trades, a master of none. Want exotic or tight control, use an FPGA/CPLD, or a part that does exactly what you need.

The above gets 50% by using TOGGLE mode, not PWM mode, which presumably means it clocks internally twice as fast, and consumes twice as much data via DMA, and might permit you to modulate the mark/space ratio.

In PWM you pull the WIDTH to 0, you get no output.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
harry_rostovtsev
Associate II
Posted on August 23, 2012 at 20:50

Clive1,

Yes, you're absolutely right. I have the accel table set up in such a way that I specify each step twice, once for the top of the wave, once for the bottom. uint16_t SRC_Buffer_DEC[20] ={15000,15000,20000,20000,25000,25000,30000,30000,35000,35000, 40000,40000,45000,45000,50000,50000,55000,55000,60000,60000}; OP should be able to do the same thing by exactly specifying his steps in the table. As far as the code that I posted earlier, I got it to work. You have to enable the CCR interrupt in the timer (very bottom of the page). Here's the final working code for my example running on STM32F207 (STM3220G-eval board):

/* Standard includes. */
/* Library includes. */
#include ''stm32f2xx.h''
#include ''stm32_eval.h''
#include ''stm32f2xx_tim.h''
#include <
stdio.h
>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define TIM8_ARR3_ADDRESS 0x4001042C
#define BufferSize 20
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t TimerPeriod = 0;
uint16_t SRC_Buffer_DEC[20] ={15000,15000,20000,20000,25000,25000,30000,30000,35000,35000,
40000,40000,45000,45000,50000,50000,55000,55000,60000,60000};
uint16_t SRC_Buffer_INC[20] ={60000,60000,55000,55000,50000,50000,45000,45000,40000,40000,
35000,35000,30000,30000,25000,25000,20000,20000,15000,15000};
/**
* @brief Main program
* @param None
* @retval : None
*/
int main(void)
{
/* For printf's */
/* USART resources configuration (Clock, GPIO pins and USART registers) ----*/
/* USART configured as follow:
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
STM_EVAL_COMInit(COM1, &USART_InitStructure);
STM_EVAL_LEDInit(LED1);
STM_EVAL_LEDInit(LED2);
STM_EVAL_LEDInit(LED3);
NVIC_InitTypeDef nvic_init;
NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
nvic_init.NVIC_IRQChannel = DMA2_Stream2_IRQn;
nvic_init.NVIC_IRQChannelPreemptionPriority = 0;
nvic_init.NVIC_IRQChannelSubPriority = 0;
nvic_init.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init);/* enables the device and sets interrupt priority */
/* set priorities of all interrupts in the system... */
NVIC_SetPriority(SysTick_IRQn, 1);
NVIC_SetPriority(DMA2_Stream2_IRQn, 2);
GPIO_InitTypeDef GPIO_InitStructure;
/* ENABLE GPIOC clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
/* GPIOC Configuration:TIM8 Channel3 in Output */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
/* Connect TIM8 pins to AF */
GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_TIM8);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* TIM8 clock enable */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM8, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 1000;
TIM_TimeBaseStructure.TIM_Prescaler = 2;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);
/* PWM Mode configuration: Channel3 */
TIM_OCInitStructure.TIM_Pulse = 0; //SRC_Buffer[0];
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; //TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
//TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
/* Output Compare PWM1 Mode configuration: Channelc Pc.08 */
TIM_OC3Init(TIM8, &TIM_OCInitStructure);
TIM_ARRPreloadConfig(TIM8, ENABLE);
/* TIM8 enable counter */
TIM_Cmd(TIM8, ENABLE);
TIM_OC3PreloadConfig(TIM8, TIM_OCPreload_Enable);
DMA_InitTypeDef DMA_InitStructure;
/* DMA clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2 , ENABLE);
/* Calculate the TIM8 CCR3 address */
uint32_t TIM8_CCR3_Addr = TIM8_BASE + 0x3C;
uint32_t TIM8_ARR_Addr = TIM8_BASE + 0x2C;
DMA_DeInit(DMA2_Stream2);
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = TIM8_ARR_Addr;
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&SRC_Buffer_DEC;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = 20;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
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(DMA2_Stream2, &DMA_InitStructure);
DMA_ITConfig(DMA2_Stream2, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA2_Stream2, ENABLE);
/* TIM8 Update DMA Request enable */
TIM_DMACmd(TIM8, TIM_DMA_CC3, ENABLE);
TIM_DMACmd(TIM8, TIM_DMA_Update, ENABLE);
TIM_CtrlPWMOutputs(TIM8, ENABLE);
while (1)
{
}
}

arre24
Associate II
Posted on August 24, 2012 at 15:43

An error occured when the source code was copied from the editor.

In the GPIO configurations, it should be

GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_TIM1);

I am using the STM3240G evaluation board and it is PB13 that is monitored with the scope.
arre24
Associate II
Posted on August 24, 2012 at 19:26

I managed to find a solution and get the variable frequency pulse train control.

The register configuratiuons are the same. But this code is instead used in the capture/compare interrupt handler.

/* TIM1 capture/compare interrupt handler */ 
void TIM1_CC_IRQHandler(void) 
{ 
/* check if the TIM1_CC4 interrupt has occurred */ 
if (TIM_GetITStatus(TIM1, TIM_IT_CC4) != RESET) 
{ 
/* clear the TIM1_CC4 interrupt pending bits */ 
TIM_ClearITPendingBit(TIM1, TIM_IT_CC4); 
} 
TIM1->CNT = 0; /* Reset the counter back to zero */ 
uint16_t fire_time = 1800; /* firing time */ 
/* Set up data for DMA-transfer */ 
TIM1->CCR1 = SRC_Buffer[0] = fire_time; 
SRC_Buffer[1] = fire_time + 100; 
SRC_Buffer[2] = fire_time + 200; 
SRC_Buffer[3] = fire_time + 300; 
SRC_Buffer[4] = fire_time + 400; 
SRC_Buffer[5] = fire_time + 500; 
/* Next interrupt to occur after 3.6 ms */ 
TIM1->CCR4 = 3600; 
}

The firing time and next interrupt time have in this example constant values. But with some modifications, these values can be set either in another interrupt or in a task. I have no idea why it works now and not before. The significant change is thatin the start of the interrupt, the TIM1 counter value is reset back to zero. So the firing time and next interrupt time can be set as absolute values. In the previous version, the TIM1 counter value TIM->CNT was used as a time base.
joseph
Associate II
Posted on December 02, 2013 at 14:44

i am trying to run the code in the  AN2820 application note in my STM3210C-eval borad. But i have to controll the stepper motor like if i press a push button, the motor should accelerate, maintain the speed for a period T, then deccelerate and stop. how can i modify this code using an external interrupt?