aboutsummaryrefslogtreecommitdiff
path: root/hwdef-Sofirn_LT1S-Pro.c
blob: 61d215753bd0d74a58701eb545465b4668b16368 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// BLF LT1S Pro hwdef functions
// Copyright (C) 2023 Selene ToyKeeper
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once


// single set of LEDs with 1 power channel and dynamic PWM
void set_level_1ch_dyn(uint8_t level) {
    if (level == 0) {
        RED_PWM_LVL  = 0;
        PWM_CNT      = 0;  // reset phase
    } else {
        level --;  // PWM array index = level - 1
        RED_PWM_LVL  = PWM_GET(pwm1_levels, level);
        // pulse frequency modulation, a.k.a. dynamic PWM
        PWM_TOP = PWM_GET(pwm_tops, level);
        // force reset phase when turning on from zero
        // (because otherwise the initial response is inconsistent)
        if (! actual_level) PWM_CNT = 0;
    }
}


// warm + cool blend w/ middle sag correction and dynamic PWM
void set_level_2ch_dyn_blend(uint8_t level) {
    #ifndef TINT_RAMPING_CORRECTION
    #define TINT_RAMPING_CORRECTION 26  // 140% brightness at middle tint
    #endif

    if (level == 0) {
        WARM_PWM_LVL = 0;
        COOL_PWM_LVL = 0;
        PWM_CNT      = 0;  // reset phase
        return;
    }

    level --;  // PWM array index = level - 1
    PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
    uint16_t top = PWM_GET(pwm_tops, level);

    // calculate actual PWM levels based on a single-channel ramp
    // and a global tint value
    uint16_t warm_PWM, cool_PWM;
    uint8_t mytint = channel_mode_args[channel_mode];

    PWM_DATATYPE2 base_PWM = brightness;
    #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
        // middle tints sag, so correct for that effect
        // by adding extra power which peaks at the middle tint
        // (correction is only necessary when PWM is fast)
        if (level > HALFSPEED_LEVEL) {
            base_PWM = brightness
                     + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64)
                        * triangle_wave(mytint) / 255);
        }
        // fade the triangle wave out when above 100% power,
        // so it won't go over 200%
        if (brightness > top) {
            base_PWM -= 2 * (
                             ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
                             * triangle_wave(mytint) / 255
                        );
        }
        // guarantee no more than 200% power
        if (base_PWM > (top << 1)) { base_PWM = top << 1; }
    #endif

    cool_PWM = (((PWM_DATATYPE2)mytint * (PWM_DATATYPE2)base_PWM) + 127) / 255;
    warm_PWM = base_PWM - cool_PWM;
    // when running at > 100% power, spill extra over to other channel
    if (cool_PWM > top) {
        warm_PWM += (cool_PWM - top);
        cool_PWM = top;
    } else if (warm_PWM > top) {
        cool_PWM += (warm_PWM - top);
        warm_PWM = top;
    }

    WARM_PWM_LVL = warm_PWM;
    COOL_PWM_LVL = cool_PWM;
    PWM_TOP = top;
    if (! actual_level) PWM_CNT = 0;  // reset phase
}


// "auto tint" channel mode with dynamic PWM
void set_level_auto_3ch_dyn_blend(uint8_t level) {
    if (level == 0) {
        WARM_PWM_LVL = 0;
        COOL_PWM_LVL = 0;
        RED_PWM_LVL  = 0;
        PWM_CNT      = 0;  // reset phase
        return;
    }

    level --;  // PWM array index = level - 1
    PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level);
    // pulse frequency modulation, a.k.a. dynamic PWM
    uint16_t top = PWM_GET(pwm_tops, level);

    // tint goes from 0 (red) to 127 (warm white) to 255 (cool white)
    uint8_t mytint;
    mytint = 255 * (uint16_t)level / RAMP_SIZE;

    PWM_DATATYPE a, b, c;

    // red is high at 0, low at 255
    a = (((PWM_DATATYPE2)(255 - mytint)
          * (PWM_DATATYPE2)vpwm) + 127) / 255;
    // warm white is low at 0 and 255, high at 127
    b = (((PWM_DATATYPE2)triangle_wave(mytint)
          * (PWM_DATATYPE2)vpwm) + 127) / 255;
    // cool white is low at 0, high at 255
    c = (((PWM_DATATYPE2)mytint
          * (PWM_DATATYPE2)vpwm) + 127) / 255;

    RED_PWM_LVL  = a;
    WARM_PWM_LVL = b;
    COOL_PWM_LVL = c;
    PWM_TOP = top;
    if (! actual_level) PWM_CNT = 0;
}


// "white + red" channel mode
void set_level_red_white_blend(uint8_t level) {
    // set the warm+cool white LEDs first
    channel_mode = CM_WHITE;
    set_level_2ch_dyn_blend(level);
    channel_mode = CM_WHITE_RED;

    // set the red LED as a ratio of the white output level
    if (level == 0) {
        RED_PWM_LVL = 0;
        PWM_CNT     = 0;  // reset phase
        return;
    }

    level --;  // PWM array index = level - 1
    PWM_DATATYPE vpwm = PWM_GET(pwm1_levels, level);

    // 0 = no red
    // 255 = red at 100% of white channel PWM
    uint8_t ratio = channel_mode_args[channel_mode];

    PWM_DATATYPE red_pwm;
    red_pwm = (((PWM_DATATYPE2)ratio * (PWM_DATATYPE2)vpwm) + 127) / 255;

    RED_PWM_LVL = red_pwm;
    if (! actual_level) PWM_CNT = 0;  // reset phase
}