completely redid PWM: Now calculate 'microprograms', i.e. pairs of
[moodlight.git] / ledpwm.c
index dc8b7bddf5e894308dd4c061d72a5139271861d3..30472f4f3800eec7c48f03a575195cb7637448d6 100644 (file)
--- 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();
 }
This page took 0.0686 seconds and 4 git commands to generate.