diff --git a/common_features.mk b/common_features.mk index a7c366a1..d0edc80d 100644 --- a/common_features.mk +++ b/common_features.mk @@ -107,6 +107,15 @@ ifeq ($(strip $(RGBLIGHT_ENABLE)), yes) endif endif +ifeq ($(strip $(RGB_MATRIX_ENABLE)), yes) + OPT_DEFS += -DRGB_MATRIX_ENABLE + SRC += is31fl3731.c + SRC += TWIlib.c + SRC += $(QUANTUM_DIR)/color.c + SRC += $(QUANTUM_DIR)/rgb_matrix.c + CIE1931_CURVE = yes +endif + ifeq ($(strip $(TAP_DANCE_ENABLE)), yes) OPT_DEFS += -DTAP_DANCE_ENABLE SRC += $(QUANTUM_DIR)/process_keycode/process_tap_dance.c diff --git a/drivers/avr/TWIlib.c b/drivers/avr/TWIlib.c new file mode 100644 index 00000000..b39e3054 --- /dev/null +++ b/drivers/avr/TWIlib.c @@ -0,0 +1,232 @@ +/* + * TWIlib.c + * + * Created: 6/01/2014 10:41:33 PM + * Author: Chris Herring + * http://www.chrisherring.net/all/tutorial-interrupt-driven-twi-interface-for-avr-part1/ + */ + +#include +#include +#include "TWIlib.h" +#include "util/delay.h" + +void TWIInit() +{ + TWIInfo.mode = Ready; + TWIInfo.errorCode = 0xFF; + TWIInfo.repStart = 0; + // Set pre-scalers (no pre-scaling) + TWSR = 0; + // Set bit rate + TWBR = ((F_CPU / TWI_FREQ) - 16) / 2; + // Enable TWI and interrupt + TWCR = (1 << TWIE) | (1 << TWEN); +} + +uint8_t isTWIReady() +{ + if ( (TWIInfo.mode == Ready) | (TWIInfo.mode == RepeatedStartSent) ) + { + return 1; + } + else + { + return 0; + } +} + +uint8_t TWITransmitData(void *const TXdata, uint8_t dataLen, uint8_t repStart) +{ + if (dataLen <= TXMAXBUFLEN) + { + // Wait until ready + while (!isTWIReady()) {_delay_us(1);} + // Set repeated start mode + TWIInfo.repStart = repStart; + // Copy data into the transmit buffer + uint8_t *data = (uint8_t *)TXdata; + for (int i = 0; i < dataLen; i++) + { + TWITransmitBuffer[i] = data[i]; + } + // Copy transmit info to global variables + TXBuffLen = dataLen; + TXBuffIndex = 0; + + // If a repeated start has been sent, then devices are already listening for an address + // and another start does not need to be sent. + if (TWIInfo.mode == RepeatedStartSent) + { + TWIInfo.mode = Initializing; + TWDR = TWITransmitBuffer[TXBuffIndex++]; // Load data to transmit buffer + TWISendTransmit(); // Send the data + } + else // Otherwise, just send the normal start signal to begin transmission. + { + TWIInfo.mode = Initializing; + TWISendStart(); + } + + } + else + { + return 1; // return an error if data length is longer than buffer + } + return 0; +} + +uint8_t TWIReadData(uint8_t TWIaddr, uint8_t bytesToRead, uint8_t repStart) +{ + // Check if number of bytes to read can fit in the RXbuffer + if (bytesToRead < RXMAXBUFLEN) + { + // Reset buffer index and set RXBuffLen to the number of bytes to read + RXBuffIndex = 0; + RXBuffLen = bytesToRead; + // Create the one value array for the address to be transmitted + uint8_t TXdata[1]; + // Shift the address and AND a 1 into the read write bit (set to write mode) + TXdata[0] = (TWIaddr << 1) | 0x01; + // Use the TWITransmitData function to initialize the transfer and address the slave + TWITransmitData(TXdata, 1, repStart); + } + else + { + return 0; + } + return 1; +} + +ISR (TWI_vect) +{ + switch (TWI_STATUS) + { + // ----\/ ---- MASTER TRANSMITTER OR WRITING ADDRESS ----\/ ---- // + case TWI_MT_SLAW_ACK: // SLA+W transmitted and ACK received + // Set mode to Master Transmitter + TWIInfo.mode = MasterTransmitter; + case TWI_START_SENT: // Start condition has been transmitted + case TWI_MT_DATA_ACK: // Data byte has been transmitted, ACK received + if (TXBuffIndex < TXBuffLen) // If there is more data to send + { + TWDR = TWITransmitBuffer[TXBuffIndex++]; // Load data to transmit buffer + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + TWISendTransmit(); // Send the data + } + // This transmission is complete however do not release bus yet + else if (TWIInfo.repStart) + { + TWIInfo.errorCode = 0xFF; + TWISendStart(); + } + // All transmissions are complete, exit + else + { + TWIInfo.mode = Ready; + TWIInfo.errorCode = 0xFF; + TWISendStop(); + } + break; + + // ----\/ ---- MASTER RECEIVER ----\/ ---- // + + case TWI_MR_SLAR_ACK: // SLA+R has been transmitted, ACK has been received + // Switch to Master Receiver mode + TWIInfo.mode = MasterReceiver; + // If there is more than one byte to be read, receive data byte and return an ACK + if (RXBuffIndex < RXBuffLen-1) + { + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + TWISendACK(); + } + // Otherwise when a data byte (the only data byte) is received, return NACK + else + { + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + TWISendNACK(); + } + break; + + case TWI_MR_DATA_ACK: // Data has been received, ACK has been transmitted. + + /// -- HANDLE DATA BYTE --- /// + TWIReceiveBuffer[RXBuffIndex++] = TWDR; + // If there is more than one byte to be read, receive data byte and return an ACK + if (RXBuffIndex < RXBuffLen-1) + { + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + TWISendACK(); + } + // Otherwise when a data byte (the only data byte) is received, return NACK + else + { + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + TWISendNACK(); + } + break; + + case TWI_MR_DATA_NACK: // Data byte has been received, NACK has been transmitted. End of transmission. + + /// -- HANDLE DATA BYTE --- /// + TWIReceiveBuffer[RXBuffIndex++] = TWDR; + // This transmission is complete however do not release bus yet + if (TWIInfo.repStart) + { + TWIInfo.errorCode = 0xFF; + TWISendStart(); + } + // All transmissions are complete, exit + else + { + TWIInfo.mode = Ready; + TWIInfo.errorCode = 0xFF; + TWISendStop(); + } + break; + + // ----\/ ---- MT and MR common ----\/ ---- // + + case TWI_MR_SLAR_NACK: // SLA+R transmitted, NACK received + case TWI_MT_SLAW_NACK: // SLA+W transmitted, NACK received + case TWI_MT_DATA_NACK: // Data byte has been transmitted, NACK received + case TWI_LOST_ARBIT: // Arbitration has been lost + // Return error and send stop and set mode to ready + if (TWIInfo.repStart) + { + TWIInfo.errorCode = TWI_STATUS; + TWISendStart(); + } + // All transmissions are complete, exit + else + { + TWIInfo.mode = Ready; + TWIInfo.errorCode = TWI_STATUS; + TWISendStop(); + } + break; + case TWI_REP_START_SENT: // Repeated start has been transmitted + // Set the mode but DO NOT clear TWINT as the next data is not yet ready + TWIInfo.mode = RepeatedStartSent; + break; + + // ----\/ ---- SLAVE RECEIVER ----\/ ---- // + + // TODO IMPLEMENT SLAVE RECEIVER FUNCTIONALITY + + // ----\/ ---- SLAVE TRANSMITTER ----\/ ---- // + + // TODO IMPLEMENT SLAVE TRANSMITTER FUNCTIONALITY + + // ----\/ ---- MISCELLANEOUS STATES ----\/ ---- // + case TWI_NO_RELEVANT_INFO: // It is not really possible to get into this ISR on this condition + // Rather, it is there to be manually set between operations + break; + case TWI_ILLEGAL_START_STOP: // Illegal START/STOP, abort and return error + TWIInfo.errorCode = TWI_ILLEGAL_START_STOP; + TWIInfo.mode = Ready; + TWISendStop(); + break; + } + +} diff --git a/drivers/avr/TWIlib.h b/drivers/avr/TWIlib.h new file mode 100644 index 00000000..23fd1f09 --- /dev/null +++ b/drivers/avr/TWIlib.h @@ -0,0 +1,82 @@ +/* + * TWIlib.h + * + * Created: 6/01/2014 10:38:42 PM + * Author: Chris Herring + * http://www.chrisherring.net/all/tutorial-interrupt-driven-twi-interface-for-avr-part1/ + */ + + +#ifndef TWILIB_H_ +#define TWILIB_H_ +// TWI bit rate (was 100000) +#define TWI_FREQ 400000 +// Get TWI status +#define TWI_STATUS (TWSR & 0xF8) +// Transmit buffer length +#define TXMAXBUFLEN 20 +// Receive buffer length +#define RXMAXBUFLEN 20 +// Global transmit buffer +uint8_t TWITransmitBuffer[TXMAXBUFLEN]; +// Global receive buffer +volatile uint8_t TWIReceiveBuffer[RXMAXBUFLEN]; +// Buffer indexes +volatile int TXBuffIndex; // Index of the transmit buffer. Is volatile, can change at any time. +int RXBuffIndex; // Current index in the receive buffer +// Buffer lengths +int TXBuffLen; // The total length of the transmit buffer +int RXBuffLen; // The total number of bytes to read (should be less than RXMAXBUFFLEN) + +typedef enum { + Ready, + Initializing, + RepeatedStartSent, + MasterTransmitter, + MasterReceiver, + SlaceTransmitter, + SlaveReciever + } TWIMode; + + typedef struct TWIInfoStruct{ + TWIMode mode; + uint8_t errorCode; + uint8_t repStart; + }TWIInfoStruct; +TWIInfoStruct TWIInfo; + + +// TWI Status Codes +#define TWI_START_SENT 0x08 // Start sent +#define TWI_REP_START_SENT 0x10 // Repeated Start sent +// Master Transmitter Mode +#define TWI_MT_SLAW_ACK 0x18 // SLA+W sent and ACK received +#define TWI_MT_SLAW_NACK 0x20 // SLA+W sent and NACK received +#define TWI_MT_DATA_ACK 0x28 // DATA sent and ACK received +#define TWI_MT_DATA_NACK 0x30 // DATA sent and NACK received +// Master Receiver Mode +#define TWI_MR_SLAR_ACK 0x40 // SLA+R sent, ACK received +#define TWI_MR_SLAR_NACK 0x48 // SLA+R sent, NACK received +#define TWI_MR_DATA_ACK 0x50 // Data received, ACK returned +#define TWI_MR_DATA_NACK 0x58 // Data received, NACK returned + +// Miscellaneous States +#define TWI_LOST_ARBIT 0x38 // Arbitration has been lost +#define TWI_NO_RELEVANT_INFO 0xF8 // No relevant information available +#define TWI_ILLEGAL_START_STOP 0x00 // Illegal START or STOP condition has been detected +#define TWI_SUCCESS 0xFF // Successful transfer, this state is impossible from TWSR as bit2 is 0 and read only + + +#define TWISendStart() (TWCR = (1<. + */ + +#include "is31fl3731.h" +#include +#include +#include +#include +#include "TWIlib.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' + +// Transfer buffer for TWITransmitData() +uint8_t g_twi_transfer_buffer[TXMAXBUFLEN]; + +// 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; + + +typedef struct +{ + uint8_t red_register; + uint8_t red_bit; + uint8_t green_register; + uint8_t green_bit; + uint8_t blue_register; + uint8_t blue_bit; +} led_control_bitmask; + +// 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 + +const led_control_bitmask g_led_control_bitmask[18] = +{ + { 0x02, 0, 0x04, 0, 0x06, 0 }, // R00,G00,B00 + { 0x00, 0, 0x04, 1, 0x06, 1 }, // R01,G01,B01 + { 0x00, 1, 0x02, 1, 0x06, 2 }, // R02,G02,B02 + { 0x00, 2, 0x02, 2, 0x04, 2 }, // R03,G03,B03 + { 0x00, 3, 0x02, 3, 0x04, 3 }, // R04,G04,B04 + { 0x00, 4, 0x02, 4, 0x04, 4 }, // R05,G05,B05 + { 0x00, 5, 0x02, 5, 0x04, 5 }, // R06,G06,B06 + { 0x00, 6, 0x02, 6, 0x04, 6 }, // R07,G07,B07 + { 0x00, 7, 0x02, 7, 0x04, 7 }, // R08,G08,B08 + + { 0x10, 0, 0x0E, 0, 0x0C, 0 }, // R09,G09,B09 + { 0x10, 1, 0x0E, 1, 0x0C, 1 }, // R10,G10,B10 + { 0x10, 2, 0x0E, 2, 0x0C, 2 }, // R11,G11,B11 + { 0x10, 3, 0x0E, 3, 0x0C, 3 }, // R12,G12,B12 + { 0x10, 4, 0x0E, 4, 0x0C, 4 }, // R13,G13,B13 + { 0x10, 5, 0x0E, 5, 0x0C, 5 }, // R14,G14,B14 + { 0x10, 6, 0x0E, 6, 0x0A, 5 }, // R15,G15,B15 + { 0x10, 7, 0x0C, 6, 0x0A, 6 }, // R16,G16,B16 + { 0x0E, 7, 0x0C, 7, 0x0A, 7 }, // R17,G17,B17 +}; + +const uint8_t g_map_control_index_to_register[2][18][3] PROGMEM = { + { + {0x34, 0x44, 0x54}, // 00 + {0x24, 0x45, 0x55}, // 01 + {0x25, 0x35, 0x56}, // 02 + {0x26, 0x36, 0x46}, // 03 + {0x27, 0x37, 0x47}, // 04 + {0x28, 0x38, 0x48}, // 05 + {0x29, 0x39, 0x49}, // 06 + {0x2a, 0x3a, 0x4a}, // 07 + {0x2b, 0x3b, 0x4b}, // 08 + + {0xa4, 0x94, 0x84}, // 09 + {0xa5, 0x95, 0x85}, // 10 + {0xa6, 0x96, 0x86}, // 11 + {0xa7, 0x97, 0x87}, // 12 + {0xa8, 0x98, 0x88}, // 13 + {0xa9, 0x99, 0x89}, // 14 + {0xaa, 0x9a, 0x79}, // 15 + {0xab, 0x8a, 0x7a}, // 16 + {0x9b, 0x8b, 0x7b} // 17 + }, { + {0x34 + 8, 0x44 + 8, 0x54 + 8}, // 00 + {0x24 + 8, 0x45 + 8, 0x55 + 8}, // 01 + {0x25 + 8, 0x35 + 8, 0x56 + 8}, // 02 + {0x26 + 8, 0x36 + 8, 0x46 + 8}, // 03 + {0x27 + 8, 0x37 + 8, 0x47 + 8}, // 04 + {0x28 + 8, 0x38 + 8, 0x48 + 8}, // 05 + {0x29 + 8, 0x39 + 8, 0x49 + 8}, // 06 + {0x2a + 8, 0x3a + 8, 0x4a + 8}, // 07 + {0x2b + 8, 0x3b + 8, 0x4b + 8}, // 08 + + {0xa4 + 8, 0x94 + 8, 0x84 + 8}, // 09 + {0xa5 + 8, 0x95 + 8, 0x85 + 8}, // 10 + {0xa6 + 8, 0x96 + 8, 0x86 + 8}, // 11 + {0xa7 + 8, 0x97 + 8, 0x87 + 8}, // 12 + {0xa8 + 8, 0x98 + 8, 0x88 + 8}, // 13 + {0xa9 + 8, 0x99 + 8, 0x89 + 8}, // 14 + {0xaa + 8, 0x9a + 8, 0x79 + 8}, // 15 + {0xab + 8, 0x8a + 8, 0x7a + 8}, // 16 + {0x9b + 8, 0x8b + 8, 0x7b + 8} // 17 +}}; + +void IS31FL3731_write_register( uint8_t addr, uint8_t reg, uint8_t data ) +{ + g_twi_transfer_buffer[0] = (addr << 1) | 0x00; + g_twi_transfer_buffer[1] = reg; + g_twi_transfer_buffer[2] = data; + + // Set the error code to have no relevant information + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + // Continuously attempt to transmit data until a successful transmission occurs + //while ( TWIInfo.errorCode != 0xFF ) + //{ + TWITransmitData( g_twi_transfer_buffer, 3, 0 ); + //} +} + +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 + + // set the I2C address + g_twi_transfer_buffer[0] = (addr << 1) | 0x00; + + // 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[1] = 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[2 + j] = pwm_buffer[i + j]; + } + + // Set the error code to have no relevant information + TWIInfo.errorCode = TWI_NO_RELEVANT_INFO; + // Continuously attempt to transmit data until a successful transmission occurs + while ( TWIInfo.errorCode != 0xFF ) + { + TWITransmitData( g_twi_transfer_buffer, 16 + 2, 0 ); + } + } +} + +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 + _delay_ms( 10 ); + + // 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 map_index_to_led( uint8_t index, is31_led *led ) { + //led = , sizeof(struct is31_led)); + // led->driver = addr->driver; + // led->matrix = addr->matrix; + // led->modifier = addr->modifier; + // led->control_index = addr->control_index; + // led->matrix_co.raw = addr->matrix_co.raw; + // led->driver = (pgm_read_byte(addr) >> 6) && 0b11; + // led->matrix = (pgm_read_byte(addr) >> 4) && 0b1; + // led->modifier = (pgm_read_byte(addr) >> 3) && 0b1; + // led->control_index = pgm_read_byte(addr+1); + // led->matrix_co.raw = pgm_read_byte(addr+2); +} + +void IS31FL3731_set_color( int index, uint8_t red, uint8_t green, uint8_t blue ) +{ + if ( index >= 0 && index < DRIVER_LED_TOTAL ) + { + is31_led led = g_is31_leds[index]; + //map_index_to_led(index, &led); + + // Subtract 0x24 to get the second index of g_pwm_buffer + g_pwm_buffer[led.driver][ pgm_read_byte(&g_map_control_index_to_register[led.matrix][led.control_index][0]) - 0x24] = red; + g_pwm_buffer[led.driver][ pgm_read_byte(&g_map_control_index_to_register[led.matrix][led.control_index][1]) - 0x24] = green; + g_pwm_buffer[led.driver][ pgm_read_byte(&g_map_control_index_to_register[led.matrix][led.control_index][2]) - 0x24] = blue; + g_pwm_buffer_update_required = true; + } +} + +void IS31FL3731_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) +{ + for ( int i = 0; i < DRIVER_LED_TOTAL; i++ ) + { + IS31FL3731_set_color( i, red, green, blue ); + } +} + +void IS31FL3731_set_led_control_register( uint8_t index, bool red, bool green, bool blue ) +{ + is31_led led = g_is31_leds[index]; + // map_index_to_led(index, &led); + + led_control_bitmask bitmask = g_led_control_bitmask[led.control_index]; + + // Matrix A and B registers are interleaved. + // Add 1 to Matrix A register to get Matrix B register + if ( red ) + { + g_led_control_registers[led.driver][bitmask.red_register+led.matrix] |= (1<. + */ + + +#ifndef IS31FL3731_DRIVER_H +#define IS31FL3731_DRIVER_H + +#include +#include + +typedef struct Point { + uint8_t x; + uint8_t y; +} __attribute__((packed)) Point; + +typedef struct is31_led { + uint8_t driver:2; + uint8_t matrix:1; + uint8_t modifier:1; + uint8_t control_index; + union { + uint8_t raw; + struct { + uint8_t row:4; // 16 max + uint8_t col:4; // 16 max + }; + } matrix_co; + Point point; +} __attribute__((packed)) is31_led; + +extern const is31_led g_is31_leds[DRIVER_LED_TOTAL]; + +void map_index_to_led( uint8_t index, is31_led *led ); + +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_color( int index, uint8_t red, uint8_t green, uint8_t blue ); +void IS31FL3731_set_color_all( uint8_t red, uint8_t green, uint8_t blue ); + +void IS31FL3731_set_led_control_register( uint8_t index, bool red, bool green, bool blue ); + +// 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 addr1, uint8_t addr2 ); +void IS31FL3731_update_led_control_registers( uint8_t addr1, uint8_t addr2 ); + +#endif // IS31FL3731_DRIVER_H diff --git a/quantum/color.c b/quantum/color.c new file mode 100644 index 00000000..8ede053e --- /dev/null +++ b/quantum/color.c @@ -0,0 +1,87 @@ +/* Copyright 2017 Jason Williams + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "color.h" +#include "led_tables.h" +#include "progmem.h" + +RGB hsv_to_rgb( HSV hsv ) +{ + RGB rgb; + uint8_t region, p, q, t; + uint16_t h, s, v, remainder; + + if ( hsv.s == 0 ) + { + rgb.r = hsv.v; + rgb.g = hsv.v; + rgb.b = hsv.v; + return rgb; + } + + h = hsv.h; + s = hsv.s; + v = hsv.v; + + region = h / 43; + remainder = (h - (region * 43)) * 6; + + p = (v * (255 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 8))) >> 8; + t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; + + switch ( region ) + { + case 0: + rgb.r = v; + rgb.g = t; + rgb.b = p; + break; + case 1: + rgb.r = q; + rgb.g = v; + rgb.b = p; + break; + case 2: + rgb.r = p; + rgb.g = v; + rgb.b = t; + break; + case 3: + rgb.r = p; + rgb.g = q; + rgb.b = v; + break; + case 4: + rgb.r = t; + rgb.g = p; + rgb.b = v; + break; + default: + rgb.r = v; + rgb.g = p; + rgb.b = q; + break; + } + + rgb.r = pgm_read_byte( &CIE1931_CURVE[rgb.r] ); + rgb.g = pgm_read_byte( &CIE1931_CURVE[rgb.g] ); + rgb.b = pgm_read_byte( &CIE1931_CURVE[rgb.b] ); + + return rgb; +} + diff --git a/quantum/color.h b/quantum/color.h new file mode 100644 index 00000000..9d51d45a --- /dev/null +++ b/quantum/color.h @@ -0,0 +1,55 @@ +/* Copyright 2017 Jason Williams + * + * 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 . + */ + + +#ifndef COLOR_H +#define COLOR_H + +#include +#include + + +#if defined(__GNUC__) +#define PACKED __attribute__ ((__packed__)) +#else +#define PACKED +#endif + +#if defined(_MSC_VER) +#pragma pack( push, 1 ) +#endif + +typedef struct PACKED +{ + uint8_t r; + uint8_t g; + uint8_t b; +} RGB; + +typedef struct PACKED +{ + uint8_t h; + uint8_t s; + uint8_t v; +} HSV; + +#if defined(_MSC_VER) +#pragma pack( pop ) +#endif + +RGB hsv_to_rgb( HSV hsv ); + +#endif // COLOR_H diff --git a/quantum/quantum.c b/quantum/quantum.c index a1a1a9d1..7d37f47e 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -279,7 +279,7 @@ bool process_record_quantum(keyrecord_t *record) { } return false; #endif - #ifdef RGBLIGHT_ENABLE + #if defined(RGBLIGHT_ENABLE) || defined(RGB_MATRIX_ENABLE) case RGB_TOG: if (record->event.pressed) { rgblight_toggle(); @@ -862,13 +862,14 @@ void backlight_set(uint8_t level) } #endif - #ifdef BACKLIGHT_BREATHING + #if defined(BACKLIGHT_BREATHING) breathing_intensity_default(); #endif } uint8_t backlight_tick = 0; +__attribute__ ((weak)) void backlight_task(void) { #ifdef NO_BACKLIGHT_CLOCK if ((0xFFFF >> ((BACKLIGHT_LEVELS - backlight_config.level) * ((BACKLIGHT_LEVELS + 1) / 2))) & (1 << backlight_tick)) { @@ -894,6 +895,23 @@ void backlight_task(void) { #ifdef BACKLIGHT_BREATHING + +#ifdef NO_BACKLIGHT_CLOCK + void breathing_enable(void) {} + void breathing_pulse(void) {} + void breathing_disable(void) {} + void breathing_self_disable(void) {} + void breathing_toggle(void) {} + bool is_breathing(void) { return false; } + void breathing_intensity_default(void) {} + void breathing_intensity_set(uint8_t value) {} + void breathing_speed_default(void) {} + void breathing_speed_set(uint8_t value) {} + void breathing_speed_inc(uint8_t value) {} + void breathing_speed_dec(uint8_t value) {} + void breathing_defaults(void) {} +#else + #define BREATHING_NO_HALT 0 #define BREATHING_HALT_OFF 1 #define BREATHING_HALT_ON 2 @@ -1093,7 +1111,7 @@ ISR(TIMER1_COMPA_vect) } - +#endif // no clock #endif // breathing @@ -1156,6 +1174,7 @@ void send_nibble(uint8_t number) { __attribute__((weak)) uint16_t hex_to_keycode(uint8_t hex) { + hex = hex & 0xF; if (hex == 0x0) { return KC_0; } else if (hex < 0xA) { diff --git a/quantum/quantum.h b/quantum/quantum.h index 534819c8..ef0b478d 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -27,9 +27,15 @@ #ifdef BACKLIGHT_ENABLE #include "backlight.h" #endif +#if !defined(RGBLIGHT_ENABLE) && !defined(RGB_MATRIX_ENABLE) + #include "rgb.h" +#endif #ifdef RGBLIGHT_ENABLE #include "rgblight.h" #endif +#ifdef RGB_MATRIX_ENABLE + #include "rgb_matrix.h" +#endif #include "action_layer.h" #include "eeconfig.h" #include diff --git a/quantum/rgb.h b/quantum/rgb.h new file mode 100644 index 00000000..792b6308 --- /dev/null +++ b/quantum/rgb.h @@ -0,0 +1,44 @@ +/* Copyright 2017 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 . + */ + +#ifndef RGB_H +#define RGB_H + +__attribute__((weak)) +void rgblight_toggle(void) {}; + +__attribute__((weak)) +void rgblight_step(void) {}; + +__attribute__((weak)) +void rgblight_increase_hue(void) {}; + +__attribute__((weak)) +void rgblight_decrease_hue(void) {}; + +__attribute__((weak)) +void rgblight_increase_sat(void) {}; + +__attribute__((weak)) +void rgblight_decrease_sat(void) {}; + +__attribute__((weak)) +void rgblight_increase_val(void) {}; + +__attribute__((weak)) +void rgblight_decrease_val(void) {}; + +#endif \ No newline at end of file diff --git a/quantum/rgb_matrix.c b/quantum/rgb_matrix.c new file mode 100644 index 00000000..9f8a5d05 --- /dev/null +++ b/quantum/rgb_matrix.c @@ -0,0 +1,1056 @@ +/* Copyright 2017 Jason Williams + * Copyright 2017 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 . + */ + + +#include "rgb_matrix.h" +#include +#include "TWIlib.h" +#include +#include +#include "progmem.h" +#include "config.h" +#include "eeprom.h" +#include "lufa.h" +#include + +#define BACKLIGHT_EFFECT_MAX 17 + +rgb_matrix_config g_config = { + .enabled = 1, + .brightness = 255, + .effect = 7, + .color_1 = { .h = 130, .s = 255, .v = 255 }, + .color_2 = { .h = 70, .s = 255, .v = 255 }, + .caps_lock_indicator = { .color = { .h = 0, .s = 0, .v = 255 }, .index = 255 }, + .layer_1_indicator = { .color = { .h = 0, .s = 0, .v = 255 }, .index = 255 }, + .layer_2_indicator = { .color = { .h = 0, .s = 0, .v = 255 }, .index = 255 }, + .layer_3_indicator = { .color = { .h = 0, .s = 0, .v = 255 }, .index = 255 }, +}; + +bool g_suspend_state = false; +uint8_t g_indicator_state = 0; + +// 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; + +#ifndef PI +#define PI 3.14159265 +#endif + +// 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) +{ + is31_led led; + *led_count = 0; + + for (uint8_t i = 0; i < DRIVER_LED_TOTAL; i++) { + // map_index_to_led(i, &led); + led = g_is31_leds[i]; + if (row == led.matrix_co.row && column == led.matrix_co.col) { + led_i[*led_count] = i; + (*led_count)++; + } + } +} + + +void backlight_update_pwm_buffers(void) +{ + IS31FL3731_update_pwm_buffers( DRIVER_ADDR_1, DRIVER_ADDR_2 ); + IS31FL3731_update_led_control_registers( DRIVER_ADDR_1, DRIVER_ADDR_2 ); +} + +void backlight_set_color( int index, uint8_t red, uint8_t green, uint8_t blue ) +{ + IS31FL3731_set_color( index, red, green, blue ); +} + +void backlight_set_color_all( uint8_t red, uint8_t green, uint8_t blue ) +{ + IS31FL3731_set_color_all( red, green, blue ); +} + +void backlight_set_key_hit(uint8_t row, uint8_t column) +{ + uint8_t led[8], led_count; + map_row_column_to_led(row,column,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; +} + +void backlight_unset_key_hit(uint8_t row, uint8_t column) +{ + 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++) + g_key_hit[led[i]] = 255; + + g_any_key_hit = 255; +} + +// This is (F_CPU/1024) / 20 Hz +// = 15625 Hz / 20 Hz +// = 781 +// #define TIMER3_TOP 781 + +void backlight_timer_init(void) +{ + + static uint8_t backlight_timer_is_init = 0; + if ( backlight_timer_is_init ) + { + return; + } + backlight_timer_is_init = 1; + + // Timer 3 setup + //TCCR3B = _BV(WGM32) | // CTC mode OCR3A as TOP + // _BV(CS32) | _BV(CS30); // prescale by /1024 + // Set TOP value + //uint8_t sreg = SREG; + //cli(); + + //OCR3AH = (TIMER3_TOP >> 8) & 0xff; + //OCR3AL = TIMER3_TOP & 0xff; + //SREG = sreg; + +} + +void backlight_timer_enable(void) +{ + //TIMSK3 |= _BV(OCIE3A); +} + +void backlight_timer_disable(void) +{ + //TIMSK3 &= ~_BV(OCIE3A); +} + +void backlight_set_suspend_state(bool state) +{ + g_suspend_state = state; +} + +void backlight_set_indicator_state(uint8_t state) +{ + g_indicator_state = state; +} + +void backlight_effect_rgb_test(void) +{ + // Mask out bits 4 and 5 + // This 2-bit value will stay the same for 16 ticks. + switch ( (g_tick & 0x30) >> 4 ) + { + case 0: + { + backlight_set_color_all( 20, 0, 0 ); + break; + } + case 1: + { + backlight_set_color_all( 0, 20, 0 ); + break; + } + case 2: + { + backlight_set_color_all( 0, 0, 20 ); + break; + } + case 3: + { + backlight_set_color_all( 20, 20, 20 ); + break; + } + } +} + +// This tests the LEDs +// Note that it will change the LED control registers +// in the LED drivers, and leave them in an invalid +// state for other backlight effects. +// ONLY USE THIS FOR TESTING LEDS! +void backlight_effect_single_LED_test(void) +{ + static uint8_t color = 0; // 0,1,2 for R,G,B + static uint8_t row = 0; + static uint8_t column = 0; + + static uint8_t tick = 0; + tick++; + + if ( tick > 2 ) + { + tick = 0; + column++; + } + if ( column > MATRIX_COLS ) + { + column = 0; + row++; + } + if ( row > MATRIX_ROWS ) + { + row = 0; + color++; + } + if ( color > 2 ) + { + color = 0; + } + + 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++) { + backlight_set_color_all( 40, 40, 40 ); + backlight_test_led( led[i], color==0, color==1, color==2 ); + } +} + +// All LEDs off +void backlight_effect_all_off(void) +{ + backlight_set_color_all( 0, 0, 0 ); +} + +// Solid color +void backlight_effect_solid_color(void) +{ + HSV hsv = { .h = g_config.color_1.h, .s = g_config.color_1.s, .v = g_config.brightness }; + RGB rgb = hsv_to_rgb( hsv ); + backlight_set_color_all( rgb.r, rgb.g, rgb.b ); +} + +void backlight_effect_solid_reactive(void) +{ + // Relies on hue being 8-bit and wrapping + for ( int i=0; i 127 ) + { + deltaH -= 256; + } + else if ( deltaH < -127 ) + { + deltaH += 256; + } + // Divide delta by 4, this gives the delta per row + deltaH /= 4; + + int16_t s1 = g_config.color_1.s; + int16_t s2 = g_config.color_2.s; + int16_t deltaS = ( s2 - s1 ) / 4; + + HSV hsv = { .h = 0, .s = 255, .v = g_config.brightness }; + RGB rgb; + Point point; + for ( int i=0; i>4); + // Relies on hue being 8-bit and wrapping + hsv.h = g_config.color_1.h + ( deltaH * y ); + hsv.s = g_config.color_1.s + ( deltaS * y ); + rgb = hsv_to_rgb( hsv ); + backlight_set_color( i, rgb.r, rgb.g, rgb.b ); + } +} + +void backlight_effect_raindrops(bool initialize) +{ + int16_t h1 = g_config.color_1.h; + int16_t h2 = g_config.color_2.h; + int16_t deltaH = h2 - h1; + deltaH /= 4; + + // Take the shortest path between hues + if ( deltaH > 127 ) + { + deltaH -= 256; + } + else if ( deltaH < -127 ) + { + deltaH += 256; + } + + int16_t s1 = g_config.color_1.s; + int16_t s2 = g_config.color_2.s; + int16_t deltaS = ( s2 - s1 ) / 4; + + HSV hsv; + RGB rgb; + + // Change one LED every tick + uint8_t led_to_change = ( g_tick & 0x000 ) == 0 ? rand() % DRIVER_LED_TOTAL : 255; + + for ( int i=0; i 0 && g_any_key_hit > g_config.disable_after_timeout * 60 * 20)); + uint8_t effect = suspend_backlight ? 0 : g_config.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; + effect_last = effect; + + // this gets ticked at 20 Hz. + // each effect can opt to do calculations + // and/or request PWM buffer updates. + switch ( effect ) + { + case 0: + backlight_effect_solid_color(); + break; + case 1: + backlight_effect_solid_reactive(); + break; + case 2: + backlight_effect_alphas_mods(); + break; + case 3: + backlight_effect_dual_beacon(); + break; + case 4: + backlight_effect_gradient_up_down(); + break; + case 5: + backlight_effect_raindrops( initialize ); + break; + case 6: + backlight_effect_cycle_all(); + break; + case 7: + backlight_effect_cycle_left_right(); + break; + case 8: + backlight_effect_cycle_up_down(); + break; + case 9: + backlight_effect_rainbow_beacon(); + break; + case 10: + backlight_effect_rainbow_pinwheels(); + break; + case 11: + backlight_effect_rainbow_moving_chevron(); + break; + case 12: + backlight_effect_jellybean_raindrops( initialize ); + break; + case 13: + backlight_effect_splash(); + break; + case 14: + backlight_effect_multisplash(); + break; + case 15: + backlight_effect_solid_splash(); + break; + case 16: + backlight_effect_solid_multisplash(); + break; + case 17: + default: + backlight_effect_custom(); + break; + } + + if ( ! suspend_backlight ) + { + backlight_effect_indicators(); + } + +} + +// void backlight_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 backlight_config_load(void) +{ + eeprom_read_block( &g_config, EEPROM_BACKLIGHT_CONFIG_ADDR, sizeof(rgb_matrix_config) ); +} + +void backlight_config_save(void) +{ + eeprom_update_block( &g_config, EEPROM_BACKLIGHT_CONFIG_ADDR, sizeof(rgb_matrix_config) ); +} + +void backlight_init_drivers(void) +{ + //sei(); + + // Initialize TWI + TWIInit(); + IS31FL3731_init( DRIVER_ADDR_1 ); + IS31FL3731_init( DRIVER_ADDR_2 ); + + for ( int index = 0; index < DRIVER_LED_TOTAL; index++ ) + { + bool enabled = true; + // This only caches it for later + IS31FL3731_set_led_control_register( index, enabled, enabled, enabled ); + } + // This actually updates the LED drivers + IS31FL3731_update_led_control_registers( DRIVER_ADDR_1, DRIVER_ADDR_2 ); + + // TODO: put the 1 second startup delay here? + + // clear the key hits + for ( int led=0; ledh = eeprom_read_byte(address); + hsv->s = eeprom_read_byte(address+1); + hsv->v = eeprom_read_byte(address+2); +} + +void backlight_set_key_color( uint8_t row, uint8_t column, HSV hsv ) +{ + 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_color_eeprom_address(led[i]); + eeprom_update_byte(address, hsv.h); + eeprom_update_byte(address+1, hsv.s); + eeprom_update_byte(address+2, hsv.v); + } + } +} + +void backlight_test_led( uint8_t index, bool red, bool green, bool blue ) +{ + for ( int i=0; i. + */ + +#ifndef RGB_MATRIX_H +#define RGB_MATRIX_H + +#include +#include +#include "color.h" +#include "is31fl3731.h" +#include "quantum.h" + +typedef struct +{ + HSV color; + uint8_t index; +} zeal_indicator; + +typedef struct +{ + bool enabled:1; // | + bool use_split_left_shift:1; // | + bool use_split_right_shift:1; // | + bool use_7u_spacebar:1; // | + bool use_iso_enter:1; // | + bool disable_when_usb_suspended:1; // | + bool __pad6:1; // | + bool __pad7:1; // 1 byte + uint8_t disable_after_timeout; // 1 byte + uint8_t brightness; // 1 byte + uint8_t effect; // 1 byte + HSV color_1; // 3 bytes + HSV color_2; // 3 bytes + zeal_indicator caps_lock_indicator; // 4 bytes + zeal_indicator layer_1_indicator; // 4 bytes + zeal_indicator layer_2_indicator; // 4 bytes + zeal_indicator layer_3_indicator; // 4 bytes + uint16_t alphas_mods[5]; // 10 bytes +} rgb_matrix_config; // = 36 bytes + +#define EEPROM_BACKLIGHT_CONFIG_ADDR ((void*)35) +// rgb_matrix_config uses 36 bytes +// 35+36=71 +#define EEPROM_BACKLIGHT_KEY_COLOR_ADDR ((void*)71) + + +void backlight_effect_single_LED_test(void); + +void backlight_config_set_alphas_mods( uint16_t *value ); +void backlight_config_load(void); +void backlight_config_save(void); + +void backlight_init_drivers(void); + +void backlight_timer_init(void); +void backlight_timer_enable(void); +void backlight_timer_disable(void); + +void backlight_set_suspend_state(bool state); +void backlight_set_indicator_state(uint8_t state); + + +void backlight_rgb_task(void); + +// 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 backlight_update_pwm_buffers(void); + +void backlight_set_key_hit(uint8_t row, uint8_t col); +void backlight_unset_key_hit(uint8_t row, uint8_t col); + +void backlight_effect_increase(void); +void backlight_effect_decrease(void); + +void backlight_brightness_increase(void); +void backlight_brightness_decrease(void); + +void backlight_color_1_hue_increase(void); +void backlight_color_1_hue_decrease(void); +void backlight_color_1_sat_increase(void); +void backlight_color_1_sat_decrease(void); +void backlight_color_2_hue_increase(void); +void backlight_color_2_hue_decrease(void); +void backlight_color_2_sat_increase(void); +void backlight_color_2_sat_decrease(void); + +void *backlight_get_key_color_eeprom_address(uint8_t led); +void backlight_get_key_color( uint8_t led, HSV *hsv ); +void backlight_set_key_color( uint8_t row, uint8_t column, HSV hsv ); + +void backlight_test_led( uint8_t index, bool red, bool green, bool blue ); +uint32_t backlight_get_tick(void); +void backlight_debug_led(bool state); + +void rgblight_toggle(void); +void rgblight_step(void); +void rgblight_increase_hue(void); +void rgblight_decrease_hue(void); +void rgblight_increase_sat(void); +void rgblight_decrease_sat(void); +void rgblight_increase_val(void); +void rgblight_decrease_val(void); +void rgblight_mode(uint8_t mode); +uint32_t rgblight_get_mode(void); + +#endif \ No newline at end of file