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