]> www.fi.muni.cz Git - bike-lights.git/blob - firmware/pwmled.c
pwmled: regulation using sum of differences
[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 static int16_t differences[N_PWMLEDS];
65
66 static void start_probing(unsigned char n)
67 {
68         pwmled_state[n] = ST_PROBING;
69         pwm_set(n, 0);
70         pwm_probes[n] = 0;
71 }
72
73 void pwmled_init()
74 {
75         unsigned char i;
76
77         for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++) {
78                 pwm_vals[i] = 0;
79                 pwmled_mode[i] = 0;
80                 pwmled_mode_set[i] = 0;
81         }
82
83         for (i = 0; i < N_PWMLEDS; i++) {
84                 differences[i] = 0;
85                 start_probing(i);
86         }
87 }
88
89 unsigned char pwmled_needs_adc(unsigned char n)
90 {
91         unsigned char st = pwmled_state[n];
92         if (st == ST_PROBING || st == ST_ON)
93                 return 1;
94         else
95                 return 0;
96 }
97
98 unsigned char pwmled_enabled(unsigned char n)
99 {
100         unsigned char st = pwmled_state[n];
101         if (st == ST_OFF || st == ST_ON)
102                 return 1;
103         else
104                 return 0;
105 }
106
107 void pwmled_set_mode(unsigned char n, unsigned char mode)
108 {
109         if (!pwmled_enabled(n))
110                 return;
111
112 #if 0
113         log_byte(0xF8);
114         log_byte(n);
115         log_byte(mode);
116 #endif
117         if (mode > 0 && mode <= N_PWMLED_MODES) {
118                 uint16_t pwmval;
119                 mode--;
120                 pwmval = pwm_vals[n*N_PWMLED_MODES+mode];
121                 pwm_set(n, pwmval);
122 #if 0
123                 log_byte(pwmval);
124 #endif
125                 pwmled_state[n] = ST_ON;
126                 pwmled_mode[n] = mode;
127                 pwmled_mode_set[n] = 1;
128                 differences[n] = 0;
129         } else {
130                 pwm_off(n);
131                 pwmled_state[n] = ST_OFF;
132         }
133 }
134
135 static void inline probing_adc(unsigned char n, uint16_t adcval)
136 {
137         unsigned char need_bigger = 0, i;
138         uint16_t *pwm_p = &pwm_vals[n*N_PWMLED_MODES];
139         uint16_t *adc_p = &adc_vals[n*N_PWMLED_MODES];
140         uint16_t pwm = pwm_probes[n];
141
142 #if 0
143         log_byte(0xF4);
144         log_byte(n);
145         log_word(adcval);
146 #endif
147
148         if (adcval > adc_max[n] // Too high
149                 || (pwm == 0 && adcval > 0) // non-zero voltage with zero PWM
150                 ) {
151                 pwm_off(n);
152                 pwmled_state[n] = ST_DISABLED;
153                 log_byte(0xF0);
154                 log_byte(n);
155                 log_word(adcval);
156                 return;
157         }
158
159         for (i = 0; i < N_PWMLED_MODES; i++, pwm_p++, adc_p++) {
160                 uint16_t adc = *adc_p;
161                 if (adc >= adcval) {
162                         *pwm_p = pwm;
163                         need_bigger = 1;
164                 }
165         }
166
167 #if 0
168         if ((n == 1 && pwm > 0x35) || adcval != 0) {
169                 log_byte(n);
170                 log_byte(0xF3);
171                 log_byte(pwm);
172                 log_word(adcval);
173         }
174 #endif
175
176         if (!need_bigger) { // successfully probed
177                 pwm_off(n);
178                 // pwm_set(n, 0);
179                 pwmled_state[n] = ST_OFF;
180                 log_byte(0xF1);
181                 log_byte(n);
182
183                 return;
184         }
185
186         if (pwm >= pwm_max[n]) { // over the maximum!
187                 pwm_off(n);
188                 pwmled_state[n] = ST_DISABLED;
189                 log_byte(0xF2);
190                 log_byte(n);
191                 // pwm_set(n, 0);
192                 return;
193         }
194
195         // try to increase
196         pwm++;
197         pwm_probes[n] = pwm;
198         pwm_set(n, pwm);
199 }
200
201 // Feedback loop
202 static void inline on_adc(unsigned char n, uint16_t adcval)
203 {
204         unsigned char mode = pwmled_mode[n];
205         uint16_t adc_exp   =  adc_vals[n*N_PWMLED_MODES+mode];
206         uint16_t *pwm_p    = &pwm_vals[n*N_PWMLED_MODES+mode];
207         int16_t old_pwm    = *pwm_p;
208         int16_t new_pwm    = old_pwm;
209
210 #if 0
211         log_byte(0xF5);
212         log_byte(n);
213         log_word(adcval);
214 #endif
215
216
217         if (pwmled_mode_set[n]) { // ignore the first reading
218                 pwmled_mode_set[n] = 0;
219                 return;
220         }
221
222         differences[n] += adcval;
223         differences[n] -= adc_exp;
224
225         if (differences[n] > 16)
226                 new_pwm -= 2;
227         else if (differences[n] > 4)
228                 new_pwm--;
229         else if (differences[n] < -16)
230                 new_pwm += 2;
231         else if (differences[n] < -4)
232                 new_pwm++;
233         // new_pwm -= differences[n] >> 3;
234
235         if (new_pwm == old_pwm)
236                 return;
237
238         differences[n] = 0;
239
240         if (new_pwm > (int16_t)pwm_max[n]) {
241                 // FIXME: disconnected?
242                 new_pwm = pwm_max[n];
243         }
244
245         if (new_pwm < 1) {
246                 // FIXME: short-circuit?
247                 new_pwm = 1;
248         }
249
250         *pwm_p = new_pwm;
251         pwm_set(n, new_pwm);
252
253         if (jiffies > 500 && n == 1) {
254                 log_byte(adcval & 0xFF);
255                 log_byte(new_pwm);
256         }
257 }
258
259 void pwmled_adc(unsigned char n, uint16_t adcval)
260 {
261         unsigned char i, probing;
262
263         switch (pwmled_state[n]) {
264         case ST_PROBING:
265                 probing_adc(n, adcval);
266 #if 1
267                 probing = 0;
268                 for (i = 0; i < N_PWMLEDS; i++)
269                         if (pwmled_state[i] == ST_PROBING)
270                                 probing = 1;
271
272                 if (!probing) {
273                         for (i = 0; i < N_PWMLEDS; i++)
274                                 log_byte(pwmled_state[i]);
275                                 
276                         for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++)
277                                 log_word(pwm_vals[i]);
278                         log_flush();
279                         log_set_state(4);
280                 }
281 #endif
282                 
283                 return;
284         case ST_ON:
285                 on_adc(n, adcval);
286                 return;
287         // WTF am I doing in this function then? Maybe recently switched off?
288         }
289 }