Initial migration of software PWM backlight (#6709)

* Initial migration of software PWM backlight

* First pass at backlight driver docs

* Correct driver name in docs

* Run backlight_task when using BACKLIGHT_PINS

* Resolve backlight docs TODOs
master
Joel Challis 2019-11-02 21:20:03 +00:00 committed by GitHub
parent ff8d436946
commit 4531cc874e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 61 deletions

View File

@ -234,7 +234,7 @@ ifeq ($(strip $(BACKLIGHT_CUSTOM_DRIVER)), yes)
BACKLIGHT_ENABLE = custom BACKLIGHT_ENABLE = custom
endif endif
VALID_BACKLIGHT_TYPES := yes custom VALID_BACKLIGHT_TYPES := yes software custom
BACKLIGHT_ENABLE ?= no BACKLIGHT_ENABLE ?= no
ifneq ($(strip $(BACKLIGHT_ENABLE)), no) ifneq ($(strip $(BACKLIGHT_ENABLE)), no)
@ -246,11 +246,13 @@ ifneq ($(strip $(BACKLIGHT_ENABLE)), no)
CIE1931_CURVE = yes CIE1931_CURVE = yes
endif endif
COMMON_VPATH += $(QUANTUM_DIR)/backlight COMMON_VPATH += $(QUANTUM_DIR)/backlight
SRC += $(QUANTUM_DIR)/backlight/backlight.c SRC += $(QUANTUM_DIR)/backlight/backlight.c
OPT_DEFS += -DBACKLIGHT_ENABLE OPT_DEFS += -DBACKLIGHT_ENABLE
ifeq ($(strip $(BACKLIGHT_ENABLE)), software)
SRC += $(QUANTUM_DIR)/backlight/backlight_soft.c
else
ifeq ($(strip $(BACKLIGHT_ENABLE)), custom) ifeq ($(strip $(BACKLIGHT_ENABLE)), custom)
OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER OPT_DEFS += -DBACKLIGHT_CUSTOM_DRIVER
endif endif
@ -261,6 +263,7 @@ ifneq ($(strip $(BACKLIGHT_ENABLE)), no)
SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c SRC += $(QUANTUM_DIR)/backlight/backlight_arm.c
endif endif
endif endif
endif
VALID_WS2812_DRIVER_TYPES := bitbang pwm spi i2c VALID_WS2812_DRIVER_TYPES := bitbang pwm spi i2c

View File

@ -6,14 +6,16 @@ QMK is able to control the brightness of these LEDs by switching them on and off
The MCU can only supply so much current to its GPIO pins. Instead of powering the backlight directly from the MCU, the backlight pin is connected to a transistor or MOSFET that switches the power to the LEDs. The MCU can only supply so much current to its GPIO pins. Instead of powering the backlight directly from the MCU, the backlight pin is connected to a transistor or MOSFET that switches the power to the LEDs.
## Usage ## Driver configuration
Most keyboards have backlighting enabled by default if they support it, but if it is not working for you, check that your `rules.mk` includes the following: Most keyboards have backlighting enabled by default if they support it, but if it is not working for you, check that your `rules.mk` includes the following:
```make ```makefile
BACKLIGHT_ENABLE = yes BACKLIGHT_ENABLE = software # Valid driver values are 'yes,software,no'
``` ```
See below for help on individual drivers.
## Keycodes ## Keycodes
Once enabled the following keycodes below can be used to change the backlight level. Once enabled the following keycodes below can be used to change the backlight level.
@ -27,8 +29,54 @@ Once enabled the following keycodes below can be used to change the backlight le
|`BL_DEC` |Decrease the backlight level | |`BL_DEC` |Decrease the backlight level |
|`BL_BRTG`|Toggle backlight breathing | |`BL_BRTG`|Toggle backlight breathing |
## Backlight Functions
|Function |Description |
|----------|-----------------------------------------------------------|
|`backlight_toggle()` |Turn the backlight on or off |
|`backlight_enable()` |Turn the backlight on |
|`backlight_disable()` |Turn the backlight off |
|`backlight_step()` |Cycle through backlight levels |
|`backlight_increase()` |Increase the backlight level |
|`backlight_decrease()` |Decrease the backlight level |
|`backlight_level(x)` |Sets the backlight level to specified level |
|`get_backlight_level()` |Return the current backlight level |
|`is_backlight_enabled()`|Return whether the backlight is currently on |
### Backlight Breathing Functions
|Function |Description |
|----------|---------------------------------------------------|
|`breathing_toggle()` |Turn the backlight breathing on or off |
|`breathing_enable()` |Turns on backlight breathing |
|`breathing_disable()` |Turns off backlight breathing |
## Common Driver Configuration
To change the behavior of the backlighting, `#define` these in your `config.h`:
|Define |Default |Description |
|---------------------|-------------|--------------------------------------------------------------------------------------|
|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) |
|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) |
|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if supported |
|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low |
### Backlight On State
Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*.
Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead.
This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define.
## AVR driver ## AVR driver
On AVR boards, the default driver currently sniffs the configuration to pick the best scenario. To enable it, add this to your rules.mk:
```makefile
BACKLIGHT_ENABLE = yes
```
### Caveats ### Caveats
Hardware PWM is supported according to the following table: Hardware PWM is supported according to the following table:
@ -64,21 +112,9 @@ When both timers are in use for Audio, the backlight PWM will not use a hardware
To change the behavior of the backlighting, `#define` these in your `config.h`: To change the behavior of the backlighting, `#define` these in your `config.h`:
|Define |Default |Description | |Define |Default |Description |
|---------------------|-------------|-------------------------------------------------------------------------------------------------------------| |---------------------|-------------|--------------------------------------------------------------------------------------------------------------|
|`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this | |`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this |
|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information | |`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information |
|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) |
|`BACKLIGHT_CAPS_LOCK`|*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) |
|`BACKLIGHT_BREATHING`|*Not defined*|Enable backlight breathing, if supported |
|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
|`BACKLIGHT_ON_STATE` |`0` |The state of the backlight pin when the backlight is "on" - `1` for high, `0` for low |
### Backlight On State
Most backlight circuits are driven by an N-channel MOSFET or NPN transistor. This means that to turn the transistor *on* and light the LEDs, you must drive the backlight pin, connected to the gate or base, *high*.
Sometimes, however, a P-channel MOSFET, or a PNP transistor is used. In this case, when the transistor is on, the pin is driven *low* instead.
This functionality is configured at the keyboard level with the `BACKLIGHT_ON_STATE` define.
### Multiple backlight pins ### Multiple backlight pins
@ -103,7 +139,7 @@ In this way `OCRxx` essentially controls the duty cycle of the LEDs, and thus th
The breathing effect is achieved by registering an interrupt handler for `TIMER1_OVF_vect` that is called whenever the counter resets, roughly 244 times per second. 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. In this handler, the value of an incrementing counter is mapped onto a precomputed brightness curve. To turn off breathing, the interrupt handler is simply disabled, and the brightness reset to the level stored in EEPROM.
### Software PWM Implementation ### Timer Assisted PWM Implementation
When `BACKLIGHT_PIN` is not set to a hardware backlight pin, QMK will use a hardware timer configured to trigger software interrupts. This time will count up to `ICRx` (by default `0xFFFF`) before resetting to 0. When `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. When resetting to 0, the CPU will fire an OVF (overflow) interrupt that will turn the LEDs on, starting the duty cycle.
@ -114,9 +150,14 @@ The breathing effect is the same as in the hardware PWM implementation.
## ARM Driver ## ARM Driver
While still in its early stages, ARM backlight support aims to eventually have feature parity with AVR. To enable it, add this to your rules.mk:
```makefile
BACKLIGHT_ENABLE = yes
```
### Caveats ### Caveats
Currently only hardware PWM is supported, and does not provide automatic configuration. Currently only hardware PWM is supported, not timer assisted, and does not provide automatic configuration.
?> STMF072 support is being investigated. ?> STMF072 support is being investigated.
@ -130,30 +171,32 @@ To change the behavior of the backlighting, `#define` these in your `config.h`:
|`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use, see ST datasheets for pin to PWM timer mapping. Unless you are designing your own keyboard, you shouldn't need to change this| |`BACKLIGHT_PWM_DRIVER` |`PWMD4` |The PWM driver to use, see ST datasheets for pin to PWM timer mapping. Unless you are designing your own keyboard, you shouldn't need to change this|
|`BACKLIGHT_PWM_CHANNEL` |`3` |The PWM channel to use, see ST datasheets for pin to PWM channel mapping. Unless you are designing your own keyboard, you shouldn't need to change this| |`BACKLIGHT_PWM_CHANNEL` |`3` |The PWM channel to use, see ST datasheets for pin to PWM channel mapping. Unless you are designing your own keyboard, you shouldn't need to change this|
|`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use, see ST datasheets for pin AF mapping. Unless you are designing your own keyboard, you shouldn't need to change this| |`BACKLIGHT_PAL_MODE` |`2` |The pin alternative function to use, see ST datasheets for pin AF mapping. Unless you are designing your own keyboard, you shouldn't need to change this|
|`BACKLIGHT_LEVELS` |`3` |The number of brightness levels (maximum 31 excluding off) |
|`BACKLIGHT_CAPS_LOCK` |*Not defined*|Enable Caps Lock indicator using backlight (for keyboards without dedicated LED) |
|`BACKLIGHT_BREATHING` |*Not defined*|Enable backlight breathing, if supported |
|`BREATHING_PERIOD` |`6` |The length of one backlight "breath" in seconds |
## Backlight Functions ## Software PWM Driver
|Function |Description | Emulation of PWM while running other keyboard tasks, it offers maximum hardware compatibility without extra platform configuration. The tradeoff is the backlight might jitter when the keyboard is busy. To enable, add this to your rules.mk:
|----------|-----------------------------------------------------------| ```makefile
|`backlight_toggle()` |Turn the backlight on or off | BACKLIGHT_ENABLE = software
|`backlight_enable()` |Turn the backlight on | ```
|`backlight_disable()` |Turn the backlight off |
|`backlight_step()` |Cycle through backlight levels |
|`backlight_increase()` |Increase the backlight level |
|`backlight_decrease()` |Decrease the backlight level |
|`backlight_level(x)` |Sets the backlight level, from 0 to |
| |`BACKLIGHT_LEVELS` |
|`get_backlight_level()` |Return the current backlight level |
|`is_backlight_enabled()`|Return whether the backlight is currently on |
### Backlight Breathing Functions ### Software PWM Configuration
|Function |Description | To change the behavior of the backlighting, `#define` these in your `config.h`:
|----------|----------------------------------------------------------|
|`breathing_toggle()` |Turn the backlight breathing on or off | |Define |Default |Description |
|`breathing_enable()` |Turns on backlight breathing | |-----------------|-------------|-------------------------------------------------------------------------------------------------------------|
|`breathing_disable()` |Turns off backlight breathing | |`BACKLIGHT_PIN` |`B7` |The pin that controls the LEDs. Unless you are designing your own keyboard, you shouldn't need to change this|
|`BACKLIGHT_PINS` |*Not defined*|experimental: see below for more information |
### Multiple backlight pins
Most keyboards have only one backlight pin which control all backlight LEDs (especially if the backlight is connected to an hardware PWM pin).
In software PWM, it is possible to define multiple backlight pins. All those pins will be turned on and off at the same time during the PWM duty cycle.
This feature allows to set for instance the Caps Lock LED (or any other controllable LED) brightness at the same level as the other LEDs of the backlight. This is useful if you have mapped LCTRL in place of Caps Lock and you need the Caps Lock LED to be part of the backlight instead of being activated when Caps Lock is on.
To activate multiple backlight pins, you need to add something like this to your user `config.h`:
```c
#undef BACKLIGHT_PIN
#define BACKLIGHT_PINS { F5, B2 }
```

View File

@ -159,7 +159,7 @@
# define BACKLIGHT_ON_STATE 0 # define BACKLIGHT_ON_STATE 0
# endif # endif
void backlight_on(uint8_t backlight_pin) { void backlight_on(pin_t backlight_pin) {
# if BACKLIGHT_ON_STATE == 0 # if BACKLIGHT_ON_STATE == 0
writePinLow(backlight_pin); writePinLow(backlight_pin);
# else # else
@ -167,7 +167,7 @@ void backlight_on(uint8_t backlight_pin) {
# endif # endif
} }
void backlight_off(uint8_t backlight_pin) { void backlight_off(pin_t backlight_pin) {
# if BACKLIGHT_ON_STATE == 0 # if BACKLIGHT_ON_STATE == 0
writePinHigh(backlight_pin); writePinHigh(backlight_pin);
# else # else
@ -191,16 +191,16 @@ void backlight_off(uint8_t backlight_pin) {
# define FOR_EACH_LED(x) \ # define FOR_EACH_LED(x) \
for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \ for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \
uint8_t backlight_pin = backlight_pins[i]; \ pin_t backlight_pin = backlight_pins[i]; \
{ x } \ { x } \
} }
static const uint8_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT; static const pin_t backlight_pins[BACKLIGHT_LED_COUNT] = BACKLIGHT_PIN_INIT;
# else // full hardware PWM # else // full hardware PWM
// we support only one backlight pin // we support only one backlight pin
static const uint8_t backlight_pin = BACKLIGHT_PIN; static const pin_t backlight_pin = BACKLIGHT_PIN;
# define FOR_EACH_LED(x) x # define FOR_EACH_LED(x) x
# endif # endif

View File

@ -0,0 +1,66 @@
#include "quantum.h"
#include "backlight.h"
#if !defined(BACKLIGHT_PIN) && !defined(BACKLIGHT_PINS)
# error "Backlight pin/pins not defined. Please configure."
#endif
#ifdef BACKLIGHT_BREATHING
# error "Backlight breathing is not available for software PWM. Please disable."
#endif
#ifndef BACKLIGHT_ON_STATE
# define BACKLIGHT_ON_STATE 0
#endif
#ifdef BACKLIGHT_PINS
# define BACKLIGHT_PIN_INIT BACKLIGHT_PINS
#else
# define BACKLIGHT_PIN_INIT \
{ BACKLIGHT_PIN }
#endif
static const pin_t backlight_pins[] = BACKLIGHT_PIN_INIT;
#define BACKLIGHT_LED_COUNT (sizeof(backlight_pins) / sizeof(pin_t))
#define FOR_EACH_LED(x) \
for (uint8_t i = 0; i < BACKLIGHT_LED_COUNT; i++) { \
pin_t backlight_pin = backlight_pins[i]; \
{ x } \
}
void backlight_on(pin_t backlight_pin) {
#if BACKLIGHT_ON_STATE == 0
writePinLow(backlight_pin);
#else
writePinHigh(backlight_pin);
#endif
}
void backlight_off(pin_t backlight_pin) {
#if BACKLIGHT_ON_STATE == 0
writePinHigh(backlight_pin);
#else
writePinLow(backlight_pin);
#endif
}
void backlight_init_ports(void) {
// Setup backlight pin as output and output to on state.
FOR_EACH_LED(setPinOutput(backlight_pin); backlight_on(backlight_pin);)
}
void backlight_task(void) {
static uint8_t backlight_tick = 0;
if ((0xFFFF >> (get_backlight_level() * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) {
FOR_EACH_LED(backlight_on(backlight_pin);)
} else {
FOR_EACH_LED(backlight_off(backlight_pin);)
}
backlight_tick = (backlight_tick + 1) % 16;
}
void backlight_set(uint8_t level) {
// noop as backlight_task uses get_backlight_level()
}

View File

@ -981,7 +981,7 @@ void matrix_scan_quantum() {
#if defined(BACKLIGHT_ENABLE) #if defined(BACKLIGHT_ENABLE)
# if defined(LED_MATRIX_ENABLE) # if defined(LED_MATRIX_ENABLE)
led_matrix_task(); led_matrix_task();
# elif defined(BACKLIGHT_PIN) # elif defined(BACKLIGHT_PIN) || defined(BACKLIGHT_PINS)
backlight_task(); backlight_task();
# endif # endif
#endif #endif

View File

@ -257,8 +257,8 @@ void tap_code16(uint16_t code);
void backlight_init_ports(void); void backlight_init_ports(void);
void backlight_task(void); void backlight_task(void);
void backlight_task_internal(void); void backlight_task_internal(void);
void backlight_on(uint8_t backlight_pin); void backlight_on(pin_t backlight_pin);
void backlight_off(uint8_t backlight_pin); void backlight_off(pin_t backlight_pin);
# ifdef BACKLIGHT_BREATHING # ifdef BACKLIGHT_BREATHING
void breathing_task(void); void breathing_task(void);