From 1fdec464891262b06d19f53d1f152a560effa576 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 13 Oct 2023 14:29:57 -0600 Subject: rewrote emisar-d4k-3ch to use delta-sigma modulation (PWM + DSM), which gives much better resolution, especially for the 8-bit channel. Also... - set_channel_mode() aborts when going from/to the same channel, to avoid unnecessary flicker - hsv2rgb() uses 16-bit R/G/B and V now - changed default channel to All - reduced default channel modes to just A, B, C, and All - smooth ramp floor defaults to 1/150 - raised level when aux LEDs turn on high during use (for better compatibility with red main LEDs) --- hwdef-emisar-d4k-3ch.c | 279 +++++++++++++++++++++++-------------------------- 1 file changed, 131 insertions(+), 148 deletions(-) (limited to 'hwdef-emisar-d4k-3ch.c') diff --git a/hwdef-emisar-d4k-3ch.c b/hwdef-emisar-d4k-3ch.c index 8c46003..3fed41a 100644 --- a/hwdef-emisar-d4k-3ch.c +++ b/hwdef-emisar-d4k-3ch.c @@ -81,133 +81,138 @@ void set_level_zero() { MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); LED3_ENABLE_PORT &= ~(1 << LED3_ENABLE_PIN ); LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN ); + main2_dsm_lvl = 0; + led3_dsm_lvl = 0; + led4_dsm_lvl = 0; MAIN2_PWM_LVL = 0; LED3_PWM_LVL = 0; LED4_PWM_LVL = 0; PWM_CNT = 0; - PWM_TOP = PWM_TOP_INIT; + //PWM_TOP = PWM_TOP_INIT; +} + +// wrap setting the dsm vars, to get a faster response +// (just setting *_dsm_lvl doesn't work well for strobes) +void set_hw_levels(PWM_DATATYPE main2, // brightness, 0 to DSM_TOP + PWM_DATATYPE led3, + PWM_DATATYPE led4, + bool main2_en, // enable even at PWM=0? + bool led3_en, + bool led4_en + ) { + + // enable/disable LED power channels + if (main2 | main2_en) + MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); + else MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); + + if (led3 | led3_en ) + LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN); + else LED3_ENABLE_PORT &= ~(1 << LED3_ENABLE_PIN); + + if (led4 | led4_en ) + LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN); + else LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN); + + // set delta-sigma soft levels + main2_dsm_lvl = main2; + led3_dsm_lvl = led3; + led4_dsm_lvl = led4; + // set hardware PWM levels and init dsm loop + MAIN2_PWM_LVL = main2_pwm = main2 >> 7; + LED3_PWM_LVL = led3_pwm = led3 >> 7; + LED4_PWM_LVL = led4_pwm = led4 >> 7; + // force phase reset + PWM_CNT = PWM_CNT2 = 0; +} + +// delta-sigma modulation of PWM outputs +// happens on each Timer0 overflow (every 512 cpu clock cycles) +// uses 8-bit pwm w/ 7-bit dsm (0b 0PPP PPPP PDDD DDDD) +ISR(TIMER0_OVF_vect) { + // set new hardware values first, + // for best timing (reduce effect of interrupt jitter) + MAIN2_PWM_LVL = main2_pwm; + LED3_PWM_LVL = led3_pwm; + LED4_PWM_LVL = led4_pwm; + + // calculate next values, now that timing matters less + + // accumulate error + main2_dsm += (main2_dsm_lvl & 0x007f); + // next PWM = base PWM value + carry bit + main2_pwm = (main2_dsm_lvl >> 7) + (main2_dsm > 0x7f); + // clear carry bit + main2_dsm &= 0x7f; + + // repeat for other channels + + led3_dsm += (led3_dsm_lvl & 0x007f); + led3_pwm = (led3_dsm_lvl >> 7) + (led3_dsm > 0x7f); + led3_dsm &= 0x7f; + + led4_dsm += (led4_dsm_lvl & 0x007f); + led4_pwm = (led4_dsm_lvl >> 7) + (led4_dsm > 0x7f); + led4_dsm &= 0x7f; } // LEDs 1+2 are 8-bit // this 8-bit channel may be LEDs 1+2 or LED 4, depending on wiring void set_level_main2(uint8_t level) { - LED3_ENABLE_PORT &= ~(1 << LED3_ENABLE_PIN ); // turn off unused LEDs - LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN ); // turn off unused LEDs - - MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); - MAIN2_PWM_LVL = PWM_GET8(pwm1_levels, level); + set_hw_levels(PWM_GET(pwm1_levels, level), 0, 0, + 1, 0, 0); } // LED 3 is 16-bit void set_level_led3(uint8_t level) { - MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); // turn off unused LEDs - LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN ); // turn off unused LEDs - - LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN); - LED3_PWM_LVL = PWM_GET16(pwm2_levels, level); - uint16_t top = PWM_GET16(pwm_tops, level); - while(actual_level && (PWM_CNT > (top - 32))) {} - PWM_TOP = top; - if (! actual_level) PWM_CNT = 0; + set_hw_levels(0, PWM_GET(pwm1_levels, level), 0, + 0, 1, 0); } // this 16-bit channel may be LED 4 or LEDs 1+2, depending on wiring void set_level_led4(uint8_t level) { - MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); // turn off unused LEDs - LED3_ENABLE_PORT &= ~(1 << LED3_ENABLE_PIN ); // turn off unused LEDs - - // gotta turn on the opamp before light can come out - LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN); - LED4_PWM_LVL = PWM_GET16(pwm2_levels, level); - // pulse frequency modulation, a.k.a. dynamic PWM - uint16_t top = PWM_GET16(pwm_tops, level); - // wait to sync the counter and avoid flashes - while(actual_level && (PWM_CNT > (top - 32))) {} - PWM_TOP = top; - // force reset phase when turning on from zero - // (because otherwise the initial response is inconsistent) - if (! actual_level) PWM_CNT = 0; + set_hw_levels(0, 0, PWM_GET(pwm1_levels, level), + 0, 0, 1); } void set_level_all(uint8_t level) { - MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); - LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN ); - LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN ); - // FIXME? It might be better to calculate the 8-bit value from the - // 16-bit tables instead of using the 8-bit ramp - // 8bit = max(1, 16bit * 255 / top) - MAIN2_PWM_LVL = PWM_GET8 (pwm1_levels, level); - LED3_PWM_LVL = PWM_GET16(pwm2_levels, level); - LED4_PWM_LVL = PWM_GET16(pwm2_levels, level); - uint16_t top = PWM_GET16(pwm_tops, level); - while(actual_level && (PWM_CNT > (top - 32))) {} - PWM_TOP = top; - if (! actual_level) PWM_CNT = 0; + PWM_DATATYPE pwm = PWM_GET(pwm1_levels, level); + set_hw_levels(pwm, pwm, pwm, 1, 1, 1); } // 8/16/16 wiring, mix 16+16 void set_level_led34a_blend(uint8_t level) { - MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); // turn off unused LEDs - PWM_DATATYPE warm_PWM, cool_PWM; - PWM_DATATYPE brightness = PWM_GET16(pwm2_levels, level); - PWM_DATATYPE top = PWM_GET16(pwm_tops, level); + PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level); uint8_t blend = cfg.channel_mode_args[channel_mode]; - calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend); + calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, DSM_TOP, blend); - if (warm_PWM > 0) LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN); - if (cool_PWM > 0) LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN); - LED3_PWM_LVL = warm_PWM; - LED4_PWM_LVL = cool_PWM; - while(actual_level && (PWM_CNT > (top - 32))) {} - PWM_TOP = top; - if (! actual_level) PWM_CNT = 0; + set_hw_levels(0, warm_PWM, cool_PWM, + 0, (blend<170), (blend>85)); } // 16/16/8 wiring, mix 16+8 void set_level_led34b_blend(uint8_t level) { - LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN ); // turn off unused LEDs - - const uint16_t top = 2047; - uint16_t warm_PWM, cool_PWM; // 11 bits, 8 bits + PWM_DATATYPE warm_PWM, cool_PWM; + PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level); uint8_t blend = cfg.channel_mode_args[channel_mode]; - uint16_t brightness = PWM_GET8(pwm1_levels, level); - - if (0 == brightness) brightness = 1; - brightness = brightness << 3; - - calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend); - // adjust to halfway between 8-bit steps - warm_PWM -= 4; - if (warm_PWM > top) warm_PWM = 0; + calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, DSM_TOP, blend); - if (cool_PWM > 0) MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); - if (warm_PWM > 0) LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN); - MAIN2_PWM_LVL = (uint8_t)(cool_PWM >> 3); - LED3_PWM_LVL = warm_PWM; - //while(actual_level && (PWM_CNT > (top - 32))) {} - PWM_TOP = top; - if (! actual_level) PWM_CNT = 0; + set_hw_levels(cool_PWM, warm_PWM, 0, + (blend>85), (blend<170), 0); } void set_level_hsv(uint8_t level) { RGB_t color; uint8_t h = cfg.channel_mode_args[channel_mode]; uint8_t s = 255; // TODO: drop saturation at brightest levels - uint8_t v = PWM_GET8(pwm1_levels, level); + PWM_DATATYPE v = PWM_GET(pwm1_levels, level); color = hsv2rgb(h, s, v); - if (color.r > 0) MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); - if (color.g > 0) LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN ); - if (color.b > 0) LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN ); - MAIN2_PWM_LVL = color.r; - LED3_PWM_LVL = color.g; - LED4_PWM_LVL = color.b; - //while(actual_level && (PWM_CNT > (255 - 32))) {} - PWM_TOP = 255; - if (! actual_level) PWM_CNT = 0; + set_hw_levels(color.r, color.g, color.b, + 0, 0, 0); } // calculate a 3-channel "auto tint" blend @@ -216,15 +221,12 @@ void set_level_hsv(uint8_t level) { // level : ramp level to convert into 3 channel levels // (assumes ramp table is "pwm1_levels") void calc_auto_3ch_blend( - uint16_t *a, // red - uint16_t *b, // warm - uint8_t *c, // cool + PWM_DATATYPE *a, // red + PWM_DATATYPE *b, // warm + PWM_DATATYPE *c, // cool uint8_t level) { - // led4=red, led3=warm - uint16_t vpwm = PWM_GET16(pwm2_levels, level); - // main2=white, 8-bit - uint8_t vpwm8 = PWM_GET8 (pwm1_levels, level); + PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level); // tint goes from 0 (red) to 127 (warm white) to 255 (cool white) uint8_t mytint; @@ -244,106 +246,88 @@ void calc_auto_3ch_blend( *b = (((PWM_DATATYPE2)triangle_wave(mytint) * (PWM_DATATYPE2)vpwm) ) / 255; // cool white is low at 0, high at 255 (linear) - *c = (uint8_t)( - (((PWM_DATATYPE2)rising - * (PWM_DATATYPE2)vpwm8) + 127) / 255 - ); + *c = (((PWM_DATATYPE2)rising + * (PWM_DATATYPE2)vpwm) + 127) / 255; } // 3-channel "auto tint" channel mode void set_level_auto3(uint8_t level) { - uint16_t a, b; - uint8_t c; + PWM_DATATYPE a, b, c; calc_auto_3ch_blend(&a, &b, &c, level); - // pulse frequency modulation, a.k.a. dynamic PWM - uint16_t top = PWM_GET(pwm_tops, level); + set_hw_levels(c, b, a, + 0, 0, (0 == level)); +} - if ((a > 0) || (0 == level)) // don't turn off at bottom level - LED4_ENABLE_PORT |= (1 << LED4_ENABLE_PIN ); - else LED4_ENABLE_PORT &= ~(1 << LED4_ENABLE_PIN ); - if (b > 0) LED3_ENABLE_PORT |= (1 << LED3_ENABLE_PIN ); - else LED3_ENABLE_PORT &= ~(1 << LED3_ENABLE_PIN ); - if (c > 0) MAIN2_ENABLE_PORT |= (1 << MAIN2_ENABLE_PIN); - else MAIN2_ENABLE_PORT &= ~(1 << MAIN2_ENABLE_PIN); +///// "gradual tick" functions for smooth thermal regulation ///// - LED4_PWM_LVL = a; // red - LED3_PWM_LVL = b; // warm - MAIN2_PWM_LVL = c; // cool +bool gradual_adjust(PWM_DATATYPE main2, PWM_DATATYPE led3, PWM_DATATYPE led4) { + // adjust multiple times based on current brightness + // (so it adjusts faster/coarser when bright, slower/finer when dim) - while(actual_level && (PWM_CNT > (255 - 32))) {} - PWM_TOP = top; - if (! actual_level) PWM_CNT = 0; -} + // higher shift = slower/finer adjustments + const uint8_t shift = 9; // ((255 << 7) >> 9) = 63 max + uint8_t steps; -///// "gradual tick" functions for smooth thermal regulation ///// + steps = main2_dsm_lvl >> shift; + for (uint8_t i=0; i<=steps; i++) + GRADUAL_ADJUST_SIMPLE(main2, main2_dsm_lvl); -bool gradual_adjust(uint8_t main2, uint16_t led3, uint16_t led4) { - GRADUAL_ADJUST_SIMPLE(main2, MAIN2_PWM_LVL); - GRADUAL_ADJUST_SIMPLE(led3, LED3_PWM_LVL ); - GRADUAL_ADJUST_SIMPLE(led4, LED4_PWM_LVL ); + steps = led3_dsm_lvl >> shift; + for (uint8_t i=0; i<=steps; i++) + GRADUAL_ADJUST_SIMPLE(led3, led3_dsm_lvl ); - if ((main2 == MAIN2_PWM_LVL) - && (led3 == LED3_PWM_LVL ) - && (led4 == LED4_PWM_LVL )) { + steps = led4_dsm_lvl >> shift; + for (uint8_t i=0; i<=steps; i++) + GRADUAL_ADJUST_SIMPLE(led4, led4_dsm_lvl ); + + if ((main2 == main2_dsm_lvl) + && (led3 == led3_dsm_lvl ) + && (led4 == led4_dsm_lvl )) { return true; // done } return false; // not done yet } bool gradual_tick_main2(uint8_t gt) { - uint8_t main2 = PWM_GET8(pwm1_levels, gt); + PWM_DATATYPE main2 = PWM_GET(pwm1_levels, gt); return gradual_adjust(main2, 0, 0); } bool gradual_tick_led3(uint8_t gt) { - uint16_t led3 = PWM_GET16(pwm2_levels, gt); + PWM_DATATYPE led3 = PWM_GET(pwm1_levels, gt); return gradual_adjust(0, led3, 0); } bool gradual_tick_led4(uint8_t gt) { - uint16_t led4 = PWM_GET16(pwm2_levels, gt); + PWM_DATATYPE led4 = PWM_GET(pwm1_levels, gt); return gradual_adjust(0, 0, led4); } bool gradual_tick_all(uint8_t gt) { - uint8_t main2 = PWM_GET8 (pwm1_levels, gt); - uint16_t led3 = PWM_GET16(pwm2_levels, gt); - uint16_t led4 = PWM_GET16(pwm2_levels, gt); - return gradual_adjust(main2, led3, led4); + PWM_DATATYPE pwm = PWM_GET(pwm1_levels, gt); + return gradual_adjust(pwm, pwm, pwm); } // 8/16/16 wiring, mix 16+16 bool gradual_tick_led34a_blend(uint8_t gt) { PWM_DATATYPE warm_PWM, cool_PWM; - PWM_DATATYPE brightness = PWM_GET16(pwm2_levels, gt); - PWM_DATATYPE top = PWM_GET16(pwm_tops, gt); + PWM_DATATYPE brightness = PWM_GET(pwm1_levels, gt); uint8_t blend = cfg.channel_mode_args[channel_mode]; - calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend); + calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, DSM_TOP, blend); return gradual_adjust(0, warm_PWM, cool_PWM); } // 16/16/8 wiring, mix 16+8 bool gradual_tick_led34b_blend(uint8_t gt) { - const uint16_t top = 2047; - uint16_t warm_PWM, cool_PWM; // 11 bits, 8 bits + PWM_DATATYPE warm_PWM, cool_PWM; + PWM_DATATYPE brightness = PWM_GET(pwm1_levels, gt); uint8_t blend = cfg.channel_mode_args[channel_mode]; - uint16_t brightness = PWM_GET8(pwm1_levels, gt); - - if (0 == brightness) brightness = 1; - brightness = brightness << 3; - - calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, top, blend); - - // adjust to halfway between 8-bit steps - warm_PWM -= 4; - if (warm_PWM > top) warm_PWM = 0; - // convert to 8-bit - cool_PWM = (uint8_t)(cool_PWM >> 3); + calc_2ch_blend(&warm_PWM, &cool_PWM, brightness, DSM_TOP, blend); return gradual_adjust(cool_PWM, warm_PWM, 0); } @@ -353,7 +337,7 @@ bool gradual_tick_hsv(uint8_t gt) { RGB_t color; uint8_t h = cfg.channel_mode_args[channel_mode]; uint8_t s = 255; // TODO: drop saturation at brightest levels - uint8_t v = PWM_GET8(pwm1_levels, gt); + PWM_DATATYPE v = PWM_GET(pwm1_levels, gt); color = hsv2rgb(h, s, v); return gradual_adjust(color.r, color.g, color.b); @@ -361,8 +345,7 @@ bool gradual_tick_hsv(uint8_t gt) { bool gradual_tick_auto3(uint8_t gt) { // figure out what exact PWM levels we're aiming for - uint16_t red, warm; - uint8_t cool; + PWM_DATATYPE red, warm, cool; calc_auto_3ch_blend(&red, &warm, &cool, gt); return gradual_adjust(cool, warm, red); } -- cgit v1.2.3