initial WIP
lots of code copied over, things filled in to hopefully get the LED matrix lighting up. untested.
This commit is contained in:
247
firmware/app/driver/aw20xxx.c
Normal file
247
firmware/app/driver/aw20xxx.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user