completely redid PWM: Now calculate 'microprograms', i.e. pairs of master
authorsimimeie <simimeie>
Sun, 8 Aug 2010 18:09:40 +0000 (18:09 +0000)
committersimimeie <simimeie>
Sun, 8 Aug 2010 18:09:40 +0000 (18:09 +0000)
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.

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.067583 seconds and 4 git commands to generate.