~toykeeper/flashlight-firmware/fsm

« back to all changes in this revision

Viewing changes to ToyKeeper/spaghetti-monster/fsm-ramping.c

  • Committer: Selene ToyKeeper
  • Date: 2023-11-02 17:05:02 UTC
  • mfrom: (483.12.159 multi-channel)
  • mto: This revision was merged to the branch mainline in revision 491.
  • Revision ID: bzr@toykeeper.net-20231102170502-sinkm18qjxlorsxa
merged multi-channel branch with a major refactor and half a year of updates

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * fsm-ramping.c: Ramping functions for SpaghettiMonster.
3
 
 * Handles 1- to 4-channel smooth ramping on a single LED.
4
 
 *
5
 
 * Copyright (C) 2017 Selene Scriven
6
 
 *
7
 
 * This program is free software: you can redistribute it and/or modify
8
 
 * it under the terms of the GNU General Public License as published by
9
 
 * the Free Software Foundation, either version 3 of the License, or
10
 
 * (at your option) any later version.
11
 
 *
12
 
 * This program is distributed in the hope that it will be useful,
13
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
 * GNU General Public License for more details.
16
 
 *
17
 
 * You should have received a copy of the GNU General Public License
18
 
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
 */
 
1
// fsm-ramping.c: Ramping functions for SpaghettiMonster.
 
2
// Copyright (C) 2017-2023 Selene ToyKeeper
 
3
// SPDX-License-Identifier: GPL-3.0-or-later
20
4
 
21
 
#ifndef FSM_RAMPING_C
22
 
#define FSM_RAMPING_C
 
5
#pragma once
23
6
 
24
7
#ifdef USE_RAMPING
25
8
 
26
 
void set_level(uint8_t level) {
27
 
    #ifdef USE_JUMP_START
28
 
    // maybe "jump start" the engine, if it's prone to slow starts
29
 
    // (pulse the output high for a moment to wake up the power regulator)
30
 
    // (only do this when starting from off and going to a low level)
31
 
    if ((! actual_level)
32
 
            && level
33
 
            && (level < jump_start_level)) {
34
 
        set_level(jump_start_level);
35
 
        delay_4ms(JUMP_START_TIME/4);
36
 
    }
37
 
    #endif  // ifdef USE_JUMP_START
38
 
 
39
 
    actual_level = level;
40
 
 
41
 
    #ifdef USE_SET_LEVEL_GRADUALLY
42
 
    gradual_target = level;
43
 
    #endif
44
 
 
 
9
#ifdef HAS_AUX_LEDS
 
10
inline void set_level_aux_leds(uint8_t level) {
45
11
    #ifdef USE_INDICATOR_LED_WHILE_RAMPING
46
12
        // use side-facing aux LEDs while main LEDs are on
47
13
        if (! go_to_standby) {
52
18
            button_led_set((level > 0) + (level > DEFAULT_LEVEL));
53
19
        #endif
54
20
        }
55
 
        //if (level > MAX_1x7135) indicator_led(2);
56
 
        //else if (level > 0) indicator_led(1);
57
 
        //else if (! go_to_standby) indicator_led(0);
58
21
    #else  // turn off front-facing aux LEDs while main LEDs are on
59
22
        #if defined(USE_INDICATOR_LED) || defined(USE_AUX_RGB_LEDS)
60
23
        if (! go_to_standby) {
70
33
        }
71
34
        #endif
72
35
    #endif
73
 
 
74
 
    #ifdef OVERRIDE_SET_LEVEL
75
 
        set_level_override(level);
76
 
    #else
77
 
 
78
 
    #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON) || defined(PWM1_PHASE_SYNC)
79
 
    static uint8_t prev_level = 0;
80
 
    uint8_t api_level = level;
81
 
    #endif
82
 
 
83
 
    //TCCR0A = PHASE;
 
36
}
 
37
#endif  // ifdef HAS_AUX_LEDS
 
38
 
 
39
#ifdef USE_AUX_RGB_LEDS_WHILE_ON
 
40
// TODO: maybe move this stuff into FSM
 
41
#include "anduril/aux-leds.h"  // for rgb_led_voltage_readout()
 
42
inline void set_level_aux_rgb_leds(uint8_t level) {
 
43
    if (! go_to_standby) {
 
44
        if (level > 0) {
 
45
            rgb_led_voltage_readout(level > USE_AUX_RGB_LEDS_WHILE_ON);
 
46
        } else {
 
47
            rgb_led_set(0);
 
48
        }
 
49
        // some drivers can be wired with RGB or single color to button
 
50
        // ... so support both even though only one is connected
 
51
        #ifdef USE_BUTTON_LED
 
52
            button_led_set((level > 0) + (level > DEFAULT_LEVEL));
 
53
        #endif
 
54
    }
 
55
}
 
56
#endif  // ifdef USE_AUX_RGB_LEDS_WHILE_ON
 
57
 
 
58
 
 
59
void set_level(uint8_t level) {
 
60
    #ifdef USE_JUMP_START
 
61
    // maybe "jump start" the engine, if it's prone to slow starts
 
62
    // (pulse the output high for a moment to wake up the power regulator)
 
63
    // (only do this when starting from off and going to a low level)
 
64
    // TODO: allow different jump start behavior per channel mode
 
65
    // FIXME: don't jump-start during factory reset
 
66
    //        (it seems to cause some eeprom issues on KR4
 
67
    //         when doing a click with a loose tailcap)
 
68
    if ((! actual_level)
 
69
            && level
 
70
            && (level < JUMP_START_LEVEL)) {
 
71
        set_level(JUMP_START_LEVEL);
 
72
        delay_4ms(JUMP_START_TIME/4);
 
73
    }
 
74
    #endif
 
75
 
 
76
    #ifdef HAS_AUX_LEDS
 
77
    set_level_aux_leds(level);
 
78
    #endif
 
79
 
 
80
    #ifdef USE_AUX_RGB_LEDS_WHILE_ON
 
81
    set_level_aux_rgb_leds(level);
 
82
    #endif
 
83
 
 
84
    if (0 == level) {
 
85
        set_level_zero();
 
86
    } else {
 
87
        // call the relevant hardware-specific set_level_*()
 
88
        SetLevelFuncPtr set_level_func = channels[channel_mode].set_level;
 
89
        set_level_func(level - 1);
 
90
    }
 
91
 
 
92
    if (actual_level != level) prev_level = actual_level;
 
93
    actual_level = level;
 
94
 
 
95
    #ifdef USE_SET_LEVEL_GRADUALLY
 
96
    gradual_target = level;
 
97
    #endif
 
98
 
 
99
    #ifdef USE_DYNAMIC_UNDERCLOCKING
 
100
    auto_clock_speed();
 
101
    #endif
 
102
}
 
103
 
 
104
#ifdef USE_LEGACY_SET_LEVEL
 
105
// (this is mostly just here for reference, temporarily)
 
106
// single set of LEDs with 1 to 3 stacked power channels,
 
107
// like linear, FET+1, and FET+N+1
 
108
// (default set_level_*() function for most lights)
 
109
void set_level_legacy(uint8_t level) {
84
110
    if (level == 0) {
85
111
        #if PWM_CHANNELS >= 1
86
 
        PWM1_LVL = 0;
 
112
            PWM1_LVL = 0;
87
113
        #endif
88
114
        #if PWM_CHANNELS >= 2
89
 
        PWM2_LVL = 0;
 
115
            PWM2_LVL = 0;
90
116
        #endif
91
117
        #if PWM_CHANNELS >= 3
92
 
        PWM3_LVL = 0;
93
 
        #endif
94
 
        #if PWM_CHANNELS >= 4
95
 
        PWM4_LVL = 0;
96
 
        #endif
97
 
        #ifdef USE_TINT_RAMPING
98
 
        TINT1_LVL = 0;
99
 
        TINT2_LVL = 0;
 
118
            PWM3_LVL = 0;
100
119
        #endif
101
120
        #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_OFF)
102
121
            PWM1_CNT = 0;
121
140
        #endif
122
141
    } else {
123
142
        // enable the power channel, if relevant
124
 
        #ifndef USE_TINT_RAMPING  // update_tint handles this better
125
143
        #ifdef LED_ENABLE_PIN
126
144
            #ifdef LED_ON_DELAY
127
145
            uint8_t led_enable_port_save = LED_ENABLE_PORT;
161
179
                delay_4ms(LED2_ON_DELAY/4);
162
180
            #endif
163
181
        #endif
164
 
        #endif  // ifndef USE_TINT_RAMPING
165
182
 
166
183
        // PWM array index = level - 1
167
184
        level --;
175
192
        #if PWM_CHANNELS >= 3
176
193
        PWM3_LVL = PWM_GET(pwm3_levels, level);
177
194
        #endif
178
 
        #if PWM_CHANNELS >= 4
179
 
        PWM4_LVL = PWM_GET(pwm4_levels, level);
180
 
        #endif
181
195
 
182
196
        #ifdef USE_DYN_PWM
183
197
            uint16_t top = PWM_GET(pwm_tops, level);
190
204
            // (but don't wait when turning on from zero, because
191
205
            //  it'll reset the phase below anyway)
192
206
            // to be safe, allow at least 32 cycles to update TOP
193
 
            while(prev_level && (PWM1_CNT > (top - 32))) {}
 
207
            while(actual_level && (PWM1_CNT > (top - 32))) {}
194
208
            #endif
195
209
            // pulse frequency modulation, a.k.a. dynamic PWM
196
210
            PWM1_TOP = top;
197
 
 
198
 
            // repeat for other channels if necessary
199
 
            #ifdef PMW2_TOP
200
 
                #if defined(PWM2_CNT) && defined(PWM2_PHASE_SYNC)
201
 
                while(prev_level && (PWM2_CNT > (top - 32))) {}
202
 
                #endif
203
 
                PWM2_TOP = top;
204
 
            #endif
205
 
            #ifdef PMW3_TOP
206
 
                #if defined(PWM3_CNT) && defined(PWM3_PHASE_SYNC)
207
 
                while(prev_level && (PWM3_CNT > (top - 32))) {}
208
 
                #endif
209
 
                PWM3_TOP = top;
210
 
            #endif
211
211
        #endif  // ifdef USE_DYN_PWM
212
212
        #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON)
213
213
            // force reset phase when turning on from zero
214
214
            // (because otherwise the initial response is inconsistent)
215
 
            if (! prev_level) {
 
215
            if (! actual_level) {
216
216
                PWM1_CNT = 0;
217
217
                #if defined(PWM2_CNT) && defined(PWM2_PHASE_RESET_ON)
218
218
                PWM2_CNT = 0;
223
223
            }
224
224
        #endif
225
225
    }
226
 
    #ifdef USE_TINT_RAMPING
227
 
    update_tint();
228
 
    #endif
229
 
 
230
 
    #if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON) || defined(PWM1_PHASE_SYNC)
231
 
    prev_level = api_level;
232
 
    #endif
233
 
    #endif  // ifdef OVERRIDE_SET_LEVEL
234
226
    #ifdef USE_DYNAMIC_UNDERCLOCKING
235
227
    auto_clock_speed();
236
228
    #endif
237
229
}
 
230
#endif
 
231
 
238
232
 
239
233
#ifdef USE_SET_LEVEL_GRADUALLY
240
234
inline void set_level_gradually(uint8_t lvl) {
241
235
    gradual_target = lvl;
242
236
}
243
237
 
244
 
#ifndef OVERRIDE_GRADUAL_TICK
 
238
 
245
239
// call this every frame or every few frames to change brightness very smoothly
246
240
void gradual_tick() {
247
 
    // go by only one ramp level at a time instead of directly to the target
248
241
    uint8_t gt = gradual_target;
249
242
    if (gt < actual_level) gt = actual_level - 1;
250
243
    else if (gt > actual_level) gt = actual_level + 1;
251
244
 
252
 
    /*
253
 
    #ifdef LED_ENABLE_PIN_LEVEL_MIN
254
 
    // only enable during part of the ramp
255
 
    if ((gt >= LED_ENABLE_PIN_LEVEL_MIN)
256
 
            && (gt <= LED_ENABLE_PIN_LEVEL_MAX))
257
 
        LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
258
 
    else  // disable during other parts of the ramp
259
 
        LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
260
 
    #endif
261
 
    */
262
 
 
263
 
    gt --;  // convert 1-based number to 0-based
264
 
 
265
 
    PWM_DATATYPE target;
266
 
 
267
 
    #if PWM_CHANNELS >= 1
268
 
    target = PWM_GET(pwm1_levels, gt);
269
 
        #if PWM_CHANNELS > 1
270
 
        if ((gt < actual_level)     // special case for FET-only turbo
271
 
                && (PWM1_LVL == 0)  // (bypass adjustment period for first step)
272
 
                && (target == PWM_TOP)) PWM1_LVL = PWM_TOP;
273
 
        else
274
 
        #endif
275
 
    if (PWM1_LVL < target) PWM1_LVL ++;
276
 
    else if (PWM1_LVL > target) PWM1_LVL --;
277
 
    #endif
278
 
    #if PWM_CHANNELS >= 2
279
 
    target = PWM_GET(pwm2_levels, gt);
280
 
        #if PWM_CHANNELS > 2
281
 
        if ((gt < actual_level)     // special case for FET-only turbo
282
 
                && (PWM2_LVL == 0)  // (bypass adjustment period for first step)
283
 
                && (target == PWM_TOP)) PWM2_LVL = PWM_TOP;
284
 
        else
285
 
        #endif
286
 
    if (PWM2_LVL < target) PWM2_LVL ++;
287
 
    else if (PWM2_LVL > target) PWM2_LVL --;
288
 
    #endif
289
 
    #if PWM_CHANNELS >= 3
290
 
    target = PWM_GET(pwm3_levels, gt);
291
 
    if (PWM3_LVL < target) PWM3_LVL ++;
292
 
    else if (PWM3_LVL > target) PWM3_LVL --;
293
 
    #endif
294
 
    #if PWM_CHANNELS >= 4
295
 
    target = PWM_GET(pwm4_levels, gt);
296
 
    if (PWM4_LVL < target) PWM4_LVL ++;
297
 
    else if (PWM4_LVL > target) PWM4_LVL --;
298
 
    #endif
299
 
 
300
 
    // did we go far enough to hit the next defined ramp level?
301
 
    // if so, update the main ramp level tracking var
302
 
    if ((PWM1_LVL == PWM_GET(pwm1_levels, gt))
303
 
        #if PWM_CHANNELS >= 2
304
 
            && (PWM2_LVL == PWM_GET(pwm2_levels, gt))
305
 
        #endif
306
 
        #if PWM_CHANNELS >= 3
307
 
            && (PWM3_LVL == PWM_GET(pwm3_levels, gt))
308
 
        #endif
309
 
        #if PWM_CHANNELS >= 4
310
 
            && (PWM4_LVL == PWM_GET(pwm4_levels, gt))
311
 
        #endif
312
 
        )
313
 
    {
314
 
        //actual_level = gt + 1;
 
245
    // call the relevant hardware-specific function
 
246
    GradualTickFuncPtr gradual_tick_func = channels[channel_mode].gradual_tick;
 
247
    bool done = gradual_tick_func(gt - 1);
 
248
 
 
249
    if (done) {
315
250
        uint8_t orig = gradual_target;
316
 
        set_level(gt + 1);
 
251
        set_level(gt);
317
252
        gradual_target = orig;
318
253
    }
319
 
    // is handled in set_level()
320
 
    //#ifdef USE_TINT_RAMPING
321
 
    //update_tint();
322
 
    //#endif
323
 
    // is handled in set_level()
324
 
    //#ifdef USE_DYNAMIC_UNDERCLOCKING
325
 
    //auto_clock_speed();
326
 
    //#endif
327
254
}
328
 
#endif  // ifdef OVERRIDE_GRADUAL_TICK
329
255
#endif  // ifdef USE_SET_LEVEL_GRADUALLY
330
256
 
331
257
 
332
 
#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
333
 
void update_tint() {
334
 
    #ifndef TINT_RAMPING_CORRECTION
335
 
    #define TINT_RAMPING_CORRECTION 26  // 140% brightness at middle tint
336
 
    #endif
337
 
 
338
 
    // calculate actual PWM levels based on a single-channel ramp
339
 
    // and a global tint value
340
 
    //PWM_DATATYPE brightness = PWM_GET(pwm1_levels, level);
341
 
    uint16_t brightness = PWM1_LVL;
342
 
    uint16_t warm_PWM, cool_PWM;
343
 
    #ifdef USE_DYN_PWM
344
 
        uint16_t top = PWM1_TOP;
345
 
        //uint16_t top = PWM_GET(pwm_tops, actual_level-1);
346
 
    #else
347
 
        const uint16_t top = PWM_TOP;
348
 
    #endif
349
 
 
350
 
    // auto-tint modes
351
 
    uint8_t mytint;
352
 
    uint8_t level = actual_level - 1;
353
 
    #if 1
354
 
    // perceptual by ramp level
355
 
    if (tint == 0) { mytint = 255 * (uint16_t)level / RAMP_SIZE; }
356
 
    else if (tint == 255) { mytint = 255 - (255 * (uint16_t)level / RAMP_SIZE); }
357
 
    #else
358
 
    // linear with power level
359
 
    //if (tint == 0) { mytint = brightness; }
360
 
    //else if (tint == 255) { mytint = 255 - brightness; }
361
 
    #endif
362
 
    // stretch 1-254 to fit 0-255 range (hits every value except 98 and 198)
363
 
    else { mytint = (tint * 100 / 99) - 1; }
364
 
 
365
 
    PWM_DATATYPE2 base_PWM = brightness;
366
 
    #if defined(TINT_RAMPING_CORRECTION) && (TINT_RAMPING_CORRECTION > 0)
367
 
        // middle tints sag, so correct for that effect
368
 
        // by adding extra power which peaks at the middle tint
369
 
        // (correction is only necessary when PWM is fast)
370
 
        if (level > HALFSPEED_LEVEL) {
371
 
            base_PWM = brightness
372
 
                     + ((((PWM_DATATYPE2)brightness) * TINT_RAMPING_CORRECTION / 64) * triangle_wave(mytint) / 255);
373
 
        }
374
 
        // fade the triangle wave out when above 100% power,
375
 
        // so it won't go over 200%
376
 
        if (brightness > top) {
377
 
            base_PWM -= 2 * (
378
 
                             ((brightness - top) * TINT_RAMPING_CORRECTION / 64)
379
 
                             * triangle_wave(mytint) / 255
380
 
                        );
381
 
        }
382
 
        // guarantee no more than 200% power
383
 
        if (base_PWM > (top << 1)) { base_PWM = top << 1; }
384
 
    #endif
385
 
 
386
 
    cool_PWM = (((PWM_DATATYPE2)mytint * (PWM_DATATYPE2)base_PWM) + 127) / 255;
387
 
    warm_PWM = base_PWM - cool_PWM;
388
 
    // when running at > 100% power, spill extra over to other channel
389
 
    if (cool_PWM > top) {
390
 
        warm_PWM += (cool_PWM - top);
391
 
        cool_PWM = top;
392
 
    } else if (warm_PWM > top) {
393
 
        cool_PWM += (warm_PWM - top);
394
 
        warm_PWM = top;
395
 
    }
396
 
 
397
 
    TINT1_LVL = warm_PWM;
398
 
    TINT2_LVL = cool_PWM;
399
 
 
400
 
    // disable the power channel, if relevant
401
 
    #ifdef LED_ENABLE_PIN
402
 
    if (warm_PWM)
403
 
        LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
404
 
    else
405
 
        LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
406
 
    #endif
407
 
    #ifdef LED2_ENABLE_PIN
408
 
    if (cool_PWM)
409
 
        LED2_ENABLE_PORT |= (1 << LED2_ENABLE_PIN);
410
 
    else
411
 
        LED2_ENABLE_PORT &= ~(1 << LED2_ENABLE_PIN);
412
 
    #endif
413
 
}
414
 
#endif  // ifdef USE_TINT_RAMPING
415
 
 
416
 
 
417
258
#endif  // ifdef USE_RAMPING
418
 
#endif
 
259