| 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 | } |