From: simimeie Date: Sun, 8 Aug 2010 18:09:40 +0000 (+0000) Subject: completely redid PWM: Now calculate 'microprograms', i.e. pairs of X-Git-Url: http://git.rrze.uni-erlangen.de/gitweb/?a=commitdiff_plain;p=moodlight.git completely redid PWM: Now calculate 'microprograms', i.e. pairs of at-this-count set-this-bitmask. This makes the actual IRQs do significantly less work. We will now stay in the interrupt handler if less than 30 CPU cycles remain until the next change, and do active waiting. --- diff --git a/ledpwm.c b/ledpwm.c index dc8b7bd..30472f4 100644 --- a/ledpwm.c +++ b/ledpwm.c @@ -1,4 +1,4 @@ -/* $Id: ledpwm.c,v 1.5 2010/08/08 17:06:11 simimeie Exp $ +/* $Id: ledpwm.c,v 1.6 2010/08/08 18:09:40 simimeie Exp $ * Functions for led brightness control via PWM (pulse width modulation). */ @@ -27,65 +27,104 @@ uint8_t ledpwm_re = 0xff, ledpwm_gr = 0xff, ledpwm_bl = 0xff; uint8_t ledpwm_bri = 128; /* Internal */ volatile uint16_t ledpwm_val[3] = { 0x8000, 0x8000, 0x8000 }; -volatile uint16_t ledpwm_curoreg = 0; +typedef struct { + uint16_t atcntval; /* at this counter value */ + uint8_t orval; /* or this value onto the output */ +} ledpwm_microprog; +ledpwm_microprog ledpwm_mprogs[2][10]; +volatile uint8_t ledpwm_activemprog = 0; +volatile ledpwm_microprog * ledpwm_amprogptr = &ledpwm_mprogs[0][0]; + /* To do a 16-bit write, the high byte must be written before the low byte. * For a 16-bit read, the low byte must be read before the high byte */ -static void ledpwm_programnextstep(void) { - uint16_t nextval = 0xffff; - uint16_t tmp1 = ledpwm_curoreg; /* Copy so the compiler can place it into - * a register despite the volatile. */ - uint8_t i; + +static inline uint16_t getnextminimum(uint16_t curmin) { + uint16_t res = 0xffff; + uint8_t i; uint8_t found = 0; for (i = 0; i < 3; i++) { - if ((ledpwm_val[i] > tmp1) && (ledpwm_val[i] <= nextval)) { - nextval = ledpwm_val[i]; + if ((ledpwm_val[i] > curmin) && (ledpwm_val[i] <= res)) { + res = ledpwm_val[i]; + found = 1; } } - /* tmp1 now reused */ - tmp1 = TCNT1L; - tmp1 |= (uint16_t)TCNT1H << 8; - if ((tmp1 + 30) > nextval) { /* 30 cycles is more or less a guess. - * This needs to be set to the number of cycles the code takes from - * here until the iret. */ - nextval = tmp1 + 30; + if (found) { + return res; + } else { + return 0; /* We already had the maximum, no higher values there (EOL) */ } - OCR1AH = nextval >> 8; - OCR1AL = nextval & 0xff; - ledpwm_curoreg = nextval; } -/* This gets called from timers.c which holds the 'main' (interrupt) - * handler for the timer1 overflow. */ -void ledpwm_TIMER1OVF_hook(void) { - /* Turn on all LEDs */ - if (ledpwm_val[LEDPWM_REDLED] > 0) { - LEDPORT |= _BV(LEDPINR); +static void ledpwm_recalculateprogram(void) { + uint8_t nextprog = !ledpwm_activemprog; + uint8_t i = 0; + uint16_t nextminimum = 0; + uint8_t orval; + + do { + orval = 0; + if (ledpwm_val[LEDPWM_REDLED] > nextminimum) { + orval |= _BV(LEDPINR); + } + if (ledpwm_val[LEDPWM_GREENLED] > nextminimum) { + orval |= _BV(LEDPING); + } + if (ledpwm_val[LEDPWM_BLUELED] > nextminimum) { + orval |= _BV(LEDPINB); + } + ledpwm_mprogs[nextprog][i].atcntval = nextminimum; + ledpwm_mprogs[nextprog][i].orval = orval; + /* now calculate nextminimum */ + nextminimum = getnextminimum(nextminimum); + i++; + } while (nextminimum > 0); + while (i < 10) { /* Fill the rest with dummys that cause no harm */ + ledpwm_mprogs[nextprog][i].atcntval = 0; + ledpwm_mprogs[nextprog][i].orval = 0; + i++; } - if (ledpwm_val[LEDPWM_GREENLED] > 0) { - LEDPORT |= _BV(LEDPING); + /* activate the freshly calculated microprogram */ + ledpwm_activemprog = nextprog; + return; +} + +static inline void programnextpwmstep(void) { + uint16_t curcnt; + curcnt = TCNT1L; + curcnt |= (uint16_t)TCNT1H << 8; + while (((curcnt + 40) > ledpwm_amprogptr->atcntval) && (ledpwm_amprogptr->atcntval > 0)) { + /* Something to do in the next few CPU cycles, so wait for it! */ + if (curcnt >= ledpwm_amprogptr->atcntval) { /* Execute! */ + LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval; + ledpwm_amprogptr++; + } + curcnt = TCNT1L; + curcnt |= (uint16_t)TCNT1H << 8; } - if (ledpwm_val[LEDPWM_BLUELED] > 0) { - LEDPORT |= _BV(LEDPINB); + /* Now program the overflow interrupt */ + if (ledpwm_amprogptr->atcntval > 0) { + uint16_t nextval = ledpwm_amprogptr->atcntval; + OCR1AH = nextval >> 8; + OCR1AL = nextval & 0xff; } - ledpwm_curoreg = 0; - ledpwm_programnextstep(); +} + +/* This gets called from timers.c which holds the 'main' (interrupt) + * handler for the timer1 overflow. */ +void ledpwm_TIMER1OVF_hook(void) { + /* (Re-)Start microprogram */ + ledpwm_amprogptr = &ledpwm_mprogs[ledpwm_activemprog][0]; + LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval; + ledpwm_amprogptr++; + programnextpwmstep(); } /* Called on compare match */ ISR(TIMER1_COMPA_vect) { - uint8_t orval = 0x00; - if (ledpwm_val[LEDPWM_REDLED] > ledpwm_curoreg) { - orval |= _BV(LEDPINR); - } - if (ledpwm_val[LEDPWM_GREENLED] > ledpwm_curoreg) { - orval |= _BV(LEDPING); - } - if (ledpwm_val[LEDPWM_BLUELED] > ledpwm_curoreg) { - orval |= _BV(LEDPINB); - } - LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | orval; - ledpwm_programnextstep(); + LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval; + ledpwm_amprogptr++; + programnextpwmstep(); } void ledpwm_init(void) { @@ -95,15 +134,18 @@ void ledpwm_init(void) { * timers.c already does that. */ /* Enable TIMER1 output compare interrupt A */ TIMSK1 |= _BV(OCIE1A); + ledpwm_recalculateprogram(); } void ledpwm_setled(uint8_t led, uint16_t val) { if (led > 2) { return; /* ignore invalid values */ } ledpwm_val[led] = val; + ledpwm_recalculateprogram(); } void ledpwm_set(uint8_t red, uint8_t green, uint8_t blue, uint8_t br) { ledpwm_val[LEDPWM_REDLED] = red * br; ledpwm_val[LEDPWM_GREENLED] = green * br; ledpwm_val[LEDPWM_BLUELED] = blue * br; + ledpwm_recalculateprogram(); }