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
227 lines
5.5 KiB
C
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();
|
|
}
|