]> www.fi.muni.cz Git - bike-lights.git/blob - firmware/pwmled.c
pwmled.c: night rear light is too bright
[bike-lights.git] / firmware / pwmled.c
1 #include <avr/io.h>
2
3 #include "lights.h"
4
5 typedef struct {
6         uint16_t target, pwm;
7         int16_t err_sum;
8         unsigned char mode, state;
9         union {
10                 unsigned char probe_steady, mode_changed;
11         };
12         uint16_t mode_pwm[N_PWMLED_MODES];
13         int16_t err_sums[N_PWMLED_MODES];
14         unsigned char modes_not_yet_stable;
15 } pwmled_t;
16
17 pwmled_t pwmleds[N_PWMLEDS];
18
19 /*
20  * Mode stabilization:
21  * when changing brightness via pwmled_set_brightness() below,
22  * we want to converge to the target value as fast as possible. Also,
23  * we would like to somehow initialize the mode 3, which is used as
24  * "mode 2 + other PWMLED on". So after the brightness is set,
25  * we also set pwmleds[n].modes_not_yet_stable to MODE_STABILIZATION_TIME.
26  * When modes_not_yet_stable is non-zero, we allow only mode 2 to be set
27  * regardless of what is fed to pwmled_set_mode. We will then converge
28  * to the target value of mode 2 only, and after MODE_STABILIZATION_TIME
29  * ADC measurements, we copy the mode_pwm value to all other modes.
30  * Only then it is allowed to set the other modes.
31  */
32 #define MODE_STABILIZATION_TIME (2*16)  // two seconds worth of measurements
33
34 #define PWMLED2_TESTING_WITH_350MA_LED
35
36 #define SENSE_MOHM      33      /* 0.033 Ohm */
37 /*
38  * Voltage in uV at ADC reading == 1 is 1100/gain/1024
39  * ADC module returns sum of 1 << PWMLED_ADC_SHIFT measurements
40  * Voltage in uV measured is current in mA * sense resistance in mOhm
41  */
42 #define MA_GAIN_TO_ADC(ma, gain) ((uint16_t) \
43         ((uint32_t)(ma) \
44         * (SENSE_MOHM) \
45         * (1 << (PWMLED_ADC_SHIFT)) \
46         * 1024 \
47         / (1100000/(gain))))
48
49 static uint16_t adc_max[N_PWMLEDS] = {
50 #ifdef TESTING_FW
51         MA_GAIN_TO_ADC( 400, 20),
52         MA_GAIN_TO_ADC(  30, 20),
53         MA_GAIN_TO_ADC( 800,  1)
54 #else
55         MA_GAIN_TO_ADC( 900, 20),
56         MA_GAIN_TO_ADC(  30, 20),
57         MA_GAIN_TO_ADC(2500,  1)
58 #endif
59 };
60
61 static uint16_t adc_targets_0[] = {
62         MA_GAIN_TO_ADC(  50, 20),
63         MA_GAIN_TO_ADC(  80, 20),
64         MA_GAIN_TO_ADC( 160, 20),
65         MA_GAIN_TO_ADC( 350, 20),
66 };
67
68 static uint16_t adc_targets_1[] = {
69         MA_GAIN_TO_ADC(   5, 20),
70         MA_GAIN_TO_ADC(  10, 20),
71         MA_GAIN_TO_ADC(  20, 20),
72 };
73
74 static uint16_t adc_targets_2[] = {
75 #ifdef TESTING_FW
76         MA_GAIN_TO_ADC( 120,  1),
77         MA_GAIN_TO_ADC( 160,  1),
78         MA_GAIN_TO_ADC( 240,  1),
79         MA_GAIN_TO_ADC( 320,  1),
80         MA_GAIN_TO_ADC( 460,  1),
81 #else
82         MA_GAIN_TO_ADC( 150,  1),
83         MA_GAIN_TO_ADC( 300,  1),
84         MA_GAIN_TO_ADC( 500,  1),
85         MA_GAIN_TO_ADC( 700,  1),
86         MA_GAIN_TO_ADC(1500,  1),
87 #endif
88 };
89
90 static uint16_t adc_vals[N_PWMLEDS*N_PWMLED_MODES];
91
92 #define ST_DISABLED 0
93 #define ST_OFF      1
94 #define ST_PROBING  2
95 #define ST_ON       3
96 // The above are constructed so that the following work:
97 #define ST_IS_ON(s)     ((s) & 0x02)
98 #define ST_CAN_SET_MODE(s)      ((s) & 0x01)
99
100 void init_pwmled()
101 {
102         unsigned char i, j;
103
104         for (i = 0; i < N_PWMLEDS; i++) {
105                 pwmled_t *led = pwmleds + i;
106                 led->err_sum = 0;
107                 led->target = adc_vals[i*N_PWMLED_MODES];
108                 led->mode = 1;
109                 led->probe_steady = 0;
110                 led->state = ST_OFF;
111                 led->pwm = 1;
112                 pwm_set(i, led->pwm);
113
114                 for (j = 0; j < N_PWMLED_MODES; j++) {
115                         led->mode_pwm[j] = 0;
116                         led->err_sums[j] = 0;
117                 }
118         }
119
120         pwmled_set_brightness(PWMLED_BRIGHTNESS(0, 2, 1, 0, 2));
121 }
122
123 void pwmled_set_mode(unsigned char n, unsigned char mode)
124 {
125         pwmled_t *led = pwmleds + n;
126
127         if (!ST_CAN_SET_MODE(led->state))
128                 return;
129
130         if (led->mode) { // save the previous state
131                 led->mode_pwm[led->mode - 1] = led->pwm;
132                 led->err_sums[led->mode - 1] = led->err_sum;
133         }
134
135         led->mode = mode;
136
137         if (mode > 0 && mode <= N_PWMLED_MODES) {
138                 if (led->modes_not_yet_stable) // only mode 2 when !stable
139                         mode = 2;
140                 led->target = adc_vals[n*N_PWMLED_MODES + mode - 1];
141                 led->state = ST_ON;
142                 led->pwm = led->mode_pwm[mode - 1];
143                 led->err_sum = led->err_sums[mode - 1];
144                 led->mode_changed = 1;
145                 pwm_set(n, led->pwm);
146         } else {
147                 led->state = ST_OFF;
148                 pwm_off(n);
149         }
150 }
151
152 #define CHECK_BRIGHTNESS(var, expr, array) \
153         do { \
154                 (var) = (expr); \
155                 if ((var) >= sizeof(array)/sizeof(array[0])) \
156                         (var) = sizeof(array)/sizeof(array[0]) - 1; \
157         } while (0)
158
159 void pwmled_set_brightness(uint16_t brightness)
160 {
161         unsigned char i;
162
163         CHECK_BRIGHTNESS(i, brightness & 0x7, adc_targets_0);
164         adc_vals[0] = adc_targets_0[i];
165         CHECK_BRIGHTNESS(i, (brightness >> 3) & 0x7, adc_targets_0);
166         if (adc_vals[1] != adc_targets_0[i]) {
167                 adc_vals[1] = adc_targets_0[i];
168                 pwmleds[0].modes_not_yet_stable = MODE_STABILIZATION_TIME;
169         }
170         adc_vals[2] = adc_vals[1];
171
172         CHECK_BRIGHTNESS(i, (brightness >> 6) & 0x7, adc_targets_1);
173         // we use only one mode, so no modes_not_yet_stable handling here
174         adc_vals[3] = adc_targets_1[i];
175         adc_vals[4] = adc_vals[3];
176         adc_vals[5] = adc_vals[3];
177
178         CHECK_BRIGHTNESS(i, (brightness >> 9) & 0x7, adc_targets_2);
179         adc_vals[6] = adc_targets_2[i];
180         CHECK_BRIGHTNESS(i, (brightness >> 12) & 0x7, adc_targets_2);
181         if (adc_vals[7] != adc_targets_2[i]) {
182                 adc_vals[7] = adc_targets_2[i];
183                 pwmleds[2].modes_not_yet_stable = MODE_STABILIZATION_TIME;
184         }
185         adc_vals[8] = adc_vals[7];
186
187         for (i = 0; i < N_PWMLEDS; i++) {
188                 pwmleds[i].err_sum = 0;
189                 pwmled_set_mode(i, pwmleds[i].mode);
190         }
191 }
192
193 #define PWMLED_PROBE_STEADY_COUNT 10
194
195 static inline unsigned char pwmled_probed_ok(unsigned char n, uint16_t old_pwm)
196 {
197         pwmled_t *led = pwmleds + n;
198
199         if (led->pwm == old_pwm) {
200                 if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT)
201                         led->probe_steady++;
202         } else {
203                 led->probe_steady = 0;
204         }
205
206         if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT
207                 && old_pwm <= led->pwm)
208                 return 0;
209
210         // probed OK
211         led->mode_pwm[led->mode - 1] = led->pwm;
212         led->err_sums[led->mode - 1] = 0;
213
214         // next mode to probe?
215         if (led->mode < N_PWMLED_MODES) {
216                 led->probe_steady = 0;
217                 led->err_sum = 0;
218
219                 led->mode++;
220                 led->target = adc_vals[n*N_PWMLED_MODES+led->mode-1];
221
222                 return 0;
223         } else {
224                 unsigned char i;
225
226                 led->state = ST_OFF;
227                 pwm_off(n);
228
229                 log_byte(0xF0);
230                 log_byte(n);
231                 log_word(jiffies);
232
233                 for (i = 0; i < N_PWMLED_MODES; i++)
234                         log_word(led->mode_pwm[i]);
235
236                 log_flush();
237
238                 pattern_reload();
239
240                 return 1;
241         }
242 }
243
244 static inline void pwmled_err(unsigned char n)
245 {
246         pwmleds[n].state = ST_DISABLED;
247         pwm_off(n);
248
249         log_byte(0xF1);
250         log_byte(n);
251         log_word(jiffies);
252         log_flush();
253
254         switch (n) {
255         case 0: err_flags.err_pwmled0 = 1; break;
256         case 1: err_flags.err_pwmled1 = 1; break;
257         case 2: err_flags.err_pwmled2 = 1; break;
258         }
259 }
260
261
262 void pwmled_adc(unsigned char n, uint16_t adcval)
263 {
264         pwmled_t *led = pwmleds + n;
265         uint16_t old_pwm;
266         int32_t sum;
267         unsigned char shift;
268
269         if (!ST_IS_ON(led->state))
270                 return;
271
272         if (led->state == ST_ON && led->mode_changed) {
273                 led->mode_changed--;
274                 return;
275         }
276         // FIXME: test for maximum adcval value (adc_max[n])
277
278         old_pwm = led->pwm;
279
280         // shift = led->state == ST_PROBING ? 3 : 8;
281         shift = 3;
282
283         sum = ((int32_t)led->pwm << shift)
284                 + led->err_sum + led->target - adcval;
285
286         if (sum < 0)
287                 sum = 0;
288
289         led->pwm = sum >> shift;
290         sum -= led->pwm << shift;
291         led->err_sum = sum;
292
293         if (led->pwm >= PWM_MAX
294                 || (n == 1 && led->pwm > PWM_MAX/2 && adcval < 0x08)) {
295                 pwmled_err(n);
296                 return;
297         }
298
299         if (led->state == ST_PROBING)
300                 if (pwmled_probed_ok(n, old_pwm))
301                         return;
302
303         if (led->modes_not_yet_stable) {
304                 if (!--led->modes_not_yet_stable) {
305                         // reached stability, copy mode 2 to mode 3 (-1)
306                         led->mode_pwm[0] = led->pwm;
307                         led->mode_pwm[2] = led->pwm;
308                         led->err_sums[0] = 0;
309                         led->err_sums[2] = 0;
310                 }
311         }
312
313         if (led->pwm == old_pwm)
314                 return;
315
316         pwm_set(n, led->pwm);
317 }
318