]> www.fi.muni.cz Git - bike-lights.git/blob - firmware/adc.c
Merge branch 'master' of ssh://anxur.fi.muni.cz/~kas/html/git/bike-lights
[bike-lights.git] / firmware / adc.c
1 #include <avr/io.h>
2 #include <avr/interrupt.h>
3 #include <util/atomic.h>
4
5 #include "lights.h"
6
7 #define AMBIENT_ADC N_PWMLEDS
8 #define BATTERY_ADC (N_PWMLEDS + 1)
9 #define ADC1_GAIN20 (N_PWMLEDS + 2)
10
11 #define NUM_ADCS 6
12 volatile static unsigned char current_adc;
13 static uint16_t adc_sum;
14 static unsigned char sum_shift;
15 static unsigned char adc_vals;
16 #define ADC1_GAIN20_OFFSET_SHIFT        6
17 static uint16_t adc1_gain20_offset;
18 static unsigned char handler_running;
19
20 static void inline setup_mux(unsigned char n)
21 {
22         /* ADC numbering: PWM LEDs first, then ambient light sensor, battery sensor */
23         switch (n) {
24         case 0: // pwmled 1: 1.1V, ADC0,1 (PA0,1), gain 20
25                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX1) | _BV(MUX0);
26                 sum_shift = PWMLED_ADC_SHIFT;
27                 break;
28         case 1: // pwmled 2: 1.1V, ADC2,1 (PA2,1), gain 20
29                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
30                 sum_shift = PWMLED_ADC_SHIFT;
31                 break;
32         case 2: // pwmled 3: 1.1V, ADC4 (PA5), single-ended
33                 ADMUX = _BV(REFS1) | _BV(MUX2);
34                 sum_shift = PWMLED_ADC_SHIFT;
35                 break;
36         case 3: // ambient light: 1.1V, ADC5 (PA6), single-ended
37                 ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX0);
38                 sum_shift = 3; // 3 measurements
39                 break;
40         case 4: // batt voltage: 1.1V, ADC6 (PA7), single-ended
41                 ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX1);
42                 sum_shift = 0; // 1 measurement
43                 break;
44         case 5: // gain stage offset: 1.1V, ADC1,1, gain 20
45                 ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
46                 sum_shift = 0; // 1 measurement
47                 break;
48         }
49
50         adc_sum = 0;
51         adc_vals = 1 << sum_shift;
52 }
53
54 static void start_next_adc()
55 {
56         if (current_adc > 0)
57                 current_adc--;
58         else
59                 // TODO: kick the watchdog here.
60                 current_adc = NUM_ADCS-1;
61
62         // set up mux, start one-shot conversion
63         setup_mux(current_adc);
64         ADCSRA |= _BV(ADSC);
65 }
66
67 /*
68  * Single synchronous ADC conversion.
69  * Has to be called with IRQs disabled (or with the ADC IRQ disabled).
70  */
71 static uint16_t read_adc_sync()
72 {
73         uint16_t rv;
74
75         ADCSRA |= _BV(ADSC); // start the conversion
76
77         // wait for the conversion to finish
78         while((ADCSRA & _BV(ADIF)) == 0)
79                 ;
80
81         rv = ADCW;
82         ADCSRA |= _BV(ADIF); // clear the IRQ flag
83
84         return rv;
85 }
86
87 void init_adc()
88 {
89         unsigned char i;
90         current_adc = NUM_ADCS;
91         handler_running = 0;
92
93         ADCSRA = _BV(ADEN)                      // enable
94                 | _BV(ADPS1) | _BV(ADPS0)       // CLK/8 = 125 kHz
95                 // | _BV(ADPS2)                 // CLK/16 = 62.5 kHz
96                 ;
97         // ADCSRB |= _BV(GSEL); // gain 8 or 32
98
99         // Disable digital input on all bits used by ADC
100         DIDR0 = _BV(ADC0D) | _BV(ADC1D) | _BV(ADC2D)
101                 | _BV(ADC4D) | _BV(ADC5D) | _BV(ADC6D);
102
103         // 1.1V, ADC1,1, gain 20
104         ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
105
106         /* Do first conversion and drop the result */
107         read_adc_sync();
108
109         adc1_gain20_offset = 0;
110
111         for (i = 0; i < (1 << ADC1_GAIN20_OFFSET_SHIFT); i++) {
112                 adc1_gain20_offset += read_adc_sync()
113                         - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
114         }
115
116         ADCSRA |= _BV(ADIE); // enable IRQ
117
118         start_next_adc();
119 }
120
121 void susp_adc()
122 {
123         ADCSRA = 0;
124         DIDR0 = 0;
125 }
126
127 ISR(ADC_vect) { // IRQ handler
128         uint16_t adcval = ADCW;
129
130         if (adc_vals)
131                 // start the next conversion immediately
132                 ADCSRA |= _BV(ADSC);
133
134         if (adc_vals < (1 << sum_shift))
135                  // drop the first conversion, use all others
136                  adc_sum += adcval;
137
138         if (adc_vals) {
139                 adc_vals--;
140                 return;
141         }
142
143         // Now handle the (1 << sum_shift) measurements
144
145         adcval = adc_sum >> sum_shift;
146
147         if (current_adc == ADC1_GAIN20) {
148                 // running average
149                 adc1_gain20_offset += adcval
150                         - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
151         } else if (current_adc == 0 || current_adc == 1) {
152                 uint16_t offset = adc1_gain20_offset
153                         >> (ADC1_GAIN20_OFFSET_SHIFT - sum_shift);
154                 if (adc_sum > offset)
155                         adc_sum -= offset;
156                 else
157                         adc_sum = 0;
158         }
159
160         if (handler_running & (1 << current_adc)) {
161                 log_byte(0xB0 + current_adc);
162
163                 // drop the result, what else to do?
164
165                 start_next_adc();
166         } else {
167                 unsigned char current_adc_copy = current_adc;
168                 uint16_t adc_sum_copy = adc_sum;
169
170                 start_next_adc();
171
172                 handler_running |= (1 << current_adc_copy);
173                 NONATOMIC_BLOCK(NONATOMIC_FORCEOFF) {
174                         if (current_adc_copy < N_PWMLEDS)
175                                 pwmled_adc(current_adc_copy, adc_sum_copy);
176                         if (current_adc_copy == AMBIENT_ADC)
177                                 ambient_adc(adc_sum_copy);
178                         if (current_adc_copy == BATTERY_ADC)
179                                 battery_adc(adc_sum_copy);
180                 }
181                 handler_running &= ~(1 << current_adc_copy);
182         }
183 }
184