Does pinSetFast work reliably?

Hi,
OK so its a leading question, as I believe there could be a problem with it :-O.

I am trying to port (and rewrite) a soft I2C Master feature, and am suffering from random glitches.

The code was using delayMicrosecond, but now that has even been disabled and a simple code loop being tried.

NB I am trying to avoid the dedicated I2C due to other pin requirements :-O.

The problem is that I use two pins (currently D6 and D7) for SDA and SCL. In the primitive function which writes a single bit, SDA is set to 1, a small delay is called, then SCL raised to 1. This code works for much of the time but I can catch bad data being sent, even the address is being corrupted.

At the time that SCL is set high - a small (couple of microsecond) glitch occurs on SDA, it drops low, then high again (I will attach a scope trace). When this happens - the receiver ā€˜seesā€™ the LOW instead of the correct value.

void SoftI2CMaster::i2c_writebit( uint8_t c )
{
  noInterrupts();
    if ( c > 0 )
    {
      pinSetFast(_sdaPin);
      //i2c_sda_hi();
    }
    else
    {
      pinResetFast(_sdaPin);
      //i2c_sda_lo();
    }
    Loopy();
    //delayMicroseconds(10); //i2cbitdelay);
    pinSetFast(_sclPin);
    //i2c_scl_hi();
    Loopy();
    //delayMicroseconds(10); //i2cbitdelay);

    pinResetFast(_sclPin);
    //i2c_scl_lo();
    Loopy();
    //delayMicroseconds(20); //i2cbitdelay);
    interrupts();
}

Loopy is a crude code loop delay :smile:
int Loopy()
{
  TP2ON
  int c = 0;
  for (int i=0;i<10000;i++)
  {
    c = i+6;
  }
  TP2OFF
  return c;
}

TP2ON and TP2OFF are simply pin D3 being turned On/Off for a scope.

Hmmā€¦so where do I add my scope trace attachment ???
ā€¦
Canā€™t see how to add an attachment - sorry :frowning:

Incidentally - I have an issue with the docs for pinSetFast (etc.), in that they say that the IO pin MUST be an output BEFORE you can set the value :-O. In my experience (mainly with PICs), the preferred method sets the pins output latch BEFORE making it an output to avoid such a glitch - ie BEFORE turning it into an output ensure that once it becomes an output it will have the correct ON or OFF setting IMMEDIATELY. BUT pinSetFast specifically tells us to make the pin an output first :-O.

BR

Graham

@GrahamS, I formatted your code to properly appear in your post (have a look over here to see how this is done). Can you include you setup() and loop() code so we can see how your configure the pins. The pinSetFast/pinResetFast() functions rely on pinMode() being correctly setup prior to their use. Using delayMicroseconds() should not cause any problems. Having the rest of your code would help.

On the pre-write question, using the fast pin commands bypasses all regular pinmode checks so it should allow you to preset the pin prior to a pinMode(). However, I donā€™t believe this has been tested. I will gladly change the docs if you find that this works. :smile:

Hi,

Sorry about the poor formatting ā€˜mea culpaā€™ :wink:

The init code sets the pins to OUT every time we ā€˜beginTransmissionā€™

    uint8_t SoftI2CMaster::beginTransmission(uint8_t address)
{
  //ensure we have control of the pins
  pinMode(_sdaPin, OUTPUT);
  pinMode(_sclPin, OUTPUT);
  pinSetFast(_sdaPin);
  pinSetFast(_sclPin);
  i2c_start();
  uint8_t rc = i2c_write((address<<1) | 0); // clr read bit
  return rc;
}

Iā€™ll try setting the output before pinMode (it makes more sense to me), but donā€™t think that is this problem, at it happens in the middle of the code - when pinSetFast is called - between two (approx 5uSec) delays.

If I could add an attachment - I would give you a scope trace of the glitch - but I just donā€™t know how to attach one on this forum :frowning:

NB The glitch is not always there - its more often cleanā€¦

BR
G

@GrahamS, is the glitch from the device or from the code? Do you have pull-up resistors on the lines?

Hi,

Yes both lines have 8k2 pullups fitted, and at this point my code is not changing pin directions anyway.

The glitch happens when the SCL pin goes high, SDA comes low for about 170 nSec - sorry I said originally a couple of microsecs - (but I just zoomed my captured glitch on the scope). So its probably the way the port is being switched, 1 clock cycle maybe ?? - havenā€™t done the math yetā€¦

So really it comes down to one line of code - pinSetFast(_sclPin);

Now I donā€™t ā€˜knowā€™ the ARM cpus (AT ALL), but it looks to me like a machine code read/write or something ???. I took a look at the fastpin.h - but the code is too alien for me - I DO speak many (chip) languages, but not this one (yet ;-). I have used PICs (1/16/32 bits) for YEARS now - but this Photon module is a ā€˜game-changerā€™ ;-)).

Its probably something really simple ;-)) - if only I knew what machine code was being compiled, and the chip architecture in more detailā€¦

Thanks though for taking the timeā€¦its a show-stopper at the moment.

BR
Graham

@GrahamS, strange. The code used by pinSetFast() is a direct write to the register which controls the pin. It is an atomic instruction in the hardware does all the pin read/modify/write so nothing in software can affect that. Can you create a minimalist test case where no I2C slave is required that I could test?

Hi,
Actually - the 170 nSec might even be my scope resolution, and this might even be a phantom measuring issue :-Oā€¦ On the analogue scope trace there is a small dip, but the digital trace shows it more pronounced :-O.

So I could well be chasing my own tail :-1:

Maybe Iā€™d better put some more decouplers on the board and see if that reduces the noise level ;-)).

Sorry if I have thrown in a ā€˜red herringā€™ here :-O.

BR
Graham
.

@GrahamS, no sweat! This is important stuff IMO. You may want to reduce your pull-ups to 4.7K to reduce the rise time as well. Make sure these donā€™t compete with your I2C breakouts if you are using any. Also, add back the delayMicroseconds() for better repeatability. Keep me posted. :smile:

1 Like

@GrahamS, I too seem to be suffering with similar issues that you have with my porting of the SoftI2CMaster source code to the Photon.

Am using the Salea Logic Analyser and am seeing glitches. It could well be a hardware issue, but the problem is not there when running the inbuilt Wire I2C module instead of SoftI2CMaster port.

Specifically my current issue is with reading the ACK bit after a write. The following routing is coming back with a 1 when it should be a 0 (as seen with the logic analyser):

uint8_t SoftI2CMaster::requestFrom(uint8_t address)
{
i2c_start();
return(i2c_write((address << 1) | 1)); // set read bit
}

Was also getting glitches with writing the SCL line - had a couple of instances where the trap below was kicked off. But this has sorted itself - this smells of a hardware issue:

void SoftI2CMaster::i2c_writebit( uint8_t c )
{

if ( c > 0 ) 
{
    i2c_sda_hi();
    // Following traps the issue when SDA is NOT hi....
    if (pinReadFast(_sdaPin) == 0)
    {
        // Is LOW - should NOT be - Trap it for reference - this is a big problem...
       .....
    }
} 
else 
    i2c_sda_lo();
 ....

This has been a particularly frustrating exercise!

@UMD, you may want to substitute all Fast pin operations for regular pin operations to see if things improve. Do you have your code available somewhere that I can take a look at it?

I wonder if timing of the software I2C code is causing a collision on the bus where the slave is still driving the SDA line low while you are trying to set it high and reading it? Does ā€˜i2c_sda_hi()ā€™ look at the SCL line to make sure itā€™s in the correct state before proceeding? Is there a delay to allow the SDA line changes time to propagate?

1 Like

@peekay123, was struggling for four hours yesterday on the task, slept on it, then fixed it in five minutes this morning!

There was an issue, and now there is not.... my mistake (confusing ACK status with device status byte) (must have) caused confusion in the device (NXP PN532) and hence my woes. So case closed!

That said, your comments have pricked up my ears (edits mine):

Q1. Does 'i2c_sda_hi()' look at the SCL line to make sure it's in the correct state before proceeding?
A1. Not in the routine itself. The read and write routines handle... see below [am now wondering about the i2c_write()]

Q2, Is there a delay to allow the SDA line changes time to propagate?
A2. Not in the routine itself. The read routine handles this... see below
.
This is what i2c_sda_hi() looks like (which is a port from the original SoftI2CMaster code):

// Setting SDA high and to input releases pin ie floating
// HR NOTE - not implementing PULLUP as is pulled up externally
#define i2c_sda_hi()           pinSetFast(_sdaPin);  pinMode(_sdaPin, INPUT);      

This is the complete i2c_writebit() - note my question

void SoftI2CMaster::i2c_writebit( uint8_t c )
{
    if ( c > 0 ) 
        i2c_sda_hi();
    else 
        i2c_sda_lo();
                             // <=== @peekay123,.no delay here - is this a prob?
    i2c_scl_hi();
    delayMicroseconds(i2cbitdelay);

    i2c_scl_lo();
    delayMicroseconds(i2cbitdelay);

    // finish with SDA LOW
    if ( c > 0 ) 
        i2c_sda_lo();

    delayMicroseconds(i2cbitdelay);
}

and here is the ic2_readbit() in its glory (which looks okay to me):

uint8_t SoftI2CMaster::i2c_readbit(void)
{
    i2c_sda_hi();
    i2c_scl_hi();
    delayMicroseconds(i2cbitdelay);

    uint8_t c = pinReadFast(_sdaPin);

    i2c_scl_lo();
    delayMicroseconds(i2cbitdelay);

    return(c);
}

I note that code is working, but anything that makes it more robust is a good thing...

1 Like

@UMD, I hope you will be posting that library to a repo and the web IDE! Good work :grinning:

Mate, I will send you the SoftI2CMaster source and get you to do it!! I am back in the dark ages in regard to this ā€œrepoā€ stuff!

Will get this off to you in a week or so once am happy with performance and removed the debug.

2 Likes