hsc26-artemis2/firmware/app/comms/soft_i2c_master.c
true 7827085947 tweaked i2c timings
haven't verified against a scope but they "work for me"

running at 24MHz at all times now for best energy use per watt before sleeping
2026-05-08 17:44:56 -07:00

227 lines
5.5 KiB
C

/*
* soft_i2c.c
*
* looked at random i2c code, used as inspiration, and wrote some
* garbage for CH59x. then ported to CH32X035.
*
* i2c hardware peripheral on CH59x is shared with debug pins or.
* USB pins. using both? touch shit, no I2C master periph for you.
*
* quirks, features and limitations:
* - this implementation is blocking.
* - clock stretching is supported, and somewhat tested
* - any slave stuck clock stretching will lock everything until released;
* there is no timeout or cancel operation implemented
*
* known good settings at 32MHz on CH59x:
* CYCLES_TO_HI = 5, CYCLES_TO_LOW = 2: ~400-500kHz
* CYCLES_TO_HI = 2, CYCLES_TO_LOW = 0: ~650KHz
* CYCLES_TO_HI = 1, bit_delay_lo = __nop: ~900KHz, but sub MCU fails to respond
*
* speed settings chosen are what the lowest speed device (the sub MCU) will
* handle without faults.
*
*/
#include <ch32x035_conf.h>
#include <stdint.h>
#define CYCLES_TO_HI 1 // was 2
#define CYCLES_TO_LO 0 // was 0
//#define CYCLES_RD 2 // cycles spent in read routine
//#define CYCLES_WR_HI 2 // extra cycles spent in write routine
//#define CYCLES_WR_LO 4
#define bit_delay_hi() { spin = delay_hi; while(spin--); }
#define bit_delay_lo() { spin = delay_lo; while(spin--); }
#define rd_delay() bit_delay_hi()
#define wr_delay_hi() bit_delay_hi()
#define wr_delay_lo() bit_delay_hi() // spin = delay_hi - CYCLES_EXTRA_WR_HI; while(spin--)
#define SDA_PIN 9
#define SDA_BIT (1 << SDA_PIN)
#define SDA_CONFOFFS ((SDA_PIN % 8) * 4)
#define SDA_GPIO GPIOB
#define SDA_CFGR SDA_GPIO->CFGHR
#define SCL_PIN 8
#define SCL_BIT (1 << SCL_PIN)
#define SCL_CONFOFFS ((SCL_PIN % 8) * 4)
#define SCL_GPIO GPIOB
#define SCL_CFGR SCL_GPIO->CFGHR
#define GPIO_CFG_LO 0x1
#define GPIO_CFG_HI 0x4
#define SDA_IN_HI() { uint32_t sda = SDA_CFGR & ~(0xf << SDA_CONFOFFS); \
sda |= (GPIO_CFG_HI << SDA_CONFOFFS); SDA_CFGR = sda; }
#define SDA_OUTLO() { uint32_t sda = SDA_CFGR & ~(0xf << SDA_CONFOFFS); \
sda |= (GPIO_CFG_LO << SDA_CONFOFFS); SDA_CFGR = sda; }
#define SDA_SET_LO() SDA_GPIO->BCR = SDA_BIT
#define SDA_GET() ( SDA_GPIO->INDR & SDA_BIT )
#define SCL_IN_HI() { uint32_t scl = SCL_CFGR & ~(0xf << SCL_CONFOFFS); \
scl |= (GPIO_CFG_HI << SCL_CONFOFFS); SCL_CFGR = scl; }
#define SCL_OUTLO() { uint32_t scl = SCL_CFGR & ~(0xf << SCL_CONFOFFS); \
scl |= (GPIO_CFG_LO << SCL_CONFOFFS); SCL_CFGR = scl; }
#define SCL_SET_LO() SCL_GPIO->BCR = SCL_BIT
#define SCL_GET() ( SCL_GPIO->INDR & SCL_BIT )
static uint16_t delay_hi, delay_lo;
static volatile uint16_t spin;
// re-run init any time the clock speed changes to recalculate delays.
void i2cm_init()
{
//uint32_t sysclk;
//uint32_t cycles;
// configure GPIO
SCL_IN_HI();
SCL_SET_LO();
SDA_IN_HI();
SDA_SET_LO();
// configure timer
//sysclk = GetSysClock();
//cycles = sysclk / 500000;
delay_hi = CYCLES_TO_HI;
delay_lo = CYCLES_TO_LO;
}
__attribute__((section(".ramfunc")))
void i2cm_start()
{
SDA_IN_HI(); bit_delay_hi();
SCL_IN_HI(); bit_delay_hi();
while (!SCL_GET()) {}; // clock stretch
SDA_OUTLO(); bit_delay_lo();
SCL_OUTLO(); bit_delay_lo();
}
__attribute__((section(".ramfunc")))
void i2cm_restart()
{
SDA_IN_HI(); bit_delay_hi();
SCL_IN_HI();
while (!SCL_GET()) {}; // clock stretch
bit_delay_hi(); // attempt to fix corruption; unnecessary?
i2cm_start();
}
__attribute__((section(".ramfunc")))
void i2cm_stop()
{
SDA_OUTLO(); bit_delay_lo();
SCL_IN_HI(); bit_delay_hi();
while (!SCL_GET()) {}; // clock stretch
SDA_IN_HI(); bit_delay_hi();
bit_delay_hi();
bit_delay_hi();
bit_delay_hi();
}
// returns: data byte
__attribute__((section(".ramfunc")))
uint8_t i2cm_rd(uint8_t ack)
{
int8_t x;
uint8_t in = 0;
SDA_IN_HI();
for (x = 8; x; x--) {
in <<= 1; // clock next bit
SCL_IN_HI();
while (!SCL_GET()) {}; // clock stretch
rd_delay();
if (SDA_GET()) in |= 1;
SCL_OUTLO(); bit_delay_lo();
}
if (ack) { SDA_OUTLO(); } // ack
else { SDA_IN_HI(); } // nack
SCL_IN_HI(); bit_delay_hi();
SCL_OUTLO(); bit_delay_lo();
return in;
}
// returns: possible ack from target
__attribute__((section(".ramfunc")))
uint8_t i2cm_wr(uint8_t dat)
{
int8_t x;
uint8_t ack;
SCL_OUTLO();
for (x = 8; x; x--) {
if (dat & 0x80) { SDA_IN_HI(); }
else { SDA_OUTLO(); }
SCL_IN_HI();
while (!SCL_GET()) {}; // clock stretch
wr_delay_hi();
dat <<= 1;
SCL_OUTLO(); bit_delay_lo();
}
SDA_IN_HI(); __asm__ volatile("nop"); // slave will now try to ack
SCL_IN_HI(); bit_delay_hi();
while (!SCL_GET()); // nothing should stretch here, but...
ack = SDA_GET();
SCL_OUTLO(); bit_delay_lo();
return ack;
}
// use a left-aligned address with this implementation.
__attribute__((section(".ramfunc")))
uint8_t i2cm_addr(uint8_t addr, uint8_t reading_bit)
{
addr &= 0xfe;
addr |= reading_bit ? 1 : 0;
return i2cm_wr(addr);
}
void i2cm_rdbuf(uint8_t *dat, uint16_t len)
{
while (len--) *dat++ = i2cm_rd(len > 0);
// i2cm_stop();
}
void i2cm_wrbuf(const uint8_t *dat, uint16_t len)
{
uint8_t nack;
while (len--) {
nack = i2cm_wr(*dat++);
if (nack) break;
}
// i2cm_stop();
}