#include #include #include #include "i2c.h" #define SCL_BIT PD3 #define SDA_BIT PD2 #define SCL_M (1 << SCL_BIT) #define SDA_M (1 << SDA_BIT) /* Open drain: PORT bits stay 0, DDR=1 drives low, DDR=0 releases (pull-up). */ #define SDA_LOW() (DDRD |= SDA_M) #define SDA_HIGH() (DDRD &= ~SDA_M) #define SCL_LOW() (DDRD |= SCL_M) #define SCL_HIGH() (DDRD &= ~SCL_M) /* * Bit timing (16 MHz). Data bits are inlined with cycle-exact pads so that * delay 0 gives at least T_BIT cycles per SCL period (400 kHz, Fast mode) with * tLOW >= 1.3 us and tHIGH >= 0.6 us. Each delay unit adds 3 cycles to each * half period: the period is 40 cycles at delay 0 and 38 + 6 * delay above: * 0 = 400 kHz, 5 = 235 kHz, 10 = 163 kHz, 47 = 50 kHz. * Reads run somewhat slower (~314 kHz at delay 0); both stay within Fast mode. * The pads below are tuned against the generated code (see wr()/rd()); the * SCL edge is followed by a read-back, so slow rise or clock stretching only * ever lengthens the high phase. */ static uint8_t dly = 0; static bool bus_err; /* START/STOP/recovery only: not on the per-bit path, >= 0.8 us at delay 0 */ static void d(void) { if (dly) _delay_loop_1(dly); } /* 3 cycles per unit; n stays in a register for the whole byte */ static inline __attribute__((always_inline)) void xdelay(uint8_t n) { if (n) __asm__ volatile ("1: dec %0\n\tbrne 1b" : "+r" (n)); } void i2c_set_delay(uint8_t v) { dly = v; } void i2c_init(void) { PORTD &= ~(SCL_M | SDA_M); DDRD &= ~(SCL_M | SDA_M); } static void __attribute__((noinline)) scl_wait(void) /* stretched or slow rise */ { uint16_t t = 0; /* ~16 ms max */ while (!(PIND & SCL_M)) if (--t == 0) { bus_err = true; break; } } /* * Release SCL and honour clock stretching. The input synchronizer needs a * cycle before PIND shows the released pin, hence the nop: without it the * check always failed and every bit took the slow path. */ static inline __attribute__((always_inline)) void scl_up(void) { SCL_HIGH(); __asm__ volatile ("nop"); if (!(PIND & SCL_M)) scl_wait(); } static void scl_release(void) { scl_up(); } /* * One SCL pulse for a data or ACK bit, SDA already set up. Returns the SDA * level sampled at the end of the high phase. PAD_LOW/PAD_HIGH make up the * difference between the instruction overhead and the timing targets. */ /* * Counted on avr-gcc 15 -Os at delay 0, sbi/cbi = 2 cycles, from the SCL * edge to the next one. wr(): data low 13 (bit 1) / 16 (bit 0), ACK low 9 + * ACK_PAD, high 10. rd(): see there. Recount after compiler or code changes. */ #define PAD_LOW 12u /* data low 25 / 28, ACK low 24 */ #define PAD_HIGH 6u /* high 16 */ #define ACK_PAD 3u static inline __attribute__((always_inline)) uint8_t clock_bit(uint8_t n) { __builtin_avr_delay_cycles(PAD_LOW); xdelay(n); scl_up(); __builtin_avr_delay_cycles(PAD_HIGH); xdelay(n); uint8_t sda = PIND & SDA_M; SCL_LOW(); return sda; } static void start(void) { SDA_HIGH(); d(); scl_release(); d(); SDA_LOW(); d(); SCL_LOW(); } static void stop(void) { SDA_LOW(); d(); scl_release(); d(); SDA_HIGH(); d(); } static bool wr(uint8_t b) /* returns true on ACK */ { uint8_t n = dly; for (uint8_t i = 0; i < 8; i++) { if (b & 0x80) SDA_HIGH(); else SDA_LOW(); b <<= 1; clock_bit(n); } SDA_HIGH(); __builtin_avr_delay_cycles(ACK_PAD); /* this path is shorter than a data bit */ return !clock_bit(n); } static uint8_t rd(bool ack) { uint8_t n = dly, v = 0; SDA_HIGH(); for (uint8_t i = 0; i < 8; i++) v = (v << 1) | (clock_bit(n) ? 1 : 0); if (ack) SDA_LOW(); else SDA_HIGH(); clock_bit(n); SDA_HIGH(); return v; } /* Diagnostic: optionally enable weak internal pull-ups, return bit0=SCL, bit1=SDA levels */ uint8_t i2c_lines(uint8_t pullup) { if (pullup) PORTD |= (SCL_M | SDA_M); else PORTD &= ~(SCL_M | SDA_M); DDRD &= ~(SCL_M | SDA_M); d(); d(); uint8_t p = PIND; return ((p & SCL_M) ? 1 : 0) | ((p & SDA_M) ? 2 : 0); } uint8_t i2c_recover(void) { bus_err = false; for (uint8_t i = 0; i < 9 && !(PIND & SDA_M); i++) { SCL_LOW(); d(); d(); scl_release(); d(); d(); } stop(); return (bus_err || !(PIND & SDA_M) || !(PIND & SCL_M)) ? I2C_BUS_ERR : I2C_OK; } /* ---- split-phase API (one transaction spread over several USB requests) ---- */ uint8_t i2c_start(uint8_t addr_rw, bool repeated) { if (!repeated) { bus_err = false; if (!(PIND & SDA_M) || !(PIND & SCL_M)) { if (i2c_recover() != I2C_OK) return I2C_BUS_ERR; } } start(); bool ack = wr(addr_rw); if (bus_err) return I2C_BUS_ERR; return ack ? I2C_OK : I2C_NACK_ADDR; } uint8_t i2c_write(uint8_t b) { bool ack = wr(b); if (bus_err) return I2C_BUS_ERR; return ack ? I2C_OK : I2C_NACK_DATA; } uint8_t i2c_read(bool ack) { return rd(ack); } bool i2c_bus_error(void) { return bus_err; } void i2c_stop(void) { stop(); } uint8_t i2c_xfer(uint8_t addr, const uint8_t *w, uint8_t wlen, uint8_t *r, uint8_t rlen) { uint8_t st = I2C_OK; bus_err = false; if (!(PIND & SDA_M) || !(PIND & SCL_M)) { if (i2c_recover() != I2C_OK) return I2C_BUS_ERR; } if (wlen || !rlen) { start(); if (!wr(addr << 1)) { st = I2C_NACK_ADDR; goto done; } for (uint8_t i = 0; i < wlen; i++) if (!wr(w[i])) { st = I2C_NACK_DATA; goto done; } } if (rlen) { start(); /* (repeated) START */ if (!wr((addr << 1) | 1)) { st = I2C_NACK_ADDR; goto done; } for (uint8_t i = 0; i < rlen; i++) r[i] = rd(i < rlen - 1); } done: stop(); return bus_err ? I2C_BUS_ERR : st; }