/* * 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 #include #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(); }