#include #include "lights.h" typedef struct { uint16_t target, pwm; int16_t err_sum; unsigned char mode, state; union { unsigned char probe_steady, mode_changed; }; uint16_t mode_pwm[N_PWMLED_MODES]; int16_t err_sums[N_PWMLED_MODES]; unsigned char modes_not_yet_stable; } pwmled_t; pwmled_t pwmleds[N_PWMLEDS]; /* * Mode stabilization: * when changing brightness via pwmled_set_brightness() below, * we want to converge to the target value as fast as possible. Also, * we would like to somehow initialize the mode 3, which is used as * "mode 2 + other PWMLED on". So after the brightness is set, * we also set pwmleds[n].modes_not_yet_stable to MODE_STABILIZATION_TIME. * When modes_not_yet_stable is non-zero, we allow only mode 2 to be set * regardless of what is fed to pwmled_set_mode. We will then converge * to the target value of mode 2 only, and after MODE_STABILIZATION_TIME * ADC measurements, we copy the mode_pwm value to all other modes. * Only then it is allowed to set the other modes. */ #define MODE_STABILIZATION_TIME (2*16) // two seconds worth of measurements #define PWMLED2_TESTING_WITH_350MA_LED #define SENSE_MOHM 33 /* 0.033 Ohm */ /* * Voltage in uV at ADC reading == 1 is 1100/gain/1024 * ADC module returns sum of 1 << PWMLED_ADC_SHIFT measurements * Voltage in uV measured is current in mA * sense resistance in mOhm */ #define MA_GAIN_TO_ADC(ma, gain) ((uint16_t) \ ((uint32_t)(ma) \ * (SENSE_MOHM) \ * (1 << (PWMLED_ADC_SHIFT)) \ * 1024 \ / (1100000/(gain)))) static uint16_t adc_max[N_PWMLEDS] = { #ifdef TESTING_FW MA_GAIN_TO_ADC( 400, 20), MA_GAIN_TO_ADC( 30, 20), MA_GAIN_TO_ADC( 800, 1) #else MA_GAIN_TO_ADC( 900, 20), MA_GAIN_TO_ADC( 30, 20), MA_GAIN_TO_ADC(2500, 1) #endif }; static uint16_t adc_targets_0[] = { MA_GAIN_TO_ADC( 50, 20), MA_GAIN_TO_ADC( 80, 20), MA_GAIN_TO_ADC( 160, 20), MA_GAIN_TO_ADC( 350, 20), }; static uint16_t adc_targets_1[] = { MA_GAIN_TO_ADC( 5, 20), MA_GAIN_TO_ADC( 10, 20), MA_GAIN_TO_ADC( 20, 20), }; static uint16_t adc_targets_2[] = { #ifdef TESTING_FW MA_GAIN_TO_ADC( 120, 1), MA_GAIN_TO_ADC( 160, 1), MA_GAIN_TO_ADC( 240, 1), MA_GAIN_TO_ADC( 320, 1), MA_GAIN_TO_ADC( 460, 1), #else MA_GAIN_TO_ADC( 150, 1), MA_GAIN_TO_ADC( 300, 1), MA_GAIN_TO_ADC( 500, 1), MA_GAIN_TO_ADC( 700, 1), MA_GAIN_TO_ADC(1500, 1), MA_GAIN_TO_ADC(2500, 1), #endif }; static uint16_t adc_vals[N_PWMLEDS*N_PWMLED_MODES]; #define ST_DISABLED 0 #define ST_OFF 1 #define ST_PROBING 2 #define ST_ON 3 // The above are constructed so that the following work: #define ST_IS_ON(s) ((s) & 0x02) #define ST_CAN_SET_MODE(s) ((s) & 0x01) void init_pwmled() { unsigned char i, j; for (i = 0; i < N_PWMLEDS; i++) { pwmled_t *led = pwmleds + i; led->err_sum = 0; led->target = adc_vals[i*N_PWMLED_MODES]; led->mode = 1; led->probe_steady = 0; led->state = ST_OFF; led->pwm = 1; pwm_set(i, led->pwm); for (j = 0; j < N_PWMLED_MODES; j++) { led->mode_pwm[j] = 0; led->err_sums[j] = 0; } } pwmled_set_brightness(PWMLED_BRIGHTNESS(0, 2, 1, 0, 2)); } void pwmled_set_mode(unsigned char n, unsigned char mode) { pwmled_t *led = pwmleds + n; if (!ST_CAN_SET_MODE(led->state)) return; if (led->mode) { // save the previous state led->mode_pwm[led->mode - 1] = led->pwm; led->err_sums[led->mode - 1] = led->err_sum; } led->mode = mode; if (mode > 0 && mode <= N_PWMLED_MODES) { if (led->modes_not_yet_stable) // only mode 2 when !stable mode = 2; led->target = adc_vals[n*N_PWMLED_MODES + mode - 1]; led->state = ST_ON; led->pwm = led->mode_pwm[mode - 1]; led->err_sum = led->err_sums[mode - 1]; led->mode_changed = 1; pwm_set(n, led->pwm); } else { led->state = ST_OFF; pwm_off(n); } } #define CHECK_BRIGHTNESS(var, expr, array) \ do { \ (var) = (expr); \ if ((var) >= sizeof(array)/sizeof(array[0])) \ (var) = sizeof(array)/sizeof(array[0]) - 1; \ } while (0) void pwmled_set_brightness(uint16_t brightness) { unsigned char i; CHECK_BRIGHTNESS(i, brightness & 0x7, adc_targets_0); adc_vals[0] = adc_targets_0[i]; CHECK_BRIGHTNESS(i, (brightness >> 3) & 0x7, adc_targets_0); if (adc_vals[1] != adc_targets_0[i]) { adc_vals[1] = adc_targets_0[i]; pwmleds[0].modes_not_yet_stable = MODE_STABILIZATION_TIME; } adc_vals[2] = adc_vals[1]; CHECK_BRIGHTNESS(i, (brightness >> 6) & 0x7, adc_targets_1); // we use only one mode, so no modes_not_yet_stable handling here adc_vals[3] = adc_targets_1[i]; adc_vals[4] = adc_vals[3]; adc_vals[5] = adc_vals[3]; CHECK_BRIGHTNESS(i, (brightness >> 9) & 0x7, adc_targets_2); adc_vals[6] = adc_targets_2[i]; CHECK_BRIGHTNESS(i, (brightness >> 12) & 0x7, adc_targets_2); if (adc_vals[7] != adc_targets_2[i]) { adc_vals[7] = adc_targets_2[i]; pwmleds[2].modes_not_yet_stable = MODE_STABILIZATION_TIME; } adc_vals[8] = adc_vals[7]; for (i = 0; i < N_PWMLEDS; i++) { pwmleds[i].err_sum = 0; pwmled_set_mode(i, pwmleds[i].mode); } } #define PWMLED_PROBE_STEADY_COUNT 10 static inline unsigned char pwmled_probed_ok(unsigned char n, uint16_t old_pwm) { pwmled_t *led = pwmleds + n; if (led->pwm == old_pwm) { if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT) led->probe_steady++; } else { led->probe_steady = 0; } if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT && old_pwm <= led->pwm) return 0; // probed OK led->mode_pwm[led->mode - 1] = led->pwm; led->err_sums[led->mode - 1] = 0; // next mode to probe? if (led->mode < N_PWMLED_MODES) { led->probe_steady = 0; led->err_sum = 0; led->mode++; led->target = adc_vals[n*N_PWMLED_MODES+led->mode-1]; return 0; } else { unsigned char i; led->state = ST_OFF; pwm_off(n); log_byte(0xF0); log_byte(n); log_word(jiffies); for (i = 0; i < N_PWMLED_MODES; i++) log_word(led->mode_pwm[i]); log_flush(); pattern_reload(); return 1; } } static inline void pwmled_err(unsigned char n) { pwmleds[n].state = ST_DISABLED; pwm_off(n); log_byte(0xF1); log_byte(n); log_word(jiffies); log_flush(); switch (n) { case 0: err_flags.err_pwmled0 = 1; break; case 1: err_flags.err_pwmled1 = 1; break; case 2: err_flags.err_pwmled2 = 1; break; } } void pwmled_adc(unsigned char n, uint16_t adcval) { pwmled_t *led = pwmleds + n; uint16_t old_pwm; int32_t sum; unsigned char shift; if (!ST_IS_ON(led->state)) return; if (led->state == ST_ON && led->mode_changed) { led->mode_changed--; return; } // FIXME: test for maximum adcval value (adc_max[n]) old_pwm = led->pwm; // shift = led->state == ST_PROBING ? 3 : 8; shift = 3; sum = ((int32_t)led->pwm << shift) + led->err_sum + led->target - adcval; if (sum < 0) sum = 0; led->pwm = sum >> shift; sum -= led->pwm << shift; led->err_sum = sum; if (led->pwm >= PWM_MAX || (n == 1 && led->pwm > PWM_MAX/2 && adcval < 0x08)) { pwmled_err(n); return; } if (led->state == ST_PROBING) if (pwmled_probed_ok(n, old_pwm)) return; if (led->modes_not_yet_stable) { if (!--led->modes_not_yet_stable) { // reached stability, copy mode 2 to mode 3 (-1) led->mode_pwm[0] = led->pwm; led->mode_pwm[2] = led->pwm; led->err_sums[0] = 0; led->err_sums[2] = 0; } } if (led->pwm == old_pwm) return; pwm_set(n, led->pwm); }