aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Challis <git@zvecr.com>2019-10-05 16:57:00 +0100
committerGitHub <noreply@github.com>2019-10-05 16:57:00 +0100
commit38aefaf78e3d9f17ef561f031679a02c9fba869c (patch)
tree9de5fc423f20d27daedeee0500faad57ce8712a2
parent60b2a9a5ea88321629e970e936652cc1ba786b80 (diff)
downloadqmk_firmware-38aefaf78e3d9f17ef561f031679a02c9fba869c.tar.gz
qmk_firmware-38aefaf78e3d9f17ef561f031679a02c9fba869c.zip
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
-rw-r--r--common_features.mk23
-rw-r--r--docs/feature_backlight.md50
-rw-r--r--keyboards/handwired/onekey/bluepill/config.h4
-rw-r--r--keyboards/handwired/onekey/bluepill/halconf.h2
-rw-r--r--keyboards/handwired/onekey/bluepill/mcuconf.h6
-rw-r--r--keyboards/handwired/onekey/bluepill/rules.mk6
-rw-r--r--keyboards/handwired/onekey/config.h2
-rw-r--r--keyboards/handwired/onekey/keymaps/backlight/config.h3
-rw-r--r--keyboards/handwired/onekey/keymaps/backlight/keymap.c40
-rw-r--r--keyboards/handwired/onekey/keymaps/backlight/rules.mk2
-rw-r--r--keyboards/handwired/onekey/proton_c/config.h5
-rw-r--r--keyboards/handwired/onekey/stm32f0_disco/config.h5
-rw-r--r--quantum/backlight/backlight.c1
-rw-r--r--quantum/backlight/backlight_arm.c218
-rw-r--r--quantum/backlight/backlight_avr.c509
-rw-r--r--quantum/quantum.c509
-rw-r--r--tmk_core/common/backlight.h4
17 files changed, 860 insertions, 529 deletions
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)
229 CIE1931_CURVE = yes 229 CIE1931_CURVE = yes
230endif 230endif
231 231
232ifeq ($(strip $(BACKLIGHT_ENABLE)), yes) 232# backward compat
233ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes)
234 BACKLIGHT_ENABLE = custom
235endif
236
237VALID_BACKLIGHT_TYPES := yes custom
238
239BACKLIGHT_ENABLE ?= no
240ifneq ($(strip $(BACKLIGHT_ENABLE)), no)
241 ifeq ($(filter $(BACKLIGHT_ENABLE),$(VALID_BACKLIGHT_TYPES)),)
242 $(error BACKLIGHT_ENABLE="$(BACKLIGHT_ENABLE)" is not a valid backlight type)
243 endif
244
233 ifeq ($(strip $(VISUALIZER_ENABLE)), yes) 245 ifeq ($(strip $(VISUALIZER_ENABLE)), yes)
234 CIE1931_CURVE = yes 246 CIE1931_CURVE = yes
235 endif 247 endif
236 ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes) 248
249 ifeq ($(strip $(BACKLIGHT_ENABLE)), custom)
237 OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER 250 OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER
238 endif 251 endif
252
253 ifeq ($(PLATFORM),AVR)
254 SRC += $(QUANTUM_DIR)/backlight/backlight_avr.c
255 else
256 SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c
257 endif
239endif 258endif
240 259
241ifeq ($(strip $(CIE1931_CURVE)), yes) 260ifeq ($(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 @@
1# Backlighting 1# Backlighting
2 2
3Many 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. 3Many 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.
4
5QMK 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.
4 6
5The 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. 7The 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.
6 8
@@ -12,9 +14,8 @@ Most keyboards have backlighting enabled by default if they support it, but if i
12BACKLIGHT_ENABLE = yes 14BACKLIGHT_ENABLE = yes
13``` 15```
14 16
15You should then be able to use the keycodes below to change the backlight level.
16
17## Keycodes 17## Keycodes
18Once enabled the following keycodes below can be used to change the backlight level.
18 19
19|Key |Description | 20|Key |Description |
20|---------|------------------------------------------| 21|---------|------------------------------------------|
@@ -26,9 +27,9 @@ You should then be able to use the keycodes below to change the backlight level.
26|`BL_DEC` |Decrease the backlight level | 27|`BL_DEC` |Decrease the backlight level |
27|`BL_BRTG`|Toggle backlight breathing | 28|`BL_BRTG`|Toggle backlight breathing |
28 29
29## Caveats 30## AVR driver
30 31
31This 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. 32### Caveats
32 33
33Hardware PWM is supported according to the following table: 34Hardware PWM is supported according to the following table:
34 35
@@ -58,9 +59,9 @@ All other pins will use software PWM. If the [Audio](feature_audio.md) feature i
58 59
59When 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. 60When 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.
60 61
61## Configuration 62### AVR Configuration
62 63
63To change the behaviour of the backlighting, `#define` these in your `config.h`: 64To change the behavior of the backlighting, `#define` these in your `config.h`:
64 65
65|Define |Default |Description | 66|Define |Default |Description |
66|---------------------|-------------|-------------------------------------------------------------------------------------------------------------| 67|---------------------|-------------|-------------------------------------------------------------------------------------------------------------|
@@ -72,14 +73,14 @@ To change the behaviour of the backlighting, `#define` these in your `config.h`:
72|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds | 73|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
73|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low | 74|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low |
74 75
75## Backlight On State 76### Backlight On State
76 77
77Most 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*. 78Most 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*.
78Sometimes, 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. 79Sometimes, 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.
79 80
80This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define. 81This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define.
81 82
82## Multiple backlight pins 83### Multiple backlight pins
83 84
84Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin). 85Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin).
85In 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. 86In 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
87 88
88To activate multiple backlight pins, you need to add something like this to your user `config.h`: 89To activate multiple backlight pins, you need to add something like this to your user `config.h`:
89 90
90~~~c 91```c
91#define BACKLIGHT_LED_COUNT 2 92#define BACKLIGHT_LED_COUNT 2
92#undef BACKLIGHT_PIN 93#undef BACKLIGHT_PIN
93#define BACKLIGHT_PINS { F5, B2 } 94#define BACKLIGHT_PINS { F5, B2 }
94~~~ 95```
95 96
96## Hardware PWM Implementation 97### Hardware PWM Implementation
97 98
98When 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. 99When 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.
99The 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. 100The 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
102The 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. 103The 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.
103In 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. 104In 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.
104 105
105## Software PWM Implementation 106### Software PWM Implementation
106 107
107When `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. 108When `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.
108When resetting to 0, the CPU will fire an OVF (overflow) interrupt that will turn the LEDs on, starting the duty cycle. 109When 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
111 112
112The breathing effect is the same as in the hardware PWM implementation. 113The breathing effect is the same as in the hardware PWM implementation.
113 114
115## ARM Driver
116
117### Caveats
118
119Currently only hardware PWM is supported, and does not provide automatic configuration.
120
121?> STMF072 support is being investigated.
122
123### ARM Configuration
124
125To change the behavior of the backlighting, `#define` these in your `config.h`:
126
127|Define |Default |Description |
128|------------------------|-------------|-------------------------------------------------------------------------------------------------------------|
129|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this|
130|`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|
131|`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|
132|`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|
133|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) |
134|`BACKLIGHT_CAPS_LOCK` |*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) |
135|`BACKLIGHT_BREATHING` |*Not defined*|Enable backlight breathing, if supported |
136|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
137
114## Backlight Functions 138## Backlight Functions
115 139
116|Function |Description | 140|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 @@
21#define MATRIX_COL_PINS { B0 } 21#define MATRIX_COL_PINS { B0 }
22#define MATRIX_ROW_PINS { A7 } 22#define MATRIX_ROW_PINS { A7 }
23#define UNUSED_PINS 23#define UNUSED_PINS
24
25#define BACKLIGHT_PIN A0
26#define BACKLIGHT_PWM_DRIVER PWMD2
27#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 @@
146 * @brief Enables the SPI subsystem. 146 * @brief Enables the SPI subsystem.
147 */ 147 */
148#if !defined(HAL_USE_SPI) || defined(__DOXYGEN__) 148#if !defined(HAL_USE_SPI) || defined(__DOXYGEN__)
149#define HAL_USE_SPI TRUE 149#define HAL_USE_SPI FALSE
150#endif 150#endif
151 151
152/** 152/**
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 @@
132 * PWM driver system settings. 132 * PWM driver system settings.
133 */ 133 */
134#define STM32_PWM_USE_ADVANCED FALSE 134#define STM32_PWM_USE_ADVANCED FALSE
135#define STM32_PWM_USE_TIM1 TRUE 135#define STM32_PWM_USE_TIM1 FALSE
136#define STM32_PWM_USE_TIM2 FALSE 136#define STM32_PWM_USE_TIM2 TRUE
137#define STM32_PWM_USE_TIM3 FALSE 137#define STM32_PWM_USE_TIM3 FALSE
138#define STM32_PWM_USE_TIM4 FALSE 138#define STM32_PWM_USE_TIM4 FALSE
139#define STM32_PWM_USE_TIM5 FALSE 139#define STM32_PWM_USE_TIM5 FALSE
@@ -168,7 +168,7 @@
168 * SPI driver system settings. 168 * SPI driver system settings.
169 */ 169 */
170#define STM32_SPI_USE_SPI1 FALSE 170#define STM32_SPI_USE_SPI1 FALSE
171#define STM32_SPI_USE_SPI2 TRUE 171#define STM32_SPI_USE_SPI2 FALSE
172#define STM32_SPI_USE_SPI3 FALSE 172#define STM32_SPI_USE_SPI3 FALSE
173#define STM32_SPI_SPI1_DMA_PRIORITY 1 173#define STM32_SPI_SPI1_DMA_PRIORITY 1
174#define STM32_SPI_SPI2_DMA_PRIORITY 1 174#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 @@
1# GENERIC STM32F103C8T6 board - stm32duino bootloader 1# GENERIC STM32F103C8T6 board - stm32duino bootloader
2BOARD = GENERIC_STM32_F103
3
2OPT_DEFS = -DCORTEX_VTOR_INIT=0x2000 4OPT_DEFS = -DCORTEX_VTOR_INIT=0x2000
3MCU_LDSCRIPT = STM32F103x8_stm32duino_bootloader 5MCU_LDSCRIPT = STM32F103x8_stm32duino_bootloader
4BOARD = GENERIC_STM32_F103 6
7DFU_ARGS = -d 1eaf:0003 -a2 -R
8DFU_SUFFIX_ARGS ?= -v 1eaf -p 0003
5 9
6# OPT_DEFS = 10# OPT_DEFS =
7# MCU_LDSCRIPT = STM32F103x8 11# 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 <http://www.gnu.org/licenses/>.
35/* Set 0 if debouncing isn't needed */ 35/* Set 0 if debouncing isn't needed */
36#define DEBOUNCE 5 36#define DEBOUNCE 5
37 37
38#define TAPPING_TERM 500
39
38/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ 40/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */
39#define LOCKING_SUPPORT_ENABLE 41#define LOCKING_SUPPORT_ENABLE
40/* Locking resynchronize hack */ 42/* 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 @@
1#pragma once
2
3#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 @@
1#include QMK_KEYBOARD_H
2
3//Tap Dance Declarations
4enum {
5 TD_BL = 0
6};
7
8void dance_cln_finished (qk_tap_dance_state_t *state, void *user_data) {
9 // noop
10}
11
12void dance_cln_reset (qk_tap_dance_state_t *state, void *user_data) {
13 switch (state->count) {
14 case 1:
15 // single tap - step through backlight
16 backlight_step();
17 break;
18#ifdef BACKLIGHT_BREATHING
19 case 2:
20 // double tap - toggle breathing
21 breathing_toggle();
22 break;
23 case 3:
24 //tripple tap - do some pulse stuff
25 breathing_pulse();
26 break;
27#endif
28 default:
29 // more - nothing
30 break;
31 }
32}
33
34qk_tap_dance_action_t tap_dance_actions[] = {
35 [TD_BL] = ACTION_TAP_DANCE_FN_ADVANCED (NULL, dance_cln_finished, dance_cln_reset)
36};
37
38const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
39 LAYOUT( TD(TD_BL) )
40};
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 @@
1BACKLIGHT_ENABLE = yes
2TAP_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 @@
21#define MATRIX_COL_PINS { A3 } 21#define MATRIX_COL_PINS { A3 }
22#define MATRIX_ROW_PINS { A2 } 22#define MATRIX_ROW_PINS { A2 }
23#define UNUSED_PINS 23#define UNUSED_PINS
24
25#define BACKLIGHT_PIN B8
26#define BACKLIGHT_PWM_DRIVER PWMD4
27#define BACKLIGHT_PWM_CHANNEL 3
28#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 @@
21#define MATRIX_COL_PINS { B4 } 21#define MATRIX_COL_PINS { B4 }
22#define MATRIX_ROW_PINS { B5 } 22#define MATRIX_ROW_PINS { B5 }
23#define UNUSED_PINS 23#define UNUSED_PINS
24
25#define BACKLIGHT_PIN C8
26#define BACKLIGHT_PWM_DRIVER PWMD3
27#define BACKLIGHT_PWM_CHANNEL 3
28#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 @@
1#include "quantum.h"
2#include "backlight.h"
3#include <hal.h>
4#include "debug.h"
5
6// TODO: remove short term bodge when refactoring BACKLIGHT_CUSTOM_DRIVER out
7#ifdef BACKLIGHT_PIN
8
9# if defined(STM32F0XX) || defined(STM32F0xx)
10# error "Backlight support for STMF072 is not available. Please disable."
11# endif
12
13# if defined(STM32F1XX) || defined(STM32F1xx)
14# define USE_GPIOV1
15# endif
16
17// GPIOV2 && GPIOV3
18# ifndef BACKLIGHT_PAL_MODE
19# define BACKLIGHT_PAL_MODE 2
20# endif
21
22// GENERIC
23# ifndef BACKLIGHT_PWM_DRIVER
24# define BACKLIGHT_PWM_DRIVER PWMD4
25# endif
26# ifndef BACKLIGHT_PWM_CHANNEL
27# define BACKLIGHT_PWM_CHANNEL 3
28# endif
29
30static void breathing_callback(PWMDriver *pwmp);
31
32static PWMConfig pwmCFG = {0xFFFF, /* PWM clock frequency */
33 256, /* PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
34 NULL, /* No Callback */
35 { /* Default all channels to disabled - Channels will be configured durring init */
36 {PWM_OUTPUT_DISABLED, NULL},
37 {PWM_OUTPUT_DISABLED, NULL},
38 {PWM_OUTPUT_DISABLED, NULL},
39 {PWM_OUTPUT_DISABLED, NULL}},
40 0, /* HW dependent part.*/
41 0};
42
43static PWMConfig pwmCFG_breathing = {0xFFFF, /** PWM clock frequency */
44 256, /* PWM period (in ticks) 1S (1/10kHz=0.1mS 0.1ms*10000 ticks=1S) */
45 breathing_callback, /* Breathing Callback */
46 { /* Default all channels to disabled - Channels will be configured durring init */
47 {PWM_OUTPUT_DISABLED, NULL},
48 {PWM_OUTPUT_DISABLED, NULL},
49 {PWM_OUTPUT_DISABLED, NULL},
50 {PWM_OUTPUT_DISABLED, NULL}},
51 0, /* HW dependent part.*/
52 0};
53
54// See http://jared.geek.nz/2013/feb/linear-led-pwm
55static uint16_t cie_lightness(uint16_t v) {
56 if (v <= 5243) // if below 8% of max
57 return v / 9; // same as dividing by 900%
58 else {
59 uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare
60 // to get a useful result with integer division, we shift left in the expression above
61 // and revert what we've done again after squaring.
62 y = y * y * y >> 8;
63 if (y > 0xFFFFUL) // prevent overflow
64 return 0xFFFFU;
65 else
66 return (uint16_t)y;
67 }
68}
69
70void backlight_init_ports(void) {
71 // printf("backlight_init_ports()\n");
72
73# ifdef USE_GPIOV1
74 palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_STM32_ALTERNATE_PUSHPULL);
75# else
76 palSetPadMode(PAL_PORT(BACKLIGHT_PIN), PAL_PAD(BACKLIGHT_PIN), PAL_MODE_ALTERNATE(BACKLIGHT_PAL_MODE));
77# endif
78
79 pwmCFG.channels[BACKLIGHT_PWM_CHANNEL - 1].mode = PWM_OUTPUT_ACTIVE_HIGH;
80 pwmCFG_breathing.channels[BACKLIGHT_PWM_CHANNEL - 1].mode = PWM_OUTPUT_ACTIVE_HIGH;
81 pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG);
82
83 backlight_set(get_backlight_level());
84 if (is_backlight_breathing()) {
85 breathing_enable();
86 }
87}
88
89void backlight_set(uint8_t level) {
90 // printf("backlight_set(%d)\n", level);
91 if (level == 0) {
92 // Turn backlight off
93 pwmDisableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1);
94 } else {
95 // Turn backlight on
96 if (!is_breathing()) {
97 uint32_t duty = (uint32_t)(cie_lightness(0xFFFF * (uint32_t)level / BACKLIGHT_LEVELS));
98 // printf("duty: (%d)\n", duty);
99 pwmEnableChannel(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty));
100 }
101 }
102}
103
104uint8_t backlight_tick = 0;
105
106void backlight_task(void) {}
107
108# define BREATHING_NO_HALT 0
109# define BREATHING_HALT_OFF 1
110# define BREATHING_HALT_ON 2
111# define BREATHING_STEPS 128
112
113static uint8_t breathing_period = BREATHING_PERIOD;
114static uint8_t breathing_halt = BREATHING_NO_HALT;
115static uint16_t breathing_counter = 0;
116
117bool is_breathing(void) { return BACKLIGHT_PWM_DRIVER.config == &pwmCFG_breathing; }
118
119static inline void breathing_min(void) { breathing_counter = 0; }
120
121static inline void breathing_max(void) { breathing_counter = breathing_period * 256 / 2; }
122
123void breathing_interrupt_enable(void) {
124 pwmStop(&BACKLIGHT_PWM_DRIVER);
125 pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG_breathing);
126 chSysLockFromISR();
127 pwmEnablePeriodicNotification(&BACKLIGHT_PWM_DRIVER);
128 pwmEnableChannelI(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, 0xFFFF));
129 chSysUnlockFromISR();
130}
131
132void breathing_interrupt_disable(void) {
133 pwmStop(&BACKLIGHT_PWM_DRIVER);
134 pwmStart(&BACKLIGHT_PWM_DRIVER, &pwmCFG);
135}
136
137void breathing_enable(void) {
138 breathing_counter = 0;
139 breathing_halt = BREATHING_NO_HALT;
140 breathing_interrupt_enable();
141}
142
143void breathing_pulse(void) {
144 if (get_backlight_level() == 0)
145 breathing_min();
146 else
147 breathing_max();
148 breathing_halt = BREATHING_HALT_ON;
149 breathing_interrupt_enable();
150}
151
152void breathing_disable(void) {
153 // printf("breathing_disable()\n");
154 breathing_interrupt_disable();
155 // Restore backlight level
156 backlight_set(get_backlight_level());
157}
158
159void breathing_self_disable(void) {
160 if (get_backlight_level() == 0)
161 breathing_halt = BREATHING_HALT_OFF;
162 else
163 breathing_halt = BREATHING_HALT_ON;
164}
165
166void breathing_toggle(void) {
167 if (is_breathing())
168 breathing_disable();
169 else
170 breathing_enable();
171}
172
173void breathing_period_set(uint8_t value) {
174 if (!value) value = 1;
175 breathing_period = value;
176}
177
178void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); }
179
180void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); }
181
182void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); }
183
184/* To generate breathing curve in python:
185 * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)]
186 */
187static 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};
188
189// Use this before the cie_lightness function.
190static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); }
191
192static void breathing_callback(PWMDriver *pwmp) {
193 (void)pwmp;
194 uint16_t interval = (uint16_t)breathing_period * 256 / BREATHING_STEPS;
195 // resetting after one period to prevent ugly reset at overflow.
196 breathing_counter = (breathing_counter + 1) % (breathing_period * 256);
197 uint8_t index = breathing_counter / interval % BREATHING_STEPS;
198
199 if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) {
200 breathing_interrupt_disable();
201 }
202
203 uint32_t duty = cie_lightness(scale_backlight(breathing_table[index] * 256));
204
205 chSysLockFromISR();
206 pwmEnableChannelI(&BACKLIGHT_PWM_DRIVER, BACKLIGHT_PWM_CHANNEL - 1, PWM_FRACTION_TO_WIDTH(&BACKLIGHT_PWM_DRIVER, 0xFFFF, duty));
207 chSysUnlockFromISR();
208}
209
210#else
211
212__attribute__((weak)) void backlight_init_ports(void) {}
213
214__attribute__((weak)) void backlight_set(uint8_t level) {}
215
216__attribute__((weak)) void backlight_task(void) {}
217
218#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 @@
1#include "quantum.h"
2#include "backlight.h"
3#include "debug.h"
4
5#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS))
6
7// This logic is a bit complex, we support 3 setups:
8//
9// 1. Hardware PWM when backlight is wired to a PWM pin.
10// Depending on this pin, we use a different output compare unit.
11// 2. Software PWM with hardware timers, but the used timer
12// depends on the Audio setup (Audio wins over Backlight).
13// 3. Full software PWM, driven by the matrix scan, if both timers are used by Audio.
14
15# 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)
16# define HARDWARE_PWM
17# define ICRx ICR1
18# define TCCRxA TCCR1A
19# define TCCRxB TCCR1B
20# define TIMERx_OVF_vect TIMER1_OVF_vect
21# define TIMSKx TIMSK1
22# define TOIEx TOIE1
23
24# if BACKLIGHT_PIN == B5
25# define COMxx1 COM1A1
26# define OCRxx OCR1A
27# elif BACKLIGHT_PIN == B6
28# define COMxx1 COM1B1
29# define OCRxx OCR1B
30# elif BACKLIGHT_PIN == B7
31# define COMxx1 COM1C1
32# define OCRxx OCR1C
33# endif
34# 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)
35# define HARDWARE_PWM
36# define ICRx ICR3
37# define TCCRxA TCCR3A
38# define TCCRxB TCCR3B
39# define TIMERx_OVF_vect TIMER3_OVF_vect
40# define TIMSKx TIMSK3
41# define TOIEx TOIE3
42
43# if BACKLIGHT_PIN == C4
44# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
45# error This MCU has no C4 pin!
46# else
47# define COMxx1 COM3C1
48# define OCRxx OCR3C
49# endif
50# elif BACKLIGHT_PIN == C5
51# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
52# error This MCU has no C5 pin!
53# else
54# define COMxx1 COM3B1
55# define OCRxx OCR3B
56# endif
57# elif BACKLIGHT_PIN == C6
58# define COMxx1 COM3A1
59# define OCRxx OCR3A
60# endif
61# elif (defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6)
62# define HARDWARE_PWM
63# define ICRx ICR1
64# define TCCRxA TCCR1A
65# define TCCRxB TCCR1B
66# define TIMERx_OVF_vect TIMER1_OVF_vect
67# define TIMSKx TIMSK1
68# define TOIEx TOIE1
69
70# if BACKLIGHT_PIN == B7
71# define COMxx1 COM1C1
72# define OCRxx OCR1C
73# elif BACKLIGHT_PIN == C5
74# define COMxx1 COM1B1
75# define OCRxx OCR1B
76# elif BACKLIGHT_PIN == C6
77# define COMxx1 COM1A1
78# define OCRxx OCR1A
79# endif
80# elif defined(__AVR_ATmega32A__) && (BACKLIGHT_PIN == D4 || BACKLIGHT_PIN == D5)
81# define HARDWARE_PWM
82# define ICRx ICR1
83# define TCCRxA TCCR1A
84# define TCCRxB TCCR1B
85# define TIMERx_OVF_vect TIMER1_OVF_vect
86# define TIMSKx TIMSK
87# define TOIEx TOIE1
88
89# if BACKLIGHT_PIN == D4
90# define COMxx1 COM1B1
91# define OCRxx OCR1B
92# elif BACKLIGHT_PIN == D5
93# define COMxx1 COM1A1
94# define OCRxx OCR1A
95# endif
96# elif defined(__AVR_ATmega328P__) && (BACKLIGHT_PIN == B1 || BACKLIGHT_PIN == B2)
97# define HARDWARE_PWM
98# define ICRx ICR1
99# define TCCRxA TCCR1A
100# define TCCRxB TCCR1B
101# define TIMERx_OVF_vect TIMER1_OVF_vect
102# define TIMSKx TIMSK1
103# define TOIEx TOIE1
104
105# if BACKLIGHT_PIN == B1
106# define COMxx1 COM1A1
107# define OCRxx OCR1A
108# elif BACKLIGHT_PIN == B2
109# define COMxx1 COM1B1
110# define OCRxx OCR1B
111# endif
112# else
113# if !defined(BACKLIGHT_CUSTOM_DRIVER)
114# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO)
115// Timer 1 is not in use by Audio feature, Backlight can use it
116# pragma message "Using hardware timer 1 with software PWM"
117# define HARDWARE_PWM
118# define BACKLIGHT_PWM_TIMER
119# define ICRx ICR1
120# define TCCRxA TCCR1A
121# define TCCRxB TCCR1B
122# define TIMERx_COMPA_vect TIMER1_COMPA_vect
123# define TIMERx_OVF_vect TIMER1_OVF_vect
124# if defined(__AVR_ATmega32A__) // This MCU has only one TIMSK register
125# define TIMSKx TIMSK
126# else
127# define TIMSKx TIMSK1
128# endif
129# define TOIEx TOIE1
130
131# define OCIExA OCIE1A
132# define OCRxx OCR1A
133# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO)
134# pragma message "Using hardware timer 3 with software PWM"
135// Timer 3 is not in use by Audio feature, Backlight can use it
136# define HARDWARE_PWM
137# define BACKLIGHT_PWM_TIMER
138# define ICRx ICR1
139# define TCCRxA TCCR3A
140# define TCCRxB TCCR3B
141# define TIMERx_COMPA_vect TIMER3_COMPA_vect
142# define TIMERx_OVF_vect TIMER3_OVF_vect
143# define TIMSKx TIMSK3
144# define TOIEx TOIE3
145
146# define OCIExA OCIE3A
147# define OCRxx OCR3A
148# else
149# pragma message "Audio in use - using pure software PWM"
150# define NO_HARDWARE_PWM
151# endif
152# else
153# pragma message "Custom driver defined - using pure software PWM"
154# define NO_HARDWARE_PWM
155# endif
156# endif
157
158# ifndef BACKLIGHT_ON_STATE
159# define BACKLIGHT_ON_STATE 0
160# endif
161
162void backlight_on(uint8_t backlight_pin) {
163# if BACKLIGHT_ON_STATE == 0
164 writePinLow(backlight_pin);
165# else
166 writePinHigh(backlight_pin);
167# endif
168}
169
170void backlight_off(uint8_t backlight_pin) {
171# if BACKLIGHT_ON_STATE == 0
172 writePinHigh(backlight_pin);
173# else
174 writePinLow(backlight_pin);
175# endif
176}
177
178# if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software
179
180// we support multiple backlight pins
181# ifndef BACKLIGHT_LED_COUNT
182# define BACKLIGHT_LED_COUNT 1
183# endif
184
185# if BACKLIGHT_LED_COUNT == 1
186# define BACKLIGHT_PIN_INIT \
187 { BACKLIGHT_PIN }
188# else
189# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS
190# endif
191
192# define FOR_EACH_LED(x) \
193 for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \
194 uint8_t backlight_pin = backlight_pins[i]; \
195 { x } \
196 }
197
198static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT;
199
200# else // full hardware PWM
201
202// we support only one backlight pin
203static const uint8_t backlight_pin = BACKLIGHT_PIN;
204# define FOR_EACH_LED(x) x
205
206# endif
207
208# ifdef NO_HARDWARE_PWM
209__attribute__((weak)) void backlight_init_ports(void) {
210 // Setup backlight pin as output and output to on state.
211 FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);)
212
213# ifdef BACKLIGHT_BREATHING
214 if (is_backlight_breathing()) {
215 breathing_enable();
216 }
217# endif
218}
219
220__attribute__((weak)) void backlight_set(uint8_t level) {}
221
222uint8_t backlight_tick = 0;
223
224# ifndef BACKLIGHT_CUSTOM_DRIVER
225void backlight_task(void) {
226 if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) {
227 FOR_EACH_LED(backlight_on(backlight_pin);)
228 } else {
229 FOR_EACH_LED(backlight_off(backlight_pin);)
230 }
231 backlight_tick = (backlight_tick + 1) % 16;
232}
233# endif
234
235# ifdef BACKLIGHT_BREATHING
236# ifndef BACKLIGHT_CUSTOM_DRIVER
237# error "Backlight breathing only available with hardware PWM. Please disable."
238# endif
239# endif
240
241# else // hardware pwm through timer
242
243# ifdef BACKLIGHT_PWM_TIMER
244
245// The idea of software PWM assisted by hardware timers is the following
246// we use the hardware timer in fast PWM mode like for hardware PWM, but
247// instead of letting the Output Match Comparator control the led pin
248// (which is not possible since the backlight is not wired to PWM pins on the
249// CPU), we do the LED on/off by oursleves.
250// The timer is setup to count up to 0xFFFF, and we set the Output Compare
251// register to the current 16bits backlight level (after CIE correction).
252// This means the CPU will trigger a compare match interrupt when the counter
253// reaches the backlight level, where we turn off the LEDs,
254// but also an overflow interrupt when the counter rolls back to 0,
255// in which we're going to turn on the LEDs.
256// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz.
257
258// Triggered when the counter reaches the OCRx value
259ISR(TIMERx_COMPA_vect) { FOR_EACH_LED(backlight_off(backlight_pin);) }
260
261// Triggered when the counter reaches the TOP value
262// this one triggers at F_CPU/65536 =~ 244 Hz
263ISR(TIMERx_OVF_vect) {
264# ifdef BACKLIGHT_BREATHING
265 if (is_breathing()) {
266 breathing_task();
267 }
268# endif
269 // for very small values of OCRxx (or backlight level)
270 // we can't guarantee this whole code won't execute
271 // at the same time as the compare match interrupt
272 // which means that we might turn on the leds while
273 // trying to turn them off, leading to flickering
274 // artifacts (especially while breathing, because breathing_task
275 // takes many computation cycles).
276 // so better not turn them on while the counter TOP is very low.
277 if (OCRxx > 256) {
278 FOR_EACH_LED(backlight_on(backlight_pin);)
279 }
280}
281
282# endif
283
284# define TIMER_TOP 0xFFFFU
285
286// See http://jared.geek.nz/2013/feb/linear-led-pwm
287static uint16_t cie_lightness(uint16_t v) {
288 if (v <= 5243) // if below 8% of max
289 return v / 9; // same as dividing by 900%
290 else {
291 uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare
292 // to get a useful result with integer division, we shift left in the expression above
293 // and revert what we've done again after squaring.
294 y = y * y * y >> 8;
295 if (y > 0xFFFFUL) // prevent overflow
296 return 0xFFFFU;
297 else
298 return (uint16_t)y;
299 }
300}
301
302// range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val.
303static inline void set_pwm(uint16_t val) { OCRxx = val; }
304
305# ifndef BACKLIGHT_CUSTOM_DRIVER
306__attribute__((weak)) void backlight_set(uint8_t level) {
307 if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS;
308
309 if (level == 0) {
310# ifdef BACKLIGHT_PWM_TIMER
311 if (OCRxx) {
312 TIMSKx &= ~(_BV(OCIExA));
313 TIMSKx &= ~(_BV(TOIEx));
314 FOR_EACH_LED(backlight_off(backlight_pin);)
315 }
316# else
317 // Turn off PWM control on backlight pin
318 TCCRxA &= ~(_BV(COMxx1));
319# endif
320 } else {
321# ifdef BACKLIGHT_PWM_TIMER
322 if (!OCRxx) {
323 TIMSKx |= _BV(OCIExA);
324 TIMSKx |= _BV(TOIEx);
325 }
326# else
327 // Turn on PWM control of backlight pin
328 TCCRxA |= _BV(COMxx1);
329# endif
330 }
331 // Set the brightness
332 set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS));
333}
334
335void backlight_task(void) {}
336# endif // BACKLIGHT_CUSTOM_DRIVER
337
338# ifdef BACKLIGHT_BREATHING
339
340# define BREATHING_NO_HALT 0
341# define BREATHING_HALT_OFF 1
342# define BREATHING_HALT_ON 2
343# define BREATHING_STEPS 128
344
345static uint8_t breathing_period = BREATHING_PERIOD;
346static uint8_t breathing_halt = BREATHING_NO_HALT;
347static uint16_t breathing_counter = 0;
348
349# ifdef BACKLIGHT_PWM_TIMER
350static bool breathing = false;
351
352bool is_breathing(void) { return breathing; }
353
354# define breathing_interrupt_enable() \
355 do { \
356 breathing = true; \
357 } while (0)
358# define breathing_interrupt_disable() \
359 do { \
360 breathing = false; \
361 } while (0)
362# else
363
364bool is_breathing(void) { return !!(TIMSKx & _BV(TOIEx)); }
365
366# define breathing_interrupt_enable() \
367 do { \
368 TIMSKx |= _BV(TOIEx); \
369 } while (0)
370# define breathing_interrupt_disable() \
371 do { \
372 TIMSKx &= ~_BV(TOIEx); \
373 } while (0)
374# endif
375
376# define breathing_min() \
377 do { \
378 breathing_counter = 0; \
379 } while (0)
380# define breathing_max() \
381 do { \
382 breathing_counter = breathing_period * 244 / 2; \
383 } while (0)
384
385void breathing_enable(void) {
386 breathing_counter = 0;
387 breathing_halt = BREATHING_NO_HALT;
388 breathing_interrupt_enable();
389}
390
391void breathing_pulse(void) {
392 if (get_backlight_level() == 0)
393 breathing_min();
394 else
395 breathing_max();
396 breathing_halt = BREATHING_HALT_ON;
397 breathing_interrupt_enable();
398}
399
400void breathing_disable(void) {
401 breathing_interrupt_disable();
402 // Restore backlight level
403 backlight_set(get_backlight_level());
404}
405
406void breathing_self_disable(void) {
407 if (get_backlight_level() == 0)
408 breathing_halt = BREATHING_HALT_OFF;
409 else
410 breathing_halt = BREATHING_HALT_ON;
411}
412
413void breathing_toggle(void) {
414 if (is_breathing())
415 breathing_disable();
416 else
417 breathing_enable();
418}
419
420void breathing_period_set(uint8_t value) {
421 if (!value) value = 1;
422 breathing_period = value;
423}
424
425void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); }
426
427void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); }
428
429void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); }
430
431/* To generate breathing curve in python:
432 * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)]
433 */
434static 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};
435
436// Use this before the cie_lightness function.
437static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); }
438
439# ifdef BACKLIGHT_PWM_TIMER
440void breathing_task(void)
441# else
442/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run
443 * about 244 times per second.
444 */
445ISR(TIMERx_OVF_vect)
446# endif
447{
448 uint16_t interval = (uint16_t)breathing_period * 244 / BREATHING_STEPS;
449 // resetting after one period to prevent ugly reset at overflow.
450 breathing_counter = (breathing_counter + 1) % (breathing_period * 244);
451 uint8_t index = breathing_counter / interval % BREATHING_STEPS;
452
453 if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) {
454 breathing_interrupt_disable();
455 }
456
457 set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U)));
458}
459
460# endif // BACKLIGHT_BREATHING
461
462__attribute__((weak)) void backlight_init_ports(void) {
463 // Setup backlight pin as output and output to on state.
464 FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);)
465
466 // I could write a wall of text here to explain... but TL;DW
467 // Go read the ATmega32u4 datasheet.
468 // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
469
470# ifdef BACKLIGHT_PWM_TIMER
471 // TimerX setup, Fast PWM mode count to TOP set in ICRx
472 TCCRxA = _BV(WGM11); // = 0b00000010;
473 // clock select clk/1
474 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
475# else // hardware PWM
476 // Pin PB7 = OCR1C (Timer 1, Channel C)
477 // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
478 // (i.e. start high, go low when counter matches.)
479 // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0
480 // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1
481
482 /*
483 14.8.3:
484 "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 [..]."
485 "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)."
486 */
487 TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010;
488 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
489# endif
490 // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0.
491 ICRx = TIMER_TOP;
492
493 backlight_init();
494# ifdef BACKLIGHT_BREATHING
495 if (is_backlight_breathing()) {
496 breathing_enable();
497 }
498# endif
499}
500
501# endif // hardware backlight
502
503#else // no backlight
504
505__attribute__((weak)) void backlight_init_ports(void) {}
506
507__attribute__((weak)) void backlight_set(uint8_t level) {}
508
509#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 @@
24# include "outputselect.h" 24# include "outputselect.h"
25#endif 25#endif
26 26
27#ifndef BREATHING_PERIOD
28# define BREATHING_PERIOD 6
29#endif
30
31#include "backlight.h" 27#include "backlight.h"
32extern backlight_config_t backlight_config; 28extern backlight_config_t backlight_config;
33 29
@@ -1019,511 +1015,6 @@ void matrix_scan_quantum() {
1019 1015
1020 matrix_scan_kb(); 1016 matrix_scan_kb();
1021} 1017}
1022#if defined(BACKLIGHT_ENABLE) && (defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS))
1023
1024// This logic is a bit complex, we support 3 setups:
1025//
1026// 1. Hardware PWM when backlight is wired to a PWM pin.
1027// Depending on this pin, we use a different output compare unit.
1028// 2. Software PWM with hardware timers, but the used timer
1029// depends on the Audio setup (Audio wins over Backlight).
1030// 3. Full software PWM, driven by the matrix scan, if both timers are used by Audio.
1031
1032# 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)
1033# define HARDWARE_PWM
1034# define ICRx ICR1
1035# define TCCRxA TCCR1A
1036# define TCCRxB TCCR1B
1037# define TIMERx_OVF_vect TIMER1_OVF_vect
1038# define TIMSKx TIMSK1
1039# define TOIEx TOIE1
1040
1041# if BACKLIGHT_PIN == B5
1042# define COMxx1 COM1A1
1043# define OCRxx OCR1A
1044# elif BACKLIGHT_PIN == B6
1045# define COMxx1 COM1B1
1046# define OCRxx OCR1B
1047# elif BACKLIGHT_PIN == B7
1048# define COMxx1 COM1C1
1049# define OCRxx OCR1C
1050# endif
1051# 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)
1052# define HARDWARE_PWM
1053# define ICRx ICR3
1054# define TCCRxA TCCR3A
1055# define TCCRxB TCCR3B
1056# define TIMERx_OVF_vect TIMER3_OVF_vect
1057# define TIMSKx TIMSK3
1058# define TOIEx TOIE3
1059
1060# if BACKLIGHT_PIN == C4
1061# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
1062# error This MCU has no C4 pin!
1063# else
1064# define COMxx1 COM3C1
1065# define OCRxx OCR3C
1066# endif
1067# elif BACKLIGHT_PIN == C5
1068# if (defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__))
1069# error This MCU has no C5 pin!
1070# else
1071# define COMxx1 COM3B1
1072# define OCRxx OCR3B
1073# endif
1074# elif BACKLIGHT_PIN == C6
1075# define COMxx1 COM3A1
1076# define OCRxx OCR3A
1077# endif
1078# elif (defined(__AVR_ATmega16U2__) || defined(__AVR_ATmega32U2__)) && (BACKLIGHT_PIN == B7 || BACKLIGHT_PIN == C5 || BACKLIGHT_PIN == C6)
1079# define HARDWARE_PWM
1080# define ICRx ICR1
1081# define TCCRxA TCCR1A
1082# define TCCRxB TCCR1B
1083# define TIMERx_OVF_vect TIMER1_OVF_vect
1084# define TIMSKx TIMSK1
1085# define TOIEx TOIE1
1086
1087# if BACKLIGHT_PIN == B7
1088# define COMxx1 COM1C1
1089# define OCRxx OCR1C
1090# elif BACKLIGHT_PIN == C5
1091# define COMxx1 COM1B1
1092# define OCRxx OCR1B
1093# elif BACKLIGHT_PIN == C6
1094# define COMxx1 COM1A1
1095# define OCRxx OCR1A
1096# endif
1097# elif defined(__AVR_ATmega32A__) && (BACKLIGHT_PIN == D4 || BACKLIGHT_PIN == D5)
1098# define HARDWARE_PWM
1099# define ICRx ICR1
1100# define TCCRxA TCCR1A
1101# define TCCRxB TCCR1B
1102# define TIMERx_OVF_vect TIMER1_OVF_vect
1103# define TIMSKx TIMSK
1104# define TOIEx TOIE1
1105
1106# if BACKLIGHT_PIN == D4
1107# define COMxx1 COM1B1
1108# define OCRxx OCR1B
1109# elif BACKLIGHT_PIN == D5
1110# define COMxx1 COM1A1
1111# define OCRxx OCR1A
1112# endif
1113# elif defined(__AVR_ATmega328P__) && (BACKLIGHT_PIN == B1 || BACKLIGHT_PIN == B2)
1114# define HARDWARE_PWM
1115# define ICRx ICR1
1116# define TCCRxA TCCR1A
1117# define TCCRxB TCCR1B
1118# define TIMERx_OVF_vect TIMER1_OVF_vect
1119# define TIMSKx TIMSK1
1120# define TOIEx TOIE1
1121
1122# if BACKLIGHT_PIN == B1
1123# define COMxx1 COM1A1
1124# define OCRxx OCR1A
1125# elif BACKLIGHT_PIN == B2
1126# define COMxx1 COM1B1
1127# define OCRxx OCR1B
1128# endif
1129# else
1130# if !defined(BACKLIGHT_CUSTOM_DRIVER)
1131# if !defined(B5_AUDIO) && !defined(B6_AUDIO) && !defined(B7_AUDIO)
1132// Timer 1 is not in use by Audio feature, Backlight can use it
1133# pragma message "Using hardware timer 1 with software PWM"
1134# define HARDWARE_PWM
1135# define BACKLIGHT_PWM_TIMER
1136# define ICRx ICR1
1137# define TCCRxA TCCR1A
1138# define TCCRxB TCCR1B
1139# define TIMERx_COMPA_vect TIMER1_COMPA_vect
1140# define TIMERx_OVF_vect TIMER1_OVF_vect
1141# if defined(__AVR_ATmega32A__) // This MCU has only one TIMSK register
1142# define TIMSKx TIMSK
1143# else
1144# define TIMSKx TIMSK1
1145# endif
1146# define TOIEx TOIE1
1147
1148# define OCIExA OCIE1A
1149# define OCRxx OCR1A
1150# elif !defined(C6_AUDIO) && !defined(C5_AUDIO) && !defined(C4_AUDIO)
1151# pragma message "Using hardware timer 3 with software PWM"
1152// Timer 3 is not in use by Audio feature, Backlight can use it
1153# define HARDWARE_PWM
1154# define BACKLIGHT_PWM_TIMER
1155# define ICRx ICR1
1156# define TCCRxA TCCR3A
1157# define TCCRxB TCCR3B
1158# define TIMERx_COMPA_vect TIMER3_COMPA_vect
1159# define TIMERx_OVF_vect TIMER3_OVF_vect
1160# define TIMSKx TIMSK3
1161# define TOIEx TOIE3
1162
1163# define OCIExA OCIE3A
1164# define OCRxx OCR3A
1165# else
1166# pragma message "Audio in use - using pure software PWM"
1167# define NO_HARDWARE_PWM
1168# endif
1169# else
1170# pragma message "Custom driver defined - using pure software PWM"
1171# define NO_HARDWARE_PWM
1172# endif
1173# endif
1174
1175# ifndef BACKLIGHT_ON_STATE
1176# define BACKLIGHT_ON_STATE 0
1177# endif
1178
1179void backlight_on(uint8_t backlight_pin) {
1180# if BACKLIGHT_ON_STATE == 0
1181 writePinLow(backlight_pin);
1182# else
1183 writePinHigh(backlight_pin);
1184# endif
1185}
1186
1187void backlight_off(uint8_t backlight_pin) {
1188# if BACKLIGHT_ON_STATE == 0
1189 writePinHigh(backlight_pin);
1190# else
1191 writePinLow(backlight_pin);
1192# endif
1193}
1194
1195# if defined(NO_HARDWARE_PWM) || defined(BACKLIGHT_PWM_TIMER) // pwm through software
1196
1197// we support multiple backlight pins
1198# ifndef BACKLIGHT_LED_COUNT
1199# define BACKLIGHT_LED_COUNT 1
1200# endif
1201
1202# if BACKLIGHT_LED_COUNT == 1
1203# define BACKLIGHT_PIN_INIT \
1204 { BACKLIGHT_PIN }
1205# else
1206# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS
1207# endif
1208
1209# define FOR_EACH_LED(x) \
1210 for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \
1211 uint8_t backlight_pin = backlight_pins[i]; \
1212 { x } \
1213 }
1214
1215static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT;
1216
1217# else // full hardware PWM
1218
1219// we support only one backlight pin
1220static const uint8_t backlight_pin = BACKLIGHT_PIN;
1221# define FOR_EACH_LED(x) x
1222
1223# endif
1224
1225# ifdef NO_HARDWARE_PWM
1226__attribute__((weak)) void backlight_init_ports(void) {
1227 // Setup backlight pin as output and output to on state.
1228 FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);)
1229
1230# ifdef BACKLIGHT_BREATHING
1231 if (is_backlight_breathing()) {
1232 breathing_enable();
1233 }
1234# endif
1235}
1236
1237__attribute__((weak)) void backlight_set(uint8_t level) {}
1238
1239uint8_t backlight_tick = 0;
1240
1241# ifndef BACKLIGHT_CUSTOM_DRIVER
1242void backlight_task(void) {
1243 if ((0xFFFF >> ((BACKLIGHT_LEVELS - get_backlight_level()) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) {
1244 FOR_EACH_LED(backlight_on(backlight_pin);)
1245 } else {
1246 FOR_EACH_LED(backlight_off(backlight_pin);)
1247 }
1248 backlight_tick = (backlight_tick + 1) % 16;
1249}
1250# endif
1251
1252# ifdef BACKLIGHT_BREATHING
1253# ifndef BACKLIGHT_CUSTOM_DRIVER
1254# error "Backlight breathing only available with hardware PWM. Please disable."
1255# endif
1256# endif
1257
1258# else // hardware pwm through timer
1259
1260# ifdef BACKLIGHT_PWM_TIMER
1261
1262// The idea of software PWM assisted by hardware timers is the following
1263// we use the hardware timer in fast PWM mode like for hardware PWM, but
1264// instead of letting the Output Match Comparator control the led pin
1265// (which is not possible since the backlight is not wired to PWM pins on the
1266// CPU), we do the LED on/off by oursleves.
1267// The timer is setup to count up to 0xFFFF, and we set the Output Compare
1268// register to the current 16bits backlight level (after CIE correction).
1269// This means the CPU will trigger a compare match interrupt when the counter
1270// reaches the backlight level, where we turn off the LEDs,
1271// but also an overflow interrupt when the counter rolls back to 0,
1272// in which we're going to turn on the LEDs.
1273// The LED will then be on for OCRxx/0xFFFF time, adjusted every 244Hz.
1274
1275// Triggered when the counter reaches the OCRx value
1276ISR(TIMERx_COMPA_vect) { FOR_EACH_LED(backlight_off(backlight_pin);) }
1277
1278// Triggered when the counter reaches the TOP value
1279// this one triggers at F_CPU/65536 =~ 244 Hz
1280ISR(TIMERx_OVF_vect) {
1281# ifdef BACKLIGHT_BREATHING
1282 if (is_breathing()) {
1283 breathing_task();
1284 }
1285# endif
1286 // for very small values of OCRxx (or backlight level)
1287 // we can't guarantee this whole code won't execute
1288 // at the same time as the compare match interrupt
1289 // which means that we might turn on the leds while
1290 // trying to turn them off, leading to flickering
1291 // artifacts (especially while breathing, because breathing_task
1292 // takes many computation cycles).
1293 // so better not turn them on while the counter TOP is very low.
1294 if (OCRxx > 256) {
1295 FOR_EACH_LED(backlight_on(backlight_pin);)
1296 }
1297}
1298
1299# endif
1300
1301# define TIMER_TOP 0xFFFFU
1302
1303// See http://jared.geek.nz/2013/feb/linear-led-pwm
1304static uint16_t cie_lightness(uint16_t v) {
1305 if (v <= 5243) // if below 8% of max
1306 return v / 9; // same as dividing by 900%
1307 else {
1308 uint32_t y = (((uint32_t)v + 10486) << 8) / (10486 + 0xFFFFUL); // add 16% of max and compare
1309 // to get a useful result with integer division, we shift left in the expression above
1310 // and revert what we've done again after squaring.
1311 y = y * y * y >> 8;
1312 if (y > 0xFFFFUL) // prevent overflow
1313 return 0xFFFFU;
1314 else
1315 return (uint16_t)y;
1316 }
1317}
1318
1319// range for val is [0..TIMER_TOP]. PWM pin is high while the timer count is below val.
1320static inline void set_pwm(uint16_t val) { OCRxx = val; }
1321
1322# ifndef BACKLIGHT_CUSTOM_DRIVER
1323__attribute__((weak)) void backlight_set(uint8_t level) {
1324 if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS;
1325
1326 if (level == 0) {
1327# ifdef BACKLIGHT_PWM_TIMER
1328 if (OCRxx) {
1329 TIMSKx &= ~(_BV(OCIExA));
1330 TIMSKx &= ~(_BV(TOIEx));
1331 FOR_EACH_LED(backlight_off(backlight_pin);)
1332 }
1333# else
1334 // Turn off PWM control on backlight pin
1335 TCCRxA &= ~(_BV(COMxx1));
1336# endif
1337 } else {
1338# ifdef BACKLIGHT_PWM_TIMER
1339 if (!OCRxx) {
1340 TIMSKx |= _BV(OCIExA);
1341 TIMSKx |= _BV(TOIEx);
1342 }
1343# else
1344 // Turn on PWM control of backlight pin
1345 TCCRxA |= _BV(COMxx1);
1346# endif
1347 }
1348 // Set the brightness
1349 set_pwm(cie_lightness(TIMER_TOP * (uint32_t)level / BACKLIGHT_LEVELS));
1350}
1351
1352void backlight_task(void) {}
1353# endif // BACKLIGHT_CUSTOM_DRIVER
1354
1355# ifdef BACKLIGHT_BREATHING
1356
1357# define BREATHING_NO_HALT 0
1358# define BREATHING_HALT_OFF 1
1359# define BREATHING_HALT_ON 2
1360# define BREATHING_STEPS 128
1361
1362static uint8_t breathing_period = BREATHING_PERIOD;
1363static uint8_t breathing_halt = BREATHING_NO_HALT;
1364static uint16_t breathing_counter = 0;
1365
1366# ifdef BACKLIGHT_PWM_TIMER
1367static bool breathing = false;
1368
1369bool is_breathing(void) { return breathing; }
1370
1371# define breathing_interrupt_enable() \
1372 do { \
1373 breathing = true; \
1374 } while (0)
1375# define breathing_interrupt_disable() \
1376 do { \
1377 breathing = false; \
1378 } while (0)
1379# else
1380
1381bool is_breathing(void) { return !!(TIMSKx & _BV(TOIEx)); }
1382
1383# define breathing_interrupt_enable() \
1384 do { \
1385 TIMSKx |= _BV(TOIEx); \
1386 } while (0)
1387# define breathing_interrupt_disable() \
1388 do { \
1389 TIMSKx &= ~_BV(TOIEx); \
1390 } while (0)
1391# endif
1392
1393# define breathing_min() \
1394 do { \
1395 breathing_counter = 0; \
1396 } while (0)
1397# define breathing_max() \
1398 do { \
1399 breathing_counter = breathing_period * 244 / 2; \
1400 } while (0)
1401
1402void breathing_enable(void) {
1403 breathing_counter = 0;
1404 breathing_halt = BREATHING_NO_HALT;
1405 breathing_interrupt_enable();
1406}
1407
1408void breathing_pulse(void) {
1409 if (get_backlight_level() == 0)
1410 breathing_min();
1411 else
1412 breathing_max();
1413 breathing_halt = BREATHING_HALT_ON;
1414 breathing_interrupt_enable();
1415}
1416
1417void breathing_disable(void) {
1418 breathing_interrupt_disable();
1419 // Restore backlight level
1420 backlight_set(get_backlight_level());
1421}
1422
1423void breathing_self_disable(void) {
1424 if (get_backlight_level() == 0)
1425 breathing_halt = BREATHING_HALT_OFF;
1426 else
1427 breathing_halt = BREATHING_HALT_ON;
1428}
1429
1430void breathing_toggle(void) {
1431 if (is_breathing())
1432 breathing_disable();
1433 else
1434 breathing_enable();
1435}
1436
1437void breathing_period_set(uint8_t value) {
1438 if (!value) value = 1;
1439 breathing_period = value;
1440}
1441
1442void breathing_period_default(void) { breathing_period_set(BREATHING_PERIOD); }
1443
1444void breathing_period_inc(void) { breathing_period_set(breathing_period + 1); }
1445
1446void breathing_period_dec(void) { breathing_period_set(breathing_period - 1); }
1447
1448/* To generate breathing curve in python:
1449 * from math import sin, pi; [int(sin(x/128.0*pi)**4*255) for x in range(128)]
1450 */
1451static 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};
1452
1453// Use this before the cie_lightness function.
1454static inline uint16_t scale_backlight(uint16_t v) { return v / BACKLIGHT_LEVELS * get_backlight_level(); }
1455
1456# ifdef BACKLIGHT_PWM_TIMER
1457void breathing_task(void)
1458# else
1459/* Assuming a 16MHz CPU clock and a timer that resets at 64k (ICR1), the following interrupt handler will run
1460 * about 244 times per second.
1461 */
1462ISR(TIMERx_OVF_vect)
1463# endif
1464{
1465 uint16_t interval = (uint16_t)breathing_period * 244 / BREATHING_STEPS;
1466 // resetting after one period to prevent ugly reset at overflow.
1467 breathing_counter = (breathing_counter + 1) % (breathing_period * 244);
1468 uint8_t index = breathing_counter / interval % BREATHING_STEPS;
1469
1470 if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) {
1471 breathing_interrupt_disable();
1472 }
1473
1474 set_pwm(cie_lightness(scale_backlight((uint16_t)pgm_read_byte(&breathing_table[index]) * 0x0101U)));
1475}
1476
1477# endif // BACKLIGHT_BREATHING
1478
1479__attribute__((weak)) void backlight_init_ports(void) {
1480 // Setup backlight pin as output and output to on state.
1481 FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);)
1482
1483 // I could write a wall of text here to explain... but TL;DW
1484 // Go read the ATmega32u4 datasheet.
1485 // And this: http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on
1486
1487# ifdef BACKLIGHT_PWM_TIMER
1488 // TimerX setup, Fast PWM mode count to TOP set in ICRx
1489 TCCRxA = _BV(WGM11); // = 0b00000010;
1490 // clock select clk/1
1491 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
1492# else // hardware PWM
1493 // Pin PB7 = OCR1C (Timer 1, Channel C)
1494 // Compare Output Mode = Clear on compare match, Channel C = COM1C1=1 COM1C0=0
1495 // (i.e. start high, go low when counter matches.)
1496 // WGM Mode 14 (Fast PWM) = WGM13=1 WGM12=1 WGM11=1 WGM10=0
1497 // Clock Select = clk/1 (no prescaling) = CS12=0 CS11=0 CS10=1
1498
1499 /*
1500 14.8.3:
1501 "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 [..]."
1502 "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)."
1503 */
1504 TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010;
1505 TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001;
1506# endif
1507 // Use full 16-bit resolution. Counter counts to ICR1 before reset to 0.
1508 ICRx = TIMER_TOP;
1509
1510 backlight_init();
1511# ifdef BACKLIGHT_BREATHING
1512 if (is_backlight_breathing()) {
1513 breathing_enable();
1514 }
1515# endif
1516}
1517
1518# endif // hardware backlight
1519
1520#else // no backlight
1521
1522__attribute__((weak)) void backlight_init_ports(void) {}
1523
1524__attribute__((weak)) void backlight_set(uint8_t level) {}
1525
1526#endif // backlight
1527 1018
1528#ifdef HD44780_ENABLED 1019#ifdef HD44780_ENABLED
1529# include "hd44780.h" 1020# 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 <http://www.gnu.org/licenses/>.
26# error "Maximum value of BACKLIGHT_LEVELS is 31" 26# error "Maximum value of BACKLIGHT_LEVELS is 31"
27#endif 27#endif
28 28
29#ifndef BREATHING_PERIOD
30# define BREATHING_PERIOD 6
31#endif
32
29typedef union { 33typedef union {
30 uint8_t raw; 34 uint8_t raw;
31 struct { 35 struct {