| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- #include <avr/io.h>
- #include <util/delay_basic.h>
- #include <stdbool.h>
- #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;
- }
- /* ---- 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;
- }
|