From 904fcf65b437c49f8a7400efb340aef7b518eb62 Mon Sep 17 00:00:00 2001 From: true Date: Thu, 17 Oct 2024 21:09:34 -0700 Subject: [PATCH] i2c master cleanup; work in progress for i2c slave support --- nametag8_CH592/user/comm/soft_i2c_master.c | 44 ++++- nametag8_CH592/user/comm/soft_i2c_master.h | 27 --- nametag8_CH592/user/comm/soft_i2c_slave.c | 182 +++++++++++++++++++++ nametag8_CH592/user/comm/soft_i2c_slave.h | 44 +++++ nametag8_CH592/user/hw/gat/gat_i2c.c | 43 ++++- 5 files changed, 306 insertions(+), 34 deletions(-) create mode 100644 nametag8_CH592/user/comm/soft_i2c_slave.c create mode 100644 nametag8_CH592/user/comm/soft_i2c_slave.h diff --git a/nametag8_CH592/user/comm/soft_i2c_master.c b/nametag8_CH592/user/comm/soft_i2c_master.c index 6881c35..9827520 100644 --- a/nametag8_CH592/user/comm/soft_i2c_master.c +++ b/nametag8_CH592/user/comm/soft_i2c_master.c @@ -10,12 +10,12 @@ * * quirks and features: * - this implementation is blocking. - * - clock stretching is supported, I think. + * - partial clock stretching is supported (only during read phase). * - timings likely haven't been tested so long as this message exists. */ -#include "soft_i2c_master.h" - +#include +#include #define CYCLES_TO_HI 16 @@ -26,6 +26,32 @@ #define CYCLES_EXTRA_WR_LO 4 +#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 ) + + static uint16_t delay_hi, delay_lo; @@ -83,6 +109,11 @@ void i2cm_stop() SDA_OUTLO(); bit_delay_lo(); SCL_IN_HI(); bit_delay_hi(); SDA_IN_HI(); bit_delay_hi(); + + // wait some extra time + bit_delay_hi(); + bit_delay_hi(); + bit_delay_hi(); } // returns: data byte @@ -122,10 +153,13 @@ uint8_t i2cm_wr(uint8_t dat) 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(); } + if (dat & 0x80) { SDA_IN_HI(); } + else { SDA_OUTLO(); } + + SCL_IN_HI(); wr_delay_hi(); dat <<= 1; + SCL_OUTLO(); } diff --git a/nametag8_CH592/user/comm/soft_i2c_master.h b/nametag8_CH592/user/comm/soft_i2c_master.h index f3fda8b..80da7b6 100644 --- a/nametag8_CH592/user/comm/soft_i2c_master.h +++ b/nametag8_CH592/user/comm/soft_i2c_master.h @@ -10,33 +10,6 @@ -#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(); diff --git a/nametag8_CH592/user/comm/soft_i2c_slave.c b/nametag8_CH592/user/comm/soft_i2c_slave.c new file mode 100644 index 0000000..2ace9d1 --- /dev/null +++ b/nametag8_CH592/user/comm/soft_i2c_slave.c @@ -0,0 +1,182 @@ +/* + * soft_i2c_slave.c + * + * hastily written. likely has bugs. + * + * known quirks: + * - STOP is never checked for + * - a fast repeated start may not work as this code + * does not check for START after the beginning + */ + +#include + +#include "soft_i2c_slave.h" + +#include + + +#define I2CSS_ERR_TIMEOUT 4 // timeout in bit-times before i2c slave releases control +#define I2CSS_IDLE_TIMEOUT 2 // idle timeout required in bit-times before i2c slave can be addressed again + + +#define SDA_PIN 13 +#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_PA_PD_DRV_1 +#define SCL_DIR_REG R8_PA_DIR_1 +#define SCL_CLR_REG R8_PA_CLR_1 +#define SCL_GET_REG R8_PA_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 ) + + + +static uint16_t err_timeout; +static uint16_t idle_timeout = 0; + + + +static inline inline void i2css_reload_timeout(uint16_t bit_time) +{ + err_timeout = I2CSS_ERR_TIMEOUT * bit_time; +} + +static inline int8_t i2css_wait_for_scl_hi() +{ + while (!SCL_GET()) { if (!err_timeout--) return -1; } + return 0; +} + +static inline int8_t i2css_wait_for_scl_lo() +{ + uint8_t old_data = SDA_GET(); + + err_timeout--; // compensation + + while (SCL_GET()) { + if (err_timeout <= 1) return -1; + if (SDA_GET() != old_data) return -2; + err_timeout -= 2; + } + + return 0; +} + +static inline int8_t i2css_ack(uint16_t bit_time) +{ + SDA_OUTLO(); + + i2css_reload_timeout(bit_time); + if (i2css_wait_for_scl_hi()) return I2CSS_ACK_ERROR; + i2css_reload_timeout(bit_time); + if (i2css_wait_for_scl_lo()) return I2CSS_ACK_TIMEOUT; + + SDA_IN_HI(); + + return 0; +} + +// blocking implementation of i2c slave, with 8-bit address register. +// basically, simulates a 24C02 EEPROM. +// externally kicked off by a low transition on SCL. +int8_t i2css_idle_init(struct I2CSoftSlave *slave) +{ + // set up a hardware timer to tick our idle timer + // todo + // idle_timeout = slave->bit_time * I2CSS_IDLE_TIMEOUT; +} + +// returns 0 if idle, or -1 if not idle. +// if not idle, restarts the idle timer. +int8_t i2css_idle_check(struct I2CSoftSlave *slave) +{ + // requires a hardware timer or something to manage bus idle time + + // if we're idle, then we're done + if (!idle_timeout) { + return 0; + } + + // not idle. restart the idle timer. + i2css_idle_init(slave); + return -1; +} + +int8_t i2css_read(struct I2CSoftSlave *slave) +{ + return 0; +} + +int8_t i2css_write(struct I2CSoftSlave *slave) +{ + return 0; +} + +int8_t i2css_blocking(struct I2CSoftSlave *slave) +{ + int8_t i; + uint8_t rx_addr = 0; + + SCL_IN_HI(); + + // wait for SCL low to indicate start condition + i2css_reload_timeout(slave->bit_time); + while (!SCL_GET()) { if (!err_timeout--) return I2CSS_START_SCL_NOT_LOW; } + + // read address from bus + for (i = 0; i < 7; i++) { + i2css_reload_timeout(slave->bit_time); + if (i2css_wait_for_scl_hi()) return I2CSS_ADDR_READ_ERROR; + + rx_addr |= SDA_GET() ? 1 : 0; + rx_addr <<=1; + + i2css_reload_timeout(slave->bit_time); + if (i2css_wait_for_scl_lo()) return I2CSS_ADDR_READ_TIMEOUT; + } + + // is this us? + if (slave->addr != rx_addr) return I2CSS_ADDR_NO_MATCH; + + // seems so. get the read/write bit + i2css_reload_timeout(slave->bit_time); + if (i2css_wait_for_scl_hi()) return I2CSS_ADDR_READ_LASTBIT_TIMEOUT; + + rx_addr |= SDA_GET() ? 1 : 0; + + // let the host know we're ready to serve + i = i2css_ack(slave->bit_time); + if (i) return i; + + switch (rx_addr & 1) { + case 0: { // writing + while (!i2css_write(slave)); + break; + } + case 1: { // reading + while (!i2css_read(slave)) { + i = i2css_ack(slave->bit_time); + if (i) return i; + } + break; + } + } + + return 0; +} diff --git a/nametag8_CH592/user/comm/soft_i2c_slave.h b/nametag8_CH592/user/comm/soft_i2c_slave.h new file mode 100644 index 0000000..cfb0e72 --- /dev/null +++ b/nametag8_CH592/user/comm/soft_i2c_slave.h @@ -0,0 +1,44 @@ +/* + * soft_i2c_slave.h + * + * Created on: Oct 16, 2024 + * Author: true + */ + +#ifndef USER_COMM_SOFT_I2C_SLAVE_H_ +#define USER_COMM_SOFT_I2C_SLAVE_H_ + + + +#include + + +typedef struct I2CSoftSlave { + uint8_t addr; + uint8_t addr2; + uint16_t bit_time; + uint8_t (*rd_cb)(uint8_t addr, uint8_t *data); // return 0 to ACK + void (*wr_cb)(uint8_t addr, uint8_t data); +} I2CSoftSlave; + + +enum I2CSoftSlave_Error { + // standard responses + I2CSS_OK, + I2CSS_ADDR_NO_MATCH, + // errors + I2CSS_START_SCL_NOT_LOW = -1, + I2CSS_ADDR_READ_ERROR = -2, // could be timeout, start or stop condition detected + I2CSS_ADDR_READ_TIMEOUT = -3, + I2CSS_ADDR_READ_LASTBIT_TIMEOUT = -4, + I2CSS_ACK_ERROR = -5, // could be timeout, start or stop condition detected + I2CSS_ACK_TIMEOUT = -6, +}; + + + +int8_t i2css_blocking(struct I2CSoftSlave *slave); + + + +#endif /* USER_COMM_SOFT_I2C_SLAVE_H_ */ diff --git a/nametag8_CH592/user/hw/gat/gat_i2c.c b/nametag8_CH592/user/hw/gat/gat_i2c.c index 1949251..9806e14 100644 --- a/nametag8_CH592/user/hw/gat/gat_i2c.c +++ b/nametag8_CH592/user/hw/gat/gat_i2c.c @@ -6,9 +6,48 @@ * * implements a shoddy i2c slave. * maximum speed 100KHz tested. + * note: not yet tested. */ -void soft_i2c_slave_irq_cb() -{ +#include +#include "gat_gpio.h" + +#include "comm/soft_i2c_slave.h" + +#include + + + +#define GAT_ADDR_BASE 0x30 +#define GAT_I2C_SPEED 100000 + +#define GAT_DATA_SIZE 256 + + + +struct I2CSoftSlave gat_slave; + +uint16_t gat_data_idx; +uint8_t gat_data[GAT_DATA_SIZE]; + + + + + +void gat_i2c_init() +{ + // determine I2C address from GP pin identification + gat_slave.addr = gat_id + GAT_ADDR_BASE; + + // roughly guess bit time length for expected speed + gat_slave.bit_time = GetSysClock() / GAT_I2C_SPEED / 4; + + // enable interrupt on SDA pin to detect start condition + // this assumes GPIO has already been configured +} + +void gat_i2c_irq_cb() +{ + i2css_blocking(&gat_slave); }