FTDI Synchronous 245 Tutorial: D2XX with Visual Studio 2019

Perhaps the easiest way to get data between an FPGA (or microcontroller) and a PC is with a UART. It works great up to a few megabits per second, but often becomes unreliable if you push it much past that. One easy way to transfer hundreds of megabits per second is to use an FTDI chip that supports their "Synchronous 245 FIFO" protocol. It is very easy for an FPGA to implement, and I have been able to reliably transfer data to my PC at just over 350Mbps.

I used an FT232H on a "UM232H" development board from FTDI. DigiKey sells them for around $22: https://www.digikey.com/product-detail/en/ftdi-future-technology-devices-international-ltd/UM232H/768-1103-ND/

I replaced it's headers with some regular 0.1" pin headers because the factory-installed pins do not mate well with jumper wires. Here's what my setup looks like when wired to a Lattice MachXO2 FPGA development board:

Some Useful Links

https://www.ftdichip.com/Support/Documents/DataSheets/Modules/DS_UM232H.pdf
Datasheet for the FTDI development board. It summarizes the features, lists the pin out, specifies how to configure the power pins, and contains the schematic.

https://www.ftdichip.com/Drivers/D2XX.htm
To use the Synchronous 245 FIFO mode, you will need the "D2XX" driver. This might have been automatically installed when you plugged an FTDI into your PC, but you need to download this anyway, because the ZIP file contains the .lib and .h files needed when writing the software.

https://www.ftdichip.com/Support/Documents/ProgramGuides/D2XX_Programmer's_Guide(FT_000071).pdf
The D2XX Programmer's Guide explains how to use their API. It covers all of the data structures and functions that will be used.

https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-03_D2XXDataThroughput.pdf
https://www.ftdichip.com/Support/Documents/AppNotes/AN232B-04_DataLatencyFlow.pdf
Applications Notes explaining how the buffers and latency timer interact, which you need to understand in order to get the best performance from the system.

https://www.ftdichip.com/Support/Documents/TechnicalNotes/TN_153%20Instructions%20on%20
Including%20the%20D2XX%20Driver%20in%20a%20VS%20Express%202013%20Project.pdf

Explains how to setup a Visual Studio project so it can use the D2XX API. This is basically the "hello world" tutorial.

https://www.ftdichip.com/Support/Documents/AppNotes/AN_130_FT2232H_Used_In_
FT245%20Synchronous%20FIFO%20Mode.pdf

Explains how to use the Synchronous 245 FIFO mode, from both a hardware and software perspective. It contains timing diagrams, demo code, some advise, etc.

Prepare a Visual Studio 2019 Project

  1. Open Visual Studio 2019, then create a new project:
    File > New > Project > Empty C++ Project > Next > Project Name = "d2xx_test", Location = Desktop > Create
  2. Create the main.cpp file:
    Right-click the project > Add > New Item > C++ File, Name = "main.cpp" > Add
  3. Copy the header file and library file from the D2XX Driver zip file ("CDM v2.12.28 WHQL Certified.zip" or similar) into the project's source code folder:
    From the ZIP file: copy "/Static/amd64/ftd2xx.lib" and "/ftd2xx.h" into "Desktop/d2xx_test/d2xx_test/"
  4. Update the IDE so it knows about those files:
    Drag-n-drop the LIB file onto the Resource Files folder in the Visual Studio Solution Explorer. Drag-n-drop the H file onto the Header Files folder in the Visual Studio Solution Explorer. Right-click the project > Properties > Configuration = "All Configurations", Platform = "All Platforms" > Configuration Properties > C / C++ > Preprocessor > Preprocessor Definitions > click the "V" icon > Edit > type "FTD2XX_STATIC" > OK Linker > Input > Additional Dependencies > click the "V" icon > Edit > type "ftd2xx.lib" > OK > OK

Demo Program

By now the Visual Studio project is fully setup, so you can start using the API. Below is a simple demo program I wrote. It's reads 1GB of data from the Lattice MachXO2 FPGA.

  1. The program starts by displaying information about each FTDI device that is attached. Keep in mind that not all FTDI devices support the Synchronous 245 FIFO protocol.
  2. If a device with a certain serial number is found, the program will attempt to connect, configure for FIFO mode, and read 1GB of data.
  3. An error message will be displayed if there are any problems, and the program will exit.

Software Source Code (C++)

#include <stdio.h> #include <time.h> #include "ftd2xx.h" int main(int argc, char** argv) { FT_HANDLE handle; // check how many FTDI devices are attached to this PC unsigned long deviceCount = 0; if(FT_CreateDeviceInfoList(&deviceCount) != FT_OK) { printf("Unable to query devices. Exiting.\r\n"); return 1; } // get a list of information about each FTDI device FT_DEVICE_LIST_INFO_NODE* deviceInfo = (FT_DEVICE_LIST_INFO_NODE*) malloc(sizeof(FT_DEVICE_LIST_INFO_NODE) * deviceCount); if(FT_GetDeviceInfoList(deviceInfo, &deviceCount) != FT_OK) { printf("Unable to get the list of info. Exiting.\r\n"); return 1; } // print the list of information for(unsigned long i = 0; i < deviceCount; i++) { printf("Device = %d\r\n", i); printf("Flags = 0x%X\r\n", deviceInfo[i].Flags); printf("Type = 0x%X\r\n", deviceInfo[i].Type); printf("ID = 0x%X\r\n", deviceInfo[i].ID); printf("LocId = 0x%X\r\n", deviceInfo[i].LocId); printf("SN = %s\r\n", deviceInfo[i].SerialNumber); printf("Description = %s\r\n", deviceInfo[i].Description); printf("Handle = 0x%X\r\n", deviceInfo[i].ftHandle); printf("\r\n"); // connect to the device with SN "FT3SSN2O" if(strcmp(deviceInfo[i].SerialNumber, "FT3SSN2O") == 0) { if (FT_OpenEx(deviceInfo[i].SerialNumber, FT_OPEN_BY_SERIAL_NUMBER, &handle) == FT_OK && FT_SetBitMode(handle, 0xFF, 0x40) == FT_OK && FT_SetLatencyTimer(handle, 2) == FT_OK && FT_SetUSBParameters(handle, 65536, 65536) == FT_OK && FT_SetFlowControl(handle, FT_FLOW_RTS_CTS, 0, 0) == FT_OK && FT_Purge(handle, FT_PURGE_RX | FT_PURGE_TX) == FT_OK && FT_SetTimeouts(handle, 1000, 1000) == FT_OK) { // connected and configured successfully // read 1GB of data from the FTDI/FPGA char rxBuffer[65536] = { 0 }; unsigned long byteCount = 0; time_t startTime = clock(); for(int i = 0; i < 16384; i++) { if(FT_Read(handle, rxBuffer, 65536, &byteCount) != FT_OK || byteCount != 65536) { printf("Error while reading from the device. Exiting.\r\n"); return 1; } } time_t stopTime = clock(); double secondsElapsed = (double)(stopTime - startTime) / CLOCKS_PER_SEC; double mbps = 8589.934592 / secondsElapsed; printf("Read 1GB from the FTDI in %0.1f seconds.\r\n", secondsElapsed); printf("Average read speed: %0.1f Mbps.\r\n", mbps); return 0; } else { // unable to connect or configure printf("Unable to connect to or configure the device. Exiting.\r\n"); return 1; } } } return 0; }

Firmware Source Code (Verilog)

`default_nettype none module top ( // ftdi 245 fifo signals output reg [7:0] data, // [7:0] = pins 1,2,3,4,9,10,11,12 input wire rx_empty, // pin 13 input wire tx_full, // pin 14 output reg read_n, // pin 19 output reg write_n, // pin 20 output reg send_immediately_n, // pin 21 input wire clock_60mhz, // pin 27 output reg output_enable_n, // pin 28 // status leds output reg power_led_n, // pin 97 output reg tx_active_led_n // pin 107 ); reg [7:0] counter; always @(posedge clock_60mhz) begin power_led_n <= 0; output_enable_n <= 1; send_immediately_n <= 1; if(!tx_full) begin write_n <= 0; data <= counter; tx_active_led_n <= 0; counter <= counter + 1; end else begin write_n <= 1; read_n <= 1; tx_active_led_n <= 1; end end endmodule

Output of Test Run

Device = 0 Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x262 SN = B Description = Lattice FTUSB Interface Cable B Handle = 0x0 Device = 1 Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x261 SN = A Description = Lattice FTUSB Interface Cable A Handle = 0x0 Device = 2 Flags = 0x2 Type = 0x8 ID = 0x4036014 LocId = 0x281 SN = FT3SSN2O Description = UM232H Handle = 0x0 Read 1GB from the FTDI in 24.1 seconds. Average read speed: 356.5 Mbps.

YouTube Video

Telemetry Viewer v0.6

Telemetry Viewer v0.6 Changelog (2019-09-08)

  • The x-axis of Time Domain Charts can now display elapsed time.
  • Timestamps are recorded. Exported CSV files contain the UNIX timestamp for each sample.
  • CSV files can be imported (replayed.)
  • New and existing charts are configured with a non-modal side panel instead of a pop-up window.
  • Layout files and CSV files can be imported via drag-n-drop.
  • Charts can be maximized (full-screened.)
  • The Time Domain Chart now renders properly even when the sample number is very large.
  • Samples are automatically swapped to disk if there's not enough space in RAM.
  • Binary mode supports uint8 values.
  • Binary mode supports bitfields (for showing boolean and enum values.)
  • Various small bug fixes. See the git commit log for more details.

Telemetry Viewer v0.6 Demo Video



Download

Executables (.jar) and source code (.zip) can be downloaded at http://www.farrellf.com/TelemetryViewer/ or the project can be viewed at https://github.com/farrellf/TelemetryViewer

Telemetry Viewer v0.5

This version of Telemetry Viewer focused mostly on adding support for TCP and UDP.

Telemetry Viewer v0.5 Changelog (2018-08-20)

  • Telemetry can be received over UART, TCP or UDP.
  • Tooltips can be drawn over a chart to see the numeric values for data under the mouse.
  • A notification system was added to display less-distracting (non-modal) alerts to the user.
  • Moved the GUI-related settings into a sidebar on the left side of the program.
  • Logitech smooth scrolling can now be disabled.
  • OpenGL antialiasing can now be disabled.
  • Chart rendering can be benchmarked (CPU and GPU times.)
  • Various small bug fixes. See the git commit log for more details.

Telemetry Viewer v0.5 Demo Video



Download

Executables (.jar) and source code (.zip) can be downloaded at http://www.farrellf.com/TelemetryViewer/ or the project can be viewed at https://github.com/farrellf/TelemetryViewer

Telemetry Viewer v0.4

This version of Telemetry Viewer focused mostly on making things easier and more intuitive for the user.

Telemetry Viewer v0.4 Changelog (2017-07-21)

  • The GUI now guides the user on how to connect to a serial port or open an existing layout.
  • Arduino sketch templates are generated to make it easier to write the firmware.
  • The GUI now guides the user on how to place charts.
  • The charts were re-themed to use a tile-based layout instead of a plain grid.
  • Most of the chart attributes can now be hidden (axis titles, axis scales, legends, etc.)
  • When adding a chart, the chart is drawn live so you can see the effects of the settings.
  • Chart settings can also be changed by clicking the gear icon near the top-right corner of each chart.
  • Faster rendering for the Waveform and Waterfall modes of the Frequency Domain Chart.
  • Various small bug fixes. See the git commit log for more details.

Telemetry Viewer v0.4 Demo Video



Download

Executables (.jar) and source code (.zip) can be downloaded at http://www.farrellf.com/TelemetryViewer/ or the project can be viewed at https://github.com/farrellf/TelemetryViewer

Basic Home Automation With an IR LED and Receiver

Controlling devices that have infrared remotes is an easy and low-cost form of home automation, so I decided to make a YouTube tutorial video about it. I'm using the "IRremote" library by Ken Shirriff, and wrote some firmware that uses an IR receiver and an IR LED as a computer-controlled universal remote. The end result was being able to press a key on my keyboard, and having household decives respond accordingly. I set it up so i can turn my TV and tower fan on or off.

After installing the IRremote library, the firmware is pretty simple:

#include <IRremote.h> IRrecv receiver(2); // receiver is connected to pin2 IRsend sender; decode_results results; long repetitions; long count; unsigned int durations[100]; void (*reset)(void) = 0; void setup() { Serial.begin(9600); receiver.enableIRIn(); // start receiving signals } void loop() { // check for text from the PC // the PC sends a string containing "r,n,a,b,c,d,e,..." where r is how many times to repeat the command, // n is the number of durations, and a/b/c/d/e/... are the durations. // the durations are how long each mark and space period of an infrared command should last, in microseconds. if(Serial.available()) { // parse the text repetitions = Serial.parseInt(); count = Serial.parseInt(); for(int i = 0; i < count; i++) durations[i] = Serial.parseInt(); // send the command using 40kHz PWM for(int i = 0; i < repetitions; i++) { sender.sendRaw(durations, count, 40); delay(50); } // for a bit of fault tolerance, reset the arduino after receiving any command reset(); } // check if a decoded infrared signal is available if(receiver.decode(&results)) { Serial.println(results.value, HEX); Serial.print(results.rawlen - 1); for(int i = 1; i < results.rawlen; i++) { unsigned int number = results.rawbuf[i] * USECPERTICK; Serial.print(","); Serial.print(number); } Serial.println(""); receiver.resume(); } }

If you aim a remote at the IR receiver and press a button, details about that waveform will be printed to the serial port. To have the Arduino send that IR command, just send that text back to the Arduino, but add a number and comma to the beginning. That number specifies how many times to repeat the command. Some protocols, like Sony's, require a command be sent three times. So you would send "3," followed by the text the Arduino printed out.

We now have the hardware and firmware all setup, but it's a pain to send those IR commands (copy-and-pasting text into the Serial Monitor.) We can create some batch files to automate the sending of text to the Arduino. These will need to be adjusted for your IR commands and COM port, but for me it was:

fan_on_off.bat

mode COM3 baud=9600 parity=n data=8 stop=1 echo "1,23,1200,450,1250,400,400,1300,1200,450,1200,450,400,1250,400,1250,400,1300,350,1300,400,1250,400,1250,1250" > COM3

tv_on_off.bat

mode COM3 baud=9600 parity=n data=8 stop=1 echo "3,25,2400,600,600,600,1150,600,1200,600,1150,650,1150,600,1150,650,550,600,1200,600,600,600,550,660,600,600,600" > COM3

By default the Arduino will reset immediately after you connect to it's serial port, so that needs to be disabled. I show how in the video, you just need to cut a trace on the PCB. After that, double clicking on those batch files will send the IR commands.

I used AutoHotKey to have key presses trigger those batch files. A simple script tells AutoHotKey to turn my tv on/off if I press F12, and turn my fan on/off if I press F11:

f12:: Run, C:\Users\FarrellF\Desktop\tv_on_off.bat Return f11:: Run, C:\Users\FarrellF\Desktop\fan_on_off.bat Return

See the video for more details.

This is the infrared LED and receiver that I used in the first part of the video: http://amzn.to/2lZ7pTy

For a higher-power LED: http://amzn.to/2lZ6oLk

< Prev  2  Next >