|
@@ -14,10 +14,29 @@
|
|
|
#define SCL_LOW() (DDRD |= SCL_M)
|
|
#define SCL_LOW() (DDRD |= SCL_M)
|
|
|
#define SCL_HIGH() (DDRD &= ~SCL_M)
|
|
#define SCL_HIGH() (DDRD &= ~SCL_M)
|
|
|
|
|
|
|
|
-static uint8_t dly = 4; /* default ~400 kHz at 16 MHz - verify with a scope */
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+ * 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;
|
|
static bool bus_err;
|
|
|
|
|
|
|
|
-static inline void d(void) { if (dly) _delay_loop_1(dly); }
|
|
|
|
|
|
|
+/* 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_set_delay(uint8_t v) { dly = v; }
|
|
|
|
|
|
|
@@ -27,13 +46,49 @@ void i2c_init(void)
|
|
|
DDRD &= ~(SCL_M | SDA_M);
|
|
DDRD &= ~(SCL_M | SDA_M);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-static void scl_release(void) /* release SCL and honour clock stretching */
|
|
|
|
|
|
|
+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();
|
|
SCL_HIGH();
|
|
|
- if (!(PIND & SCL_M)) {
|
|
|
|
|
- uint16_t t = 0; /* ~16 ms max */
|
|
|
|
|
- while (!(PIND & SCL_M)) if (--t == 0) { bus_err = true; break; }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ __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)
|
|
static void start(void)
|
|
@@ -53,29 +108,27 @@ static void stop(void)
|
|
|
|
|
|
|
|
static bool wr(uint8_t b) /* returns true on ACK */
|
|
static bool wr(uint8_t b) /* returns true on ACK */
|
|
|
{
|
|
{
|
|
|
|
|
+ uint8_t n = dly;
|
|
|
|
|
+
|
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
|
if (b & 0x80) SDA_HIGH(); else SDA_LOW();
|
|
if (b & 0x80) SDA_HIGH(); else SDA_LOW();
|
|
|
b <<= 1;
|
|
b <<= 1;
|
|
|
- d(); scl_release(); d(); SCL_LOW();
|
|
|
|
|
|
|
+ clock_bit(n);
|
|
|
}
|
|
}
|
|
|
SDA_HIGH();
|
|
SDA_HIGH();
|
|
|
- d(); scl_release(); d();
|
|
|
|
|
- bool ack = !(PIND & SDA_M);
|
|
|
|
|
- SCL_LOW();
|
|
|
|
|
- return ack;
|
|
|
|
|
|
|
+ __builtin_avr_delay_cycles(ACK_PAD); /* this path is shorter than a data bit */
|
|
|
|
|
+ return !clock_bit(n);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static uint8_t rd(bool ack)
|
|
static uint8_t rd(bool ack)
|
|
|
{
|
|
{
|
|
|
- uint8_t v = 0;
|
|
|
|
|
|
|
+ uint8_t n = dly, v = 0;
|
|
|
|
|
+
|
|
|
SDA_HIGH();
|
|
SDA_HIGH();
|
|
|
- for (uint8_t i = 0; i < 8; i++) {
|
|
|
|
|
- d(); scl_release(); d();
|
|
|
|
|
- v = (v << 1) | ((PIND & SDA_M) ? 1 : 0);
|
|
|
|
|
- SCL_LOW();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (uint8_t i = 0; i < 8; i++)
|
|
|
|
|
+ v = (v << 1) | (clock_bit(n) ? 1 : 0);
|
|
|
if (ack) SDA_LOW(); else SDA_HIGH();
|
|
if (ack) SDA_LOW(); else SDA_HIGH();
|
|
|
- d(); scl_release(); d(); SCL_LOW();
|
|
|
|
|
|
|
+ clock_bit(n);
|
|
|
SDA_HIGH();
|
|
SDA_HIGH();
|
|
|
return v;
|
|
return v;
|
|
|
}
|
|
}
|