512268ccf03ee3b468ac87c56a05b193b6526382
[bike-lights.git] / firmware / pwm.c
1 #include <avr/io.h>
2 #include <avr/interrupt.h>
3 #include <avr/power.h>
4 #include <util/delay.h>
5 #include <util/atomic.h>
6
7 #include "lights.h"
8
9 #define PWM_STEP_SHIFT 2 /* sub-LSB precision */
10 #define PWM_TOP (((PWM_MAX) + (4 << (PWM_STEP_SHIFT))) >> (PWM_STEP_SHIFT))
11 #if PWM_TOP > 0x3FF
12 #error PWM_TOP too high
13 #endif
14
15 static uint16_t pwm[N_PWMLEDS];
16 static volatile unsigned char step;
17 static unsigned char channels_running, pll_enabled;
18
19 static void enable_pll()
20 {
21         /* Async clock */
22         PLLCSR = _BV(PLLE);
23
24         /* Synchronize to the phase lock */
25         _delay_us(100);
26         while ((PLLCSR & _BV(PLOCK)) == 0)
27                 ;
28         PLLCSR |= _BV(PCKE);
29
30         pll_enabled = 1;
31 }
32
33 void init_pwm()
34 {
35         int i;
36
37         step = 0;
38         channels_running = 0;
39         pll_enabled = 0;
40
41         for (i = 0; i < N_PWMLEDS; i++)
42                 pwm[i] = 0;
43
44         // PWM channel D is inverted, ...
45         TCCR1C = _BV(COM1D1) | _BV(COM1D0) | _BV(PWM1D);
46         // PWM channels A and B are not
47         TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(PWM1A) | _BV(PWM1B);
48         TCCR1D = 0;
49         TCCR1B = _BV(CS10);                     // no clock prescaling
50
51         TC1H = PWM_TOP >> 8;
52         OCR1C = PWM_TOP & 0xFF;                         // TOP value
53
54         TC1H = PWM_TOP >> 8;            // PWM3 is inverted
55         OCR1D = PWM_TOP & 0xFF;
56
57         TC1H = 0x00;
58         OCR1B = OCR1A = 0;              // initial stride is 0
59
60         DDRB  &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 )); // tristate it
61         PORTB &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 )); // set to zero
62 }
63
64 void susp_pwm()
65 {
66         unsigned char i;
67
68         for (i = 0; i < N_PWMLEDS; i++)
69                 pwm[i] = 0;
70
71         DDRB &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 ));
72         TCCR1D = TCCR1C = TCCR1B = TCCR1A = 0;
73         TIMSK = 0;
74         TIFR = 0;
75
76         PLLCSR &= ~(_BV(PLLE) | _BV(PCKE));
77 }
78
79 void pwm_off(unsigned char n)
80 {
81         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
82                 pwm[n] = 0;
83                 channels_running &= ~(1 << n);
84
85                 switch (n) {
86                 case 0: DDRB &= ~_BV(PB1); break;
87                 case 1: DDRB &= ~_BV(PB3); break;
88                 case 2: DDRB &= ~_BV(PB5); break;
89                 }
90         }
91 }
92
93 static void pwm_update_hw(unsigned char n)
94 {
95         unsigned char hi, lo;
96         uint16_t stride = (pwm[n] + step) >> PWM_STEP_SHIFT;
97
98         if (n == 2)
99                 stride = PWM_TOP - stride;
100
101         hi = stride >> 8;
102         lo = stride & 0xFF;
103
104         switch (n) {
105         case 0:
106                 TC1H = hi;
107                 OCR1A = lo;
108                 break;
109         case 1:
110                 TC1H = hi;
111                 OCR1B = lo;
112                 break;
113         case 2:
114                 TC1H = hi;
115                 OCR1D = lo;
116                 break;
117         }
118 }
119
120 void pwm_set(unsigned char n, uint16_t stride)
121 {
122         if (stride > PWM_MAX)
123                 stride = PWM_MAX;
124
125         ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
126                 pwm[n] = stride;
127                 channels_running |= (1 << n);
128
129                 if (!pll_enabled) {
130                         power_timer1_enable();
131                         enable_pll();
132                 }
133
134                 pwm_update_hw(n);
135
136                 switch(n) {
137                 case 0: DDRB |= _BV(PB1); break;
138                 case 1: DDRB |= _BV(PB3); break;
139                 case 2: DDRB |= _BV(PB5); break;
140                 }
141         }
142 }
143
144 void pwm_timer()
145 {
146         unsigned char i;
147
148         if (++step >= (1 << PWM_STEP_SHIFT))
149                 step = 0;
150
151         for (i = 0; i < N_PWMLEDS; i++)
152                 if (pwm[i])
153                         pwm_update_hw(i);
154 }
155
156 void pwm_disable_if_not_needed()
157 {
158         if (channels_running)
159                 return;
160
161         pll_enabled = 0;
162         DDRB &= ~(_BV(PB1) | _BV(PB3) | _BV(PB5));
163         PLLCSR &= ~(_BV(PLLE) | _BV(PCKE));
164
165         power_timer1_disable();
166 }
167