/* * awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver * * originally written by true in 2022 * while sleep deprived for a super constrained mcu * * some bugs fixed for defcon 32 on aug 6-7, 2024 * - don't remember what was fixed * * some bugs fixed for supercon on oct 15, 2024 * - fixed support for AW20054, which only supports 9 rows * did this by respecting aw->rows * * * driver assumptions: * - rows and columns are as is ordered on the chip, lowest to highest * (if any are skipped, just skip this data in your buffer) * - buffer size does not need to encompass all possible LEDs, only those * which you have specified you are using. * ensure sizeof(aw->fade) == aw->rows + aw->cols * - duty cycle will be set automatically according to aw->cols * - all AW20xxx chips will operate on the same i2c bus * - a static data buffer is used * * driver notices: * - updates only happen one column at a time, and are blocking * (future version may implement a callback when each column is done) * - this driver has not yet implemented the pattern controller * - this driver has not yet implemented the GAIN register, except as * a global configuration, so dimming only operates 8-bit * (will be implemented later to allow for beyond-8-bit operation) * - this driver has not yet implemented FADEDIM mode * * if you need anything different, write it yourself */ #include "aw20xxx.h" #define AW20X_THIS_PAGE (aw->state & AW20X_STATE_PAGE_MASK) #define AW20X_SET_PAGE(x) if (AW20X_THIS_PAGE != (x)) { \ aw20x_page(aw, x); \ aw->state &= ~AW20X_STATE_PAGE_MASK; \ aw->state |= x; } static uint8_t aw_buf[25]; // enough bytes for register and single column FADEDIM update (1 + (12*2)) void aw20x_page(struct AW20x *aw, uint8_t page) { // the datasheet isn't clear on this. the default is zero, and only the lower // three bits are specified. yet the DS says to send 0xCy where y is the page bits. // we'll just do what the DS says even though it contradicts itself. aw_buf[0] = 0xc0 | (page & AW20X_PAGE_MASK); AW20X_I2C_writereg(aw->addr, AW20X_REG_PAGE, aw_buf, 1); } void aw20x_init(struct AW20x *aw, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t imax) { // set config register as specified aw->addr = addr; aw->cols = cols; aw->rows = rows; aw->config = imax & AW20X_CONF_IMAX_MASK; // wake up (and set page to config page) aw20x_set_sleep(aw, 0); // enabled columns aw_buf[0] = cols - 1; while (AW20X_I2C_busy()) {}; AW20X_I2C_writereg(aw->addr, AW20X_REG_SIZE, aw_buf, 1); // general config aw_buf[0] = imax & AW20X_CONF_IMAX_MASK; while (AW20X_I2C_busy()) {}; AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1); } void aw20x_set_sleep(struct AW20x *aw, uint8_t sleep) { // make sure we're on the config page AW20X_SET_PAGE(AW20X_PAGE0_CONFIG); aw_buf[0] = sleep ? AW20X_SLPCR_SLEEP : 0; // send sleep bit while (AW20X_I2C_busy()) {}; AW20X_I2C_writereg(aw->addr, AW20X_REG_SLPCR, aw_buf, 1); // set state if (sleep) aw->state |= AW20X_STATE_SLEEP_MASK; else aw->state &= ~AW20X_STATE_SLEEP_MASK; // burn some cycles if we woke up if (!sleep) AW20X_INIT_DELAY(); } void aw20x_set_imax(struct AW20x *aw, uint8_t imax) { // make sure we're on the config page AW20X_SET_PAGE(AW20X_PAGE0_CONFIG); aw_buf[0] = imax & AW20X_CONF_IMAX_MASK; // send imax while (AW20X_I2C_busy()) {}; AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1); aw->config = imax & AW20X_CONF_IMAX_MASK; } /* * sends LED values to the chip */ void aw20x_set_fade(struct AW20x *aw) { uint8_t c; uint8_t row; uint8_t offset; // make sure we're on the fade page AW20X_SET_PAGE(AW20X_PAGE2_FADE); // don't touch the buffer until we are allowed while (AW20X_I2C_busy()) {}; row = offset = 0; for (c = 0; c < aw->cols; c++) { // write to chip AW20X_I2C_writereg(aw->addr, offset, aw->fade + row, aw->rows); while (AW20X_I2C_busy()); row += aw->rows; offset += AW20X_MAX_ROWS; } } void aw20x_set_dim(struct AW20x *aw) { // todo: implement } /* * sets all LEDs to the specified 6-bit DIM value. * used when just using FADE and 8-bit mode * to set initial and fine tune from IMAX the output current. */ void aw20x_set_dim_global(struct AW20x *aw, uint8_t dim) { uint8_t i; uint8_t row; uint8_t offset; // ceil if (dim > 0x3f) dim = 0x3f; // make sure we're on the dim page AW20X_SET_PAGE(AW20X_PAGE1_DIM); // don't touch the buffer until we are allowed while (AW20X_I2C_busy()) {}; // clear buffer for (i = 0; i <= aw->rows; i++) aw_buf[i] = dim; // send buffer for each column row = offset = 0; for (i = 0; i < aw->cols; i++) { AW20X_I2C_writereg(aw->addr, offset, aw_buf, aw->rows); while (AW20X_I2C_busy()); row += aw->rows; offset += AW20X_MAX_ROWS; } } void aw20x_commit_fadedim(struct AW20x *aw) { } void aw20x_led_on(struct AW20x *aw, uint8_t first, uint8_t last, uint8_t on_bit) { } /* * enables LEDs based on user LED count, zero-indexed * assumes AW20X_MAX_LEDON_BITS per row but will only turn on active defined rows * AW20036 would be 0-35, but AW20054 would be 0-71 and so on * for example, LEDs 8-12 on AW20054 with 9 roww enabled * would enable C0R8, C1R0, C1R1, C1R2. all other LEDs are disabled * * todo: * - read current state, and apply bitfields to the currently active state * - allow bypassing the readback for faster operation (such as setting all LEDs on at startup) * - make this more efficient (36 LEDs takes ~0.3ms on a 48MHz PIC!) */ void aw20x_led_enable_range(struct AW20x *aw, uint8_t first, uint8_t last) { uint8_t c, r; uint8_t idx; uint8_t boff; uint8_t *buf; // make sure we're on the config page AW20X_SET_PAGE(AW20X_PAGE0_CONFIG); // don't touch the buffer until we are allowed while (AW20X_I2C_busy()); // bits are stored 6 bits per byte, 2 bytes per column, one bit for each row // for a maximum of the AW20X_MAX_ROWS of LED on bits // we only want to touch bits that exist on the chip and in the correct order idx = 0; for (c = 0; c < aw->cols; c++) { buf = &aw_buf[c*2]; buf[0] = buf[1] = 0; boff = 0; for (r = 0; r < AW20X_MAX_LEDON_BITS*2; r++) { if (r + idx < first) continue; // only start at the first led if (r + idx > last) break; // and stop at the last if (r >= aw->rows) continue; // max bits to process per row if (r == AW20X_MAX_LEDON_BITS) { // only this many bits per byte boff += AW20X_MAX_LEDON_BITS; buf++; } *buf |= (1 << (r - boff)); } idx += AW20X_MAX_LEDON_BITS*2; } AW20X_I2C_writereg(aw->addr, AW20X_REG_LEDON0, aw_buf, c*2); } /* * disables LEDs based on user LED count, zero-indexed * (for example, LEDs 8-12 on AW20054 would enable C0R8, C0R9, C1R0, C1R1) */ void aw20x_led_disable(struct AW20x *aw, uint8_t first, uint8_t last) { }