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