0
\$\begingroup\$

I implement bit-banging I2C on Atmega328P and met the problem when the Master receives data from the slave. I'm in depression in debugging this code since I found no problem. Master Write work perfectly in many cases (Receive ACK, NAK if wrong slave address, sen multiple data,... ) But When Master want to read data from slave it only send 7/8 bit :( The receive algorithm seems correct just missing 1 bit! Please help me!

Master Arduino Using bit-banging i2c on AVR, Slave Arduino using Wire.h (Master writing work perfect)

As you can see, just missing one bit!

Master AVR code

#include <util/delay.h>
#include <stdint.h>

// CODE from STM32
#define SDA PC4
#define SCL PC5
//OUTPUT REGISTER: PORTC
// PC4: Pin A4 - SDA | PC5: Pin A5 - SCL | Arduino (Atmega328P)
#define SDA_ON (PORTC |= (1 << PC4))
#define SDA_OFF (PORTC &= ~(1 << PC4))
#define SCL_ON (PORTC |= (1 << PC5)) // PORTC = 0b0000 0010
#define SCL_OFF (PORTC &= ~(1 << PC5))

#define READ_SDA() (PINC & (1 << SDA))
#define READ_SCL() (PINC & (1 << SCL))

#define SET_SDA_OUT() DDRC |= (1 << SDA)
#define SET_SDA_IN() DDRC &= ~(1 << SDA)

#define I2C_READ 0x01
#define I2C_WRITE 0x00

#define ADDR 0x08 //Slave Address
#define ADDR_W (ADDR << 1)
#define ADDR_R (ADDR_W | 1)

void init()
{
    DDRC = (1 << DDC5) | (1 << DDC4);
    SCL_ON;
    SDA_ON;
}

void start()
{
    SDA_ON;
    _delay_us(4.5);
    SCL_ON;
    _delay_us(4.5);
    SDA_OFF;
    _delay_us(4.5);
    SCL_OFF;
    _delay_us(4.5);
}

/*  i2c stop sequence */
void stop()
{
    SDA_OFF;
    _delay_us(4.5);
    SCL_ON;
    _delay_us(4.5);
    SDA_ON;
    _delay_us(4.5);
}

/* Transmit 8 bit data to slave */
uint8_t Tx(uint8_t dat)
{

    for (uint8_t i = 0; i < 8; i++)
    {
        if (dat & 0x80)
        {
            SDA_ON;
        }
        else
        {
            SDA_OFF;
        }
        _delay_us(3);
        SCL_ON;
        _delay_us(5);
        SCL_OFF;
        _delay_us(2);
        dat <<= 1;
    }
    SET_SDA_IN();
    _delay_us(3);
    SCL_ON; // the 9th pulse is high
    _delay_us(3);
    if (READ_SDA())
    {
        _delay_us(2);
        SCL_OFF; // the 9th pulse is low
        PORTC = 0x00;
        SET_SDA_OUT();
        return 0;
    }
    else
    {
        _delay_us(2);
        SCL_OFF; // the 9th pulse is low
        PORTC = 0x00;
        SET_SDA_OUT();
        return 1;
    }
}

uint8_t Rx(void)
{
    uint8_t byte = 0;
    uint8_t bit = 0;

    _delay_us(10);
    SET_SDA_IN();

    for (bit = 0; bit < 8; bit++)
    {
        byte <<= 1;
        do
        {
            SCL_ON;
        } while (READ_SCL() == 0);
        _delay_us(3);
        if (READ_SDA())
        {
            byte |= 0x01;
        }
        _delay_us(2);
        SCL_OFF;
        _delay_us(5);
    }

    PORTC = 0b00010000;
    SET_SDA_OUT();
    SDA_ON;
    SCL_ON;
    _delay_us(3);
    SCL_OFF;
    _delay_us(3);

    return 1;
}

int main(void)
{
    init();
    _delay_ms(3000);
    unsigned char ack;
    while (1)
    {
        start();
        ack = Tx(ADDR_R);
        if (!ack)
        {
            stop();
            break;
        }
        Rx();
        stop();

        _delay_ms(1000);
    }
}```

    
**Slave code using Wire.h**

```#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  pinMode(10,OUTPUT);
}

void loop() {
  Wire.requestFrom(0x08, 1);    // (a,b) request b bytes from slave device #a

  while (Wire.available()) { // slave may send less than requested
    char c = Wire.read(); // receive a byte as character
    Serial.print("Master received data: ");Serial.println(c);
    delay(100);
  }
}```


\$\endgroup\$
3
  • \$\begingroup\$ Can you describe what exactly is the issue? Dumping code and flawless-looking logic capture is of little use. The largest issue is that you seem to think SDA and SCL are push-pull outputs. They are not, they are open-drain outputs, they must never be push-pull outputs, that would prevent any MCU slave from working properly with the master if it is slow enough to stretch the clock. \$\endgroup\$
    – Justme
    May 28 at 18:36
  • \$\begingroup\$ As justme says, i2c is open drain. You should set the ddr bit (ie make the pin an output) in order to output a ‘0’ and clear it (ie make the pin an input) for a ‘1’ and the pullup resistors do their job. \$\endgroup\$
    – Kartman
    May 29 at 0:21
  • \$\begingroup\$ Thank you for your response, I have solved this problem by stretching the SCL before go into Rx(void) by 12us and now the data frame is correct. \$\endgroup\$
    – MinhTem
    May 29 at 1:32
0
\$\begingroup\$

I just using Master Wire.h then compare the waveform then stretching and it work :) :) Only 2us difference

enter image description here

{
    uint8_t byte = 0;
    uint8_t bit = 0;

    _delay_us(12); <---- From 10 to 12us
    SET_SDA_IN(); <--- Already release the SDA so Slave can pull down the SDA line 

    for (bit = 0; bit < 8; bit++)
    {
        byte <<= 1;
        do
        {```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.