-/* $Id: ledpwm.c,v 1.1 2010/06/26 19:08:18 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).
*/
#include <avr/io.h>
+#include <avr/interrupt.h>
#include "ledpwm.h"
-/* Select between Phase correct PWM and Fast PWM mode.
- * setting this to 1 selects phase correct mode, everything else is Fast PWM. */
-#define PHASECORRECTPWM 1
/*
- * Our hardware connections are as follows:
- * Red -> PD6 / OC0A
- * Green -> PD5 / OC0B
- * Blue -> PD3 / OC2B
+ * Our hardware connections on the first prototype are as follows:
+ * Red -> PD6 (OC0A)
+ * Green -> PD5 (OC0B)
+ * Blue -> PD3 (OC2B) CHANGED umgeloetet durch Julian jetzt PD7
+ * In the next hardware revision this will be:
+ * Red -> PC3
+ * Green -> PC2
+ * Blue -> PC1
*/
-void ledpwm_init(void)
-{
- /* Set OC2B, OC0A and OC0B to output */
- DDRD |= _BV(PD3) | _BV(PD5) | _BV(PD6);
- /* Set compare output mode for OC2B in timer counter control register: */
-#if (PHASECORRECTPWM == 1)
- /* select phase correct PWM mode (1). WGM2 bit is 0 so we don't need to touch TCCR2B. */
- TCCR2A |= _BV(COM2B1) | _BV(WGM20);
-#else /* not PHASECORRECTPWM */
- /* set output at BOTTOM (=0), clear it on compare match.
- * select fast PWM mode 3. WGM2 bit is 0 so we don't need to touch TCCR2B. */
- TCCR2A |= _BV(COM2B1) | _BV(WGM20) /*| _BV(WGM21)*/;
-#endif /* PHASECORRECTPWM */
- /* select clock source: cpu clock without prescaler. */
- TCCR2B |= _BV(CS20);
- /* Default brightness: PWM 0 */
- OCR2B = 0;
- /* Set compare output mode for OC0A and OC0B in timer counter control register: */
-#if (PHASECORRECTPWM == 1)
- /* select phase correct PWM mode (1). WGM2 bit is 0 so we don't need to touch TCCR0B. */
- TCCR0A |= _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
-#else /* not PHASECORRECTPWM */
- /* set output at BOTTOM (=0), clear it on compare match.
- * select fast PWM mode 3. WGM2 bit is 0 so we don't need to touch TCCR0B. */
- TCCR0A |= _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00) | _BV(WGM01);
-#endif /* PHASECORRECTPWM */
- /* select clock source: i/o clock without prescaler. */
- TCCR0B |= _BV(CS20);
- /* Default brightness: PWM 0 */
- OCR0A = 0;
- OCR0B = 0;
-}
-/* In none-phase-correct PWM mode, This function does some mapping of
- * the values:
- * 0 really turns the LED off.
- * 1 is an PWM value of 0, which still lets the LED glimmer.
- * 2 to 254 are mapped to PWM 3 to 253 (always one less!)
- * 255 is mapped to PWM 255.
- * So there is no way to set PWM 254, but you cannot visually see the
- * difference to 253 or 255 anyways...
- * In phase-correct PWM mode there does not need to be a mapping, 0 means off.
- */
-void ledpwm_setled(uint8_t led, uint8_t val)
-{
-#if (PHASECORRECTPWM == 1)
- switch (led) {
- case LEDPWM_REDLED: OCR0A = val; break;
- case LEDPWM_GREENLED: OCR0B = val; break;
- case LEDPWM_BLUELED: OCR2B = val; break;
- };
-#else /* not PHASECORRECTPWM */
- if (val == 0) {
- switch (led) {
- case LEDPWM_REDLED: DDRD &= (uint8_t)~_BV(PD6);
- break;
- case LEDPWM_GREENLED: DDRD &= (uint8_t)~_BV(PD5);
- break;
- case LEDPWM_BLUELED: DDRD &= (uint8_t)~_BV(PD3);
- break;
- };
+#define LEDPORT PORTD
+#define LEDDDR DDRD
+#define LEDPINR 6
+#define LEDPING 5
+#define LEDPINB 7
+
+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 };
+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 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] > curmin) && (ledpwm_val[i] <= res)) {
+ res = ledpwm_val[i];
+ found = 1;
+ }
+ }
+ if (found) {
+ return res;
} else {
- val -= 1;
- if (val == 254) {
- val = 255;
+ return 0; /* We already had the maximum, no higher values there (EOL) */
+ }
+}
+
+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);
}
- switch (led) {
- case LEDPWM_REDLED: DDRD |= _BV(PD6);
- OCR0A = val;
- break;
- case LEDPWM_GREENLED: DDRD |= _BV(PD5);
- OCR0B = val;
- break;
- case LEDPWM_BLUELED: DDRD |= _BV(PD3);
- OCR2B = val;
- break;
- };
+ 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++;
}
-#endif /* PHASECORRECTPWM */
+ /* 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;
+ }
+ /* Now program the overflow interrupt */
+ if (ledpwm_amprogptr->atcntval > 0) {
+ uint16_t nextval = ledpwm_amprogptr->atcntval;
+ OCR1AH = nextval >> 8;
+ OCR1AL = nextval & 0xff;
+ }
+}
+
+/* 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)
+{
+ LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval;
+ ledpwm_amprogptr++;
+ programnextpwmstep();
+}
+
+void ledpwm_init(void) {
+ /* Set our Port Pins to output */
+ LEDDDR |= _BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB);
+ /* DO NOT initialize TIMER1 frequency and overflow interrupt.
+ * 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();
}