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