]> www.fi.muni.cz Git - bike-lights.git/blob - firmware/pwmled.c
pwmled.c: use mA-based values instead of PWM value-based
[bike-lights.git] / firmware / pwmled.c
1 #include <avr/io.h>
2
3 #include "lights.h"
4
5 static uint16_t pwm_vals[N_PWMLEDS*N_PWMLED_MODES];
6 static uint16_t pwm_max[N_PWMLEDS] = {
7         PWM_MAX/2,
8         PWM_MAX - (PWM_MAX >> 4), // step-up
9         PWM_MAX/2
10 };
11
12 #define PWMLED2_TESTING_WITH_350MA_LED
13
14 #define SENSE_MOHM      33      /* 0.033 Ohm */
15 #define MA_MOHM_GAIN_TO_ADC(ma, mohm, gain) (\
16         ((unsigned long)(ma))*(mohm) /* voltage at sensing resistor in uV */ \
17         /(1100000UL/gain/1024UL)     /* voltage of ADC reading == 1 */ \
18 )
19 static uint16_t adc_max[N_PWMLEDS] = {
20         MA_MOHM_GAIN_TO_ADC( 400, SENSE_MOHM, 20),
21         MA_MOHM_GAIN_TO_ADC(  30, SENSE_MOHM, 20),
22 #ifdef PWMLED2_TESTING_WITH_350MA_LED
23         MA_MOHM_GAIN_TO_ADC( 400, SENSE_MOHM,  1)
24 #else
25         MA_MOHM_GAIN_TO_ADC(2500, SENSE_MOHM,  1)
26 #endif
27 };
28 static uint16_t adc_vals[N_PWMLEDS*N_PWMLED_MODES] = {
29         /* pwmled0 */
30         MA_MOHM_GAIN_TO_ADC(  20, SENSE_MOHM, 20),
31         MA_MOHM_GAIN_TO_ADC(  50, SENSE_MOHM, 20),
32         MA_MOHM_GAIN_TO_ADC( 100, SENSE_MOHM, 20),
33         MA_MOHM_GAIN_TO_ADC( 350, SENSE_MOHM, 20),
34         /* pwmled1 */
35         MA_MOHM_GAIN_TO_ADC(   5, SENSE_MOHM, 20),
36         MA_MOHM_GAIN_TO_ADC(  12, SENSE_MOHM, 20),
37         MA_MOHM_GAIN_TO_ADC(  16, SENSE_MOHM, 20),
38         MA_MOHM_GAIN_TO_ADC(  20, SENSE_MOHM, 20),
39         /* pwmled2 */
40 #ifdef PWMLED2_TESTING_WITH_350MA_LED
41         MA_MOHM_GAIN_TO_ADC( 100, SENSE_MOHM,  1),
42         MA_MOHM_GAIN_TO_ADC( 140, SENSE_MOHM,  1),
43         MA_MOHM_GAIN_TO_ADC( 250, SENSE_MOHM,  1),
44         MA_MOHM_GAIN_TO_ADC( 350, SENSE_MOHM,  1),
45 #else
46         MA_MOHM_GAIN_TO_ADC( 150, SENSE_MOHM,  1),
47         MA_MOHM_GAIN_TO_ADC( 350, SENSE_MOHM,  1),
48         MA_MOHM_GAIN_TO_ADC( 700, SENSE_MOHM,  1),
49         MA_MOHM_GAIN_TO_ADC(2400, SENSE_MOHM,  1),
50 #endif
51 };
52
53 // TODO: maybe convert this to bitmask to simplify pwmled_needs_adc() ?
54 static unsigned char pwmled_state[N_PWMLEDS];
55 #define ST_DISABLED 0
56 #define ST_PROBING  1
57 #define ST_OFF      2
58 #define ST_ON       3
59
60 static unsigned char pwmled_mode[N_PWMLEDS];
61 static unsigned char pwmled_mode_set[N_PWMLEDS];
62
63 static uint16_t pwm_probes[N_PWMLEDS];
64
65 static void start_probing(unsigned char n)
66 {
67         pwmled_state[n] = ST_PROBING;
68         pwm_set(n, 0);
69         pwm_probes[n] = 0;
70 }
71
72 void pwmled_init()
73 {
74         unsigned char i;
75
76         for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++) {
77                 pwm_vals[i] = 0;
78                 pwmled_mode[i] = 0;
79                 pwmled_mode_set[i] = 0;
80         }
81
82         for (i = 0; i < N_PWMLEDS; i++) {
83                 start_probing(i);
84         }
85 }
86
87 unsigned char pwmled_needs_adc(unsigned char n)
88 {
89         unsigned char st = pwmled_state[n];
90         if (st == ST_PROBING || st == ST_ON)
91                 return 1;
92         else
93                 return 0;
94 }
95
96 unsigned char pwmled_enabled(unsigned char n)
97 {
98         unsigned char st = pwmled_state[n];
99         if (st == ST_OFF || st == ST_ON)
100                 return 1;
101         else
102                 return 0;
103 }
104
105 void pwmled_set_mode(unsigned char n, unsigned char mode)
106 {
107         if (!pwmled_enabled(n))
108                 return;
109
110 #if 0
111         log_byte(0xF8);
112         log_byte(n);
113         log_byte(mode);
114 #endif
115
116         if (mode == 0) {
117                 pwm_off(n);
118                 pwmled_state[n] = ST_OFF;
119                 return;
120         }
121
122         if (mode <= N_PWMLED_MODES) {
123                 uint16_t pwmval;
124                 mode--;
125                 pwmval = pwm_vals[n*N_PWMLED_MODES+mode];
126                 pwm_set(n, pwmval);
127 #if 0
128                 log_byte(pwmval);
129 #endif
130                 pwmled_state[n] = ST_ON;
131                 pwmled_mode[n] = mode;
132                 pwmled_mode_set[n] = 1;
133         }
134 }
135
136 static void inline probing_adc(unsigned char n, uint16_t adcval)
137 {
138         unsigned char need_bigger = 0, i;
139         uint16_t *pwm_p = &pwm_vals[n*N_PWMLED_MODES];
140         uint16_t *adc_p = &adc_vals[n*N_PWMLED_MODES];
141         uint16_t pwm = pwm_probes[n];
142
143 #if 0
144         log_byte(0xF4);
145         log_byte(n);
146         log_word(adcval);
147 #endif
148
149         if (adcval > adc_max[n] // Too high
150                 || (pwm == 0 && adcval > 0) // non-zero voltage with zero PWM
151                 ) {
152                 pwm_off(n);
153                 pwmled_state[n] = ST_DISABLED;
154                 log_byte(0xF0);
155                 log_byte(n);
156                 log_word(adcval);
157                 return;
158         }
159
160         for (i = 0; i < N_PWMLED_MODES; i++, pwm_p++, adc_p++) {
161                 uint16_t adc = *adc_p;
162                 if (adc >= adcval) {
163                         *pwm_p = pwm;
164                         need_bigger = 1;
165                 }
166         }
167
168 #if 0
169         if ((n == 1 && pwm > 0x35) || adcval != 0) {
170                 log_byte(n);
171                 log_byte(0xF3);
172                 log_byte(pwm);
173                 log_word(adcval);
174         }
175 #endif
176
177         if (!need_bigger) { // successfully probed
178                 pwm_off(n);
179                 // pwm_set(n, 0);
180                 pwmled_state[n] = ST_OFF;
181                 log_byte(0xF1);
182                 log_byte(n);
183
184                 return;
185         }
186
187         if (pwm >= pwm_max[n]) { // over the maximum!
188                 pwm_off(n);
189                 pwmled_state[n] = ST_DISABLED;
190                 log_byte(0xF2);
191                 log_byte(n);
192                 // pwm_set(n, 0);
193                 return;
194         }
195
196         // try to increase
197         pwm++;
198         pwm_probes[n] = pwm;
199         pwm_set(n, pwm);
200 }
201
202 // Feedback loop
203 static void inline on_adc(unsigned char n, uint16_t adcval)
204 {
205         unsigned char mode = pwmled_mode[n];
206         uint16_t adc_exp     =  adc_vals[n*N_PWMLED_MODES+mode];
207         uint16_t *pwm_p      = &pwm_vals[n*N_PWMLED_MODES+mode];
208         uint16_t old_pwm     = *pwm_p;
209         uint16_t new_pwm = old_pwm;
210
211 #if 0
212         log_byte(0xF5);
213         log_byte(n);
214         log_word(adcval);
215 #endif
216
217         if (pwmled_mode_set[n]) { // ignore the first reading
218                 pwmled_mode_set[n] = 0;
219                 return;
220         }
221
222         // FIXME: running average?
223         if (2*adcval > 5*adc_exp) { // >2.5x expected, lower significantly
224                 new_pwm = 2*old_pwm/3;
225         } else if (3*adcval > 4*adc_exp) { // >1.33x expected, lower a bit
226                 new_pwm = old_pwm - 1;
227         } else if (4*adcval < 3*adc_exp) { // 0.75x expected, raise a bit
228                 new_pwm = old_pwm + 1;
229         }
230
231         // FIXME: better disconnect detection
232         if (new_pwm > pwm_max[n]) { // FIXME: disconnected?
233                 new_pwm = pwm_max[n];
234         }
235         if (new_pwm < 2) { // short-circuit?
236                 new_pwm = 2;
237         }
238
239         if (new_pwm != old_pwm) {
240                 *pwm_p = new_pwm;
241                 pwm_set(n, new_pwm);
242 #if 0
243                 log_byte(0xF9);
244                 log_byte(new_pwm);
245 #endif
246         }
247 }
248
249 void pwmled_adc(unsigned char n, uint16_t adcval)
250 {
251         unsigned char i, probing;
252         switch (pwmled_state[n]) {
253         case ST_PROBING:
254                 probing_adc(n, adcval);
255
256 #if 1
257                 probing = 0;
258                 for (i = 0; i < N_PWMLEDS; i++)
259                         if (pwmled_state[i] == ST_PROBING)
260                                 probing = 1;
261
262                 if (!probing) {
263                         for (i = 0; i < N_PWMLEDS; i++)
264                                 log_byte(pwmled_state[i]);
265                                 
266                         for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++)
267                                 log_word(pwm_vals[i]);
268                         log_flush();
269                         log_set_state(4);
270                 }
271 #endif
272                 
273                 return;
274         case ST_ON:
275                 on_adc(n, adcval);
276                 return;
277         // WTF am I doing in this function then? Maybe recently switched off?
278         }
279 }