//------------------------------------------------------------------------------ // USB communication library -- // (C) Piero Giubilato 2008-2010, Berkeley Lab -- //------------------------------------------------------------------------------ //______________________________________________________________________________ // {Trace} // [File name] "comm_USB.cpp" // [Author] "Piero Giubilato" // [Version] "1.0" // [Modified by] "Piero Giubilato" // [Last revision] "13 Feb 2009" // [Language] "C++" // [Compiler] "Visual C++ 9" // [Member of] "Cool SEAL" // [Project] "SEAL" // [Description] "This library handles the cy_USB class and provides advanced" // "USB communication capabilities. It allows to communicate with different" // "devices at the same time by the use of multi-threaded processes." // [Key documentation] // "Visual C++ Reference Help" // "Cypress CyUSB.NET DLL Programmer's Reference" // {Trace} //______________________________________________________________________________ // Standard libraries #include #include #include #include #include #include // Application components #include "global.h" #include "cypr_USB.h" #include "comm_USB.h" //______________________________________________________________________________ // Static member instantiation // Devices int comm_USB::dev_Count = 0; int comm_USB::dev_ID[8] = {0,1,2,3,4,5,6,7}; // Multi threading int comm_USB::dev_Flag[8] = {0,0,0,0,0,0,0,0}; int comm_USB::dev_Last[8] = {16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384}; int comm_USB::dev_opt_read_Len[8] = {16384, 16384, 16384, 16384, 16384, 16384, 16384, 16384}; HANDLE comm_USB::dev_thread_Hnd[8] = {0,0,0,0,0,0,0,0}; unsigned int comm_USB::dev_thread_ID[8] = {0,0,0,0,0,0,0,0}; unsigned short* comm_USB::dev_buffer_Adr[8] = {0,0,0,0,0,0,0,0}; int comm_USB::dev_req_Len[8] = {0,0,0,0,0,0,0,0}; // Test chunks unsigned short* comm_USB::fake_Chunk = NULL; //______________________________________________________________________________ comm_USB::comm_USB() { // Default constructor dbg_Print("comm_USB::comm_USB()", DBG_LVL_ZERO); } //______________________________________________________________________________ int comm_USB::Open(int VendorID, int ProductID) { // Searches for USB devices and enumerates them, then try search for every // device into the list compliant with the provided Vendor and Product ID. // Opens all the connected devices dev_Count = cypr_USB::Open(VendorID, ProductID); // Verbose dbg_Print("comm_USB::Open:(Int, Int)", DBG_LVL_MAKE); //dbg_Value(dev_Count, DBG_LVL_MAKE); // Return the number of compliant devices found return dev_Count; } //______________________________________________________________________________ int comm_USB::Close() { // Closes all the compliant opened devices. // Verbose dbg_Print("comm_USB::Close:()", DBG_LVL_MAKE); // Return the number of compliant devices found return cypr_USB::Close(); } //______________________________________________________________________________ int comm_USB::Reset() { // Resets all the connected USB devices. dbg_Print("comm_USB::Reset()", DBG_LVL_STEP); // Reset all the devices for (int devID = 0; devID < dev_Count; devID++) comm_USB::Reset(devID); // Exit with no error; return 0; } //______________________________________________________________________________ int comm_USB::Reset(int devID) { // Resets the specified USB device. if (devID < 0 || devID >= dev_Count) return -1; dbg_Print("comm_USB::Reset:(Int)", DBG_LVL_STEP); // Check the thread status for the device if (dev_Flag[devID] != 0) { // Stop the thread (if any) CloseHandle(dev_thread_Hnd[devID]); // Reset the associated device status dev_Flag[devID] = 0; dev_Last[devID] = 0; } // Reset the specified device return cypr_USB::Reset(devID); } //______________________________________________________________________________ int comm_USB::Clear(int tOut) { // Clear the IN endpoint of all the connected USB devices. dbg_Print("comm_USB::Clear:(Int)", DBG_LVL_STEP); // Reset all the devices for (int devID = 0; devID < dev_Count; devID++) comm_USB::Clear(devID, tOut); // Exit with no error; return 0; } //______________________________________________________________________________ int comm_USB::Clear(int devID, int tOut) { // Clear the IN endpoint of the specified USB device. if (devID < 0 || devID >= dev_Count) return -1; dbg_Print("comm_USB::Clear:(Int, Int)", DBG_LVL_STEP); // Clears the buffer unsigned short* buffer = new unsigned short[buffer_Len(devID)]; int buffSize = buffer_Len(devID); int rLen = 0; clock_t start; // Reads as long as the buffer has no more output start = clock(); do { // Reads a chunk from the buffer rLen = buffSize; Read(devID, buffer, &rLen, true); // Checks for timeout if((1000 * (double)(clock() - start) / CLOCKS_PER_SEC) > tOut) break; } while (rLen > 0); // Remove the temporary buffer delete []buffer; // Checks for timeout exit if (rLen > 0) { dbg_Print("comm_USB::Clear:(Int, Int): impossible to clean the buffer!", DBG_LVL_MAKE); return -1; } // Reset the associated device status dev_Flag[devID] = 0; dev_Last[devID] = 0; // Everything fine! return 0; } //______________________________________________________________________________ int comm_USB::buffer_Len(int devID) { // Gets the buffer lennght for the device (words, 2 bytes each) dbg_Print("comm_USB::buffer_Len:(Int)", DBG_LVL_FLOW); return cypr_USB::buffer_Len(devID); } //______________________________________________________________________________ int comm_USB::buffer_Len(int devID, int* len) { // Sets the buffer lenght for the device (words, 2 bytes each) dbg_Print("comm_USB::buffer_Len:(Int, Int*)", DBG_LVL_FLOW); return cypr_USB::buffer_Len(devID, len); } //______________________________________________________________________________ int comm_USB::timeout_Len(int devID) { // Gets the timeout setting for the device dbg_Print("comm_USB::timeout_Len:(Int)", DBG_LVL_FLOW); return cypr_USB::timeout_Len(devID); } //______________________________________________________________________________ int comm_USB::timeout_Len(int devID, int* len) { // Sets the timeout setting for the device dbg_Print("comm_USB::timeou_Len:(Int, Int*)", DBG_LVL_FLOW); return cypr_USB::timeout_Len(devID, len); } /* //______________________________________________________________________________ int comm_USB::Set(int* buffSize, int* timeout) { // Sets the buffer size and the timeout value for all the connected // USB devices. 'buffSize' is the number of word of the buffer (the buffer // is made of unsigned short, 2 bytes each) while the timeout is expressed // in milliseconds. // Debug dbg_Print("comm_USB::Set:(Int*, Int*)", DBG_LVL_STEP); // Set all the devices for (int devID = 0; devID < dev_Count; devID++) comm_USB::Set(devID, buffSize, timeout); // Exit with no error; return 0; } */ /* //______________________________________________________________________________ int comm_USB::Set(int devID, int* buffSize, int* timeout) { // Sets the buffer size and the timeout value for the 'devID' device // USB devices. 'buffSize' is the number of word of the buffer (the buffer // is made of unsigned short, 2 bytes each) while the timeout is expressed // in milliseconds. if (devID < 0 || devID >= dev_Count) return -1; dbg_Print("comm_USB::Set:(Int, Int*, Int*)", DBG_LVL_STEP); // Check the thread status for the device if (dev_Flag[devID] != 0) CloseHandle(dev_thread_Hnd[devID]); // Set the buffer for the device cypr_USB::buffer_Len(devID, buffSize); cypr_USB::timeout_Len(devID, timeout); // Reset the specified device (this will NOT affect the buffer size) return cypr_USB::Reset(devID); } */ //______________________________________________________________________________ int comm_USB::read_Flag(int devID) { // Returns the 'devID' current reading status. if (devID < 0 || devID >= dev_Count) return -1; // Returns return dev_Flag[devID]; } //______________________________________________________________________________ bool comm_USB::read_Busy(int devID) { // Returns the status of a device, 0 = available, 1 = busy. if (devID < 0 || devID >= dev_Count) return -1; // Check if it has stoppe running if (dev_Flag[devID] == kf_Ready) { // The thread is free! return false; } else if (dev_Flag[devID] != kf_Busy) { // This works, but takes an HUGE amount of time /* // Wait to be sure the thread has exited DWORD result; result = WaitForSingleObject(dev_thread_Hnd[devID], 10); // Check the status of the thread and exit if (result == WAIT_OBJECT_0) return 0; else if (result == WAIT_TIMEOUT) return 1; else if (result == WAIT_FAILED) return 0; else return 1; */ // This is the FAST way, but needs careful programming // to avoid crashes! Basically, it tells you that the // device is available, but with some error condition! return false; } else { // Return still busy return true; } } //______________________________________________________________________________ int comm_USB::read_Len(int devID) { // This function returns the length in words of the last completed Read call. // Returns 0 if the call has failed or if it has still to complete. Use the // read_Flag function for more information about the status of a read call. if (devID < 0 || devID >= dev_Count) return -1; // Returns return dev_Last[devID]; } //______________________________________________________________________________ int comm_USB::opt_read_Len(int devID) { // This function returns the buffer lenght which maximize the // reading transfer. The value returned has really been optimized // only if the Tune function has been called, otherwise it just // returns the default value. if (devID < 0 || devID >= dev_Count) return -1; // Return return dev_opt_read_Len[devID]; } //______________________________________________________________________________ int comm_USB::Write(int devID, unsigned short* buffer, int* len) { // Writes a (buffer) of length (len) bytes into the selected device 'devID'. // 'buffer' is an unsigned short pointer to the reading buffer, while 'len' // is the lenght of the buffer itself (elements, not size!). The function // returns the number of written words. // Check if the device is instantiated if (devID < 0 || devID >= dev_Count) return -1; // Debug dbg_Print("comm_USB::Write:(UShort*, Int*)", DBG_LVL_MAKE); //dbg_Value(devID, DBG_LVL_MAKE); // Returns the number of written words return cypr_USB::Write(devID, buffer, len); } //______________________________________________________________________________ int comm_USB::Read(int devID, unsigned short* buffer, int* len, bool wait, int tOut) { // Reads a 'buffer' of length 'len' words from the selected device 'devID'. // This function support multithreading, so it starts each read call into // a separate thread and immediately return the control to the calling // function. To check if the buffer has been filled use the busy function. // It is not possible to issue a read call to a device which has not yet // returned the completed flag. // By setting wait=true the function will wait for the thread to complete // before returning control. // Flag values are: kf_Ready = thread is not committed // kf_Busy = thread is running // kf_Done = thread completed successfully // kf_tOut = thread completed with timeout // kf_Error = thread completed with error // Checks if the device is instantiated if (devID < 0 || devID >= dev_Count) return -1; dbg_Print("comm_USB::Read: reading device...", DBG_LVL_MAKE); // Flag checking and asserting, thread cleaning in case if (dev_Flag[devID] == kf_Busy) { // Debug dbg_Print("comm_USB::Read: device already in use", DBG_LVL_MAKE); //dbg_Value(devID, DBG_LVL_MAKE); // Device currently in use, cannot continue return 0; // Device has terminated without error //} else if (dev_Flag[devID] == kf_Done || dev_Flag[devID] == kf_tOut) { // // Close previous thread and continue // CloseHandle(dev_thread_Hnd[devID]); // Device has terminated with error } else if (dev_Flag[devID] == kf_Error) { // Debug dbg_Print("comm_USB::Read: error on device!", DBG_LVL_MAKE); //dbg_Value(devID, DBG_LVL_MAKE); // Anyway close the thread but exit CloseHandle(dev_thread_Hnd[devID]); } // Set timeout in case if (tOut >= 0) timeout_Len(devID, &tOut); // Set the flag as running; this will prevent any other call // to the thread bounded to this device. dev_Flag[devID] = kf_Busy; // Save the parameters for this device dev_req_Len[devID] = *len; dev_buffer_Adr[devID] = buffer; dev_Last[devID] = 0; // Make the call to the threaded function, which in turn will call // the class library to read the device. Note how the passed argument // is a reference to a static variable, so even if the new threads // starts after the father thred has already been closed, the data are // still available. dev_thread_Hnd[devID] = (HANDLE)_beginthreadex(NULL, 0, &ReadTR, (void*)&dev_ID[devID], 0, &dev_thread_ID[devID]); // If we need, we can stop (kill sounds bad) the father: all the data // the child needs are store in static vars, so they will survive the // closing of the father scope. // Anyway, if wait = true, wait for the end of the child thread if (wait) { // Waits for the end of the process WaitForSingleObject(dev_thread_Hnd[devID], INFINITE); // Updates the numebr of words read and exits *len = dev_Last[devID]; // Interprets the end status if (dev_Flag[devID] == kf_Done || dev_Flag[devID] == kf_tOut) { dev_Flag[devID] = kf_Ready; return 0; } else { dev_Flag[devID] = kf_Error; return -1; } } else { // Exit with no error return 0; } } //______________________________________________________________________________ unsigned int comm_USB::ReadTR(void* Args) { // This is a special function that is called as a new thread each time a // reading has to be performed on a device. // Disentagle the device ID! int devID = *((int*)Args); // Debug dbg_Print("comm_USB::ReadTR: started NEW thread", DBG_LVL_MAKE); // Store the requested amount of words int len = dev_req_Len[devID]; // Cal the class read function and assert the exit flag if (cypr_USB::Read(devID, dev_buffer_Adr[devID], &len) == 0) { // Check the actual number of read words if (len == dev_req_Len[devID]) { // Reading completed (pay attention tha the flag var MUST be the last // to be updated! Otherwise a go will be given without all the other // variables set!) dbg_Print("comm_USB::ReadTR: read done, len = buffer_Len", DBG_LVL_MAKE); dev_Last[devID] = len; dev_Flag[devID] = kf_Done; } else { // Reading completed (pay attention tha the flag var MUST be the last // to be updated! Otherwise a go will be giiven without all the other // variables set!) dbg_Print("comm_USB::ReadTR: read done, len < buffer_Len", DBG_LVL_MAKE); dev_Last[devID] = len; dev_Flag[devID] = kf_tOut; //std::cout << len << "\n"; } // Exit with no error _endthreadex(0); return 0; // cy_USB::Read returned an error condition } else { // Debug dbg_Print("comm_USB::ReadTR: error reading device!", DBG_LVL_MAKE); //dbg_Value(devID, DBG_LVL_MAKE); std::cout << dev_req_Len[devID] << ", " << len << std::endl; // Sets the flags dev_Last[devID] = 0; dev_Flag[devID] = kf_Error; // Exit with error _endthreadex(0); return -1; } } //______________________________________________________________________________ int comm_USB::read_Fake(int devID, unsigned short* buffer, int* len, bool wait) { // Fake reading function, which provides a simulation of a real // data chunk from the USB static int call_ID = 0; /* // This is the quick, fast buffer: 4 layers of 2*2 points, 10 frames // Static member to simulate different calls static unsigned short fake_Chunk [10][18] = {{ 0, 9, 4, 4,100,110,120,130,101,111,121,131,102,112,122,132,103,113}, {123,133, 1, 9, 4, 4,200,210,220,230,201,211,221,231,202,212,222,232}, {203,213,223,233, 2, 9, 4, 4,300,310,320,330,301,311,321,331,302,312}, {322,332,303,313,323,333, 3, 9, 4, 4,400,410,420,430,401,411,421,431}, {402,412,422,432,403,413,423,433, 4, 9, 4, 4,500,510,520,530,501,511}, {521,531,502,512,522,532,503,513,523,533, 5, 9, 4, 4,600,610,620,630}, {601,611,621,631,602,612,622,632,603,613,623,633, 6, 9, 4, 4,700,710}, {720,730,701,711,721,731,702,712,722,732,703,713,723,733, 7, 9, 4, 4}, {800,810,820,830,801,811,821,831,802,812,822,832,803,813,823,833, 8, 9}, { 4, 4,900,910,920,930,901,911,921,931,902,912,922,932,903,913,923,933}}; //{ 9, 10, 4, 4,999,999,999,999,999,999,999,999,999,999,999,999,999,999}}; // Fill the buffer *len = 18; for (int i = 0; i < 18; i++) buffer[i] = fake_Chunk[call_ID][i]; call_ID++; if (call_ID == 10) call_ID = 0; */ // Generate a realistic buffer if (fake_Chunk) delete []fake_Chunk; fake_Chunk = new unsigned short[6*3 + 3072]; for (int f = 0; f < 3; f++) { // Header fake_Chunk[f * 1028] = 3; // frame count fake_Chunk[f * 1028 + 1] = f; // frame Idx fake_Chunk[f * 1028 + 2] = 4; // Channel count fake_Chunk[f * 1028 + 3] = 256; // dp Count fake_Chunk[f * 1028 + 4] = 1; // Timestamp fake_Chunk[f * 1028 + 5] = 1; // Timestamp // Data for (int i = 0; i < 1024; i += 4) { fake_Chunk[f * 1028 + 6 + i] = ((i/4) % (8 * (f + 1))); fake_Chunk[f * 1028 + 7 + i] = ((i/4) % (16 * (f + 1))); fake_Chunk[f * 1028 + 8 + i] = ((i/4) % (32 * (f + 1))); fake_Chunk[f * 1028 + 9 + i] = ((i/4) % (64 * (f + 1))); } } // Fill the buffer *len = (6*3 + 3072); for (int i = 0; i < *len; i++) buffer[i] = fake_Chunk[i]; call_ID++; if (call_ID == 3) call_ID = 0; // Exit with no error std::cout << "Sending chunk #" << call_ID << std::endl; return 0; } //______________________________________________________________________________ int comm_USB::Tune() { // Performs a speed test, and returns the optimal buffer size to be used // in order to maximize the bandwidth. // The maximum achieved bandwidth in kB/s will be store in 'speed', while // the optimal suggested buffer size in words will be stored in 'size'. // This function is still R&D!!! // Current buffer reading int buf_size_Test = 65536; // START value for buffer testing int buf_tOut = 1000; unsigned int buf_size_Max = 1024 * 1024; // One MWord (2M bytes) int store_Read = 1024 * 1024; // The amount of words to read unsigned short *store = new unsigned short [dev_count_Max * store_Read]; int store_Count[dev_count_Max]; int buf_Err = 0; // Speed and timing double speed_Last[dev_count_Max]; double speed_Now[dev_count_Max]; double speed_Max[dev_count_Max]; clock_t time_Start[dev_count_Max]; clock_t time_Stop[dev_count_Max]; double time_Elapsed; // Write buffer unsigned short wBuff[16]; for (int n = 0; n < 16; n++) wBuff[n] = 0; int wLen = 16; // Check if any device available if (dev_Count == 0) { dbg_Print("comm_USB::Tune: need at least one device to tune!" , DBG_LVL_PLAY); return 0; }; // Debug dbg_Print("comm_USB::Tune: tuning started..." , DBG_LVL_MAKE); std::cout << "Buffer tuning"; // Starts the firmware(s) counter (ID = 1) wBuff[0] = 1; // Set the counter reference ID wBuff[1] = 2; // Counter count command for (int devID = 0; devID < dev_Count; devID++) Write(devID, wBuff, &wLen); // Check for speed do { // Presets counters for (int n = 0; n < dev_Count; n++) { // Sets the new buffer size buffer_Len(n, &buf_size_Test); // Preset variables store_Count[n] = 0; // How many buffers written during the test time speed_Last[n] = speed_Now[n]; time_Start[n] = clock(); } // Multithreads reading of all available devices do { // Scans all available devices, and re-start reading each // one as soon as the previous reading has been executed. for (int devID = 0; devID < dev_Count; devID++) { // If the previous thread has terminated, make the read call if (read_Busy(devID) == 0) { store_Count[devID]++; comm_USB::Read(devID, &store[devID * store_Read], &store_Read, false); } } // Check timing time_Stop[0] = clock(); time_Elapsed = time_Stop[0] - time_Start[0]; // Longer the time, more accurate the result! } while (time_Elapsed < 100); // ms // Wait to finish and measure all processes int comp_Count = 0; int completed[8] = {0,0,0,0,0,0,0,0}; do { for (int devID = 0; devID < dev_Count; devID++) { if (completed[devID] == 0) { if (read_Busy(devID) == 0) { time_Stop[devID] = clock(); completed[devID] = 1; comp_Count++; } } } } while (comp_Count < dev_Count); // Get speed(s) for (int n = 0; n < dev_Count; n++) { // Calculates the last speed speed_Now[n] = ((0.001 * store_Read * store_Count[n] * 2) / (time_Stop[n] - time_Start[n])); // std::cout << " Store COUNT(" << n << ") = " << store_Count[n] << "\n"; //std::cout << " Buffer lenght(" << n << "): " << buf_size_Test << "; speed = " << speed_Now[n] << "\n"; std::cout << "."; // Save the buffer size if the speed has increased if (speed_Now[n] >= speed_Last[n]) { dev_opt_read_Len[n] = buf_size_Test; speed_Max[n] = speed_Now[n]; } } // Increase buf_Size for next iteration buf_size_Test *= 2; // Continue up to a 512 * 512 buffer size } while (buf_size_Test <= buf_size_Max); // Stop the DARK counter wBuff[0] = 1; // Set the counter reference ID wBuff[1] = 4; // Issues the counter stop command(s) for (int devID = 0; devID < dev_Count; devID++) comm_USB::Write(devID, wBuff, &wLen); // Generate info for (int n = 0; n < dev_Count; n++) { std::cout << "\nDevice #" << n << " max speed is " << speed_Max[n] << " MB/s with a buffer size of " << dev_opt_read_Len[n] << " words\n"; } // Release teast buffer delete[] store; // Clear device(s) from the last bunch o datas comm_USB::Clear(); // Everything ok return 0; }