Files
hsc26-artemis2/firmware/app/driver/aw20xxx.c
true 9bc0ad4952 reduced default power due to hardware spec fault
The boost converter is fairly weak. This was known going into this design. However, the LED brightness wasn't ever really calculated and was just assumed it would be kept under control by the specific LED programs in use.

Since per-color gain tuning isn't implemented yet, for now the default global gain is turned down to prevent voltage drop from turning off white LEDs when programs that light a significant number of LEDs are run. If turning up the brightness the white LEDs can still drop.
2026-05-09 10:29:33 -07:00

247 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 = 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
//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)
{
}