1

I've been trying to write a small i2c lib in C over the past week. I managed to get writing to slaves working (for example I can write to an ssd1306 oled) but reading (for example date/time from an ds3231) never works as my slaves don't seem to set SDA high when I request bytes.

uint8_t
i2c_read(uint8_t ack)
{
    uint8_t i, val;

    val = 0;
    cbi(SCL);
    DDRB &= ~(1 << SDA); /* Use SDA for input. */
    cbi(SDA); /* We need to pull down SDA to receive input. */

    /* Read 8bit from the slave. */
    _delay_us(DELAY);
    for (i = 0; i < 8; ++i) {
        sbi(SCL); /* Request one more bit. */
        _delay_us(DELAY); /* Wait a little so the slave can send his answer. */
        if (PORTB & (1 << SDA)) { /* Here the slave should have set SDA to 1 but it never does. */
            PORTB |= (1 << PB4);
            val = (val << 1) | 1;
        } else {
            val = (val << 1);
        }
        cbi(SCL);
        _delay_us(DELAY);
    }

    /* Send back an ACK or NAK. */
    DDRB |= 1 << SDA; /* Use SDA for writing. */
    if (ack)
        cbi(SDA); /* Set SDA to 0 to send ACK. */
    else
        sbi(SDA); /* Set SDA to 1 to send NAK. */

    sbi(SCL);
    _delay_us(DELAY);
    cbi(SCL);
    cbi(SDA);

    return val;
}

I set PB4 with to 1 when a slave actually sends a 1. Apparently that never happens. I also had a look at how the various other libs out there do it but I didn't spot anything that would make a difference.

Any clues on what I migth be missing? Before reading my program sends the slave address to the bus. Also: please don't tell me to use other libraries since this is a learning project.

2
  • Hi and welcome to the Arduino.SE community. Thanks for your question.Please add more detail in your question on how this relates to Arduino. Cheers. – sa_leinad Oct 1 '19 at 15:20
  • Does the clock line really get high? A slave can pull down the clock to pause the communciation until it is ready. Can you show us an output of an oscilloscope or a logic analyzer? So that we can see, what exactly happens on the lines? – chrisl Oct 1 '19 at 16:08
1

Not sure, if this is the only problem, but you are reading from the wrong register.

There are 3 registers associated with a digital output pin: DDR, PORT and PIN. To read a pin, you need to read from the PIN register. Currently you are reading the PORT register, which will only ever have the value, that you set it to (zero in this case, because you cleared the bit beforehand with cbi(SDA);). When you configure the pin as input with the DDR register, the PORT register determines, if the internal pullup resistor is activated or not, so it cannot reflect the actual state of the pin. For this you need the PIN register.


Besides that, you might want to look into another thing, that might or might not be a problem in your case: I2C is an open drain bus, meaning, that it is driven actively low and passively high (through pullup resistors). I guess you already have external pullup resistors on your lines, so that you really can deactivate the internal pullup without breaking the communication. But imagine the following situation: The slave get's a request, but cannot clock it out that fast (for whatever reason). It can pull the clock to LOW, until it is ready to push out the data bit, slowing down the clock speed. (This can be done for every bit, unless you are in highspeed mode, then only before the start of a new byte). (This behavior is called clock stretching - Thanks to Edgar for the information) You don't monitor the line, so it can happen, that you drive the clock line actively to high, before the slave has released it. This results in a significant current between master and slave, since the master drives actively high and slave actively low.

There are implementations of I2C communication, that are just doing that and are working fine in most situations. I just wanted to give you this information. And if you at some point want to implement bus arbitration (to allow a multi-master setup), you definitely need this open drain behaviour.

4
  • Re. the slave pulling SCL low: I would add that this is called “clock stretching”, just to provide a search keyword. – Edgar Bonet Oct 1 '19 at 18:40
  • You are correct, I totally forgot to mention it, though I thought I had done this. I will add this information to the answer – chrisl Oct 1 '19 at 20:19
  • Thanks for the info! I didn't know that theres another register for reading input pins. But it makes sense since PORT is used for pullup/down in input mode. I have pull up resistors in my cirquit. They are integrated on the i2l slave ICs afaik. I am also waiting for the slave to stop pulling down SCL now (clock streching). Now I have the same problem but the slave does't pull down SDA (I guess) thus my SDA is always high (im reading from PINB). This is quite confusing. Have you got any ideas on what I might be doing wrong? – kessler bebe Oct 1 '19 at 21:04
  • Edit: I have two i2c slaves that both have internal pull up resistort. Is it possible that they interfere in some way? I don't know a lot about electronics. – kessler bebe Oct 1 '19 at 21:06

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.