Compare commits
3 Commits
db44d3dbab
...
16ffde1077
Author | SHA1 | Date |
---|---|---|
true | 16ffde1077 | |
true | 904fcf65b4 | |
true | 9159a37dd2 |
|
@ -10,12 +10,13 @@
|
|||
*
|
||||
* quirks and features:
|
||||
* - this implementation is blocking.
|
||||
* - clock stretching is supported, I think.
|
||||
* - partial clock stretching is supported
|
||||
* (only during start of write, and within 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 +27,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 +110,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
|
||||
|
@ -121,11 +153,17 @@ uint8_t i2cm_wr(uint8_t dat)
|
|||
uint8_t x;
|
||||
uint8_t ack;
|
||||
|
||||
SCL_IN_HI();
|
||||
while (!SCL_GET()); // clock stretch
|
||||
|
||||
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,324 @@
|
|||
/*
|
||||
* soft_i2c_slave.c
|
||||
*
|
||||
* hastily written. likely has bugs.
|
||||
*
|
||||
* known quirks:
|
||||
* - 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 6 // timeout in bit-times before i2c slave releases control
|
||||
#define I2CSS_IDLE_TIMEOUT 3 // idle timeout required in bit-times before i2c slave can be addressed again
|
||||
|
||||
|
||||
// todo: use with struct instead of hard-coding to allow for multiple slaves, but make it fast too
|
||||
// note: will have issues with non-responsive slaves with more than one, so is it necessary?
|
||||
#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 bit_time;
|
||||
static uint16_t idle_timeout = 0;
|
||||
|
||||
|
||||
|
||||
static int8_t i2css_wait_for_scl_hi()
|
||||
{
|
||||
int16_t timeout = I2CSS_ERR_TIMEOUT * bit_time;
|
||||
|
||||
while (!SCL_GET()) { if (!timeout--) return I2CSS_SCL_TIMEOUT; }
|
||||
return I2CSS_OK;
|
||||
}
|
||||
|
||||
static int8_t i2css_wait_for_scl_lo()
|
||||
{
|
||||
uint8_t old_data = SDA_GET();
|
||||
int16_t timeout = I2CSS_ERR_TIMEOUT * bit_time;
|
||||
|
||||
timeout--; // compensation
|
||||
|
||||
while (SCL_GET()) {
|
||||
if (timeout <= 1) return I2CSS_SCL_TIMEOUT;
|
||||
if (SDA_GET() != old_data) return I2CSS_START_STOP;
|
||||
timeout -= 2;
|
||||
}
|
||||
|
||||
return I2CSS_OK;
|
||||
}
|
||||
|
||||
static inline int8_t i2css_ack(uint16_t bit_time)
|
||||
{
|
||||
uint8_t ret;
|
||||
|
||||
SDA_OUTLO();
|
||||
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_ACK_ERROR;
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
|
||||
SDA_IN_HI();
|
||||
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_ACK_TIMEOUT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
int8_t i;
|
||||
int8_t ret;
|
||||
|
||||
uint8_t ack = 0;
|
||||
uint8_t dat = 0;
|
||||
|
||||
|
||||
// with no read function configured, we can't do anything
|
||||
if (!slave->rd_cb) {
|
||||
SCL_IN_HI();
|
||||
return I2CSS_NO_READ_FUNCTION;
|
||||
}
|
||||
|
||||
// clock low for first bit / stretch, get data to send to master
|
||||
SCL_OUTLO();
|
||||
dat = slave->rd_cb();
|
||||
|
||||
// release clock, wait for clock to be low by the master
|
||||
SCL_IN_HI();
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
|
||||
// send data to master
|
||||
for (i = 8; i; i--) {
|
||||
if (dat &= 0x80) { SDA_IN_HI(); }
|
||||
else { SDA_OUTLO(); }
|
||||
|
||||
dat <<= 1;
|
||||
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_READ_ERROR;
|
||||
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// get (n)ack from master
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_READ_ERROR;
|
||||
|
||||
ack = SDA_GET();
|
||||
|
||||
return ack ? I2CSS_NACK : I2CSS_OK;
|
||||
}
|
||||
|
||||
int8_t i2css_write(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int8_t i;
|
||||
int8_t ret;
|
||||
|
||||
uint8_t dat = 0;
|
||||
uint8_t ack;
|
||||
|
||||
|
||||
// release clock for master
|
||||
SCL_IN_HI();
|
||||
|
||||
// with no read function configured, we can't do anything
|
||||
if (!slave->wr_cb) {
|
||||
return I2CSS_NO_WRITE_FUNCTION;
|
||||
}
|
||||
|
||||
// get data from master
|
||||
for (i = 8; i; i--) {
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_WRITE_ERROR;
|
||||
|
||||
dat |= SDA_GET() ? 1 : 0;
|
||||
dat <<=1;
|
||||
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_WRITE_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// stretch clock, write our data with callback
|
||||
SCL_OUTLO();
|
||||
ack = slave->wr_cb(dat);
|
||||
|
||||
// send (n)ack to master
|
||||
if (ack) { SDA_IN_HI(); } // nack
|
||||
else { SDA_OUTLO(); } // ack
|
||||
|
||||
// release clock and wait
|
||||
SCL_IN_HI();
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_WRITE_ERROR;
|
||||
|
||||
// wait for clock to go low
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_WRITE_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
|
||||
// then stretch
|
||||
SCL_OUTLO();
|
||||
|
||||
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.
|
||||
static inline int8_t i2css_process(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int32_t i;
|
||||
int8_t ret;
|
||||
uint8_t rx_addr = 0;
|
||||
|
||||
|
||||
// wait for SCL low to indicate data has begun
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_START_SCL_NOT_LOW;
|
||||
if (ret) return ret;
|
||||
|
||||
|
||||
// read address from bus
|
||||
for (i = 7; i; i--) {
|
||||
if (i2css_wait_for_scl_hi()) return I2CSS_ADDR_READ_ERROR;
|
||||
|
||||
rx_addr |= SDA_GET() ? 1 : 0;
|
||||
rx_addr <<=1;
|
||||
|
||||
ret = i2css_wait_for_scl_lo();
|
||||
if (ret == I2CSS_SCL_TIMEOUT) return I2CSS_ADDR_READ_TIMEOUT;
|
||||
if (ret) return ret;
|
||||
}
|
||||
|
||||
// is this us?
|
||||
slave->addr_last_seen = rx_addr;
|
||||
if (slave->addr != rx_addr) return I2CSS_ADDR_NO_MATCH;
|
||||
|
||||
// seems so. get the read/write bit
|
||||
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
|
||||
ret = i2css_ack(slave->bit_time);
|
||||
if (ret) return ret;
|
||||
|
||||
// stretch clock while preparing slave to receive
|
||||
SCL_OUTLO();
|
||||
if (slave->start_cb) slave->start_cb();
|
||||
|
||||
switch (rx_addr & 1) {
|
||||
case 0: { // master is writing to us
|
||||
do {
|
||||
ret = i2css_write(slave);
|
||||
} while (!ret);
|
||||
break;
|
||||
}
|
||||
case 1: { // master is reading from us
|
||||
do {
|
||||
// stretch clock
|
||||
SCL_OUTLO();
|
||||
ret = i2css_read(slave);
|
||||
} while (!ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int8_t i2css_blocking(struct I2CSoftSlave *slave)
|
||||
{
|
||||
int8_t ret;
|
||||
int8_t start;
|
||||
|
||||
do {
|
||||
ret = i2css_process(slave);
|
||||
start = 0;
|
||||
if (ret == I2CSS_START_STOP) {
|
||||
// determine which
|
||||
// high = stop, low = start
|
||||
start = SDA_GET() ? 0 : 1;
|
||||
}
|
||||
} while (start);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// call this function with SDA falling low interrupt.
|
||||
int8_t i2css_start(struct I2CSoftSlave *slave)
|
||||
{
|
||||
uint8_t scl;
|
||||
|
||||
// idle our clock and check for start/stop
|
||||
SCL_IN_HI();
|
||||
scl = SCL_GET();
|
||||
|
||||
// module will use this bit time for this communication
|
||||
bit_time = slave->bit_time;
|
||||
|
||||
// ensure SDA pin will go low when set as an output
|
||||
SDA_SET_LO();
|
||||
|
||||
// if SCL is high, this should be a start condition.
|
||||
if (scl) {
|
||||
return i2css_blocking(slave);
|
||||
}
|
||||
|
||||
// if it isn't, there must be some other communication going on, and it doesn't involve us
|
||||
return I2CSS_BUS_BUSY;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 addr_last_seen;
|
||||
uint16_t bit_time;
|
||||
void (*start_cb)(); // call on any new read/write operation
|
||||
uint8_t (*rd_cb)(); // returns data
|
||||
int8_t (*wr_cb)(uint8_t data); // return 0 to ACK, all other NACK
|
||||
} I2CSoftSlave;
|
||||
|
||||
|
||||
enum I2CSoftSlave_Error {
|
||||
// standard responses
|
||||
I2CSS_OK,
|
||||
I2CSS_STOP,
|
||||
I2CSS_NACK,
|
||||
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,
|
||||
I2CSS_READ_ERROR = -7,
|
||||
I2CSS_READ_TIMEOUT = -8,
|
||||
I2CSS_WRITE_ERROR = -9,
|
||||
I2CSS_WRITE_TIMEOUT = -10,
|
||||
I2CSS_NO_READ_FUNCTION = -32,
|
||||
I2CSS_NO_WRITE_FUNCTION = -33,
|
||||
I2CSS_SCL_TIMEOUT = -126,
|
||||
I2CSS_BUS_BUSY = -127,
|
||||
I2CSS_START_STOP = -128
|
||||
};
|
||||
|
||||
|
||||
|
||||
// call this function with SDA falling low interrupt.
|
||||
void i2css_init();
|
||||
int8_t i2css_start(struct I2CSoftSlave *slave);
|
||||
|
||||
|
||||
|
||||
#endif /* USER_COMM_SOFT_I2C_SLAVE_H_ */
|
|
@ -107,5 +107,5 @@ void ch32sub_write_1b(uint16_t reg, uint8_t dat)
|
|||
|
||||
void ch32sub_rgb_hwen(uint8_t en)
|
||||
{
|
||||
i2c_addr(SUB_I2C_ADDR, 1);
|
||||
ch32sub_write_1b(REG_RGB_HWEN, en);
|
||||
}
|
||||
|
|
|
@ -6,9 +6,117 @@
|
|||
*
|
||||
* 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;
|
||||
|
||||
|
||||
|
||||
enum {
|
||||
GAT_MODE_STARTING,
|
||||
GAT_MODE_READ,
|
||||
GAT_MODE_WRITE
|
||||
};
|
||||
|
||||
|
||||
uint8_t gat_state;
|
||||
uint16_t gat_data_idx;
|
||||
uint8_t gat_data[GAT_DATA_SIZE];
|
||||
|
||||
|
||||
|
||||
uint8_t gat_i2c_read_cb()
|
||||
{
|
||||
uint8_t ret;
|
||||
|
||||
switch (gat_state) {
|
||||
case GAT_MODE_STARTING: {
|
||||
gat_state = GAT_MODE_READ;
|
||||
// no break
|
||||
}
|
||||
case GAT_MODE_READ: {
|
||||
// can't read past end of buffer; just send 0xff
|
||||
if (gat_data_idx == GAT_DATA_SIZE) {
|
||||
ret = 0xff;
|
||||
} else {
|
||||
ret = gat_data[gat_data_idx & 0xff];
|
||||
if (gat_data_idx <= GAT_DATA_SIZE) gat_data_idx++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
default: {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int8_t gat_i2c_write_cb(uint8_t dat)
|
||||
{
|
||||
switch (gat_state) {
|
||||
|
||||
case GAT_MODE_STARTING: {
|
||||
// master wrote our address
|
||||
gat_data_idx = dat;
|
||||
gat_state = GAT_MODE_WRITE;
|
||||
break;
|
||||
}
|
||||
case GAT_MODE_WRITE: {
|
||||
if (gat_data_idx >= GAT_DATA_SIZE) {
|
||||
return I2CSS_NACK;
|
||||
} else {
|
||||
gat_data[gat_data_idx++] = dat;
|
||||
}
|
||||
return I2CSS_OK;
|
||||
}
|
||||
default: {
|
||||
return I2CSS_NACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gat_i2c_start_cb()
|
||||
{
|
||||
gat_state = GAT_MODE_STARTING;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
// set up callbacks
|
||||
gat_slave.start_cb = gat_i2c_start_cb;
|
||||
gat_slave.rd_cb = gat_i2c_read_cb;
|
||||
gat_slave.wr_cb = gat_i2c_write_cb;
|
||||
|
||||
// enable interrupt on SDA pin to detect start condition
|
||||
// this assumes GPIO has already been configured
|
||||
}
|
||||
|
||||
void gat_i2c_irq_cb()
|
||||
{
|
||||
i2css_start(&gat_slave);
|
||||
}
|
||||
|
|
|
@ -35,16 +35,28 @@ static void update_i2c_reg(uint8_t idx)
|
|||
|
||||
void btn_push_cb(uint8_t idx)
|
||||
{
|
||||
// indicate which button is being pushed in dedicated register
|
||||
i2cs_reg[REG_BTN_PUSHED] |= (1 << idx);
|
||||
i2cs_reg[REG_BTN_PUSHED_LATCHED] |= (1 << idx);
|
||||
|
||||
update_i2c_reg(idx);
|
||||
}
|
||||
|
||||
void btn_hold_cb(uint8_t idx)
|
||||
{
|
||||
// indicate which button is being held in dedicated register
|
||||
i2cs_reg[REG_BTN_HELD] |= (1 << idx);
|
||||
|
||||
// before updating general registers
|
||||
update_i2c_reg(idx);
|
||||
}
|
||||
|
||||
void btn_release_cb(uint8_t idx)
|
||||
{
|
||||
// indicate which button is being released in dedicated register
|
||||
i2cs_reg[REG_BTN_RELEASED] |= (1 << idx);
|
||||
i2cs_reg[REG_BTN_PUSHED_LATCHED] &= ~(1 << idx);
|
||||
|
||||
update_i2c_reg(idx);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
#define INT_BTN (1 << 0) // user pressed / held / released a button
|
||||
// set if per-button interrupt enabled for the action when action is taken
|
||||
// cleared on writing 1 to bit offset in REG_INT_FLAGS_CLEAR or reading any REG_BTNx_MASK register
|
||||
// cleared on writing 1 to bit offset in REG_INT_FLAGS_CLEAR
|
||||
|
||||
#define INT_ACCEL (1 << 1) // accelerometer has interrupt
|
||||
// set when accelerometer INT pin goes high
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
* can be made to work in a time crunch.
|
||||
*
|
||||
* some notes:
|
||||
* - writing does not loop; if writing to the end then new writes
|
||||
* will cause an error.
|
||||
* - writing does not loop; if writing to the end then new writes will cause an error interrupt.
|
||||
* - added write protection for first 16 bytes.
|
||||
* - many registers will only commit to action upon valid I2C stop received.
|
||||
*
|
||||
|
@ -32,7 +31,7 @@ volatile uint8_t i2cs_reg[256*3] = {0};
|
|||
i2c_slave_state i2cs_state;
|
||||
|
||||
static const uint8_t write_cb_trig[] = {
|
||||
REG_INT_FLAGS_CLEAR,
|
||||
REG_INTR_FLAGS_CLEAR,
|
||||
REG_RGB_HWEN,
|
||||
REG_IRDA_MODE,
|
||||
REG_BTN1_HOLD_LO,
|
||||
|
@ -49,7 +48,7 @@ static const uint8_t write_cb_trig[] = {
|
|||
|
||||
static void i2cs_int_check()
|
||||
{
|
||||
if (i2cs_reg[REG_INT_FLAGS] & i2cs_reg[REG_INT_ENABLE]) {
|
||||
if (i2cs_reg[REG_INTR_FLAGS] & i2cs_reg[REG_INTR_ENABLE]) {
|
||||
INT_OUT_SET();
|
||||
} else {
|
||||
INT_OUT_CLEAR();
|
||||
|
@ -69,13 +68,6 @@ void i2cs_write_cb(uint16_t initial_reg, uint16_t last_reg)
|
|||
// nothing written?
|
||||
if (initial_reg > last_reg) return;
|
||||
|
||||
// button registers
|
||||
if ((initial_reg <= REG_BTN1_MASK) && (last_reg >= REG_BTN5_MASK)) {
|
||||
// if any button is read, clear the button interrupt automatically
|
||||
i2cs_reg[REG_INT_FLAGS] &= ~INT_BTN;
|
||||
i2cs_int_check();
|
||||
}
|
||||
|
||||
// other single registers
|
||||
for (i = 0; i < sizeof(write_cb_trig); i++) {
|
||||
if ((initial_reg <= write_cb_trig[i]) && (last_reg >= write_cb_trig[i])) {
|
||||
|
@ -83,8 +75,8 @@ void i2cs_write_cb(uint16_t initial_reg, uint16_t last_reg)
|
|||
}
|
||||
|
||||
switch (trig) {
|
||||
case REG_INT_FLAGS_CLEAR: {
|
||||
i2cs_reg[REG_INT_FLAGS] &= ~i2cs_reg[REG_INT_FLAGS_CLEAR];
|
||||
case REG_INTR_FLAGS_CLEAR: {
|
||||
i2cs_reg[REG_INTR_FLAGS] &= ~i2cs_reg[REG_INTR_FLAGS_CLEAR];
|
||||
i2cs_int_check();
|
||||
|
||||
break;
|
||||
|
@ -144,7 +136,14 @@ void i2cs_werr_cb(uint16_t reg, uint8_t data, uint8_t error)
|
|||
// called upon every register read
|
||||
void i2cs_read_cb(uint16_t reg)
|
||||
{
|
||||
|
||||
switch (reg) {
|
||||
case REG_BTN_PUSHED:
|
||||
case REG_BTN_HELD:
|
||||
case REG_BTN_RELEASED: {
|
||||
i2cs_reg[reg] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void i2cs_init(uint8_t address, uint8_t read_only_mode)
|
||||
|
@ -185,7 +184,7 @@ void i2cs_init(uint8_t address, uint8_t read_only_mode)
|
|||
void i2cs_append_flag(uint8_t flag)
|
||||
{
|
||||
if (flag) {
|
||||
i2cs_reg[REG_INT_FLAGS] |= flag;
|
||||
i2cs_reg[REG_INTR_FLAGS] |= flag;
|
||||
i2cs_int_check();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,37 +20,28 @@ enum i2cs_werr {
|
|||
I2CS_WRITE_BEYOND_END
|
||||
};
|
||||
|
||||
/* ....
|
||||
* 0x20 btn1
|
||||
* ....
|
||||
* 0x30..0x3f btn2 (see btn1 for register map)
|
||||
* 0x40..0x4f btn3 (see btn1 for register map)
|
||||
* 0x50..0x5f btn4 (see btn1 for register map)
|
||||
* 0x60..0x6f btn5 (see btn1 for register map)
|
||||
*/
|
||||
|
||||
enum i2cs_regmap {
|
||||
enum ch32sub_regmap {
|
||||
// read-only
|
||||
REG_INT_FLAGS = 0x00,
|
||||
REG_INTR_FLAGS = 0x00,
|
||||
REG_RSVD_01,
|
||||
REG_BTN_PUSHED_NEW, // unused
|
||||
REG_BTN_PUSHED, // unused
|
||||
REG_BTN_HELD, // unused
|
||||
REG_BTN_RELEASED, // unused
|
||||
REG_BTN1_MASK, // raw bitmask of button 1 state
|
||||
REG_BTN2_MASK, // button interrupt is cleared on read of ANY button register
|
||||
REG_BTN_PUSHED_LATCHED, // buttons currently pushed.
|
||||
REG_BTN_PUSHED, // buttons pushed. reading will clear this value.
|
||||
REG_BTN_HELD, // buttons currently being held. reading will clear this value.
|
||||
REG_BTN_RELEASED, // buttons released. reading will clear this value.
|
||||
REG_BTN1_MASK, // raw bitmask of button 1 state
|
||||
REG_BTN2_MASK, // button interrupt is cleared on read of ANY REG_BTNx_MASK register
|
||||
REG_BTN3_MASK,
|
||||
REG_BTN4_MASK,
|
||||
REG_BTN5_MASK,
|
||||
|
||||
REG_WERR_HI = 0x0c, // write error count, high byte
|
||||
REG_WERR_LO, // write error coutn, low byte
|
||||
REG_IRDA_READ_HI, // IrDA read packet length, hi byte (currently always 0)
|
||||
REG_IRDA_READ_LO, // IrDA read packet length, lo byte
|
||||
REG_WERR_HI = 0x0c, // write error count, high byte
|
||||
REG_WERR_LO, // write error count, low byte
|
||||
REG_IRDA_READ_HI, // IrDA read packet length, hi byte (currently always 0)
|
||||
REG_IRDA_READ_LO, // IrDA read packet length, lo byte
|
||||
|
||||
// read-write
|
||||
REG_INT_FLAGS_CLEAR = 0x10,
|
||||
REG_INT_ENABLE, // interrupt enable flags
|
||||
REG_INTR_FLAGS_CLEAR = 0x10,
|
||||
REG_INTR_ENABLE, // interrupt enable flags
|
||||
REG_BTN_DEBOUNCE_TIME,
|
||||
REG_BTN1_INT_ENABLE, // button 1 interrupt mask
|
||||
REG_BTN2_INT_ENABLE, // button 2 interrupt mask
|
||||
|
|
Loading…
Reference in New Issue