leomar01 8 Posted July 7, 2014 Share Posted July 7, 2014 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. Quote Link to post Share on other sites
leomar01 8 Posted July 7, 2014 Author Share Posted July 7, 2014 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: 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? Quote Link to post Share on other sites
roadrunner84 466 Posted July 7, 2014 Share Posted July 7, 2014 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. Quote Link to post Share on other sites
leomar01 8 Posted July 7, 2014 Author Share Posted July 7, 2014 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). Quote Link to post Share on other sites
igor 163 Posted July 7, 2014 Share Posted July 7, 2014 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. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.