i2c master cleanup; work in progress for i2c slave support
This commit is contained in:
parent
9159a37dd2
commit
904fcf65b4
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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_ */
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue