Friday, September 28, 2012

FT232R D2XX driver with MinGW

I recently got a FT232R USB breakout board for programming my universal remote control with a JP1 interface. It works great as a serial port, which is how the remote is programmed. This chip also has a bit bang mode, where one has direct access to the I/O lines on the chip. This is similar to the old parallel port. The idea I had was make an IR transmitter out of the FT232R chip. This is not a new idea. As I was debugging my code using the FT232R driver I found this. It's basically the same idea. They also implement a receiver. This is all for Linux, though, and they've only done the RC5 protocol. I guess my ideas are just four years too late. That's not going to stop me though. I've got parts on their way from Mouser right now. That will be a future post.

I wanted to do some programming with the bit bang mode so I set out to the Internet for some examples. The FTDI website has a lot of great resources for this chip. They provide tools for using the D2XX driver in your own code by giving you h, dll, and lib files for 32 and 64 bit programs. The documentation is pretty good, too. I also found a great tutorial on the Hack A Day website with code examples on how to use this mode and get you started.

I had to do a couple of things to get the Hack A Day code to compile using MinGW on Windows 7. The code relies on the ftd2xx.h header file. This is written to be used in Microsoft Visual Studio. That's fine and there's the free Express version available. But I really wanted it to work without Visual Studio. So I had to modify the h file so it could compile. Thanks to some Binging (that's just not as snappy as Googling, and the word is spelled like I went on some weird binge. Maybe Bing-ing? But hyphens... anyway), I was able to get it to work. The two primary modifications were, first, to add the following includes to ftd2xx.h:

#include <windef.h>
#include <winnt.h>
#include <winbase.h>

These have a lot of the missing typedef statements and other things that gcc needs to know about. If you compiled without these includes, you'd get errors saying it doesn't know what BOOL is and many others. I found a page which lists many of these data types and what h file they live in and their equivalent typedefs. I also had to change MinGW's winbase.h to include:

#include <stdarg.h>

Once those changes were made, the second thing I needed to do was to make sure gcc uses the dll file (this isn't 100% right as you'll soon see. I'll actually use the .lib file). I copied the dll to my project folder. I changed the project's linker settings so ftd2xx.dll was included as a library file in the gcc arguments. Once I did that, my make file did the following:

gcc.exe -c -g -MMD -MP -MF main.o.d -o main.o main.c

gcc.exe -o ft232r_test main.o ftd2xx.dll

Notice the final gcc execution includes ftd2xx.dll. Now when we call our executable, it will look for ftd2xx.dll in the system, which should exist if the driver was installed already. That happened for me automatically through Windows when I plugged the device in.

Here is where I went wrong and wasted a lot of time (but I came out stronger? okay, but I'll never get that time back). At this point I was getting some compile time errors I was getting about function names. I found this page when searching for help. That page explains how names in a dll vary based on the call type and which compiler was used. I naively made the assumption I just need to change the call type so the complier looks for the right function names. So in ftd2xx.h I replaced all "WINAPI" with "__cdecl" (but without the quotes, obviously). This compiles and may lead you to think it's working. IT DOES NOT. (Yeah. I know. You C/C++ geniuses already knew that. Can I use the excuse that this is my first dll experience?)

Like I said, this DOES NOT WORK (man that was a waste of time). It compiles without errors but fails at runtime. Running through the code with the debugger left me scratching my head. All sorts of weird things were happening like variables changing randomly from one line to the next. I thought it was weird that FT_Open worked right but nothing else did. I always got an FT_INVALID_HANDLE value returned for any other function call. I was suspicious of that __cdecl change I had made.

Come to find out, leaving the WINAPI alone was the right thing to do. The MSDN library pages on __cdecl and __stdcall (or WINAPI) explain the differences in how they handle argument passing and stack management. This explains why they are not interchangeable for a precompiled library. It also explains why things were changing randomly. I was seriously screwing up the stack by changing the way the functions were called. Once I changed it back, though, it wouldn't compile. That's what motivated the change to __cdecl in the first place. It couldn't find functions and I knew it had to do with WINAPI because of that page that lead me to try __cdecl. At this point I tried using the ftd2xx.lib file for my library file instead of the .dll file and it worked! (This is the .lib that lives in the same folder as the .dll file, the i386 folder and not the Static folder. These files and folders are in the zip file you download for the device driver from the FTDI website. I'm using the 32 bit files.) Now my final gcc line looks like this:

gcc.exe -o ft232r_test main.o ftd2xx.lib

It compiled and when I debugged it I actually got results that I was expecting. No weird errors and everything behaved normally (again, because I wasn't screwing up the stack anymore by using __cdecl instead of WINAPI or __stdcall). The FT functions all returned FT_OK, too. Awesome. Now I can move forward with actually using the driver. Sheesh.

The primary reason I want to use FTDI's driver and code over the open source libftdi is that the FTDI driver allows for bit bang mode or serial mode with the same driver and I'd like to switch between the two. Using the FTDI driver allows me to do that. Now you can't use both modes simultaneously but switching between the two is simple. Otherwise, if I want to switch from one mode to the other, I have to reinstall the device driver. No thanks.

So in summary, add three includes to ftd2xx.h, one include to winbase.h, and use the lib file in your linker settings. For my project, I had all ftd2xx files living in the project folder.

What's even more embarrassing is that if I just keep reading the Hack A Day post, the #include lines are right there. Doh! Maybe I should just spend more time reading instead of learning the hard way.

Maybe this was all really obvious to everyone else but it wasn't to me so maybe it'll help someone else, too.

A little off topic but relevant:
I'm trying out NetBeans as a C++ IDE. NetBeans 7.2 was no cake walk to set up. Their own instructions didn't work (as of the time of this writing). See the pdf attached at this forum post for how to set up NetBeans for C++ with MinGW so it actually works. The short version is that in the original directions, they say not to install MSYS with MinGW but to do it separately and that's not true. You should install it using the MinGW installer with MinGW. Read the pdf for all the details. I also found that using the repositories that came with the MinGW install file works and downloading newer ones at install time didn't.

So far I'm not particularly enamored with NetBeans but I'm not an experienced C/C++ coder, either (which is probably obvious from what I wrote here). The emacs keybindings aren't great. I'll keep trying it out.