Creating timer-based interrupts

I’d like to interface with the TLC5940 LED driver chip. The chip lacks an internal clock so it relies on an external blanking signal from the microcontroller every couple hundred picoseconds. I’d like to issue the blanking signal via a periodic interrupt handler. What is the best way to configure the hardware timers of the Spark Core to trigger an interrupt handler? Additionally, does the Spark Core base firmware depend on the hardware timers for any of its functionality?

1 Like

The Spark Core firmware indeed uses Hardware Timers to generate PWM outputs via the following Wiring APIs:
1)analogWrite(pin, value) [Refer spark_wiring.c] This produces a fixed frequency of 500Hz with configurable duty cycle[0-100%]
2)Servo::writeMicroseconds(pulseWidth) [Refer spark_wiring_servo.c] This produces a fixed frequency of 50Hz

Apart from above, We also use the hardware timer to control the RGB LED and Button debouncing but those channels are not accessible via external pins.

There are 10 pins on Core mapped to hardware timer channels: D0, D1, A0, A1, A4, A5, A6, A7, RX, TX

We believe in your case, you can use any of the above pins to generate the necessary PWM signal for the LED driver IC.
We can also have a Timer interrupt handler implemented but I don’t think that would be necessary (not sure, we need to check the LED driver management)

For driving other than the default frequency of 500Hz, What we suggest is you can use the analogWrite() function as follows:
Assuming D0 is used, call the following function as one time setup call to configure and enable the Timers mapped to this pin. Do this in setup() call

analogWrite(D0, 0); //value is 0 since we don’t need the default 500Hz PWM output

Now vary the Timer’s Prescaler, Auto Reload register and Channel Compare register to produce the desired PWM ouput as follows:
So suppose on D0, you want a 10KHz PWM with 50% duty cycle, call the below code:
Note: D0 is mapped to Timer 4 => Channel 2

//Set the Prescaler value
TIM4->PSC = 71;
//Load prescaler immediately
TIM4->EGR = 1;
//Set the Autoreload Register value
TIM4->ARR = 99;
//Set the Capture Compare2 Register value
TIM4->CCR2 = 50;//CCR2 is for Channel 2

Hope this answers your query :slight_smile:

3 Likes

Thanks for the follow up. I still need to trigger an interrupt on a timer to periodically copy a the frame buffer into the TLC5940 over the SPI bus. I can send the blanking signal to the chip after the copy is complete. This way I don’t have to use any PWM and can instead reuse TIM4 to triger the interrupt.

Do you have a recommendation for how to bind an interrupt handler to TIM4?

Ok to bind an interrupt handler for TIM4,

Please follow these steps:
1)You need to override the TIM4_IRQHandler which is defined as weak in startup code. For this in your application, define a function:
void TIM4_IRQHandler(void)
{
//Your interrupt handler code should look something like this.
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update); //Clear the interrupt bit
//Your code goes here…
}

if (TIM_GetITStatus(TIM4, TIM_IT_CC2) != RESET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC2); //Clear the interrupt bit
//Your code goes here…
}
}

2)Configure NVIC for TIM4
NVIC_InitTypeDef NVIC_InitStructure;
//Enable the TIM4 global Interrupt
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//Highest priority - depends on you application
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

3)Enable the TIM4 needed interrupt Flags.
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); //TIM Update IT enable
//TIM_ITConfig(TIM4, TIM_IT_CC2, ENABLE); //TIM CCR2 IT enable

The above is just an idea how to get the interrupt thingy working. Provided high level API usage to make the code readable.

Hope this helps.
Thanks.
Satish Nair

3 Likes

I seem to be having some issues when enabling timer-based interrupts. I have been looking at your example, and the tutorial http://visualgdb.com/tutorials/arm/stm32/timers/, and I can’t seem to get the interrupt handler working.

After initializing the timer, I have the following in loop():

if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    if (state == 1){
        digitalWrite(LED, HIGH);
        state = 0;
    }
    else{
        digitalWrite(LED, LOW);
        state = 1;
    }
}

This works, and will periodically toggle the LED. However, if I move the same code to TIM2_IRQHandlerand enable the interrupt with:

NVIC_InitTypeDef nvicStructure;
nvicStructure.NVIC_IRQChannel = TIM2_IRQn;
nvicStructure.NVIC_IRQChannelPreemptionPriority = 0;
nvicStructure.NVIC_IRQChannelSubPriority = 1;
nvicStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicStructure);

it does not work. I also tried keeping the LED toggle code in loop() as well as in the interrupt handler, and enabling the interrupt. However, as soon as the interrupt is enabled, the LED stops toggling. Are you able to provide any insight to my issues?

UPDATE

Defining the TIM2_IRQHandler with extern "C" fixes the problem. This gives the interrupt handler C linkage so the linker can properly link the interrupt handler. For example, define:

extern "C" void TIM2_IRQHandler(void){
 if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

NOTE: The online IDE will not compile properly with the extern ‘C’ linkage. As of this writing (1/18/2014), you must compile locally in order for this to work.

So you are using TIM2. Did you change the default priority grouping since I see this in your code.
nvicStructure.NVIC_IRQChannelSubPriority = 1;

Do you have this statement in your code: TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

Try adding this declaration in your stm32_it.h file:
void TIM2_IRQHandler(void);

Let me know if the above works?

I was experimenting with changing the priority grouping, although I changed it back to 0 so we are both on the same page. And yes, I have TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); in my code.

I just tried adding void TIM2_IRQHandler(void); to stm32_it.h, but unfortunately it did not seem to fix the issue.

It seems like it could be an issue with enabling the timer interrupt. Just for testing purposes, I defined TIM2_IRQHandler() as an empty function in my code and enabled the timer interrupt with:

NVIC_InitTypeDef nvicStructure;
nvicStructure.NVIC_IRQChannel = TIM2_IRQn;
nvicStructure.NVIC_IRQChannelPreemptionPriority = 0;
nvicStructure.NVIC_IRQChannelSubPriority = 0;
nvicStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicStructure);

In my main loop, I have the LED toggling code mentioned in my previous post. When I compile and load this onto the device, the LED will not toggle. However, if I comment out nvicStructure.NVIC_IRQChannelCmd = ENABLE; in my interrupt enable function, the LED will toggle (obviously the interrupt will not be properly initialized).

Can you send me the complete code related to TIM2 usage?
Thanks

Ok. Resolved.

This was a linking issue.
Since application is .cpp file, you require extern “C” before your Interrupt handler definition.

So your TIM2 interrupt handler code should look like:

extern “C” void TIM2_IRQHandler(void)
{

}

1 Like

Would love to see the full source for this. I want to control my own TLC5940. Plus I want a better understanding of SPI to understand other SPI chips.

I’m having the same problem that @mb3 was – The interrupt handler is not binding. I can call it directly from loop, but if I try to do all the NVIC stuff it’ll compile but the timer update event won’t actually trigger the handler.

I see that @mb3’s solution was to compile with extern "C", but he notes that it won’t compile in the IDE at that point. Is there any way, currently, to make this work from the IDE?

Thanks!

Hi @kmarsh

Did you see this link:

And this nice new library from @peekay123

I had not yet seen either of those! Looking now. They seem very promising. Thanks a bundle!

1 Like

@deltahat You talked about using a TLC5940, did you get this working with the core or the photon? I am looking for library to use with a Photon that would control a TLC5940. I have all my code working on an Arduino but want to move into the Photons.