FTDI Community

Please login or register.

Login with username, password and session length.
Advanced Search  

News:

Welcome to the FTDI Community!

Please read our Welcome Note

Technical Support enquires
please contact the team
@ FTDI Support


New Bridgetek Community is now open

Please note that we have created the Bridgetek Community to discuss all Bridgetek products e.g. EVE, MCU.

Please follow this link and create a new user account to get started.

Bridgetek Community

Author Topic: DELAY needed after FT4222_I2CMaster_Write() before FT4222_I2CMaster_GetStatus()  (Read 8089 times)

allenhuffman

  • Newbie
  • *
  • Posts: 49
  • Mostly harmless.
    • View Profile
    • Sub-Etha Software

As has been reported elsewhere in this forum...

https://www.ftdicommunity.com/index.php?topic=822

...problems can happen if you try to use FT4222_I2CMaster_GetStatus() after a Write operation if the Write has not completed.

See: https://ftdichip.com/wp-content/uploads/2022/03/TN_161_FT4222H-Errata-Technical-Note.pdf
Section 3.4.2

FT4222_I2CMaster_GetStatus() is used to determine if the I2C write was ACK'd by a device. But, you are told to only use it AFTER transmisson is complete:

Quote
Read the status of the I2C master controller. This can be used to poll a slave after i2c transmission is complete.

One of the GetStatus bits is "BUS BUSY" and it seems you could have used it to determine when a write was complete, but due to the problem in the FTDI chip, you cannot. I suspect something like this was the intent:

Code: [Select]
// This WILL NOT WORK. See:
//
// TTN_161 FT422H Errate Technical Note
// Version 1.5
// Document Reference No.: FT_001198
//
// Section 3.4.2
// 'I2C data is corrupt when FT4222_I2CMaster_GetStatus" is being called'
//

// Write message.
ft4222Status = FT4222_I2CMaster_Write(ftHandle, deviceAddress,
&msg[0], sizeof(msg), &sizeTransferred);

if (ft4222Status == FT4222_OK)
{
// Wait for it to be sent so we can check status.
unsigned char controllerStatus;
NAKSeen = false;

// Deadlock prevention. Try up to 10 times...
for (int tries = 0; tries < 10; tries++)
{
ft4222Status = FT4222_I2CMaster_GetStatus (ftHandle,
&controllerStatus);

if (ft4222Status == FT4222_OK) // We have a status.
{
// If CONTROLLER BUSY, no status bits are valid.
if (I2CM_CONTROLLER_BUSY(controllerStatus)==1)
{
// controller busy: all other status bits invalid
continue;
}

// If here, status bits are valid.

// If BUS BUSY, we are still writing.
if (I2CM_BUS_BUSY(controllerStatus))
{
continue;
}

// Check to see if ADDR was ACK'd.
if (I2CM_ADDRESS_NACK(controllerStatus))
{
NAKSeen = true;
}
// If here, we were NACK'd or ACK'd.
break;
}
else // ft4222Status NOT OK
{
// Considered.
}
} // end of for.

if (NAKSeen == true)
{
printf ("NAK\n");

// Handle...
}
else // ACK'd
{
// Write was okay.
printf ("Wrote %u bytes.\n", sizeTransferred);

// Handle...

} // end of if (NAKSeen == true) else

} // end of if (ft4222Status == FT4222_OK)

If FTDI releases a fixed chip in the future, that code could be enhanced to check for other error conditions as well.

Currently, if you want to use it to see if your write was received (ACK), you have to wait until transmission is complete then use GetStatus. For casual hobby projects, it may be fine to just blindly write to non-existent (or locked up) devices, but if the project is important knowing is a device is present (and has received the message) may be required.

Since FT4222_I2CMaster_Write() is non-blocking, the call returns immediately, and the write is handled in the background. This means if you do a Write and follow it with a GetStatus, problems may occur. The above-linked post demonstrates data corruption as one such problem.

I am not sure what happens if you try doing a Write while a previous Write is still in progress, but I *assume* it may also be bad. It is unfortunate this defect exists.

FTDI Workaround

The FTDI response is to wait before calling GetStatus, so I thought I'd provide details in one post (which I will edit with corrections/updates if needed) for others who have run in to this.

The delay can be calculated by knowing the baud rate -- the kbps used in FT4222_I2CMaster_Init() -- and the number of bytes written.

Keep in mind that each byte has an ACK/NAK bit added, so calculate 9 bits per byte rather than 8.

Also, if you look at the output of the I2C write under a logic analyzer (such as a Saleae), you will see there are also gaps between bytes (see attached screenshot). These gaps are not consistent. In my example, I see sending a byte took about 10 uS, with a gap afterwards that ranged from 7 uS to more than 60 uS.

To use the following workaround, you need to use a worst-case "gap time" and add it to the calculation. I am unsure. I am going to *assume* that this gap time can vary -- on a busy Windows machine I expect they could be much larger in time.

Calculating

The FTDI part can operate from 60 kbps to 3400 kbps. Since the Sleep resolution may be limited to milliseconds, a Sleep(1) is the minimal time. This may be long enough to cover writing 128 bytes of I2C data at high baud rates (1000 or faster). But, at slower speeds, you will see the calculated write time needs to be much longer. Just doing a Sleep(1) is not enough if you plan to use a lower baud rate.

The wait time formula is basically something like:"

Code: [Select]
ms = (9 * (1000/(kbps)) * (sizeTransferred+1)) / 1000;
The 9 is for the 9 clock pulses to send a byte (8 pulses for the 8 bits, plus 1 pulse for ack/nak).

The sizeTransferred (bytes sent) has 1 added to it to round up.

Unfortunately, this does not take in to consideration the gaps between bytes.  If I knew that it took 10 uS to send a byte, and then there was a 7 uS gap after, I could tweak this time by adding extra bits. Say, 7 bits of overhead (worth of time):  "(9+7) * ..."

BUT, since I see that sometimes the gap might be closer to 70 uS (7 times as long as it took to send the byte), I might just want to use "9" there, and multiply the "sizeTransferred" by 7. Worst case.

This produces much longer waits than are needing, slowing down the I2C system considerably. But, if you really need to use GetStatus, this may be the only option.

Here is a C program to print out an example table. You can see the formula I was using added 6 extra bits, but I have since found this is not long enough:

Code: [Select]
#include <stdio.h>  // for printf
#include <stdint.h> // for uint16_t
#include <stdlib.h> // for EXIT_SUCCESS

// Table of I2C baud rates.
uint16_t kbps[] = { 60, 100, 300, 500, 1000, 3400 };

int main()
{
    // Print header.
    printf ("bytes  ");

    for (int idx=0; idx<sizeof(kbps)/sizeof(kbps[0]); idx++)
    {
        printf ("%4u  ", kbps[idx]);
    }
    printf ("\n");

    // Print table entries.
    for (uint16_t sizeTransferred=0; sizeTransferred<=128; sizeTransferred++)
    {
        printf ("%5u  ", sizeTransferred); // Bytes written.

        for (int speed=0; speed<sizeof(kbps)/sizeof(kbps[0]); speed++)
        {
            // "Since there is a separate byte for the address, and since
    // there are 9 pulses for each byte (ack/nak bit), a formula
    // probably needs to take that in to consideration."
    //
    // "When looking at the FTDI Master (Windows 11) in a Saleae capture,
    // I also see gaps between each byte that range from 10us to 14us, so
    // I added some worst-case extra bits (6 for my case) to that to cover
    // it:"
    //
    //             (bits ) * (         uS       ) * (      bytes      )
        uint32_t ms = ((8+1+6) * (1000/(kbps[speed])) * (sizeTransferred+1)) / 1000;
        if (ms < 1)
        {
        ms = 1;
        }

            printf ("%4u  ", ms);
        }
        printf ("\n");
    }

    return EXIT_SUCCESS;
}

And here is the output:

Code: [Select]
bytes    60   100   300   500  1000  3400
    0     1     1     1     1     1     1
    1     1     1     1     1     1     1
    2     1     1     1     1     1     1
    3     1     1     1     1     1     1
    4     1     1     1     1     1     1
    5     1     1     1     1     1     1
    6     1     1     1     1     1     1
    7     1     1     1     1     1     1
    8     2     1     1     1     1     1
    9     2     1     1     1     1     1
   10     2     1     1     1     1     1
   11     2     1     1     1     1     1
   12     3     1     1     1     1     1
   13     3     2     1     1     1     1
   14     3     2     1     1     1     1
   15     3     2     1     1     1     1
   16     4     2     1     1     1     1
   17     4     2     1     1     1     1
   18     4     2     1     1     1     1
   19     4     3     1     1     1     1
   20     5     3     1     1     1     1
   21     5     3     1     1     1     1
   22     5     3     1     1     1     1
   23     5     3     1     1     1     1
   24     6     3     1     1     1     1
   25     6     3     1     1     1     1
   26     6     4     1     1     1     1
   27     6     4     1     1     1     1
   28     6     4     1     1     1     1
   29     7     4     1     1     1     1
   30     7     4     1     1     1     1
   31     7     4     1     1     1     1
   32     7     4     1     1     1     1
   33     8     5     1     1     1     1
   34     8     5     1     1     1     1
   35     8     5     1     1     1     1
   36     8     5     1     1     1     1
   37     9     5     1     1     1     1
   38     9     5     1     1     1     1
   39     9     6     1     1     1     1
   40     9     6     1     1     1     1
   41    10     6     1     1     1     1
   42    10     6     1     1     1     1
   43    10     6     1     1     1     1
   44    10     6     2     1     1     1
   45    11     6     2     1     1     1
   46    11     7     2     1     1     1
   47    11     7     2     1     1     1
   48    11     7     2     1     1     1
   49    12     7     2     1     1     1
   50    12     7     2     1     1     1
   51    12     7     2     1     1     1
   52    12     7     2     1     1     1
   53    12     8     2     1     1     1
   54    13     8     2     1     1     1
   55    13     8     2     1     1     1
   56    13     8     2     1     1     1
   57    13     8     2     1     1     1
   58    14     8     2     1     1     1
   59    14     9     2     1     1     1
   60    14     9     2     1     1     1
   61    14     9     2     1     1     1
   62    15     9     2     1     1     1
   63    15     9     2     1     1     1
   64    15     9     2     1     1     1
   65    15     9     2     1     1     1
   66    16    10     3     2     1     1
   67    16    10     3     2     1     1
   68    16    10     3     2     1     1
   69    16    10     3     2     1     1
   70    17    10     3     2     1     1
   71    17    10     3     2     1     1
   72    17    10     3     2     1     1
   73    17    11     3     2     1     1
   74    18    11     3     2     1     1
   75    18    11     3     2     1     1
   76    18    11     3     2     1     1
   77    18    11     3     2     1     1
   78    18    11     3     2     1     1
   79    19    12     3     2     1     1
   80    19    12     3     2     1     1
   81    19    12     3     2     1     1
   82    19    12     3     2     1     1
   83    20    12     3     2     1     1
   84    20    12     3     2     1     1
   85    20    12     3     2     1     1
   86    20    13     3     2     1     1
   87    21    13     3     2     1     1
   88    21    13     4     2     1     1
   89    21    13     4     2     1     1
   90    21    13     4     2     1     1
   91    22    13     4     2     1     1
   92    22    13     4     2     1     1
   93    22    14     4     2     1     1
   94    22    14     4     2     1     1
   95    23    14     4     2     1     1
   96    23    14     4     2     1     1
   97    23    14     4     2     1     1
   98    23    14     4     2     1     1
   99    24    15     4     3     1     1
  100    24    15     4     3     1     1
  101    24    15     4     3     1     1
  102    24    15     4     3     1     1
  103    24    15     4     3     1     1
  104    25    15     4     3     1     1
  105    25    15     4     3     1     1
  106    25    16     4     3     1     1
  107    25    16     4     3     1     1
  108    26    16     4     3     1     1
  109    26    16     4     3     1     1
  110    26    16     4     3     1     1
  111    26    16     5     3     1     1
  112    27    16     5     3     1     1
  113    27    17     5     3     1     1
  114    27    17     5     3     1     1
  115    27    17     5     3     1     1
  116    28    17     5     3     1     1
  117    28    17     5     3     1     1
  118    28    17     5     3     1     1
  119    28    18     5     3     1     1
  120    29    18     5     3     1     1
  121    29    18     5     3     1     1
  122    29    18     5     3     1     1
  123    29    18     5     3     1     1
  124    30    18     5     3     1     1
  125    30    18     5     3     1     1
  126    30    19     5     3     1     1
  127    30    19     5     3     1     1
  128    30    19     5     3     1     1

You will see that at the lowest baud rate, writing out 128 bytes is calculated to take around 30 ms. If you actually test this in a logic analyzer, you will see it's not very accurate. See my second screen shot and you will see that when writing 128 bytes, the gaps between the bytes vary - some are 7 uS, some are 13 uS, some are 51 uS (!), even 63 uS. There is overhead on the Windows system and other things are happening.

My table claims that 128 bytes at 1000 kbps should need a 1ms delay. BUT, when I actually look at the time it took, it was OVER 3ms. I was off by a factor of 3! Using my formula to calculate wait time could have led to reading corrupt data (per the poster in the other topic).

Because of this, the formula I was using will not work. As far as I know, currently, you just have to make sure you always wait a worst-worst case amount of time before trying to use GetStatus. And since I don't know how fast the PC will be that runs my I2C code, whatever timing I come up with may not be long enough.

That's the problem.

Ever since we tried to implement ACK/NAK detection (probing for devices), we've had occasional problems, and it seems this is the culprit.

I hope this information helps others who run in to this. What we really need is a way to determine if a Write is busy, but at least when I asked last year, the only option was "Sleep". But...how long?

Cheers!

« Last Edit: April 11, 2023, 09:46:33 PM by allenhuffman »
Logged