hsc26-artemis2/firmware/app/driver/aw20xxx.c
true d95af918fa initial WIP
lots of code copied over, things filled in to hopefully get the LED matrix lighting up. untested.
2026-05-08 11:54:12 -07:00

248 lines
7.3 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
* - 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)
{
}