~toykeeper/flashlight-firmware/fsm

483.12.14 by Selene ToyKeeper
switched the rest of FSM + Anduril to use SPDX license headers
1
// fsm-ramping.c: Ramping functions for SpaghettiMonster.
2
// Copyright (C) 2017-2023 Selene ToyKeeper
3
// SPDX-License-Identifier: GPL-3.0-or-later
205 by Selene Scriven
Added a ramping UI example.
4
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
5
#pragma once
205 by Selene Scriven
Added a ramping UI example.
6
7
#ifdef USE_RAMPING
8
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
9
#ifdef HAS_AUX_LEDS
10
inline void set_level_aux_leds(uint8_t level) {
336 by Selene Scriven
Started adding support for aux LEDs which go out the front instead of lighting up the button.
11
    #ifdef USE_INDICATOR_LED_WHILE_RAMPING
483.1.169 by Selene ToyKeeper
allow supporting single-color and RGB side button in the same build
12
        // use side-facing aux LEDs while main LEDs are on
13
        if (! go_to_standby) {
433.1.1 by Selene Scriven
merged a sanitized copy of the Emisar D4v2 branch; history summarized below:
14
        #ifdef USE_INDICATOR_LED
481.1.4 by Selene Scriven
made lighted button go low/high based on DEFAULT_LEVEL instead of MAX_1x7135,
15
            indicator_led((level > 0) + (level > DEFAULT_LEVEL));
433.1.1 by Selene Scriven
merged a sanitized copy of the Emisar D4v2 branch; history summarized below:
16
        #endif
483.1.169 by Selene ToyKeeper
allow supporting single-color and RGB side button in the same build
17
        #ifdef USE_BUTTON_LED
18
            button_led_set((level > 0) + (level > DEFAULT_LEVEL));
19
        #endif
20
        }
21
    #else  // turn off front-facing aux LEDs while main LEDs are on
433.1.1 by Selene Scriven
merged a sanitized copy of the Emisar D4v2 branch; history summarized below:
22
        #if defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS)
23
        if (! go_to_standby) {
24
            #ifdef USE_INDICATOR_LED
25
                indicator_led(0);
26
            #endif
27
            #ifdef USE_AUX_RGB_LEDS
28
                rgb_led_set(0);
439.1.1 by Selene Scriven
added button LED support to D4v2
29
                #ifdef USE_BUTTON_LED
481.1.4 by Selene Scriven
made lighted button go low/high based on DEFAULT_LEVEL instead of MAX_1x7135,
30
                    button_led_set((level > 0) + (level > DEFAULT_LEVEL));
439.1.1 by Selene Scriven
added button LED support to D4v2
31
                #endif
433.1.1 by Selene Scriven
merged a sanitized copy of the Emisar D4v2 branch; history summarized below:
32
            #endif
33
        }
34
        #endif
303 by Selene Scriven
Moved indicator_led() from Anduril into FSM. Made it auto-set indicator level based on ramp level.
35
    #endif
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
36
}
37
#endif  // ifdef HAS_AUX_LEDS
38
39
40
void set_level(uint8_t level) {
41
    #ifdef USE_JUMP_START
42
    // maybe "jump start" the engine, if it's prone to slow starts
43
    // (pulse the output high for a moment to wake up the power regulator)
44
    // (only do this when starting from off and going to a low level)
45
    // TODO: allow different jump start behavior per channel mode
483.12.71 by Selene ToyKeeper
refactored how channel modes are defined, and converted emisar-2ch build
46
    // FIXME: don't jump-start during factory reset
47
    //        (it seems to cause some eeprom issues on KR4
48
    //         when doing a click with a loose tailcap)
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
49
    if ((! actual_level)
50
            && level
483.12.13 by Selene ToyKeeper
reduced ROM by ~600 bytes by moving all eeprom config values to a "cfg" struct
51
            && (level < JUMP_START_LEVEL)) {
52
        set_level(JUMP_START_LEVEL);
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
53
        delay_4ms(JUMP_START_TIME/4);
54
    }
55
    #endif
56
57
    #ifdef HAS_AUX_LEDS
58
    set_level_aux_leds(level);
59
    #endif
60
61
    // call the relevant hardware-specific set_level_*()
483.12.71 by Selene ToyKeeper
refactored how channel modes are defined, and converted emisar-2ch build
62
    SetLevelFuncPtr set_level_func = channels[CH_MODE].set_level;
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
63
    set_level_func(level);
64
483.12.53 by Selene ToyKeeper
post-off voltage display: use low brightness when torch was at moon level before,
65
    if (actual_level != level) prev_level = actual_level;
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
66
    actual_level = level;
67
68
    #ifdef USE_SET_LEVEL_GRADUALLY
69
    gradual_target = level;
70
    #endif
71
72
    #ifdef USE_DYNAMIC_UNDERCLOCKING
73
    auto_clock_speed();
74
    #endif
75
}
76
77
#ifdef USE_LEGACY_SET_LEVEL
78
// (this is mostly just here for reference, temporarily)
79
// single set of LEDs with 1 to 3 stacked power channels,
80
// like linear, FET+1, and FET+N+1
81
// (default set_level_*() function for most lights)
82
void set_level_legacy(uint8_t level) {
205 by Selene Scriven
Added a ramping UI example.
83
    if (level == 0) {
84
        #if PWM_CHANNELS >= 1
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
85
            PWM1_LVL = 0;
205 by Selene Scriven
Added a ramping UI example.
86
        #endif
87
        #if PWM_CHANNELS >= 2
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
88
            PWM2_LVL = 0;
205 by Selene Scriven
Added a ramping UI example.
89
        #endif
90
        #if PWM_CHANNELS >= 3
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
91
            PWM3_LVL = 0;
483.8.4 by Selene Scriven
got D4Sv2 tint ramping to work, with dynamic PWM (PFM)
92
        #endif
483.11.4 by Selene ToyKeeper
sp10-pro: hard reset phase while turning off, fixes shutoff bug without a delay
93
        #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_OFF)
94
            PWM1_CNT = 0;
95
        #endif
96
        #if defined(PWM2_CNT) && defined(PWM2_PHASE_RESET_OFF)
97
            PWM2_CNT = 0;
98
        #endif
99
        #if defined(PWM3_CNT) && defined(PWM3_PHASE_RESET_OFF)
100
            PWM3_CNT = 0;
101
        #endif
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
102
        #ifdef LED_OFF_DELAY
103
            // for drivers with a slow regulator chip (eg, boost converter),
104
            // delay before turning off to prevent flashes
105
            delay_4ms(LED_OFF_DELAY/4);
483.3.34 by Gabriel Hart
Created LED_DISABLE_DELAY option and enabled it for SP10 Pro to hopefully eliminate flashes when turning off at certain levels
106
        #endif
433.1.6 by Selene Scriven
fsm-ramping: turn opamp chip on and off automatically based on ramp level
107
        // disable the power channel, if relevant
108
        #ifdef LED_ENABLE_PIN
109
        LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
110
        #endif
483.2.2 by Selene Scriven
renamed LED_ENABLE2_* to LED2_ENABLE_*
111
        #ifdef LED2_ENABLE_PIN
112
        LED2_ENABLE_PORT &= ~(1 << LED2_ENABLE_PIN);
433.2.2 by Selene Scriven
added support for boost PMIC enable pin (K1 12V has 2 pins which need to turn on/off for main LED output)
113
        #endif
205 by Selene Scriven
Added a ramping UI example.
114
    } else {
433.1.6 by Selene Scriven
fsm-ramping: turn opamp chip on and off automatically based on ramp level
115
        // enable the power channel, if relevant
116
        #ifdef LED_ENABLE_PIN
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
117
            #ifdef LED_ON_DELAY
483.3.29 by Gabriel Hart
Experimental: add optional delay when using LED_ENABLE_PIN to avoid flashes of light
118
            uint8_t led_enable_port_save = LED_ENABLE_PORT;
119
            #endif
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
120
487.1.3 by Selene Scriven
added support for LED_ENABLE_PIN having a min/max ramp level where it turns on
121
            #ifndef LED_ENABLE_PIN_LEVEL_MIN
122
            LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
123
            #else
124
            // only enable during part of the ramp
125
            if ((level >= LED_ENABLE_PIN_LEVEL_MIN)
126
                    && (level <= LED_ENABLE_PIN_LEVEL_MAX))
127
                LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
128
            else  // disable during other parts of the ramp
129
                LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
130
            #endif
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
131
132
            // for drivers with a slow regulator chip (eg, boost converter),
133
            // delay before lighting up to prevent flashes
134
            #ifdef LED_ON_DELAY
135
            // only delay if the pin status changed
136
            if (LED_ENABLE_PORT != led_enable_port_save)
137
                delay_4ms(LED_ON_DELAY/4);
483.3.29 by Gabriel Hart
Experimental: add optional delay when using LED_ENABLE_PIN to avoid flashes of light
138
            #endif
433.1.6 by Selene Scriven
fsm-ramping: turn opamp chip on and off automatically based on ramp level
139
        #endif
483.2.2 by Selene Scriven
renamed LED_ENABLE2_* to LED2_ENABLE_*
140
        #ifdef LED2_ENABLE_PIN
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
141
            #ifdef LED2_ON_DELAY
483.3.30 by Gabriel Hart
Experimental: add optional delay when using LED_ENABLE_PIN to avoid flashes of light (this time for LED2_ENABLE pin)
142
            uint8_t led2_enable_port_save = LED2_ENABLE_PORT;
143
            #endif
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
144
483.3.30 by Gabriel Hart
Experimental: add optional delay when using LED_ENABLE_PIN to avoid flashes of light (this time for LED2_ENABLE pin)
145
            LED2_ENABLE_PORT |= (1 << LED2_ENABLE_PIN);
483.11.2 by Selene ToyKeeper
moved fuses from cfg to hwdef, renamed LED_EN/DISABLE_DELAY to LED_ON/OFF_DELAY
146
147
            // for drivers with a slow regulator chip (eg, boost converter),
148
            // delay before lighting up to prevent flashes
149
            #ifdef LED2_ON_DELAY
150
            // only delay if the pin status changed
151
            if (LED2_ENABLE_PORT != led2_enable_port_save)
152
                delay_4ms(LED2_ON_DELAY/4);
483.3.30 by Gabriel Hart
Experimental: add optional delay when using LED_ENABLE_PIN to avoid flashes of light (this time for LED2_ENABLE pin)
153
            #endif
433.2.2 by Selene Scriven
added support for boost PMIC enable pin (K1 12V has 2 pins which need to turn on/off for main LED output)
154
        #endif
433.1.6 by Selene Scriven
fsm-ramping: turn opamp chip on and off automatically based on ramp level
155
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
156
        // PWM array index = level - 1
487.1.3 by Selene Scriven
added support for LED_ENABLE_PIN having a min/max ramp level where it turns on
157
        level --;
158
205 by Selene Scriven
Added a ramping UI example.
159
        #if PWM_CHANNELS >= 1
433.1.3 by Selene Scriven
fsm-ramping: made it possible to change bit depth of PWM values (8-bit or 16-bit)
160
        PWM1_LVL = PWM_GET(pwm1_levels, level);
205 by Selene Scriven
Added a ramping UI example.
161
        #endif
162
        #if PWM_CHANNELS >= 2
433.1.3 by Selene Scriven
fsm-ramping: made it possible to change bit depth of PWM values (8-bit or 16-bit)
163
        PWM2_LVL = PWM_GET(pwm2_levels, level);
205 by Selene Scriven
Added a ramping UI example.
164
        #endif
165
        #if PWM_CHANNELS >= 3
433.1.3 by Selene Scriven
fsm-ramping: made it possible to change bit depth of PWM values (8-bit or 16-bit)
166
        PWM3_LVL = PWM_GET(pwm3_levels, level);
205 by Selene Scriven
Added a ramping UI example.
167
        #endif
366.1.1 by Selene Scriven
Added tint ramping. Not tested yet. Also added BLF Lantern build target.
168
483.1.121 by Selene Scriven
got PFM / dynamic PWM actually working on Noctigon KR4
169
        #ifdef USE_DYN_PWM
483.1.126 by Selene Scriven
made dynamic PWM phase-correct; replaced brute-force phase reset to avoid flickering
170
            uint16_t top = PWM_GET(pwm_tops, level);
483.11.4 by Selene ToyKeeper
sp10-pro: hard reset phase while turning off, fixes shutoff bug without a delay
171
            #if defined(PWM1_CNT) && defined(PWM1_PHASE_SYNC)
483.1.126 by Selene Scriven
made dynamic PWM phase-correct; replaced brute-force phase reset to avoid flickering
172
            // wait to ensure compare match won't be missed
173
            // (causes visible flickering when missed, because the counter
174
            //  goes all the way to 65535 before returning)
175
            // (see attiny1634 reference manual page 103 for a warning about
176
            //  the timing of changing the TOP value (section 12.8.4))
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
177
            // (but don't wait when turning on from zero, because
178
            //  it'll reset the phase below anyway)
483.10.5 by Selene ToyKeeper
reduced SP10 downward ramp flicker even more
179
            // to be safe, allow at least 32 cycles to update TOP
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
180
            while(actual_level && (PWM1_CNT > (top - 32))) {}
483.1.126 by Selene Scriven
made dynamic PWM phase-correct; replaced brute-force phase reset to avoid flickering
181
            #endif
483.1.121 by Selene Scriven
got PFM / dynamic PWM actually working on Noctigon KR4
182
            // pulse frequency modulation, a.k.a. dynamic PWM
483.1.126 by Selene Scriven
made dynamic PWM phase-correct; replaced brute-force phase reset to avoid flickering
183
            PWM1_TOP = top;
184
        #endif  // ifdef USE_DYN_PWM
483.11.4 by Selene ToyKeeper
sp10-pro: hard reset phase while turning off, fixes shutoff bug without a delay
185
        #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON)
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
186
            // force reset phase when turning on from zero
187
            // (because otherwise the initial response is inconsistent)
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
188
            if (! actual_level) {
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
189
                PWM1_CNT = 0;
483.11.4 by Selene ToyKeeper
sp10-pro: hard reset phase while turning off, fixes shutoff bug without a delay
190
                #if defined(PWM2_CNT) && defined(PWM2_PHASE_RESET_ON)
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
191
                PWM2_CNT = 0;
192
                #endif
483.11.4 by Selene ToyKeeper
sp10-pro: hard reset phase while turning off, fixes shutoff bug without a delay
193
                #if defined(PWM3_CNT) && defined(PWM3_PHASE_RESET_ON)
483.1.131 by Selene Scriven
force reset PWM phase when turning on from zero
194
                PWM3_CNT = 0;
195
                #endif
196
            }
197
        #endif
205 by Selene Scriven
Added a ramping UI example.
198
    }
284 by Selene Scriven
Added dynamic underclocking to FSM, instead of doing it manually in Anduril.
199
    #ifdef USE_DYNAMIC_UNDERCLOCKING
200
    auto_clock_speed();
201
    #endif
205 by Selene Scriven
Added a ramping UI example.
202
}
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
203
#endif
204
205 by Selene Scriven
Added a ramping UI example.
205
260 by Selene Scriven
Made thermal regulation adjust smoothly (1 PWM step at a time) to make adjustments less noticeable.
206
#ifdef USE_SET_LEVEL_GRADUALLY
207
inline void set_level_gradually(uint8_t lvl) {
208
    gradual_target = lvl;
209
}
210
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
211
262 by Selene Scriven
Rewrote gradual_tick(). Is smaller, better, and safer now.
212
// call this every frame or every few frames to change brightness very smoothly
260 by Selene Scriven
Made thermal regulation adjust smoothly (1 PWM step at a time) to make adjustments less noticeable.
213
void gradual_tick() {
483.12.19 by Selene ToyKeeper
updated D4Sv2-tintramp -> Emisar 2-channel build target ...
214
    uint8_t gt = gradual_target;
215
    if (gt < actual_level) gt = actual_level - 1;
216
    else if (gt > actual_level) gt = actual_level + 1;
217
    gt --;
218
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
219
    // call the relevant hardware-specific function
483.12.71 by Selene ToyKeeper
refactored how channel modes are defined, and converted emisar-2ch build
220
    GradualTickFuncPtr gradual_tick_func = channels[CH_MODE].gradual_tick;
483.12.19 by Selene ToyKeeper
updated D4Sv2-tintramp -> Emisar 2-channel build target ...
221
    bool done = gradual_tick_func(gt);
222
223
    if (done) {
224
        uint8_t orig = gradual_target;
225
        set_level(gt + 1);
226
        gradual_target = orig;
227
    }
483.12.2 by Selene ToyKeeper
refactor progress checkpoint ... got Sofirn LT1S Pro and Emisar D4v2 working
228
}
483.2.7 by Selene Scriven
made gradual_tick() work on K9.3 (via override), fixed strobe config,
229
#endif  // ifdef USE_SET_LEVEL_GRADUALLY
260 by Selene Scriven
Made thermal regulation adjust smoothly (1 PWM step at a time) to make adjustments less noticeable.
230
483.8.4 by Selene Scriven
got D4Sv2 tint ramping to work, with dynamic PWM (PFM)
231
205 by Selene Scriven
Added a ramping UI example.
232
#endif  // ifdef USE_RAMPING
483.12.14 by Selene ToyKeeper
switched the rest of FSM + Anduril to use SPDX license headers
233