From 4137685f8eb3ecde7ba0158a78cd9c411ee05606 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 21 Jul 2023 09:27:55 +1000 Subject: [PATCH] backlight: split AVR PWM and timer drivers (#21540) --- docs/feature_backlight.md | 346 +++++++++++------- keyboards/40percentclub/4pack/info.json | 1 + keyboards/40percentclub/sixpack/info.json | 1 + keyboards/8pack/info.json | 1 + keyboards/ai03/equinox/rev0/info.json | 1 + keyboards/amjkeyboard/amj66/info.json | 1 + keyboards/anavi/macropad8/info.json | 1 + keyboards/bear_face/info.json | 1 + .../cablecardesigns/cypher/rev6/info.json | 1 + keyboards/cest73/tkm/info.json | 1 + keyboards/checkerboards/nop60/info.json | 1 + keyboards/checkerboards/quark_plus/info.json | 1 + keyboards/checkerboards/snop60/info.json | 1 + keyboards/efreet/info.json | 1 + keyboards/gray_studio/cod67/info.json | 1 + keyboards/handwired/bdn9_ble/info.json | 1 + keyboards/kc60se/info.json | 1 + keyboards/keebio/iris/rev1/info.json | 1 + keyboards/kopibeng/xt65/info.json | 1 + keyboards/kprepublic/cospad/info.json | 1 + keyboards/ktec/daisy/info.json | 1 + keyboards/ktec/staryu/info.json | 1 + .../maple_computing/christmas_tree/info.json | 1 + keyboards/maple_computing/ivy/rev1/info.json | 1 + keyboards/maple_computing/jnao/info.json | 1 + keyboards/melgeek/mj6xy/rev3/info.json | 1 - keyboards/mntre/config.h | 2 +- keyboards/mt/mt40/info.json | 1 + .../nopunin10did/jabberwocky/v2/info.json | 4 +- keyboards/org60/info.json | 1 + keyboards/playkbtw/ca66/info.json | 1 + keyboards/sandwich/keeb68/info.json | 1 + keyboards/tkc/osav2/info.json | 1 + keyboards/v60_type_r/info.json | 1 + keyboards/viktus/osav2/info.json | 1 + keyboards/westfoxtrot/cypher/rev5/info.json | 1 + keyboards/xiudi/xd60/rev2/info.json | 1 + keyboards/xiudi/xd60/rev3/info.json | 1 + keyboards/xiudi/xd68/info.json | 1 + keyboards/xiudi/xd84pro/info.json | 1 + keyboards/xiudi/xd87/info.json | 1 + platforms/avr/drivers/backlight_pwm.c | 200 ++-------- platforms/avr/drivers/backlight_timer.c | 267 ++++++++++++++ 43 files changed, 555 insertions(+), 302 deletions(-) create mode 100644 platforms/avr/drivers/backlight_timer.c diff --git a/docs/feature_backlight.md b/docs/feature_backlight.md index 24057c608f8c47b05fac281b3ef49f24d8dc443f..d032c33f18a121c6d05b3c12682bd7d2f788c53f 100644 --- a/docs/feature_backlight.md +++ b/docs/feature_backlight.md @@ -1,12 +1,12 @@ # Backlighting :id=backlighting -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. +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. +## Usage :id=usage -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: +Most keyboards have backlighting enabled by default if they support it, but if it is not working for you (or you have added support), check that your `rules.mk` includes the following: ```make BACKLIGHT_ENABLE = yes @@ -14,57 +14,23 @@ BACKLIGHT_ENABLE = yes ## Keycodes :id=keycodes -Once enabled, the following keycodes below can be used to change the backlight level. +|Key |Aliases |Description | +|-------------------------------|---------|-----------------------------------| +|`QK_BACKLIGHT_TOGGLE` |`BL_TOGG`|Turn the backlight on or off | +|`QK_BACKLIGHT_STEP` |`BL_STEP`|Cycle through backlight levels | +|`QK_BACKLIGHT_ON` |`BL_ON` |Set the backlight to max brightness| +|`QK_BACKLIGHT_OFF` |`BL_OFF` |Turn the backlight off | +|`QK_BACKLIGHT_UP` |`BL_UP` |Increase the backlight level | +|`QK_BACKLIGHT_DOWN` |`BL_DOWN`|Decrease the backlight level | +|`QK_BACKLIGHT_TOGGLE_BREATHING`|`BL_BRTG`|Toggle backlight breathing | -| Key | Aliases | Description | -|---------------------------------|-----------|-------------------------------------| -| `QK_BACKLIGHT_TOGGLE` | `BL_TOGG` | Turn the backlight on or off | -| `QK_BACKLIGHT_STEP` | `BL_STEP` | Cycle through backlight levels | -| `QK_BACKLIGHT_ON` | `BL_ON` | Set the backlight to max brightness | -| `QK_BACKLIGHT_OFF` | `BL_OFF` | Turn the backlight off | -| `QK_BACKLIGHT_UP` | `BL_UP` | Increase the backlight level | -| `QK_BACKLIGHT_DOWN` | `BL_DOWN` | Decrease the backlight level | -| `QK_BACKLIGHT_TOGGLE_BREATHING` | `BL_BRTG` | Toggle backlight breathing | +## Basic Configuration :id=basic-configuration -## Functions :id=functions - -These functions can be used to change the backlighting in custom code: - -|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| - -If backlight breathing is enabled (see below), the following functions are also available: - -|Function |Description | -|---------------------|--------------------------------------| -|`breathing_toggle()` |Turn the backlight breathing on or off| -|`breathing_enable()` |Turns on backlight breathing | -|`breathing_disable()`|Turns off backlight breathing | - -## Configuration :id=configuration - -To select which driver to use, configure your `rules.mk` with the following: - -```make -BACKLIGHT_DRIVER = software -``` - -Valid driver values are `pwm`, `software`, `custom` or `no`. See below for help on individual drivers. - -To configure the backlighting, `#define` these in your `config.h`: +Add the following to your `config.h`: |Define |Default |Description | |-----------------------------|------------------|-----------------------------------------------------------------------------------------------------------------| -|`BACKLIGHT_PIN` |*Not defined* |The pin that controls the LED(s) | +|`BACKLIGHT_PIN` |*Not defined* |The pin that controls the LEDs | |`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 | @@ -76,24 +42,88 @@ To configure the backlighting, `#define` these in your `config.h`: Unless you are designing your own keyboard, you generally should not need to change the `BACKLIGHT_PIN` or `BACKLIGHT_ON_STATE`. -### Backlight On State :id=backlight-on-state +### "On" State :id=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. +To configure the "on" state of the backlight circuit, add the following to your `config.h`: + +```c +#define BACKLIGHT_ON_STATE 0 +``` + +### Multiple Backlight Pins :id=multiple-backlight-pins + +Most keyboards have only one backlight pin which controls all backlight LEDs (especially if the backlight is connected to a hardware PWM pin). +The `timer` and `software` drivers allow you to define multiple backlight pins, which 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's (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped Control 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, as it is usually wired to a separate pin from the backlight. + +To configure multiple backlight pins, add something like this to your `config.h`, instead of `BACKLIGHT_PIN`: + +```c +#define BACKLIGHT_PINS { F5, B2 } +``` + +## Driver Configuration :id=driver-configuration + +Backlight driver selection is configured in `rules.mk`. Valid drivers are `pwm` (default), `timer`, `software`, or `custom`. See below for information on individual drivers. -### AVR Driver :id=avr-driver +### PWM Driver :id=pwm-driver -The `pwm` driver is configured by default, however the equivalent setting within `rules.mk` would be: +This is the default backlight driver, which leverages the hardware PWM output capability of the microcontroller. ```make BACKLIGHT_DRIVER = pwm ``` -#### Caveats :id=avr-caveats +### Timer Driver :id=timer-driver + +This driver is similar to the PWM driver, but instead of directly configuring the pin to output a PWM signal, an interrupt handler is attached to the timer to turn the pin on and off as appropriate. + +```make +BACKLIGHT_DRIVER = timer +``` + +### Software Driver :id=software-driver + +In this mode, PWM is "emulated" while running other keyboard tasks. It offers maximum hardware compatibility without extra platform configuration. However, breathing is not supported, and the backlight can flicker when the keyboard is busy. + +```make +BACKLIGHT_DRIVER = software +``` + +### Custom Driver :id=custom-driver + +If none of the above drivers apply to your board (for example, you are using a separate IC to control the backlight), you can implement a custom backlight driver using a simple API. + +```make +BACKLIGHT_DRIVER = custom +``` + +```c +void backlight_init_ports(void) { + // Optional - runs on startup + // Usually you want to configure pins here +} +void backlight_set(uint8_t level) { + // Optional - runs on level change + // Usually you want to respond to the new value +} + +void backlight_task(void) { + // Optional - runs periodically + // Note that this is called in the main keyboard loop, + // so long running actions here can cause performance issues +} +``` + +## AVR Configuration :id=avr-configuration + +### PWM Driver :id=avr-pwm-driver -On AVR boards, QMK automatically decides which driver to use according to the following table: +The following table describes the supported pins for the PWM driver. Only cells marked with a timer number are capable of hardware PWM output; any others must use the `timer` driver. |Backlight Pin|AT90USB64/128|AT90USB162|ATmega16/32U4|ATmega16/32U2|ATmega32A|ATmega328/P| |-------------|-------------|----------|-------------|-------------|---------|-----------| @@ -108,113 +138,169 @@ On AVR boards, QMK automatically decides which driver to use according to the fo |`D4` | | | | |Timer 1 | | |`D5` | | | | |Timer 1 | | -All other pins will use timer-assisted software PWM: +### Timer Driver :id=avr-timer-driver -|Audio Pin|Audio Timer|Software PWM Timer| -|---------|-----------|------------------| -|`C4` |Timer 3 |Timer 1 | -|`C5` |Timer 3 |Timer 1 | -|`C6` |Timer 3 |Timer 1 | -|`B5` |Timer 1 |Timer 3 | -|`B6` |Timer 1 |Timer 3 | -|`B7` |Timer 1 |Timer 3 | +Any GPIO pin can be used with this driver. The following table describes the supported timers: -When both timers are in use for Audio, the backlight PWM cannot use a hardware timer, and 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. +|AT90USB64/128|AT90USB162|ATmega16/32U4|ATmega16/32U2|ATmega32A|ATmega328/P| +|-------------|----------|-------------|-------------|---------|-----------| +|Timers 1 & 3 |Timer 1 |Timers 1 & 3 |Timer 1 |Timer 1 |Timer 1 | -#### Hardware PWM Implementation :id=hardware-pwm-implementation +The following `#define`s apply only to the `timer` driver: -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. -In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus the brightness, where `0x0000` is completely off and `0xFFFF` is completely on. +|Define |Default|Description | +|-----------------------|-------|----------------| +|`BACKLIGHT_PWM_TIMER` |`1` |The timer to use| -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. +Note that the choice of timer may conflict with the [Audio](feature_audio.md) feature. -#### Timer Assisted PWM Implementation :id=timer-assisted-implementation +## ChibiOS/ARM Configuration :id=arm-configuration -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. -The desired brightness is calculated and stored in the `OCRxx` register. When the counter reaches this value, the CPU will fire a Compare Output match interrupt, which will turn the LEDs off. -In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus the brightness, where `0x0000` is completely off and `0xFFFF` is completely on. +### PWM Driver :id=arm-pwm-driver -The breathing effect is the same as in the hardware PWM implementation. +Depending on the ChibiOS board configuration, you may need to enable PWM at the keyboard level. For STM32, this would look like: -### ARM Driver :id=arm-configuration +`halconf.h`: +```c +#define HAL_USE_PWM TRUE +``` +`mcuconf.h`: +```c +#undef STM32_PWM_USE_TIM4 +#define STM32_PWM_USE_TIM4 TRUE +``` -While still in its early stages, ARM backlight support aims to eventually have feature parity with AVR. The `pwm` driver is configured by default, however the equivalent setting within `rules.mk` would be: +The following `#define`s apply only to the `pwm` driver: -```make -BACKLIGHT_DRIVER = pwm +|Define |Default |Description | +|-----------------------|--------|-----------------------------------| +|`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use | +|`BACKLIGHT_PWM_CHANNEL`|`3` |The PWM channel to use | +|`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use| + +Refer to the ST datasheet for your particular MCU to determine these values. For example, these defaults are set up for pin `B8` on a Proton-C (STM32F303) using `TIM4_CH3` on AF2. Unless you are designing your own keyboard, you generally should not need to change them. + +### Timer Driver :id=arm-timer-driver + +Depending on the ChibiOS board configuration, you may need to enable general-purpose timers at the keyboard level. For STM32, this would look like: + +`halconf.h`: +```c +#define HAL_USE_GPT TRUE +``` +`mcuconf.h`: +```c +#undef STM32_GPT_USE_TIM15 +#define STM32_GPT_USE_TIM15 TRUE ``` -#### ChibiOS Configuration :id=arm-configuration +The following `#define`s apply only to the `timer` driver: -The following `#define`s apply only to ARM-based keyboards: +|Define |Default |Description | +|----------------------|--------|----------------| +|`BACKLIGHT_GPT_DRIVER`|`GPTD15`|The timer to use| -|Define |Default|Description | -|-----------------------|-------|-----------------------------------| -|`BACKLIGHT_PWM_DRIVER` |`PWMD4`|The PWM driver to use | -|`BACKLIGHT_PWM_CHANNEL`|`3` |The PWM channel to use | -|`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use| +## Example Schematic -See the ST datasheet for your particular MCU to determine these values. Unless you are designing your own keyboard, you generally should not need to change them. +Since 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. -#### Caveats :id=arm-caveats +In this typical example, the backlight LEDs are all connected in parallel towards an N-channel MOSFET. Its gate pin is wired to one of the microcontroller's GPIO pins through a 470Ω resistor to avoid ringing. +A pulldown resistor is also placed between the gate pin and ground to keep it at a defined state when it is not otherwise being driven by the MCU. +The values of these resistors are not critical - see [this Electronics StackExchange question](https://electronics.stackexchange.com/q/68748) for more information. -Currently only hardware PWM is supported, not timer assisted, and does not provide automatic configuration. +![Backlight example circuit](https://i.imgur.com/BmAvoUC.png) -### Software PWM Driver :id=software-pwm-driver +## API :id=api -In this mode, PWM is "emulated" 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`: +### `void backlight_toggle(void)` :id=api-backlight-toggle -```make -BACKLIGHT_DRIVER = software -``` +Toggle the backlight on or off. -#### Multiple Backlight Pins :id=multiple-backlight-pins +--- -Most keyboards have only one backlight pin which controls all backlight LEDs (especially if the backlight is connected to a hardware PWM pin). -In software PWM, it is possible to define multiple backlight pins, which will be turned on and off at the same time during the PWM duty cycle. +### `void backlight_enable(void)` :id=api-backlight-enable -This feature allows to set, for instance, the Caps Lock LED's (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped Control 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, as it is usually wired to a separate pin from the backlight. +Turn the backlight on. -To activate multiple backlight pins, add something like this to your `config.h`, instead of `BACKLIGHT_PIN`: +--- -```c -#define BACKLIGHT_PINS { F5, B2 } -``` +### `void backlight_disable(void)` :id=api-backlight-disable -### Custom Driver :id=custom-driver +Turn the backlight off. -If none of the above drivers apply to your board (for example, you are using a separate IC to control the backlight), you can implement a custom backlight driver using this simple API provided by QMK. To enable, add this to your `rules.mk`: +--- -```make -BACKLIGHT_DRIVER = custom -``` +### `void backlight_step(void)` :id=api-backlight-step -Then implement any of these hooks: +Cycle through backlight levels. -```c -void backlight_init_ports(void) { - // Optional - runs on startup - // Usually you want to configure pins here -} -void backlight_set(uint8_t level) { - // Optional - runs on level change - // Usually you want to respond to the new value -} +--- -void backlight_task(void) { - // Optional - runs periodically - // Note that this is called in the main keyboard loop, - // so long running actions here can cause performance issues -} -``` +### `void backlight_increase(void)` :id=api-backlight-increase -## Example Schematic +Increase the backlight level. -In this typical example, the backlight LEDs are all connected in parallel towards an N-channel MOSFET. Its gate pin is wired to one of the microcontroller's GPIO pins through a 470Ω resistor to avoid ringing. -A pulldown resistor is also placed between the gate pin and ground to keep it at a defined state when it is not otherwise being driven by the MCU. -The values of these resistors are not critical - see [this Electronics StackExchange question](https://electronics.stackexchange.com/q/68748) for more information. +--- -![Backlight example circuit](https://i.imgur.com/BmAvoUC.png) +### `void backlight_decrease(void)` :id=api-backlight-decrease + +Decrease the backlight level. + +--- + +### `void backlight_level(uint8_t level)` :id=api-backlight-level + +Set the backlight level. + +#### Arguments :id=api-backlight-level-arguments + + - `uint8_t level` + The level to set, from 0 to `BACKLIGHT_LEVELS`. + +--- + +### `uint8_t get_backlight_level(void)` :id=api-get-backlight-level + +Get the current backlight level. + +#### Return Value :id=api-get-backlight-level-return + +The current backlight level, from 0 to `BACKLIGHT_LEVELS`. + +--- + +### `bool is_backlight_enabled(void)` :id=api-is-backlight-enabled + +Get the current backlight state. + +#### Return Value :id=api-is-backlight-enabled-return + +`true` if the backlight is enabled. + +--- + +### `void backlight_toggle_breathing(void)` :id=api-backlight-toggle-breathing + +Toggle backlight breathing on or off. + +--- + +### `void backlight_enable_breathing(void)` :id=api-backlight-enable-breathing + +Turn backlight breathing on. + +--- + +### `void backlight_disable_breathing(void)` :id=api-backlight-disable-breathing + +Turn backlight breathing off. + +--- + +### `bool is_backlight_breathing(void)` :id=api-is-backlight-breathing + +Get the current backlight breathing state. + +#### Return Value :id=api-is-backlight-breathing-return + +`true` if backlight breathing is enabled. diff --git a/keyboards/40percentclub/4pack/info.json b/keyboards/40percentclub/4pack/info.json index 9709807612c019608827131ec8c86527b4a86394..31d45598833895fa31e7f5880bf7c525f20c7da7 100644 --- a/keyboards/40percentclub/4pack/info.json +++ b/keyboards/40percentclub/4pack/info.json @@ -9,6 +9,7 @@ "device_version": "0.0.1" }, "backlight": { + "driver": "timer", "pins": ["F6", "F7"] }, "processor": "atmega32u4", diff --git a/keyboards/40percentclub/sixpack/info.json b/keyboards/40percentclub/sixpack/info.json index 3483c7d4df657718895e039ec325e2f2250b7454..cd4864fbd127b74728da5d8ae5864af8d7602df0 100644 --- a/keyboards/40percentclub/sixpack/info.json +++ b/keyboards/40percentclub/sixpack/info.json @@ -9,6 +9,7 @@ "device_version": "10.0.1" }, "backlight": { + "driver": "timer", "pins": ["F4", "F5"], "levels": 6, "breathing": true diff --git a/keyboards/8pack/info.json b/keyboards/8pack/info.json index fffac7d4268c48b9ed4a6910fe70cf3859c132aa..b572bdd0e92f9ebb68d36d497626825d5fc0a6c6 100644 --- a/keyboards/8pack/info.json +++ b/keyboards/8pack/info.json @@ -8,6 +8,7 @@ "pid": "0x2171" }, "backlight": { + "driver": "timer", "pins": ["D1", "D0", "D4", "C6", "D7", "E6", "B4", "B5"], "levels": 8 }, diff --git a/keyboards/ai03/equinox/rev0/info.json b/keyboards/ai03/equinox/rev0/info.json index f5dd365564584a0c8d55cca3758c5cdd489cb025..396f50c376562bf11fcb1e15cb11492047853dae 100644 --- a/keyboards/ai03/equinox/rev0/info.json +++ b/keyboards/ai03/equinox/rev0/info.json @@ -5,6 +5,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D1", "levels": 5, "breathing": true diff --git a/keyboards/amjkeyboard/amj66/info.json b/keyboards/amjkeyboard/amj66/info.json index a28b91d7c60c90797eacdf07ac2924cc349916dd..07a170a3f6acb0e44868228446bfbcf69f7ff563 100644 --- a/keyboards/amjkeyboard/amj66/info.json +++ b/keyboards/amjkeyboard/amj66/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D4", "breathing": true }, diff --git a/keyboards/anavi/macropad8/info.json b/keyboards/anavi/macropad8/info.json index b21f12b7d7dd7a12e2be68139bc94c5374ae3bb2..2ff901c0d5a391cca9975b5d0b323774953ae230 100644 --- a/keyboards/anavi/macropad8/info.json +++ b/keyboards/anavi/macropad8/info.json @@ -9,6 +9,7 @@ "device_version": "0.0.1" }, "backlight": { + "driver": "timer", "pin": "D7", "breathing": true }, diff --git a/keyboards/bear_face/info.json b/keyboards/bear_face/info.json index 0c3e6148033c5bc049d118e0f8ecd5f46a2b29d4..e0df316093d52e890cf1c11645809d54ea8a10d5 100644 --- a/keyboards/bear_face/info.json +++ b/keyboards/bear_face/info.json @@ -15,6 +15,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F7" }, "processor": "atmega32u4", diff --git a/keyboards/cablecardesigns/cypher/rev6/info.json b/keyboards/cablecardesigns/cypher/rev6/info.json index 9722db7a63dc7bd1083c73c8bb4062e0d4b66851..5bd725322e004368188081f759684d217e8acfaa 100644 --- a/keyboards/cablecardesigns/cypher/rev6/info.json +++ b/keyboards/cablecardesigns/cypher/rev6/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0", "levels": 5, "breathing": true diff --git a/keyboards/cest73/tkm/info.json b/keyboards/cest73/tkm/info.json index 028e028159d102e63d7d65bfce2e09eca4989efe..6447189b936f3972b98292a588d832939731dad4 100644 --- a/keyboards/cest73/tkm/info.json +++ b/keyboards/cest73/tkm/info.json @@ -15,6 +15,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "E6" }, "indicators": { diff --git a/keyboards/checkerboards/nop60/info.json b/keyboards/checkerboards/nop60/info.json index 8b164a998217681bfe19a0c35c795bc0e7506c27..7dff63e7118a10db3aee9375c3a34f7dfcdffb5d 100644 --- a/keyboards/checkerboards/nop60/info.json +++ b/keyboards/checkerboards/nop60/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D1", "levels": 6, "breathing": true diff --git a/keyboards/checkerboards/quark_plus/info.json b/keyboards/checkerboards/quark_plus/info.json index 3b8803ce71c4f7c0968a518ae85a67662215370e..bbe63a750b6f2df8638ee0cd20424fce69a1a281 100644 --- a/keyboards/checkerboards/quark_plus/info.json +++ b/keyboards/checkerboards/quark_plus/info.json @@ -31,6 +31,7 @@ ] }, "backlight": { + "driver": "timer", "pin": "C4", "levels": 6, "breathing": true diff --git a/keyboards/checkerboards/snop60/info.json b/keyboards/checkerboards/snop60/info.json index 00194c0184a9c9459252fbd9a5714d98caf88863..fe9f30e289c1baf03b5a22db9b2aa0d35f600f7e 100644 --- a/keyboards/checkerboards/snop60/info.json +++ b/keyboards/checkerboards/snop60/info.json @@ -19,6 +19,7 @@ ] }, "backlight": { + "driver": "timer", "pin": "D1", "levels": 6, "breathing": true diff --git a/keyboards/efreet/info.json b/keyboards/efreet/info.json index ea2a48be25bc1f77300103c984762056c9dd9b8f..b7749ed3419e3bee7bf51f0955392c6b0a32baa5 100644 --- a/keyboards/efreet/info.json +++ b/keyboards/efreet/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0", "breathing": true }, diff --git a/keyboards/gray_studio/cod67/info.json b/keyboards/gray_studio/cod67/info.json index 91b66685816fd53c2989cffb31c9ed5fd9795fa4..fa4739923250881b7cebc4551509e6f0638adbfb 100644 --- a/keyboards/gray_studio/cod67/info.json +++ b/keyboards/gray_studio/cod67/info.json @@ -23,6 +23,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D4", "on_state": 0 }, diff --git a/keyboards/handwired/bdn9_ble/info.json b/keyboards/handwired/bdn9_ble/info.json index eb6e57abdec45961f6b2d5c5b8f529d0f994ccb7..bb8258c7cf1e0651ce582619cc21b879edcf3fde 100644 --- a/keyboards/handwired/bdn9_ble/info.json +++ b/keyboards/handwired/bdn9_ble/info.json @@ -9,6 +9,7 @@ "device_version": "1.0.0" }, "backlight": { + "driver": "timer", "pin": "F6", "levels": 5 }, diff --git a/keyboards/kc60se/info.json b/keyboards/kc60se/info.json index afca6764460d4ff945bf8404ed14f4c123f759ff..ff9fa35165190027aac53c6c0ac4429770a0ad9f 100644 --- a/keyboards/kc60se/info.json +++ b/keyboards/kc60se/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 6 }, diff --git a/keyboards/keebio/iris/rev1/info.json b/keyboards/keebio/iris/rev1/info.json index a389e7ae092ae87846bf24ef719b8cd5336f44eb..fef671c4570437aa0e315c06d25dc1e875e4b108 100644 --- a/keyboards/keebio/iris/rev1/info.json +++ b/keyboards/keebio/iris/rev1/info.json @@ -10,6 +10,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D2", "levels": 5 }, diff --git a/keyboards/kopibeng/xt65/info.json b/keyboards/kopibeng/xt65/info.json index 650e0e05b2dc07deda9b49bde53d91a2304aea78..17e61aaf4d326216b4b30f19c22fbdc74cb3d7d1 100644 --- a/keyboards/kopibeng/xt65/info.json +++ b/keyboards/kopibeng/xt65/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "B1", "levels": 5 }, diff --git a/keyboards/kprepublic/cospad/info.json b/keyboards/kprepublic/cospad/info.json index 90b9dab4a144d5cc37817720ac84375132752843..82b6abec6c716dc582ce62d2dfb78048e1b382ab 100644 --- a/keyboards/kprepublic/cospad/info.json +++ b/keyboards/kprepublic/cospad/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F7", "on_state": 0 }, diff --git a/keyboards/ktec/daisy/info.json b/keyboards/ktec/daisy/info.json index aa9a45477a4a4654268d7b969d28fb7b7163fe30..b11e9460961fe48d0ffea1ee3345f827bab4a245 100644 --- a/keyboards/ktec/daisy/info.json +++ b/keyboards/ktec/daisy/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0", "levels": 6 }, diff --git a/keyboards/ktec/staryu/info.json b/keyboards/ktec/staryu/info.json index da277a0ae055452428eeb33107a6298902b9bc3c..2235bafbb3505714dcd87294d7f053323b0b8118 100644 --- a/keyboards/ktec/staryu/info.json +++ b/keyboards/ktec/staryu/info.json @@ -9,6 +9,7 @@ "device_version": "2.0.5" }, "backlight": { + "driver": "timer", "pins": ["C2", "C7", "D5", "D6", "B0"], "levels": 10 }, diff --git a/keyboards/maple_computing/christmas_tree/info.json b/keyboards/maple_computing/christmas_tree/info.json index 0a19667957c7fe71a75fbe17207ab453f5014851..e675f2f932775dd2423799ec84b0c42dde80ddd9 100644 --- a/keyboards/maple_computing/christmas_tree/info.json +++ b/keyboards/maple_computing/christmas_tree/info.json @@ -13,6 +13,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D2" }, "processor": "atmega32u4", diff --git a/keyboards/maple_computing/ivy/rev1/info.json b/keyboards/maple_computing/ivy/rev1/info.json index 3b971cb67360de0d33e2fe01cb9f12afb3c8e777..de89abee75cb5abc65d2ca33609e507a1d09ff92 100644 --- a/keyboards/maple_computing/ivy/rev1/info.json +++ b/keyboards/maple_computing/ivy/rev1/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D2" }, "processor": "atmega32u4", diff --git a/keyboards/maple_computing/jnao/info.json b/keyboards/maple_computing/jnao/info.json index f1e753017f1df7384997bdcc96b13405b8fc5eb9..73ddf107ecb9024f4f61cff46ae9798e31b75a1c 100644 --- a/keyboards/maple_computing/jnao/info.json +++ b/keyboards/maple_computing/jnao/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0" }, "processor": "atmega32u4", diff --git a/keyboards/melgeek/mj6xy/rev3/info.json b/keyboards/melgeek/mj6xy/rev3/info.json index 3b5c7375a48698ab1290e91c455dc3a846293a22..74e77035ca3b36a2d527c4489be7ca112811321d 100644 --- a/keyboards/melgeek/mj6xy/rev3/info.json +++ b/keyboards/melgeek/mj6xy/rev3/info.json @@ -5,7 +5,6 @@ }, "diode_direction": "COL2ROW", "backlight": { - "driver": "software", "pin": "B7", "levels": 10 }, diff --git a/keyboards/mntre/config.h b/keyboards/mntre/config.h index 55a7a8457e44c6e01475390101e3f334d054cdc0..c12ca236724e160e9b2ec48c15023cdfcaa0e747 100644 --- a/keyboards/mntre/config.h +++ b/keyboards/mntre/config.h @@ -3,7 +3,7 @@ #pragma once -#define BACKLIGHT_CUSTOM_RESOLUTION 0x400 +#define BACKLIGHT_RESOLUTION 0x400 /* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */ #define LOCKING_SUPPORT_ENABLE diff --git a/keyboards/mt/mt40/info.json b/keyboards/mt/mt40/info.json index f53e9f60d468efb9397d2bfb819e01aae0ef037b..7b4a0cac64371a1af30a6c43bc4aaa5afafe3176 100644 --- a/keyboards/mt/mt40/info.json +++ b/keyboards/mt/mt40/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D2" }, "rgblight": { diff --git a/keyboards/nopunin10did/jabberwocky/v2/info.json b/keyboards/nopunin10did/jabberwocky/v2/info.json index 71d6d94542b5fab73eb58ba944dc908b8c4b29d9..64848c0552061389537bb671486944c9ff11d3fa 100644 --- a/keyboards/nopunin10did/jabberwocky/v2/info.json +++ b/keyboards/nopunin10did/jabberwocky/v2/info.json @@ -14,8 +14,8 @@ }, "diode_direction": "COL2ROW", "backlight": { - "driver": "software", - "pins": ["D6"], + "driver": "timer", + "pin": "D6", "levels": 6 }, "indicators": { diff --git a/keyboards/org60/info.json b/keyboards/org60/info.json index bc922012f0934414a44f8fa6341733754f3ddc2f..c1de3084ed861dd8393ec15a1e7eed3755c56c5e 100644 --- a/keyboards/org60/info.json +++ b/keyboards/org60/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 6 }, diff --git a/keyboards/playkbtw/ca66/info.json b/keyboards/playkbtw/ca66/info.json index 3f9538f31c0f008551034f6ed5ebc8d975b55f75..60b17cfb4672ff0cad60f00c2d938dbebc750778 100644 --- a/keyboards/playkbtw/ca66/info.json +++ b/keyboards/playkbtw/ca66/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F0" }, "rgblight": { diff --git a/keyboards/sandwich/keeb68/info.json b/keyboards/sandwich/keeb68/info.json index ec09badedfa988a1db17e12587c6a6bae652d08c..df8b91a3385a11648f5fe5f744808dae9de80fc0 100644 --- a/keyboards/sandwich/keeb68/info.json +++ b/keyboards/sandwich/keeb68/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "C7", "levels": 5, "breathing": true diff --git a/keyboards/tkc/osav2/info.json b/keyboards/tkc/osav2/info.json index 7a59bb65dbf9901033298785fed295cfcb5fe538..c882c073ac27cd26ab480130d84ba120d5fa9938 100644 --- a/keyboards/tkc/osav2/info.json +++ b/keyboards/tkc/osav2/info.json @@ -21,6 +21,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D6", "breathing": true }, diff --git a/keyboards/v60_type_r/info.json b/keyboards/v60_type_r/info.json index 8a2b7827ceb64111b004731c77bfe1f7d822d437..760a4f73562d6b01a9bdc07730f34db0faacb415 100644 --- a/keyboards/v60_type_r/info.json +++ b/keyboards/v60_type_r/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F7", "on_state": 0 }, diff --git a/keyboards/viktus/osav2/info.json b/keyboards/viktus/osav2/info.json index eba02a41378102c3b898d21e04c402aac6d7cfba..b528bac281f9f823336729e442ee2ed338e12713 100644 --- a/keyboards/viktus/osav2/info.json +++ b/keyboards/viktus/osav2/info.json @@ -44,6 +44,7 @@ "pin": "D4" }, "backlight": { + "driver": "timer", "levels": 6, "max_brightness": 191, "pin": "D6" diff --git a/keyboards/westfoxtrot/cypher/rev5/info.json b/keyboards/westfoxtrot/cypher/rev5/info.json index c7e62632e200c6efec824ab26cc16836268883e4..6cb14f3b0e27c6076a178e9b7c984491ad6480fa 100644 --- a/keyboards/westfoxtrot/cypher/rev5/info.json +++ b/keyboards/westfoxtrot/cypher/rev5/info.json @@ -12,6 +12,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0", "levels": 5, "breathing": true diff --git a/keyboards/xiudi/xd60/rev2/info.json b/keyboards/xiudi/xd60/rev2/info.json index 2fae5080c3f664f4542bdd1e68f66388f658ec05..a253f14b245d29bce5bad24e9adc7ebf8cca47ae 100644 --- a/keyboards/xiudi/xd60/rev2/info.json +++ b/keyboards/xiudi/xd60/rev2/info.json @@ -9,6 +9,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 6, "on_state": 0 diff --git a/keyboards/xiudi/xd60/rev3/info.json b/keyboards/xiudi/xd60/rev3/info.json index c77b145607ce2f73ff1a7b4b384980a0e8f887aa..6cc83bef59b902659c0ed1478d0ae8a36bd69181 100644 --- a/keyboards/xiudi/xd60/rev3/info.json +++ b/keyboards/xiudi/xd60/rev3/info.json @@ -9,6 +9,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 6, "on_state": 0 diff --git a/keyboards/xiudi/xd68/info.json b/keyboards/xiudi/xd68/info.json index 27f40e6ee8458a27cba215fad5673a07910b547e..5332e1243f4b7fe713331c2344352d8567471001 100644 --- a/keyboards/xiudi/xd68/info.json +++ b/keyboards/xiudi/xd68/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 6, "breathing": true, diff --git a/keyboards/xiudi/xd84pro/info.json b/keyboards/xiudi/xd84pro/info.json index 1df9eccb0f00ec21a1c1fbce4b048ccc8ee48f7e..697d26774143370dc09bcad71787917b74b99faf 100644 --- a/keyboards/xiudi/xd84pro/info.json +++ b/keyboards/xiudi/xd84pro/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "F5", "levels": 10, "on_state": 0 diff --git a/keyboards/xiudi/xd87/info.json b/keyboards/xiudi/xd87/info.json index 4226947c25104b8a33666da72cb25ac15f460327..74495b0f9ffc40f2787f96a491aeb399e1807e15 100644 --- a/keyboards/xiudi/xd87/info.json +++ b/keyboards/xiudi/xd87/info.json @@ -14,6 +14,7 @@ }, "diode_direction": "COL2ROW", "backlight": { + "driver": "timer", "pin": "D0", "on_state": 0 }, diff --git a/platforms/avr/drivers/backlight_pwm.c b/platforms/avr/drivers/backlight_pwm.c index d2341156410a21e44a5e361b001a3cf740953ef7..74d25753a4e66074ad1fa1680a532a5e12e606fd 100644 --- a/platforms/avr/drivers/backlight_pwm.c +++ b/platforms/avr/drivers/backlight_pwm.c @@ -1,5 +1,5 @@ #include "backlight.h" -#include "backlight_driver_common.h" +#include "gpio.h" #include "progmem.h" #include #include @@ -9,14 +9,6 @@ # define BACKLIGHT_LIMIT_VAL 255 #endif -// 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 ICRx ICR1 # define TCCRxA TCCR1A @@ -122,106 +114,34 @@ # define COMxx1 COM1B1 # define OCRxx OCR1B # endif -#elif (AUDIO_PIN != B5) && (AUDIO_PIN != B6) && (AUDIO_PIN != B7) && (AUDIO_PIN_ALT != B5) && (AUDIO_PIN_ALT != B6) && (AUDIO_PIN_ALT != B7) -// Timer 1 is not in use by Audio feature, Backlight can use it -# pragma message "Using hardware timer 1 with software 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 +#endif -# define OCIExA OCIE1A -# define OCRxx OCR1A -#elif (AUDIO_PIN != C4) && (AUDIO_PIN != C5) && (AUDIO_PIN != C6) -# pragma message "Using hardware timer 3 with software PWM" -// Timer 3 is not in use by Audio feature, Backlight can use it -# 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 +#ifndef BACKLIGHT_RESOLUTION +# define BACKLIGHT_RESOLUTION 0xFFFFU +#endif -# define OCIExA OCIE3A -# define OCRxx OCR3A +#if (BACKLIGHT_RESOLUTION > 0xFFFF || BACKLIGHT_RESOLUTION < 0x00FF) +# error "Backlight resolution must be between 0x00FF and 0xFFFF" #endif -#ifndef BACKLIGHT_PWM_TIMER // pwm through software +#define BREATHING_SCALE_FACTOR F_CPU / BACKLIGHT_RESOLUTION / 120 static inline void enable_pwm(void) { -# if BACKLIGHT_ON_STATE == 1 +#if BACKLIGHT_ON_STATE == 1 TCCRxA |= _BV(COMxx1); -# else +#else TCCRxA |= _BV(COMxx1) | _BV(COMxx0); -# endif +#endif } static inline void disable_pwm(void) { -# if BACKLIGHT_ON_STATE == 1 +#if BACKLIGHT_ON_STATE == 1 TCCRxA &= ~(_BV(COMxx1)); -# else +#else TCCRxA &= ~(_BV(COMxx1) | _BV(COMxx0)); -# endif -} - #endif - -#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, -// or F_CPU/BACKLIGHT_CUSTOM_RESOLUTION if used. - -// Triggered when the counter reaches the OCRx value -ISR(TIMERx_COMPA_vect) { - backlight_pins_off(); } -// Triggered when the counter reaches the TOP value -// this one triggers at F_CPU/ICRx = 16MHz/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 > ICRx / 250 + 5) { - backlight_pins_on(); - } -} - -#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 <= (uint32_t)ICRx / 12) // If the value is less than or equal to ~8% of max @@ -254,26 +174,11 @@ 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)); - } -#else // Turn off PWM control on backlight pin disable_pwm(); -#endif - backlight_pins_off(); } else { -#ifdef BACKLIGHT_PWM_TIMER - if (!OCRxx) { - TIMSKx |= _BV(OCIExA); - TIMSKx |= _BV(TOIEx); - } -#else // Turn on PWM control of backlight pin enable_pwm(); -#endif } // Set the brightness set_pwm(cie_lightness(rescale_limit_val(ICRx * (uint32_t)level / BACKLIGHT_LEVELS))); @@ -282,7 +187,6 @@ void backlight_set(uint8_t level) { void backlight_task(void) {} #ifdef BACKLIGHT_BREATHING - # define BREATHING_NO_HALT 0 # define BREATHING_HALT_OFF 1 # define BREATHING_HALT_ON 2 @@ -293,39 +197,20 @@ static uint16_t breathing_counter = 0; static uint8_t breath_scale_counter = 1; /* Run the breathing loop at ~120Hz*/ -const uint8_t breathing_ISR_frequency = 120; -static uint16_t breathing_freq_scale_factor = 2; - -# 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 +const uint8_t breathing_ISR_frequency = 120; 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_interrupt_enable() \ + do { \ + TIMSKx |= _BV(TOIEx); \ + } while (0) +# define breathing_interrupt_disable() \ + do { \ + TIMSKx &= ~_BV(TOIEx); \ + } while (0) # define breathing_min() \ do { \ @@ -374,20 +259,14 @@ 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. * * The following ISR runs at F_CPU/ISRx. With a 16MHz clock and default pwm resolution, that means 244Hz */ -ISR(TIMERx_OVF_vect) -# endif -{ - +ISR(TIMERx_OVF_vect) { // Only run this ISR at ~120 Hz - if (breath_scale_counter++ == breathing_freq_scale_factor) { + if (breath_scale_counter++ == BREATHING_SCALE_FACTOR) { breath_scale_counter = 1; } else { return; @@ -412,19 +291,17 @@ ISR(TIMERx_OVF_vect) #endif // BACKLIGHT_BREATHING void backlight_init_ports(void) { - // Setup backlight pin as output and output to on state. - backlight_pins_init(); + setPinOutput(BACKLIGHT_PIN); +#if BACKLIGHT_ON_STATE == 1 + writePinLow(BACKLIGHT_PIN); +#else + writePinHigh(BACKLIGHT_PIN); +#endif // 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.) @@ -438,23 +315,10 @@ void backlight_init_ports(void) { */ TCCRxA = _BV(COMxx1) | _BV(WGM11); // = 0b00001010; TCCRxB = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // = 0b00011001; -#endif - -#ifdef BACKLIGHT_CUSTOM_RESOLUTION -# if (BACKLIGHT_CUSTOM_RESOLUTION > 0xFFFF || BACKLIGHT_CUSTOM_RESOLUTION < 1) -# error "This out of range of the timer capabilities" -# elif (BACKLIGHT_CUSTOM_RESOLUTION < 0xFF) -# warning "Resolution lower than 0xFF isn't recommended" -# endif -# ifdef BACKLIGHT_BREATHING - breathing_freq_scale_factor = F_CPU / BACKLIGHT_CUSTOM_RESOLUTION / 120; -# endif - ICRx = BACKLIGHT_CUSTOM_RESOLUTION; -#else - ICRx = TIMER_TOP; -#endif + ICRx = BACKLIGHT_RESOLUTION; backlight_init(); + #ifdef BACKLIGHT_BREATHING if (is_backlight_breathing()) { breathing_enable(); diff --git a/platforms/avr/drivers/backlight_timer.c b/platforms/avr/drivers/backlight_timer.c new file mode 100644 index 0000000000000000000000000000000000000000..e1f4286557f4ba8c06538751bcb602f32991bea2 --- /dev/null +++ b/platforms/avr/drivers/backlight_timer.c @@ -0,0 +1,267 @@ +#include "backlight.h" +#include "backlight_driver_common.h" +#include "progmem.h" +#include +#include + +// Maximum duty cycle limit +#ifndef BACKLIGHT_LIMIT_VAL +# define BACKLIGHT_LIMIT_VAL 255 +#endif + +#ifndef BACKLIGHT_PWM_TIMER +# define BACKLIGHT_PWM_TIMER 1 +#endif + +#if BACKLIGHT_PWM_TIMER == 1 +# 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 BACKLIGHT_PWM_TIMER == 3 +# 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 +# error Invalid backlight PWM timer! +#endif + +#ifndef BACKLIGHT_RESOLUTION +# define BACKLIGHT_RESOLUTION 0xFFFFU +#endif + +#if (BACKLIGHT_RESOLUTION > 0xFFFF || BACKLIGHT_RESOLUTION < 0x00FF) +# error "Backlight resolution must be between 0x00FF and 0xFFFF" +#endif + +#define BREATHING_SCALE_FACTOR F_CPU / BACKLIGHT_RESOLUTION / 120 + +// 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, +// or F_CPU/BACKLIGHT_RESOLUTION if used. + +// Triggered when the counter reaches the OCRx value +ISR(TIMERx_COMPA_vect) { + backlight_pins_off(); +} + +// Triggered when the counter reaches the TOP value +// this one triggers at F_CPU/ICRx = 16MHz/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 > ICRx / 250 + 5) { + backlight_pins_on(); + } +} + +// See http://jared.geek.nz/2013/feb/linear-led-pwm +static uint16_t cie_lightness(uint16_t v) { + if (v <= (uint32_t)ICRx / 12) // If the value is less than or equal to ~8% of max + { + return v / 9; // Same as dividing by 900% + } else { + // In the next two lines values are bit-shifted. This is to avoid loosing decimals in integer math. + uint32_t y = (((uint32_t)v + (uint32_t)ICRx / 6) << 5) / ((uint32_t)ICRx / 6 + ICRx); // If above 8%, add ~16% of max, and normalize with (max + ~16% max) + uint32_t out = (y * y * y * ICRx) >> 15; // Cube it and undo the bit-shifting. (which is now three times as much due to the cubing) + + if (out > ICRx) // Avoid overflows + { + out = ICRx; + } + return (uint16_t)out; + } +} + +// rescale the supplied backlight value to be in terms of the value limit // range for val is [0..ICRx]. PWM pin is high while the timer count is below val. +static uint32_t rescale_limit_val(uint32_t val) { + return (val * (BACKLIGHT_LIMIT_VAL + 1)) / 256; +} + +// range for val is [0..ICRx]. PWM pin is high while the timer count is below val. +static inline void set_pwm(uint16_t val) { + OCRxx = val; +} + +void backlight_set(uint8_t level) { + if (level > BACKLIGHT_LEVELS) level = BACKLIGHT_LEVELS; + + if (level == 0) { + if (OCRxx) { + TIMSKx &= ~(_BV(OCIExA)); + TIMSKx &= ~(_BV(TOIEx)); + } + backlight_pins_off(); + } else { + if (!OCRxx) { + TIMSKx |= _BV(OCIExA); + TIMSKx |= _BV(TOIEx); + } + } + // Set the brightness + set_pwm(cie_lightness(rescale_limit_val(ICRx * (uint32_t)level / BACKLIGHT_LEVELS))); +} + +void backlight_task(void) {} + +#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_halt = BREATHING_NO_HALT; +static uint16_t breathing_counter = 0; + +static uint8_t breath_scale_counter = 1; +/* Run the breathing loop at ~120Hz*/ +const uint8_t breathing_ISR_frequency = 120; + +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) + +# define breathing_min() \ + do { \ + breathing_counter = 0; \ + } while (0) +# define breathing_max() \ + do { \ + breathing_counter = get_breathing_period() * breathing_ISR_frequency / 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; +} + +/* 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(); +} + +void breathing_task(void) { + // Only run this ISR at ~120 Hz + if (breath_scale_counter++ == BREATHING_SCALE_FACTOR) { + breath_scale_counter = 1; + } else { + return; + } + uint16_t interval = (uint16_t)get_breathing_period() * breathing_ISR_frequency / BREATHING_STEPS; + // resetting after one period to prevent ugly reset at overflow. + breathing_counter = (breathing_counter + 1) % (get_breathing_period() * breathing_ISR_frequency); + uint8_t index = breathing_counter / interval; + // limit index to max step value + if (index >= BREATHING_STEPS) { + index = BREATHING_STEPS - 1; + } + + if (((breathing_halt == BREATHING_HALT_ON) && (index == BREATHING_STEPS / 2)) || ((breathing_halt == BREATHING_HALT_OFF) && (index == BREATHING_STEPS - 1))) { + breathing_interrupt_disable(); + } + + // Set PWM to a brightnessvalue scaled to the configured resolution + set_pwm(cie_lightness(rescale_limit_val(scale_backlight((uint32_t)pgm_read_byte(&breathing_table[index]) * ICRx / 255)))); +} + +#endif // BACKLIGHT_BREATHING + +void backlight_init_ports(void) { + // Setup backlight pin as output and output to on state. + backlight_pins_init(); + + // 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 + + // 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; + ICRx = BACKLIGHT_RESOLUTION; + + backlight_init(); + +#ifdef BACKLIGHT_BREATHING + if (is_backlight_breathing()) { + breathing_enable(); + } +#endif +}