From dab4967f1bebc9a70374ed3e1fe7906828b280c2 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Tue, 3 Sep 2019 08:34:31 -0700 Subject: Add Dip Switch as a core feature (#6140) * Add Dip Switches as a core feature * Add documentation for Dip Switch feature * Update Preonic Rev3 to use new feature and remove custom matrix * Apply suggestions from code review Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Remove custom matrix line completely Rather than just disabling it Co-Authored-By: fauxpark * DIP changes Co-Authored-By: fauxpark * Use better check for DIP Switch configuration * Add to show features * Add bitmask callback for dip switch * Fix OLKB Boards dip switch config * Update docs to include bitmask example * Fix comments/documentation Co-Authored-By: fauxpark * Fix issues with docs and use example from @tuzonghua * Fix wording Co-Authored-By: fauxpark * Fix example to use proper formatting Bad, BAAAAAAD drashna!!! * Handle dip switch initialization better --- common_features.mk | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 3296424a1..3bc6f1c73 100644 --- a/common_features.mk +++ b/common_features.mk @@ -358,3 +358,9 @@ ifeq ($(strip $(SPACE_CADET_ENABLE)), yes) SRC += $(QUANTUM_DIR)/process_keycode/process_space_cadet.c OPT_DEFS += -DSPACE_CADET_ENABLE endif + + +ifeq ($(strip $(DIP_SWITCH_ENABLE)), yes) + SRC += $(QUANTUM_DIR)/dip_switch.c + OPT_DEFS += -DDIP_SWITCH_ENABLE +endif -- cgit v1.3 From 265d8abee16ef73bcaf8cdc70a8e0532fbfb0fda Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Sat, 14 Sep 2019 19:57:07 -0700 Subject: Fix enables for Haptic Feedback (#6707) * Fix enables for Haptic Feedback If you enabled bothe DRV2605 and SOLENOID, it would only enable one of these, not both. This fixes the check so that you can enable both options. * Fix check for haptic feature --- common_features.mk | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 3bc6f1c73..79af8a225 100644 --- a/common_features.mk +++ b/common_features.mk @@ -267,20 +267,21 @@ ifeq ($(strip $(ENCODER_ENABLE)), yes) OPT_DEFS += -DENCODER_ENABLE endif -ifeq ($(strip $(HAPTIC_ENABLE)), DRV2605L) - COMMON_VPATH += $(DRIVER_PATH)/haptic - SRC += haptic.c +HAPTIC_ENABLE ?= no +ifneq ($(strip $(HAPTIC_ENABLE)),no) + COMMON_VPATH += $(DRIVER_PATH)/haptic + SRC += haptic.c + OPT_DEFS += -DHAPTIC_ENABLE +endif + +ifneq ($(filter DRV2605L, $(HAPTIC_ENABLE)), ) SRC += DRV2605L.c QUANTUM_LIB_SRC += i2c_master.c - OPT_DEFS += -DHAPTIC_ENABLE OPT_DEFS += -DDRV2605L endif -ifeq ($(strip $(HAPTIC_ENABLE)), SOLENOID) - COMMON_VPATH += $(DRIVER_PATH)/haptic - SRC += haptic.c +ifneq ($(filter SOLENOID, $(HAPTIC_ENABLE)), ) SRC += solenoid.c - OPT_DEFS += -DHAPTIC_ENABLE OPT_DEFS += -DSOLENOID_ENABLE endif -- cgit v1.3 From 38aefaf78e3d9f17ef561f031679a02c9fba869c Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 5 Oct 2019 16:57:00 +0100 Subject: ARM - Initial backlight support (#6487) * Move AVR backlight to own file, add borrowed ARM implementation * Tiny fix for backlight custom logic * Remove duplicate board from rebase * Fix f303 onekey example * clang-format * clang-format * Remove backlight keymap debug * Initial pass of ARM backlight docs * Initial pass of ARM backlight docs - resolve todos * fix rules validation logic * Add f072 warning * Add f072 warning * tidy up breathing in backlight keymap * tidy up breathing in backlight keymap * add missing break to backlight keymap --- common_features.mk | 23 +- docs/feature_backlight.md | 50 +- keyboards/handwired/onekey/bluepill/config.h | 4 + keyboards/handwired/onekey/bluepill/halconf.h | 2 +- keyboards/handwired/onekey/bluepill/mcuconf.h | 6 +- keyboards/handwired/onekey/bluepill/rules.mk | 6 +- keyboards/handwired/onekey/config.h | 2 + .../handwired/onekey/keymaps/backlight/config.h | 3 + .../handwired/onekey/keymaps/backlight/keymap.c | 40 ++ .../handwired/onekey/keymaps/backlight/rules.mk | 2 + keyboards/handwired/onekey/proton_c/config.h | 5 + keyboards/handwired/onekey/stm32f0_disco/config.h | 5 + quantum/backlight/backlight.c | 1 + quantum/backlight/backlight_arm.c | 218 +++++++++ quantum/backlight/backlight_avr.c | 509 +++++++++++++++++++++ quantum/quantum.c | 509 --------------------- tmk_core/common/backlight.h | 4 + 17 files changed, 860 insertions(+), 529 deletions(-) create mode 100644 keyboards/handwired/onekey/keymaps/backlight/config.h create mode 100644 keyboards/handwired/onekey/keymaps/backlight/keymap.c create mode 100644 keyboards/handwired/onekey/keymaps/backlight/rules.mk create mode 100644 quantum/backlight/backlight.c create mode 100644 quantum/backlight/backlight_arm.c create mode 100644 quantum/backlight/backlight_avr.c (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 79af8a225..05a99fc63 100644 --- a/common_features.mk +++ b/common_features.mk @@ -229,13 +229,32 @@ ifeq ($(strip $(LCD_ENABLE)), yes) CIE1931_CURVE = yes endif -ifeq ($(strip $(BACKLIGHT_ENABLE)), yes) +# backward compat +ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes) + BACKLIGHT_ENABLE = custom +endif + +VALID_BACKLIGHT_TYPES := yes custom + +BACKLIGHT_ENABLE ?= no +ifneq ($(strip $(BACKLIGHT_ENABLE)), no) + ifeq ($(filter $(BACKLIGHT_ENABLE),$(VALID_BACKLIGHT_TYPES)),) + $(error BACKLIGHT_ENABLE="$(BACKLIGHT_ENABLE)" is not a valid backlight type) + endif + ifeq ($(strip $(VISUALIZER_ENABLE)), yes) CIE1931_CURVE = yes endif - ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes) + + ifeq ($(strip $(BACKLIGHT_ENABLE)), custom) OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER endif + + ifeq ($(PLATFORM),AVR) + SRC += $(QUANTUM_DIR)/backlight/backlight_avr.c + else + SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c + endif endif ifeq ($(strip $(CIE1931_CURVE)), yes) diff --git a/docs/feature_backlight.md b/docs/feature_backlight.md index 556da7385..6a2946fd6 100644 --- a/docs/feature_backlight.md +++ b/docs/feature_backlight.md @@ -1,6 +1,8 @@ # Backlighting -Many keyboards support backlit keys by way of individual LEDs placed through or underneath the keyswitches. QMK is able to control the brightness of these LEDs by switching them on and off rapidly in a certain ratio, a technique known as *Pulse Width Modulation*, or PWM. By altering the duty cycle of the PWM signal, it creates the illusion of dimming. +Many keyboards support backlit keys by way of individual LEDs placed through or underneath the keyswitches. This feature is distinct from both the [RGB underglow](feature_rgblight.md) and [RGB matrix](feature_rgb_matrix.md) features as it usually allows for only a single colour per switch, though you can obviously install multiple different single coloured LEDs on a keyboard. + +QMK is able to control the brightness of these LEDs by switching them on and off rapidly in a certain ratio, a technique known as *Pulse Width Modulation*, or PWM. By altering the duty cycle of the PWM signal, it creates the illusion of dimming. The MCU can only supply so much current to its GPIO pins. Instead of powering the backlight directly from the MCU, the backlight pin is connected to a transistor or MOSFET that switches the power to the LEDs. @@ -12,9 +14,8 @@ Most keyboards have backlighting enabled by default if they support it, but if i BACKLIGHT_ENABLE = yes ``` -You should then be able to use the keycodes below to change the backlight level. - ## Keycodes +Once enabled the following keycodes below can be used to change the backlight level. |Key |Description | |---------|------------------------------------------| @@ -26,9 +27,9 @@ You should then be able to use the keycodes below to change the backlight level. |`BL_DEC` |Decrease the backlight level | |`BL_BRTG`|Toggle backlight breathing | -## Caveats +## AVR driver -This feature is distinct from both the [RGB underglow](feature_rgblight.md) and [RGB matrix](feature_rgb_matrix.md) features as it usually allows for only a single colour per switch, though you can obviously use multiple different coloured LEDs on a keyboard. +### Caveats Hardware PWM is supported according to the following table: @@ -58,9 +59,9 @@ All other pins will use software PWM. If the [Audio](feature_audio.md) feature i When both timers are in use for Audio, the backlight PWM will not use a hardware timer, but will instead be triggered during the matrix scan. In this case, breathing is not supported, and the backlight might flicker, because the PWM computation may not be called with enough timing precision. -## Configuration +### AVR Configuration -To change the behaviour of the backlighting, `#define` these in your `config.h`: +To change the behavior of the backlighting, `#define` these in your `config.h`: |Define |Default |Description | |---------------------|-------------|-------------------------------------------------------------------------------------------------------------| @@ -72,14 +73,14 @@ To change the behaviour of the backlighting, `#define` these in your `config.h`: |`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | |`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low | -## Backlight On State +### Backlight On State Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*. Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead. This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define. -## Multiple backlight pins +### Multiple backlight pins Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin). In software PWM, it is possible to define multiple backlight pins. All those pins will be turned on and off at the same time during the PWM duty cycle. @@ -87,13 +88,13 @@ This feature allows to set for instance the Caps Lock LED (or any other controll To activate multiple backlight pins, you need to add something like this to your user `config.h`: -~~~c +```c #define BACKLIGHT_LED_COUNT 2 #undef BACKLIGHT_PIN #define BACKLIGHT_PINS { F5, B2 } -~~~ +``` -## Hardware PWM Implementation +### Hardware PWM Implementation When using the supported pins for backlighting, QMK will use a hardware timer configured to output a PWM signal. This timer will count up to `ICRx` (by default `0xFFFF`) before resetting to 0. The desired brightness is calculated and stored in the `OCRxx` register. When the counter reaches this value, the backlight pin will go low, and is pulled high again when the counter resets. @@ -102,7 +103,7 @@ In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus th The breathing effect is achieved by registering an interrupt handler for `TIMER1_OVF_vect` that is called whenever the counter resets, roughly 244 times per second. In this handler, the value of an incrementing counter is mapped onto a precomputed brightness curve. To turn off breathing, the interrupt handler is simply disabled, and the brightness reset to the level stored in EEPROM. -## Software PWM Implementation +### Software PWM Implementation When `BACKLIGHT_PIN` is not set to a hardware backlight pin, QMK will use a hardware timer configured to trigger software interrupts. This time will count up to `ICRx` (by default `0xFFFF`) before resetting to 0. When resetting to 0, the CPU will fire an OVF (overflow) interrupt that will turn the LEDs on, starting the duty cycle. @@ -111,6 +112,29 @@ In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus th The breathing effect is the same as in the hardware PWM implementation. +## ARM Driver + +### Caveats + +Currently only hardware PWM is supported, and does not provide automatic configuration. + +?> STMF072 support is being investigated. + +### ARM Configuration + +To change the behavior of the backlighting, `#define` these in your `config.h`: + +|Define |Default |Description | +|------------------------|-------------|-------------------------------------------------------------------------------------------------------------| +|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this| +|`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use, see ST datasheets for pin to PWM timer mapping. Unless you are designing your own keyboard, you shouldn't need to change this| +|`BACKLIGHT_PWM_CHANNEL` |`3` |The PWM channel to use, see ST datasheets for pin to PWM channel mapping. Unless you are designing your own keyboard, you shouldn't need to change this| +|`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use, see ST datasheets for pin AF mapping. Unless you are designing your own keyboard, you shouldn't need to change this| +|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) | +|`BACKLIGHT_CAPS_LOCK` |*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | +|`BACKLIGHT_BREATHING` |*Not defined*|Enable backlight breathing, if supported | +|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | + ## Backlight Functions |Function |Description | diff --git a/keyboards/handwired/onekey/bluepill/config.h b/keyboards/handwired/onekey/bluepill/config.h index 3d88ee00e..81282ae1f 100644 --- a/keyboards/handwired/onekey/bluepill/config.h +++ b/keyboards/handwired/onekey/bluepill/config.h @@ -21,3 +21,7 @@ #define MATRIX_COL_PINS { B0 } #define MATRIX_ROW_PINS { A7 } #define UNUSED_PINS + +#define BACKLIGHT_PIN A0 +#define BACKLIGHT_PWM_DRIVER PWMD2 +#define BACKLIGHT_PWM_CHANNEL 1 diff --git a/keyboards/handwired/onekey/bluepill/halconf.h b/keyboards/handwired/onekey/bluepill/halconf.h index 72879a575..53b2f91e3 100644 --- a/keyboards/handwired/onekey/bluepill/halconf.h +++ b/keyboards/handwired/onekey/bluepill/halconf.h @@ -146,7 +146,7 @@ * @brief Enables the SPI subsystem. */ #if !defined(HAL_USE_SPI) || defined(__DOXYGEN__) -#define HAL_USE_SPI TRUE +#define HAL_USE_SPI FALSE #endif /** diff --git a/keyboards/handwired/onekey/bluepill/mcuconf.h b/keyboards/handwired/onekey/bluepill/mcuconf.h index fced27289..a645d3c5d 100644 --- a/keyboards/handwired/onekey/bluepill/mcuconf.h +++ b/keyboards/handwired/onekey/bluepill/mcuconf.h @@ -132,8 +132,8 @@ * PWM driver system settings. */ #define STM32_PWM_USE_ADVANCED FALSE -#define STM32_PWM_USE_TIM1 TRUE -#define STM32_PWM_USE_TIM2 FALSE +#define STM32_PWM_USE_TIM1 FALSE +#define STM32_PWM_USE_TIM2 TRUE #define STM32_PWM_USE_TIM3 FALSE #define STM32_PWM_USE_TIM4 FALSE #define STM32_PWM_USE_TIM5 FALSE @@ -168,7 +168,7 @@ * SPI driver system settings. */ #define STM32_SPI_USE_SPI1 FALSE -#define STM32_SPI_USE_SPI2 TRUE +#define STM32_SPI_USE_SPI2 FALSE #define STM32_SPI_USE_SPI3 FALSE #define STM32_SPI_SPI1_DMA_PRIORITY 1 #define STM32_SPI_SPI2_DMA_PRIORITY 1 diff --git a/keyboards/handwired/onekey/bluepill/rules.mk b/keyboards/handwired/onekey/bluepill/rules.mk index 46274066d..aeda2782b 100644 --- a/keyboards/handwired/onekey/bluepill/rules.mk +++ b/keyboards/handwired/onekey/bluepill/rules.mk @@ -1,7 +1,11 @@ # GENERIC STM32F103C8T6 board - stm32duino bootloader +BOARD = GENERIC_STM32_F103 + OPT_DEFS = -DCORTEX_VTOR_INIT=0x2000 MCU_LDSCRIPT = STM32F103x8_stm32duino_bootloader -BOARD = GENERIC_STM32_F103 + +DFU_ARGS = -d 1eaf:0003 -a2 -R +DFU_SUFFIX_ARGS ?= -v 1eaf -p 0003 # OPT_DEFS = # MCU_LDSCRIPT = STM32F103x8 diff --git a/keyboards/handwired/onekey/config.h b/keyboards/handwired/onekey/config.h index 6f7ec1289..64a447481 100644 --- a/keyboards/handwired/onekey/config.h +++ b/keyboards/handwired/onekey/config.h @@ -35,6 +35,8 @@ along with this program. If not, see . /* Set 0 if debouncing isn't needed */ #define DEBOUNCE 5 +#define TAPPING_TERM 500 + /* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ #define LOCKING_SUPPORT_ENABLE /* Locking resynchronize hack */ diff --git a/keyboards/handwired/onekey/keymaps/backlight/config.h b/keyboards/handwired/onekey/keymaps/backlight/config.h new file mode 100644 index 000000000..af01528b4 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/backlight/config.h @@ -0,0 +1,3 @@ +#pragma once + +#define BACKLIGHT_BREATHING diff --git a/keyboards/handwired/onekey/keymaps/backlight/keymap.c b/keyboards/handwired/onekey/keymaps/backlight/keymap.c new file mode 100644 index 000000000..1f4be16a6 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/backlight/keymap.c @@ -0,0 +1,40 @@ +#include QMK_KEYBOARD_H + +//Tap Dance Declarations +enum { + TD_BL = 0 +}; + +void dance_cln_finished (qk_tap_dance_state_t *state, void *user_data) { + // noop +} + +void dance_cln_reset (qk_tap_dance_state_t *state, void *user_data) { + switch (state->count) { + case 1: + // single tap - step through backlight + backlight_step(); + break; +#ifdef BACKLIGHT_BREATHING + case 2: + // double tap - toggle breathing + breathing_toggle(); + break; + case 3: + //tripple tap - do some pulse stuff + breathing_pulse(); + break; +#endif + default: + // more - nothing + break; + } +} + +qk_tap_dance_action_t tap_dance_actions[] = { + [TD_BL] = ACTION_TAP_DANCE_FN_ADVANCED (NULL, dance_cln_finished, dance_cln_reset) +}; + +const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = { + LAYOUT( TD(TD_BL) ) +}; diff --git a/keyboards/handwired/onekey/keymaps/backlight/rules.mk b/keyboards/handwired/onekey/keymaps/backlight/rules.mk new file mode 100644 index 000000000..176e09977 --- /dev/null +++ b/keyboards/handwired/onekey/keymaps/backlight/rules.mk @@ -0,0 +1,2 @@ +BACKLIGHT_ENABLE = yes +TAP_DANCE_ENABLE = yes diff --git a/keyboards/handwired/onekey/proton_c/config.h b/keyboards/handwired/onekey/proton_c/config.h index f6bedcfe6..d4fb9c829 100644 --- a/keyboards/handwired/onekey/proton_c/config.h +++ b/keyboards/handwired/onekey/proton_c/config.h @@ -21,3 +21,8 @@ #define MATRIX_COL_PINS { A3 } #define MATRIX_ROW_PINS { A2 } #define UNUSED_PINS + +#define BACKLIGHT_PIN B8 +#define BACKLIGHT_PWM_DRIVER PWMD4 +#define BACKLIGHT_PWM_CHANNEL 3 +#define BACKLIGHT_PAL_MODE 2 diff --git a/keyboards/handwired/onekey/stm32f0_disco/config.h b/keyboards/handwired/onekey/stm32f0_disco/config.h index 039a1beff..4024ee1ca 100644 --- a/keyboards/handwired/onekey/stm32f0_disco/config.h +++ b/keyboards/handwired/onekey/stm32f0_disco/config.h @@ -21,3 +21,8 @@ #define MATRIX_COL_PINS { B4 } #define MATRIX_ROW_PINS { B5 } #define UNUSED_PINS + +#define BACKLIGHT_PIN C8 +#define BACKLIGHT_PWM_DRIVER PWMD3 +#define BACKLIGHT_PWM_CHANNEL 3 +#define BACKLIGHT_PAL_MODE 0 diff --git a/quantum/backlight/backlight.c b/quantum/backlight/backlight.c new file mode 100644 index 000000000..e26de86bf --- /dev/null +++ b/quantum/backlight/backlight.c @@ -0,0 +1 @@ +// TODO: Add common code here, for example cie_lightness implementation diff --git a/quantum/backlight/backlight_arm.c b/quantum/backlight/backlight_arm.c new file mode 100644 index 000000000..3f94ccef8 --- /dev/null +++ b/quantum/backlight/backlight_arm.c @@ -0,0 +1,218 @@ +#include "quantum.h" +#include "backlight.h" +#include +#include "debug.h" + +// TODO: remove short term bodge when refactoring BACKLIGHT_CUSTOM_DRIVER out +#ifdef BACKLIGHT_PIN + +# if defined(STM32F0XX) || defined(STM32F0xx) +# error "Backlight support for STMF072 is not available. Please disable." +# endif + +# if defined(STM32F1XX) || defined(STM32F1xx) +# define USE_GPIOV1 +# endif + +// GPIOV2 && GPIOV3 +# ifndef BACKLIGHT_PAL_MODE +# define BACKLIGHT_PAL_MODE 2 +# endif + +// GENERIC +# ifndef BACKLIGHT_PWM_DRIVER +# define BACKLIGHT_PWM_DRIVER PWMD4 +# endif +# ifndef BACKLIGHT_PWM_CHANNEL +# define BACKLIGHT_PWM_CHANNEL 3 +# endif + +static void breathing_callback(PWMDriver *pwmp); + +static PWMConfig pwmCFG = {0xFFFF, /* PWM clock frequency */ + 256, /* PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */ + NULL, /* No Callback */ + { /* Default all channels to disabled - Channels will be configured durring init */ + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}}, + 0, /* HW dependent part.*/ + 0}; + +static PWMConfig pwmCFG_breathing = {0xFFFF, /** PWM clock frequency */ + 256, /* PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */ + breathing_callback, /* Breathing Callback */ + { /* Default all channels to disabled - Channels will be configured durring init */ + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}, + {PWM_OUTPUT_DISABLED, NULL}}, + 0, /* HW dependent part.*/ + 0}; + +// See http://jared.geek.nz/2013/feb/linear-led-pwm +static uint16_t cie_lightness(uint16_t v) { + if (v <= 5243) // if below 8% of max + return v / 9; // same as dividing by 900% + else { + uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare + // to get a useful result with integer division, we shift left in the expression above + // and revert what we've done again after squaring. + y = y * y * y >> 8; + if (y > 0xFFFFUL) // prevent overflow + return 0xFFFFU; + else + return (uint16_t)y; + } +} + +void backlight_init_ports(void) { + // printf("backlight_init_ports()\n"); + +# ifdef USE_GPIOV1 + palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL); +# else + palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_ALTERNATE(BACKLIGHT_PAL_MODE)); +# endif + + pwmCFG.channels[BACKLIGHT_PWM_CHANNEL - 1].mode = PWM_OUTPUT_ACTIVE_HIGH; + pwmCFG_breathing.channels[BACKLIGHT_PWM_CHANNEL - 1].mode = PWM_OUTPUT_ACTIVE_HIGH; + pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG); + + backlight_set(get_backlight_level()); + if (is_backlight_breathing()) { + breathing_enable(); + } +} + +void backlight_set(uint8_t level) { + // printf("backlight_set(%d)\n", level); + if (level == 0) { + // Turn backlight off + pwmDisableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1); + } else { + // Turn backlight on + if (!is_breathing()) { + uint32_t duty = (uint32_t)(cie_lightness(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS)); + // printf("duty: (%d)\n", duty); + pwmEnableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty)); + } + } +} + +uint8_t backlight_tick = 0; + +void backlight_task(void) {} + +# define BREATHING_NO_HALT 0 +# define BREATHING_HALT_OFF 1 +# define BREATHING_HALT_ON 2 +# define BREATHING_STEPS 128 + +static uint8_t breathing_period = BREATHING_PERIOD; +static uint8_t breathing_halt = BREATHING_NO_HALT; +static uint16_t breathing_counter = 0; + +bool is_breathing(void) { return BACKLIGHT_PWM_DRIVER.config == &pwmCFG_breathing; } + +static inline void breathing_min(void) { breathing_counter = 0; } + +static inline void breathing_max(void) { breathing_counter = breathing_period * 256 / 2; } + +void breathing_interrupt_enable(void) { + pwmStop(&BACKLIGHT_PWM_DRIVER); + pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG_breathing); + chSysLockFromISR(); + pwmEnablePeriodicNotification(&BACKLIGHT_PWM_DRIVER); + pwmEnableChannelI(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, 0xFFFF)); + chSysUnlockFromISR(); +} + +void breathing_interrupt_disable(void) { + pwmStop(&BACKLIGHT_PWM_DRIVER); + pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG); +} + +void breathing_enable(void) { + breathing_counter = 0; + breathing_halt = BREATHING_NO_HALT; + breathing_interrupt_enable(); +} + +void breathing_pulse(void) { + if (get_backlight_level() == 0) + breathing_min(); + else + breathing_max(); + breathing_halt = BREATHING_HALT_ON; + breathing_interrupt_enable(); +} + +void breathing_disable(void) { + // printf("breathing_disable()\n"); + breathing_interrupt_disable(); + // Restore backlight level + backlight_set(get_backlight_level()); +} + +void breathing_self_disable(void) { + if (get_backlight_level() == 0) + breathing_halt = BREATHING_HALT_OFF; + else + breathing_halt = BREATHING_HALT_ON; +} + +void breathing_toggle(void) { + if (is_breathing()) + breathing_disable(); + else + breathing_enable(); +} + +void breathing_period_set(uint8_t value) { + if (!value) value = 1; + breathing_period = value; +} + +void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); } + +void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); } + +void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); } + +/* To generate breathing curve in python: + * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)] + */ +static const uint8_t breathing_table[BREATHING_STEPS] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 17, 20, 24, 28, 32, 36, 41, 46, 51, 57, 63, 70, 76, 83, 91, 98, 106, 113, 121, 129, 138, 146, 154, 162, 170, 178, 185, 193, 200, 207, 213, 220, 225, 231, 235, 240, 244, 247, 250, 252, 253, 254, 255, 254, 253, 252, 250, 247, 244, 240, 235, 231, 225, 220, 213, 207, 200, 193, 185, 178, 170, 162, 154, 146, 138, 129, 121, 113, 106, 98, 91, 83, 76, 70, 63, 57, 51, 46, 41, 36, 32, 28, 24, 20, 17, 15, 12, 10, 8, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +// Use this before the cie_lightness function. +static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); } + +static void breathing_callback(PWMDriver *pwmp) { + (void)pwmp; + uint16_t interval = (uint16_t)breathing_period * 256 / BREATHING_STEPS; + // resetting after one period to prevent ugly reset at overflow. + breathing_counter = (breathing_counter + 1) % (breathing_period * 256); + uint8_t index = breathing_counter / interval % BREATHING_STEPS; + + if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) { + breathing_interrupt_disable(); + } + + uint32_t duty = cie_lightness(scale_backlight(breathing_table[index] * 256)); + + chSysLockFromISR(); + pwmEnableChannelI(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty)); + chSysUnlockFromISR(); +} + +#else + +__attribute__((weak)) void backlight_init_ports(void) {} + +__attribute__((weak)) void backlight_set(uint8_t level) {} + +__attribute__((weak)) void backlight_task(void) {} + +#endif diff --git a/quantum/backlight/backlight_avr.c b/quantum/backlight/backlight_avr.c new file mode 100644 index 000000000..445698f47 --- /dev/null +++ b/quantum/backlight/backlight_avr.c @@ -0,0 +1,509 @@ +#include "quantum.h" +#include "backlight.h" +#include "debug.h" + +#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS)) + +// This logic is a bit complex, we support 3 setups: +// +// 1. Hardware PWM when backlight is wired to a PWM pin. +// Depending on this pin, we use a different output compare unit. +// 2. Software PWM with hardware timers, but the used timer +// depends on the Audio setup (Audio wins over Backlight). +// 3. Full software PWM, driven by the matrix scan, if both timers are used by Audio. + +# if (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == B5 || BACKLIGHT_PIN == B6 || BACKLIGHT_PIN == B7) +# define HARDWARE_PWM +# define ICRx ICR1 +# define TCCRxA TCCR1A +# define TCCRxB TCCR1B +# define TIMERx_OVF_vect TIMER1_OVF_vect +# define TIMSKx TIMSK1 +# define TOIEx TOIE1 + +# if BACKLIGHT_PIN == B5 +# define COMxx1 COM1A1 +# define OCRxx OCR1A +# elif BACKLIGHT_PIN == B6 +# define COMxx1 COM1B1 +# define OCRxx OCR1B +# elif BACKLIGHT_PIN == B7 +# define COMxx1 COM1C1 +# define OCRxx OCR1C +# endif +# elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == C4 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) +# define HARDWARE_PWM +# define ICRx ICR3 +# define TCCRxA TCCR3A +# define TCCRxB TCCR3B +# define TIMERx_OVF_vect TIMER3_OVF_vect +# define TIMSKx TIMSK3 +# define TOIEx TOIE3 + +# if BACKLIGHT_PIN == C4 +# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) +# error This MCU has no C4 pin! +# else +# define COMxx1 COM3C1 +# define OCRxx OCR3C +# endif +# elif BACKLIGHT_PIN == C5 +# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) +# error This MCU has no C5 pin! +# else +# define COMxx1 COM3B1 +# define OCRxx OCR3B +# endif +# elif BACKLIGHT_PIN == C6 +# define COMxx1 COM3A1 +# define OCRxx OCR3A +# endif +# elif (defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) +# define HARDWARE_PWM +# define ICRx ICR1 +# define TCCRxA TCCR1A +# define TCCRxB TCCR1B +# define TIMERx_OVF_vect TIMER1_OVF_vect +# define TIMSKx TIMSK1 +# define TOIEx TOIE1 + +# if BACKLIGHT_PIN == B7 +# define COMxx1 COM1C1 +# define OCRxx OCR1C +# elif BACKLIGHT_PIN == C5 +# define COMxx1 COM1B1 +# define OCRxx OCR1B +# elif BACKLIGHT_PIN == C6 +# define COMxx1 COM1A1 +# define OCRxx OCR1A +# endif +# elif defined(__AVR_ATmega32A__) && (BACKLIGHT_PIN == D4 || BACKLIGHT_PIN == D5) +# define HARDWARE_PWM +# define ICRx ICR1 +# define TCCRxA TCCR1A +# define TCCRxB TCCR1B +# define TIMERx_OVF_vect TIMER1_OVF_vect +# define TIMSKx TIMSK +# define TOIEx TOIE1 + +# if BACKLIGHT_PIN == D4 +# define COMxx1 COM1B1 +# define OCRxx OCR1B +# elif BACKLIGHT_PIN == D5 +# define COMxx1 COM1A1 +# define OCRxx OCR1A +# endif +# elif defined(__AVR_ATmega328P__) && (BACKLIGHT_PIN == B1 || BACKLIGHT_PIN == B2) +# define HARDWARE_PWM +# define ICRx ICR1 +# define TCCRxA TCCR1A +# define TCCRxB TCCR1B +# define TIMERx_OVF_vect TIMER1_OVF_vect +# define TIMSKx TIMSK1 +# define TOIEx TOIE1 + +# if BACKLIGHT_PIN == B1 +# define COMxx1 COM1A1 +# define OCRxx OCR1A +# elif BACKLIGHT_PIN == B2 +# define COMxx1 COM1B1 +# define OCRxx OCR1B +# endif +# else +# if !defined(BACKLIGHT_CUSTOM_DRIVER) +# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO) +// Timer 1 is not in use by Audio feature, Backlight can use it +# pragma message "Using hardware timer 1 with software PWM" +# define HARDWARE_PWM +# define BACKLIGHT_PWM_TIMER +# define ICRx ICR1 +# define TCCRxA TCCR1A +# define TCCRxB TCCR1B +# define TIMERx_COMPA_vect TIMER1_COMPA_vect +# define TIMERx_OVF_vect TIMER1_OVF_vect +# if defined(__AVR_ATmega32A__) // This MCU has only one TIMSK register +# define TIMSKx TIMSK +# else +# define TIMSKx TIMSK1 +# endif +# define TOIEx TOIE1 + +# define OCIExA OCIE1A +# define OCRxx OCR1A +# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO) +# pragma message "Using hardware timer 3 with software PWM" +// Timer 3 is not in use by Audio feature, Backlight can use it +# define HARDWARE_PWM +# define BACKLIGHT_PWM_TIMER +# define ICRx ICR1 +# define TCCRxA TCCR3A +# define TCCRxB TCCR3B +# define TIMERx_COMPA_vect TIMER3_COMPA_vect +# define TIMERx_OVF_vect TIMER3_OVF_vect +# define TIMSKx TIMSK3 +# define TOIEx TOIE3 + +# define OCIExA OCIE3A +# define OCRxx OCR3A +# else +# pragma message "Audio in use - using pure software PWM" +# define NO_HARDWARE_PWM +# endif +# else +# pragma message "Custom driver defined - using pure software PWM" +# define NO_HARDWARE_PWM +# endif +# endif + +# ifndef BACKLIGHT_ON_STATE +# define BACKLIGHT_ON_STATE 0 +# endif + +void backlight_on(uint8_t backlight_pin) { +# if BACKLIGHT_ON_STATE == 0 + writePinLow(backlight_pin); +# else + writePinHigh(backlight_pin); +# endif +} + +void backlight_off(uint8_t backlight_pin) { +# if BACKLIGHT_ON_STATE == 0 + writePinHigh(backlight_pin); +# else + writePinLow(backlight_pin); +# endif +} + +# if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software + +// we support multiple backlight pins +# ifndef BACKLIGHT_LED_COUNT +# define BACKLIGHT_LED_COUNT 1 +# endif + +# if BACKLIGHT_LED_COUNT == 1 +# define BACKLIGHT_PIN_INIT \ + { BACKLIGHT_PIN } +# else +# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS +# endif + +# define FOR_EACH_LED(x) \ + for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \ + uint8_t backlight_pin = backlight_pins[i]; \ + { x } \ + } + +static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT; + +# else // full hardware PWM + +// we support only one backlight pin +static const uint8_t backlight_pin = BACKLIGHT_PIN; +# define FOR_EACH_LED(x) x + +# endif + +# ifdef NO_HARDWARE_PWM +__attribute__((weak)) void backlight_init_ports(void) { + // Setup backlight pin as output and output to on state. + FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);) + +# ifdef BACKLIGHT_BREATHING + if (is_backlight_breathing()) { + breathing_enable(); + } +# endif +} + +__attribute__((weak)) void backlight_set(uint8_t level) {} + +uint8_t backlight_tick = 0; + +# ifndef BACKLIGHT_CUSTOM_DRIVER +void backlight_task(void) { + if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) { + FOR_EACH_LED(backlight_on(backlight_pin);) + } else { + FOR_EACH_LED(backlight_off(backlight_pin);) + } + backlight_tick = (backlight_tick + 1) % 16; +} +# endif + +# ifdef BACKLIGHT_BREATHING +# ifndef BACKLIGHT_CUSTOM_DRIVER +# error "Backlight breathing only available with hardware PWM. Please disable." +# endif +# endif + +# else // hardware pwm through timer + +# ifdef BACKLIGHT_PWM_TIMER + +// The idea of software PWM assisted by hardware timers is the following +// we use the hardware timer in fast PWM mode like for hardware PWM, but +// instead of letting the Output Match Comparator control the led pin +// (which is not possible since the backlight is not wired to PWM pins on the +// CPU), we do the LED on/off by oursleves. +// The timer is setup to count up to 0xFFFF, and we set the Output Compare +// register to the current 16bits backlight level (after CIE correction). +// This means the CPU will trigger a compare match interrupt when the counter +// reaches the backlight level, where we turn off the LEDs, +// but also an overflow interrupt when the counter rolls back to 0, +// in which we're going to turn on the LEDs. +// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz. + +// Triggered when the counter reaches the OCRx value +ISR(TIMERx_COMPA_vect) { FOR_EACH_LED(backlight_off(backlight_pin);) } + +// Triggered when the counter reaches the TOP value +// this one triggers at F_CPU/65536 =~ 244 Hz +ISR(TIMERx_OVF_vect) { +# ifdef BACKLIGHT_BREATHING + if (is_breathing()) { + breathing_task(); + } +# endif + // for very small values of OCRxx (or backlight level) + // we can't guarantee this whole code won't execute + // at the same time as the compare match interrupt + // which means that we might turn on the leds while + // trying to turn them off, leading to flickering + // artifacts (especially while breathing, because breathing_task + // takes many computation cycles). + // so better not turn them on while the counter TOP is very low. + if (OCRxx > 256) { + FOR_EACH_LED(backlight_on(backlight_pin);) + } +} + +# endif + +# define TIMER_TOP 0xFFFFU + +// See http://jared.geek.nz/2013/feb/linear-led-pwm +static uint16_t cie_lightness(uint16_t v) { + if (v <= 5243) // if below 8% of max + return v / 9; // same as dividing by 900% + else { + uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare + // to get a useful result with integer division, we shift left in the expression above + // and revert what we've done again after squaring. + y = y * y * y >> 8; + if (y > 0xFFFFUL) // prevent overflow + return 0xFFFFU; + else + return (uint16_t)y; + } +} + +// range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val. +static inline void set_pwm(uint16_t val) { OCRxx = val; } + +# ifndef BACKLIGHT_CUSTOM_DRIVER +__attribute__((weak)) void backlight_set(uint8_t level) { + if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS; + + if (level == 0) { +# ifdef BACKLIGHT_PWM_TIMER + if (OCRxx) { + TIMSKx &= ~(_BV(OCIExA)); + TIMSKx &= ~(_BV(TOIEx)); + FOR_EACH_LED(backlight_off(backlight_pin);) + } +# else + // Turn off PWM control on backlight pin + TCCRxA &= ~(_BV(COMxx1)); +# endif + } else { +# ifdef BACKLIGHT_PWM_TIMER + if (!OCRxx) { + TIMSKx |= _BV(OCIExA); + TIMSKx |= _BV(TOIEx); + } +# else + // Turn on PWM control of backlight pin + TCCRxA |= _BV(COMxx1); +# endif + } + // Set the brightness + set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS)); +} + +void backlight_task(void) {} +# endif // BACKLIGHT_CUSTOM_DRIVER + +# ifdef BACKLIGHT_BREATHING + +# define BREATHING_NO_HALT 0 +# define BREATHING_HALT_OFF 1 +# define BREATHING_HALT_ON 2 +# define BREATHING_STEPS 128 + +static uint8_t breathing_period = BREATHING_PERIOD; +static uint8_t breathing_halt = BREATHING_NO_HALT; +static uint16_t breathing_counter = 0; + +# ifdef BACKLIGHT_PWM_TIMER +static bool breathing = false; + +bool is_breathing(void) { return breathing; } + +# define breathing_interrupt_enable() \ + do { \ + breathing = true; \ + } while (0) +# define breathing_interrupt_disable() \ + do { \ + breathing = false; \ + } while (0) +# else + +bool is_breathing(void) { return !!(TIMSKx & _BV(TOIEx)); } + +# define breathing_interrupt_enable() \ + do { \ + TIMSKx |= _BV(TOIEx); \ + } while (0) +# define breathing_interrupt_disable() \ + do { \ + TIMSKx &= ~_BV(TOIEx); \ + } while (0) +# endif + +# define breathing_min() \ + do { \ + breathing_counter = 0; \ + } while (0) +# define breathing_max() \ + do { \ + breathing_counter = breathing_period * 244 / 2; \ + } while (0) + +void breathing_enable(void) { + breathing_counter = 0; + breathing_halt = BREATHING_NO_HALT; + breathing_interrupt_enable(); +} + +void breathing_pulse(void) { + if (get_backlight_level() == 0) + breathing_min(); + else + breathing_max(); + breathing_halt = BREATHING_HALT_ON; + breathing_interrupt_enable(); +} + +void breathing_disable(void) { + breathing_interrupt_disable(); + // Restore backlight level + backlight_set(get_backlight_level()); +} + +void breathing_self_disable(void) { + if (get_backlight_level() == 0) + breathing_halt = BREATHING_HALT_OFF; + else + breathing_halt = BREATHING_HALT_ON; +} + +void breathing_toggle(void) { + if (is_breathing()) + breathing_disable(); + else + breathing_enable(); +} + +void breathing_period_set(uint8_t value) { + if (!value) value = 1; + breathing_period = value; +} + +void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); } + +void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); } + +void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); } + +/* To generate breathing curve in python: + * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)] + */ +static const uint8_t breathing_table[BREATHING_STEPS] PROGMEM = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 17, 20, 24, 28, 32, 36, 41, 46, 51, 57, 63, 70, 76, 83, 91, 98, 106, 113, 121, 129, 138, 146, 154, 162, 170, 178, 185, 193, 200, 207, 213, 220, 225, 231, 235, 240, 244, 247, 250, 252, 253, 254, 255, 254, 253, 252, 250, 247, 244, 240, 235, 231, 225, 220, 213, 207, 200, 193, 185, 178, 170, 162, 154, 146, 138, 129, 121, 113, 106, 98, 91, 83, 76, 70, 63, 57, 51, 46, 41, 36, 32, 28, 24, 20, 17, 15, 12, 10, 8, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +// Use this before the cie_lightness function. +static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); } + +# ifdef BACKLIGHT_PWM_TIMER +void breathing_task(void) +# else +/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run + * about 244 times per second. + */ +ISR(TIMERx_OVF_vect) +# endif +{ + uint16_t interval = (uint16_t)breathing_period * 244 / BREATHING_STEPS; + // resetting after one period to prevent ugly reset at overflow. + breathing_counter = (breathing_counter + 1) % (breathing_period * 244); + uint8_t index = breathing_counter / interval % BREATHING_STEPS; + + if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) { + breathing_interrupt_disable(); + } + + set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U))); +} + +# endif // BACKLIGHT_BREATHING + +__attribute__((weak)) void backlight_init_ports(void) { + // Setup backlight pin as output and output to on state. + FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);) + + // I could write a wall of text here to explain... but TL;DW + // Go read the ATmega32u4 datasheet. + // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on + +# ifdef BACKLIGHT_PWM_TIMER + // TimerX setup, Fast PWM mode count to TOP set in ICRx + TCCRxA = _BV(WGM11); // = 0b00000010; + // clock select clk/1 + TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; +# else // hardware PWM + // Pin PB7 = OCR1C (Timer 1, Channel C) + // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0 + // (i.e. start high, go low when counter matches.) + // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0 + // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1 + + /* + 14.8.3: + "In fast PWM mode, the compare units allow generation of PWM waveforms on the OCnx pins. Setting the COMnx1:0 bits to two will produce a non-inverted PWM [..]." + "In fast PWM mode the counter is incremented until the counter value matches either one of the fixed values 0x00FF, 0x01FF, or 0x03FF (WGMn3:0 = 5, 6, or 7), the value in ICRn (WGMn3:0 = 14), or the value in OCRnA (WGMn3:0 = 15)." + */ + TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010; + TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; +# endif + // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0. + ICRx = TIMER_TOP; + + backlight_init(); +# ifdef BACKLIGHT_BREATHING + if (is_backlight_breathing()) { + breathing_enable(); + } +# endif +} + +# endif // hardware backlight + +#else // no backlight + +__attribute__((weak)) void backlight_init_ports(void) {} + +__attribute__((weak)) void backlight_set(uint8_t level) {} + +#endif // backlight \ No newline at end of file diff --git a/quantum/quantum.c b/quantum/quantum.c index 16922dd01..f4999456e 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -24,10 +24,6 @@ # include "outputselect.h" #endif -#ifndef BREATHING_PERIOD -# define BREATHING_PERIOD 6 -#endif - #include "backlight.h" extern backlight_config_t backlight_config; @@ -1019,511 +1015,6 @@ void matrix_scan_quantum() { matrix_scan_kb(); } -#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS)) - -// This logic is a bit complex, we support 3 setups: -// -// 1. Hardware PWM when backlight is wired to a PWM pin. -// Depending on this pin, we use a different output compare unit. -// 2. Software PWM with hardware timers, but the used timer -// depends on the Audio setup (Audio wins over Backlight). -// 3. Full software PWM, driven by the matrix scan, if both timers are used by Audio. - -# if (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == B5 || BACKLIGHT_PIN == B6 || BACKLIGHT_PIN == B7) -# define HARDWARE_PWM -# define ICRx ICR1 -# define TCCRxA TCCR1A -# define TCCRxB TCCR1B -# define TIMERx_OVF_vect TIMER1_OVF_vect -# define TIMSKx TIMSK1 -# define TOIEx TOIE1 - -# if BACKLIGHT_PIN == B5 -# define COMxx1 COM1A1 -# define OCRxx OCR1A -# elif BACKLIGHT_PIN == B6 -# define COMxx1 COM1B1 -# define OCRxx OCR1B -# elif BACKLIGHT_PIN == B7 -# define COMxx1 COM1C1 -# define OCRxx OCR1C -# endif -# elif (defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) && (BACKLIGHT_PIN == C4 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) -# define HARDWARE_PWM -# define ICRx ICR3 -# define TCCRxA TCCR3A -# define TCCRxB TCCR3B -# define TIMERx_OVF_vect TIMER3_OVF_vect -# define TIMSKx TIMSK3 -# define TOIEx TOIE3 - -# if BACKLIGHT_PIN == C4 -# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) -# error This MCU has no C4 pin! -# else -# define COMxx1 COM3C1 -# define OCRxx OCR3C -# endif -# elif BACKLIGHT_PIN == C5 -# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)) -# error This MCU has no C5 pin! -# else -# define COMxx1 COM3B1 -# define OCRxx OCR3B -# endif -# elif BACKLIGHT_PIN == C6 -# define COMxx1 COM3A1 -# define OCRxx OCR3A -# endif -# elif (defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6) -# define HARDWARE_PWM -# define ICRx ICR1 -# define TCCRxA TCCR1A -# define TCCRxB TCCR1B -# define TIMERx_OVF_vect TIMER1_OVF_vect -# define TIMSKx TIMSK1 -# define TOIEx TOIE1 - -# if BACKLIGHT_PIN == B7 -# define COMxx1 COM1C1 -# define OCRxx OCR1C -# elif BACKLIGHT_PIN == C5 -# define COMxx1 COM1B1 -# define OCRxx OCR1B -# elif BACKLIGHT_PIN == C6 -# define COMxx1 COM1A1 -# define OCRxx OCR1A -# endif -# elif defined(__AVR_ATmega32A__) && (BACKLIGHT_PIN == D4 || BACKLIGHT_PIN == D5) -# define HARDWARE_PWM -# define ICRx ICR1 -# define TCCRxA TCCR1A -# define TCCRxB TCCR1B -# define TIMERx_OVF_vect TIMER1_OVF_vect -# define TIMSKx TIMSK -# define TOIEx TOIE1 - -# if BACKLIGHT_PIN == D4 -# define COMxx1 COM1B1 -# define OCRxx OCR1B -# elif BACKLIGHT_PIN == D5 -# define COMxx1 COM1A1 -# define OCRxx OCR1A -# endif -# elif defined(__AVR_ATmega328P__) && (BACKLIGHT_PIN == B1 || BACKLIGHT_PIN == B2) -# define HARDWARE_PWM -# define ICRx ICR1 -# define TCCRxA TCCR1A -# define TCCRxB TCCR1B -# define TIMERx_OVF_vect TIMER1_OVF_vect -# define TIMSKx TIMSK1 -# define TOIEx TOIE1 - -# if BACKLIGHT_PIN == B1 -# define COMxx1 COM1A1 -# define OCRxx OCR1A -# elif BACKLIGHT_PIN == B2 -# define COMxx1 COM1B1 -# define OCRxx OCR1B -# endif -# else -# if !defined(BACKLIGHT_CUSTOM_DRIVER) -# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO) -// Timer 1 is not in use by Audio feature, Backlight can use it -# pragma message "Using hardware timer 1 with software PWM" -# define HARDWARE_PWM -# define BACKLIGHT_PWM_TIMER -# define ICRx ICR1 -# define TCCRxA TCCR1A -# define TCCRxB TCCR1B -# define TIMERx_COMPA_vect TIMER1_COMPA_vect -# define TIMERx_OVF_vect TIMER1_OVF_vect -# if defined(__AVR_ATmega32A__) // This MCU has only one TIMSK register -# define TIMSKx TIMSK -# else -# define TIMSKx TIMSK1 -# endif -# define TOIEx TOIE1 - -# define OCIExA OCIE1A -# define OCRxx OCR1A -# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO) -# pragma message "Using hardware timer 3 with software PWM" -// Timer 3 is not in use by Audio feature, Backlight can use it -# define HARDWARE_PWM -# define BACKLIGHT_PWM_TIMER -# define ICRx ICR1 -# define TCCRxA TCCR3A -# define TCCRxB TCCR3B -# define TIMERx_COMPA_vect TIMER3_COMPA_vect -# define TIMERx_OVF_vect TIMER3_OVF_vect -# define TIMSKx TIMSK3 -# define TOIEx TOIE3 - -# define OCIExA OCIE3A -# define OCRxx OCR3A -# else -# pragma message "Audio in use - using pure software PWM" -# define NO_HARDWARE_PWM -# endif -# else -# pragma message "Custom driver defined - using pure software PWM" -# define NO_HARDWARE_PWM -# endif -# endif - -# ifndef BACKLIGHT_ON_STATE -# define BACKLIGHT_ON_STATE 0 -# endif - -void backlight_on(uint8_t backlight_pin) { -# if BACKLIGHT_ON_STATE == 0 - writePinLow(backlight_pin); -# else - writePinHigh(backlight_pin); -# endif -} - -void backlight_off(uint8_t backlight_pin) { -# if BACKLIGHT_ON_STATE == 0 - writePinHigh(backlight_pin); -# else - writePinLow(backlight_pin); -# endif -} - -# if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software - -// we support multiple backlight pins -# ifndef BACKLIGHT_LED_COUNT -# define BACKLIGHT_LED_COUNT 1 -# endif - -# if BACKLIGHT_LED_COUNT == 1 -# define BACKLIGHT_PIN_INIT \ - { BACKLIGHT_PIN } -# else -# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS -# endif - -# define FOR_EACH_LED(x) \ - for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \ - uint8_t backlight_pin = backlight_pins[i]; \ - { x } \ - } - -static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT; - -# else // full hardware PWM - -// we support only one backlight pin -static const uint8_t backlight_pin = BACKLIGHT_PIN; -# define FOR_EACH_LED(x) x - -# endif - -# ifdef NO_HARDWARE_PWM -__attribute__((weak)) void backlight_init_ports(void) { - // Setup backlight pin as output and output to on state. - FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);) - -# ifdef BACKLIGHT_BREATHING - if (is_backlight_breathing()) { - breathing_enable(); - } -# endif -} - -__attribute__((weak)) void backlight_set(uint8_t level) {} - -uint8_t backlight_tick = 0; - -# ifndef BACKLIGHT_CUSTOM_DRIVER -void backlight_task(void) { - if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) { - FOR_EACH_LED(backlight_on(backlight_pin);) - } else { - FOR_EACH_LED(backlight_off(backlight_pin);) - } - backlight_tick = (backlight_tick + 1) % 16; -} -# endif - -# ifdef BACKLIGHT_BREATHING -# ifndef BACKLIGHT_CUSTOM_DRIVER -# error "Backlight breathing only available with hardware PWM. Please disable." -# endif -# endif - -# else // hardware pwm through timer - -# ifdef BACKLIGHT_PWM_TIMER - -// The idea of software PWM assisted by hardware timers is the following -// we use the hardware timer in fast PWM mode like for hardware PWM, but -// instead of letting the Output Match Comparator control the led pin -// (which is not possible since the backlight is not wired to PWM pins on the -// CPU), we do the LED on/off by oursleves. -// The timer is setup to count up to 0xFFFF, and we set the Output Compare -// register to the current 16bits backlight level (after CIE correction). -// This means the CPU will trigger a compare match interrupt when the counter -// reaches the backlight level, where we turn off the LEDs, -// but also an overflow interrupt when the counter rolls back to 0, -// in which we're going to turn on the LEDs. -// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz. - -// Triggered when the counter reaches the OCRx value -ISR(TIMERx_COMPA_vect) { FOR_EACH_LED(backlight_off(backlight_pin);) } - -// Triggered when the counter reaches the TOP value -// this one triggers at F_CPU/65536 =~ 244 Hz -ISR(TIMERx_OVF_vect) { -# ifdef BACKLIGHT_BREATHING - if (is_breathing()) { - breathing_task(); - } -# endif - // for very small values of OCRxx (or backlight level) - // we can't guarantee this whole code won't execute - // at the same time as the compare match interrupt - // which means that we might turn on the leds while - // trying to turn them off, leading to flickering - // artifacts (especially while breathing, because breathing_task - // takes many computation cycles). - // so better not turn them on while the counter TOP is very low. - if (OCRxx > 256) { - FOR_EACH_LED(backlight_on(backlight_pin);) - } -} - -# endif - -# define TIMER_TOP 0xFFFFU - -// See http://jared.geek.nz/2013/feb/linear-led-pwm -static uint16_t cie_lightness(uint16_t v) { - if (v <= 5243) // if below 8% of max - return v / 9; // same as dividing by 900% - else { - uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare - // to get a useful result with integer division, we shift left in the expression above - // and revert what we've done again after squaring. - y = y * y * y >> 8; - if (y > 0xFFFFUL) // prevent overflow - return 0xFFFFU; - else - return (uint16_t)y; - } -} - -// range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val. -static inline void set_pwm(uint16_t val) { OCRxx = val; } - -# ifndef BACKLIGHT_CUSTOM_DRIVER -__attribute__((weak)) void backlight_set(uint8_t level) { - if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS; - - if (level == 0) { -# ifdef BACKLIGHT_PWM_TIMER - if (OCRxx) { - TIMSKx &= ~(_BV(OCIExA)); - TIMSKx &= ~(_BV(TOIEx)); - FOR_EACH_LED(backlight_off(backlight_pin);) - } -# else - // Turn off PWM control on backlight pin - TCCRxA &= ~(_BV(COMxx1)); -# endif - } else { -# ifdef BACKLIGHT_PWM_TIMER - if (!OCRxx) { - TIMSKx |= _BV(OCIExA); - TIMSKx |= _BV(TOIEx); - } -# else - // Turn on PWM control of backlight pin - TCCRxA |= _BV(COMxx1); -# endif - } - // Set the brightness - set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS)); -} - -void backlight_task(void) {} -# endif // BACKLIGHT_CUSTOM_DRIVER - -# ifdef BACKLIGHT_BREATHING - -# define BREATHING_NO_HALT 0 -# define BREATHING_HALT_OFF 1 -# define BREATHING_HALT_ON 2 -# define BREATHING_STEPS 128 - -static uint8_t breathing_period = BREATHING_PERIOD; -static uint8_t breathing_halt = BREATHING_NO_HALT; -static uint16_t breathing_counter = 0; - -# ifdef BACKLIGHT_PWM_TIMER -static bool breathing = false; - -bool is_breathing(void) { return breathing; } - -# define breathing_interrupt_enable() \ - do { \ - breathing = true; \ - } while (0) -# define breathing_interrupt_disable() \ - do { \ - breathing = false; \ - } while (0) -# else - -bool is_breathing(void) { return !!(TIMSKx & _BV(TOIEx)); } - -# define breathing_interrupt_enable() \ - do { \ - TIMSKx |= _BV(TOIEx); \ - } while (0) -# define breathing_interrupt_disable() \ - do { \ - TIMSKx &= ~_BV(TOIEx); \ - } while (0) -# endif - -# define breathing_min() \ - do { \ - breathing_counter = 0; \ - } while (0) -# define breathing_max() \ - do { \ - breathing_counter = breathing_period * 244 / 2; \ - } while (0) - -void breathing_enable(void) { - breathing_counter = 0; - breathing_halt = BREATHING_NO_HALT; - breathing_interrupt_enable(); -} - -void breathing_pulse(void) { - if (get_backlight_level() == 0) - breathing_min(); - else - breathing_max(); - breathing_halt = BREATHING_HALT_ON; - breathing_interrupt_enable(); -} - -void breathing_disable(void) { - breathing_interrupt_disable(); - // Restore backlight level - backlight_set(get_backlight_level()); -} - -void breathing_self_disable(void) { - if (get_backlight_level() == 0) - breathing_halt = BREATHING_HALT_OFF; - else - breathing_halt = BREATHING_HALT_ON; -} - -void breathing_toggle(void) { - if (is_breathing()) - breathing_disable(); - else - breathing_enable(); -} - -void breathing_period_set(uint8_t value) { - if (!value) value = 1; - breathing_period = value; -} - -void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); } - -void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); } - -void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); } - -/* To generate breathing curve in python: - * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)] - */ -static const uint8_t breathing_table[BREATHING_STEPS] PROGMEM = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 17, 20, 24, 28, 32, 36, 41, 46, 51, 57, 63, 70, 76, 83, 91, 98, 106, 113, 121, 129, 138, 146, 154, 162, 170, 178, 185, 193, 200, 207, 213, 220, 225, 231, 235, 240, 244, 247, 250, 252, 253, 254, 255, 254, 253, 252, 250, 247, 244, 240, 235, 231, 225, 220, 213, 207, 200, 193, 185, 178, 170, 162, 154, 146, 138, 129, 121, 113, 106, 98, 91, 83, 76, 70, 63, 57, 51, 46, 41, 36, 32, 28, 24, 20, 17, 15, 12, 10, 8, 6, 5, 4, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -// Use this before the cie_lightness function. -static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); } - -# ifdef BACKLIGHT_PWM_TIMER -void breathing_task(void) -# else -/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run - * about 244 times per second. - */ -ISR(TIMERx_OVF_vect) -# endif -{ - uint16_t interval = (uint16_t)breathing_period * 244 / BREATHING_STEPS; - // resetting after one period to prevent ugly reset at overflow. - breathing_counter = (breathing_counter + 1) % (breathing_period * 244); - uint8_t index = breathing_counter / interval % BREATHING_STEPS; - - if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) { - breathing_interrupt_disable(); - } - - set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U))); -} - -# endif // BACKLIGHT_BREATHING - -__attribute__((weak)) void backlight_init_ports(void) { - // Setup backlight pin as output and output to on state. - FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);) - - // I could write a wall of text here to explain... but TL;DW - // Go read the ATmega32u4 datasheet. - // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on - -# ifdef BACKLIGHT_PWM_TIMER - // TimerX setup, Fast PWM mode count to TOP set in ICRx - TCCRxA = _BV(WGM11); // = 0b00000010; - // clock select clk/1 - TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; -# else // hardware PWM - // Pin PB7 = OCR1C (Timer 1, Channel C) - // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0 - // (i.e. start high, go low when counter matches.) - // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0 - // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1 - - /* - 14.8.3: - "In fast PWM mode, the compare units allow generation of PWM waveforms on the OCnx pins. Setting the COMnx1:0 bits to two will produce a non-inverted PWM [..]." - "In fast PWM mode the counter is incremented until the counter value matches either one of the fixed values 0x00FF, 0x01FF, or 0x03FF (WGMn3:0 = 5, 6, or 7), the value in ICRn (WGMn3:0 = 14), or the value in OCRnA (WGMn3:0 = 15)." - */ - TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010; - TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; -# endif - // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0. - ICRx = TIMER_TOP; - - backlight_init(); -# ifdef BACKLIGHT_BREATHING - if (is_backlight_breathing()) { - breathing_enable(); - } -# endif -} - -# endif // hardware backlight - -#else // no backlight - -__attribute__((weak)) void backlight_init_ports(void) {} - -__attribute__((weak)) void backlight_set(uint8_t level) {} - -#endif // backlight #ifdef HD44780_ENABLED # include "hd44780.h" diff --git a/tmk_core/common/backlight.h b/tmk_core/common/backlight.h index bb1f897ee..1e581055d 100644 --- a/tmk_core/common/backlight.h +++ b/tmk_core/common/backlight.h @@ -26,6 +26,10 @@ along with this program. If not, see . # error "Maximum value of BACKLIGHT_LEVELS is 31" #endif +#ifndef BREATHING_PERIOD +# define BREATHING_PERIOD 6 +#endif + typedef union { uint8_t raw; struct { -- cgit v1.3 From abfd6ed9613013d3c9f15da8b575a902d9bcf274 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 17 Oct 2019 17:48:58 +0100 Subject: Move tmk_core/common/backlight to quantum/backlight (#6710) * Move tmk_core/common/backlight to quantum/backlight * Add guards to backlight inclusion * Add guards to backlight inclusion * Update backlight guards on clueboard/60 * Use full paths to avoid vpath issues --- common_features.mk | 5 + keyboards/clueboard/60/led.c | 10 +- quantum/backlight/backlight.c | 194 +++++++++++++++++++++++++++++++++++++- quantum/backlight/backlight.h | 62 ++++++++++++ quantum/keymap_common.c | 5 +- quantum/quantum.c | 6 +- tmk_core/common.mk | 5 - tmk_core/common/action.c | 5 +- tmk_core/common/avr/suspend.c | 5 +- tmk_core/common/backlight.c | 193 ------------------------------------- tmk_core/common/backlight.h | 62 ------------ tmk_core/common/chibios/suspend.c | 5 +- tmk_core/common/command.c | 5 +- tmk_core/common/keyboard.c | 4 +- 14 files changed, 292 insertions(+), 274 deletions(-) create mode 100644 quantum/backlight/backlight.h delete mode 100644 tmk_core/common/backlight.c delete mode 100644 tmk_core/common/backlight.h (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 05a99fc63..83b2b51ae 100644 --- a/common_features.mk +++ b/common_features.mk @@ -246,6 +246,11 @@ ifneq ($(strip $(BACKLIGHT_ENABLE)), no) CIE1931_CURVE = yes endif + + COMMON_VPATH += $(QUANTUM_DIR)/backlight + SRC += $(QUANTUM_DIR)/backlight/backlight.c + OPT_DEFS += -DBACKLIGHT_ENABLE + ifeq ($(strip $(BACKLIGHT_ENABLE)), custom) OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER endif diff --git a/keyboards/clueboard/60/led.c b/keyboards/clueboard/60/led.c index 350696736..91a2c537d 100644 --- a/keyboards/clueboard/60/led.c +++ b/keyboards/clueboard/60/led.c @@ -16,21 +16,21 @@ */ #include "hal.h" -#include "backlight.h" #include "led.h" #include "printf.h" +#ifdef BACKLIGHT_ENABLE +#include "backlight.h" + void backlight_init_ports(void) { printf("backlight_init_ports()\n"); - #ifdef BACKLIGHT_ENABLE + palSetPadMode(GPIOB, 8, PAL_MODE_OUTPUT_PUSHPULL); palSetPad(GPIOB, 8); - #endif } void backlight_set(uint8_t level) { printf("backlight_set(%d)\n", level); - #ifdef BACKLIGHT_ENABLE if (level == 0) { // Turn backlight off palSetPad(GPIOB, 8); @@ -38,8 +38,8 @@ void backlight_set(uint8_t level) { // Turn backlight on palClearPad(GPIOB, 8); } - #endif } +#endif void led_set_kb(uint8_t usb_led) { printf("led_set_kb(%d)\n", usb_led); diff --git a/quantum/backlight/backlight.c b/quantum/backlight/backlight.c index e26de86bf..708022f68 100644 --- a/quantum/backlight/backlight.c +++ b/quantum/backlight/backlight.c @@ -1 +1,193 @@ -// TODO: Add common code here, for example cie_lightness implementation +/* +Copyright 2013 Mathias Andersson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "backlight.h" +#include "eeconfig.h" +#include "debug.h" + +backlight_config_t backlight_config; + +/** \brief Backlight initialization + * + * FIXME: needs doc + */ +void backlight_init(void) { + /* check signature */ + if (!eeconfig_is_enabled()) { + eeconfig_init(); + } + backlight_config.raw = eeconfig_read_backlight(); + if (backlight_config.level > BACKLIGHT_LEVELS) { + backlight_config.level = BACKLIGHT_LEVELS; + } + backlight_set(backlight_config.enable ? backlight_config.level : 0); +} + +/** \brief Backlight increase + * + * FIXME: needs doc + */ +void backlight_increase(void) { + if (backlight_config.level < BACKLIGHT_LEVELS) { + backlight_config.level++; + } + backlight_config.enable = 1; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight increase: %u\n", backlight_config.level); + backlight_set(backlight_config.level); +} + +/** \brief Backlight decrease + * + * FIXME: needs doc + */ +void backlight_decrease(void) { + if (backlight_config.level > 0) { + backlight_config.level--; + backlight_config.enable = !!backlight_config.level; + eeconfig_update_backlight(backlight_config.raw); + } + dprintf("backlight decrease: %u\n", backlight_config.level); + backlight_set(backlight_config.level); +} + +/** \brief Backlight toggle + * + * FIXME: needs doc + */ +void backlight_toggle(void) { + bool enabled = backlight_config.enable; + dprintf("backlight toggle: %u\n", enabled); + if (enabled) + backlight_disable(); + else + backlight_enable(); +} + +/** \brief Enable backlight + * + * FIXME: needs doc + */ +void backlight_enable(void) { + if (backlight_config.enable) return; // do nothing if backlight is already on + + backlight_config.enable = true; + if (backlight_config.raw == 1) // enabled but level == 0 + backlight_config.level = 1; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight enable\n"); + backlight_set(backlight_config.level); +} + +/** \brief Disable backlight + * + * FIXME: needs doc + */ +void backlight_disable(void) { + if (!backlight_config.enable) return; // do nothing if backlight is already off + + backlight_config.enable = false; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight disable\n"); + backlight_set(0); +} + +/** /brief Get the backlight status + * + * FIXME: needs doc + */ +bool is_backlight_enabled(void) { return backlight_config.enable; } + +/** \brief Backlight step through levels + * + * FIXME: needs doc + */ +void backlight_step(void) { + backlight_config.level++; + if (backlight_config.level > BACKLIGHT_LEVELS) { + backlight_config.level = 0; + } + backlight_config.enable = !!backlight_config.level; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight step: %u\n", backlight_config.level); + backlight_set(backlight_config.level); +} + +/** \brief Backlight set level + * + * FIXME: needs doc + */ +void backlight_level(uint8_t level) { + if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS; + backlight_config.level = level; + backlight_config.enable = !!backlight_config.level; + eeconfig_update_backlight(backlight_config.raw); + backlight_set(backlight_config.level); +} + +/** \brief Get backlight level + * + * FIXME: needs doc + */ +uint8_t get_backlight_level(void) { return backlight_config.level; } + +#ifdef BACKLIGHT_BREATHING +/** \brief Backlight breathing toggle + * + * FIXME: needs doc + */ +void backlight_toggle_breathing(void) { + bool breathing = backlight_config.breathing; + dprintf("backlight breathing toggle: %u\n", breathing); + if (breathing) + backlight_disable_breathing(); + else + backlight_enable_breathing(); +} + +/** \brief Enable backlight breathing + * + * FIXME: needs doc + */ +void backlight_enable_breathing(void) { + if (backlight_config.breathing) return; // do nothing if breathing is already on + + backlight_config.breathing = true; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight breathing enable\n"); + breathing_enable(); +} + +/** \brief Disable backlight breathing + * + * FIXME: needs doc + */ +void backlight_disable_breathing(void) { + if (!backlight_config.breathing) return; // do nothing if breathing is already off + + backlight_config.breathing = false; + eeconfig_update_backlight(backlight_config.raw); + dprintf("backlight breathing disable\n"); + breathing_disable(); +} + +/** \brief Get the backlight breathing status + * + * FIXME: needs doc + */ +bool is_backlight_breathing(void) { return backlight_config.breathing; } +#endif diff --git a/quantum/backlight/backlight.h b/quantum/backlight/backlight.h new file mode 100644 index 000000000..1e581055d --- /dev/null +++ b/quantum/backlight/backlight.h @@ -0,0 +1,62 @@ +/* +Copyright 2013 Mathias Andersson + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#ifndef BACKLIGHT_LEVELS +# define BACKLIGHT_LEVELS 3 +#elif BACKLIGHT_LEVELS > 31 +# error "Maximum value of BACKLIGHT_LEVELS is 31" +#endif + +#ifndef BREATHING_PERIOD +# define BREATHING_PERIOD 6 +#endif + +typedef union { + uint8_t raw; + struct { + bool enable : 1; + bool breathing : 1; + uint8_t reserved : 1; // Reserved for possible future backlight modes + uint8_t level : 5; + }; +} backlight_config_t; + +void backlight_init(void); +void backlight_increase(void); +void backlight_decrease(void); +void backlight_toggle(void); +void backlight_enable(void); +void backlight_disable(void); +bool is_backlight_enabled(void); +void backlight_step(void); +void backlight_set(uint8_t level); +void backlight_level(uint8_t level); +uint8_t get_backlight_level(void); + +#ifdef BACKLIGHT_BREATHING +void backlight_toggle_breathing(void); +void backlight_enable_breathing(void); +void backlight_disable_breathing(void); +bool is_backlight_breathing(void); +void breathing_enable(void); +void breathing_disable(void); +#endif diff --git a/quantum/keymap_common.c b/quantum/keymap_common.c index 9af951008..4fa45ac37 100644 --- a/quantum/keymap_common.c +++ b/quantum/keymap_common.c @@ -26,9 +26,12 @@ along with this program. If not, see . #include "action.h" #include "action_macro.h" #include "debug.h" -#include "backlight.h" #include "quantum.h" +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif + #ifdef MIDI_ENABLE # include "process_midi.h" #endif diff --git a/quantum/quantum.c b/quantum/quantum.c index e615cfc0f..2020770ea 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -24,8 +24,10 @@ # include "outputselect.h" #endif -#include "backlight.h" -extern backlight_config_t backlight_config; +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" + extern backlight_config_t backlight_config; +#endif #ifdef FAUXCLICKY_ENABLE # include "fauxclicky.h" diff --git a/tmk_core/common.mk b/tmk_core/common.mk index 221688755..db5535346 100644 --- a/tmk_core/common.mk +++ b/tmk_core/common.mk @@ -153,11 +153,6 @@ ifeq ($(strip $(NO_SUSPEND_POWER_DOWN)), yes) TMK_COMMON_DEFS += -DNO_SUSPEND_POWER_DOWN endif -ifeq ($(strip $(BACKLIGHT_ENABLE)), yes) - TMK_COMMON_SRC += $(COMMON_DIR)/backlight.c - TMK_COMMON_DEFS += -DBACKLIGHT_ENABLE -endif - ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK diff --git a/tmk_core/common/action.c b/tmk_core/common/action.c index 44b19d368..bd6aeba4f 100644 --- a/tmk_core/common/action.c +++ b/tmk_core/common/action.c @@ -20,7 +20,6 @@ along with this program. If not, see . #include "mousekey.h" #include "command.h" #include "led.h" -#include "backlight.h" #include "action_layer.h" #include "action_tapping.h" #include "action_macro.h" @@ -28,6 +27,10 @@ along with this program. If not, see . #include "action.h" #include "wait.h" +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif + #ifdef DEBUG_ACTION # include "debug.h" #else diff --git a/tmk_core/common/avr/suspend.c b/tmk_core/common/avr/suspend.c index 574000fcd..c59c19688 100644 --- a/tmk_core/common/avr/suspend.c +++ b/tmk_core/common/avr/suspend.c @@ -4,7 +4,6 @@ #include #include "matrix.h" #include "action.h" -#include "backlight.h" #include "suspend_avr.h" #include "suspend.h" #include "timer.h" @@ -16,6 +15,10 @@ # include "lufa.h" #endif +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif + #ifdef AUDIO_ENABLE # include "audio.h" #endif /* AUDIO_ENABLE */ diff --git a/tmk_core/common/backlight.c b/tmk_core/common/backlight.c deleted file mode 100644 index 708022f68..000000000 --- a/tmk_core/common/backlight.c +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2013 Mathias Andersson - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#include "backlight.h" -#include "eeconfig.h" -#include "debug.h" - -backlight_config_t backlight_config; - -/** \brief Backlight initialization - * - * FIXME: needs doc - */ -void backlight_init(void) { - /* check signature */ - if (!eeconfig_is_enabled()) { - eeconfig_init(); - } - backlight_config.raw = eeconfig_read_backlight(); - if (backlight_config.level > BACKLIGHT_LEVELS) { - backlight_config.level = BACKLIGHT_LEVELS; - } - backlight_set(backlight_config.enable ? backlight_config.level : 0); -} - -/** \brief Backlight increase - * - * FIXME: needs doc - */ -void backlight_increase(void) { - if (backlight_config.level < BACKLIGHT_LEVELS) { - backlight_config.level++; - } - backlight_config.enable = 1; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight increase: %u\n", backlight_config.level); - backlight_set(backlight_config.level); -} - -/** \brief Backlight decrease - * - * FIXME: needs doc - */ -void backlight_decrease(void) { - if (backlight_config.level > 0) { - backlight_config.level--; - backlight_config.enable = !!backlight_config.level; - eeconfig_update_backlight(backlight_config.raw); - } - dprintf("backlight decrease: %u\n", backlight_config.level); - backlight_set(backlight_config.level); -} - -/** \brief Backlight toggle - * - * FIXME: needs doc - */ -void backlight_toggle(void) { - bool enabled = backlight_config.enable; - dprintf("backlight toggle: %u\n", enabled); - if (enabled) - backlight_disable(); - else - backlight_enable(); -} - -/** \brief Enable backlight - * - * FIXME: needs doc - */ -void backlight_enable(void) { - if (backlight_config.enable) return; // do nothing if backlight is already on - - backlight_config.enable = true; - if (backlight_config.raw == 1) // enabled but level == 0 - backlight_config.level = 1; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight enable\n"); - backlight_set(backlight_config.level); -} - -/** \brief Disable backlight - * - * FIXME: needs doc - */ -void backlight_disable(void) { - if (!backlight_config.enable) return; // do nothing if backlight is already off - - backlight_config.enable = false; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight disable\n"); - backlight_set(0); -} - -/** /brief Get the backlight status - * - * FIXME: needs doc - */ -bool is_backlight_enabled(void) { return backlight_config.enable; } - -/** \brief Backlight step through levels - * - * FIXME: needs doc - */ -void backlight_step(void) { - backlight_config.level++; - if (backlight_config.level > BACKLIGHT_LEVELS) { - backlight_config.level = 0; - } - backlight_config.enable = !!backlight_config.level; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight step: %u\n", backlight_config.level); - backlight_set(backlight_config.level); -} - -/** \brief Backlight set level - * - * FIXME: needs doc - */ -void backlight_level(uint8_t level) { - if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS; - backlight_config.level = level; - backlight_config.enable = !!backlight_config.level; - eeconfig_update_backlight(backlight_config.raw); - backlight_set(backlight_config.level); -} - -/** \brief Get backlight level - * - * FIXME: needs doc - */ -uint8_t get_backlight_level(void) { return backlight_config.level; } - -#ifdef BACKLIGHT_BREATHING -/** \brief Backlight breathing toggle - * - * FIXME: needs doc - */ -void backlight_toggle_breathing(void) { - bool breathing = backlight_config.breathing; - dprintf("backlight breathing toggle: %u\n", breathing); - if (breathing) - backlight_disable_breathing(); - else - backlight_enable_breathing(); -} - -/** \brief Enable backlight breathing - * - * FIXME: needs doc - */ -void backlight_enable_breathing(void) { - if (backlight_config.breathing) return; // do nothing if breathing is already on - - backlight_config.breathing = true; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight breathing enable\n"); - breathing_enable(); -} - -/** \brief Disable backlight breathing - * - * FIXME: needs doc - */ -void backlight_disable_breathing(void) { - if (!backlight_config.breathing) return; // do nothing if breathing is already off - - backlight_config.breathing = false; - eeconfig_update_backlight(backlight_config.raw); - dprintf("backlight breathing disable\n"); - breathing_disable(); -} - -/** \brief Get the backlight breathing status - * - * FIXME: needs doc - */ -bool is_backlight_breathing(void) { return backlight_config.breathing; } -#endif diff --git a/tmk_core/common/backlight.h b/tmk_core/common/backlight.h deleted file mode 100644 index 1e581055d..000000000 --- a/tmk_core/common/backlight.h +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2013 Mathias Andersson - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#pragma once - -#include -#include - -#ifndef BACKLIGHT_LEVELS -# define BACKLIGHT_LEVELS 3 -#elif BACKLIGHT_LEVELS > 31 -# error "Maximum value of BACKLIGHT_LEVELS is 31" -#endif - -#ifndef BREATHING_PERIOD -# define BREATHING_PERIOD 6 -#endif - -typedef union { - uint8_t raw; - struct { - bool enable : 1; - bool breathing : 1; - uint8_t reserved : 1; // Reserved for possible future backlight modes - uint8_t level : 5; - }; -} backlight_config_t; - -void backlight_init(void); -void backlight_increase(void); -void backlight_decrease(void); -void backlight_toggle(void); -void backlight_enable(void); -void backlight_disable(void); -bool is_backlight_enabled(void); -void backlight_step(void); -void backlight_set(uint8_t level); -void backlight_level(uint8_t level); -uint8_t get_backlight_level(void); - -#ifdef BACKLIGHT_BREATHING -void backlight_toggle_breathing(void); -void backlight_enable_breathing(void); -void backlight_disable_breathing(void); -bool is_backlight_breathing(void); -void breathing_enable(void); -void breathing_disable(void); -#endif diff --git a/tmk_core/common/chibios/suspend.c b/tmk_core/common/chibios/suspend.c index ae1c6f53e..c0f9c28d4 100644 --- a/tmk_core/common/chibios/suspend.c +++ b/tmk_core/common/chibios/suspend.c @@ -8,10 +8,13 @@ #include "action_util.h" #include "mousekey.h" #include "host.h" -#include "backlight.h" #include "suspend.h" #include "wait.h" +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif + /** \brief suspend idle * * FIXME: needs doc diff --git a/tmk_core/common/command.c b/tmk_core/common/command.c index 8bf72ef25..82cd80609 100644 --- a/tmk_core/common/command.c +++ b/tmk_core/common/command.c @@ -32,10 +32,13 @@ along with this program. If not, see . #include "sleep_led.h" #include "led.h" #include "command.h" -#include "backlight.h" #include "quantum.h" #include "version.h" +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif + #ifdef MOUSEKEY_ENABLE # include "mousekey.h" #endif diff --git a/tmk_core/common/keyboard.c b/tmk_core/common/keyboard.c index 9806b5015..63ace9793 100644 --- a/tmk_core/common/keyboard.c +++ b/tmk_core/common/keyboard.c @@ -29,8 +29,10 @@ along with this program. If not, see . #include "util.h" #include "sendchar.h" #include "eeconfig.h" -#include "backlight.h" #include "action_layer.h" +#ifdef BACKLIGHT_ENABLE +# include "backlight.h" +#endif #ifdef BOOTMAGIC_ENABLE # include "bootmagic.h" #else -- cgit v1.3 From 64b7cfe735339193ae78fe0f13029b0e027af7a7 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 29 Oct 2019 22:53:11 +0000 Subject: Refactor ps2avrgb i2c ws2812 to core (#7183) * Refactor ps2avrgb i2c ws2812 to core * Refactor jj40 to use ws2812 i2c driver * Refactor ps2avrgb template to use ws2812 i2c driver * Add ws2812 stub files * clang-format and driver config * Add ws2812 driver docs * Fix default config values * Update tmk_core/protocol/vusb/main.c Co-Authored-By: Drashna Jaelre --- common_features.mk | 24 +++++++++++++++++++++-- docs/_summary.md | 1 + docs/ws2812_driver.md | 33 +++++++++++++++++++++++++++++++ drivers/arm/ws2812.c | 1 + drivers/arm/ws2812_pwm.c | 1 + drivers/arm/ws2812_spi.c | 1 + drivers/avr/ws2812_i2c.c | 31 +++++++++++++++++++++++++++++ keyboards/jj40/jj40.c | 37 ----------------------------------- keyboards/jj40/rules.mk | 4 +--- quantum/template/ps2avrgb/keyboard.c | 38 ------------------------------------ quantum/template/ps2avrgb/rules.mk | 4 +--- tmk_core/protocol/vusb/main.c | 9 +++++++++ 12 files changed, 101 insertions(+), 83 deletions(-) create mode 100644 docs/ws2812_driver.md create mode 100644 drivers/arm/ws2812.c create mode 100644 drivers/arm/ws2812_pwm.c create mode 100644 drivers/arm/ws2812_spi.c create mode 100644 drivers/avr/ws2812_i2c.c (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 83b2b51ae..7bb9187bb 100644 --- a/common_features.mk +++ b/common_features.mk @@ -112,7 +112,7 @@ ifeq ($(strip $(RGBLIGHT_ENABLE)), yes) ifeq ($(strip $(RGBLIGHT_CUSTOM_DRIVER)), yes) OPT_DEFS += -DRGBLIGHT_CUSTOM_DRIVER else - SRC += ws2812.c + WS2812_DRIVER_REQUIRED = yes endif endif @@ -176,7 +176,7 @@ endif ifeq ($(strip $(RGB_MATRIX_ENABLE)), WS2812) OPT_DEFS += -DWS2812 - SRC += ws2812.c + WS2812_DRIVER_REQUIRED = yes endif ifeq ($(strip $(RGB_MATRIX_CUSTOM_KB)), yes) @@ -262,6 +262,26 @@ ifneq ($(strip $(BACKLIGHT_ENABLE)), no) endif endif +VALID_WS2812_DRIVER_TYPES := bitbang pwm spi i2c + +WS2812_DRIVER ?= bitbang +ifeq ($(strip $(WS2812_DRIVER_REQUIRED)), yes) + ifeq ($(filter $(WS2812_DRIVER),$(VALID_WS2812_DRIVER_TYPES)),) + $(error WS2812_DRIVER="$(WS2812_DRIVER)" is not a valid WS2812 driver) + endif + + ifeq ($(strip $(WS2812_DRIVER)), bitbang) + SRC += ws2812.c + else + SRC += ws2812_$(strip $(WS2812_DRIVER)).c + endif + + # add extra deps + ifeq ($(strip $(WS2812_DRIVER)), i2c) + QUANTUM_LIB_SRC += i2c_master.c + endif +endif + ifeq ($(strip $(CIE1931_CURVE)), yes) OPT_DEFS += -DUSE_CIE1931_CURVE LED_TABLES = yes diff --git a/docs/_summary.md b/docs/_summary.md index 65bfa11f0..dc6e88f95 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -98,6 +98,7 @@ * [ISP Flashing Guide](isp_flashing_guide.md) * [ARM Debugging Guide](arm_debugging.md) * [I2C Driver](i2c_driver.md) + * [WS2812 Driver](ws2812_driver.md) * [GPIO Controls](internals_gpio_control.md) * [Proton C Conversion](proton_c_conversion.md) diff --git a/docs/ws2812_driver.md b/docs/ws2812_driver.md new file mode 100644 index 000000000..6fa5d324c --- /dev/null +++ b/docs/ws2812_driver.md @@ -0,0 +1,33 @@ +# WS2812 Driver +This driver powers the [RGB Lighting](feature_rgblight.md) and [RGB Matrix](feature_rgb_matrix.md) features. + +Currently QMK supports the following addressable LEDs on AVR microcontrollers (however, the white LED in RGBW variants is not supported): + + WS2811, WS2812, WS2812B, WS2812C, etc. + SK6812, SK6812MINI, SK6805 + +These LEDs are called "addressable" because instead of using a wire per color, each LED contains a small microchip that understands a special protocol sent over a single wire. The chip passes on the remaining data to the next LED, allowing them to be chained together. In this way, you can easily control the color of the individual LEDs. + +## Driver configuration + +### Bitbang +Default driver, the absence of configuration assumes this driver. To configure it, add this to your rules.mk: + +```make +WS2812_DRIVER = bitbang +``` + +!> ARM does not yet support WS2182. Progress is being made, but we are not quite there, yet. + +### I2C +Targeting boards where WS2812 support is offloaded to a 2nd MCU. Currently the driver is limited to AVR given the known consumers are ps2avrGB/BMC. To configure it, add this to your rules.mk: + +```make +WS2812_DRIVER = i2c +``` + +Configure the hardware via your config.h: +```c +#define WS2812_ADDRESS 0xb0 // default: 0xb0 +#define WS2812_TIMEOUT 100 // default: 100 +``` diff --git a/drivers/arm/ws2812.c b/drivers/arm/ws2812.c new file mode 100644 index 000000000..2094e5009 --- /dev/null +++ b/drivers/arm/ws2812.c @@ -0,0 +1 @@ +#error("NOT SUPPORTED") \ No newline at end of file diff --git a/drivers/arm/ws2812_pwm.c b/drivers/arm/ws2812_pwm.c new file mode 100644 index 000000000..2094e5009 --- /dev/null +++ b/drivers/arm/ws2812_pwm.c @@ -0,0 +1 @@ +#error("NOT SUPPORTED") \ No newline at end of file diff --git a/drivers/arm/ws2812_spi.c b/drivers/arm/ws2812_spi.c new file mode 100644 index 000000000..2094e5009 --- /dev/null +++ b/drivers/arm/ws2812_spi.c @@ -0,0 +1 @@ +#error("NOT SUPPORTED") \ No newline at end of file diff --git a/drivers/avr/ws2812_i2c.c b/drivers/avr/ws2812_i2c.c new file mode 100644 index 000000000..8525a026c --- /dev/null +++ b/drivers/avr/ws2812_i2c.c @@ -0,0 +1,31 @@ +#include "ws2812.h" +#include "i2c_master.h" + +#ifndef WS2812_ADDRESS +# define WS2812_ADDRESS 0xb0 +#endif + +#ifndef WS2812_TIMEOUT +# define WS2812_TIMEOUT 100 +#endif + +void ws2812_init(void) { i2c_init(); } + +// Setleds for standard RGB +void ws2812_setleds(LED_TYPE *ledarray, uint16_t leds) { + static bool s_init = false; + if (!s_init) { + ws2812_init(); + s_init = true; + } + + i2c_transmit(WS2812_ADDRESS, (uint8_t *)ledarray, sizeof(LED_TYPE) * leds, WS2812_TIMEOUT); +} + +// Setleds for SK6812RGBW +void ws2812_setleds_rgbw(LED_TYPE *ledarray, uint16_t leds) { +// not supported - for now error out if its enabled +#ifdef RGBW +# error "RGBW not supported" +#endif +} diff --git a/keyboards/jj40/jj40.c b/keyboards/jj40/jj40.c index 26cfa6c06..894ed4907 100644 --- a/keyboards/jj40/jj40.c +++ b/keyboards/jj40/jj40.c @@ -17,40 +17,3 @@ along with this program. If not, see . */ #include "jj40.h" - -#ifdef RGBLIGHT_ENABLE - -#include -#include "i2c_master.h" -#include "rgblight.h" - -extern rgblight_config_t rgblight_config; - -void matrix_init_kb(void) { - i2c_init(); - // call user level keymaps, if any - matrix_init_user(); -} -// custom RGB driver -void rgblight_set(void) { - if (!rgblight_config.enable) { - memset(led, 0, 3 * RGBLED_NUM); - } - - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); -} - -bool rgb_init = false; - -void matrix_scan_kb(void) { - // if LEDs were previously on before poweroff, turn them back on - if (rgb_init == false && rgblight_config.enable) { - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); - rgb_init = true; - } - - rgblight_task(); - matrix_scan_user(); -} - -#endif diff --git a/keyboards/jj40/rules.mk b/keyboards/jj40/rules.mk index b0bf574bd..8e0e8c864 100644 --- a/keyboards/jj40/rules.mk +++ b/keyboards/jj40/rules.mk @@ -40,7 +40,7 @@ SLEEP_LED_ENABLE = no # Breathing sleep LED during USB suspend NKRO_ENABLE = no # USB Nkey Rollover BACKLIGHT_ENABLE = yes # Enable keyboard backlight functionality on B7 by default RGBLIGHT_ENABLE = yes # Enable keyboard RGB underglow -RGBLIGHT_CUSTOM_DRIVER = yes +WS2812_DRIVER = i2c MIDI_ENABLE = no # MIDI support (+2400 to 4200, depending on config) UNICODE_ENABLE = no # Unicode BLUETOOTH_ENABLE = no # Enable Bluetooth with the Adafruit EZ-Key HID @@ -48,6 +48,4 @@ AUDIO_ENABLE = no # Audio output on port C6 FAUXCLICKY_ENABLE = no # Use buzzer to emulate clicky switches HD44780_ENABLE = no # Enable support for HD44780 based LCDs (+400) -SRC += i2c_master.c - LAYOUTS = ortho_4x12 planck_mit diff --git a/quantum/template/ps2avrgb/keyboard.c b/quantum/template/ps2avrgb/keyboard.c index efc851748..73cd668f8 100644 --- a/quantum/template/ps2avrgb/keyboard.c +++ b/quantum/template/ps2avrgb/keyboard.c @@ -16,44 +16,6 @@ #include "%KEYBOARD%.h" -#ifdef RGBLIGHT_ENABLE - -# include -# include "i2c_master.h" -# include "rgblight.h" - -extern rgblight_config_t rgblight_config; - -void matrix_init_kb(void) { - i2c_init(); - // call user level keymaps, if any - matrix_init_user(); -} - -// custom RGB driver -void rgblight_set(void) { - if (!rgblight_config.enable) { - memset(led, 0, 3 * RGBLED_NUM); - } - - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); -} - -bool rgb_init = false; - -void matrix_scan_kb(void) { - // if LEDs were previously on before poweroff, turn them back on - if (rgb_init == false && rgblight_config.enable) { - i2c_transmit(0xb0, (uint8_t*)led, 3 * RGBLED_NUM, 100); - rgb_init = true; - } - - rgblight_task(); - matrix_scan_user(); -} - -#endif - // Optional override functions below. // You can leave any or all of these undefined. // These are only required if you want to perform custom actions. diff --git a/quantum/template/ps2avrgb/rules.mk b/quantum/template/ps2avrgb/rules.mk index bda115db5..ec57b03dc 100644 --- a/quantum/template/ps2avrgb/rules.mk +++ b/quantum/template/ps2avrgb/rules.mk @@ -19,8 +19,6 @@ CONSOLE_ENABLE = yes COMMAND_ENABLE = yes BACKLIGHT_ENABLE = no RGBLIGHT_ENABLE = yes -RGBLIGHT_CUSTOM_DRIVER = yes +WS2812_DRIVER = i2c OPT_DEFS = -DDEBUG_LEVEL=0 - -SRC += i2c_master.c diff --git a/tmk_core/protocol/vusb/main.c b/tmk_core/protocol/vusb/main.c index f8322d94a..8cc736497 100644 --- a/tmk_core/protocol/vusb/main.c +++ b/tmk_core/protocol/vusb/main.c @@ -20,6 +20,11 @@ #include "timer.h" #include "uart.h" #include "debug.h" +#include "rgblight_reconfig.h" + +#if (defined(RGB_MIDI) | defined(RGBLIGHT_ANIMATIONS)) & defined(RGBLIGHT_ENABLE) +# include "rgblight.h" +#endif #define UART_BAUD_RATE 115200 @@ -94,6 +99,10 @@ int main(void) { // To prevent failing to configure NOT scan keyboard during configuration if (usbConfiguration && usbInterruptIsReady()) { keyboard_task(); + +#if defined(RGBLIGHT_ANIMATIONS) && defined(RGBLIGHT_ENABLE) + rgblight_task(); +#endif } vusb_transfer_keyboard(); } -- cgit v1.3 From 4531cc874e1bb8602fede9dc038b692673521590 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sat, 2 Nov 2019 21:20:03 +0000 Subject: Initial migration of software PWM backlight (#6709) * Initial migration of software PWM backlight * First pass at backlight driver docs * Correct driver name in docs * Run backlight_task when using BACKLIGHT_PINS * Resolve backlight docs TODOs --- common_features.mk | 21 +++--- docs/feature_backlight.md | 131 ++++++++++++++++++++++++------------- quantum/backlight/backlight_avr.c | 10 +-- quantum/backlight/backlight_soft.c | 66 +++++++++++++++++++ quantum/quantum.c | 2 +- quantum/quantum.h | 4 +- 6 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 quantum/backlight/backlight_soft.c (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index 7bb9187bb..a1f95955a 100644 --- a/common_features.mk +++ b/common_features.mk @@ -234,7 +234,7 @@ ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes) BACKLIGHT_ENABLE = custom endif -VALID_BACKLIGHT_TYPES := yes custom +VALID_BACKLIGHT_TYPES := yes software custom BACKLIGHT_ENABLE ?= no ifneq ($(strip $(BACKLIGHT_ENABLE)), no) @@ -246,19 +246,22 @@ ifneq ($(strip $(BACKLIGHT_ENABLE)), no) CIE1931_CURVE = yes endif - COMMON_VPATH += $(QUANTUM_DIR)/backlight SRC += $(QUANTUM_DIR)/backlight/backlight.c OPT_DEFS += -DBACKLIGHT_ENABLE - ifeq ($(strip $(BACKLIGHT_ENABLE)), custom) - OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER - endif - - ifeq ($(PLATFORM),AVR) - SRC += $(QUANTUM_DIR)/backlight/backlight_avr.c + ifeq ($(strip $(BACKLIGHT_ENABLE)), software) + SRC += $(QUANTUM_DIR)/backlight/backlight_soft.c else - SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c + ifeq ($(strip $(BACKLIGHT_ENABLE)), custom) + OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER + endif + + ifeq ($(PLATFORM),AVR) + SRC += $(QUANTUM_DIR)/backlight/backlight_avr.c + else + SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c + endif endif endif diff --git a/docs/feature_backlight.md b/docs/feature_backlight.md index 6a2946fd6..71f375594 100644 --- a/docs/feature_backlight.md +++ b/docs/feature_backlight.md @@ -6,14 +6,16 @@ QMK is able to control the brightness of these LEDs by switching them on and off The MCU can only supply so much current to its GPIO pins. Instead of powering the backlight directly from the MCU, the backlight pin is connected to a transistor or MOSFET that switches the power to the LEDs. -## Usage +## Driver configuration Most keyboards have backlighting enabled by default if they support it, but if it is not working for you, check that your `rules.mk` includes the following: -```make -BACKLIGHT_ENABLE = yes +```makefile +BACKLIGHT_ENABLE = software # Valid driver values are 'yes,software,no' ``` +See below for help on individual drivers. + ## Keycodes Once enabled the following keycodes below can be used to change the backlight level. @@ -27,8 +29,54 @@ Once enabled the following keycodes below can be used to change the backlight le |`BL_DEC` |Decrease the backlight level | |`BL_BRTG`|Toggle backlight breathing | +## Backlight Functions + +|Function |Description | +|----------|-----------------------------------------------------------| +|`backlight_toggle()` |Turn the backlight on or off | +|`backlight_enable()` |Turn the backlight on | +|`backlight_disable()` |Turn the backlight off | +|`backlight_step()` |Cycle through backlight levels | +|`backlight_increase()` |Increase the backlight level | +|`backlight_decrease()` |Decrease the backlight level | +|`backlight_level(x)` |Sets the backlight level to specified level | +|`get_backlight_level()` |Return the current backlight level | +|`is_backlight_enabled()`|Return whether the backlight is currently on | + +### Backlight Breathing Functions + +|Function |Description | +|----------|---------------------------------------------------| +|`breathing_toggle()` |Turn the backlight breathing on or off | +|`breathing_enable()` |Turns on backlight breathing | +|`breathing_disable()` |Turns off backlight breathing | + +## Common Driver Configuration + +To change the behavior of the backlighting, `#define` these in your `config.h`: + +|Define |Default |Description | +|---------------------|-------------|--------------------------------------------------------------------------------------| +|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) | +|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | +|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if supported | +|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | +|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low | + +### Backlight On State + +Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*. +Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead. + +This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define. + ## AVR driver +On AVR boards, the default driver currently sniffs the configuration to pick the best scenario. To enable it, add this to your rules.mk: +```makefile +BACKLIGHT_ENABLE = yes +``` + ### Caveats Hardware PWM is supported according to the following table: @@ -63,22 +111,10 @@ When both timers are in use for Audio, the backlight PWM will not use a hardware To change the behavior of the backlighting, `#define` these in your `config.h`: -|Define |Default |Description | -|---------------------|-------------|-------------------------------------------------------------------------------------------------------------| -|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this| -|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information | -|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) | -|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | -|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if supported | -|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | -|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low | - -### Backlight On State - -Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*. -Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead. - -This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define. +|Define |Default |Description | +|---------------------|-------------|--------------------------------------------------------------------------------------------------------------| +|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this | +|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information | ### Multiple backlight pins @@ -103,7 +139,7 @@ In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus th The breathing effect is achieved by registering an interrupt handler for `TIMER1_OVF_vect` that is called whenever the counter resets, roughly 244 times per second. In this handler, the value of an incrementing counter is mapped onto a precomputed brightness curve. To turn off breathing, the interrupt handler is simply disabled, and the brightness reset to the level stored in EEPROM. -### Software PWM Implementation +### Timer Assisted PWM Implementation When `BACKLIGHT_PIN` is not set to a hardware backlight pin, QMK will use a hardware timer configured to trigger software interrupts. This time will count up to `ICRx` (by default `0xFFFF`) before resetting to 0. When resetting to 0, the CPU will fire an OVF (overflow) interrupt that will turn the LEDs on, starting the duty cycle. @@ -114,9 +150,14 @@ The breathing effect is the same as in the hardware PWM implementation. ## ARM Driver +While still in its early stages, ARM backlight support aims to eventually have feature parity with AVR. To enable it, add this to your rules.mk: +```makefile +BACKLIGHT_ENABLE = yes +``` + ### Caveats -Currently only hardware PWM is supported, and does not provide automatic configuration. +Currently only hardware PWM is supported, not timer assisted, and does not provide automatic configuration. ?> STMF072 support is being investigated. @@ -130,30 +171,32 @@ To change the behavior of the backlighting, `#define` these in your `config.h`: |`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use, see ST datasheets for pin to PWM timer mapping. Unless you are designing your own keyboard, you shouldn't need to change this| |`BACKLIGHT_PWM_CHANNEL` |`3` |The PWM channel to use, see ST datasheets for pin to PWM channel mapping. Unless you are designing your own keyboard, you shouldn't need to change this| |`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use, see ST datasheets for pin AF mapping. Unless you are designing your own keyboard, you shouldn't need to change this| -|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) | -|`BACKLIGHT_CAPS_LOCK` |*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) | -|`BACKLIGHT_BREATHING` |*Not defined*|Enable backlight breathing, if supported | -|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | -## Backlight Functions +## Software PWM Driver -|Function |Description | -|----------|-----------------------------------------------------------| -|`backlight_toggle()` |Turn the backlight on or off | -|`backlight_enable()` |Turn the backlight on | -|`backlight_disable()` |Turn the backlight off | -|`backlight_step()` |Cycle through backlight levels | -|`backlight_increase()` |Increase the backlight level | -|`backlight_decrease()` |Decrease the backlight level | -|`backlight_level(x)` |Sets the backlight level, from 0 to | -| |`BACKLIGHT_LEVELS` | -|`get_backlight_level()` |Return the current backlight level | -|`is_backlight_enabled()`|Return whether the backlight is currently on | +Emulation of PWM while running other keyboard tasks, it offers maximum hardware compatibility without extra platform configuration. The tradeoff is the backlight might jitter when the keyboard is busy. To enable, add this to your rules.mk: +```makefile +BACKLIGHT_ENABLE = software +``` -### Backlight Breathing Functions +### Software PWM Configuration + +To change the behavior of the backlighting, `#define` these in your `config.h`: + +|Define |Default |Description | +|-----------------|-------------|-------------------------------------------------------------------------------------------------------------| +|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this| +|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information | -|Function |Description | -|----------|----------------------------------------------------------| -|`breathing_toggle()` |Turn the backlight breathing on or off | -|`breathing_enable()` |Turns on backlight breathing | -|`breathing_disable()` |Turns off backlight breathing | +### Multiple backlight pins + +Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin). +In software PWM, it is possible to define multiple backlight pins. All those pins will be turned on and off at the same time during the PWM duty cycle. +This feature allows to set for instance the Caps Lock LED (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped LCTRL in place of Caps Lock and you need the Caps Lock LED to be part of the backlight instead of being activated when Caps Lock is on. + +To activate multiple backlight pins, you need to add something like this to your user `config.h`: + +```c +#undef BACKLIGHT_PIN +#define BACKLIGHT_PINS { F5, B2 } +``` diff --git a/quantum/backlight/backlight_avr.c b/quantum/backlight/backlight_avr.c index 445698f47..648a37adf 100644 --- a/quantum/backlight/backlight_avr.c +++ b/quantum/backlight/backlight_avr.c @@ -159,7 +159,7 @@ # define BACKLIGHT_ON_STATE 0 # endif -void backlight_on(uint8_t backlight_pin) { +void backlight_on(pin_t backlight_pin) { # if BACKLIGHT_ON_STATE == 0 writePinLow(backlight_pin); # else @@ -167,7 +167,7 @@ void backlight_on(uint8_t backlight_pin) { # endif } -void backlight_off(uint8_t backlight_pin) { +void backlight_off(pin_t backlight_pin) { # if BACKLIGHT_ON_STATE == 0 writePinHigh(backlight_pin); # else @@ -191,16 +191,16 @@ void backlight_off(uint8_t backlight_pin) { # define FOR_EACH_LED(x) \ for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \ - uint8_t backlight_pin = backlight_pins[i]; \ + pin_t backlight_pin = backlight_pins[i]; \ { x } \ } -static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT; +static const pin_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT; # else // full hardware PWM // we support only one backlight pin -static const uint8_t backlight_pin = BACKLIGHT_PIN; +static const pin_t backlight_pin = BACKLIGHT_PIN; # define FOR_EACH_LED(x) x # endif diff --git a/quantum/backlight/backlight_soft.c b/quantum/backlight/backlight_soft.c new file mode 100644 index 000000000..a6aba7782 --- /dev/null +++ b/quantum/backlight/backlight_soft.c @@ -0,0 +1,66 @@ +#include "quantum.h" +#include "backlight.h" + +#if !defined(BACKLIGHT_PIN) && !defined(BACKLIGHT_PINS) +# error "Backlight pin/pins not defined. Please configure." +#endif + +#ifdef BACKLIGHT_BREATHING +# error "Backlight breathing is not available for software PWM. Please disable." +#endif + +#ifndef BACKLIGHT_ON_STATE +# define BACKLIGHT_ON_STATE 0 +#endif + +#ifdef BACKLIGHT_PINS +# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS +#else +# define BACKLIGHT_PIN_INIT \ + { BACKLIGHT_PIN } +#endif + +static const pin_t backlight_pins[] = BACKLIGHT_PIN_INIT; +#define BACKLIGHT_LED_COUNT (sizeof(backlight_pins) / sizeof(pin_t)) + +#define FOR_EACH_LED(x) \ + for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \ + pin_t backlight_pin = backlight_pins[i]; \ + { x } \ + } + +void backlight_on(pin_t backlight_pin) { +#if BACKLIGHT_ON_STATE == 0 + writePinLow(backlight_pin); +#else + writePinHigh(backlight_pin); +#endif +} + +void backlight_off(pin_t backlight_pin) { +#if BACKLIGHT_ON_STATE == 0 + writePinHigh(backlight_pin); +#else + writePinLow(backlight_pin); +#endif +} + +void backlight_init_ports(void) { + // Setup backlight pin as output and output to on state. + FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);) +} + +void backlight_task(void) { + static uint8_t backlight_tick = 0; + + if ((0xFFFF >> (get_backlight_level() * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) { + FOR_EACH_LED(backlight_on(backlight_pin);) + } else { + FOR_EACH_LED(backlight_off(backlight_pin);) + } + backlight_tick = (backlight_tick + 1) % 16; +} + +void backlight_set(uint8_t level) { + // noop as backlight_task uses get_backlight_level() +} diff --git a/quantum/quantum.c b/quantum/quantum.c index d689a9fbf..571dda4c5 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -981,7 +981,7 @@ void matrix_scan_quantum() { #if defined(BACKLIGHT_ENABLE) # if defined(LED_MATRIX_ENABLE) led_matrix_task(); -# elif defined(BACKLIGHT_PIN) +# elif defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS) backlight_task(); # endif #endif diff --git a/quantum/quantum.h b/quantum/quantum.h index f5ac97379..01abe1c0a 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -257,8 +257,8 @@ void tap_code16(uint16_t code); void backlight_init_ports(void); void backlight_task(void); void backlight_task_internal(void); -void backlight_on(uint8_t backlight_pin); -void backlight_off(uint8_t backlight_pin); +void backlight_on(pin_t backlight_pin); +void backlight_off(pin_t backlight_pin); # ifdef BACKLIGHT_BREATHING void breathing_task(void); -- cgit v1.3 From 542cb0a8ce3f324c6bd46751d733daf86384a8f6 Mon Sep 17 00:00:00 2001 From: Drashna Jaelre Date: Mon, 4 Nov 2019 22:59:13 -0800 Subject: [Core] Convert Dynamic Macro to a Core Feature (#5948) * Convert Dynamic Macro to a Core Feature This imports the code from Dynamic Macro into the core code, and handles it, as such. This deprecates the old method but does not remove it, for legacy support. This way, no existing user files need to be touched. Additionally, this reorganizes the documentation to better reflect the changes. Also, it adds user hooks to the feature so users can customize the existing functionality. Based heavily on and closes #2976 * Apply suggestions from code review Co-Authored-By: fauxpark Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Cleanup based on feedback * Add short-form keycodes and document them - add short-form keycodes to quantum/quantum_keycodes.h - document the new aliases in docs/feature_dynamic_macros.md * Add Dynamic Macros section and keycodes to docs/keycodes.md * Make anti-nesting optional * Add documentation for DYNAMIC_MACRO_NO_NESTING option * Fix Merge artifacts * Fix formatting typo in docs Co-Authored-By: James Young <18669334+noroadsleft@users.noreply.github.com> * Remove DYNAMIC_MACRO_RANGE as it's not needed * Fix includes and layer var type --- common_features.mk | 6 +- docs/feature_dynamic_macros.md | 75 +++---- docs/keycodes.md | 10 + quantum/dynamic_macro.h | 20 +- quantum/process_keycode/process_dynamic_macro.c | 257 ++++++++++++++++++++++++ quantum/process_keycode/process_dynamic_macro.h | 41 ++++ quantum/quantum.c | 14 +- quantum/quantum.h | 4 + quantum/quantum_keycodes.h | 16 +- 9 files changed, 384 insertions(+), 59 deletions(-) create mode 100644 quantum/process_keycode/process_dynamic_macro.c create mode 100644 quantum/process_keycode/process_dynamic_macro.h (limited to 'common_features.mk') diff --git a/common_features.mk b/common_features.mk index a1f95955a..e50207b0b 100644 --- a/common_features.mk +++ b/common_features.mk @@ -407,8 +407,12 @@ ifeq ($(strip $(SPACE_CADET_ENABLE)), yes) OPT_DEFS += -DSPACE_CADET_ENABLE endif - ifeq ($(strip $(DIP_SWITCH_ENABLE)), yes) SRC += $(QUANTUM_DIR)/dip_switch.c OPT_DEFS += -DDIP_SWITCH_ENABLE endif + +ifeq ($(strip $(DYNAMIC_MACRO_ENABLE)), yes) + SRC += $(QUANTUM_DIR)/process_keycode/process_dynamic_macro.c + OPT_DEFS += -DDYNAMIC_MACRO_ENABLE +endif diff --git a/docs/feature_dynamic_macros.md b/docs/feature_dynamic_macros.md index 0d11a2864..b86df6c60 100644 --- a/docs/feature_dynamic_macros.md +++ b/docs/feature_dynamic_macros.md @@ -4,51 +4,45 @@ QMK supports temporary macros created on the fly. We call these Dynamic Macros. You can store one or two macros and they may have a combined total of 128 keypresses. You can increase this size at the cost of RAM. -To enable them, first add a new element to the end of your `keycodes` enum — `DYNAMIC_MACRO_RANGE`: +To enable them, first include `DYNAMIC_MACRO_ENABLE = yes` in your `rules.mk`. Then, add the following keys to your keymap: -```c -enum keycodes { - QWERTY = SAFE_RANGE, - COLEMAK, - DVORAK, - PLOVER, - LOWER, - RAISE, - BACKLIT, - EXT_PLV, - DYNAMIC_MACRO_RANGE, -}; -``` +|Key |Alias |Description | +|------------------|----------|---------------------------------------------------| +|`DYN_REC_START1` |`DM_REC1` |Start recording Macro 1 | +|`DYN_REC_START2` |`DM_REC2` |Start recording Macro 2 | +|`DYN_MACRO_PLAY1` |`DM_PLY1` |Replay Macro 1 | +|`DYN_MACRO_PLAY2` |`DM_PLY2` |Replay Macro 2 | +|`DYN_REC_STOP` |`DM_RSTP` |Finish the macro that is currently being recorded. | -Your `keycodes` enum may have a slightly different name. You must add `DYNAMIC_MACRO_RANGE` as the last element because `dynamic_macros.h` will add some more keycodes after it. +That should be everything necessary. -Below it, include the `dynamic_macro.h` header: +To start recording the macro, press either `DYN_REC_START1` or `DYN_REC_START2`. -```c - #include "dynamic_macro.h"` -``` +To finish the recording, press the `DYN_REC_STOP` layer button. -Add the following keys to your keymap: +To replay the macro, press either `DYN_MACRO_PLAY1` or `DYN_MACRO_PLAY2`. -* `DYN_REC_START1` — start recording the macro 1, -* `DYN_REC_START2` — start recording the macro 2, -* `DYN_MACRO_PLAY1` — replay the macro 1, -* `DYN_MACRO_PLAY2` — replay the macro 2, -* `DYN_REC_STOP` — finish the macro that is currently being recorded. +It is possible to replay a macro as part of a macro. It's ok to replay macro 2 while recording macro 1 and vice versa but never create recursive macros i.e. macro 1 that replays macro 1. If you do so and the keyboard will get unresponsive, unplug the keyboard and plug it again. You can disable this completly by defining `DYNAMIC_MACRO_NO_NESTING` in your `config.h` file. -Add the following code to the very beginning of your `process_record_user()` function: +?> For the details about the internals of the dynamic macros, please read the comments in the `process_dynamic_macro.h` and `process_dynamic_macro.c` files. -```c - if (!process_record_dynamic_macro(keycode, record)) { - return false; - } -``` +## Customization -That should be everything necessary. To start recording the macro, press either `DYN_REC_START1` or `DYN_REC_START2`. To finish the recording, press the `DYN_REC_STOP` layer button. To replay the macro, press either `DYN_MACRO_PLAY1` or `DYN_MACRO_PLAY2`. +There are a number of options added that should allow some additional degree of customization -Note that it's possible to replay a macro as part of a macro. It's ok to replay macro 2 while recording macro 1 and vice versa but never create recursive macros i.e. macro 1 that replays macro 1. If you do so and the keyboard will get unresponsive, unplug the keyboard and plug it again. +|Define |Default |Description | +|----------------------------|----------------|-----------------------------------------------------------------------------------------------------------------| +|`DYNAMIC_MACRO_SIZE` |128 |Sets the amount of memory that Dynamic Macros can use. This is a limited resource, dependent on the controller. | +|`DYNAMIC_MACRO_USER_CALL` |*Not defined* |Defining this falls back to using the user `keymap.c` file to trigger the macro behavior. | +|`DYNAMIC_MACRO_NO_NESTING` |*Not Defined* |Defining this disables the ability to call a macro from another macro (nested macros). | -For users of the earlier versions of dynamic macros: It is still possible to finish the macro recording using just the layer modifier used to access the dynamic macro keys, without a dedicated `DYN_REC_STOP` key. If you want this behavior back, use the following snippet instead of the one above: + +If the LEDs start blinking during the recording with each keypress, it means there is no more space for the macro in the macro buffer. To fit the macro in, either make the other macro shorter (they share the same buffer) or increase the buffer size by adding the `DYNAMIC_MACRO_SIZE` define in your `config.h` (default value: 128; please read the comments for it in the header). + + +### DYNAMIC_MACRO_USER_CALL + +For users of the earlier versions of dynamic macros: It is still possible to finish the macro recording using just the layer modifier used to access the dynamic macro keys, without a dedicated `DYN_REC_STOP` key. If you want this behavior back, add `#define DYNAMIC_MACRO_USER_CALL` to your `config.h` and insert the following snippet at the beginning of your `process_record_user()` function: ```c uint16_t macro_kc = (keycode == MO(_DYN) ? DYN_REC_STOP : keycode); @@ -58,6 +52,15 @@ For users of the earlier versions of dynamic macros: It is still possible to fin } ``` -If the LEDs start blinking during the recording with each keypress, it means there is no more space for the macro in the macro buffer. To fit the macro in, either make the other macro shorter (they share the same buffer) or increase the buffer size by setting the `DYNAMIC_MACRO_SIZE` preprocessor macro (default value: 128; please read the comments for it in the header). +### User Hooks + +There are a number of hooks that you can use to add custom functionality and feedback options to Dynamic Macro feature. This allows for some additional degree of customization. + +Note, that direction indicates which macro it is, with `1` being Macro 1, `-1` being Macro 2, and 0 being no macro. + +* `dynamic_macro_record_start_user(void)` - Triggered when you start recording a macro. +* `dynamic_macro_play_user(int8_t direction)` - Triggered when you play back a macro. +* `dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record)` - Triggered on each keypress while recording a macro. +* `dynamic_macro_record_end_user(int8_t direction)` - Triggered when the macro recording is stopped. -For the details about the internals of the dynamic macros, please read the comments in the `dynamic_macro.h` header. +Additionally, you can call `dynamic_macro_led_blink()` to flash the backlights if that feature is enabled. diff --git a/docs/keycodes.md b/docs/keycodes.md index 7dcff03fd..fa01df63d 100644 --- a/docs/keycodes.md +++ b/docs/keycodes.md @@ -297,6 +297,16 @@ This is a reference only. Each group of keys links to the page documenting their |`OUT_USB` |USB only | |`OUT_BT` |Bluetooth only | +## [Dynamic Macros](feature_dynamic_macros.md) + +|Key |Alias |Description | +|-----------------|---------|--------------------------------------------------| +|`DYN_REC_START1` |`DM_REC1`|Start recording Macro 1 | +|`DYN_REC_START2` |`DM_REC2`|Start recording Macro 2 | +|`DYN_MACRO_PLAY1`|`DM_PLY1`|Replay Macro 1 | +|`DYN_MACRO_PLAY2`|`DM_PLY2`|Replay Macro 2 | +|`DYN_REC_STOP` |`DM_RSTP`|Finish the macro that is currently being recorded.| + ## [Layer Switching](feature_advanced_keycodes.md#switching-and-toggling-layers) |Key |Description | diff --git a/quantum/dynamic_macro.h b/quantum/dynamic_macro.h index c7632c004..fe9de6fa6 100644 --- a/quantum/dynamic_macro.h +++ b/quantum/dynamic_macro.h @@ -15,8 +15,10 @@ */ /* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */ -#ifndef DYNAMIC_MACROS_H -#define DYNAMIC_MACROS_H +#pragma once + +/* Warn users that this is now deprecated and they should use the core feature instead. */ +#pragma message "Dynamic Macros is now a core feature. See updated documentation to see how to configure it: https://docs.qmk.fm/#/feature_dynamic_macros" #include "action_layer.h" @@ -33,18 +35,6 @@ # define DYNAMIC_MACRO_SIZE 128 #endif -/* DYNAMIC_MACRO_RANGE must be set as the last element of user's - * "planck_keycodes" enum prior to including this header. This allows - * us to 'extend' it. - */ -enum dynamic_macro_keycodes { - DYN_REC_START1 = DYNAMIC_MACRO_RANGE, - DYN_REC_START2, - DYN_REC_STOP, - DYN_MACRO_PLAY1, - DYN_MACRO_PLAY2, -}; - /* Blink the LEDs to notify the user about some event. */ void dynamic_macro_led_blink(void) { #ifdef BACKLIGHT_ENABLE @@ -272,5 +262,3 @@ bool process_record_dynamic_macro(uint16_t keycode, keyrecord_t *record) { #undef DYNAMIC_MACRO_CURRENT_SLOT #undef DYNAMIC_MACRO_CURRENT_LENGTH #undef DYNAMIC_MACRO_CURRENT_CAPACITY - -#endif diff --git a/quantum/process_keycode/process_dynamic_macro.c b/quantum/process_keycode/process_dynamic_macro.c new file mode 100644 index 000000000..2065f242d --- /dev/null +++ b/quantum/process_keycode/process_dynamic_macro.c @@ -0,0 +1,257 @@ +/* Copyright 2016 Jack Humbert + * Copyright 2019 Drashna Jael're (@drashna, aka Christopher Courtney) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */ +#include "process_dynamic_macro.h" + +// default feedback method +void dynamic_macro_led_blink(void) { +#ifdef BACKLIGHT_ENABLE + backlight_toggle(); + wait_ms(100); + backlight_toggle(); +#endif +} + +/* User hooks for Dynamic Macros */ + +__attribute__((weak)) void dynamic_macro_record_start_user(void) { dynamic_macro_led_blink(); } + +__attribute__((weak)) void dynamic_macro_play_user(int8_t direction) { dynamic_macro_led_blink(); } + +__attribute__((weak)) void dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record) { dynamic_macro_led_blink(); } + +__attribute__((weak)) void dynamic_macro_record_end_user(int8_t direction) { dynamic_macro_led_blink(); } + +/* Convenience macros used for retrieving the debug info. All of them + * need a `direction` variable accessible at the call site. + */ +#define DYNAMIC_MACRO_CURRENT_SLOT() (direction > 0 ? 1 : 2) +#define DYNAMIC_MACRO_CURRENT_LENGTH(BEGIN, POINTER) ((int)(direction * ((POINTER) - (BEGIN)))) +#define DYNAMIC_MACRO_CURRENT_CAPACITY(BEGIN, END2) ((int)(direction * ((END2) - (BEGIN)) + 1)) + +/** + * Start recording of the dynamic macro. + * + * @param[out] macro_pointer The new macro buffer iterator. + * @param[in] macro_buffer The macro buffer used to initialize macro_pointer. + */ +void dynamic_macro_record_start(keyrecord_t **macro_pointer, keyrecord_t *macro_buffer) { + dprintln("dynamic macro recording: started"); + + dynamic_macro_record_start_user(); + + clear_keyboard(); + layer_clear(); + *macro_pointer = macro_buffer; +} + +/** + * Play the dynamic macro. + * + * @param macro_buffer[in] The beginning of the macro buffer being played. + * @param macro_end[in] The element after the last macro buffer element. + * @param direction[in] Either +1 or -1, which way to iterate the buffer. + */ +void dynamic_macro_play(keyrecord_t *macro_buffer, keyrecord_t *macro_end, int8_t direction) { + dprintf("dynamic macro: slot %d playback\n", DYNAMIC_MACRO_CURRENT_SLOT()); + + layer_state_t saved_layer_state = layer_state; + + clear_keyboard(); + layer_clear(); + + while (macro_buffer != macro_end) { + process_record(macro_buffer); + macro_buffer += direction; + } + + clear_keyboard(); + + layer_state = saved_layer_state; + + dynamic_macro_play_user(direction); +} + +/** + * Record a single key in a dynamic macro. + * + * @param macro_buffer[in] The start of the used macro buffer. + * @param macro_pointer[in,out] The current buffer position. + * @param macro2_end[in] The end of the other macro. + * @param direction[in] Either +1 or -1, which way to iterate the buffer. + * @param record[in] The current keypress. + */ +void dynamic_macro_record_key(keyrecord_t *macro_buffer, keyrecord_t **macro_pointer, keyrecord_t *macro2_end, int8_t direction, keyrecord_t *record) { + /* If we've just started recording, ignore all the key releases. */ + if (!record->event.pressed && *macro_pointer == macro_buffer) { + dprintln("dynamic macro: ignoring a leading key-up event"); + return; + } + + /* The other end of the other macro is the last buffer element it + * is safe to use before overwriting the other macro. + */ + if (*macro_pointer - direction != macro2_end) { + **macro_pointer = *record; + *macro_pointer += direction; + } else { + dynamic_macro_record_key_user(direction, record); + } + + dprintf("dynamic macro: slot %d length: %d/%d\n", DYNAMIC_MACRO_CURRENT_SLOT(), DYNAMIC_MACRO_CURRENT_LENGTH(macro_buffer, *macro_pointer), DYNAMIC_MACRO_CURRENT_CAPACITY(macro_buffer, macro2_end)); +} + +/** + * End recording of the dynamic macro. Essentially just update the + * pointer to the end of the macro. + */ +void dynamic_macro_record_end(keyrecord_t *macro_buffer, keyrecord_t *macro_pointer, int8_t direction, keyrecord_t **macro_end) { + dynamic_macro_record_end_user(direction); + + /* Do not save the keys being held when stopping the recording, + * i.e. the keys used to access the layer DYN_REC_STOP is on. + */ + while (macro_pointer != macro_buffer && (macro_pointer - direction)->event.pressed) { + dprintln("dynamic macro: trimming a trailing key-down event"); + macro_pointer -= direction; + } + + dprintf("dynamic macro: slot %d saved, length: %d\n", DYNAMIC_MACRO_CURRENT_SLOT(), DYNAMIC_MACRO_CURRENT_LENGTH(macro_buffer, macro_pointer)); + + *macro_end = macro_pointer; +} + +/* Handle the key events related to the dynamic macros. Should be + * called from process_record_user() like this: + * + * bool process_record_user(uint16_t keycode, keyrecord_t *record) { + * if (!process_record_dynamic_macro(keycode, record)) { + * return false; + * } + * <...THE REST OF THE FUNCTION...> + * } + */ +bool process_dynamic_macro(uint16_t keycode, keyrecord_t *record) { + /* Both macros use the same buffer but read/write on different + * ends of it. + * + * Macro1 is written left-to-right starting from the beginning of + * the buffer. + * + * Macro2 is written right-to-left starting from the end of the + * buffer. + * + * ¯o_buffer macro_end + * v v + * +------------------------------------------------------------+ + * |>>>>>> MACRO1 >>>>>> <<<<<<<<<<<<< MACRO2 <<<<<<<<<<<<<| + * +------------------------------------------------------------+ + * ^ ^ + * r_macro_end r_macro_buffer + * + * During the recording when one macro encounters the end of the + * other macro, the recording is stopped. Apart from this, there + * are no arbitrary limits for the macros' length in relation to + * each other: for example one can either have two medium sized + * macros or one long macro and one short macro. Or even one empty + * and one using the whole buffer. + */ + static keyrecord_t macro_buffer[DYNAMIC_MACRO_SIZE]; + + /* Pointer to the first buffer element after the first macro. + * Initially points to the very beginning of the buffer since the + * macro is empty. */ + static keyrecord_t *macro_end = macro_buffer; + + /* The other end of the macro buffer. Serves as the beginning of + * the second macro. */ + static keyrecord_t *const r_macro_buffer = macro_buffer + DYNAMIC_MACRO_SIZE - 1; + + /* Like macro_end but for the second macro. */ + static keyrecord_t *r_macro_end = r_macro_buffer; + + /* A persistent pointer to the current macro position (iterator) + * used during the recording. */ + static keyrecord_t *macro_pointer = NULL; + + /* 0 - no macro is being recorded right now + * 1,2 - either macro 1 or 2 is being recorded */ + static uint8_t macro_id = 0; + + if (macro_id == 0) { + /* No macro recording in progress. */ + if (!record->event.pressed) { + switch (keycode) { + case DYN_REC_START1: + dynamic_macro_record_start(¯o_pointer, macro_buffer); + macro_id = 1; + return false; + case DYN_REC_START2: + dynamic_macro_record_start(¯o_pointer, r_macro_buffer); + macro_id = 2; + return false; + case DYN_MACRO_PLAY1: + dynamic_macro_play(macro_buffer, macro_end, +1); + return false; + case DYN_MACRO_PLAY2: + dynamic_macro_play(r_macro_buffer, r_macro_end, -1); + return false; + } + } + } else { + /* A macro is being recorded right now. */ + switch (keycode) { + case DYN_REC_STOP: + /* Stop the macro recording. */ + if (record->event.pressed) { /* Ignore the initial release + * just after the recoding + * starts. */ + switch (macro_id) { + case 1: + dynamic_macro_record_end(macro_buffer, macro_pointer, +1, ¯o_end); + break; + case 2: + dynamic_macro_record_end(r_macro_buffer, macro_pointer, -1, &r_macro_end); + break; + } + macro_id = 0; + } + return false; +#ifdef DYNAMIC_MACRO_NO_NESTING + case DYN_MACRO_PLAY1: + case DYN_MACRO_PLAY2: + dprintln("dynamic macro: ignoring macro play key while recording"); + return false; +#endif + default: + /* Store the key in the macro buffer and process it normally. */ + switch (macro_id) { + case 1: + dynamic_macro_record_key(macro_buffer, ¯o_pointer, r_macro_end, +1, record); + break; + case 2: + dynamic_macro_record_key(r_macro_buffer, ¯o_pointer, macro_end, -1, record); + break; + } + return true; + break; + } + } + + return true; +} diff --git a/quantum/process_keycode/process_dynamic_macro.h b/quantum/process_keycode/process_dynamic_macro.h new file mode 100644 index 000000000..39036541b --- /dev/null +++ b/quantum/process_keycode/process_dynamic_macro.h @@ -0,0 +1,41 @@ +/* Copyright 2016 Jack Humbert + * Copyright 2019 Drashna Jael're (@drashna, aka Christopher Courtney) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* Author: Wojciech Siewierski < wojciech dot siewierski at onet dot pl > */ +#pragma once + +#include "quantum.h" + +/* May be overridden with a custom value. Be aware that the effective + * macro length is half of this value: each keypress is recorded twice + * because of the down-event and up-event. This is not a bug, it's the + * intended behavior. + * + * Usually it should be fine to set the macro size to at least 256 but + * there have been reports of it being too much in some users' cases, + * so 128 is considered a safe default. + */ +#ifndef DYNAMIC_MACRO_SIZE +# define DYNAMIC_MACRO_SIZE 128 +#endif + +void dynamic_macro_led_blink(void); +bool process_dynamic_macro(uint16_t keycode, keyrecord_t *record); +void dynamic_macro_record_start_user(void); +void dynamic_macro_play_user(int8_t direction); +void dynamic_macro_record_key_user(int8_t direction, keyrecord_t *record); +void dynamic_macro_record_end_user(int8_t direction); diff --git a/quantum/quantum.c b/quantum/quantum.c index 571dda4c5..1f17c6ff7 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -26,7 +26,7 @@ #ifdef BACKLIGHT_ENABLE # include "backlight.h" - extern backlight_config_t backlight_config; +extern backlight_config_t backlight_config; #endif #ifdef FAUXCLICKY_ENABLE @@ -89,7 +89,7 @@ static void do_code16(uint16_t code, void (*f)(uint8_t)) { uint8_t mods_to_send = 0; - if (code & QK_RMODS_MIN) { // Right mod flag is set + if (code & QK_RMODS_MIN) { // Right mod flag is set if (code & QK_LCTL) mods_to_send |= MOD_BIT(KC_RCTL); if (code & QK_LSFT) mods_to_send |= MOD_BIT(KC_RSFT); if (code & QK_LALT) mods_to_send |= MOD_BIT(KC_RALT); @@ -222,6 +222,10 @@ bool process_record_quantum(keyrecord_t *record) { // Must run first to be able to mask key_up events. process_key_lock(&keycode, record) && #endif +#if defined(DYNAMIC_MACRO_ENABLE) && !defined(DYNAMIC_MACRO_USER_CALL) + // Must run asap to ensure all keypresses are recorded. + process_dynamic_macro(keycode, record) && +#endif #if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY) process_clicky(keycode, record) && #endif // AUDIO_CLICKY @@ -563,7 +567,7 @@ bool process_record_quantum(keyrecord_t *record) { keymap_config.swap_backslash_backspace = true; break; case MAGIC_HOST_NKRO: - clear_keyboard(); // clear first buffer to prevent stuck keys + clear_keyboard(); // clear first buffer to prevent stuck keys keymap_config.nkro = true; break; case MAGIC_SWAP_ALT_GUI: @@ -606,7 +610,7 @@ bool process_record_quantum(keyrecord_t *record) { keymap_config.swap_backslash_backspace = false; break; case MAGIC_UNHOST_NKRO: - clear_keyboard(); // clear first buffer to prevent stuck keys + clear_keyboard(); // clear first buffer to prevent stuck keys keymap_config.nkro = false; break; case MAGIC_UNSWAP_ALT_GUI: @@ -644,7 +648,7 @@ bool process_record_quantum(keyrecord_t *record) { #endif break; case MAGIC_TOGGLE_NKRO: - clear_keyboard(); // clear first buffer to prevent stuck keys + clear_keyboard(); // clear first buffer to prevent stuck keys keymap_config.nkro = !keymap_config.nkro; break; case MAGIC_EE_HANDS_LEFT: diff --git a/quantum/quantum.h b/quantum/quantum.h index 01abe1c0a..87343a15d 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -149,6 +149,10 @@ extern layer_state_t layer_state; #include "dip_switch.h" #endif +#ifdef DYNAMIC_MACRO_ENABLE + #include "process_dynamic_macro.h" +#endif + // Function substitutions to ease GPIO manipulation #if defined(__AVR__) diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index 5fac6a5ca..51a7e290f 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -505,6 +505,13 @@ enum quantum_keycodes { MAGIC_EE_HANDS_LEFT, MAGIC_EE_HANDS_RIGHT, + // Dynamic Macros + DYN_REC_START1, + DYN_REC_START2, + DYN_REC_STOP, + DYN_MACRO_PLAY1, + DYN_MACRO_PLAY2, + // always leave at the end SAFE_RANGE }; @@ -757,4 +764,11 @@ enum quantum_keycodes { # define SH_OFF (QK_SWAP_HANDS | OP_SH_OFF) #endif -#endif // QUANTUM_KEYCODES_H +// Dynamic Macros aliases +#define DM_REC1 DYN_REC_START1 +#define DM_REC2 DYN_REC_START2 +#define DM_RSTP DYN_REC_STOP +#define DM_PLY1 DYN_MACRO_PLAY1 +#define DM_PLY2 DYN_MACRO_PLAY2 + +#endif // QUANTUM_KEYCODES_H -- cgit v1.3