|
| 1 | +/* Copyright 2017 Jason Williams |
| 2 | + * Copyright 2018 Jack Humbert |
| 3 | + * Copyright 2018 Yiancar |
| 4 | + * Copyright 2021 Doni Crosby |
| 5 | + * |
| 6 | + * This program is free software: you can redistribute it and/or modify |
| 7 | + * it under the terms of the GNU General Public License as published by |
| 8 | + * the Free Software Foundation, either version 2 of the License, or |
| 9 | + * (at your option) any later version. |
| 10 | + * |
| 11 | + * This program is distributed in the hope that it will be useful, |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | + * GNU General Public License for more details. |
| 15 | + * |
| 16 | + * You should have received a copy of the GNU General Public License |
| 17 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 18 | + */ |
| 19 | + |
| 20 | +#include "is31fl3763.h" |
| 21 | +#include "i2c_master.h" |
| 22 | +#include "gpio.h" |
| 23 | +#include "wait.h" |
| 24 | + |
| 25 | + |
| 26 | +#define ISSI_ADDR_DEFAULT 0x6C |
| 27 | + |
| 28 | +#define ISSI_COMMANDREGISTER 0xFE |
| 29 | +#define ISSI_COMMANDREGISTER_WRITELOCK 0xFF |
| 30 | +#define ISSI_INTERRUPTMASKREGISTER 0xF0 |
| 31 | +#define ISSI_INTERRUPTSTATUSREGISTER 0xF1 |
| 32 | + |
| 33 | + |
| 34 | +#define ISSI_PAGE_PWM 0x00 // PG0 |
| 35 | +#define ISSI_PAGE_PWM_CONTROL 0x01 // PG1 |
| 36 | + |
| 37 | +#define ISSI_PAGE_AUTOBREATH 0x02 // PG2 |
| 38 | +#define ISSI_PAGE_FUNCTION 0x03 // PG3 |
| 39 | + |
| 40 | +#define ISSI_REG_CONFIGURATION 0x00 // PG3 |
| 41 | +#define ISSI_REG_GLOBALCURRENT 0x01 // PG3 |
| 42 | +#define ISSI_REG_RESET 0x11 // PG3 |
| 43 | +#define ISSI_REG_SWPULLUP 0x0F // PG3 |
| 44 | +#define ISSI_REG_CSPULLUP 0x10 // PG3 |
| 45 | + |
| 46 | +#ifndef ISSI_TIMEOUT |
| 47 | +# define ISSI_TIMEOUT 100 |
| 48 | +#endif |
| 49 | + |
| 50 | +#ifndef ISSI_PERSISTENCE |
| 51 | +# define ISSI_PERSISTENCE 0 |
| 52 | +#endif |
| 53 | + |
| 54 | +#ifndef ISSI_PWM_FREQUENCY |
| 55 | +# define ISSI_PWM_FREQUENCY 0b000 |
| 56 | +#endif |
| 57 | + |
| 58 | +#ifndef ISSI_SWPULLUP |
| 59 | +# define ISSI_SWPULLUP PUR_0R |
| 60 | +#endif |
| 61 | + |
| 62 | +#ifndef ISSI_CSPULLUP |
| 63 | +# define ISSI_CSPULLUP PUR_0R |
| 64 | +#endif |
| 65 | + |
| 66 | +#ifndef ISSI_GLOBALCURRENT |
| 67 | +# define ISSI_GLOBALCURRENT 0x80 |
| 68 | +#endif |
| 69 | + |
| 70 | +const uint8_t i2c_addresses[DRIVER_COUNT] = { |
| 71 | + DRIVER_ADDR_1,DRIVER_ADDR_2, |
| 72 | +}; |
| 73 | + |
| 74 | +// Transfer buffer for TWITransmitData() |
| 75 | +uint8_t g_twi_transfer_buffer[20]; |
| 76 | + |
| 77 | +// These buffers match the IS31FL3733 PWM registers. |
| 78 | +// The control buffers match the PG0 LED On/Off registers. |
| 79 | +// Storing them like this is optimal for I2C transfers to the registers. |
| 80 | +// We could optimize this and take out the unused registers from these |
| 81 | +// buffers and the transfers in is31fl3733_write_pwm_buffer() but it's |
| 82 | +// probably not worth the extra complexity. |
| 83 | +uint8_t g_pwm_buffer[DRIVER_COUNT][328]; |
| 84 | +bool g_pwm_buffer_update_required[DRIVER_COUNT] = {false}; |
| 85 | + |
| 86 | +uint8_t g_led_control_registers[DRIVER_COUNT][24] = {0}; |
| 87 | +bool g_led_control_registers_update_required[DRIVER_COUNT] = {false}; |
| 88 | + |
| 89 | + |
| 90 | +// I2C |
| 91 | +void is31fl3763_write_register(uint8_t index, uint8_t reg, uint8_t data) { |
| 92 | + // If the transaction fails function returns false. |
| 93 | +#if ISSI_PERSISTENCE > 0 |
| 94 | + for (uint8_t i = 0; i < ISSI_PERSISTENCE; i++) { |
| 95 | + if (i2c_write_register(i2c_addresses[index] << 1, reg, &data, 1, ISSI_TIMEOUT) != 0) { |
| 96 | + return false; |
| 97 | + } |
| 98 | + } |
| 99 | +#else |
| 100 | + i2c_write_register(i2c_addresses[index] << 1, reg, &data, 1, ISSI_TIMEOUT); |
| 101 | +#endif |
| 102 | +} |
| 103 | + |
| 104 | +uint8_t PWM_H_Tab[14][12] = |
| 105 | +{ |
| 106 | +//sw 1 2 3 4 5 6 7 8 9 10 11 12 |
| 107 | + {0x01,0x02,0x04,0x05,0x07,0x08,0x0A,0x0B,0X0D,0X0E,0X10,0X11}, //CS01 | CS15 |
| 108 | + {0X13,0X14,0X16,0X17,0X19,0X1A,0X1C,0X1D,0X1F,0X20,0X22,0X23}, //CS02 | CS16 |
| 109 | + {0X25,0X26,0X28,0X29,0X2B,0X2C,0X2E,0X2F,0X31,0X32,0X34,0X35}, //CS03 | CS17 |
| 110 | + {0X37,0X38,0X3A,0X3B,0X3D,0X3E,0X40,0X41,0X43,0X44,0X46,0X47}, //CS04 | CS18 |
| 111 | + {0X49,0X4A,0X4C,0X4D,0X4F,0X50,0X52,0X53,0X55,0X56,0X58,0X59}, //CS05 |
| 112 | + {0X5B,0X5C,0X5E,0X5F,0X61,0X62,0X64,0X65,0X67,0X68,0X6A,0X6B}, //CS06 |
| 113 | + {0X6D,0X6E,0X70,0X71,0X73,0X74,0X76,0X77,0X79,0X7A,0X7C,0X7D}, //CS07 |
| 114 | + {0X7F,0X80,0X82,0X83,0X85,0X86,0X88,0X89,0X8B,0X8C,0X8E,0X8F}, //CS08 |
| 115 | + {0x91,0x92,0x94,0x95,0x97,0x98,0x9A,0x9B,0X9D,0X9E,0XA0,0XA1}, //CS09 |
| 116 | + {0XA3,0XA4,0XA6,0XA7,0XA9,0XAA,0XAC,0XAD,0XAF,0XB0,0XB2,0XB3}, //CS10 |
| 117 | + {0XB5,0XB6,0XB8,0XB9,0XBB,0XBC,0XBE,0XBF,0XC1,0XC2,0XC4,0XC5}, //CS11 |
| 118 | + {0XC7,0XC8,0XCA,0XCB,0XCD,0XCE,0XD0,0XD1,0XD3,0XD4,0XD6,0XD7}, //CS12 |
| 119 | + {0XD9,0XDA,0XDC,0XDD,0XDF,0XE0,0XE2,0XE3,0XE5,0XE6,0XE8,0XE9}, //CS13 |
| 120 | + {0XEB,0XEC,0XEE,0XEF,0XF1,0XF2,0XF4,0XF5,0XF7,0XF8,0XFA,0XFB} //CS14 |
| 121 | +}; |
| 122 | + |
| 123 | + |
| 124 | +void is31fl3763_write_pwm_buffer(uint8_t index) { |
| 125 | + |
| 126 | + uint16_t addr_sart = 0; |
| 127 | + |
| 128 | + for (int i = 0; i < 18; i += 1) { |
| 129 | + |
| 130 | + if(i >= 14) { |
| 131 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 132 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM_CONTROL); |
| 133 | + |
| 134 | + addr_sart = ((i-14)*18); |
| 135 | + |
| 136 | + i2c_write_register(i2c_addresses[index] << 1, addr_sart, &g_pwm_buffer[index][(addr_sart | 0X100)], 18, ISSI_TIMEOUT); |
| 137 | + |
| 138 | + } else { |
| 139 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 140 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM); |
| 141 | + |
| 142 | + addr_sart = (i*18); |
| 143 | + |
| 144 | + i2c_write_register(i2c_addresses[index] << 1, addr_sart, &g_pwm_buffer[index][(addr_sart)], 18, ISSI_TIMEOUT); |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + is31fl3763_write_register(index, 0xFD, 0x00); |
| 149 | +} |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | +void is31fl3763_init_drivers(void) { |
| 154 | + i2c_init(); |
| 155 | + |
| 156 | + for (uint8_t i = 0; i < DRIVER_COUNT; i++) { |
| 157 | + is31fl3763_init(i); |
| 158 | + } |
| 159 | + |
| 160 | + for (int i = 0; i < DRIVER_COUNT; i++) { |
| 161 | + is31fl3763_set_led_control_register(i, true, true, true); |
| 162 | + } |
| 163 | + |
| 164 | + for (uint8_t i = 0; i < DRIVER_COUNT; i++) { |
| 165 | + is31fl3763_update_led_control_registers(i); |
| 166 | + } |
| 167 | +} |
| 168 | + |
| 169 | +void is31fl3763_init(uint8_t index) { |
| 170 | + |
| 171 | + uint8_t i, j; |
| 172 | + |
| 173 | + // Unlock the command register. |
| 174 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 175 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM); |
| 176 | + |
| 177 | + // Turn off all LEDs. |
| 178 | + for(i = 0;i < 14;i++) { |
| 179 | + for(j = 0;j < 12; j++) { |
| 180 | + is31fl3763_write_register(index, PWM_H_Tab[i][j], 0X00); |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + // Unlock the command register. |
| 185 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 186 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM_CONTROL); |
| 187 | + |
| 188 | + // Turn off all LEDs. |
| 189 | + for(i = 0;i < 4;i++) { |
| 190 | + for(j = 0;j < 12; j++) { |
| 191 | + is31fl3763_write_register(index, PWM_H_Tab[i][j], 0X00); |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + // Unlock the command register. |
| 196 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 197 | + // Select PG1 |
| 198 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM_CONTROL); |
| 199 | + // Set de-ghost pull-up resistors (SWx)(CSx) |
| 200 | + is31fl3763_write_register(index, 0x62, 0x66); // |
| 201 | + // Set global current to maximum. |
| 202 | + is31fl3763_write_register(index, 0x61, ISSI_GLOBALCURRENT); |
| 203 | + // Disable software shutdown. |
| 204 | + is31fl3763_write_register(index, 0x60,0x09); |
| 205 | + |
| 206 | + is31fl3763_write_register(index, 0x63, 0x00); |
| 207 | + |
| 208 | + uint8_t SL_Set; |
| 209 | + for(SL_Set = 0x49;SL_Set <= 0x5A;SL_Set++) { |
| 210 | + is31fl3763_write_register(index, SL_Set, 0x80); |
| 211 | + } |
| 212 | + is31fl3763_write_register(index, 0xFD, 0x00); //update |
| 213 | + |
| 214 | + // Wait 10ms to ensure the device has woken up. |
| 215 | + wait_ms(10); |
| 216 | +} |
| 217 | + |
| 218 | +void is31fl3763_set_color(int index, uint8_t red, uint8_t green, uint8_t blue) { |
| 219 | + is31_led led; |
| 220 | + if (index >= 0 && index < RGB_MATRIX_LED_COUNT) { |
| 221 | + memcpy_P(&led, (&g_is31_leds[index]), sizeof(led)); |
| 222 | + |
| 223 | + g_pwm_buffer[led.driver][led.r] = red; |
| 224 | + g_pwm_buffer[led.driver][led.g] = green; |
| 225 | + g_pwm_buffer[led.driver][led.b] = blue; |
| 226 | + g_pwm_buffer_update_required[led.driver] = true; |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +void is31fl3763_set_color_all(uint8_t red, uint8_t green, uint8_t blue) { |
| 231 | + for (int i = 0; i < RGB_MATRIX_LED_COUNT; i++) { |
| 232 | + is31fl3763_set_color(i, red, green, blue); |
| 233 | + } |
| 234 | +} |
| 235 | + |
| 236 | +void is31fl3763_set_led_control_register(uint8_t index, bool red, bool green, bool blue) { |
| 237 | + is31_led led; |
| 238 | + memcpy_P(&led, (&g_is31_leds[index]), sizeof(led)); |
| 239 | + |
| 240 | + uint8_t control_register_r = led.r / 8; |
| 241 | + uint8_t control_register_g = led.g / 8; |
| 242 | + uint8_t control_register_b = led.b / 8; |
| 243 | + uint8_t bit_r = led.r % 8; |
| 244 | + uint8_t bit_g = led.g % 8; |
| 245 | + uint8_t bit_b = led.b % 8; |
| 246 | + |
| 247 | + if (red) { |
| 248 | + g_led_control_registers[led.driver][control_register_r] |= (1 << bit_r); |
| 249 | + } else { |
| 250 | + g_led_control_registers[led.driver][control_register_r] &= ~(1 << bit_r); |
| 251 | + } |
| 252 | + if (green) { |
| 253 | + g_led_control_registers[led.driver][control_register_g] |= (1 << bit_g); |
| 254 | + } else { |
| 255 | + g_led_control_registers[led.driver][control_register_g] &= ~(1 << bit_g); |
| 256 | + } |
| 257 | + if (blue) { |
| 258 | + g_led_control_registers[led.driver][control_register_b] |= (1 << bit_b); |
| 259 | + } else { |
| 260 | + g_led_control_registers[led.driver][control_register_b] &= ~(1 << bit_b); |
| 261 | + } |
| 262 | + |
| 263 | + g_led_control_registers_update_required[led.driver] = true; |
| 264 | +} |
| 265 | + |
| 266 | +void is31fl3763_update_pwm_buffers(uint8_t index) { |
| 267 | + if (g_pwm_buffer_update_required[index]) { |
| 268 | + // Firstly we need to unlock the command register and select PG1. |
| 269 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 270 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, ISSI_PAGE_PWM); |
| 271 | + |
| 272 | + is31fl3763_write_pwm_buffer(index); |
| 273 | + |
| 274 | + g_pwm_buffer_update_required[index] = false; |
| 275 | + } |
| 276 | +} |
| 277 | + |
| 278 | + |
| 279 | +void is31fl3763_update_led_control_registers(uint8_t index) { |
| 280 | + if (g_led_control_registers_update_required[index]) { |
| 281 | + // Firstly we need to unlock the command register and select PG0 |
| 282 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER_WRITELOCK, 0xC5); |
| 283 | + is31fl3763_write_register(index, ISSI_COMMANDREGISTER, 0x01); |
| 284 | + for (int i = 0; i < 24; i++) { |
| 285 | + is31fl3763_write_register(index, i, g_led_control_registers[index][i]); |
| 286 | + } |
| 287 | + } |
| 288 | + g_led_control_registers_update_required[index] = false; |
| 289 | +} |
| 290 | + |
| 291 | +void is31fl3763_flush(void) { |
| 292 | + for (uint8_t i = 0; i < DRIVER_COUNT; i++) { |
| 293 | + is31fl3763_update_pwm_buffers(i); |
| 294 | + } |
| 295 | +} |
0 commit comments