libftdi Archives

Subject: Please review: Streaming read for libftdi-1

From: Uwe Bonnes <bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
To: libftdi@xxxxxxxxxxxxxxxxxxxxxxx
Date: Tue, 27 Apr 2010 19:18:37 +0200
Hello,

appended patch adds a streaming read for libftdi-1. It's mostly taken from 
Micah Dowty <micahjd@xxxxxxxxx> fastftdi.c|h.

I have a testboard with an FT2232H connected to an Spartan-3, with the
Spartan programmed to start sending some sequence when synchronous fifo mode
is switched on. I can read about 36 MiByte/s with that setup. If the verilog
code is of interest, I can provide to.

The patch adds libusb_context to the ftdi_context structure, but otherwise
touches none of the existing code. libusb_context is needed for handling
libusb_handle_events_timeout().  

Feedback welcome.
-- 
Uwe Bonnes                bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Institut fuer Kernphysik  Schlossgartenstrasse 9  64289 Darmstadt
--------- Tel. 06151 162516 -------- Fax. 06151 164321 ----------
>From cfdcda29202875a8327799ef992a160d883569d5 Mon Sep 17 00:00:00 2001
From: Uwe Bonnes <bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
Date: Tue, 27 Apr 2010 19:06:59 +0200
Subject: Add streaming read from  Micah Dowty <micahjd@xxxxxxxxx> fastftdi.c

---
 CMakeLists.txt          |    1 +
 examples/CMakeLists.txt |    2 +
 examples/stream_test.c  |  168 ++++++++++++++++++++++++++++++++++++
 src/ftdi.c              |  217 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/ftdi.h              |   20 +++++
 5 files changed, 407 insertions(+), 1 deletions(-)
 create mode 100644 examples/stream_test.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3e6c96d..b561418 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,7 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "")
    set(CMAKE_BUILD_TYPE     Debug)
 endif("${CMAKE_BUILD_TYPE}" STREQUAL "")
 set(CMAKE_COLOR_MAKEFILE ON)
+set(CMAKE_CXX_FLAGS "-g -Wall")
 cmake_minimum_required(VERSION 2.6 FATAL_ERROR)
 
 # Debug build
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index d2aeecb..7b702c2 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -20,6 +20,7 @@ if (EXAMPLES)
     add_executable(find_all find_all.c)
     add_executable(serial_read serial_read.c)
     add_executable(baud_test baud_test.c)
+    add_executable(stream_test stream_test.c)
 
     # Linkage
     target_link_libraries(simple ftdi)
@@ -30,6 +31,7 @@ if (EXAMPLES)
     target_link_libraries(find_all ftdi)
     target_link_libraries(serial_read ftdi)
     target_link_libraries(baud_test ftdi)
+    target_link_libraries(stream_test ftdi)
 
     # libftdi++ examples
     if(FTDI_BUILD_CPP)
diff --git a/examples/stream_test.c b/examples/stream_test.c
new file mode 100644
index 0000000..33c26e2
--- /dev/null
+++ b/examples/stream_test.c
@@ -0,0 +1,168 @@
+/* stream_test.c
+ *
+ * Test reading  from FT2232H in synchronous FIFO mode.
+ *
+ * The FT2232H must supply data due to an appropriate circuit
+ *
+ * After start, data will be read in streaming until the program is aborted
+ * Progess information wil be prointed out
+ * If a filename is given on the command line, the data read will be
+ * written to that file
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <signal.h>
+#include <errno.h>
+#include <ftdi.h>
+
+static FILE *outputFile;
+
+static int exitRequested;
+/*
+ * sigintHandler --
+ *
+ *    SIGINT handler, so we can gracefully exit when the user hits ctrl-C.
+ */
+
+static void
+sigintHandler(int signum)
+{
+   exitRequested = 1;
+}
+
+static void
+usage(const char *argv0)
+{
+   fprintf(stderr,
+           "Usage: %s [options...] \n"
+           "Test streaming read from FT2232H\n"
+           "\n"
+           "If some filename is given, write data read to that file\n"
+           "Progess information is printed each second\n"
+           "Abort with ^C\n"
+           "\n"
+           "Options:\n"
+           "\n"
+           "Copyright (C) 2009 Micah Dowty <micah@xxxxxxx>\n",
+           "Adapted for use with libftdi (C) 2010 Uwe Bonnes 
<bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>\n",
+           argv0);
+   exit(1);
+}
+
+static int
+readCallback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void 
*userdata)
+{
+   if (length) {
+      if (outputFile) {
+         if (fwrite(buffer, length, 1, outputFile) != 1) {
+            perror("Write error");
+            return 1;
+         }
+      }
+   }
+   if (progress) {
+     fprintf(stderr, "%10.02fs total time %9.3f MiB captured %7.1f kB/s curr 
rate %7.1f kB/s totalrate \n",
+             progress->totalTime,
+             progress->current.totalBytes / (1024.0 * 1024.0),
+             progress->currentRate / 1024.0,
+             progress->totalRate / 1024.0);
+   }
+   return exitRequested ? 1 : 0;
+}
+
+int main(int argc, char **argv)
+{
+   struct ftdi_context ftdic;
+   int err, c;
+   char const *outfile  = 0;
+   outputFile =0;
+
+   exitRequested = 0;
+
+   while (1) {
+      int option_index;
+      static struct option long_options[] = {
+          {NULL},
+      };
+
+      c = getopt_long(argc, argv, "", long_options, &option_index);
+      if (c == -1)
+         break;
+
+      switch (c) {
+
+      default:
+         usage(argv[0]);
+      }
+   }
+
+   if (optind == argc - 1) {
+      // Exactly one extra argument- a trace file
+      outfile = argv[optind];
+   } else if (optind < argc) {
+      // Too many extra args
+      usage(argv[0]);
+   }
+
+    if (ftdi_init(&ftdic) < 0)
+    {
+        fprintf(stderr, "ftdi_init failed\n");
+        return EXIT_FAILURE;
+    }
+
+    if (ftdi_set_interface(&ftdic, INTERFACE_A) < 0)
+    {
+        fprintf(stderr, "ftdi_set_interface failed\n");
+        return EXIT_FAILURE;
+    }
+
+    if (ftdi_usb_open_desc(&ftdic, 0x0403, 0x6010, NULL, NULL) < 0)
+    {
+        fprintf(stderr,"Can't open ftdi device: 
%s\n",ftdi_get_error_string(&ftdic));
+        return EXIT_FAILURE;
+    }
+
+    if(ftdi_usb_purge_buffers(&ftdic) < 0)
+    {
+        fprintf(stderr,"Can't purge\n",ftdi_get_error_string(&ftdic));
+        return EXIT_FAILURE;
+    }
+
+    usleep(10000);
+    if (ftdi_set_bitmode(&ftdic,  0xff, BITMODE_SYNCFF) < 0)
+    {
+        fprintf(stderr,"Can't set synchronous fifo 
mode\n",ftdi_get_error_string(&ftdic));
+        return EXIT_FAILURE;
+    }
+
+    if (outfile)
+      if ((outputFile = fopen(outfile,"w+")) == 0)
+        fprintf(stderr,"Can't open logfile %s, Error %s\n", outfile, 
strerror(errno));
+
+    signal(SIGINT, sigintHandler);
+
+    err = ftdi_readstream(&ftdic, readCallback, NULL, 8, 256);
+    if (err < 0 && !exitRequested)
+        exit(1);
+
+    if (outputFile) {
+      fclose(outputFile);
+      outputFile = NULL;
+    }
+    fprintf(stderr, "Capture ended.\n");
+
+    if (ftdi_set_bitmode(&ftdic,  0xff, BITMODE_RESET) < 0)
+      {
+        fprintf(stderr,"Can't set synchronous fifo 
mode\n",ftdi_get_error_string(&ftdic));
+        return EXIT_FAILURE;
+      }
+    ftdi_usb_close(&ftdic);
+    ftdi_deinit(&ftdic);
+    exit (0);
+}
+    
+   
+
diff --git a/src/ftdi.c b/src/ftdi.c
index 5c25abd..8c6dab6 100644
--- a/src/ftdi.c
+++ b/src/ftdi.c
@@ -236,7 +236,7 @@ int ftdi_usb_find_all(struct ftdi_context *ftdi, struct 
ftdi_device_list **devli
     int count = 0;
     int i = 0;
 
-    if (libusb_init(NULL) < 0)
+    if (libusb_init(&ftdi->libusb) < 0)
         ftdi_error_return(-4, "libusb_init() failed");
 
     if (libusb_get_device_list(NULL, &devs) < 0)
@@ -477,6 +477,11 @@ int ftdi_usb_open_dev(struct ftdi_context *ftdi, 
libusb_device *dev)
     // set configuration (needed especially for windows)
     // tolerate EBUSY: one device with one configuration, but two interfaces
     //    and libftdi sessions to both interfaces (e.g. FT2232)
+        if (libusb_set_configuration(ftdi->usb_dev, cfg0) < 0)
+        {
+            ftdi_usb_close_internal (ftdi);
+            ftdi_error_return(-3, "unable to set usb configuration. Make sure 
ftdi_sio is unloaded!");
+        }
     if (desc.bNumConfigurations > 0 && cfg != cfg0)
     {
         if (libusb_set_configuration(ftdi->usb_dev, cfg0) < 0)
@@ -1736,6 +1741,216 @@ int ftdi_read_data_get_chunksize(struct ftdi_context 
*ftdi, unsigned int *chunks
     return 0;
 }
 
+typedef struct {
+   FTDIStreamCallback *callback;
+   void *userdata;
+   int packetsize;
+   int result;
+   FTDIProgressInfo progress;
+} FTDIStreamState;
+
+static void
+ftdi_readstream_cb(struct libusb_transfer *transfer)
+{
+   FTDIStreamState *state = transfer->user_data;
+   int packet_size = state->packetsize;
+
+   if (state->result == 0) {
+      if (transfer->status == LIBUSB_TRANSFER_COMPLETED
+          || transfer->status == LIBUSB_TRANSFER_CANCELLED) {
+
+         int i;
+         uint8_t *ptr = transfer->buffer;
+         int length = transfer->actual_length;
+         int numPackets = (length + packet_size - 1) / packet_size;
+ 
+         for (i = 0; i < numPackets; i++) {
+            int payloadLen;
+            int packetLen = length;
+
+            if (packetLen > packet_size)
+               packetLen = packet_size;
+
+            payloadLen = packetLen - 2;
+            state->progress.current.totalBytes += payloadLen;
+
+            state->result = state->callback(ptr + 2, payloadLen,
+                                            NULL, state->userdata);
+            if (state->result)
+               break;
+
+            ptr += packetLen;
+            length -= packetLen;
+         }
+         if(transfer->status == LIBUSB_TRANSFER_CANCELLED) {
+            free(transfer->buffer);
+            libusb_free_transfer(transfer);
+            return;
+         }
+             
+
+      } else {
+         state->result = LIBUSB_ERROR_IO;
+      }
+   }
+
+   if (state->result == 0) {
+      transfer->status = -1;
+      state->result = libusb_submit_transfer(transfer);
+   }
+}
+
+/**
+   Helper function to calculate (unix) time differences
+
+   \param a timeval
+   \param b timeval
+*/
+static double
+TimevalDiff(const struct timeval *a, const struct timeval *b)
+{
+   return (a->tv_sec - b->tv_sec) + 1e-6 * (a->tv_usec - b->tv_usec);
+}
+
+/**
+    Streaming reading of data from the device
+    
+    Use asynchronous transfers in libusb-1.0 for high-performance
+    streaming of data from a device interface back to the PC. This
+    function continuously transfers data until either an error occurs
+    or the callback returns a nonzero value. This function returns
+    a libusb error code or the callback's return value.
+
+    For every contiguous block of received data, the callback will
+    be invoked.
+
+    \param  ftdi pointer to ftdi_context
+    \param  callback to user supplied function for one block of data
+    \param  userdata
+    \param  packetsPerTransfer number of packets per transfer
+    \param  numTransfers Number of transfers per callback
+
+*/
+
+int
+ftdi_readstream(struct ftdi_context *ftdi,
+                      FTDIStreamCallback *callback, void *userdata,
+                      int packetsPerTransfer, int numTransfers)
+{
+   struct libusb_transfer **transfers;
+   FTDIStreamState state = { callback, userdata, ftdi->max_packet_size };
+   int bufferSize = packetsPerTransfer * ftdi->max_packet_size;
+   int xferIndex;
+   int err = 0;
+
+   fprintf(stderr, "ftdi_readstream\n");
+   /*
+    * Set up all transfers
+    */
+
+   transfers = calloc(numTransfers, sizeof *transfers);
+   if (!transfers) {
+      err = LIBUSB_ERROR_NO_MEM;
+      goto cleanup;
+   }
+
+   for (xferIndex = 0; xferIndex < numTransfers; xferIndex++) {
+      struct libusb_transfer *transfer;
+
+      transfer = libusb_alloc_transfer(0);
+      transfers[xferIndex] = transfer;
+      if (!transfer) {
+         err = LIBUSB_ERROR_NO_MEM;
+         goto cleanup;
+      }
+
+      libusb_fill_bulk_transfer(transfer, ftdi->usb_dev, ftdi->out_ep,
+                                malloc(bufferSize), bufferSize, 
ftdi_readstream_cb,
+                                &state, 0);
+
+      if (!transfer->buffer) {
+         err = LIBUSB_ERROR_NO_MEM;
+         goto cleanup;
+      }
+
+      transfer->status = -1;
+      err = libusb_submit_transfer(transfer);
+      if (err)
+         goto cleanup;
+   }
+
+   /*
+    * Run the transfers, and periodically assess progress.
+    */
+
+   gettimeofday(&state.progress.first.time, NULL);
+
+   do {
+      FTDIProgressInfo  *progress = &state.progress;
+      const double progressInterval = 1.0;
+      struct timeval timeout = { 0, ftdi->usb_read_timeout };
+      struct timeval now;
+
+      int err = libusb_handle_events_timeout(ftdi->libusb, &timeout);
+      if (!state.result) {
+         state.result = err;
+      }
+
+      // If enough time has elapsed, update the progress
+      gettimeofday(&now, NULL);
+      if (TimevalDiff(&now, &progress->current.time) >= progressInterval) {
+
+         progress->current.time = now;
+         progress->totalTime = TimevalDiff(&progress->current.time,
+                                              &progress->first.time);
+
+         if (progress->prev.totalBytes) {
+            // We have enough information to calculate rates
+
+            double currentTime;
+
+            currentTime = TimevalDiff(&progress->current.time,
+                                      &progress->prev.time);
+
+            progress->totalRate = progress->current.totalBytes / 
progress->totalTime;
+            progress->currentRate = (progress->current.totalBytes -
+                                     progress->prev.totalBytes) / currentTime;
+         }
+
+         state.result = state.callback(NULL, 0, progress, state.userdata);
+         progress->prev = progress->current;
+
+      }
+   } while (!state.result);
+
+   /*
+    * Cancel any outstanding transfers, and free memory.
+    */
+
+ cleanup:
+  fprintf(stderr, "cleanup\n");
+  if (transfers) {
+      int i;
+      for (xferIndex = 0; xferIndex < numTransfers; xferIndex++) {
+         struct libusb_transfer *transfer = transfers[xferIndex];
+
+         if (transfer) {
+            if (transfer->status == -1)
+                libusb_cancel_transfer(transfer);
+         }
+      }
+      for(i=0; i<numTransfers; i++) {
+        libusb_handle_events(ftdi->libusb);
+      }
+      free(transfers);
+   }
+
+   if (err)
+      return err;
+   else
+      return state.result;
+}
+
 
 /**
     Enable bitbang mode.
diff --git a/src/ftdi.h b/src/ftdi.h
index 179d3ca..a148133 100644
--- a/src/ftdi.h
+++ b/src/ftdi.h
@@ -175,6 +175,8 @@ struct ftdi_transfer_control
 struct ftdi_context
 {
     /* USB specific */
+    /** libusb's context */
+    struct libusb_context *libusb;
     /** libusb's usb_dev_handle */
     struct libusb_device_handle *usb_dev;
     /** usb read timeout */
@@ -278,6 +280,24 @@ struct ftdi_eeprom
     int size;
 };
 
+/**
+    \brief Progress Info for streaming read
+*/
+typedef struct {
+   struct {
+      uint64_t       totalBytes;
+      struct timeval time;
+   } first, prev, current;
+
+   double totalTime;
+   double totalRate;
+   double currentRate;
+} FTDIProgressInfo;
+
+typedef int (FTDIStreamCallback)(uint8_t *buffer, int length,
+                                 FTDIProgressInfo *progress, void *userdata);
+
+
 #ifdef __cplusplus
 extern "C"
 {
-- 
1.6.4.2


--
libftdi - see http://www.intra2net.com/en/developer/libftdi for details.
To unsubscribe send a mail to libftdi+unsubscribe@xxxxxxxxxxxxxxxxxxxxxxx   

Current Thread