libftdi Archives

Subject: Re: Please review: Streaming read for libftdi-1

From: George Boudreau <george.boudreau@xxxxxxxxx>
To: libftdi@xxxxxxxxxxxxxxxxxxxxxxx
Date: Tue, 27 Apr 2010 13:23:55 -0400
Hi Uwe,

  I would love to see the verilog code it sounds interesting.

Regards,
 George

On Tue, Apr 27, 2010 at 1:18 PM, Uwe Bonnes <bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx> wrote:
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




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


Current Thread