]> www.fi.muni.cz Git - bike-lights.git/blob - firmware/adc.c
pwm.c: channels running - visible from the outside
[bike-lights.git] / firmware / adc.c
1 #include <avr/io.h>
2 #include <avr/interrupt.h>
3
4 #include "lights.h"
5
6 // pwmleds are measured continuously (when active)
7 #define AMBIENT_ADC N_PWMLEDS           // measured every jiffy (16 Hz)
8 #define BUTTON_ADC  (N_PWMLEDS + 1)     // measured every jiffy (16 Hz)
9 #define BATTERY_ADC (N_PWMLEDS + 2)     // once per second
10 #define ADC1_GAIN20 (N_PWMLEDS + 3)     // once per second
11 #define ZERO_ADC    (N_PWMLEDS + 4)     // must be last
12
13 #define NUM_ADCS        ZERO_ADC
14
15 struct {
16         unsigned char read_zero_log : 2;
17         unsigned char read_drop_log : 2;
18         unsigned char read_keep_log : 4;
19 } adc_params[NUM_ADCS] = {
20         { 0, 1, PWMLED_ADC_SHIFT },     // pwmled 1
21         { 0, 1, PWMLED_ADC_SHIFT },     // pwmled 2
22         { 0, 1, PWMLED_ADC_SHIFT },     // pwmled 3
23         { 0, 1, AMBIENT_ADC_SHIFT },    // ambient
24         { 0, 1, 0 },                    // buttons
25         { 0, 1, 0 },                    // battery
26         { 0, 1, 0 },                    // gain20
27 };
28
29 volatile static unsigned char current_adc, current_slow_adc;
30 static uint16_t adc_sum, zero_count, drop_count, read_count, n_reads_log;
31 #define ADC1_GAIN20_OFFSET_SHIFT        6
32 static uint16_t adc1_gain20_offset;
33
34
35 static void setup_mux(unsigned char n)
36 {
37         /* ADC numbering: PWM LEDs first, then others, zero at the end */
38         switch (n) {
39         case 0: // pwmled 1: 1.1V, ADC0,1 (PA0,1), gain 20
40                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX1) | _BV(MUX0);
41                 break;
42         case 1: // pwmled 2: 1.1V, ADC2,1 (PA2,1), gain 20
43                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
44                 break;
45         case 2: // pwmled 3: 1.1V, ADC4 (PA5), single-ended
46                 ADMUX = _BV(REFS1) | _BV(MUX2);
47                 break;
48         case AMBIENT_ADC: // ambient light: 1.1V, ADC5 (PA6), single-ended
49                 ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX0);
50                 break;
51         case BUTTON_ADC: // buttons: 1.1V, ADC3, single-ended
52                 PORTA |= _BV(PA3); // +5V to the voltage splitter
53                 ADMUX = _BV(REFS1) | _BV(MUX1) | _BV(MUX0);
54                 break;
55         case BATTERY_ADC: // batt voltage: 1.1V, ADC6 (PA7), single-ended
56                 ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX1);
57                 break;
58         case ADC1_GAIN20: // gain stage offset: 1.1V, ADC1,1, gain 20
59                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
60                 break;
61         case ZERO_ADC: // zero: 1.1V, ADC1 (PA1), single-ended
62                 ADMUX = _BV(REFS1) | _BV(MUX0);
63                 break;
64         }
65 }
66
67 static void start_next_adc()
68 {
69         if (current_adc == 0) {
70                 if (current_slow_adc > N_PWMLEDS) {
71                         // read one of the non-PWMLED ADCs
72                         current_adc = --current_slow_adc;
73                 } else {
74                         // no more non-PWMLEDs to do, start with PWMLEDs
75                         current_adc = N_PWMLEDS-1;
76                 }
77         } else if (current_adc >= N_PWMLEDS) {
78                 // one of the non-PWMLED ADCs just finished, skip to PWMLEDs.
79                 current_adc = N_PWMLEDS-1;
80         } else {
81                 // next PWMLED
82                 current_adc--;
83         }
84
85 #if 0
86         log_byte(0x90 + current_adc); // debug ADC switching
87 #endif
88
89         adc_sum = 0;
90         // we use the last iteration of zero_count to set up the MUX
91         // to its final destination, hence the "1 +" below:
92         if (adc_params[current_adc].read_zero_log)
93                 zero_count = 1 + (1 << (adc_params[current_adc].read_zero_log-1));
94         else
95                 zero_count = 1;
96
97         if (adc_params[current_adc].read_drop_log)
98                 drop_count = 1 << (adc_params[current_adc].read_drop_log - 1);
99         else
100                 drop_count = 0;
101
102         read_count = 1 << adc_params[current_adc].read_keep_log;
103         n_reads_log = adc_params[current_adc].read_keep_log;
104
105         // set up mux, start one-shot conversion
106         if (zero_count > 1)
107                 setup_mux(ZERO_ADC);
108         else
109                 setup_mux(current_adc);
110
111         ADCSRA |= _BV(ADSC);
112 }
113
114 void timer_start_slow_adcs()
115 {
116         if (current_slow_adc > N_PWMLEDS) { // Don't start if in progress
117                 log_byte(0x80 + current_slow_adc);
118         } else {
119                 current_slow_adc = NUM_ADCS;
120                 // TODO: kick the watchdog here
121         }
122 }
123
124 /*
125  * Single synchronous ADC conversion.
126  * Has to be called with IRQs disabled (or with the ADC IRQ disabled).
127  */
128 static uint16_t read_adc_sync()
129 {
130         uint16_t rv;
131
132         ADCSRA |= _BV(ADSC); // start the conversion
133
134         // wait for the conversion to finish
135         while((ADCSRA & _BV(ADIF)) == 0)
136                 ;
137
138         rv = ADCW;
139         ADCSRA |= _BV(ADIF); // clear the IRQ flag
140
141         return rv;
142 }
143
144 void init_adc()
145 {
146         unsigned char i;
147         current_slow_adc = NUM_ADCS;
148         current_adc = 0;
149
150         ADCSRA = _BV(ADEN)                      // enable
151                 | _BV(ADPS1) | _BV(ADPS0)       // CLK/8 = 125 kHz
152                 // | _BV(ADPS2)                 // CLK/16 = 62.5 kHz
153                 ;
154         // ADCSRB |= _BV(GSEL); // gain 8 or 32
155
156         // Disable digital input on all bits used by ADC
157         DIDR0 = _BV(ADC0D) | _BV(ADC1D) | _BV(ADC2D) | _BV(ADC3D)
158                 | _BV(ADC4D) | _BV(ADC5D) | _BV(ADC6D);
159
160         // 1.1V, ADC1,1, gain 20
161         ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
162
163         /* Do first conversion and drop the result */
164         read_adc_sync();
165
166         adc1_gain20_offset = 0;
167
168         for (i = 0; i < (1 << ADC1_GAIN20_OFFSET_SHIFT); i++) {
169                 adc1_gain20_offset += read_adc_sync()
170                         - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
171         }
172
173         ADCSRA |= _BV(ADIE); // enable IRQ
174
175         start_next_adc();
176 }
177
178 void susp_adc()
179 {
180         ADCSRA = 0;
181         DIDR0 = 0;
182 }
183
184 static void adc1_gain20_adc(uint16_t adcsum)
185 {
186         // running average
187         adc1_gain20_offset += adcsum
188                         - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
189 }
190
191 ISR(ADC_vect) { // IRQ handler
192         uint16_t adcval = ADCW;
193
194         if (zero_count) {
195                 if (zero_count > 1) {
196                         ADCSRA |= _BV(ADSC);
197                         zero_count--;
198                         return;
199                 } else {
200                         setup_mux(current_adc);
201                         zero_count = 0;
202                         /* fall through */
203                 }
204         }
205
206         if (drop_count) {
207                 ADCSRA |= _BV(ADSC); // drop this one, start the next
208                 drop_count--;
209                 return;
210         }
211
212         if (read_count) {
213                 ADCSRA |= _BV(ADSC);
214                 adc_sum += adcval;
215                 read_count--;
216                 return;
217         }
218
219         /*
220          * Now we have performed read_count measurements and have them
221          * in adc_sum.
222          */
223
224         // For inputs with gain, subtract the measured gain stage offset
225         if (current_adc < 2) {
226                 uint16_t offset = adc1_gain20_offset
227                         >> (ADC1_GAIN20_OFFSET_SHIFT - n_reads_log);
228
229                 if (adc_sum > offset)
230                         adc_sum -= offset;
231                 else
232                         adc_sum = 0;
233         }
234
235         switch (current_adc) {
236         case 0:
237         case 1:
238         case 2:
239                 pwmled_adc(current_adc, adc_sum);
240                 break;
241         case AMBIENT_ADC:
242                 ambient_adc(adc_sum);
243                 break;
244         case BUTTON_ADC:
245                 button_adc(adc_sum);
246                 break;
247         case BATTERY_ADC:
248                 battery_adc(adc_sum);
249                 break;
250         case ADC1_GAIN20:
251                 adc1_gain20_adc(adcval);
252                 break;
253         }
254
255         start_next_adc();
256 }
257