Monday 20 April 2009

pulse timers

As I need a short pulse to fire the triacs I checked upon the possibilities of the other timers. TIM2,3 and 4 act as general purpose timers and are well suited for the job. I experimented with PWM mode but I found it not so good in my case. I decided to toggle the IOs manually on compare match events. The good thing is that you have 4 compare match events per timer. This opens up a lot of possibilities. I will put the principal of working here using TIM2 as an example but in fact I use all 3 of them to fire 6 triacs in total. The first thing to do is setting the period of the timer. In my case this must be set to 10ms as we work with 50HZ/20ms mains frequency. We must fire for the positive as well as for the negative sine wave so that makes a period of 10ms.

/* Time 2 base configuration 10ms */
TIM_TimeBaseStructure.TIM_Period = 9999;
TIM_TimeBaseStructure.TIM_Prescaler = 72;
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

Period calculation:

72MHz / 72 = 1MHz or 1µs period unit.
1µs + 9999 x (1µs) = 10ms

Now we need just a little bit more to set up comparing to TIM1 because we want to use the compare matches. This is done initializing the OC_struct with ride;

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = triacvalue;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);

This one above sets the CC1 to trigger at triacvalue. This trigger will be signalled with an interrupt. The preload must be disabled to able to change the compare value at run time. Be aware to put OCMode to TIM_OCMode_Inactive because otherwise the timer output lines are enabled. In this design I wanted to select my own gpio pins.

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = triacvalue + 50;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);

The second compare match will be just a bit later than first one to make our short fire pulse. In this case we make a pulse of 50µs. This process of setting up the compare matches repeats over all the timers you want to bind with some interrupt event. The NVIC interrupt controller must be set as follows:

/* Enable the TIM2 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

The only thing you need to do after that is writing your code in the interrupt handler and don't forget to start the timer clock which is different from the TIM1 :

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);

You interrupt handler code might be :

void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
GPIO_SetBits(GPIOC, GPIO_Pin_9);
}
else if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
GPIO_ResetBits(GPIOC, GPIO_Pin_9);
}
.....

Also here as simply as that. So the pulses are made by toggling the gpios on interrupt events. You can also change your interrupt priority here. But there i will cover that later on as there's plenty to say on it.

Complexity is completely avoided in this architecture. The speed of writing code is highly increased if you use the ride firmware library for the arm target. You have to download the rkit for arm to be able to use this and that library in your ride IDE.

main timer

My first timer (TIM1) is acting as the start of ADC conversion. I know I can bind some timers on the ADC events but I choose to separate that. The code snippet is:

/* Time 1 Base configuration 25µs */
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 4;
TIM_TimeBaseStructure.TIM_Prescaler = 360;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

TIM_CKD_DIV1 is actually the main cpu clock and in my case 72MHz. The precaler is dividing that frequency to have the final period.

72MHz/360 = 200kHZ or 5µs period unit.

The minimum period you can set up is 5µs because the STM needs one complete cycle first to start counting. The TIM_Period struct member defines the final timer period you want to work with in my case:

5µs + 4 x (5µs) = 25µs or 40kHz sampling frequency.

We want our timer to just count up until it's overflowed and gives an interrupt on overflow that will be done by taking TIM_CounterMode_Up as the value for CounterMode member. To enable the interrupt on that timer following snippet is needed:

/* Enable and configure TIM1 IRQ channel */
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

The only thing you need to do now is simply put some code in your int handler known as vector address. In the ride environment this is done in a separate file [target]_it.c

void TIM1_UP_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

You can change the interrupt priority here but I will go into details on general interrupt handling in an other blog. Don't forget to enable the clock for the timer and the interrupt:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

TIM_ITConfig(TIM1, TIM_IT_Update , ENABLE);

That's basically it so pretty simple and straightforward and easy to learn if you are familiar with other targets.