libftdi Archives

Subject: Streaming Read, was Re: Dual-Licensing

From: Uwe Bonnes <bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
To: libftdi@xxxxxxxxxxxxxxxxxxxxxxx
Date: Sun, 2 May 2010 16:12:55 +0200
>>>>> "Micah" == Micah Dowty <micahjd@xxxxxxxxx> writes:

    Micah> Hi Uwe, I'm fine with you dual-licensing the code under both MIT
    Micah> and LGPL. The way I understand it: As the MIT license is
    Micah> LGPL-compatible, you don't need my permission to do this- my
    Micah> original version can be MIT-licensed while your derivative work
    Micah> can be licensed under anything MIT-compatible. You can, for
    Micah> example, choose to dual-license it or make it LGPL-only as long
    Micah> as you also comply with the MIT license by including my copyright
    Micah> and disclaimer notice.

Trying to comply, appended patch adds Micah's adapted code as a new file
(ftdi_stream.c), keeping the original header and adding the ftdi.c license.

The patch is clean up from unrelated changes and tries to follow the coding
style. 

-- 
Uwe Bonnes                bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Institut fuer Kernphysik  Schlossgartenstrasse 9  64289 Darmstadt
--------- Tel. 06151 162516 -------- Fax. 06151 164321 ----------
>From 5ad286ebf47b027506061cd59dcb458283059bf5 Mon Sep 17 00:00:00 2001
From: Uwe Bonnes <bon@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>
Date: Sun, 2 May 2010 16:06:49 +0200
Subject: Integrate Micah Dowty's implementation for streaming read

---
 examples/CMakeLists.txt |    2 +
 examples/stream_test.c  |  171 +++++++++++++++++++++++++++++
 src/CMakeLists.txt      |    2 +-
 src/Makefile.am         |    2 +-
 src/ftdi.h              |   25 +++++
 src/ftdi_stream.c       |  272 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 472 insertions(+), 2 deletions(-)
 create mode 100644 examples/stream_test.c
 create mode 100644 src/ftdi_stream.c

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..0c2c884
--- /dev/null
+++ b/examples/stream_test.c
@@ -0,0 +1,171 @@
+/* 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 dump 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/CMakeLists.txt b/src/CMakeLists.txt
index 0e12e54..a4da870 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,7 +4,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR}
                      )
 
 # Targets
-set(c_sources     ftdi.c)
+set(c_sources     ftdi.c ftdi_stream.c)
 set(c_headers     ftdi.h)
 
 add_library(ftdi SHARED ${c_sources})
diff --git a/src/Makefile.am b/src/Makefile.am
index 9de0d6d..2ac154a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,6 +1,6 @@
 # the library search path.
 lib_LTLIBRARIES =  libftdi.la
-libftdi_la_SOURCES =  ftdi.c
+libftdi_la_SOURCES =  ftdi.c ftdi:stream.c
 include_HEADERS =  ftdi.h
 
 # Note:  If you specify a:b:c as the version in the next line,
diff --git a/src/ftdi.h b/src/ftdi.h
index 179d3ca..9c838ba 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,29 @@ struct ftdi_eeprom
     int size;
 };
 
+/**
+    \brief Progress Info for streaming read
+*/
+struct size_and_time
+{
+        uint64_t totalBytes;
+        struct timeval time;
+};
+
+typedef struct
+{
+    struct size_and_time first;
+    struct size_and_time prev;
+    struct size_and_time current;
+    double totalTime;
+    double totalRate;
+    double currentRate;
+} FTDIProgressInfo;
+
+typedef int (FTDIStreamCallback)(uint8_t *buffer, int length,
+                                 FTDIProgressInfo *progress, void *userdata);
+
+
 #ifdef __cplusplus
 extern "C"
 {
diff --git a/src/ftdi_stream.c b/src/ftdi_stream.c
new file mode 100644
index 0000000..46119ed
--- /dev/null
+++ b/src/ftdi_stream.c
@@ -0,0 +1,272 @@
+/***************************************************************************
+                          ftdi_stream.c  -  description
+                             -------------------
+    copyright            : (C) 2009 Micah Dowty 2010 Uwe Bonnes
+    email                : opensource@xxxxxxxxxxxxx
+ ***************************************************************************/
+ 
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU Lesser General Public License           *
+ *   version 2.1 as published by the Free Software Foundation;             *
+ *                                                                         *
+ ***************************************************************************/
+
+/* Adapted from 
+ * fastftdi.c - A minimal FTDI FT232H interface for which supports bit-bang
+ *              mode, but focuses on very high-performance support for
+ *              synchronous FIFO mode. Requires libusb-1.0
+ *
+ * Copyright (C) 2009 Micah Dowty
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "ftdi.h"
+
+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;
+}
+
-- 
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
  • Streaming Read, was Re: Dual-Licensing, Uwe Bonnes <=