As some of may know, with the advent of Silverlight 5 RC a new feature was introduced: P/Invoke or Platform Invoke. This provides the ability to interact with system libraries or with any other native DLL. This opens a whole new range of exciting new possibilities!
For one of my clients I wrote an interactive dashboard for an ignition system for classic cars, that uses the FTDI FT232R USB-to-Virtual-Serial-Port chip to exchange data back and forth. See the picture below for the breadboard with a prototype ignition on top with the micro USB connected to the FTDI chip. The switches simulate sensor input such as engine RPM, temp, selected ignition curve etc. Other than you may think, the seringe is for simulating vacuum pressure in the inlet manifold
. Now wouldn’t it be cool if we could connect to it from a browser using Silverlight 5 RC?
Now, in this monster post, I’ll explain the following:
- Porting the wrapper library
- Creating the application
- Listing devices
- Write to a device
- Read / write the EEPROM
- Wrapup and source code
Porting the wrapper library
The first step then off course is to write a wrapper to the native DLL that the driver exposes. This DLL is called FTD2XX.dll and is installed in the Windows directory when the driver is installed. Since this is on the system path, we should not have any problems accessing it from a wrapper. Luckily, FTDI provides a head start with a wrapper library, that can be found on the site in the Code Examples section.
However, this is written for .NET 2.0 so we need to port it. With FTDI’s permission, I used Reflector to get the code and tried to compile it. First issue was this statement in the constructor:
MessageBox.Show("Failed to load FTD2XX.DLL. Are the FTDI drivers installed?");
Well, why one would reference a GUI component (and WinForms on top of that) in a class library is beyond me, but it was easy to fix by just throwing an Exception. Find / Replace all.
ASCII
Next, there was a method to list the devices that are currently connected to the system, and that uses ASCII encoding to read the serial number and description of the device:
Encoding.ASCII.GetString(serialnumber, 0, serialnumber.Length);
Silverlight does not have an ASCII encoding, and I’m stuck with a shortcut solution for this, so for now, I just replaced all these occurrences with UTF8 (I’m open for suggestions!):
#if SILVERLIGHT devices[i].SerialNumber = Encoding.UTF8.GetString(serialnumber, 0, serialnumber.Length); #else devices[i].SerialNumber = Encoding.ASCII.GetString(serialnumber, 0, serialnumber.Length); #endif
AllocHGlobal, StringToHGlobalAnsi
Now, on to the interesting bits. The DLL also exposes functionality to read/write the EEPROM of the device, to set various default settings in the driver and provide your custom Manufacturer and SerialNumber ID’s. We use a custom Manufacturer and Description to distinguish our own devices that a connected to a customer PC. To read the EEPROM, the wrapper classes uses a class FT_PROGRAM_DATA and StructLayout to convert a struct from unmanaged memory to a managed class. In it are a number of string conversions:
pData.Manufacturer = Marshal.AllocHGlobal(0x20); .. ee2232.Manufacturer = Marshal.PtrToStringAnsi(pData.Manufacturer); .. Marshal.FreeHGlobal(pData.Manufacturer);
However, AllocHGlobal and FreeHGlobal do not exist in the Silverlight Marshal class. Well, they are basically only wrappers around other methods, so we could replicate that. Using Reflector on the mscorlib.dll of .NET 4, I created a MarshalHelper class that implements these methods, with some small adjustments. In this class I reference the methods LocalAlloc_NoSafeHandle, LocalFree and CopyMemoryAnsi from kernel32.dll and provided implementations for AllocHGlobal, FreeHGlobal and StringToHGlobalAnsi. Using this class, the code becomes something like this:
#if SILVERLIGHT pData.Manufacturer = MarshalHelper.AllocHGlobal(0x20); .. ee2232.Manufacturer = MarshalHelper.PtrToStringAnsi(pData.Manufacturer); .. MarshalHelper.FreeHGlobal(pData.Manufacturer); #else pData.Manufacturer = Marshal.AllocHGlobal(0x20); .. ee2232.Manufacturer = Marshal.PtrToStringAnsi(pData.Manufacturer); .. Marshal.FreeHGlobal(pData.Manufacturer); #endif
As an example, AllocHGlobal replacement now looks like:
[DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
internal static extern IntPtr LocalAlloc_NoSafeHandle(int uFlags, IntPtr sizetdwBytes);
public static IntPtr AllocHGlobal(IntPtr cb)
{
IntPtr num = LocalAlloc_NoSafeHandle(0, cb);
if (num == IntPtr.Zero)
throw new OutOfMemoryException();
return num;
}
SafeWaitHandle
Another functionality the wrapper provides is called SetEventNotification. It uses an EventWaitHandle to expose new data events. This provides an excellent mechanism to receive small chunks of new data from the device with a small latency. No need to setup a seperate thread! However, here I encountered my first real bummer. The function delegate needs SafeWaitHandle, a property on the WaitHandle class. But in Silverlight 5 RC, this property is sadly declared internal so we cannot access it. Any workaround tips would be greatly appreciated!
Creating the application
Now that we have an almost complete working wrapper library, it is time to demonstrate some of its features in a sample application: listing devices, connect to a device, read / write data and read / write the EEPROM. See the screenshot for a basic application showing these functionalities. It consists only of a MainPage.xaml and a convenience Device class I created to combine some of the information that is spread over several classes in the wrapper library. Note that this does not use MVVM to keeps things simple for demonstration purposes. All code shown below is in the code behind of the MainPage.
Listing devices
To fill the list of devices, an ObservableCollection is created of Devices, a Ftdi object and a connected device. Once the layoutroot is loaded, the Ftdi object is created (the constructor loads the FTD2XX.dll) and calls the GetDevices method. The ObservableCollection is set as ItemsSource on the device list.
private readonly ObservableCollection<Device> _devices = new ObservableCollection<Device>();
private Device _connectedDevice;
private FTDI _ftdi;
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
lbDevices.ItemsSource = _devices;
Ftdi = new FTDI();
GetDevices();
}
The GetDevices method first reads the number of available devices, initializes a array of items and fills the array with devices.
private void GetDevices()
{
UInt32 deviceCount = 0;
var status = _ftdi.GetNumberOfDevices(ref deviceCount);
if (deviceCount > 0 && status == FTDI.FT_STATUS.FT_OK)
{
var deviceList = new FTDI.FT_DEVICE_INFO_NODE[deviceCount];
try
{
status = _ftdi.GetDeviceList(deviceList);
}
catch (FTDI.FT_EXCEPTION exception)
{
Debug.WriteLine("DeviceConnection.UpdateDevices: ERROR getting device list " + exception);
return;
}
if (status == FTDI.FT_STATUS.FT_OK)
{
foreach (var device in deviceList.Where(device => device != null))
{
Debug.WriteLine("Device : " + device.SerialNumber);
_devices.Add(new Device {Info = device});
}
}
}
}
Connect to a device
Once the user selects a device and clicks the connect button, the connect method is called. This method opens the device (using the serial number as an unique reference) and sets a number of settings for the connection. For a serial port this means at least setting the baud rate and data characteristics, just as when you would use a SerialPort from .NET to open the device. Now the very neat thing about using the FTDI wrapper (instead of the SerialPort in .NET which many people also use and is frankly totally borked with USB converters, see here and here), is that it exposes all kinds of interesting extra options. For example, the latency timing can be set, which can have a real impact depending on how you use the device. In our case, the device echoes every character inputted and if we want to have a fast “turnaround” time we should set the latency time low, if we want better performance with large amounts of data (buffering) we would set it high.
private bool Connect(Device device)
{
var status = _ftdi.OpenBySerialNumber(device.Info.SerialNumber);
if (status != FTDI.FT_STATUS.FT_OK)
return false;
// read COM port
string comPort;
var comPortStatus = _ftdi.GetCOMPort(out comPort);
device.COMPort = comPort;
// set connection settings
int baudRate = 19200;
byte latency = 16;
var baudStatus = _ftdi.SetBaudRate((uint) baudRate);
var dataStatus = _ftdi.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8,
FTDI.FT_STOP_BITS.FT_STOP_BITS_1,
FTDI.FT_PARITY.FT_PARITY_NONE);
var timeOutsStatus = _ftdi.SetTimeouts(0, 0);
var resetRetryStatus = _ftdi.SetResetPipeRetryCount(10);
var latencyStatus = _ftdi.SetLatency(latency);
if (comPortStatus != FTDI.FT_STATUS.FT_OK || baudStatus != FTDI.FT_STATUS.FT_OK ||
dataStatus != FTDI.FT_STATUS.FT_OK || timeOutsStatus != FTDI.FT_STATUS.FT_OK ||
resetRetryStatus != FTDI.FT_STATUS.FT_OK || latencyStatus != FTDI.FT_STATUS.FT_OK)
{
return false;
}
return true;
}
Read from a device
As said, because of the internal declared SafeWaitHandle property of WaitHandle, we cannot use the driver event notification to read the data. Instead, I have just created a dumb reader thread that checks if data is available every 100ms.
private void StartReadingData()
{
// use dumm read thread until we have a proper solution to use EventNotification
var readThread = new Thread(ReadData);
readThread.Start();
}
Reading data from a FTDI device is done by checking the number of available bytes, create an array and read the data into a byte array. For this example we convert the byte array to a string and call the AddData method which updates the ui.
private void ReadData()
{
UInt32 nrOfBytesAvailable = 0;
while (true)
{
var readStatus = _ftdi.GetRxBytesAvailable(ref nrOfBytesAvailable);
if (readStatus != FTDI.FT_STATUS.FT_OK)
{
Thread.Sleep(100);
continue;
}
if (nrOfBytesAvailable > 0)
{
byte[] readData = new byte[nrOfBytesAvailable];
UInt32 numBytesRead = 0;
readStatus = _ftdi.Read(readData, nrOfBytesAvailable, ref numBytesRead);
string data = Encoding.UTF8.GetString(readData, 0, readData.Length);
AddData(data);
}
Thread.Sleep(100);
}
}
Because we are accessing the UI thread, we need to use the Dispatcher to add the data to the data textbox, and tell the scrollviewer to automatically scroll to the end.
private void AddData(string data)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
tbData.Text += data;
svData.UpdateLayout();
svData.ScrollToVerticalOffset(double.MaxValue);
});
}
Write to a device
Writing data is very similar to reading, just get the number of bytes you want to write and call the Write method in the wrapper.
private void WriteData(string data)
{
UInt32 numBytesWritten = 0;
byte[] bytes = Encoding.UTF8.GetBytes(data);
var writeStatus = _ftdi.Write(bytes, bytes.Length, ref numBytesWritten);
if (writeStatus != FTDI.FT_STATUS.FT_OK)
{
Debug.WriteLine("WriteData: write error!!");
}
}
In this example, I also added a “Version” button to the interface. The ignition system is commanded by an Atmel chip (similar to the ones used in the infamous Arduino boards), which talks to the FTDI. We have developed a custom protocol and “v@” for example is a means to get the version number and nr of cylinders programmed for a car from the EEPROM in the Atmel chip.
Read / write the EEPROM
The FTDI chip itself also has an EEPROM, in which amongst other things the Vendor and Product data is stored. Manufacturers can customize this to completely align how Windows detects the devices with their name and such (if they also update the drivers signature and send it through WHQL, but that is another story).
private void btEEPROM_Click(object sender, RoutedEventArgs e)
{
if (_connectedDevice == null || !_ftdi.IsOpen)
{
lblEepromStatus.Content = "Not connected";
return;
}
_connectedDevice.Eeprom = new FTDI.FT232R_EEPROM_STRUCTURE();
var status = _ftdi.ReadFT232REEPROM(_connectedDevice.Eeprom);
if (status == FTDI.FT_STATUS.FT_OK)
{
lblEepromStatus.Content = "Eeprom read";
SetEepromInfo(_connectedDevice.Eeprom.VendorID, _connectedDevice.Eeprom.ProductID);
}
}
Again the SetEepromInfo uses the Dispatcher to update the two textboxes. Writing the EEPROM is easy-peasy: set a property of the EEPROM class and call the WriteEEPROM method in the wrapper.
private void btWriteEEPROM_Click(object sender, RoutedEventArgs e)
{
if (_connectedDevice == null || !_ftdi.IsOpen || _connectedDevice.Eeprom == null)
{
lblEepromStatus.Content = "Not connected or no eeprom read";
return;
}
_connectedDevice.Eeprom.VendorID = Convert.ToUInt16(tbVendorId.Text);
_connectedDevice.Eeprom.ProductID = Convert.ToUInt16(tbProductId.Text);
var status = _ftdi.WriteFT232REEPROM(_connectedDevice.Eeprom);
lblEepromStatus.Content = (status == FTDI.FT_STATUS.FT_OK) ? "Eeprom updated ok" : "Eeprom update failed";
}
To finish it off, here is a screenshot showing all the functionality in its active state:
Wrapup and source code
Still reading huh? I hope you found the read interesting and can be of use. Other than the internal SafeWaitHandle I could port all the existing functionality. For your convenience, I split the solution into a Silverlight and WPF part (version is set to .NET 2.0), so you can also use this library for any existing desktop project.
Looking at the source code in the wrapper library, there is many room for improvement, to make it more “C# style”. Maybe for a next post…
I appreciate any suggestions, rants, advice, yada yada. Drop me a line or leave a comment.



This is an awesome article. I love mixing Silverlight and devices this way. Thanks for sharing this with everyone.
I’ve asked some folks on the team about any SafeWaitHandle work-arounds (I don’t have any experience there). Hopefully they’ll have something you can use.
Pete Brown [Microsoft]
Thanks Pete! I hope you can convince some people to change the declaration of SafeWaitHandle. That would be truly great.