Hello, appended patch adds a streaming read for libftdi-1. It's mostly taken from Micah Dowty <[email protected]> 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 [email protected] 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 <[email protected]> Date: Tue, 27 Apr 2010 19:06:59 +0200 Subject: Add streaming read from Micah Dowty <[email protected]> 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 <[email protected]>\n", + "Adapted for use with libftdi (C) 2010 Uwe Bonnes <[email protected]>\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 [email protected]
