Compare commits

...

3 Commits

Author SHA1 Message Date
true 16ffde1077 soft i2c work
added a check to soft i2c master for clock stretch on write.

mostly implemented soft i2c slave.
2024-10-19 19:14:33 -07:00
true 904fcf65b4 i2c master cleanup; work in progress for i2c slave support 2024-10-17 21:09:34 -07:00
true 9159a37dd2 V003: slave registers for buttons updated 2024-10-17 21:06:29 -07:00
10 changed files with 577 additions and 74 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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