diff options
-rw-r--r-- | common_features.mk | 20 | ||||
-rw-r--r-- | docs/feature_led_matrix.md | 217 | ||||
-rw-r--r-- | drivers/issi/is31fl3731-simple.c | 240 | ||||
-rw-r--r-- | drivers/issi/is31fl3731-simple.h | 209 | ||||
-rw-r--r-- | quantum/led_matrix.c | 404 | ||||
-rw-r--r-- | quantum/led_matrix.h | 142 | ||||
-rw-r--r-- | quantum/led_matrix_drivers.c | 147 | ||||
-rw-r--r-- | quantum/rgb_matrix.h | 12 |
8 files changed, 1380 insertions, 11 deletions
diff --git a/common_features.mk b/common_features.mk index 8c3361732c..8c7043cb7e 100644 --- a/common_features.mk +++ b/common_features.mk @@ -114,8 +114,26 @@ ifeq ($(strip $(RGBLIGHT_ENABLE)), yes) endif endif -RGB_MATRIX_ENABLE ?= no VALID_MATRIX_TYPES := yes IS31FL3731 IS31FL3733 custom + +LED_MATRIX_ENABLE ?= no +ifneq ($(strip $(LED_MATRIX_ENABLE)), no) +ifeq ($(filter $(LED_MATRIX_ENABLE),$(VALID_MATRIX_TYPES)),) + $(error LED_MATRIX_ENABLE="$(LED_MATRIX_ENABLE)" is not a valid matrix type) +endif + OPT_DEFS += -DLED_MATRIX_ENABLE + SRC += $(QUANTUM_DIR)/led_matrix.c + SRC += $(QUANTUM_DIR)/led_matrix_drivers.c +endif + +ifeq ($(strip $(LED_MATRIX_ENABLE)), IS31FL3731) + OPT_DEFS += -DIS31FL3731 + COMMON_VPATH += $(DRIVER_PATH)/issi + SRC += is31fl3731-simple.c + SRC += i2c_master.c +endif + +RGB_MATRIX_ENABLE ?= no ifneq ($(strip $(RGB_MATRIX_ENABLE)), no) ifeq ($(filter $(RGB_MATRIX_ENABLE),$(VALID_MATRIX_TYPES)),) $(error RGB_MATRIX_ENABLE="$(RGB_MATRIX_ENABLE)" is not a valid matrix type) diff --git a/docs/feature_led_matrix.md b/docs/feature_led_matrix.md new file mode 100644 index 0000000000..5a62cc5309 --- /dev/null +++ b/docs/feature_led_matrix.md @@ -0,0 +1,217 @@ +# RGB Matrix Lighting + +## Driver configuration + +### IS31FL3731 + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3731 RGB controller. To enable it, add this to your `rules.mk`: + + LED_MATRIX_ENABLE = IS31FL3731 + +Configure the hardware via your `config.h`: + + // This is a 7-bit address, that gets left-shifted and bit 0 + // set to 0 for write, 1 for read (as per I2C protocol) + // The address will vary depending on your wiring: + // 0b1110100 AD <-> GND + // 0b1110111 AD <-> VCC + // 0b1110101 AD <-> SCL + // 0b1110110 AD <-> SDA + #define LED_DRIVER_ADDR_1 0b1110100 + #define LED_DRIVER_ADDR_2 0b1110110 + + #define LED_DRIVER_COUNT 2 + #define LED_DRIVER_1_LED_TOTAL 25 + #define LED_DRIVER_2_LED_TOTAL 24 + #define LED_DRIVER_LED_TOTAL LED_DRIVER_1_LED_TOTAL + LED_DRIVER_2_LED_TOTAL + +Currently only 2 drivers are supported, but it would be trivial to support all 4 combinations. + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + + const is31_led g_is31_leds[DRIVER_LED_TOTAL] = { + /* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, C1_3, C2_3, C3_3}, + .... + } + +Where `Cx_y` is the location of the LED in the matrix defined by [the datasheet](http://www.issi.com/WW/pdf/31FL3731.pdf) and the header file `drivers/issi/is31fl3731.h`. The `driver` is the index of the driver you defined in your `config.h` (`0` or `1` right now). + +### IS31FL3733 + +There is basic support for addressable RGB matrix lighting with the I2C IS31FL3733 RGB controller. To enable it, add this to your `rules.mk`: + + RGB_MATRIX_ENABLE = IS31FL3733 + +Configure the hardware via your `config.h`: + + // This is a 7-bit address, that gets left-shifted and bit 0 + // set to 0 for write, 1 for read (as per I2C protocol) + // The address will vary depending on your wiring: + // 00 <-> GND + // 01 <-> SCL + // 10 <-> SDA + // 11 <-> VCC + // ADDR1 represents A1:A0 of the 7-bit address. + // ADDR2 represents A3:A2 of the 7-bit address. + // The result is: 0b101(ADDR2)(ADDR1) + #define DRIVER_ADDR_1 0b1010000 + #define DRIVER_ADDR_2 0b1010000 // this is here for compliancy reasons. + + #define DRIVER_COUNT 1 + #define DRIVER_1_LED_TOTAL 64 + #define DRIVER_LED_TOTAL DRIVER_1_LED_TOTAL + +Currently only a single drivers is supported, but it would be trivial to support all 4 combinations. For now define `DRIVER_ADDR_2` as `DRIVER_ADDR_1` + +Define these arrays listing all the LEDs in your `<keyboard>.c`: + + const is31_led g_is31_leds[DRIVER_LED_TOTAL] = { + /* Refer to IS31 manual for these locations + * driver + * | R location + * | | G location + * | | | B location + * | | | | */ + {0, B_1, A_1, C_1}, + .... + } + +Where `X_Y` is the location of the LED in the matrix defined by [the datasheet](http://www.issi.com/WW/pdf/31FL3733.pdf) and the header file `drivers/issi/is31fl3733.h`. The `driver` is the index of the driver you defined in your `config.h` (Only `0` right now). + +From this point forward the configuration is the same for all the drivers. + + const rgb_led g_rgb_leds[DRIVER_LED_TOTAL] = { + /* {row | col << 4} + * | {x=0..224, y=0..64} + * | | modifier + * | | | */ + {{0|(0<<4)}, {20.36*0, 21.33*0}, 1}, + {{0|(1<<4)}, {20.36*1, 21.33*0}, 1}, + .... + } + +The format for the matrix position used in this array is `{row | (col << 4)}`. The `x` is between (inclusive) 0-224, and `y` is between (inclusive) 0-64. The easiest way to calculate these positions is: + + x = 224 / ( NUMBER_OF_ROWS - 1 ) * ROW_POSITION + y = 64 / (NUMBER_OF_COLS - 1 ) * COL_POSITION + +Where all variables are decimels/floats. + +`modifier` is a boolean, whether or not a certain key is considered a modifier (used in some effects). + +## Keycodes + +All RGB keycodes are currently shared with the RGBLIGHT system: + + * `RGB_TOG` - toggle + * `RGB_MOD` - cycle through modes + * `RGB_HUI` - increase hue + * `RGB_HUD` - decrease hue + * `RGB_SAI` - increase saturation + * `RGB_SAD` - decrease saturation + * `RGB_VAI` - increase value + * `RGB_VAD` - decrease value + * `RGB_SPI` - increase speed effect (no EEPROM support) + * `RGB_SPD` - decrease speed effect (no EEPROM support) + + + * `RGB_MODE_*` keycodes will generally work, but are not currently mapped to the correct effects for the RGB Matrix system + +## RGB Matrix Effects + +These are the effects that are currently available: + + enum rgb_matrix_effects { + RGB_MATRIX_SOLID_COLOR = 1, + RGB_MATRIX_ALPHAS_MODS, + RGB_MATRIX_DUAL_BEACON, + RGB_MATRIX_GRADIENT_UP_DOWN, + RGB_MATRIX_RAINDROPS, + RGB_MATRIX_CYCLE_ALL, + RGB_MATRIX_CYCLE_LEFT_RIGHT, + RGB_MATRIX_CYCLE_UP_DOWN, + RGB_MATRIX_RAINBOW_BEACON, + RGB_MATRIX_RAINBOW_PINWHEELS, + RGB_MATRIX_RAINBOW_MOVING_CHEVRON, + RGB_MATRIX_JELLYBEAN_RAINDROPS, + RGB_MATRIX_DIGITAL_RAIN, + #ifdef RGB_MATRIX_KEYPRESSES + RGB_MATRIX_SOLID_REACTIVE, + RGB_MATRIX_SPLASH, + RGB_MATRIX_MULTISPLASH, + RGB_MATRIX_SOLID_SPLASH, + RGB_MATRIX_SOLID_MULTISPLASH, + #endif + RGB_MATRIX_EFFECT_MAX + }; + +You can disable a single effect by defining `DISABLE_[EFFECT_NAME]` in your `config.h`: + + +|Define |Description | +|---------------------------------------------------|--------------------------------------------| +|`#define DISABLE_RGB_MATRIX_ALPHAS_MODS` |Disables `RGB_MATRIX_ALPHAS_MODS` | +|`#define DISABLE_RGB_MATRIX_DUAL_BEACON` |Disables `RGB_MATRIX_DUAL_BEACON` | +|`#define DISABLE_RGB_MATRIX_GRADIENT_UP_DOWN` |Disables `RGB_MATRIX_GRADIENT_UP_DOWN` | +|`#define DISABLE_RGB_MATRIX_RAINDROPS` |Disables `RGB_MATRIX_RAINDROPS` | +|`#define DISABLE_RGB_MATRIX_CYCLE_ALL` |Disables `RGB_MATRIX_CYCLE_ALL` | +|`#define DISABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT` |Disables `RGB_MATRIX_CYCLE_LEFT_RIGHT` | +|`#define DISABLE_RGB_MATRIX_CYCLE_UP_DOWN` |Disables `RGB_MATRIX_CYCLE_UP_DOWN` | +|`#define DISABLE_RGB_MATRIX_RAINBOW_BEACON` |Disables `RGB_MATRIX_RAINBOW_BEACON` | +|`#define DISABLE_RGB_MATRIX_RAINBOW_PINWHEELS` |Disables `RGB_MATRIX_RAINBOW_PINWHEELS` | +|`#define DISABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON`|Disables `RGB_MATRIX_RAINBOW_MOVING_CHEVRON`| +|`#define DISABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS` |Disables `RGB_MATRIX_JELLYBEAN_RAINDROPS` | +|`#define DISABLE_RGB_MATRIX_DIGITAL_RAIN` |Disables `RGB_MATRIX_DIGITAL_RAIN` | +|`#define DISABLE_RGB_MATRIX_SOLID_REACTIVE` |Disables `RGB_MATRIX_SOLID_REACTIVE` | +|`#define DISABLE_RGB_MATRIX_SPLASH` |Disables `RGB_MATRIX_SPLASH` | +|`#define DISABLE_RGB_MATRIX_MULTISPLASH` |Disables `RGB_MATRIX_MULTISPLASH` | +|`#define DISABLE_RGB_MATRIX_SOLID_SPLASH` |Disables `RGB_MATRIX_SOLID_SPLASH` | +|`#define DISABLE_RGB_MATRIX_SOLID_MULTISPLASH` |Disables `RGB_MATRIX_SOLID_MULTISPLASH` | + + +## Custom layer effects + +Custom layer effects can be done by defining this in your `<keyboard>.c`: + + void rgb_matrix_indicators_kb(void) { + rgb_matrix_set_color(index, red, green, blue); + } + +A similar function works in the keymap as `rgb_matrix_indicators_user`. + +## Additional `config.h` Options + + #define RGB_MATRIX_KEYPRESSES // reacts to keypresses (will slow down matrix scan by a lot) + #define RGB_MATRIX_KEYRELEASES // reacts to keyreleases (not recommened) + #define RGB_DISABLE_AFTER_TIMEOUT 0 // number of ticks to wait until disabling effects + #define RGB_DISABLE_WHEN_USB_SUSPENDED false // turn off effects when suspended + #define RGB_MATRIX_SKIP_FRAMES 1 // number of frames to skip when displaying animations (0 is full effect) if not defined defaults to 1 + #define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200 // limits maximum brightness of LEDs to 200 out of 255. If not defined maximum brightness is set to 255 + +## EEPROM storage + +The EEPROM for it is currently shared with the RGBLIGHT system (it's generally assumed only one RGB would be used at a time), but could be configured to use its own 32bit address with: + + #define EECONFIG_RGB_MATRIX (uint32_t *)16 + +Where `16` is an unused index from `eeconfig.h`. + +## Suspended state + +To use the suspend feature, add this to your `<keyboard>.c`: + + void suspend_power_down_kb(void) + { + rgb_matrix_set_suspend_state(true); + } + + void suspend_wakeup_init_kb(void) + { + rgb_matrix_set_suspend_state(false); + } diff --git a/drivers/issi/is31fl3731-simple.c b/drivers/issi/is31fl3731-simple.c new file mode 100644 index 0000000000..46d51dac70 --- /dev/null +++ b/drivers/issi/is31fl3731-simple.c @@ -0,0 +1,240 @@ +/* Copyright 2017 Jason Williams + * Copyright 2018 Jack Humbert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef __AVR__ +#include <avr/interrupt.h> +#include <avr/io.h> +#include <util/delay.h> +#else +#include "wait.h" +#endif + +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include "is31fl3731-simple.h" +#include "i2c_master.h" +#include "progmem.h" + +// This is a 7-bit address, that gets left-shifted and bit 0 +// set to 0 for write, 1 for read (as per I2C protocol) +// The address will vary depending on your wiring: +// 0b1110100 AD <-> GND +// 0b1110111 AD <-> VCC +// 0b1110101 AD <-> SCL +// 0b1110110 AD <-> SDA +#define ISSI_ADDR_DEFAULT 0x74 + +#define ISSI_REG_CONFIG 0x00 +#define ISSI_REG_CONFIG_PICTUREMODE 0x00 +#define ISSI_REG_CONFIG_AUTOPLAYMODE 0x08 +#define ISSI_REG_CONFIG_AUDIOPLAYMODE 0x18 + +#define ISSI_CONF_PICTUREMODE 0x00 +#define ISSI_CONF_AUTOFRAMEMODE 0x04 +#define ISSI_CONF_AUDIOMODE 0x08 + +#define ISSI_REG_PICTUREFRAME 0x01 + +#define ISSI_REG_SHUTDOWN 0x0A +#define ISSI_REG_AUDIOSYNC 0x06 + +#define ISSI_COMMANDREGISTER 0xFD +#define ISSI_BANK_FUNCTIONREG 0x0B // helpfully called 'page nine' + +#ifndef ISSI_TIMEOUT + #define ISSI_TIMEOUT 100 +#endif + +#ifndef ISSI_PERSISTENCE + #define ISSI_PERSISTENCE 0 +#endif + +// Transfer buffer for TWITransmitData() +uint8_t g_twi_transfer_buffer[20]; + +// These buffers match the IS31FL3731 PWM registers 0x24-0xB3. +// Storing them like this is optimal for I2C transfers to the registers. +// We could optimize this and take out the unused registers from these +// buffers and the transfers in IS31FL3731_write_pwm_buffer() but it's +// probably not worth the extra complexity. +uint8_t g_pwm_buffer[DRIVER_COUNT][144]; +bool g_pwm_buffer_update_required = false; + +uint8_t g_led_control_registers[DRIVER_COUNT][18] = { { 0 }, { 0 } }; +bool g_led_control_registers_update_required = false; + +// This is the bit pattern in the LED control registers +// (for matrix A, add one to register for matrix B) +// +// reg - b7 b6 b5 b4 b3 b2 b1 b0 +// 0x00 - R08,R07,R06,R05,R04,R03,R02,R01 +// 0x02 - G08,G07,G06,G05,G04,G03,G02,R00 +// 0x04 - B08,B07,B06,B05,B04,B03,G01,G00 +// 0x06 - - , - , - , - , - ,B02,B01,B00 +// 0x08 - - , - , - , - , - , - , - , - +// 0x0A - B17,B16,B15, - , - , - , - , - +// 0x0C - G17,G16,B14,B13,B12,B11,B10,B09 +// 0x0E - R17,G15,G14,G13,G12,G11,G10,G09 +// 0x10 - R16,R15,R14,R13,R12,R11,R10,R09 + + +void IS31FL3731_write_register(uint8_t addr, uint8_t reg, uint8_t data) { + g_twi_transfer_buffer[0] = reg; + g_twi_transfer_buffer[1] = data; + + #if ISSI_PERSISTENCE > 0 + for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { + if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT) == 0) { + break; + } + } + #else + i2c_transmit(addr << 1, g_twi_transfer_buffer, 2, ISSI_TIMEOUT); + #endif +} + +void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer) { + // assumes bank is already selected + + // transmit PWM registers in 9 transfers of 16 bytes + // g_twi_transfer_buffer[] is 20 bytes + + // iterate over the pwm_buffer contents at 16 byte intervals + for (int i = 0; i < 144; i += 16) { + // set the first register, e.g. 0x24, 0x34, 0x44, etc. + g_twi_transfer_buffer[0] = 0x24 + i; + // copy the data from i to i+15 + // device will auto-increment register for data after the first byte + // thus this sets registers 0x24-0x33, 0x34-0x43, etc. in one transfer + for (int j = 0; j < 16; j++) { + g_twi_transfer_buffer[1 + j] = pwm_buffer[i + j]; + } + + #if ISSI_PERSISTENCE > 0 + for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { + if (i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT) == 0) + break; + } + #else + i2c_transmit(addr << 1, g_twi_transfer_buffer, 17, ISSI_TIMEOUT); + #endif + } +} + +void IS31FL3731_init(uint8_t addr) { + // In order to avoid the LEDs being driven with garbage data + // in the LED driver's PWM registers, first enable software shutdown, + // then set up the mode and other settings, clear the PWM registers, + // then disable software shutdown. + + // select "function register" bank + IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, ISSI_BANK_FUNCTIONREG); + + // enable software shutdown + IS31FL3731_write_register(addr, ISSI_REG_SHUTDOWN, 0x00); + // this delay was copied from other drivers, might not be needed + // FIXME: Don't we have a wrapper for this already? + #ifdef __AVR__ + _delay_ms(10); + #else + wait_ms(10); + #endif + + // picture mode + IS31FL3731_write_register(addr, ISSI_REG_CONFIG, ISSI_REG_CONFIG_PICTUREMODE); + // display frame 0 + IS31FL3731_write_register(addr, ISSI_REG_PICTUREFRAME, 0x00); + // audio sync off + IS31FL3731_write_register(addr, ISSI_REG_AUDIOSYNC, 0x00); + + // select bank 0 + IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, 0); + + // turn off all LEDs in the LED control register + for (int i = 0x00; i <= 0x11; i++) { + IS31FL3731_write_register(addr, i, 0x00); + } + + // turn off all LEDs in the blink control register (not really needed) + for (int i = 0x12; i <= 0x23; i++) { + IS31FL3731_write_register(addr, i, 0x00); + } + + // set PWM on all LEDs to 0 + for (int i = 0x24; i <= 0xB3; i++) { + IS31FL3731_write_register(addr, i, 0x00); + } + + // select "function register" bank + IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, ISSI_BANK_FUNCTIONREG); + + // disable software shutdown + IS31FL3731_write_register(addr, ISSI_REG_SHUTDOWN, 0x01); + + // select bank 0 and leave it selected. + // most usage after initialization is just writing PWM buffers in bank 0 + // as there's not much point in double-buffering + IS31FL3731_write_register(addr, ISSI_COMMANDREGISTER, 0); + +} + +void IS31FL3731_set_value(int index, uint8_t value) { + if (index >= 0 && index < DRIVER_LED_TOTAL) { + is31_led led = g_is31_leds[index]; + + // Subtract 0x24 to get the second index of g_pwm_buffer + g_pwm_buffer[led.driver][led.v - 0x24] = value; + g_pwm_buffer_update_required = true; + } +} + +void IS31FL3731_set_value_all(uint8_t value) { + for (int i = 0; i < DRIVER_LED_TOTAL; i++) { + IS31FL3731_set_value(i, value); + } +} + +void IS31FL3731_set_led_control_register(uint8_t index, bool value) { + is31_led led = g_is31_leds[index]; + + uint8_t control_register = (led.v - 0x24) / 8; + uint8_t bit_value = (led.v - 0x24) % 8; + + if (value) { + g_led_control_registers[led.driver][control_register] |= (1 << bit_value); + } else { + g_led_control_registers[led.driver][control_register] &= ~(1 << bit_value); + } + + g_led_control_registers_update_required = true; +} + +void IS31FL3731_update_pwm_buffers(uint8_t addr, uint8_t index) { + if (g_pwm_buffer_update_required) { + IS31FL3731_write_pwm_buffer(addr, g_pwm_buffer[index]); + g_pwm_buffer_update_required = false; + } +} + +void IS31FL3731_update_led_control_registers(uint8_t addr, uint8_t index) { + if (g_led_control_registers_update_required) { + for (int i=0; i<18; i++) { + IS31FL3731_write_register(addr, i, g_led_control_registers[index][i]); + } + } +} diff --git a/drivers/issi/is31fl3731-simple.h b/drivers/issi/is31fl3731-simple.h new file mode 100644 index 0000000000..c102837a30 --- /dev/null +++ b/drivers/issi/is31fl3731-simple.h @@ -0,0 +1,209 @@ +/* Copyright 2017 Jason Williams + * Copyright 2018 Jack Humbert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#ifndef IS31FL3731_DRIVER_H +#define IS31FL3731_DRIVER_H + + +typedef struct is31_led { + uint8_t driver:2; + uint8_t v; +} __attribute__((packed)) is31_led; + +extern const is31_led g_is31_leds[DRIVER_LED_TOTAL]; + +void IS31FL3731_init(uint8_t addr); +void IS31FL3731_write_register(uint8_t addr, uint8_t reg, uint8_t data); +void IS31FL3731_write_pwm_buffer(uint8_t addr, uint8_t *pwm_buffer); + +void IS31FL3731_set_value(int index, uint8_t value); +void IS31FL3731_set_value_all(uint8_t value); + +void IS31FL3731_set_led_control_register(uint8_t index, bool value); + +// This should not be called from an interrupt +// (eg. from a timer interrupt). +// Call this while idle (in between matrix scans). +// If the buffer is dirty, it will update the driver with the buffer. +void IS31FL3731_update_pwm_buffers(uint8_t addr, uint8_t index); +void IS31FL3731_update_led_control_registers(uint8_t addr, uint8_t index); + +#define C1_1 0x24 +#define C1_2 0x25 +#define C1_3 0x26 +#define C1_4 0x27 +#define C1_5 0x28 +#define C1_6 0x29 +#define C1_7 0x2A +#define C1_8 0x2B + +#define C1_9 0x2C +#define C1_10 0x2D +#define C1_11 0x2E +#define C1_12 0x2F +#define C1_13 0x30 +#define C1_14 0x31 +#define C1_15 0x32 +#define C1_16 0x33 + +#define C2_1 0x34 +#define C2_2 0x35 +#define C2_3 0x36 +#define C2_4 0x37 +#define C2_5 0x38 +#define C2_6 0x39 +#define C2_7 0x3A +#define C2_8 0x3B + +#define C2_9 0x3C +#define C2_10 0x3D +#define C2_11 0x3E +#define C2_12 0x3F +#define C2_13 0x40 +#define C2_14 0x41 +#define C2_15 0x42 +#define C2_16 0x43 + +#define C3_1 0x44 +#define C3_2 0x45 +#define C3_3 0x46 +#define C3_4 0x47 +#define C3_5 0x48 +#define C3_6 0x49 +#define C3_7 0x4A +#define C3_8 0x4B + +#define C3_9 0x4C +#define C3_10 0x4D +#define C3_11 0x4E +#define C3_12 0x4F +#define C3_13 0x50 +#define C3_14 0x51 +#define C3_15 0x52 +#define C3_16 0x53 + +#define C4_1 0x54 +#define C4_2 0x55 +#define C4_3 0x56 +#define C4_4 0x57 +#define C4_5 0x58 +#define C4_6 0x59 +#define C4_7 0x5A +#define C4_8 0x5B + +#define C4_9 0x5C +#define C4_10 0x5D +#define C4_11 0x5E +#define C4_12 0x5F +#define C4_13 0x60 +#define C4_14 0x61 +#define C4_15 0x62 +#define C4_16 0x63 + +#define C5_1 0x64 +#define C5_2 0x65 +#define C5_3 0x66 +#define C5_4 0x67 +#define C5_5 0x68 +#define C5_6 0x69 +#define C5_7 0x6A +#define C5_8 0x6B + +#define C5_9 0x6C +#define C5_10 0x6D +#define C5_11 0x6E +#define C5_12 0x6F +#define C5_13 0x70 +#define C5_14 0x71 +#define C5_15 0x72 +#define C5_16 0x73 + +#define C6_1 0x74 +#define C6_2 0x75 +#define C6_3 0x76 +#define C6_4 0x77 +#define C6_5 0x78 +#define C6_6 0x79 +#define C6_7 0x7A +#define C6_8 0x7B + +#define C6_9 0x7C +#define C6_10 0x7D +#define C6_11 0x7E +#define C6_12 0x7F +#define C6_13 0x80 +#define C6_14 0x81 +#define C6_15 0x82 +#define C6_16 0x83 + +#define C7_1 0x84 +#define C7_2 0x85 +#define C7_3 0x86 +#define C7_4 0x87 +#define C7_5 0x88 +#define C7_6 0x89 +#define C7_7 0x8A +#define C7_8 0x8B + +#define C7_9 0x8C +#define C7_10 0x8D +#define C7_11 0x8E +#define C7_12 0x8F +#define C7_13 0x90 +#define C7_14 0x91 +#define C7_15 0x92 +#define C7_16 0x93 + +#define C8_1 0x94 +#define C8_2 0x95 +#define C8_3 0x96 +#define C8_4 0x97 +#define C8_5 0x98 +#define C8_6 0x99 +#define C8_7 0x9A +#define C8_8 0x9B + +#define C8_9 0x9C +#define C8_10 0x9D +#define C8_11 0x9E +#define C8_12 0x9F +#define C8_13 0xA0 +#define C8_14 0xA1 +#define C8_15 0xA2 +#define C8_16 0xA3 + +#define C9_1 0xA4 +#define C9_2 0xA5 +#define C9_3 0xA6 +#define C9_4 0xA7 +#define C9_5 0xA8 +#define C9_6 0xA9 +#define C9_7 0xAA +#define C9_8 0xAB + +#define C9_9 0xAC +#define C9_10 0xAD +#define C9_11 0xAE +#define C9_12 0xAF +#define C9_13 0xB0 +#define C9_14 0xB1 +#define C9_15 0xB2 +#define C9_16 0xB3 + + +#endif // IS31FL3731_DRIVER_H diff --git a/quantum/led_matrix.c b/quantum/led_matrix.c new file mode 100644 index 0000000000..9a0aa6acd9 --- /dev/null +++ b/quantum/led_matrix.c @@ -0,0 +1,404 @@ +/* Copyright 2017 Jason Williams + * Copyright 2017 Jack Humbert + * Copyright 2018 Yiancar + * Copyright 2019 Clueboard + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdint.h> +#include <stdbool.h> +#include "quantum.h" +#include "led_matrix.h" +#include "progmem.h" +#include "config.h" +#include "eeprom.h" +#include <string.h> +#include <math.h> + +led_config_t led_matrix_config; + +#ifndef MAX + #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#endif + +#ifndef MIN + #define MIN(a,b) ((a) < (b)? (a): (b)) +#endif + +#ifndef LED_DISABLE_AFTER_TIMEOUT + #define LED_DISABLE_AFTER_TIMEOUT 0 +#endif + +#ifndef LED_DISABLE_WHEN_USB_SUSPENDED + #define LED_DISABLE_WHEN_USB_SUSPENDED false +#endif + +#ifndef EECONFIG_LED_MATRIX + #define EECONFIG_LED_MATRIX EECONFIG_RGBLIGHT +#endif + +#if !defined(LED_MATRIX_MAXIMUM_BRIGHTNESS) || LED_MATRIX_MAXIMUM_BRIGHTNESS > 255 + #define LED_MATRIX_MAXIMUM_BRIGHTNESS 255 +#endif + +bool g_suspend_state = false; + +// Global tick at 20 Hz +uint32_t g_tick = 0; + +// Ticks since this key was last hit. +uint8_t g_key_hit[DRIVER_LED_TOTAL]; + +// Ticks since any key was last hit. +uint32_t g_any_key_hit = 0; + +uint32_t eeconfig_read_led_matrix(void) { + return eeprom_read_dword(EECONFIG_LED_MATRIX); +} +void eeconfig_update_led_matrix(uint32_t config_value) { + eeprom_update_dword(EECONFIG_LED_MATRIX, config_value); +} +void eeconfig_update_led_matrix_default(void) { + dprintf("eeconfig_update_led_matrix_default\n"); + led_matrix_config.enable = 1; + led_matrix_config.mode = LED_MATRIX_UNIFORM_BRIGHTNESS; + led_matrix_config.val = 128; + led_matrix_config.speed = 0; + eeconfig_update_led_matrix(led_matrix_config.raw); +} +void eeconfig_debug_led_matrix(void) { + dprintf("led_matrix_config eprom\n"); + dprintf("led_matrix_config.enable = %d\n", led_matrix_config.enable); + dprintf("led_matrix_config.mode = %d\n", led_matrix_config.mode); + dprintf("led_matrix_config.val = %d\n", led_matrix_config.val); + dprintf("led_matrix_config.speed = %d\n", led_matrix_config.speed); +} + +// Last led hit +#define LED_HITS_TO_REMEMBER 8 +uint8_t g_last_led_hit[LED_HITS_TO_REMEMBER] = {255}; +uint8_t g_last_led_count = 0; + +void map_row_column_to_led(uint8_t row, uint8_t column, uint8_t *led_i, uint8_t *led_count) { + led_matrix led; + *led_count = 0; + + for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) { + // map_index_to_led(i, &led); + led = g_leds[i]; + if (row == led.matrix_co.row && column == led.matrix_co.col) { + led_i[*led_count] = i; + (*led_count)++; + } + } +} + +void led_matrix_update_pwm_buffers(void) { + led_matrix_driver.flush(); +} + +void led_matrix_set_index_value(int index, uint8_t value) { + led_matrix_driver.set_value(index, value); +} + +void led_matrix_set_index_value_all(uint8_t value) { + led_matrix_driver.set_value_all(value); +} + +bool process_led_matrix(uint16_t keycode, keyrecord_t *record) { + if (record->event.pressed) { + uint8_t led[8], led_count; + map_row_column_to_led(record->event.key.row, record->event.key.col, led, &led_count); + if (led_count > 0) { + for (uint8_t i = LED_HITS_TO_REMEMBER; i > 1; i--) { + g_last_led_hit[i - 1] = g_last_led_hit[i - 2]; + } + g_last_led_hit[0] = led[0]; + g_last_led_count = MIN(LED_HITS_TO_REMEMBER, g_last_led_count + 1); + } + for(uint8_t i = 0; i < led_count; i++) + g_key_hit[led[i]] = 0; + g_any_key_hit = 0; + } else { + #ifdef LED_MATRIX_KEYRELEASES + uint8_t led[8], led_count; + map_row_column_to_led(record->event.key.row, record->event.key.col, led, &led_count); + for(uint8_t i = 0; i < led_count; i++) + g_key_hit[led[i]] = 255; + + g_any_key_hit = 255; + #endif + } + return true; +} + +void led_matrix_set_suspend_state(bool state) { + g_suspend_state = state; +} + +// All LEDs off +void led_matrix_all_off(void) { + led_matrix_set_index_value_all(0); +} + +// Uniform brightness +void led_matrix_uniform_brightness(void) { + led_matrix_set_index_value_all(led_matrix_config.val); +} + +void led_matrix_custom(void) {} + +void led_matrix_task(void) { + #ifdef TRACK_PREVIOUS_EFFECT + static uint8_t toggle_enable_last = 255; + #endif + if (!led_matrix_config.enable) { + led_matrix_all_off(); + led_matrix_indicators(); + #ifdef TRACK_PREVIOUS_EFFECT + toggle_enable_last = led_matrix_config.enable; + #endif + return; + } + + // delay 1 second before driving LEDs or doing anything else + // FIXME: Can't we use wait_ms() here? + static uint8_t startup_tick = 0; + if (startup_tick < 20) { + startup_tick++; + return; + } + + g_tick++; + + if (g_any_key_hit < 0xFFFFFFFF) { + g_any_key_hit++; + } + + for (int led = 0; led < DRIVER_LED_TOTAL; led++) { + if (g_key_hit[led] < 255) { + if (g_key_hit[led] == 254) + g_last_led_count = MAX(g_last_led_count - 1, 0); + g_key_hit[led]++; + } + } + + // Factory default magic value + if (led_matrix_config.mode == 255) { + led_matrix_uniform_brightness(); + return; + } + + // Ideally we would also stop sending zeros to the LED driver PWM buffers + // while suspended and just do a software shutdown. This is a cheap hack for now. + bool suspend_backlight = ((g_suspend_state && LED_DISABLE_WHEN_USB_SUSPENDED) || + (LED_DISABLE_AFTER_TIMEOUT > 0 && g_any_key_hit > LED_DISABLE_AFTER_TIMEOUT * 60 * 20)); + uint8_t effect = suspend_backlight ? 0 : led_matrix_config.mode; + + #ifdef TRACK_PREVIOUS_EFFECT + // Keep track of the effect used last time, + // detect change in effect, so each effect can + // have an optional initialization. + + static uint8_t effect_last = 255; + bool initialize = (effect != effect_last) || (led_matrix_config.enable != toggle_enable_last); + effect_last = effect; + toggle_enable_last = led_matrix_config.enable; + #endif + + // this gets ticked at 20 Hz. + // each effect can opt to do calculations + // and/or request PWM buffer updates. + switch (effect) { + case LED_MATRIX_UNIFORM_BRIGHTNESS: + led_matrix_uniform_brightness(); + break; + default: + led_matrix_custom(); + break; + } + + if (! suspend_backlight) { + led_matrix_indicators(); + } + +} + +void led_matrix_indicators(void) { + led_matrix_indicators_kb(); + led_matrix_indicators_user(); +} + +__attribute__((weak)) +void led_matrix_indicators_kb(void) {} + +__attribute__((weak)) +void led_matrix_indicators_user(void) {} + + +// void led_matrix_set_indicator_index(uint8_t *index, uint8_t row, uint8_t column) +// { +// if (row >= MATRIX_ROWS) +// { +// // Special value, 255=none, 254=all +// *index = row; +// } +// else +// { +// // This needs updated to something like +// // uint8_t led[8], led_count; +// // map_row_column_to_led(row,column,led,&led_count); +// // for(uint8_t i = 0; i < led_count; i++) +// map_row_column_to_led(row, column, index); +// } +// } + +void led_matrix_init(void) { + led_matrix_driver.init(); + + // TODO: put the 1 second startup delay here? + + // clear the key hits + for (int led=0; led<DRIVER_LED_TOTAL; led++) { + g_key_hit[led] = 255; + } + + + if (!eeconfig_is_enabled()) { + dprintf("led_matrix_init_drivers eeconfig is not enabled.\n"); + eeconfig_init(); + eeconfig_update_led_matrix_default(); + } + led_matrix_config.raw = eeconfig_read_led_matrix(); + if (!led_matrix_config.mode) { + dprintf("led_matrix_init_drivers led_matrix_config.mode = 0. Write default values to EEPROM.\n"); + eeconfig_update_led_matrix_default(); + led_matrix_config.raw = eeconfig_read_led_matrix(); + } + eeconfig_debug_led_matrix(); // display current eeprom values +} + +// Deals with the messy details of incrementing an integer +static uint8_t increment(uint8_t value, uint8_t step, uint8_t min, uint8_t max) { + int16_t new_value = value; + new_value += step; + return MIN(MAX( new_value, min), max ); +} + +static uint8_t decrement(uint8_t value, uint8_t step, uint8_t min, uint8_t max) { + int16_t new_value = value; + new_value -= step; + return MIN(MAX( new_value, min), max ); +} + +// void *backlight_get_custom_key_value_eeprom_address(uint8_t led) { +// // 3 bytes per value +// return EECONFIG_LED_MATRIX + (led * 3); +// } + +// void backlight_get_key_value(uint8_t led, uint8_t *value) { +// void *address = backlight_get_custom_key_value_eeprom_address(led); +// value = eeprom_read_byte(address); +// } + +// void backlight_set_key_value(uint8_t row, uint8_t column, uint8_t value) { +// uint8_t led[8], led_count; +// map_row_column_to_led(row,column,led,&led_count); +// for(uint8_t i = 0; i < led_count; i++) { +// if (led[i] < DRIVER_LED_TOTAL) { +// void *address = backlight_get_custom_key_value_eeprom_address(led[i]); +// eeprom_update_byte(address, value); +// } +// } +// } + +uint32_t led_matrix_get_tick(void) { + return g_tick; +} + +void led_matrix_toggle(void) { + led_matrix_config.enable ^= 1; + eeconfig_update_led_matrix(led_matrix_config.raw); +} + +void led_matrix_enable(void) { + led_matrix_config.enable = 1; + eeconfig_update_led_matrix(led_matrix_config.raw); +} + +void led_matrix_enable_noeeprom(void) { + led_matrix_config.enable = 1; +} + +void led_matrix_disable(void) { + led_matrix_config.enable = 0; + eeconfig_update_led_matrix(led_matrix_config.raw); +} + +void led_matrix_disable_noeeprom(void) { + led_matrix_config.enable = 0; +}< |