218 lines
6.2 KiB
C
218 lines
6.2 KiB
C
|
/*
|
||
|
* 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
|
||
|
*
|
||
|
* driver assumptions:
|
||
|
* - rows and columns are used in order on the chip, lowest to highest
|
||
|
* (if any are skipped, just skip this data in your buffer)
|
||
|
* - duty cycle will be set according to the column count
|
||
|
* - all AW20xxx chips will operate on the same i2c bus
|
||
|
* - the only i2c write routine does not have register arguments
|
||
|
*
|
||
|
* 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, so only operates 8-bit
|
||
|
* (will be implemented later to allow for beyond-8-bit operation)
|
||
|
* - this driver has not yet implemented FADEDIM mode
|
||
|
* - all transfers result in copies of data, which is wasteful
|
||
|
* (future version may transfer LED data directly from the buffer)
|
||
|
*
|
||
|
* 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;
|
||
|
|
||
|
// ensure we are on page 0 to start
|
||
|
aw20x_page(aw, 0);
|
||
|
while (AW20X_I2C_busy());
|
||
|
|
||
|
// wake up
|
||
|
aw20x_sleep(aw, 0);
|
||
|
|
||
|
// enabled columns
|
||
|
aw_buf[0] = cols - 1;
|
||
|
AW20X_I2C_writereg(aw->addr, AW20X_REG_SIZE, aw_buf, 1);
|
||
|
|
||
|
// general config
|
||
|
aw_buf[0] = imax & AW20X_CONF_IMAX_MASK;
|
||
|
AW20X_I2C_writereg(aw->addr, AW20X_REG_GCCR, aw_buf, 1);
|
||
|
while (AW20X_I2C_busy());
|
||
|
}
|
||
|
|
||
|
void aw20x_sleep(struct AW20x *aw, uint8_t sleep)
|
||
|
{
|
||
|
// 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());
|
||
|
|
||
|
// send sleep bit
|
||
|
aw_buf[0] = sleep ? AW20X_SLPCR_SLEEP : 0;
|
||
|
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) PLATFORM_INIT_DELAY();
|
||
|
}
|
||
|
|
||
|
void aw20x_imax(struct AW20x *aw, uint8_t imax)
|
||
|
{
|
||
|
AW20X_SET_PAGE(AW20X_PAGE0_CONFIG);
|
||
|
|
||
|
// todo: implement
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* sends LED values to the chip
|
||
|
*/
|
||
|
void aw20x_commit_fade(struct AW20x *aw)
|
||
|
{
|
||
|
uint8_t c;
|
||
|
uint8_t row;
|
||
|
|
||
|
// 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 = 0;
|
||
|
for (c = 0; c < aw->cols; c++) {
|
||
|
// write to chip
|
||
|
AW20X_I2C_writereg(aw->addr, row, aw->fade + row, aw->rows);
|
||
|
while (AW20X_I2C_busy());
|
||
|
row += AW20X_MAX_ROWS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void aw20x_commit_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_commit_dim_global(struct AW20x *aw, uint8_t dim)
|
||
|
{
|
||
|
uint8_t i;
|
||
|
uint8_t row = 0;
|
||
|
|
||
|
// 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
|
||
|
for (i = 0; i < aw->cols; i++) {
|
||
|
AW20X_I2C_writereg(aw->addr, row, aw_buf, aw->rows);
|
||
|
while (AW20X_I2C_busy());
|
||
|
row += AW20X_MAX_ROWS;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void aw20x_commit_fadedim(struct AW20x *aw)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
static inline 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
|
||
|
* AW20036 would be 0-35, AW00054 would be 0-53, and so on
|
||
|
* for example, LEDs 8-12 on AW20054 would enable C0R8, C1R0, C1R1, C1R2
|
||
|
*
|
||
|
* 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(struct AW20x *aw, uint8_t first, uint8_t last)
|
||
|
{
|
||
|
uint8_t c, r;
|
||
|
uint8_t boff;
|
||
|
|
||
|
// 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
|
||
|
// we only want to touch bits that exist on the chip and in the correct order
|
||
|
boff = 0;
|
||
|
for (c = 0; c < (aw->cols * 2); c++) {
|
||
|
aw_buf[c] = 0;
|
||
|
for (r = 0; r < AW20X_MAX_LEDON_BITS; r++) {
|
||
|
if (r+boff >= first) {
|
||
|
if (r+boff <= last) {
|
||
|
aw_buf[c] |= (1 << r);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
boff += AW20X_MAX_LEDON_BITS;
|
||
|
}
|
||
|
|
||
|
AW20X_I2C_writereg(aw->addr, AW20X_REG_LEDON0, aw_buf, c);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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)
|
||
|
{
|
||
|
|
||
|
}
|