rgb-led-string: Christmas tree mod after real-world testing
[tinyboard.git] / projects / step-up / pwmled.c
1 #include <avr/io.h>
2
3 #include "lights.h"
4
5 static uint16_t target;
6 static uint16_t pwm_val;
7 static int16_t err_sum;
8 static unsigned char state;
9 unsigned char mode_changed;
10
11 #define SENSE_MOHM      3000    /* 1 Ohm */
12 /*
13  * Voltage in uV at ADC reading == 1 is 1100/gain/1024
14  * ADC module returns sum of 1 << PWMLED_ADC_SHIFT measurements
15  * Voltage in uV measured is current in mA * sense resistance in mOhm
16  */
17 #define MA_TO_ADC(ma) ((uint16_t) \
18         ((uint32_t)(ma) \
19         * (SENSE_MOHM) \
20         * (1 << (PWMLED_ADC_SHIFT)) \
21         * 1024 \
22         / 1100000))
23
24 static uint16_t adc_max = MA_TO_ADC(30);
25
26 static uint16_t targets[N_PWMLED_MODES] = {
27         MA_TO_ADC( 2),
28         MA_TO_ADC( 6),
29         MA_TO_ADC(12),
30         MA_TO_ADC(20),
31 };
32
33 #define ST_DISABLED 0
34 #define ST_OFF      1
35 #define ST_PROBING  2
36 #define ST_ON       3
37 // The above are constructed so that the following work:
38 #define ST_IS_ON(s)     ((s) & 0x02)
39 #define ST_CAN_SET_MODE(s)      ((s) & 0x01)
40
41 void init_pwmled()
42 {
43         pwm_val = 0;
44         err_sum = 0;
45         target = targets[0];
46         state = ST_OFF;
47 }
48
49 void pwmled_set_target(unsigned char mode)
50 {
51         target = targets[mode];
52         mode_changed = 1;
53 }
54
55 void pwmled_on_off(unsigned char mode)
56 {
57         if (!ST_CAN_SET_MODE(state))
58                 return;
59
60         if (mode) {
61                 state = ST_ON;
62                 mode_changed = 1;
63                 need_pwmled_adc = 1;
64                 pwm_set(pwm_val);
65         } else {
66                 state = ST_OFF;
67                 need_pwmled_adc = 0;
68                 pwm_off();
69         }
70 }
71
72 static inline void pwmled_err()
73 {
74         state = ST_DISABLED;
75         pwm_off();
76
77         log_byte(0xF1);
78         log_flush();
79
80         set_error(ERR_PWMLED);
81 }
82
83
84 void pwmled_adc(uint16_t adcval)
85 {
86         int32_t sum;
87         unsigned char shift;
88
89         if (!ST_IS_ON(state))
90                 return;
91
92         // skip the first reading after mode change
93         if (state == ST_ON && mode_changed) {
94                 mode_changed--;
95                 return;
96         }
97
98         if (adcval > adc_max) {
99                 pwmled_err();
100                 return;
101         }
102
103         shift = 5;
104
105         sum = ((int32_t)pwm_val << shift)
106                 + err_sum + target - adcval;
107
108         if (sum < 0)
109                 sum = 0;
110
111         pwm_val = sum >> shift;
112         sum -= pwm_val << shift;
113         err_sum = sum;
114
115         if (pwm_val >= PWM_MAX
116                 || (pwm_val > (2*PWM_MAX/3) && adcval < 0x08)) {
117                 pwmled_err();
118                 need_pwmled_adc = 0;
119                 return;
120         }
121
122         pwm_set(pwm_val);
123 }
124