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.pdfSection 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:
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:
// 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 WorkaroundThe 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.
CalculatingThe 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:"
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:
#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:
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!