cancel
Showing results for 
Search instead for 
Did you mean: 

F0discovery PWM with external clock and trigger

chrispadbury
Associate II
Posted on November 13, 2012 at 17:38

I want to create a simple PWM implementation which uses an external clock source and is synchronised to a trigger pulse (one pulse per PWM period). I need at least three (ideally four) channels of output with different duty cycles but all synchronised.

I have started by using the TIM_PWM_Output example in the firmware package. This provides the four outputs I want. What I am unclear on is how to set up the external clock source and which pin should be used for that. The code uses TIM1. I see my first hurdle as using an external clock source, then I can try to add the external trigger. Here is my code as it looks copied from the TIM_PWM_Output example:

#include ''stm32f0_discovery.h'' 
int Set_PWM_Period(int period); 
int Set_PWM_Duty_Cycle(int duty_cycle_1, int duty_cycle_2, int duty_cycle_3, int duty_cycle_4); 
int Synchronise_To_Row_Clock(void); 
int TIM_Config(void); 
int Set_PWM_Period(int period) { 
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 
TIM_OCInitTypeDef TIM_OCInitStructure; 
uint16_t TimerPeriod = 0; 
uint16_t Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0; 
/* Compute the value to be set in ARR regiter to generate signal frequency at 57 Khz */ 
TimerPeriod = (SystemCoreClock / 17570 ) - 1; 
TimerPeriod = period; //<-- ************************************************************************ 
/* Compute CCR1 value to generate a duty cycle at 50% for channel 1 */ 
Channel1Pulse = (uint16_t) (((uint32_t) 5 * (TimerPeriod - 1)) / 10); 
/* Compute CCR2 value to generate a duty cycle at 5% for channel 2 */ 
Channel2Pulse = (uint16_t) (((uint32_t) 375 * (TimerPeriod - 1)) / 1000); 
/* Compute CCR3 value to generate a duty cycle at 25% for channel 3 */ 
Channel3Pulse = (uint16_t) (((uint32_t) 25 * (TimerPeriod - 1)) / 100); 
/* Compute CCR4 value to generate a duty cycle at 5% for channel 4 */ 
Channel4Pulse = (uint16_t) (((uint32_t) 125 * (TimerPeriod- 1)) / 1000); 
/* TIM1 clock enable */ 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE); 
/* Time Base configuration */ 
TIM_TimeBaseStructure.TIM_Prescaler = 0; 
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
TIM_TimeBaseStructure.TIM_Period = TimerPeriod; 
TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; 
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); 
/* Channel 1, 2, 3 and 4 Configuration in PWM mode */ 
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; 
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; 
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; 
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; 
TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; 
TIM_OC1Init(TIM1, &TIM_OCInitStructure); 
TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; 
TIM_OC2Init(TIM1, &TIM_OCInitStructure); 
TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; 
TIM_OC3Init(TIM1, &TIM_OCInitStructure); 
TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; 
TIM_OC4Init(TIM1, &TIM_OCInitStructure); 
/* TIM1 counter enable */ 
TIM_Cmd(TIM1, ENABLE); 
/* TIM1 Main Output Enable */ 
TIM_CtrlPWMOutputs(TIM1, ENABLE); 
return 0; 
} 
int Set_PWM_Duty_Cycle(int duty_cycle_1, int duty_cycle_2, int duty_cycle_3, int duty_cycle_4) { 
return 0; 
} 
int Synchronise_To_Trigger_Pulse(void) { 
return 0; 
} 
/** 
* @brief Configure the TIM1 Pins. 
* @param None 
* @retval None 
*/ 
int TIM_Config(void) 
{ 
GPIO_InitTypeDef GPIO_InitStructure; 
/* GPIOA Clocks enable */ 
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE); 
/* GPIOA Configuration: Channel 1, 2, 3 and 4 as alternate function push-pull */ 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2); 
return 0; 
} 
int main(void) { 
int PWM_period, PWM_duty_cycle; 
TIM_Config(); 
PWM_period = 1000; //manual setting of PWM_period 
Set_PWM_Period(PWM_period); 
//PWM_duty_cycle_1 = 500; //manually set PWM_duty_cycle for test 
//PWM_duty_cycle_2 = 375; //manually set PWM_duty_cycle for test 
//PWM_duty_cycle_3 = 250; //manually set PWM_duty_cycle for test 
//PWM_duty_cycle_4 = 125; //manually set PWM_duty_cycle for test 
//Set_PWM_Duty_Cycle(PWM_duty_cycle_1, PWM_duty_cycle_2, PWM_duty_cycle_3, PWM_duty_cycle_4); 
//Synchronise_To_Trigger_Pulse(); // trigger for sync 
while(1); 
} 

Any help gratefully received. #pwm-input-external-clock #pwm-stm32f0-discovery-external #external-clock-pwm #pwm-synchronisation-external
7 REPLIES 7
chrispadbury
Associate II
Posted on November 14, 2012 at 14:43

Here is the code with it changed to use extrenal clock (Mode 1)

int Set_PWM_Period(uint16_t period) { 
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; 
TIM_OCInitTypeDef TIM_OCInitStructure; 
uint16_t TimerPeriod = 0; 
uint16_t Channel1Pulse = 0, Channel2Pulse = 0, Channel3Pulse = 0, Channel4Pulse = 0; 
/* Compute the value to be set in ARR regiter to generate signal frequency at 57 Khz */ 
TimerPeriod = (SystemCoreClock / 17570 ) - 1; 
TimerPeriod = period; //<-- ************************************************************************ 
/* Compute CCR1 value to generate a duty cycle at 50% for channel 1 */ 
Channel1Pulse = (uint16_t) (((uint32_t) 5 * (TimerPeriod - 1)) / 10); 
/* Compute CCR2 value to generate a duty cycle at 5% for channel 2 */ 
Channel2Pulse = (uint16_t) (((uint32_t) 375 * (TimerPeriod - 1)) / 1000); 
/* Compute CCR3 value to generate a duty cycle at 25% for channel 3 */ 
Channel3Pulse = (uint16_t) (((uint32_t) 25 * (TimerPeriod - 1)) / 100); 
/* Compute CCR4 value to generate a duty cycle at 5% for channel 4 */ 
Channel4Pulse = (uint16_t) (((uint32_t) 125 * (TimerPeriod- 1)) / 1000); 
/* TIM1 clock enable */ 
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE); 
/* Time Base configuration */ 
TIM_TimeBaseStructure.TIM_Prescaler = 0; 
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
TIM_TimeBaseStructure.TIM_Period = TimerPeriod; 
TIM_TimeBaseStructure.TIM_ClockDivision = 0; 
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; 
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); 
/* Channel 1, 2, 3 and 4 Configuration in PWM mode */ 
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //TIM_OCMode_PWM1; //PWM1 vs PWM2 sets polarity 
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; 
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; 
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; 
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; 
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; 
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset; 
TIM_OCInitStructure.TIM_Pulse = Channel1Pulse; 
TIM_OC1Init(TIM1, &TIM_OCInitStructure); 
//TIM_OCInitStructure.TIM_Pulse = Channel2Pulse; 
//TIM_OC2Init(TIM1, &TIM_OCInitStructure); 
TIM_OCInitStructure.TIM_Pulse = Channel3Pulse; 
TIM_OC3Init(TIM1, &TIM_OCInitStructure); 
TIM_OCInitStructure.TIM_Pulse = Channel4Pulse; 
TIM_OC4Init(TIM1, &TIM_OCInitStructure); 
//******************************************************************** 
TIM_ETRClockMode1Config(TIM1, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00); //<--- Clocked by PA9(TIM1_CH2) , Mode 1 
TIM_TIxExternalClockConfig(TIM1, TIM_TIxExternalCLK1Source_TI2, TIM_ICPolarity_Rising, 0x0); //<--- Clocked by PA9(TIM1_CH2) 
//******************************************************************** 
/* TIM1 counter enable */ 
TIM_Cmd(TIM1, ENABLE); 
/* TIM1 Main Output Enable */ 
TIM_CtrlPWMOutputs(TIM1, ENABLE); 
return 0; 
} 
int TIM_Config(void) 
{ 
GPIO_InitTypeDef GPIO_InitStructure; 
/* GPIOA Clocks enable */ 
RCC_AHBPeriphClockCmd( RCC_AHBPeriph_GPIOA, ENABLE); 
/* GPIOA Configuration: Channel 1, 2, 3 and 4 as alternate function push-pull */ 
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_10 | GPIO_Pin_11; 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; 
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //<--- Clocked by PA9(TIM1_CH2) , Mode 1 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2); //<--- Clocked by PA9(TIM1_CH2) , Mode 1 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2); 
return 0; 
}

So that solves the external clocking of the PWM (seems good up to just over 20MHz).
chrispadbury
Associate II
Posted on November 28, 2012 at 16:57

Had to re-add the last post as I must have accidentally overwritten it. The key thing was having GPIO_Mode_AF for pin PA9 (not GPIO_Mode_IN).

For synchronisation, I'm using Mode 2. Replace the section between stars with:

//******************************************************************** 
TIM_ETRClockMode2Config(TIM1, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00); //<--- Clocked by PA12(TIM1_ETR) , Mode 2 
TIM_ETRClockMode2Config(TIM1, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x00); //<--- Clocked by PA12(TIM1_ETR) , Mode 2 
//******************************************************************** 
//******************************************************************** 
// Synchronisation: 
TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2); // CAUTION: what's the filter??!! 
// TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Trigger); 
TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset); 
//********************************************************************

Also, setup GPIO PA12 as well as GPIO PA9:

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_12; //<--- PA9(TIM1_CH2) & PA12(TIM1_ETR) 
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; 
GPIO_Init(GPIOA, &GPIO_InitStructure); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_2); //<--- Clocked by PA9(TIM1_CH2) , Mode 1 or sync for Mode 2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource12, GPIO_AF_2); //<--- Clocked by PA12(TIM1_ETR) , Mode 2 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_2); 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource11, GPIO_AF_2);

Notice there are two options there (one commented out). TIM_SlaveMode_Trigger causes the PWM to start and then run, triggeredoff a singlerising edge (synchronised start). I want TIM_SlaveMode_Reset which causes the PWM to reset each time it receives a synchronisation pulse. Using TIM_SlaveMode_Reset I set the PWM period very high, the pulse duration to what I want and then each rising edge on PA9 causes a new PWM cycle (new pulse of my set duration).
chrispadbury
Associate II
Posted on November 28, 2012 at 17:15

One thing I have noticed is jitter with the external clock. This is in both modes (with and without synchronisation). By attaching a scope to the external clock and the PWM output pulse, and triggeriung the scope of the PWM output pulse rising edge, I see the external clock moving back and forward. I hoped for it to be static. This suggests that the PWM isn't truly clocked off the external closk source, but is in some way sampling the external clock source.

Varying the clock source frequency doesn't change the absolute size of the jitter either which again suggests some for of high speed sampling of the external clock source.  ??The jitter seems to be about 10ns. It varies the length of the PWM pulse as the end of the pulse is better tied to the external clock ?? [I'll check this]
chrispadbury
Associate II
Posted on November 29, 2012 at 12:11

It doesn't look to be better synchronised for the falling edge of the pulse.

Here is a scope trace of the issue:

0690X00000603FxQAI.jpg

The scope is set up with 20ns timebase, 2V/div and persistence so you can see the jitter. Green is the external signal generator (20MHz Square wave), Pink is the same signal measured at the board via a scope probe, and Blue is the PWM output.

As you can see from the scope image, the resulting PWM pulse, which should be one external clock cycle long (50ns), randomly adopts one of two lengths 40ns or 60ns.

The 20ns difference points at the GPIO_Speed_50MHz of all the pins used (PA8 to PA12).
chrispadbury
Associate II
Posted on November 30, 2012 at 10:34

''The 20ns difference points at the GPIO_Speed_50MHz of all the pins used (PA8 to PA12).''

To investigate this I tried changing it to GPIO_Speed_10MHz and then GPIO_Speed_2MHz, neither of which made a change to the 20ns difference (and ability to externally clock the PWM at 20MHz). It still points at a 50MHz sampling, but it isn't controlled by the GPIO_InitStructure.GPIO_Speed
crt2
Associate II
Posted on December 05, 2012 at 09:53

I took a peek at your photo and I see 2 falling edges and 2 rising edges. Rising edge is when next few samples are higher than previous one if before they were lower. Electronics inside trigger to that and same goes for falling edge. From my point of view how can you say that green or pink signals are SQUARE waves? They should look like PWM. The jitter in PWM signal can be ''supressed'' by using Capacitor (RC particle) (just the right one - you need to calculate that based on your situation).

First make waves look like you've described them (trigger waves) then I'd take a look where that 20ns sneaks in (in case it still will)
rob239955_stm1_st
Associate
Posted on December 12, 2012 at 11:19

Hi Chris

The cause of the jitter is likely to be:

1. The external timer input should be no faster than 1/4 of the internal timer clock, APB2, which is probably 60MHz in your system. A divider is available if your external clock is too fast.

2. The external input is re-clocked into the APB2 domain so you'll get some jitter waiting for the next clock edge.

Regards

Rob

Fen Consultants, UK