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