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