This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Having a bit of trouble waiting for a pin to go high

I'm fairly new to working this close to hardware and am playing around with a few things.  I'm trying to bit bang i2c over a couple of gpio lines and everything seems to be working just fine until I try to read the ack/nack bit after a write.  In the attached image the address is sent just fine, but when the register is sent it appears the slave holds down the line, which should be fine.  However my code for the byte write looks like the following:

uint8 i2c_write(uint8 data, const uint32_t clockPin, const uint32_t dataPin)
{
    uint8 ndx, ack  = 0;

    nrf_gpio_cfg_output(dataPin);

    for(ndx = 0; ndx < 8; ndx++)
    {
        nrf_gpio_pin_clear(clockPin);

        //get the current bit to send
        if(data & 0x80)
        {
            nrf_gpio_pin_set(dataPin);
        }
        else
        {
            nrf_gpio_pin_clear(dataPin);
        }

        nrf_gpio_pin_set(clockPin);

        /* Ensure that the clock is high */
        if( !waitForSCL(clockPin) )
        {
            //we failed to bring the clock high
            comTimeout = TRUE;
            break;
        }

        //next bit
        data <<= 1;
    }//end for

    nrf_gpio_pin_clear(clockPin);

    nrf_gpio_cfg_input(dataPin, NRF_GPIO_PIN_NOPULL);

    nrf_gpio_pin_set(clockPin);

    /* Ensure that the clock is high */
    if( !waitForSCL(clockPin) )
    {
        //we failed to bring the clock high
        comTimeout = TRUE;
    }

    ack = nrf_gpio_pin_read(dataPin);

    // pull both lines low again
    nrf_gpio_pin_clear(clockPin);

    nrf_gpio_cfg_output(dataPin);
    I2C_LOW(dataPin);

    //acks are low, nacks are high
    return !ack;
} //end i2c_write

static boolean waitForSCL(uint32_t clockPin)
{
    uint16 maxTries = MAX_TRIES;

    while( 0 == nrf_gpio_pin_out_read(clockPin)  && (maxTries>0) )
    {
        nrf_gpio_pin_set(clockPin);
        maxTries--;
    }

    return ( maxTries > 0 );
} // end of waitForSCL

Output from logic analyzer (SCL on bottom, SDA on top):

It looks like the function waitForSCL isn't doing what I'm intending for it to do.  Since I'm setting the clock high right before I check this function it makes sense that many times the first call to nrf_gpio_pin_out_read would read the line high right away, but when the slave is clock stretching it seems to still always read that as high.  I've tried a bunch of different delays and a bunch of other stuff, but I appear to be stuck.  Most likely this is an easy fix that I'm just not familiar enough with the SDK APIs to figure out.  Thanks for any help!

  • I'm definitely having trouble seeing this so I quickly implemented the same functionality with TWI just to test against.  The output seems pretty similar to what I have in the image above (timing is different of course).

    After the clock cycle on this one on the far right the two bytes that are expected to be written to the register are there, which leads me to believe that this is a very delayed ack/nack bit similar to the image above.  The difference is the TWI code waits for that ack/nack and my code does not.

    The goal of the WaitForSCL function for me was to essentially hang just like this

    until the clock line went high again so I could read the data line for the ack/nack.

  • OK a few comments and questions here. I can see from the trace that SCL *does* eventually go high, how long are you waiting for a potential clock stretch (no scale)? It's a reasonably tight loop and you don't say what MAX_TRIES is. I2C specs often state 500us as the max slave clock stretch. If your clock's 400kHz, eyeballing it you're getting SCL high after about 50us. What if you remove the MAX_CYCLES and just wait, as a test. 

    The SDA cycle after the clock goes low is a bit odd. Not sure what's going on there. 

    Do you have external pullups on SDA and SCL? I see you're configuring SDA as input no pull, if you don't have an external pull, that's not right. SDA and SCL need pulls either external (best) or at least internal (ok). 

    Similar for SCL, that's not supposed to be driven high but for '1' you're supposed to tri-state it and let the pullup pull it high. Can't see how you have that configured either but the Nordic description of that would be something like S0D1 (standard drive for 0, disconnect for 1) with a pullup. 

    My read of that diagram is you just have a slow slave and you're not waiting long enough but check the other points too and remember that nothing ever drives a line high in I2C, it's low or tri-state. 

  • The weird thing about the waitForSCL function is I not only never return false from that function, I don't even ever enter the condition where the nrf_gpio_pin_out_read call reads anything but high (1).  So it is immediately reading that the clock is high despite what the output from the logic analyzer is telling me.  Again this is probably something that is related to my lack of experience with these APIs though.  As soon as I set the clock high before calling waitForSCL it seems to think the pin is set.  My guess is the SDA line going low immediately afterwards is me setting the data line low at the end of the write function.  That shouldn't happen until after the clock line has been driven high and the ack bit has been read, but it is seemingly blowing right through that.

    There are external pull ups on both lines.  I'm using configuring the SCL line with nrf_gpio_cfg_input( (NRF_GPIO_PIN_DIR_INPUT, NRF_GPIO_PIN_INPUT_CONNECT, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_S0S1,  NRF_GPIO_PIN_NOSENSE).  I can try changing the drive from SOS1 to SOD1 to see if that makes a difference.

    Thanks for your reply!

  • Bah, ignore that.  Its configured as nrf_gpio_cfg_output() (NOT input)  So replace NRF_GPIO_PIN_DIR_INPUT with NRF_GPIO_PIN_DIR_OUTPUT and NRF_GPIO_PIN_INPUT_CONNECT with NRF_GPIO_PIN_INPUT_DISCONNECT above.

  • I was just replying when you corrected yourself :) If the input is disconnected then you are reading the value you set not the value actually on the pin. I wish I could remember all the discussions we've had the last 5 years about this and whether you can connect the input buffer on an output pin and read what's on the pin or whether you always get the value you set, you might want to test that by grounding a pin and trying various register combinations to see if you can set it as an output, set it 'high' and still read low. 

    Your alternative when you're waiting for the slave is to reconfigure it as an input (like you did SDA) and then you definitely will read the value actually on the pin. 

Related