As you could read in my previous stories there were a few issues using the FTDI in bit bang mode. The most important one was the minimum latency of 2ms to send a few bytes and converting them to I2C protocol. Now I took a totally different approach in using the FTDI chip as a usual USB to RS232 converter. I used the latest FTDI chip series FT2232H as I had the opportunity with this huge change. This chip has 4096 byte buffers on board and can have speeds of more than 4M bit a second. Because I use the chip pure as RS232 device I had to make the I2C with something else of course. And guess what , I took the STM32 to do this job. I have quit some experience now with this MCU that I challenged myself to interface it with the FTDI chip. So the USB2I2C bridge came finally to an end. I will use it as a gateway in my dimmer project ( see my other blogs ) On the picture you can see the final result. I will post the schematics later on and will tell a bit on speeds too.
Saturday, October 17, 2009
brand new approach
As you could read in my previous stories there were a few issues using the FTDI in bit bang mode. The most important one was the minimum latency of 2ms to send a few bytes and converting them to I2C protocol. Now I took a totally different approach in using the FTDI chip as a usual USB to RS232 converter. I used the latest FTDI chip series FT2232H as I had the opportunity with this huge change. This chip has 4096 byte buffers on board and can have speeds of more than 4M bit a second. Because I use the chip pure as RS232 device I had to make the I2C with something else of course. And guess what , I took the STM32 to do this job. I have quit some experience now with this MCU that I challenged myself to interface it with the FTDI chip. So the USB2I2C bridge came finally to an end. I will use it as a gateway in my dimmer project ( see my other blogs ) On the picture you can see the final result. I will post the schematics later on and will tell a bit on speeds too.
Thursday, October 9, 2008
multithreaded approach
- PollThread
- LogicThread
- MainThread
Friday, September 26, 2008
final results

On the picture you can see my test equipment for the FTDI. Three parts are involved:
1. Asus EEE PC as platform
2. FTDI interface board ( small one)
3. I2C I/O board ( big one )
The final test I did was based on the fact that I simulated the real domotic logic as close as possible to the final program logical blocks. I also included the fact of worse case timings. The best way to explain this is by showing you the piece of test code I wrote:
void TestWorstCaseTiming(void)
UCHAR in [5]; //I2C InBuf
UCHAR out [5]; //I2C OutBuf
UCHAR ad [1]; //Int
//*********INIT BLOCK
status = FTDI_I2C_STARTUP( _400KHZ );
//*********PREPARE IN AND INVERT
status = FTDI_I2C_PCA_READN_ALL( PCA9698_INPUT_READ , in );
BitSetExtended<40> InSet(in);// start with real values
InSet.flip();// invert push = 1
//*********PREPARE OUT ALL TO ZERO
BitSetExtended<40> OutSet(CmdOut);
//OutSet.none();
OutSet.ToByteBuf(CmdOut);
status = FTDI_I2C_PCA_WRITE_ALL( PCA9698_OUTPUT_WRITE , CmdOut );//16ms
//*********MAIN LOOP
while (1)
{
//*********POLL LOOP
status = FTDI_2XX_READN_ADBUS(ad);
while ( (ad[0] & INT_MASK_FLAG) != 0 )
{
//overwrite time to have total chain time at the end
GetSystemTime(&begin);
status = FTDI_2XX_READN_ADBUS(ad);//2ms
}
//*********DOMOTICS LOGIC READ
status = FTDI_I2C_PCA_READN_ALL( PCA9698_INPUT_READ , in );//22ms
//*********DOMOTICS LOGIC COMPARE AND MODIFY
BitSetExtended<40> InNew(in);
InNew.flip();
for ( int input = 0 ; input <>
{
if ( (InSet[input] != InNew[input]) && (InNew.test(input)) )
{
OutSet.flip(input);
}
}
//*********DOMOTICS LOGIC UPDATE LAST IN
InSet = InNew;
//*********DOMOTICS LOGIC PREPARE AND OUT
OutSet.ToByteBuf(CmdOut);
status = FTDI_I2C_PCA_WRITE_ALL( PCA9698_OUTPUT_WRITE , CmdOut );//16ms
status = FTDI_I2C_DAC_SET(0,0);//10ms
//*********CHECK TIME
//Sleep(30); simulate cpu charge
GetSystemTime(&end);
printf ("cpu time : %d ms\n",end.wMilliseconds-begin.wMilliseconds);
// should be ca 50ms + Xms logic
}
}
The program flow is very simple. I poll on interrupt flag change in the inner while loop. As soon this becomes signals I leave the loop to read in the values of the PCA-IN. I check upon which bits are changed and also if this bit equal to 1. Due to the fact I use the inputs as button device I'm not interested if this bit is zero because this indicates the released state. I'm only act upon state pushed here. I invert the PCA-OUT accordingly to have a toggle behaviour on the leds. I didn't use a complex mapping for the in and out simply in-bit[0] relates to out-bit[0] etc for all 40 of them. Finally I write the new output state to the I/O board. I also added the DAC write just for timing simulating purposes but has no further meaning in this test. As you can see in the code I use the BitSetExtended<40> InNew(in) class. This one I wrote to ease the work with atomic bits. It's an extended (derived) STD class of BitSet:
template
{
typedef __int64 _Ty;
public:
BitSetExtended ( );
BitSetExtended ( __int64 ll );
BitSetExtended ( PUCHAR pC );
void ToByteBuf ( PUCHAR pC);
private:
enum { _Nb = CHAR_BIT * sizeof (_Ty),_Nw = _N == 0 ? 0 : (_N - 1) / _Nb };
void _Treat64( __int64 _X);
void _Tidy(_Ty _X = 0);
void _Trim( void );
_Ty _A[_Nw + 1];
};
Conclusion:
I find this a very good result even with quit slow communication of the FTDI USB device. In this test I wrote the code chained one block after the other. I didn't use any optimizations on the code flow and avoided working with multiple threads. I can think of a couple changes in terms of logic and threading which can optimize the program a little bit eg seperate the poll code into one thread using events and syncronizations on the FTDI object. Perhaps writing the read-in bits to a queue system and trait the changes in an other thread. I will look into this possibilties soon and report in my blog as usually do. But I must say I'm one step closer to my decision for taking this approach as my future domotic server.
Sunday, September 21, 2008
timings
- Init
- Read ADBUS interrupt line
- Read byte(s) from PCA
- Write bytes(s) to PCA
Basically the init function put the PCA in the correct mode and initialize the FTDI chip properly regarding the speed. I used 1Mhz speed as the PCA can handle this fast I2C speed but this will not be a bottleneck as we'll see. To initialize it took 350ms. This sounds big and actually it's but this is only done ones at the start of the program and is not that important.
Than I started to poll on the INT line :
read int time : 1 ms
status : ef
read int time : 3 ms
status : ef
read int time : 1 ms
status : ef
read int time : 3 ms
status : ef
As you can see an average of 2ms. This was after I changed the LatencyTime parameter of the FTDI from 16ms to 2ms which is the minimum. If you don't do that the fastest result you get is 16ms ! I continued with the read of 5 consequent bytes from the PCA and I got ca 22ms. This is a bit disappointed. I did the same writing 5 bytes and this was a bit better ca 16ms. To deduce the overhead per byte I wrote a command to read/write just one byte:
read pca time : 9 ms
status : ff
read pca time : 9 ms
status : ff
write pca time : 8 ms
write pca time : 8 ms
So the fasted time in read or write to an external I2C device using the USB FTDI chip is ca 10ms. So this had a 2ms overhead for any extra byte to read or send. I see a few reasons for these poor performance results:
1. The USB overhead for small data packets
2. Minimum FTDI latency is 2ms
3. High level MPSSE command structure (atomic)
I checked the DLL code and most of the functions were well written and can not be optimized. However in the read cycle this can be optimized a little bit but not worth to try and see much differences I think.
Although the performance is not that fantastic, this design let you interface a PC platform (linux,windows,CE,mac) with any customized external board that uses a I2C bus as communication. And it's a very cheap and powerful solution. You can make your programs on your favourite platform without using embedded micro controllers. Use the power of that platform to interact with other high level software components without having the limitations of embedded boards. So anyone that have to work with some extended I2C based device can make use of this chip. Be ware this only counts for the latest series of FTDI eg 2232C or D. One of the drawbacks I see is that to serve systems running 24/7 using this solution, you have to let a PC running too and that's may be a kind of overkill. In that case it's better to have a small embedded system with very low power consumption or at least a PC platform without sensitive mechanical parts like fans or hard drives.
So the question still remains for me if I'm going to use this as my domotic server system or not. I keep you posted on in this blog. In my next I'll present the overall setup with a nice picture of the components I used.
Friday, September 19, 2008
FTCI2C DLL functionalities
Mainly two possible ways to work with this device. You can pick the VCP (virtual com port) or the D2XX (low level design and special modes). In my case I downloaded the D2XX package. I took the windows based package but also linux , windows CE and even MAC are possible. Resources are available on the following site:
http://www.ftdichip.com/Drivers/D2XX.htm
The FTDI software comes with a zip file archive with following components:
- ftdi driver
- ftd2xx.dll
- ftd2xx.lib
- ftd2xx.h
These are the minimum components you need to start making your programs using visual studio. If you choise to interface an I2C bus peripheral than you must download an ectra development package to be able to work easier with the internal command structure of the FT2232. This can be found :
http://www.ftdichip.com/Projects/MPSSE/FTCI2C.htm
Components included are :
- ftci2c.h
- ftci2c.dll
- ftci2c.lib
Basically you can do pretty much with the default API functions the DLL exports and are quit simple to use. Here is the list of APIs:
FTCI2C_API
FTC_STATUS WINAPI I2C_GetNumDevices(LPDWORD lpdwNumDevices);
FTCI2C_API
FTC_STATUS WINAPI I2C_GetDeviceNameLocID(DWORD dwDeviceNameIndex, LPSTR lpDeviceNameBuffer, DWORD dwBufferSize, LPDWORD lpdwLocationID);
FTCI2C_API
FTC_STATUS WINAPI I2C_OpenEx(LPSTR lpDeviceName, DWORD dwLocationID, FTC_HANDLE *pftHandle);
FTCI2C_API
FTC_STATUS WINAPI I2C_Open(FTC_HANDLE *pftHandle);
FTCI2C_API
FTC_STATUS WINAPI I2C_Close(FTC_HANDLE ftHandle);
FTCI2C_API
FTC_STATUS WINAPI I2C_InitDevice(FTC_HANDLE ftHandle, DWORD dwClockDivisor);
FTCI2C_API
FTC_STATUS WINAPI I2C_GetClock(DWORD dwClockDivisor, LPDWORD lpdwClockFrequencyHz);
FTCI2C_API
FTC_STATUS WINAPI I2C_SetClock(FTC_HANDLE ftHandle, DWORD dwClockDivisor, LPDWORD lpdwClockFrequencyHz);
FTCI2C_API
FTC_STATUS WINAPI I2C_SetLoopback(FTC_HANDLE ftHandle, BOOL bLoopbackState);
FTCI2C_API
FTC_STATUS WINAPI I2C_SetMode(FTC_HANDLE ftHandle, DWORD dwCommsMode);
FTCI2C_API
FTC_STATUS WINAPI I2C_Write(FTC_HANDLE ftHandle, PWriteControlByteBuffer pWriteControlBuffer,
DWORD dwNumControlBytesToWrite, BOOL bControlAcknowledge, DWORD dwControlAckTimeoutmSecs,
BOOL bStopCondition, DWORD dwDataWriteTypes, PWriteDataByteBuffer pWriteDataBuffer, DWORD NumDataBytesToWrite,
BOOL bDataAcknowledge, DWORD dwDataAckTimeoutmSecs, PFTC_PAGE_WRITE_DATA pPageWriteData);
FTCI2C_API
FTC_STATUS WINAPI I2C_Read(FTC_HANDLE ftHandle, PWriteControlByteBuffer pWriteControlBuffer,
DWORD dwNumControlBytesToWrite, BOOL bControlAcknowledge, DWORD dwControlAckTimeoutmSecs,
DWORD dwDataReadTypes, PReadDataByteBuffer pReadDataBuffer, DWORD dwNumDataBytesToRead);
FTCI2C_API
FTC_STATUS WINAPI I2C_GetDllVersion(LPSTR lpDllVersionBuffer, DWORD dwBufferSize);
FTCI2C_API
FTC_STATUS WINAPI I2C_GetErrorCodeString(LPSTR lpLanguage, FTC_STATUS StatusCode,
LPSTR lpErrorMessageBuffer, DWORD dwBufferSize);
As you can see quit a complete list of functions that you can call almost directly. The principe is based on dividing the i2c commands into a ControlBuffer and a DataBuffer. This is done to simplify the internal calls to the FTDI2232 command set available. Here is an example out of my library I write to prepare a command for the PCA device:
FTDI_API FTC_STATUS FTDI_I2C_PCA_WRITE_ALL ( UCHAR adr , PCHAR buf )
{
FTC_STATUS stat;
PageWriteData.dwNumBytesPerPage = PCA9698_MAX_DATA;
PageWriteData.dwNumPages = PCA9698_MAX_PAGE;
WriteControlBuffer[0] = adr;
WriteControlBuffer[1] = PCA9698_WRITE_ALL;
memcpy ( WriteDataBuffer , buf , PCA9698_MAX_DATA );
stat = I2C_Write( hI2C,
&WriteControlBuffer,
PCA9698_CONTROL_BYTES,
FTDI_WANT_ACK,
FTDI_TIME_OUT,
FTDI_WANT_ACK,
PAGE_WRITE_TYPE,
&WriteDataBuffer,
PCA9698_MAX_DATA,
FTDI_WANT_ACK,
FTDI_TIME_OUT,
&PageWriteData
);I2C_RETURN_ERROR;
I2C_RETURN_SUCCES;
}
So in fact you can organize your calls in seperate functions library for readability or you can of course also call it directly within your main logic. Due to the fact I modified the FTDI DLL code I better add my functions directly in the code of the DLL. This has an extra advantage in using only one single device handle. If you need the GPIO calls together with I2C you need to call functions out of the basic ftd2xx.dll and you need to open up an other handle. This means that if you work like that you simply can't let open two handles in your program. I show you the lines that I changed to be able to access the ACBUS:
In function FT2232cMpsseI2c::InitDataInOutClockFrequency I commented out these lines:
// outputs on GPIO21-24
// FTC_AddByteToOutputBuffer(SET_HIGH_BYTE_DATA_BITS_CMD, false);
// FTC_AddByteToOutputBuffer('\x0F', false);
// FTC_AddByteToOutputBuffer('\x0F', false);
If you don't do that every time the device gets initialized the ACBUS is set to output and the 4 lines become high. Another thing I did was changing the WriteProtectEnable flag. This flag is related to pin 4 of the ADBUS and not documented in the specifications. It's not quit clear to me why they use it but I guess to make the internal command calls save for multihreading.
In function FT2232cMpsseI2c::ReadDataFromExternalDevice I modified the flag to false :
Status = WriteAddressExternalDevice(ftHandle, (*pWriteControlBuffer)[0], ControlAckType, dwControlAckTimeoutmSecs, false);
I needed to that because my PCA OE was connected to pin 4. After these changes the OE was working properly and the ACBUS was available for input as well as for output whitout any changes when calling the InitDevice function.
I went not into great details regarding the code and of course I assume that you know how to build a DLL and use third party DLLs from within visual studio. The purpose of this post is just showing you the fact that FDTI device can be put into other modes than the widly used VCP mode. Next I will tell you bit more on the results of my tests. For the ones interested in reading some hardware specifications
http://www.ftdichip.com/Documents/DataSheets.htm
I can't attach the DLL source code here in this google blog which is a pitty but if you're interested just give me a reaction on this post.