completely redid PWM: Now calculate 'microprograms', i.e. pairs of
[moodlight.git] / ledpwm.c
1 /* $Id: ledpwm.c,v 1.6 2010/08/08 18:09:40 simimeie Exp $
2  * Functions for led brightness control via PWM (pulse width modulation).
3  */
4
5 #include <avr/io.h>
6 #include <avr/interrupt.h>
7 #include "ledpwm.h"
8
9 /*
10  * Our hardware connections on the first prototype are as follows:
11  *  Red ->   PD6 (OC0A)
12  *  Green -> PD5 (OC0B)
13  *  Blue ->  PD3 (OC2B)  CHANGED umgeloetet durch Julian jetzt PD7
14  * In the next hardware revision this will be:
15  *  Red ->   PC3
16  *  Green -> PC2
17  *  Blue ->  PC1
18  */
19
20 #define LEDPORT PORTD
21 #define LEDDDR DDRD
22 #define LEDPINR 6
23 #define LEDPING 5
24 #define LEDPINB 7
25
26 uint8_t ledpwm_re = 0xff, ledpwm_gr = 0xff, ledpwm_bl = 0xff;
27 uint8_t ledpwm_bri = 128;
28 /* Internal */
29 volatile uint16_t ledpwm_val[3] = { 0x8000, 0x8000, 0x8000 };
30 typedef struct {
31   uint16_t atcntval;  /* at this counter value */
32   uint8_t orval;   /* or this value onto the output */
33 } ledpwm_microprog;
34 ledpwm_microprog ledpwm_mprogs[2][10];
35 volatile uint8_t ledpwm_activemprog = 0;
36 volatile ledpwm_microprog * ledpwm_amprogptr = &ledpwm_mprogs[0][0];
37
38
39 /* To do a 16-bit write, the high byte must be written before the low byte.
40  * For a 16-bit read, the low byte must be read before the high byte */
41
42 static inline uint16_t getnextminimum(uint16_t curmin) {
43   uint16_t res = 0xffff;
44   uint8_t i; uint8_t found = 0;
45   for (i = 0; i < 3; i++) {
46     if ((ledpwm_val[i] > curmin) && (ledpwm_val[i] <= res)) {
47       res = ledpwm_val[i];
48       found = 1;
49     }
50   }
51   if (found) {
52     return res;
53   } else {
54     return 0; /* We already had the maximum, no higher values there (EOL) */
55   }
56 }
57
58 static void ledpwm_recalculateprogram(void) {
59   uint8_t nextprog = !ledpwm_activemprog;
60   uint8_t i = 0;
61   uint16_t nextminimum = 0;
62   uint8_t orval;
63   
64   do {
65     orval = 0;
66     if (ledpwm_val[LEDPWM_REDLED] > nextminimum) {
67       orval |= _BV(LEDPINR);
68     }
69     if (ledpwm_val[LEDPWM_GREENLED] > nextminimum) {
70       orval |= _BV(LEDPING);
71     }
72     if (ledpwm_val[LEDPWM_BLUELED] > nextminimum) {
73       orval |= _BV(LEDPINB);
74     }
75     ledpwm_mprogs[nextprog][i].atcntval = nextminimum;
76     ledpwm_mprogs[nextprog][i].orval = orval;
77     /* now calculate nextminimum */
78     nextminimum = getnextminimum(nextminimum);
79     i++;
80   } while (nextminimum > 0);
81   while (i < 10) { /* Fill the rest with dummys that cause no harm */
82     ledpwm_mprogs[nextprog][i].atcntval = 0;
83     ledpwm_mprogs[nextprog][i].orval = 0;
84     i++;
85   }
86   /* activate the freshly calculated microprogram */
87   ledpwm_activemprog = nextprog;
88   return;
89 }
90
91 static inline void programnextpwmstep(void) {
92   uint16_t curcnt;
93   curcnt = TCNT1L;
94   curcnt |= (uint16_t)TCNT1H << 8;
95   while (((curcnt + 40) > ledpwm_amprogptr->atcntval) && (ledpwm_amprogptr->atcntval > 0)) {
96     /* Something to do in the next few CPU cycles, so wait for it! */
97     if (curcnt >= ledpwm_amprogptr->atcntval) { /* Execute! */
98       LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval;
99       ledpwm_amprogptr++;
100     }
101     curcnt = TCNT1L;
102     curcnt |= (uint16_t)TCNT1H << 8;
103   }
104   /* Now program the overflow interrupt */
105   if (ledpwm_amprogptr->atcntval > 0) {
106     uint16_t nextval = ledpwm_amprogptr->atcntval;
107     OCR1AH = nextval >> 8;
108     OCR1AL = nextval & 0xff;
109   }
110 }
111
112 /* This gets called from timers.c which holds the 'main' (interrupt)
113  * handler for the timer1 overflow. */
114 void ledpwm_TIMER1OVF_hook(void) {
115   /* (Re-)Start microprogram */
116   ledpwm_amprogptr = &ledpwm_mprogs[ledpwm_activemprog][0];
117   LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval;
118   ledpwm_amprogptr++;
119   programnextpwmstep();
120 }
121
122 /* Called on compare match */
123 ISR(TIMER1_COMPA_vect)
124 {
125   LEDPORT = (LEDPORT & (uint8_t)~(_BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB))) | ledpwm_amprogptr->orval;
126   ledpwm_amprogptr++;
127   programnextpwmstep();
128 }
129
130 void ledpwm_init(void) {
131   /* Set our Port Pins to output */
132   LEDDDR |= _BV(LEDPINR) | _BV(LEDPING) | _BV(LEDPINB);
133   /* DO NOT initialize TIMER1 frequency and overflow interrupt.
134    * timers.c already does that. */
135   /* Enable TIMER1 output compare interrupt A */
136   TIMSK1 |= _BV(OCIE1A);
137   ledpwm_recalculateprogram();
138 }
139
140 void ledpwm_setled(uint8_t led, uint16_t val) {
141   if (led > 2) { return; /* ignore invalid values */ }
142   ledpwm_val[led] = val;
143   ledpwm_recalculateprogram();
144 }
145
146 void ledpwm_set(uint8_t red, uint8_t green, uint8_t blue, uint8_t br) {
147   ledpwm_val[LEDPWM_REDLED]   = red * br;
148   ledpwm_val[LEDPWM_GREENLED] = green * br;
149   ledpwm_val[LEDPWM_BLUELED]  = blue * br;
150   ledpwm_recalculateprogram();
151 }
This page took 0.039614 seconds and 3 git commands to generate.