diff --git a/datasheets/CH592DS1_EN.PDF b/datasheets/CH592DS1_EN.PDF new file mode 100644 index 0000000..d50e104 Binary files /dev/null and b/datasheets/CH592DS1_EN.PDF differ diff --git a/nametag8_CH592/user/comm/i2c.h b/nametag8_CH592/user/comm/i2c.h new file mode 100644 index 0000000..64ead2d --- /dev/null +++ b/nametag8_CH592/user/comm/i2c.h @@ -0,0 +1,41 @@ +/* + * i2c.h + * + * Created on: Oct 13, 2024 + * Author: true + */ + +#ifndef USER_COMM_I2C_H_ +#define USER_COMM_I2C_H_ + + + +#ifdef SOFT_I2C_MASTER + + +#include "soft_i2c_master.h" + + +#define i2c_init() i2cm_init() +#define i2c_start() i2cm_start() +#define i2c_restart() i2cm_restart() +#define i2c_stop() i2cm_stop() +#define i2c_rd(ack) i2cm_rd(ack) +#define i2c_wr(dat) i2cm_wr(dat) +#define i2c_addr(a, w) i2c_start(); i2cm_addr(a, w) + +#define i2c_rdbuf(d, x) i2cm_rdbuf(d, x) +#define i2c_wrbuf(d, x) i2cm_wrbuf(d, x) + + +#else + + +#error HW I2C NOT YET IMPLEMENTED! + + +#endif + + + +#endif /* USER_COMM_I2C_H_ */ diff --git a/nametag8_CH592/user/comm/soft_i2c_master.c b/nametag8_CH592/user/comm/soft_i2c_master.c index 66bba2e..831e819 100644 --- a/nametag8_CH592/user/comm/soft_i2c_master.c +++ b/nametag8_CH592/user/comm/soft_i2c_master.c @@ -1,128 +1,166 @@ /* * soft_i2c.c * - * Created on: Oct 11, 2024 - * Author: true + * copied some random code and fixed it up for intricacies of CH59x. + * + * i2c hardware peripheral is shared on CH59x with debug pins and is + * not attached to DMA. while having the peripheral would result in + * higher speeds and less power usage, without debug it makes soft I2C + * master a helpful tool. + * + * quirks and features: + * - this implementation is blocking. + * - this implementation uses a hardware timer. + * - clock stretching is not supported. + * - timings likely haven't been tested so long as this message exists. */ - -// =================================================================================== -// Software I2C Master Functions for CH32X035/X034/X033 * v1.1 * -// modified for CH592 by true -// =================================================================================== -// -// Simple I2C bitbanging. ACK bit of the slave is ignored. Clock stretching by the -// slave is not allowed. External pull-up resistors (4k7 - 10k) are mandatory! -// -// Further information: https://github.com/wagiminator/ATtiny13-TinyOLEDdemo -// 2023 by Stefan Wagner: https://github.com/wagiminator -// -// This implementation requires 48MHz clock for 400KHz communication. - #include "soft_i2c_master.h" -// =================================================================================== -// I2C Delay -// =================================================================================== -#define I2C_DLY_TICKS_H (((F_CPU * 9) / (I2C_CLKRATE * 25)) - 41) -#define I2C_DLY_TICKS_L (((F_CPU * 16) / (I2C_CLKRATE * 25)) - 76) -#if I2C_DLY_TICKS_H >= 1 - #define I2C_DELAY_H() DLY_ticks(I2C_DLY_TICKS_H) -#else - #define I2C_DELAY_H() -#endif -#if I2C_DLY_TICKS_L >= 1 - #define I2C_DELAY_L() DLY_ticks(I2C_DLY_TICKS_L) -#else - #define I2C_DELAY_L() -#endif +#define CYCLES_TO_HI 16 +#define CYCLES_TO_LO 16 -// =================================================================================== -// I2C Pin Macros -// =================================================================================== -#define I2C_SDA_HIGH() PIN_input(PIN_SDA) // release SDA -> pulled HIGH by resistor -#define I2C_SDA_LOW() PIN_output(PIN_SDA) // SDA LOW -> pulled LOW by MCU -#define I2C_SCL_HIGH() PIN_input(PIN_SCL) // release SCL -> pulled HIGH by resistor -#define I2C_SCL_LOW() PIN_output(PIN_SCL) // SCL LOW -> pulled LOW by MCU -#define I2C_SDA_READ() PIN_read(PIN_SDA) // read SDA pin -#define I2C_CLOCKOUT() I2C_DELAY_L();I2C_SCL_HIGH();I2C_DELAY_H();I2C_SCL_LOW() +#define CYCLES_RD 2 // cycles spent in read routine +#define CYCLES_EXTRA_WR_HI 2 // extra cycles spent in write routine +#define CYCLES_EXTRA_WR_LO 4 -// =================================================================================== -// I2C Functions -// =================================================================================== -// I2C init function -void I2C_init(void) { - PIN_input(PIN_SCL); // release SCL - PIN_input(PIN_SDA); // release SDA - PIN_low(PIN_SCL); // preset for SCL low - PIN_low(PIN_SDA); // preset for SDA low + +static uint16_t delay_hi, delay_lo; + + +static volatile uint16_t spin; + +#define bit_delay_hi() spin = delay_hi; while(spin--) +#define bit_delay_lo() spin = delay_lo; while(spin--) + +#define rd_delay() spin = delay_hi - CYCLES_RD; while(spin--) + +#define wr_delay_hi() spin = delay_hi - CYCLES_EXTRA_WR_HI; while(spin--) +#define wr_delay_lo() spin = delay_lo - CYCLES_EXTRA_WR_LO; while(spin--) + + + +// re-run init any time the clock speed changes to recalculate delays. +void i2cm_init() +{ + uint32_t sysclk; + uint32_t cycles; + + // configure GPIO + SCL_IN_HI(); + SCL_SET_LO(); + + SDA_IN_HI(); + SDA_SET_LO(); + + // configure timer + sysclk = GetSysClock(); + cycles = sysclk / 500000; + + delay_hi = (cycles - CYCLES_TO_HI) / 4; + delay_lo = (cycles - CYCLES_TO_LO) / 4; } -// I2C transmit one data byte to the slave, ignore ACK bit, no clock stretching allowed -void I2C_write(uint8_t data) { - uint8_t i; - for(i=8; i; i--, data<<=1) { // transmit 8 bits, MSB first - (data & 0x80) ? (I2C_SDA_HIGH()) : (I2C_SDA_LOW()); // SDA HIGH if bit is 1 - I2C_CLOCKOUT(); // clock out -> slave reads the bit - } - I2C_SDA_HIGH(); // release SDA for ACK bit of slave - I2C_CLOCKOUT(); // 9th clock pulse is for the ignored ACK bit +void i2cm_start() +{ + SDA_IN_HI(); bit_delay_hi(); + SCL_IN_HI(); bit_delay_hi(); + SDA_OUTLO(); bit_delay_lo(); + SCL_OUTLO(); bit_delay_lo(); } -// I2C start transmission -void I2C_start(uint8_t addr) { - I2C_SDA_LOW(); // start condition: SDA goes LOW first - I2C_DELAY_H(); // delay - I2C_SCL_LOW(); // start condition: SCL goes LOW second - I2C_write(addr); // send slave address +void i2cm_restart() +{ + SDA_IN_HI(); bit_delay_hi(); + SCL_IN_HI(); + i2cm_start(); } -// I2C restart transmission -void I2C_restart(uint8_t addr) { - I2C_SDA_HIGH(); // prepare SDA for HIGH to LOW transition - I2C_DELAY_H(); // delay - I2C_SCL_HIGH(); // restart condition: clock HIGH - I2C_start(addr); // start again +void i2cm_stop() +{ + SDA_OUTLO(); bit_delay_lo(); + SCL_IN_HI(); bit_delay_hi(); + SDA_IN_HI(); bit_delay_hi(); } -// I2C stop transmission -void I2C_stop(void) { - I2C_SDA_LOW(); // prepare SDA for LOW to HIGH transition - I2C_DELAY_H(); // delay - I2C_SCL_HIGH(); // stop condition: SCL goes HIGH first - I2C_DELAY_H(); // delay - I2C_SDA_HIGH(); // stop condition: SDA goes HIGH second +// returns: data byte +uint8_t i2cm_rd(uint8_t ack) +{ + uint8_t x, in = 0; + + SDA_IN_HI(); + for (x = 8; x; x--) { + in <<= 1; // clock next bit + + SCL_IN_HI(); + while (!SCL_GET()); // clock stretch + + rd_delay(); + + if (SDA_GET()) in |= 1; + + SCL_OUTLO(); + } + + if (ack) { SDA_OUTLO(); } // ack + else { SDA_IN_HI(); } // nack + + SCL_IN_HI(); bit_delay_hi(); + + SDA_IN_HI(); + SCL_OUTLO(); + + return in; } -// I2C receive one data byte from the slave (ack=0 for last byte, ack>0 if more bytes to follow) -uint8_t I2C_read(uint8_t ack) { - uint8_t i; - uint8_t data = 0; // variable for the received byte - I2C_SDA_HIGH(); // release SDA -> will be toggled by slave - for(i=8; i; i--) { // receive 8 bits - data <<= 1; // bits shifted in right (MSB first) - I2C_DELAY_L(); // delay - I2C_SCL_HIGH(); // clock HIGH - I2C_DELAY_H(); // delay - if(I2C_SDA_READ()) data |= 1; // read bit - I2C_SCL_LOW(); // clock LOW -> slave prepares next bit - } - if(ack) I2C_SDA_LOW(); // pull SDA LOW to acknowledge (ACK) - I2C_CLOCKOUT(); // clock out -> slave reads ACK bit - return data; // return the received byte +// returns: possible ack from target +uint8_t i2cm_wr(uint8_t dat) +{ + uint8_t x; + uint8_t ack; + + for (x = 8; x; x--) { + if (dat & 0x80) { SDA_IN_HI(); SCL_IN_HI(); wr_delay_hi(); } + else { SDA_OUTLO(); SCL_OUTLO(); wr_delay_lo(); } + + dat <<= 1; + SCL_OUTLO(); + } + + SDA_IN_HI(); + SCL_IN_HI(); bit_delay_hi(); + + ack = SDA_GET(); + + SCL_OUTLO(); + return ack; } -// Send data buffer via I2C bus and stop -void I2C_writeBuffer(uint8_t* buf, uint16_t len) { - while(len--) I2C_write(*buf++); // write buffer - I2C_stop(); // stop transmission +// use a left-aligned address with this implementation. +uint8_t i2cm_addr(uint8_t addr, uint8_t write) +{ + addr &= ~0x1; + addr |= write ? 0 : 1; + i2cm_wr(addr); } -// Read data via I2C bus to buffer and stop -void I2C_readBuffer(uint8_t* buf, uint16_t len) { - while(len--) *buf++ = I2C_read(len > 0); - I2C_stop(); + +void i2cm_rdbuf(uint8_t *dat, uint8_t len) +{ + while(len--) *dat++ = i2cm_rd(len > 0); + i2cm_stop(); +} + +void i2cm_wrbuf(uint8_t *dat, uint8_t len) +{ + uint8_t nack; + + while(len--) { + nack = i2cm_wr(*dat++); + if (nack) break; + } + i2cm_stop(); } diff --git a/nametag8_CH592/user/comm/soft_i2c_master.h b/nametag8_CH592/user/comm/soft_i2c_master.h index 4c88fbe..d0d2e98 100644 --- a/nametag8_CH592/user/comm/soft_i2c_master.h +++ b/nametag8_CH592/user/comm/soft_i2c_master.h @@ -1,13 +1,55 @@ /* * soft_i2c.h - * - * Created on: Oct 11, 2024 - * Author: true */ -#ifndef USER_COMM_SOFT_I2C_H_ -#define USER_COMM_SOFT_I2C_H_ +#ifndef USER_COMM_SOFT_I2C_MASTER_H_ +#define USER_COMM_SOFT_I2C_MASTER_H_ + +#include +#include -#endif /* USER_COMM_SOFT_I2C_H_ */ +#define SDA_PIN 4 +#define SDA_BIT (1 << (SDA_PIN % 8)) +#define SDA_DIR_REG R8_PA_DIR_0 +#define SDA_DRV_REG R8_PA_PD_DRV_0 +#define SDA_CLR_REG R8_PA_CLR_0 +#define SDA_GET_REG R8_PA_PIN_0 + +#define SCL_PIN 12 +#define SCL_BIT (1 << (SCL_PIN % 8)) +#define SCL_DRV_REG R8_PB_PD_DRV_1 +#define SCL_DIR_REG R8_PB_DIR_1 +#define SCL_CLR_REG R8_PB_CLR_1 +#define SCL_GET_REG R8_PB_PIN_1 + + +#define SDA_IN_HI() { SDA_DRV_REG &= ~SDA_BIT; SDA_DIR_REG &= ~SDA_BIT; } +#define SDA_OUTLO() { SDA_DIR_REG |= SDA_BIT; SDA_DRV_REG |= SDA_BIT; } +#define SDA_SET_LO() SDA_CLR_REG = SDA_BIT +#define SDA_GET() ( SDA_GET_REG & SDA_BIT ) + +#define SCL_IN_HI() { SCL_DRV_REG &= ~SCL_BIT; SCL_DIR_REG &= ~SCL_BIT; } +#define SCL_OUTLO() { SCL_DIR_REG |= SCL_BIT; SCL_DRV_REG |= SCL_BIT; } +#define SCL_SET_LO() SCL_CLR_REG = SCL_BIT +#define SCL_GET() ( SCL_GET_REG & SCL_BIT ) + + + +void i2cm_init(); + +void i2cm_start(); +void i2cm_restart(); +void i2cm_stop(); + +uint8_t i2cm_rd(uint8_t ack); +uint8_t i2cm_wr(uint8_t dat); +uint8_t i2cm_addr(uint8_t addr, uint8_t write); + +void i2cm_rdbuf(uint8_t *dat, uint8_t len); +void i2cm_wrbuf(uint8_t *dat, uint8_t len); + + + +#endif /* USER_COMM_SOFT_I2C_MASTER_H_ */ diff --git a/nametag8_CH592/user/device/aw20xxx.c b/nametag8_CH592/user/device/aw20xxx.c index 7eee92c..37db2b0 100644 --- a/nametag8_CH592/user/device/aw20xxx.c +++ b/nametag8_CH592/user/device/aw20xxx.c @@ -1,8 +1,217 @@ /* - * aw20xxx.c + * awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver * - * Created on: Oct 11, 2024 - * Author: true + * 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) +{ + +} + +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) +{ + +} diff --git a/nametag8_CH592/user/device/aw20xxx.h b/nametag8_CH592/user/device/aw20xxx.h index d850ea4..52da264 100644 --- a/nametag8_CH592/user/device/aw20xxx.h +++ b/nametag8_CH592/user/device/aw20xxx.h @@ -1,13 +1,193 @@ /* - * aw20xxx.h - * - * Created on: Oct 11, 2024 - * Author: true + * awinic AW20108 / AW20072 / AW20054 / AW20036 LED Matrix Driver */ -#ifndef USER_DEVICE_AW20XXX_H_ -#define USER_DEVICE_AW20XXX_H_ +#ifndef AW20X_LED_MATRIX_H +#define AW20X_LED_MATRIX_H -#endif /* USER_DEVICE_AW20XXX_H_ */ +#include + +#include "../comm/i2c.h" + + + +#define PLATFORM_INIT_DELAY() { uint16_t zz = 1000; while(zz--); } + // burn cycles for ~200us + +#define AW20X_MAX_COLS 9 +#define AW20X_MAX_ROWS 12 + +#define AW20X_MAX_LEDON_BITS 6 + +#define AW20X_ADDR_SCL 0x38 // AD pin tied to SCL +#define AW20X_ADDR_SDA 0x39 // AD pin tied to SDA +#define AW20X_ADDR_GND 0x3A // AD pin tied to GND +#define AW20X_ADDR_VDD 0x3B // AD pin tied to VDD + +#define AW20X_CONF_IMAX_MASK 0xf0 // 7:4 +#define AW20X_CONF_IMAX_OFFSET 4 // 7:4 +#define AW20X_CONF_ADDR_MASK 0x03 // 1:0 +#define AW20X_CONF_ADDR_OFFSET 0 // 1:0 + +#define AW20X_CONF_USE_FADEDIM 0x08 // aw20x.fade now becomes fadedim; updates are done with FADEDIM page + // this mode uses 2 bytes per LED; dim as byte 0, fade as byte 1 +#define AW20X_CONF_USE_EXPEN 0x04 // sets GCCR.EXPEN=1; fade is now only 6 bits (not yet implemented) + +#define AW20X_PAGE_MASK 0x07 + +#define AW20X_STATE_PAGE_MASK AW20X_PAGE_MASK +#define AW20X_STATE_SLEEP_MASK 0x80 + + +#define AW20X_PAGE0_CONFIG 0x00 // function register +#define AW20X_PAGE1_DIM 0x01 // 5:0 dim; 8 bits per LED +#define AW20X_PAGE2_FADE 0x02 // 7:0 fade; 8 bits per LED +#define AW20X_PAGE3_PATTERN 0x03 // 1:0 pattern; 8 bits per LED +#define AW20X_PAGE4_DIMFADE 0x04 // 13:8 dim, 7:0 fade; 16 bits per LED +#define AW20X_PAGE5_DFP 0x05 // 15:14 pat, 13:8 dim, 7:0 fade; 16 bits per LED + +#define AW20X_REG_IDR 0x00 // R 7:0 chip ID {0x18} +#define AW20X_REG_SLPCR 0x01 // RW 7 sleep {0x80} +#define AW20X_REG_RSTR 0x02 // W 7:0 SW_RSTN (?) +#define AW20X_REG_GCCR 0x03 // RW 7:3 IMAX(7:4), ALLON, -, -, EXPEN {0x10} +#define AW20X_REG_FCD 0x04 // W 0 FCDE (fast clear display enable) +#define AW20X_REG_CLKSYS 0x05 // RW 1:0 CLK_IO, CLK_SEL +#define AW20X_REG_FLTCFG1 0x09 // RW 5:0 UVLOPE, OTPE, UVIE, OTIE, UVLOE, OTE +#define AW20X_REG_FLTCFG2 0x0a // RW 3:2 UVTH +#define AW20X_REG_ISRFLT 0x0b // RW 5:0 PAT2IS, PAT1IS, PAT0IS, -, -, UVLOIS, OTIS +#define AW20X_REG_LEDON0 0x31 // W 5:0 ON0:ON5, same pattern through to LEDON17 (0x42) +#define AW20X_REG_PATCR 0x43 // RW 6:0 PAT2IE, PAT1IE, PAT0IE, -, PAT2EN, PAT1EN, PAT0EN +#define AW20X_REG_FADEH0 0x44 // RW 7:0 FADEH0 +#define AW20X_REG_FADEH1 0x45 +#define AW20X_REG_FADEH2 0x46 +#define AW20X_REG_FADEL0 0x47 +#define AW20X_REG_FADEL1 0x48 +#define AW20X_REG_FADEL2 0x49 // RW 7:0 FADEL2 +#define AW20X_REG_PAT0T0 0x4a // RW 7:0 T1[4], T2[4] +#define AW20X_REG_PAT0T1 0x4b // RW 7:0 T3[4], T4[4] +#define AW20X_REG_PAT0T2 0x4c // RW 7:0 LE[2], LB[2], LT(11:8)[4] +#define AW20X_REG_PAT0T3 0x4d // RW 7:0 LT(7:0) +#define AW20X_REG_PAT1T0 0x4e +#define AW20X_REG_PAT1T1 0x4f +#define AW20X_REG_PAT1T2 0x50 +#define AW20X_REG_PAT1T3 0x51 +#define AW20X_REG_PAT2T0 0x52 +#define AW20X_REG_PAT2T1 0x53 +#define AW20X_REG_PAT2T2 0x54 +#define AW20X_REG_PAT2T3 0x55 +#define AW20X_REG_PAT0CFG 0x56 // RW 2:0 SWITCH, RAMPE, PATMD +#define AW20X_REG_PAT1CFG 0x57 +#define AW20X_REG_PAT2CFG 0x58 +#define AW20X_REG_PATGO 0x59 // RW 6:0 PAT2ST, PAT1ST, PAT0ST, -, RUN2, RUN1, RUN0 +#define AW20X_REG_SIZE 0x80 // RW 3:0 SWSEL +#define AW20X_REG_PAGE 0xf0 // RW 2:0 page select, 0-5; available from all pages + +#define AW20X_IDR_ID 0x18 // value for all chips in this series + +#define AW20X_SLPCR_SLEEP 0x01 // sleep mode (default is HIGH / asleep) + +#define AW20X_RSTR_SW_RSTN 0x01 // write value to soft reset the chip + +#define AW20X_GCCR_IMAX 0xf0 // global current setting (default 20mA) +#define AW20X_GCCR_ALLON 0x08 // 0=normal, 1=force all on +#define AW20X_GCCR_EXPEN 0x01 // 0=fade is linear 8-bit, 1=fade is exponential 6-bit + +#define AW20X_FCD_FCDE 0x01 // write value to clear display (DS doesn't specify; is this a blanker?) + +#define AW20X_CLKSYS_CLK_IO 0x02 // 0=no clk output, 1=clk output on (output) CLKIO pin +#define AW20X_CLKSYS_CLK_SEL 0x01 // 0=internal 4MHz, 1=use clk on (input) CLKIO pin + +#define AW20X_FLTCFG1_UVLOPE 0x20 // 1=enable UVLO protection; chip sets SLPCR.SLEEP when ISRFLT.UVLOIS=1 +#define AW20X_FLTCFG1_OTPE 0x10 // 1=enable overtemp protection; chip sets SLPCR.SLEEP when ISRFLT.UVLOIS=1 +#define AW20X_FLTCFG1_UVIE 0x08 // 1=UVLO interrupt enable +#define AW20X_FLTCFG1_OTIE 0x04 // 1=overtemp interrupt enable +#define AW20X_FLTCFG1_UVLOE 0x02 // 1=enable UVLO detect +#define AW20X_FLTCFG1_OTE 0x01 // 1=enable overtemp detect + +#define AW20X_FLTCFG1_UVTH_2V0 (0x00 << 2) // UVLO threshold voltage +#define AW20X_FLTCFG1_UVTH_2V1 (0x01 << 2) // UVLO threshold voltage +#define AW20X_FLTCFG1_UVTH_2V2 (0x02 << 2) // UVLO threshold voltage +#define AW20X_FLTCFG1_UVTH_2V3 (0x03 << 2) // UVLO threshold voltage + +#define AW20X_ISRFLT_PAT2IS 0x40 // pattern controller 2 interrupt (finished breath loop) +#define AW20X_ISRFLT_PAT1IS 0x20 // pattern controller 1 interrupt (finished breath loop) +#define AW20X_ISRFLT_PAT0IS 0x10 // pattern controller 0 interrupt (finished breath loop) +#define AW20X_ISRFLT_UVLOIS 0x02 // 0=normal, 1=UVLO detected +#define AW20X_ISRFLT_OTIS 0x01 // 0=normal, 1=overtemp detected + +// todo: fill in all values from PATCR onward + + + +#define AW20X_SOFT_RESET AW20X_RSTR_SW_RSTN + +enum aw20x_imax { + AW20X_IMAX_10MA = 0x00, + AW20X_IMAX_20MA = 0x10, + AW20X_IMAX_30MA = 0x20, + AW20X_IMAX_40MA = 0x30, + AW20X_IMAX_60MA = 0x40, + AW20X_IMAX_80MA = 0x50, + AW20X_IMAX_120MA = 0x60, + AW20X_IMAX_160MA = 0x70, + AW20X_IMAX_3_3MA = 0x80, + AW20X_IMAX_6_7MA = 0x90, + AW20X_IMAX_10MA_2 = 0xa0, + AW20X_IMAX_13_3MA = 0xb0, + AW20X_IMAX_20MA_2 = 0xc0, + AW20X_IMAX_26_7MA = 0xd0, + AW20X_IMAX_40MA_2 = 0xe0, + AW20X_IMAX_53_3MA = 0xf0 +}; + +enum aw20x_size { + AW20X_SIZE_1COL = 0, + AW20X_SIZE_2COL, + AW20X_SIZE_3COL, + AW20X_SIZE_4COL, + AW20X_SIZE_5COL, + AW20X_SIZE_6COL, + AW20X_SIZE_7COL, + AW20X_SIZE_8COL, + AW20X_SIZE_9COL +}; + + + +#ifdef SOFT_I2C_MASTER +#define AW20X_I2C_busy() (0) +#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_addr(adr, 1); i2c_wr(reg); i2c_wrbuf(buf, siz); +#else +#define AW20X_I2C_busy() (0) +#define AW20X_I2C_writereg(adr, reg, buf, siz) i2c_write_addr1b(adr, reg, buf, siz); +#endif + + + +typedef struct AW20x { + uint8_t addr; + uint8_t config; // settings for the chip + uint8_t cols; // highest column used, 1-6 + uint8_t rows; // highest row used, 1-12 + uint8_t state; // keeps track of active page, and high bit is set if asleep + uint8_t pad[3]; + uint8_t *fade; // led buffer location for FADE (required), of size cols+rows + uint8_t *gain; // led buffer location for GAIN (optional), of size cols+rows +} AW20x; + + + +void aw20x_init(struct AW20x *aw, uint8_t addr, uint8_t cols, uint8_t rows, uint8_t imax); + +void aw20x_sleep(struct AW20x *aw, uint8_t sleep); + +void aw20x_commit_fade(struct AW20x *aw); +void aw20x_commit_dim_global(struct AW20x *aw, uint8_t dim); + +void aw20x_led_enable(struct AW20x *aw, uint8_t first, uint8_t last); + + + +#endif /* AW02X_LED_MATRIX_H */ diff --git a/nametag8_CH592/user/device/ch32sub.c b/nametag8_CH592/user/device/ch32sub.c index 927735b..9ec08f7 100644 --- a/nametag8_CH592/user/device/ch32sub.c +++ b/nametag8_CH592/user/device/ch32sub.c @@ -14,10 +14,14 @@ #include "ch32sub.h" #include "port_intr.h" +#include "../comm/i2c.h" + + volatile uint8_t intr_flag = 0; + void ch32sub_isr() { // we'll check what the MCU has to say when we're done processing @@ -45,3 +49,9 @@ void ch32sub_init() // configure interrupt to be rising edge GPIOB_ITModeCfg(SUB_INTR_PIN, GPIO_ITMode_RiseEdge); } + +// things to do +void ch32sub_rgb_hwen(uint8_t en) +{ + i2c_addr(SUB_I2C_ADDR, 1); +} diff --git a/nametag8_CH592/user/device/ch32sub.h b/nametag8_CH592/user/device/ch32sub.h index 44f4896..a47d634 100644 --- a/nametag8_CH592/user/device/ch32sub.h +++ b/nametag8_CH592/user/device/ch32sub.h @@ -14,6 +14,8 @@ +#define SUB_I2C_ADDR 0x5e + #define SUB_INTR_PORT GPIOB #define SUB_INTR_PIN GPIO_Pin_13 #define SUB_INTR_PIN_NR 13 @@ -28,6 +30,8 @@ void ch32sub_init(); void ch32sub_process(); +void ch32sub_rgb_hwen(uint8_t en); + #endif /* USER_DEVICE_CH32SUB_H_ */ diff --git a/nametag8_CH592/user/gat/gat_gpio.c b/nametag8_CH592/user/gat/gat_gpio.c index 4d697af..5c2c767 100644 --- a/nametag8_CH592/user/gat/gat_gpio.c +++ b/nametag8_CH592/user/gat/gat_gpio.c @@ -1,8 +1,13 @@ /* * gat_gpio.c * - * Created on: Oct 11, 2024 - * Author: true + * just gets the GAT ID and does nothing else. + * but if you wanted to do things with the GPIO, this is where you could do it. + * + * hardware peripherals: + * - GP1 has UART0 TX and PWM9. + * - GP2 has UART0 RX and PWM7. + * */ diff --git a/nametag8_CH592/user/led/rgbled.c b/nametag8_CH592/user/led/rgbled.c new file mode 100644 index 0000000..1891edd --- /dev/null +++ b/nametag8_CH592/user/led/rgbled.c @@ -0,0 +1,62 @@ +/* + * rgbled.c + * + * Created on: Oct 13, 2024 + * Author: true + */ + +#include + +#include "rgbled.h" + +#include "../device/aw20xxx.h" + + + +#define AW20X_DIM 31 // initial global current setting + +#define AW20X_COLS 4 +#define AW20X_ROWS 9 +#define AW20X_FADE_COUNT (AW20X_ROWS * AW20X_COLS) + + + +static const uint16_t pwm_cie_256in_1024out[] = { + 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 7, 8, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 15, + 15, 16, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, + 45, 47, 48, 50, 51, 52, 54, 55, 57, 58, 60, 61, 63, 65, 66, 68, + 70, 71, 73, 75, 77, 79, 81, 83, 84, 86, 88, 90, 93, 95, 97, 99, + 101, 103, 106, 108, 110, 113, 115, 118, 120, 123, 125, 128, 130, 133, 136, 138, + 141, 144, 147, 149, 152, 155, 158, 161, 164, 167, 171, 174, 177, 180, 183, 187, + 190, 194, 197, 200, 204, 208, 211, 215, 218, 222, 226, 230, 234, 237, 241, 245, + 249, 254, 258, 262, 266, 270, 275, 279, 283, 288, 292, 297, 301, 306, 311, 315, + 320, 325, 330, 335, 340, 345, 350, 355, 360, 365, 370, 376, 381, 386, 392, 397, + 403, 408, 414, 420, 425, 431, 437, 443, 449, 455, 461, 467, 473, 480, 486, 492, + 499, 505, 512, 518, 525, 532, 538, 545, 552, 559, 566, 573, 580, 587, 594, 601, + 609, 616, 624, 631, 639, 646, 654, 662, 669, 677, 685, 693, 701, 709, 717, 726, + 734, 742, 751, 759, 768, 776, 785, 794, 802, 811, 820, 829, 838, 847, 857, 866, + 875, 885, 894, 903, 913, 923, 932, 942, 952, 962, 972, 982, 992, 1002, 1013, 1023, +}; + + +AW20x awled; +static uint8_t awled_fade[AW20X_FADE_COUNT]; + +static uint8_t led_matrix_updated = 0; + + + +void rgbled_init() +{ + volatile uint32_t x; + + ch32sub_rgb_hwen(1); + + // wait a little while to ensure controller is awake + x = GetSysClock() / 16384; + while (x--); + + aw20x_init(&awled, AW20X_ADDR_GND << 1, AW20X_COLS, AW20X_ROWS, AW20X_IMAX_13_3MA); +} diff --git a/nametag8_CH592/user/led/rgbled.h b/nametag8_CH592/user/led/rgbled.h new file mode 100644 index 0000000..66efbd5 --- /dev/null +++ b/nametag8_CH592/user/led/rgbled.h @@ -0,0 +1,25 @@ +/* + * rgbled.h + * + * Created on: Oct 13, 2024 + * Author: true + */ + +#ifndef USER_LED_RGBLED_H_ +#define USER_LED_RGBLED_H_ + + + +#include + +#include "../comm/i2c.h" + +#include "../device/ch32sub.h" + + + +void rgbled_init(); + + + +#endif /* USER_LED_RGBLED_H_ */ diff --git a/nametag8_CH592/user/main.c b/nametag8_CH592/user/main.c index 73653d8..2b0461c 100644 --- a/nametag8_CH592/user/main.c +++ b/nametag8_CH592/user/main.c @@ -1,19 +1,31 @@ -/********************************** (C) COPYRIGHT ******************************* - * File Name : Main.c - * Author : WCH - * Version : V1.0 - * Date : 2020/08/06 - * Description : 串口1收发演示 - ********************************************************************************* - * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. - * Attention: This software (modified or not) and binary are used for - * microcontroller manufactured by Nanjing Qinheng Microelectronics. - *******************************************************************************/ +/* + * true's GAT Nametag + * Rushed SUPERCON 8 EDITION + * main MCU firmware + * 2024 true + * + * the main MCU is responsible for the following: + * - rendering OLED UI + * - rendering LED programs + * - storing fonts + * - communicating with I2C devices, including: + * - oled display, accelerometer + * - eeprom, sub MCU, RGBLED controller + * - implementing USB interface + * - implementing BLE (maybe) + * + */ #include "CH59x_common.h" #include "port_intr.h" + +#include "comm/i2c.h" + #include "gat/gat_gpio.h" + +#include "led/rgbled.h" + #include "device/ch32sub.h" @@ -24,120 +36,34 @@ void ch59x_xtal_conf() HSECFG_Capacitance(HSECap_14p); } -uint8_t TxBuff[] = "This is a tx exam\r\n"; -uint8_t RxBuff[100]; -uint8_t trigB; - -/********************************************************************* - * @fn main - * - * @brief 主函数 - * - * @return none - */ int main() { - //uint8_t len; - // configure clock ch59x_xtal_conf(); - SetSysClock(CLK_SOURCE_PLL_32MHz); + SetSysClock(CLK_SOURCE_HSE_16MHz); - // enable DC-DC converter + // enable DC-DC converter; brings significant power saving PWR_DCDCCfg(ENABLE); - // configure port-based interrupts + // get i2c up and running + i2c_init(); + + // configure port-based interrupts (used for chsub interrupt, mainly) port_intr_init(); // configure aux MCU initial settings, attention interrupt ch32sub_init(); + // and enable RGBLED controller hardware pin so the controller can wake up + + // configure RGBLED controller + rgbled_init(); // configure GAT aux GPIOs, get gat ID gat_gpio_init(); // configure GAT i2c slave - -/* - // 配置串口1:先配置IO口模式,再配置串口 - GPIOA_SetBits(GPIO_Pin_9); - GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU); // RXD-配置上拉输入 - GPIOA_ModeCfg(GPIO_Pin_9, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平 - UART1_DefInit(); - -#if 1 // 测试串口发送字符串 - UART1_SendString(TxBuff, sizeof(TxBuff)); - -#endif - -#if 1 // 查询方式:接收数据后发送出去 - while(1) - { - len = UART1_RecvString(RxBuff); - if(len) - { - UART1_SendString(RxBuff, len); - } - } - -#endif - -#if 0 // 中断方式:接收数据后发送出去 - UART1_ByteTrigCfg(UART_7BYTE_TRIG); - trigB = 7; - UART1_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT); - PFIC_EnableIRQ(UART1_IRQn); -#endif -*/ - while(1) { // only care about aux MCU when all other processing is done ch32sub_process(); } } - -/********************************************************************* - * @fn UART1_IRQHandler - * - * @brief UART1中断函数 - * - * @return none - */ -__INTERRUPT -__HIGH_CODE -void UART1_IRQHandler(void) -{ - /* - volatile uint8_t i; - - switch(UART1_GetITFlag()) - { - case UART_II_LINE_STAT: // 线路状态错误 - { - UART1_GetLinSTA(); - break; - } - - case UART_II_RECV_RDY: // 数据达到设置触发点 - for(i = 0; i != trigB; i++) - { - RxBuff[i] = UART1_RecvByte(); - UART1_SendByte(RxBuff[i]); - } - break; - - case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成 - i = UART1_RecvString(RxBuff); - UART1_SendString(RxBuff, i); - break; - - case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送 - break; - - case UART_II_MODEM_CHG: // 只支持串口0 - break; - - default: - break; - } - */ -} diff --git a/nametag8_CH592/user/port_intr.c b/nametag8_CH592/user/port_intr.c index c0227b1..af37a46 100644 --- a/nametag8_CH592/user/port_intr.c +++ b/nametag8_CH592/user/port_intr.c @@ -1,8 +1,14 @@ /* * port_intr.c * - * Created on: Oct 11, 2024 - * Author: true + * notes: + * + * - pins 22 and 23 are shifted into pins 9 and 10, + * so configure these pins if you want to use them. + * + * - maximum real possible then is 22 entries. + * I have no need above 15 so that's the maximum I set. + * */ #include "CH59x_common.h" @@ -12,16 +18,25 @@ -static void (*cb[2][24])(void) = {0}; +#define MAX_PIN 15+1 + + + +static void (*cb[2][MAX_PIN-4])(void) = {0}; void port_intr_cb_register(uint8_t port, uint8_t idx, void (*fn)(void)) { - if (idx >= 24) return; + if (idx == 22) idx = 9; + if (idx == 23) idx = 10; + + if (idx < 4) return; + if (idx > MAX_PIN) return; + if (port > 2) return; - cb[port][idx] = fn; + cb[port][idx - 4] = fn; } void port_intr_init() @@ -46,6 +61,9 @@ void GPIOB_IRQHandler(void) uint8_t i; uint16_t flag = R16_PB_INT_IF; + // clear flags + R16_PB_INT_IF = flag; + // high priority actions // none. @@ -58,6 +76,5 @@ void GPIOB_IRQHandler(void) } } - // clear flags - R16_PB_INT_IF = flag; + }