initial soft i2c routines written; AW20xxx code copied in

completely untested. i2c delay cycles are at best a guess.
This commit is contained in:
true 2024-10-13 02:53:18 -07:00
parent b6582a599a
commit b1bca1012f
13 changed files with 792 additions and 233 deletions

BIN
datasheets/CH592DS1_EN.PDF Normal file

Binary file not shown.

View File

@ -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_ */

View File

@ -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();
}

View File

@ -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 <CH59x_common.h>
#include <stdint.h>
#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_ */

View File

@ -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)
{
}

View File

@ -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 <stdint.h>
#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 */

View File

@ -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);
}

View File

@ -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_ */

View File

@ -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.
*
*/

View File

@ -0,0 +1,62 @@
/*
* rgbled.c
*
* Created on: Oct 13, 2024
* Author: true
*/
#include <CH59x_common.h>
#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);
}

View File

@ -0,0 +1,25 @@
/*
* rgbled.h
*
* Created on: Oct 13, 2024
* Author: true
*/
#ifndef USER_LED_RGBLED_H_
#define USER_LED_RGBLED_H_
#include <stdint.h>
#include "../comm/i2c.h"
#include "../device/ch32sub.h"
void rgbled_init();
#endif /* USER_LED_RGBLED_H_ */

View File

@ -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;
}
*/
}

View File

@ -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;
}