How would you bit-bang I2C START and STOP conditions? What is the most common mistake?
START and STOP are the only I2C conditions where SDA transitions while SCL is high. During normal data transfer, SDA may only change when SCL is low. This is how receivers distinguish control conditions from data.
void i2c_start(void) {sda_high(); // Release SDA (pull-up brings it high)scl_high(); // Release SCLdelay_us(5); // Bus free time (t_BUF)sda_low(); // SDA falls while SCL is high = STARTdelay_us(5); // Hold time (t_HD;STA)scl_low(); // Pull SCL low to prepare for first data bit}void i2c_stop(void) {sda_low(); // Ensure SDA is lowdelay_us(5);scl_high(); // Release SCL (pull-up brings it high)delay_us(5); // Setup time (t_SU;STO)sda_high(); // SDA rises while SCL is high = STOPdelay_us(5); // Bus free time before next START}
The most common mistake in bit-banged I2C is not reading SCL back after releasing it. When the code calls scl_high(), it releases the open-drain output and expects the pull-up to bring SCL high. But if a slave is performing clock stretching, SCL stays low. The code must poll SCL until it actually goes high before proceeding — otherwise, timing is wrong and the transaction fails silently:
void scl_high_with_stretch(void) {scl_release(); // Stop driving SCL lowuint32_t timeout = 10000;while (!scl_read() && --timeout); // Wait for slave to releaseif (!timeout) handle_bus_error(); // Timeout = bus stuck}
Other common bit-bang mistakes: (1) using push-pull GPIO mode instead of open-drain, (2) forgetting delays between transitions (violating setup/hold times), and (3) not implementing the repeated START correctly (failing to pull SCL low before transitioning SDA for the repeated START, which creates an accidental STOP).
Source: I2C Q&A
