i2c master cleanup; work in progress for i2c slave support

This commit is contained in:
true 2024-10-17 21:09:34 -07:00
parent 9159a37dd2
commit 904fcf65b4
5 changed files with 306 additions and 34 deletions

View File

@ -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 <CH59x_common.h>
#include <stdint.h>
#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();
}

View File

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

View File

@ -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 <CH59x_common.h>
#include "soft_i2c_slave.h"
#include <stdint.h>
#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;
}

View File

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

View File

@ -6,9 +6,48 @@
*
* implements a shoddy i2c slave.
* maximum speed 100KHz tested.
* note: not yet tested.
*/
void soft_i2c_slave_irq_cb()
{
#include <CH59x_common.h>
#include "gat_gpio.h"
#include "comm/soft_i2c_slave.h"
#include <stdint.h>
#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);
}