2
* fsm-ramping.c: Ramping functions for SpaghettiMonster.
3
* Handles 1- to 4-channel smooth ramping on a single LED.
5
* Copyright (C) 2017 Selene Scriven
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.
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.
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/>.
1
// fsm-ramping.c: Ramping functions for SpaghettiMonster.
2
// Copyright (C) 2017-2023 Selene ToyKeeper
3
// SPDX-License-Identifier: GPL-3.0-or-later
26
void set_level(uint8_t level) {
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)
33
&& (level < jump_start_level)) {
34
set_level(jump_start_level);
35
delay_4ms(JUMP_START_TIME/4);
37
#endif // ifdef USE_JUMP_START
41
#ifdef USE_SET_LEVEL_GRADUALLY
42
gradual_target = level;
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) {
74
#ifdef OVERRIDE_SET_LEVEL
75
set_level_override(level);
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;
37
#endif // ifdef HAS_AUX_LEDS
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) {
45
rgb_led_voltage_readout(level > USE_AUX_RGB_LEDS_WHILE_ON);
49
// some drivers can be wired with RGB or single color to button
50
// ... so support both even though only one is connected
52
button_led_set((level > 0) + (level > DEFAULT_LEVEL));
56
#endif // ifdef USE_AUX_RGB_LEDS_WHILE_ON
59
void set_level(uint8_t level) {
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)
70
&& (level < JUMP_START_LEVEL)) {
71
set_level(JUMP_START_LEVEL);
72
delay_4ms(JUMP_START_TIME/4);
77
set_level_aux_leds(level);
80
#ifdef USE_AUX_RGB_LEDS_WHILE_ON
81
set_level_aux_rgb_leds(level);
87
// call the relevant hardware-specific set_level_*()
88
SetLevelFuncPtr set_level_func = channels[channel_mode].set_level;
89
set_level_func(level - 1);
92
if (actual_level != level) prev_level = actual_level;
95
#ifdef USE_SET_LEVEL_GRADUALLY
96
gradual_target = level;
99
#ifdef USE_DYNAMIC_UNDERCLOCKING
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) {
85
111
#if PWM_CHANNELS >= 1
88
114
#if PWM_CHANNELS >= 2
91
117
#if PWM_CHANNELS >= 3
97
#ifdef USE_TINT_RAMPING
101
120
#if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_OFF)
226
#ifdef USE_TINT_RAMPING
230
#if defined(PWM1_CNT) && defined(PWM1_PHASE_RESET_ON) || defined(PWM1_PHASE_SYNC)
231
prev_level = api_level;
233
#endif // ifdef OVERRIDE_SET_LEVEL
234
226
#ifdef USE_DYNAMIC_UNDERCLOCKING
235
227
auto_clock_speed();
239
233
#ifdef USE_SET_LEVEL_GRADUALLY
240
234
inline void set_level_gradually(uint8_t lvl) {
241
235
gradual_target = lvl;
244
#ifndef OVERRIDE_GRADUAL_TICK
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;
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);
263
gt --; // convert 1-based number to 0-based
267
#if PWM_CHANNELS >= 1
268
target = PWM_GET(pwm1_levels, gt);
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;
275
if (PWM1_LVL < target) PWM1_LVL ++;
276
else if (PWM1_LVL > target) PWM1_LVL --;
278
#if PWM_CHANNELS >= 2
279
target = PWM_GET(pwm2_levels, gt);
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;
286
if (PWM2_LVL < target) PWM2_LVL ++;
287
else if (PWM2_LVL > target) PWM2_LVL --;
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 --;
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 --;
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))
306
#if PWM_CHANNELS >= 3
307
&& (PWM3_LVL == PWM_GET(pwm3_levels, gt))
309
#if PWM_CHANNELS >= 4
310
&& (PWM4_LVL == PWM_GET(pwm4_levels, gt))
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);
315
250
uint8_t orig = gradual_target;
317
252
gradual_target = orig;
319
// is handled in set_level()
320
//#ifdef USE_TINT_RAMPING
323
// is handled in set_level()
324
//#ifdef USE_DYNAMIC_UNDERCLOCKING
325
//auto_clock_speed();
328
#endif // ifdef OVERRIDE_GRADUAL_TICK
329
255
#endif // ifdef USE_SET_LEVEL_GRADUALLY
332
#if defined(USE_TINT_RAMPING) && (!defined(TINT_RAMP_TOGGLE_ONLY))
334
#ifndef TINT_RAMPING_CORRECTION
335
#define TINT_RAMPING_CORRECTION 26 // 140% brightness at middle tint
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;
344
uint16_t top = PWM1_TOP;
345
//uint16_t top = PWM_GET(pwm_tops, actual_level-1);
347
const uint16_t top = PWM_TOP;
352
uint8_t level = actual_level - 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); }
358
// linear with power level
359
//if (tint == 0) { mytint = brightness; }
360
//else if (tint == 255) { mytint = 255 - brightness; }
362
// stretch 1-254 to fit 0-255 range (hits every value except 98 and 198)
363
else { mytint = (tint * 100 / 99) - 1; }
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);
374
// fade the triangle wave out when above 100% power,
375
// so it won't go over 200%
376
if (brightness > top) {
378
((brightness - top) * TINT_RAMPING_CORRECTION / 64)
379
* triangle_wave(mytint) / 255
382
// guarantee no more than 200% power
383
if (base_PWM > (top << 1)) { base_PWM = top << 1; }
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);
392
} else if (warm_PWM > top) {
393
cool_PWM += (warm_PWM - top);
397
TINT1_LVL = warm_PWM;
398
TINT2_LVL = cool_PWM;
400
// disable the power channel, if relevant
401
#ifdef LED_ENABLE_PIN
403
LED_ENABLE_PORT |= (1 << LED_ENABLE_PIN);
405
LED_ENABLE_PORT &= ~(1 << LED_ENABLE_PIN);
407
#ifdef LED2_ENABLE_PIN
409
LED2_ENABLE_PORT |= (1 << LED2_ENABLE_PIN);
411
LED2_ENABLE_PORT &= ~(1 << LED2_ENABLE_PIN);
414
#endif // ifdef USE_TINT_RAMPING
417
258
#endif // ifdef USE_RAMPING