#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) static uint8_t dly = 4; /* default ~400 kHz at 16 MHz - verify with a scope */ static bool bus_err; static inline void d(void) { if (dly) _delay_loop_1(dly); } 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 scl_release(void) /* release SCL and honour clock stretching */ { SCL_HIGH(); if (!(PIND & SCL_M)) { uint16_t t = 0; /* ~16 ms max */ while (!(PIND & SCL_M)) if (--t == 0) { bus_err = true; break; } } } 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 */ { for (uint8_t i = 0; i < 8; i++) { if (b & 0x80) SDA_HIGH(); else SDA_LOW(); b <<= 1; d(); scl_release(); d(); SCL_LOW(); } SDA_HIGH(); d(); scl_release(); d(); bool ack = !(PIND & SDA_M); SCL_LOW(); return ack; } static uint8_t rd(bool ack) { uint8_t v = 0; SDA_HIGH(); for (uint8_t i = 0; i < 8; i++) { d(); scl_release(); d(); v = (v << 1) | ((PIND & SDA_M) ? 1 : 0); SCL_LOW(); } if (ack) SDA_LOW(); else SDA_HIGH(); d(); scl_release(); d(); SCL_LOW(); 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; } 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; }