Jump to content
43oh

Interrupt/Timer-driven function call approach


Recommended Posts

Hi there,

 

I've got something in my mind and need your advice :)

 

Let's imagine we've got 8 LED's and 8 buttons attached to MSP430. (actually, it doesn't matter how many LED's or buttons, or which port they're attached to)

We want a solution where a press of button one causes the first LED to light up for 1 second. After that time it'll switch off automatically.

For now that's nothing special. Now, let's say every LED lights up when the corresponding button has been pressed, but every one of these LEDs switches off

after a different amount of time. Moreover this whole mechanism should be independent of when which button has been pressed. It should be somehow interrupt-driven

and low power (using low power modes between the interrupts). OR: use the spare processing power inbetween status changes for something else.

 

My first thoughts were:

 

1. functions for each LED like "LED1_on();", "LED1_off();" etc.

2. a timer function like "millis();" in Arduino IDE

3. a array or struct that will hold a function pointer and the amount of time (millis) it should take until the function will be executed

4. a function to allocate i.e. "LED1_off();" to this array with the desired amount of time

5. timer-ISR checks every ms if there exists a function in the array that needs to be serviced and if so, executes it

 

my program could look something like this:



if(button1 pressed)
{
    LED1_on();
    AddTimerEvent(LED1_off, 500); // after 500ms it should "fire"
}
// move on doing other stuff


Adding this to a port ISR makes it completely independet of my main program code.

 

 

What do you think about my approach? Any advice? Does it already exist and I missed it?

 

 

Regards,

Leo

 

PS: I have to admit I never used function pointers. I only read "The C programming language" (Kernighan & Richie) some days ago and thought this would be a good match.

Link to post
Share on other sites

Erm, I think I've got it:

 

 

EventTimer.h

/*
 * EventTimer.h
 *
 *  Created on: 07.07.2014
 *      Author: Leo_2
 */

#include <msp430.h>
#include "../types.h"

#ifndef EVENTTIMER_H_
#define EVENTTIMER_H_

#define MAXEVENTS 10

#define EVENTTIMER_TxR 		TBR 	// Timer_B counter
#define EVENTTIMER_CTL		TBCTL	// Timer_B control
#define EVENTTIMER_MC0		MC0		// Mode control 0
#define EVENTTIMER_MC1		MC1		// Mode control 1

int AddTimerEvent(void (*function)(void), unsigned int millis, bool infinite);
void initEventTimer(void);
void startEventTimer(void);
void stopEventTimer(void);
bool getTimerRunning(void);
unsigned long millis(void);
void incMillis(void);
void workEvents(void);
unsigned int deleteEvent(void (*function)(void));

#endif /* EVENTTIMER_H_ */

EventTimer.c

#include "EventTimer.h"

int getFreeArrayIndex(void);
unsigned long milliseconds;					// 2^32 -> max. ~49 Days

// struct that holds events
struct timedEvent{
	void (*function)(void);		// pointer to function to be executed
	unsigned long nextTime;		// time when function will be executed next
	bool infinite;				// will it be executed over and over?
	unsigned int interval;		// time interval between executions
}eventArray[MAXEVENTS];			// array of timedEvent-structs

void initEventTimer(void)
{
	milliseconds = 0;

	for(int i=0; i<MAXEVENTS; i++)
	{
		eventArray[i].function = NULL;
		eventArray[i].infinite = false;
		eventArray[i].nextTime = 0;
		eventArray[i].interval = 0;
	}
}

void incMillis(void)
{
	milliseconds++;
}

void startEventTimer(void)
{
	// reset counter of timer
	EVENTTIMER_TxR = 0;
	// start timer in up mode
	EVENTTIMER_CTL |= (EVENTTIMER_MC0);
}

void stopEventTimer(void)
{
	// switch off timer to conserve power
	EVENTTIMER_CTL &= ~(EVENTTIMER_MC0 | EVENTTIMER_MC1);
}

unsigned long millis(void)
{
	return milliseconds;
}

int getFreeArrayIndex(void)
{
	for(int i=0; i < MAXEVENTS; i++)
	{
		if(eventArray[i].function == NULL)
		{
			return i;
		}
	}
	// we didn't find a free spot
	return -1;
}

void workEvents(void)
{
	for(int i=0; i < MAXEVENTS; i++)
	{
		if(eventArray[i].nextTime == millis())
		{
			eventArray[i].function();
			if(eventArray[i].infinite == true)
			{
				eventArray[i].nextTime = millis() + eventArray[i].interval;
			}
			else
			{
				eventArray[i].function = NULL;
				eventArray[i].nextTime = 0;
			}
		}
	}
}

unsigned int deleteEvent(void (*function)(void))
{
	for(int i=0; i < MAXEVENTS; i++)
	{
		if(eventArray[i].function == function)
		{
			eventArray[i].function = NULL;
			eventArray[i].nextTime = 0;
			return 0;
		}
	}
	return 1;
}

int AddTimerEvent(void (*function)(void), unsigned int mils, bool infinite)
{
	int arrayIndex = getFreeArrayIndex();

	if(arrayIndex < 0)
	{
		return -1;
	}

	eventArray[arrayIndex].function = function;
	eventArray[arrayIndex].interval = mils;
	eventArray[arrayIndex].nextTime = millis() + mils;
	eventArray[arrayIndex].infinite = infinite;

	return 0;
}

bool getTimerRunning(void)
{
	return (EVENTTIMER_CTL & (EVENTTIMER_MC0 | EVENTTIMER_MC1));
}

I configured Timer B in Grace like this:

post-1273-0-51212400-1404740489_thumb.png

 

 

The corresponding ISR looks like this:

/*
 *  ======== Timer_B3 Interrupt Service Routine ======== 
 */
#pragma vector=TIMERB0_VECTOR
__interrupt void TIMERB0_ISR_HOOK(void)
{
    /* USER CODE START (section: TIMERB0_ISR_HOOK) */
	incMillis();
	workEvents();
    /* USER CODE END (section: TIMERB0_ISR_HOOK) */
}

Usage:

int main(void)
{
    Grace_init();                   // Activate Grace-generated configuration

    initEventTimer();
    startEventTimer();
    AddTimerEvent(tuerAuf_toggle,500,true);
    AddTimerEvent(tuerZu_toggle,650,true);
    AddTimerEvent(blinkerLinks_toggle,730,true);
    AddTimerEvent(blinkerRechts_toggle,970,true);
    
    _bis_SR_register(LPM0_bits|GIE);

}

These 4 functions just toggle output pins like so:

 

Don't know how embedding a Youtube video works, here's the link.

 

Regards,

Leo

 

PS: maybe worth moving this into the code vault?

Link to post
Share on other sites

The MSP430 version of Wiring (the Arduino IDE) is called Energia, it's used extensively on this forum. Since you want a millis() function, this might be a very good start for you.

 

calling a millis() ISR every millisecond is the easiest way to do tings like this, but it also means your controller will spend a significant amount of time not in low power. You could set a timer to fire at the time the first LED should go off. Thenlet tha one schedule the next timer, etc.

 

Your solution is overly complex for the scetched scenario; you allow looping in your timers, you allow different time intervals. If you drop these two features you could use a single ringbuffer to add timeouts to, and use the solution I described above without ever having to iterate the whole buffer.

 

In workEvents(), first store the value of millis() in a local variable and use that in your compares. If you do not, the trigger millisecond might have elapsed before you reach it, and thus miss an event.

Link to post
Share on other sites

In workEvents(), first store the value of millis() in a local variable and use that in your compares. If you do not, the trigger millisecond might have elapsed before you reach it, and thus miss an event.

that's a good point, thanks @@roadrunner84

If I change the check to "eventArray[i].nextTime <= millis()" the problem is also gone and saves the 4 bytes for another variable ;-)

 

I just had a look at Energia source code.

Energia spends quite some time to do the millis(); alone:

__attribute__((interrupt(WDT_VECTOR)))
void watchdog_isr (void)
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = wdt_millis;
	unsigned int f = wdt_fract;

	m += sleeping ? SMILLIS_INC:MILLIS_INC;
	f += sleeping ? SFRACT_INC:FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	wdt_fract = f;
	wdt_millis = m;
	wdt_overflow_count++;

        /* Exit from LMP3 on reti (this includes LMP0) */
        __bic_status_register_on_exit(LPM3_bits);
}

calling a millis() ISR every millisecond is the easiest way to do tings like this, but it also means your controller will spend a significant amount of time not in low power. You could set a timer to fire at the time the first LED should go off. Thenlet tha one schedule the next timer, etc.

 

Your solution is overly complex for the scetched scenario; you allow looping in your timers, you allow different time intervals. If you drop these two features you could use a single ringbuffer to add timeouts to, and use the solution I described above without ever having to iterate the whole buffer.

 

I measured my current implementation to be 97,7% of the time in sleep state. Not optimal, but ok. 

Actually, in my real use case low power won't matter that much, rather the ability to do other things in the meantime without having to worry about the timings. Hence, the ability to stop the whole event timer engine and go completely down to LPM4 until a external interrupt from the wireless module fires.

 

 

To sum it all up, I'm aiming on compiling my own library of portable code snippets to get such things done the easier way in future (see my portable ringbuffer approach).

Link to post
Share on other sites

The mechanism you are describing is very much like the central part of resource scheduler for an operating system.

 

Along the lines of what roadrunner84 said - if you keep track of when the next event has to happen you can

spend a lot less time with the interrupt services routine, etc. 

(You are sort of doing a polling scheme with the timer - wake up every so often and see if something needs to be done).

 

Instead you could keep a queue of when the timers expire, ordered by time.

Then each time the timer fires you know there is something to do (call the appropriate routine),

and you know when the next thing to do will be (just grab the next item in the queue).

Since the queue is kept in time order, you know there is nothing to do until that time, so you can sleep or do other processing.

 

Of course this makes the queue more complex (have to be able to insert a new item in a position based on time).

(Also have to handle the case where the new item is sooner than any other item in the queue, so you have to 

reprogram when the next interrupt is to occur.)

But since button presses are probably relatively rare, the overall overhead is reduced compared to having interrupts and

checking to see if there is anything to do.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...