From 45ef9b9ff96a189915efd120ce2d5a9a02770e63 Mon Sep 17 00:00:00 2001 From: Saeid Bazazzadeh Date: Mon, 3 Dec 2018 13:40:46 -0500 Subject: [PATCH] Ver 1.1.0 commit --- README.md | 18 +- configure.ac | 6 +- examples/AIn.c | 4 +- examples/AInScan.c | 4 +- examples/AInScanWithEvents.c | 4 +- examples/AInScanWithQueue.c | 19 +- examples/AInScanWithTrigger.c | 13 +- examples/AOut.c | 2 +- examples/AOutScan.c | 2 +- examples/DBitIn.c | 12 +- examples/DBitOut.c | 21 +- examples/DIn.c | 17 +- examples/DInScan.c | 6 +- examples/DOut.c | 24 +- examples/DOutScan.c | 6 +- examples/Makefile.am | 4 +- examples/TIn.c | 165 ++++ examples/utility.h | 133 ++- fpga/USB_1808.bin | Bin 464196 -> 464196 bytes rules/50-uldaq.rules | 26 +- src/AiConfig.cpp | 22 +- src/AiConfig.h | 9 +- src/AiDevice.cpp | 101 +- src/AiDevice.h | 28 +- src/AiInfo.cpp | 34 +- src/AiInfo.h | 12 + src/AoConfig.cpp | 12 +- src/AoConfig.h | 3 + src/AoDevice.cpp | 77 ++ src/AoDevice.h | 6 + src/AoInfo.cpp | 11 + src/AoInfo.h | 3 + src/CtrDevice.cpp | 12 +- src/DaqDevice.cpp | 4 +- src/DaqDevice.h | 5 +- src/DaqDeviceId.h | 25 + src/DaqDeviceManager.cpp | 29 +- src/DaqDeviceManager.h | 2 +- src/DaqEventHandler.cpp | 6 +- src/DaqEventHandler.h | 2 +- src/DioConfig.cpp | 20 + src/DioConfig.h | 6 + src/DioDevice.cpp | 192 +++- src/DioDevice.h | 10 + src/DioInfo.cpp | 18 +- src/DioInfo.h | 2 +- src/IoDevice.cpp | 35 +- src/Makefile.am | 2 +- src/UlDaqDeviceManager.cpp | 69 +- src/UlDaqDeviceManager.h | 2 +- src/hid/HidDaqDevice.cpp | 783 +++++++++++++++ src/hid/HidDaqDevice.h | 83 ++ src/hid/Usb3100.cpp | 35 + src/hid/Usb3100.h | 29 + src/hid/UsbDio24.cpp | 36 + src/hid/UsbDio24.h | 33 + src/hid/UsbDio96h.cpp | 29 + src/hid/UsbDio96h.h | 24 + src/hid/UsbErbxx.cpp | 25 + src/hid/UsbErbxx.h | 24 + src/hid/UsbPdiso8.cpp | 25 + src/hid/UsbPdiso8.h | 24 + src/hid/UsbSsrxx.cpp | 25 + src/hid/UsbSsrxx.h | 24 + src/hid/UsbTemp.cpp | 28 + src/hid/UsbTemp.h | 24 + src/hid/UsbTempAi.cpp | 31 + src/hid/UsbTempAi.h | 24 + src/hid/ai/AiHidBase.cpp | 64 ++ src/hid/ai/AiHidBase.h | 33 + src/hid/ai/AiUsbTemp.cpp | 394 ++++++++ src/hid/ai/AiUsbTemp.h | 52 + src/hid/ai/AiUsbTempAi.cpp | 624 ++++++++++++ src/hid/ai/AiUsbTempAi.h | 65 ++ src/hid/ao/AoHidBase.cpp | 23 + src/hid/ao/AoHidBase.h | 30 + src/hid/ao/AoUsb3100.cpp | 268 +++++ src/hid/ao/AoUsb3100.h | 54 + src/hid/ctr/CtrHid.cpp | 64 ++ src/hid/ctr/CtrHid.h | 32 + src/hid/ctr/CtrHidBase.cpp | 23 + src/hid/ctr/CtrHidBase.h | 30 + src/hid/ctr/CtrUsbDio24.cpp | 79 ++ src/hid/ctr/CtrUsbDio24.h | 33 + src/hid/dio/DioHidAux.cpp | 117 +++ src/hid/dio/DioHidAux.h | 41 + src/hid/dio/DioHidBase.cpp | 23 + src/hid/dio/DioHidBase.h | 30 + src/hid/dio/DioUsbDio24.cpp | 219 +++++ src/hid/dio/DioUsbDio24.h | 45 + src/hid/dio/DioUsbDio96h.cpp | 217 ++++ src/hid/dio/DioUsbDio96h.h | 48 + src/hid/dio/DioUsbErbxx.cpp | 129 +++ src/hid/dio/DioUsbErbxx.h | 42 + src/hid/dio/DioUsbPdiso8.cpp | 124 +++ src/hid/dio/DioUsbPdiso8.h | 42 + src/hid/dio/DioUsbSsrxx.cpp | 222 +++++ src/hid/dio/DioUsbSsrxx.h | 46 + src/hid/hid_linux.cpp | 1687 ++++++++++++++++++++++++++++++++ src/hid/hid_mac.cpp | 1126 +++++++++++++++++++++ src/hid/hidapi.h | 398 ++++++++ src/interfaces/UlAiConfig.h | 10 +- src/interfaces/UlAiDevice.h | 3 + src/interfaces/UlAiInfo.h | 4 + src/interfaces/UlAoConfig.h | 5 + src/interfaces/UlAoDevice.h | 1 + src/interfaces/UlDaqDevice.h | 1 - src/interfaces/UlDioConfig.h | 4 + src/main.cpp | 6 +- src/ul_internal.h | 2 +- src/ulc.cpp | 412 +++++++- src/uldaq.h | 261 ++++- src/usb/Usb1208fsPlus.cpp | 2 +- src/usb/Usb1208fsPlus.h | 2 +- src/usb/Usb1208hs.cpp | 3 +- src/usb/Usb1208hs.h | 2 +- src/usb/Usb1608fsPlus.cpp | 2 +- src/usb/Usb1608fsPlus.h | 2 +- src/usb/Usb1608g.cpp | 3 +- src/usb/Usb1608g.h | 2 +- src/usb/Usb1808.cpp | 2 +- src/usb/Usb1808.h | 2 +- src/usb/Usb20x.cpp | 3 +- src/usb/Usb20x.h | 2 +- src/usb/Usb26xx.cpp | 3 +- src/usb/Usb26xx.h | 2 +- src/usb/UsbCtrx.cpp | 2 +- src/usb/UsbCtrx.h | 2 +- src/usb/UsbDaqDevice.cpp | 43 +- src/usb/UsbDaqDevice.h | 3 +- src/usb/UsbDio32hs.cpp | 2 +- src/usb/UsbDio32hs.h | 2 +- src/usb/UsbFpgaDevice.cpp | 103 +- src/usb/UsbFpgaDevice.h | 2 +- src/usb/UsbScanTransferIn.cpp | 6 +- src/usb/UsbScanTransferOut.cpp | 11 +- src/usb/UsbScanTransferOut.h | 2 +- src/usb/ai/AiUsb1208fsPlus.cpp | 2 +- src/usb/ai/AiUsb1208hs.cpp | 3 +- src/usb/ai/AiUsb1608fsPlus.cpp | 2 +- src/usb/ai/AiUsb1608fsPlus.h | 1 - src/usb/ai/AiUsb1608g.cpp | 3 +- src/usb/ai/AiUsb1808.cpp | 43 +- src/usb/ai/AiUsb20x.cpp | 2 + src/usb/ai/AiUsb26xx.cpp | 3 +- src/usb/ai/AiUsbBase.cpp | 22 +- src/usb/ao/AoUsb1208fsPlus.cpp | 1 + src/usb/ao/AoUsb1208hs.cpp | 1 + src/usb/ao/AoUsb1808.cpp | 31 +- src/usb/ao/AoUsbBase.cpp | 24 +- src/usb/ao/AoUsbBase.h | 1 - src/usb/ctr/CtrUsb1808.cpp | 27 +- src/usb/ctr/CtrUsbCtrx.cpp | 41 +- src/usb/ctr/CtrUsbCtrx.h | 1 - src/usb/daqi/DaqIUsb1808.cpp | 113 ++- src/usb/daqi/DaqIUsbBase.cpp | 2 +- src/usb/daqi/DaqIUsbBase.h | 1 - src/usb/daqi/DaqIUsbCtrx.cpp | 2 + src/usb/daqo/DaqOUsb1808.cpp | 84 +- src/usb/daqo/DaqOUsbBase.cpp | 2 +- src/usb/daqo/DaqOUsbBase.h | 1 - src/usb/dio/DioUsb1808.cpp | 27 +- src/usb/dio/DioUsbCtrx.cpp | 14 +- src/usb/dio/DioUsbDio32hs.cpp | 43 + src/usb/dio/DioUsbDio32hs.h | 3 + src/usb/dio/UsbDInScan.cpp | 4 +- src/usb/dio/UsbDOutScan.cpp | 2 +- src/usb/tmr/TmrUsb1208hs.cpp | 2 + src/usb/tmr/TmrUsb1808.cpp | 2 + src/utility/Endian.h | 22 +- src/utility/ErrorMap.cpp | 8 +- src/utility/EuScale.cpp | 5 + 172 files changed, 9929 insertions(+), 510 deletions(-) create mode 100644 examples/TIn.c create mode 100644 src/hid/HidDaqDevice.cpp create mode 100644 src/hid/HidDaqDevice.h create mode 100644 src/hid/Usb3100.cpp create mode 100644 src/hid/Usb3100.h create mode 100644 src/hid/UsbDio24.cpp create mode 100644 src/hid/UsbDio24.h create mode 100644 src/hid/UsbDio96h.cpp create mode 100644 src/hid/UsbDio96h.h create mode 100644 src/hid/UsbErbxx.cpp create mode 100644 src/hid/UsbErbxx.h create mode 100644 src/hid/UsbPdiso8.cpp create mode 100644 src/hid/UsbPdiso8.h create mode 100644 src/hid/UsbSsrxx.cpp create mode 100644 src/hid/UsbSsrxx.h create mode 100644 src/hid/UsbTemp.cpp create mode 100644 src/hid/UsbTemp.h create mode 100644 src/hid/UsbTempAi.cpp create mode 100644 src/hid/UsbTempAi.h create mode 100644 src/hid/ai/AiHidBase.cpp create mode 100644 src/hid/ai/AiHidBase.h create mode 100644 src/hid/ai/AiUsbTemp.cpp create mode 100644 src/hid/ai/AiUsbTemp.h create mode 100644 src/hid/ai/AiUsbTempAi.cpp create mode 100644 src/hid/ai/AiUsbTempAi.h create mode 100644 src/hid/ao/AoHidBase.cpp create mode 100644 src/hid/ao/AoHidBase.h create mode 100644 src/hid/ao/AoUsb3100.cpp create mode 100644 src/hid/ao/AoUsb3100.h create mode 100644 src/hid/ctr/CtrHid.cpp create mode 100644 src/hid/ctr/CtrHid.h create mode 100644 src/hid/ctr/CtrHidBase.cpp create mode 100644 src/hid/ctr/CtrHidBase.h create mode 100644 src/hid/ctr/CtrUsbDio24.cpp create mode 100644 src/hid/ctr/CtrUsbDio24.h create mode 100644 src/hid/dio/DioHidAux.cpp create mode 100644 src/hid/dio/DioHidAux.h create mode 100644 src/hid/dio/DioHidBase.cpp create mode 100644 src/hid/dio/DioHidBase.h create mode 100644 src/hid/dio/DioUsbDio24.cpp create mode 100644 src/hid/dio/DioUsbDio24.h create mode 100644 src/hid/dio/DioUsbDio96h.cpp create mode 100644 src/hid/dio/DioUsbDio96h.h create mode 100644 src/hid/dio/DioUsbErbxx.cpp create mode 100644 src/hid/dio/DioUsbErbxx.h create mode 100644 src/hid/dio/DioUsbPdiso8.cpp create mode 100644 src/hid/dio/DioUsbPdiso8.h create mode 100644 src/hid/dio/DioUsbSsrxx.cpp create mode 100644 src/hid/dio/DioUsbSsrxx.h create mode 100644 src/hid/hid_linux.cpp create mode 100644 src/hid/hid_mac.cpp create mode 100644 src/hid/hidapi.h diff --git a/README.md b/README.md index a83c86e..551f96d 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,13 @@ Building the **uldaq** package requires C/C++ compilers, make tool, and the deve $ sudo zypper install gcc gcc-c++ make $ sudo zypper install libusb-devel ``` + - MacOS (Version 10.11 or later recommended) + + ``` + $ xcode-select --install + $ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + $ brew install libusb + ``` ### Build Instructions --------------------- @@ -42,18 +49,21 @@ Building the **uldaq** package requires C/C++ compilers, make tool, and the deve ``` Linux -     $ wget https://github.com/mccdaq/uldaq/releases/download/v1.0.0/libuldaq-1.0.0.tar.bz2 +     $ wget https://github.com/mccdaq/uldaq/releases/download/v1.1.0/libuldaq-1.1.0.tar.bz2 + + MacOS +     $ curl -L -O https://github.com/mccdaq/uldaq/releases/download/v1.1.0/libuldaq-1.1.0.tar.bz2 ``` 2. Extract the tar file: ``` -  $ tar -xvjf libuldaq-1.0.0.tar.bz2 +  $ tar -xvjf libuldaq-1.1.0.tar.bz2 ``` 3. Run the following commands to build and install the library: ``` -  $ cd libuldaq-1.0.0 +  $ cd libuldaq-1.1.0  $ ./configure && make $ sudo make install ``` @@ -120,7 +130,7 @@ int main(void) } ``` ### Support/Feedback -The **uldaq** package is supported by MCC. For support for uldaq, contact technical through [support page](https://www.mccdaq.com/support/support_form.aspx). Please include detailed steps on how to reproduce the problem in your request. +The **uldaq** package is supported by MCC. For support for uldaq, contact technical support through [support page](https://www.mccdaq.com/support/support_form.aspx). Please include detailed steps on how to reproduce the problem in your request. ### Documentation Online help for the Universal Library for Linux is available for [C/C++](https://www.mccdaq.com/PDFs/Manuals/UL-Linux/c/index.html) and [Python](https://www.mccdaq.com/PDFs/Manuals/UL-Linux/python/index.html). diff --git a/configure.ac b/configure.ac index e8429e0..b117a10 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ m4_define([LIBUL_MAJOR], 1) -m4_define([LIBUL_MINOR], 0) +m4_define([LIBUL_MINOR], 1) m4_define([LIBUL_MICRO], 0) m4_define([LIBUL_RC],) @@ -9,9 +9,9 @@ AC_INIT([libuldaq],[LIBUL_MAJOR[.]LIBUL_MINOR[.]LIBUL_MICRO[]LIBUL_RC],[info@mcc # These numbers should be tweaked on every release. Read carefully: # http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html # http://sourceware.org/autobook/autobook/autobook_91.html -lt_current=1 +lt_current=2 lt_revision=0 -lt_age=0 +lt_age=1 LTLDFLAGS="-version-info ${lt_current}:${lt_revision}:${lt_age}" CFLAGS='-O3' diff --git a/examples/AIn.c b/examples/AIn.c index 515a32b..ab38905 100644 --- a/examples/AIn.c +++ b/examples/AIn.c @@ -38,8 +38,8 @@ int main(void) int lowChan = 0; int highChan = 3; int chan = 0; - AiInputMode inputMode = AI_DIFFERENTIAL; - Range range = BIP10VOLTS; + AiInputMode inputMode; + Range range; AInFlag flags = AIN_FF_DEFAULT; int hasAI = 0; diff --git a/examples/AInScan.c b/examples/AInScan.c index 4e60e8b..a52ef95 100644 --- a/examples/AInScan.c +++ b/examples/AInScan.c @@ -42,8 +42,8 @@ int main(void) // set some variables that are used to acquire data int lowChan = 0; int highChan = 3; - AiInputMode inputMode = AI_SINGLE_ENDED; - Range range = BIP10VOLTS; + AiInputMode inputMode; + Range range; int samplesPerChannel = 10000; double rate = 1000; ScanOption scanOptions = (ScanOption) (SO_DEFAULTIO | SO_CONTINUOUS); diff --git a/examples/AInScanWithEvents.c b/examples/AInScanWithEvents.c index b23ec9b..fa58fa0 100644 --- a/examples/AInScanWithEvents.c +++ b/examples/AInScanWithEvents.c @@ -54,8 +54,8 @@ int main(void) // set some variables that are used to acquire data int lowChan = 0; int highChan = 3; - AiInputMode inputMode = AI_SINGLE_ENDED; - Range range = BIP10VOLTS; + AiInputMode inputMode; + Range range; int samplesPerChannel = 10000; AInScanFlag flags = AINSCAN_FF_DEFAULT; DaqEventType eventTypes = (DaqEventType) (DE_ON_DATA_AVAILABLE | DE_ON_INPUT_SCAN_ERROR | DE_ON_END_OF_INPUT_SCAN); diff --git a/examples/AInScanWithQueue.c b/examples/AInScanWithQueue.c index df382f4..02aa557 100644 --- a/examples/AInScanWithQueue.c +++ b/examples/AInScanWithQueue.c @@ -32,7 +32,7 @@ #define MAX_RANGE_COUNT 8 #define MAX_STR_LENGTH 64 #define MAX_SCAN_OPTIONS_LENGTH 256 -#define MAX_QUEUE_SIZE 8 // arbitrary limit for the size for the queue +#define MAX_QUEUE_SIZE 32 // arbitrary limit for the size for the queue int main(void) { @@ -46,8 +46,8 @@ int main(void) // parameters are ignored since they are specified in queueArray int lowChan = 0; int highChan = 3; - AiInputMode inputMode = AI_SINGLE_ENDED; - Range range = BIP10VOLTS; + AiInputMode inputMode; + Range range; // set some variables that are used to acquire data int samplesPerChannel = 10000; @@ -121,9 +121,6 @@ int main(void) goto end; } - // get the analog input ranges - err = getAiInfoRanges(daqDeviceHandle, inputMode, &numRanges, ranges); - // get the queue types err = getAiInfoQueueTypes(daqDeviceHandle, &queueTypes); @@ -133,9 +130,6 @@ int main(void) if (highChan >= numberOfChannels) highChan = numberOfChannels - 1; - if (highChan >= MAX_QUEUE_SIZE) - highChan = MAX_QUEUE_SIZE - 1; - chanCount = highChan - lowChan + 1; // does the device support a queue @@ -145,6 +139,9 @@ int main(void) goto end; } + // get the analog input ranges + err = getAiInfoRanges(daqDeviceHandle, inputMode, &numRanges, ranges); + // assign each channel in the queue an input mode (SE/DIFF) and a range ... if // multiple ranges are supported, we will cycle through them and repeat ranges if the // number of channels exceeds the number of ranges @@ -199,6 +196,10 @@ int main(void) // clear the display ret = system("clear"); + // the range argument of the ulAInScan function is ignored becasue the ragnes are set by the ulAInLoadQueue() function, + // however the range variable is initilized to an arbitrary range here to prevent compiler warnings + range = BIP10VOLTS; + // start the acquisition // // when using the queue, the lowChan, highChan, inputMode, and range diff --git a/examples/AInScanWithTrigger.c b/examples/AInScanWithTrigger.c index 2f0274f..6908fde 100644 --- a/examples/AInScanWithTrigger.c +++ b/examples/AInScanWithTrigger.c @@ -44,14 +44,15 @@ int main(void) // set some variables that are used to acquire data int lowChan = 0; int highChan = 3; - AiInputMode inputMode = AI_SINGLE_ENDED; - Range range = BIP10VOLTS; + AiInputMode inputMode; + Range range; int samplesPerChannel = 10000; double rate = 1000; ScanOption scanOptions = (ScanOption) (SO_DEFAULTIO | SO_CONTINUOUS | SO_EXTTRIGGER); AInScanFlag flags = AINSCAN_FF_DEFAULT; int hasAI = 0; + int hasPacer = 0; int numberOfChannels = 0; TriggerType triggerType; int index = 0; @@ -104,6 +105,14 @@ int main(void) goto end; } + // verify the specified device supports hardware pacing for analog input + err = getAiInfoHasPacer(daqDeviceHandle, &hasPacer); + if (!hasPacer) + { + printf("\nThe specified DAQ device does not support hardware paced analog input\n"); + goto end; + } + printf("\nConnecting to device %s - please wait ...\n", devDescriptors[descriptorIndex].devString); // establish a connection to the device diff --git a/examples/AOut.c b/examples/AOut.c index a9b3544..b4fdbdc 100644 --- a/examples/AOut.c +++ b/examples/AOut.c @@ -33,7 +33,7 @@ int main(void) unsigned int numDevs = MAX_DEV_COUNT; int channel = 0; - Range range = BIP10VOLTS; + Range range; AOutFlag flags = AOUT_FF_DEFAULT; int hasAO = 0; diff --git a/examples/AOutScan.c b/examples/AOutScan.c index fb8f45e..ebbb4cf 100644 --- a/examples/AOutScan.c +++ b/examples/AOutScan.c @@ -45,7 +45,7 @@ int main(void) int lowChan = 0; int highChan = 0; - Range range = BIP10VOLTS; + Range range; const int samplesPerChannel = 1000; double rate = 1000; ScanOption scanOptions = (ScanOption) (SO_DEFAULTIO | SO_CONTINUOUS); diff --git a/examples/DBitIn.c b/examples/DBitIn.c index c146ef9..49b5c63 100644 --- a/examples/DBitIn.c +++ b/examples/DBitIn.c @@ -39,8 +39,8 @@ int main(void) int hasDIO = 0; int bitsPerPort = 0; - DigitalPortType portType = AUXPORT; - DigitalPortIoType portIoType = DPIOT_IN; + DigitalPortType portType; + DigitalPortIoType portIoType; char portTypeStr[MAX_STR_LENGTH]; char portIoTypeStr[MAX_STR_LENGTH]; @@ -95,11 +95,11 @@ int main(void) if (err != ERR_NO_ERROR) goto end; - // get the port types for the device (AUXPORT0, FIRSTPORTA, ...) + // get the first port type (AUXPORT0, FIRSTPORTA, ...) err = getDioInfoFirstSupportedPortType(daqDeviceHandle, &portType, portTypeStr); - // get the port IO type - err = getDioInfoPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); + // get the I/O type for the fisrt port + err = getDioInfoFirstSupportedPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); // get the number of bits for the first port (port index = 0) err = getDioInfoNumberOfBitsForFirstPort(daqDeviceHandle, &bitsPerPort); @@ -116,7 +116,7 @@ int main(void) break; } } - else + else if (portIoType == DPIOT_IO) { // configure the entire port for input err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); diff --git a/examples/DBitOut.c b/examples/DBitOut.c index eef494d..c6ddbc6 100644 --- a/examples/DBitOut.c +++ b/examples/DBitOut.c @@ -39,8 +39,8 @@ int main(void) int hasDIO = 0; int bitsPerPort = 0; - DigitalPortType portType = AUXPORT; - DigitalPortIoType portIoType = DPIOT_IN; + DigitalPortType portType; + DigitalPortIoType portIoType; int maxPortValue = 0; @@ -99,11 +99,11 @@ int main(void) if (err != ERR_NO_ERROR) goto end; - // get the port types for the device (AUXPORT0, FIRSTPORTA, ...) + // get the first port type (AUXPORT0, FIRSTPORTA, ...) err = getDioInfoFirstSupportedPortType(daqDeviceHandle, &portType, portTypeStr); - // get the port IO type - err = getDioInfoPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); + // get the I/O type for the fisrt port + err = getDioInfoFirstSupportedPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); // get the number of bits for the first port (port index = 0) err = getDioInfoNumberOfBitsForFirstPort(daqDeviceHandle, &bitsPerPort); @@ -120,7 +120,7 @@ int main(void) break; } } - else + else if (portIoType == DPIOT_IO) { // configure the entire port for output err = ulDConfigPort(daqDeviceHandle, portType, DD_OUTPUT); @@ -140,7 +140,7 @@ int main(void) ret = system("clear"); - while(err == ERR_NO_ERROR && err == ERR_NO_ERROR) + while(err == ERR_NO_ERROR) { // reset the cursor to the top of the display and // show the termination message @@ -166,8 +166,11 @@ int main(void) usleep(100000); } - // before leaving, configure the entire port for input - err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + if(portIoType == DPIOT_IO || portIoType == DPIOT_BITIO) + { + // before leaving, configure the entire port for input + err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + } // disconnect from the DAQ device ulDisconnectDaqDevice(daqDeviceHandle); diff --git a/examples/DIn.c b/examples/DIn.c index bfff827..46e294a 100644 --- a/examples/DIn.c +++ b/examples/DIn.c @@ -37,9 +37,11 @@ int main(void) unsigned int numDevs = MAX_DEV_COUNT; int hasDIO = 0; - DigitalPortType portType = AUXPORT; + DigitalPortType portType; + DigitalPortIoType portIoType; char portTypeStr[MAX_STR_LENGTH]; + char portIoTypeStr[MAX_STR_LENGTH]; unsigned long long data = 0; UlError err = ERR_NO_ERROR; @@ -90,15 +92,22 @@ int main(void) if (err != ERR_NO_ERROR) goto end; - // get the port types for the device (AUXPORT0, FIRSTPORTA, ...) + // get the first port type (AUXPORT0, FIRSTPORTA, ...) err = getDioInfoFirstSupportedPortType(daqDeviceHandle, &portType, portTypeStr); - // configure the first port for input - err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + // get the I/O type for the fisrt port + err = getDioInfoFirstSupportedPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); + + if(portIoType == DPIOT_IO || portIoType == DPIOT_BITIO) + { + // configure the first port for input + err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + } printf("\n%s ready\n", devDescriptors[descriptorIndex].devString); printf(" Function demonstrated: ulDIn()\n"); printf(" Port: %s\n", portTypeStr); + printf(" Port I/O type: %s\n", portIoTypeStr); printf("\nHit ENTER to continue\n"); ret = scanf("%c", &c); diff --git a/examples/DInScan.c b/examples/DInScan.c index a2814d1..082a3fd 100644 --- a/examples/DInScan.c +++ b/examples/DInScan.c @@ -38,8 +38,8 @@ int main(void) DaqDeviceHandle daqDeviceHandle = 0; unsigned int numDevs = MAX_DEV_COUNT; - DigitalPortType lowPort = AUXPORT; - DigitalPortType highPort = AUXPORT; + DigitalPortType lowPort; + DigitalPortType highPort; int samplesPerPort = 1000; double rate = 100; ScanOption scanOptions = (ScanOption) (SO_DEFAULTIO | SO_CONTINUOUS); @@ -52,7 +52,7 @@ int main(void) char portTypeStr[MAX_STR_LENGTH]; char scanOptionsStr[MAX_SCAN_OPTIONS_LENGTH]; - DigitalPortType portType = AUXPORT; + DigitalPortType portType; int chanCount = 1; unsigned long long* buffer; diff --git a/examples/DOut.c b/examples/DOut.c index 62b3069..5e745e7 100644 --- a/examples/DOut.c +++ b/examples/DOut.c @@ -36,9 +36,11 @@ int main(void) int hasDIO = 0; int bitsPerPort = 0; - DigitalPortType portType = AUXPORT; + DigitalPortType portType; + DigitalPortIoType portIoType; char portTypeStr[MAX_STR_LENGTH]; + char portIoTypeStr[MAX_STR_LENGTH]; unsigned long long maxPortValue = 0; @@ -93,14 +95,20 @@ int main(void) if (err != ERR_NO_ERROR) goto end; - // get the port types for the device (AUXPORT0, FIRSTPORTA, ...) + // get the first port type (AUXPORT0, FIRSTPORTA, ...) err = getDioInfoFirstSupportedPortType(daqDeviceHandle, &portType, portTypeStr); + // get the I/O type for the fisrt port + err = getDioInfoFirstSupportedPortIoType(daqDeviceHandle, &portIoType, portIoTypeStr); + // get the number of bits for the first port err = getDioInfoNumberOfBitsForFirstPort(daqDeviceHandle, &bitsPerPort); - // configure the first port for output - err = ulDConfigPort(daqDeviceHandle, portType, DD_OUTPUT); + if(portIoType == DPIOT_IO || portIoType == DPIOT_BITIO) + { + // configure the first port for output + err = ulDConfigPort(daqDeviceHandle, portType, DD_OUTPUT); + } // calculate the max value for the port maxPortValue = (unsigned long long) pow(2.0, (double)bitsPerPort) - 1; @@ -108,6 +116,7 @@ int main(void) printf("\n%s ready\n", devDescriptors[descriptorIndex].devString); printf(" Function demonstrated: ulDOut()\n"); printf(" Port: %s\n", portTypeStr); + printf(" Port I/O type: %s\n", portIoTypeStr); printf("\nHit ENTER to continue\n"); ret = scanf("%c", &c); @@ -142,8 +151,11 @@ int main(void) usleep(100000); } - // before leaving, configure the entire first port for input - err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + if(portIoType == DPIOT_IO || portIoType == DPIOT_BITIO) + { + // before leaving, configure the entire port for input + err = ulDConfigPort(daqDeviceHandle, portType, DD_INPUT); + } // disconnect from the DAQ device ulDisconnectDaqDevice(daqDeviceHandle); diff --git a/examples/DOutScan.c b/examples/DOutScan.c index 3d28e79..0858380 100644 --- a/examples/DOutScan.c +++ b/examples/DOutScan.c @@ -42,8 +42,8 @@ int main(void) DaqDeviceHandle daqDeviceHandle = 0; unsigned int numDevs = MAX_DEV_COUNT; - DigitalPortType lowPort = AUXPORT; - DigitalPortType highPort = AUXPORT; + DigitalPortType lowPort; + DigitalPortType highPort; const int samplesPerPort = 1000; double rate = 1000; ScanOption scanOptions = (ScanOption) (SO_DEFAULTIO | SO_CONTINUOUS); @@ -57,7 +57,7 @@ int main(void) char portTypeStr[MAX_STR_LENGTH]; char scanOptionsStr[MAX_SCAN_OPTIONS_LENGTH]; - DigitalPortType portType = AUXPORT; + DigitalPortType portType; int chanCount = 1; unsigned long long* buffer; diff --git a/examples/Makefile.am b/examples/Makefile.am index b7d4b48..966dbf5 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -7,7 +7,8 @@ CIn CInScan CInScanWithEncoder\ DaqInScan DaqInScanWithTrigger\ DaqOutScan\ DIn DBitIn DInScan DOut DBitOut DOutScan\ -TmrPulseOut +TmrPulseOut\ +TIn AIn_SOURCES = AIn.c utility.h AInScan_SOURCES = AInScan.c @@ -34,6 +35,7 @@ DBitOut_SOURCES = DBitOut.c DOutScan_LDADD = $(LDADD) -lm DOutScan_SOURCES = DOutScan.c TmrPulseOut_SOURCES = TmrPulseOut.c +TIn_SOURCES = TIn.c diff --git a/examples/TIn.c b/examples/TIn.c new file mode 100644 index 0000000..7456326 --- /dev/null +++ b/examples/TIn.c @@ -0,0 +1,165 @@ +/* + UL call demonstrated: ulTIn() + + Purpose: Reads the user-specified temperature input channels + + Demonstration: Displays the temperature data for each of + the user-specified channels + + Steps: + 1. Call ulGetDaqDeviceInventory() to get the list of available DAQ devices + 2. Call ulCreateDaqDevice() to to get a handle for the first DAQ device + 3. Verify the DAQ device has an analog input subsystem + 4. Verify the DAQ device supports temperature input + 5. Call ulConnectDaqDevice() to establish a UL connection to the DAQ device + 6. Call ulTIn() for each channel specified to read a value from the A/D input channel + 7. Display the data for each channel + 8. Call ulDisconnectDaqDevice() and ulReleaseDaqDevice() before exiting the process. +*/ + +#include +#include +#include "uldaq.h" +#include "utility.h" + +#define MAX_DEV_COUNT 100 +#define MAX_STR_LENGTH 64 + +int main(void) +{ + int descriptorIndex = 0; + DaqDeviceDescriptor devDescriptors[MAX_DEV_COUNT]; + DaqDeviceInterface interfaceType = ANY_IFC; + DaqDeviceHandle daqDeviceHandle = 0; + unsigned int numDevs = MAX_DEV_COUNT; + + // set some variables used to acquire data + int lowChan = 0; + int highChan = 3; + int chan = 0; + TempScale scale = TS_CELSIUS; + TInFlag flags = TIN_FF_DEFAULT; + + int hasAI = 0; + int hasTempChan = 0; + + char chanTypeStr[MAX_STR_LENGTH]; + char sensorStr[MAX_STR_LENGTH]; + + int i = 0; + double data = 0; + UlError err = ERR_NO_ERROR; + + int __attribute__((unused)) ret; + char c; + + // Get descriptors for all of the available DAQ devices + err = ulGetDaqDeviceInventory(interfaceType, devDescriptors, &numDevs); + + if (err != ERR_NO_ERROR) + goto end; + + // verify at least one DAQ device is detected + if (numDevs == 0) + { + printf("No DAQ device is detected\n"); + goto end; + } + + printf("Found %d DAQ device(s)\n", numDevs); + for (i = 0; i < (int) numDevs; i++) + printf(" %s: (%s)\n", devDescriptors[i].productName, devDescriptors[i].uniqueId); + + // get a handle to the DAQ device associated with the first descriptor + daqDeviceHandle = ulCreateDaqDevice(devDescriptors[descriptorIndex]); + + if (daqDeviceHandle == 0) + { + printf ("\nUnable to create a handle to the specified DAQ device\n"); + goto end; + } + + // verify the specified DAQ device supports analog input + err = getDevInfoHasAi(daqDeviceHandle, &hasAI); + if (!hasAI) + { + printf("\nThe specified DAQ device does not support analog input\n"); + goto end; + } + + // verify the specified DAQ device has temperature channels + err = getAiInfoHasTempChan(daqDeviceHandle, &hasTempChan); + if (!hasTempChan) + { + printf("\nThe specified DAQ device does not have a temperature channel\n"); + goto end; + } + + printf("\nConnecting to device %s - please wait ...\n", devDescriptors[descriptorIndex].devString); + + // establish a connection to the DAQ device + err = ulConnectDaqDevice(daqDeviceHandle); + + if (err != ERR_NO_ERROR) + goto end; + + printf("\n%s ready\n", devDescriptors[descriptorIndex].devString); + printf(" Function demonstrated: ulTIn()\n"); + printf(" Channels: %d - %d\n", lowChan, highChan); + + for (chan = lowChan; chan <= highChan; chan++) + { + getAiInfoTempChanConfig(daqDeviceHandle, chan, chanTypeStr, sensorStr); + printf(" Channel %d: type: %s (%s)\n", chan, chanTypeStr, sensorStr); + } + + printf("\nHit ENTER to continue\n"); + + ret = scanf("%c", &c); + + ret = system("clear"); + + while(err == ERR_NO_ERROR && !enter_press()) + { + // reset the cursor to the top of the display and + // show the termination message + resetCursor(); + printf("Hit 'Enter' to terminate the process\n\n"); + + // display data for the first 4 analog input channels + for (chan = lowChan; chan <= highChan; chan++) + { + err = ulTIn(daqDeviceHandle, chan, scale, flags, &data); + + if(err == ERR_NO_ERROR) + printf("Channel(%d) Data: %10.6f %20s\n", chan, data, ""); + else if(err == ERR_OPEN_CONNECTION) + { + printf("Channel(%d) Data: %10.6f (open connection)\n", chan, data); + err = ERR_NO_ERROR; + } + } + + usleep(500000); + } + + // disconnect from the DAQ device + ulDisconnectDaqDevice(daqDeviceHandle); + +end: + + // release the handle to the DAQ device + if(daqDeviceHandle) + ulReleaseDaqDevice(daqDeviceHandle); + + if(err != ERR_NO_ERROR) + { + char errMsg[ERR_MSG_LEN]; + ulGetErrMsg(err, errMsg); + printf("Error Code: %d \n", err); + printf("Error Message: %s \n", errMsg); + } + + return 0; +} + diff --git a/examples/utility.h b/examples/utility.h index c846e4a..8466ddf 100644 --- a/examples/utility.h +++ b/examples/utility.h @@ -166,6 +166,9 @@ void ConvertRangeToString(Range range, char* rangeStr) case(UNIPT005VOLTS): strcpy(rangeStr, "UNIPT005VOLTS"); break; + case(MA0TO20): + strcpy(rangeStr, "MA0TO20"); + break; } } @@ -342,6 +345,10 @@ void ConvertRangeToMinMax(Range range, double* min, double* max) *min = 0.0; *max = 0.005; break; + case(MA0TO20): + *min = 0.0; + *max = 20.0; + break; } } @@ -640,6 +647,56 @@ void ConvertDaqOChanTypeToString(DaqOutChanType daqoChanType, char* daqoChanType } } +void ConvertTCTypeToString(TcType type, char* typeStr) +{ + switch(type) + { + case(TC_J): + strcpy(typeStr, "J"); + break; + case(TC_K): + strcpy(typeStr, "K"); + break; + case(TC_T): + strcpy(typeStr, "T"); + break; + case(TC_E): + strcpy(typeStr, "E"); + break; + case(TC_R): + strcpy(typeStr, "R"); + break; + case(TC_S): + strcpy(typeStr, "S"); + break; + case(TC_B): + strcpy(typeStr, "B"); + break; + case(TC_N): + strcpy(typeStr, "N"); + break; + } +} + +void ConvertSensorConnectionTypeToString(SensorConnectionType type, char* typeStr) +{ + switch(type) + { + case(SCT_2_WIRE_1): + strcpy(typeStr, "2-wire (1 sensor)"); + break; + case(SCT_2_WIRE_2): + strcpy(typeStr, "2-wire (2 sensors)"); + break; + case(SCT_3_WIRE): + strcpy(typeStr, "3-wire"); + break; + case(SCT_4_WIRE): + strcpy(typeStr, "4-wire"); + break; + } +} + /**************************************************************************** * Device Info Functions ****************************************************************************/ @@ -752,7 +809,7 @@ UlError getAiInfoFirstTriggerType(DaqDeviceHandle daqDeviceHandle, TriggerType* long long triggerTypes = 0; err = ulAIGetInfo(daqDeviceHandle, AI_INFO_TRIG_TYPES, 0, &triggerTypes); - if (err == ERR_NO_ERROR) + if (err == ERR_NO_ERROR && triggerTypes != 0) { // use the first available trigger type long long triggerMask = 1; @@ -810,9 +867,16 @@ UlError getAiInfoFirstSupportedInputMode(DaqDeviceHandle daqDeviceHandle, int* n err = ulAIGetInfo(daqDeviceHandle, AI_INFO_NUM_CHANS_BY_MODE, AI_SINGLE_ENDED, &numChans); if (numChans > 0) + { *inputMode = AI_SINGLE_ENDED; + } else - *inputMode = AI_DIFFERENTIAL; + { + err = ulAIGetInfo(daqDeviceHandle, AI_INFO_NUM_CHANS_BY_MODE, AI_DIFFERENTIAL, &numChans); + + if (numChans > 0) + *inputMode = AI_DIFFERENTIAL; + } ConvertInputModeToString(*inputMode, inputModeStr); @@ -854,6 +918,67 @@ UlError getAiInfoQueueTypes(DaqDeviceHandle daqDeviceHandle, int* queueTypes) return err; } +UlError getAiInfoHasTempChan(DaqDeviceHandle daqDeviceHandle, int* hasTempChan) +{ + long long chanTypeMask = 0; + UlError err = ERR_NO_ERROR; + + err = ulAIGetInfo(daqDeviceHandle, AI_INFO_CHAN_TYPES, 0, &chanTypeMask); + + if(chanTypeMask & (AI_TC | AI_RTD | AI_THERMISTOR | AI_SEMICONDUCTOR)) + *hasTempChan = 1; + else + *hasTempChan = 0; + + return err; +} + +UlError getAiInfoTempChanConfig(DaqDeviceHandle daqDeviceHandle, int chan, char* chanTypeStr, char* sensorStr) +{ + UlError err = ERR_NO_ERROR; + + long long chanType, cfg; + char typeStr[64] = ""; + char cfgStr[64] = "N/A"; + + err = ulAIGetConfig(daqDeviceHandle, AI_CFG_CHAN_TYPE, chan, &chanType); + + if(chanType == AI_TC) + { + strcpy(typeStr, "Thermocouple"); + + err = ulAIGetConfig(daqDeviceHandle, AI_CFG_CHAN_TC_TYPE, chan, &cfg); + + ConvertTCTypeToString((TcType)cfg, cfgStr); + } + else if(chanType == AI_RTD || chanType == AI_THERMISTOR) + { + if(chanType == AI_RTD) + strcpy(typeStr, "RTD"); + else + strcpy(typeStr, "Thermistor"); + + err = ulAIGetConfig(daqDeviceHandle, AI_CFG_CHAN_SENSOR_CONNECTION_TYPE, chan, &cfg); + + ConvertSensorConnectionTypeToString((SensorConnectionType) cfg, cfgStr); + } + else if(chanType == AI_SEMICONDUCTOR) + { + strcpy(typeStr, "Semicoductor"); + } + else if(chanType == AI_VOLTAGE) + { + strcpy(typeStr, "Voltage"); + } + else if(chanType == AI_DISABLED) + strcpy(typeStr, "Disabled"); + + strcpy(chanTypeStr, typeStr); + strcpy(sensorStr, cfgStr); + + return err; +} + /**************************************************************************** * Analog Output Info Functions @@ -926,7 +1051,7 @@ UlError getDioInfoNumberOfBitsForFirstPort(DaqDeviceHandle daqDeviceHandle, int* return err; } -UlError getDioInfoPortIoType(DaqDeviceHandle daqDeviceHandle, DigitalPortIoType* portIoType, char* portIoTypeStr) +UlError getDioInfoFirstSupportedPortIoType(DaqDeviceHandle daqDeviceHandle, DigitalPortIoType* portIoType, char* portIoTypeStr) { UlError err = ERR_NO_ERROR; long long ioType; @@ -1062,7 +1187,7 @@ UlError getDaqiInfoFirstTriggerType(DaqDeviceHandle daqDeviceHandle, TriggerType long long triggerTypes = 0; err = ulDaqIGetInfo(daqDeviceHandle, DAQI_INFO_TRIG_TYPES, 0, &triggerTypes); - if (err == ERR_NO_ERROR) + if (err == ERR_NO_ERROR && triggerTypes != 0) { // use the first available trigger type long long triggerMask = 1; diff --git a/fpga/USB_1808.bin b/fpga/USB_1808.bin index b4841b81515e1d325f0f6a6654e9d4a6943a0c2b..7401612ccd85c6b3d30961faefbe274617c6883d 100644 GIT binary patch literal 464196 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 #tfL5pBYmGjzaMz{L?Dp#!^psQclVm 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 diff --git a/rules/50-uldaq.rules b/rules/50-uldaq.rules index 91ac47d..645ed3c 100644 --- a/rules/50-uldaq.rules +++ b/rules/50-uldaq.rules @@ -1,4 +1,28 @@ -# allow access to usb devices by non-root users +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0076", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="007f", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0085", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0086", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="008a", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="008b", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="008c", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="008d", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0090", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0092", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0093", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0094", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0095", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="0096", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009a", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009b", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009c", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009d", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009e", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="009f", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00a2", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00a3", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00a4", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00bb", GROUP="adm", MODE="0666" +SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00bc", GROUP="adm", MODE="0666" SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00c4", GROUP="adm", MODE="0666" SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00c5", GROUP="adm", MODE="0666" SUBSYSTEM=="usb", ATTR{idVendor}=="09db", ATTR{idProduct}=="00c6", GROUP="adm", MODE="0666" diff --git a/src/AiConfig.cpp b/src/AiConfig.cpp index f69c31e..f8df817 100644 --- a/src/AiConfig.cpp +++ b/src/AiConfig.cpp @@ -37,19 +37,19 @@ TcType AiConfig::getChanTcType(int channel) return mAiDevice.getCfg_ChanTcType(channel); } -void AiConfig::setChanTempUnit(int channel, TempUnit unit) +void AiConfig::setScanChanTempUnit(int channel, TempUnit unit) { - mAiDevice.setCfg_ChanTempUnit(channel, unit); + mAiDevice.setCfg_ScanChanTempUnit(channel, unit); } -TempUnit AiConfig::getChanTempUnit(int channel) +TempUnit AiConfig::getScanChanTempUnit(int channel) { - return mAiDevice.getCfg_ChanTempUnit(channel); + return mAiDevice.getCfg_ScanChanTempUnit(channel); } -void AiConfig::setTempUnit(TempUnit unit) +void AiConfig::setScanTempUnit(TempUnit unit) { - mAiDevice.setCfg_TempUnit(unit); + mAiDevice.setCfg_ScanTempUnit(unit); } void AiConfig::setAutoZeroMode(AutoZeroMode mode) @@ -131,4 +131,14 @@ void AiConfig::getCalDateStr(char* calDate, unsigned int* maxStrLen) return mAiDevice.getCfg_CalDateStr(calDate, maxStrLen); } +void AiConfig::getChanCoefsStr(int channel, char* coefs, unsigned int* maxStrLen) +{ + return mAiDevice.getCfg_ChanCoefsStr(channel, coefs, maxStrLen); +} + +SensorConnectionType AiConfig::getChanSensorConnectionType(int channel) +{ + return mAiDevice.getCfg_SensorConnectionType(channel); +} + } /* namespace ul */ diff --git a/src/AiConfig.h b/src/AiConfig.h index daf1cad..c39402c 100644 --- a/src/AiConfig.h +++ b/src/AiConfig.h @@ -25,10 +25,10 @@ public: virtual void setChanTcType(int channel, TcType tcType); virtual TcType getChanTcType(int channel); - virtual void setChanTempUnit(int channel, TempUnit unit); - virtual TempUnit getChanTempUnit(int channel); + virtual void setScanChanTempUnit(int channel, TempUnit unit); + virtual TempUnit getScanChanTempUnit(int channel); - virtual void setTempUnit(TempUnit unit); + virtual void setScanTempUnit(TempUnit unit); //virtual TempUnit getTempUnit(); virtual void setAutoZeroMode(AutoZeroMode mode); @@ -55,6 +55,9 @@ public: virtual unsigned long long getCalDate(); // returns number of seconds since unix epoch virtual void getCalDateStr(char* calDate, unsigned int* maxStrLen); + virtual void getChanCoefsStr(int channel, char* coefs, unsigned int* maxStrLen); + virtual SensorConnectionType getChanSensorConnectionType(int channel); + private: AiDevice& mAiDevice; }; diff --git a/src/AiDevice.cpp b/src/AiDevice.cpp index 7f5b034..6abcd15 100644 --- a/src/AiDevice.cpp +++ b/src/AiDevice.cpp @@ -20,6 +20,7 @@ AiDevice::AiDevice(const DaqDevice& daqDevice) : IoDevice(daqDevice), UlAiDevice mAiConfig = new AiConfig(*this); mCalDate = 0; mCalModeEnabled = false; + mScanTempChanSupported = false; } AiDevice::~AiDevice() @@ -75,6 +76,15 @@ void AiDevice::stopBackground() throw UlException(ERR_BAD_DEV_TYPE); } +void AiDevice::tIn(int channel, TempScale scale, TInFlag flags, double* data) +{ + throw UlException(ERR_BAD_DEV_TYPE); +} +void AiDevice::tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) +{ + throw UlException(ERR_BAD_DEV_TYPE); +} + void AiDevice::check_AIn_Args(int channel, AiInputMode inputMode, Range range, AInFlag flags) const { if(!mAiInfo.isInputModeSupported(inputMode)) @@ -151,7 +161,7 @@ void AiDevice::check_AInScan_Args(int lowChan, int highChan, AiInputMode inputMo if(samplesPerChan < mMinScanSampleCount) throw UlException(ERR_BAD_SAMPLE_COUNT); - long long totalCount = samplesPerChan * numOfScanChan; + long long totalCount = (long long) samplesPerChan * numOfScanChan; if(options & SO_BURSTIO) { @@ -477,6 +487,58 @@ void AiDevice::initCustomScales() } } +void AiDevice::initTempUnits() +{ + for(int i = 0; i < mAiInfo.getNumChans(); i++) + { + mScanChanTempUnit.push_back(TU_CELSIUS); + } +} + +void AiDevice::check_TIn_Args(int channel, TempScale scale, TInFlag flags) const +{ + if(channel < 0 || channel >= mAiInfo.getNumChans()) + { + bool cjcChan = false; + + if(channel > 0) + { + for(int i = 0; i < mAiInfo.getNumCjcChans(); i++) + { + if(channel == (0x80 + i)) + { + cjcChan = true; + break; + } + } + } + + if(!cjcChan) + throw UlException(ERR_BAD_AI_CHAN); + } + + if(~mAiInfo.getTInFlags() & flags) + throw UlException(ERR_BAD_FLAG); + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); +} + +void AiDevice::check_TInArray_Args(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) const +{ + if(lowChan < 0 || highChan < 0 || lowChan >= mAiInfo.getNumChans() || highChan >= mAiInfo.getNumChans() || lowChan > highChan ) + throw UlException(ERR_BAD_AI_CHAN); + + if(~mAiInfo.getTInArrayFlags() & flags) + throw UlException(ERR_BAD_FLAG); + + if(data == NULL) + throw UlException(ERR_BAD_BUFFER); + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); +} + ////////////////////// Configuration functions ///////////////////////////////// @@ -498,31 +560,40 @@ TcType AiDevice::getCfg_ChanTcType(int channel) const throw UlException(ERR_CONFIG_NOT_SUPPORTED); } -void AiDevice::setCfg_TempUnit(TempUnit unit) +void AiDevice::setCfg_ScanTempUnit(TempUnit unit) { + if(!mScanTempChanSupported) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + if( unit < TU_CELSIUS || unit > TU_KELVIN) throw UlException(ERR_BAD_UNIT); - for(unsigned int i = 0; i < mChanTempUnit.size(); i++) - mChanTempUnit[i] = unit; + for(unsigned int i = 0; i < mScanChanTempUnit.size(); i++) + mScanChanTempUnit[i] = unit; } -void AiDevice::setCfg_ChanTempUnit(int channel, TempUnit unit) +void AiDevice::setCfg_ScanChanTempUnit(int channel, TempUnit unit) { - if(channel < 0 || channel >= (int) mChanTempUnit.size()) + if(!mScanTempChanSupported) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= (int) mScanChanTempUnit.size()) throw UlException(ERR_BAD_AI_CHAN); if( unit < TU_CELSIUS || unit > TU_KELVIN) throw UlException(ERR_BAD_UNIT); - mChanTempUnit[channel] = unit; + mScanChanTempUnit[channel] = unit; } -TempUnit AiDevice::getCfg_ChanTempUnit(int channel) const +TempUnit AiDevice::getCfg_ScanChanTempUnit(int channel) const { - if(channel < 0 || channel >= (int) mChanTempUnit.size()) + if(!mScanTempChanSupported) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= (int) mScanChanTempUnit.size()) throw UlException(ERR_BAD_AI_CHAN); - return mChanTempUnit[channel]; + return mScanChanTempUnit[channel]; } void AiDevice::setCfg_AutoZeroMode(AutoZeroMode mode) @@ -559,7 +630,7 @@ void AiDevice::setCfg_ChanCouplingMode(int channel, CouplingMode mode) } CouplingMode AiDevice::getCfg_ChanCouplingMode(int channel) { - return CM_DC; + throw UlException(ERR_CONFIG_NOT_SUPPORTED); } void AiDevice::setCfg_ChanSensorSensitivity(int channel, double sensitivity) @@ -636,8 +707,16 @@ void AiDevice::getCfg_CalDateStr(char* calDate, unsigned int* maxStrLen) throw UlException(ERR_BAD_BUFFER_SIZE); } +} +SensorConnectionType AiDevice::getCfg_SensorConnectionType(int channel) const +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} +void AiDevice::getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); } diff --git a/src/AiDevice.h b/src/AiDevice.h index 951e629..3244ba1 100644 --- a/src/AiDevice.h +++ b/src/AiDevice.h @@ -32,6 +32,9 @@ public: virtual UlError getStatus(ScanStatus* status, TransferStatus* xferStatus); virtual void stopBackground(); + virtual void tIn(int channel, TempScale scale, TInFlag flags, double* data); + virtual void tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]); + double convertTempUnit(double tempC, TempUnit unit); ////////////////////// Configuration functions ///////////////////////////////// @@ -41,11 +44,11 @@ public: virtual void setCfg_ChanTcType(int channel, TcType tcType); virtual TcType getCfg_ChanTcType(int channel) const; - virtual void setCfg_TempUnit(TempUnit unit); + virtual void setCfg_ScanTempUnit(TempUnit unit); //virtual TempUnit getCfg_TempUnit() const; - virtual void setCfg_ChanTempUnit(int channel, TempUnit unit); - virtual TempUnit getCfg_ChanTempUnit(int channel) const; + virtual void setCfg_ScanChanTempUnit(int channel, TempUnit unit); + virtual TempUnit getCfg_ScanChanTempUnit(int channel) const; virtual void setCfg_AutoZeroMode(AutoZeroMode mode); virtual AutoZeroMode getCfg_AutoZeroMode() const; @@ -70,6 +73,10 @@ public: virtual unsigned long long getCfg_CalDate(); virtual void getCfg_CalDateStr(char* calDate, unsigned int* maxStrLen); + virtual SensorConnectionType getCfg_SensorConnectionType(int channel) const; + virtual void getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const; + + protected: virtual void loadAdcCoefficients() = 0; virtual int getCalCoefIndex(int channel, AiInputMode inputMode, Range range) const = 0; @@ -80,10 +87,12 @@ protected: bool queueEnabled() const; int queueLength() const; - void check_AIn_Args(int channel, AiInputMode inputMode, Range range, AInFlag flags) const; - void check_AInScan_Args(int lowChan, int highChan, AiInputMode inputMode, Range range, int samplesPerChan, double rate, ScanOption options, AInScanFlag flags, double data[]) const; - void check_AInLoadQueue_Args(const AiQueueElement queue[], unsigned int numElements) const; - void check_AInSetTrigger_Args(TriggerType trigtype, int trigChan, double level, double variance, unsigned int retriggerCount) const; + virtual void check_AIn_Args(int channel, AiInputMode inputMode, Range range, AInFlag flags) const; + virtual void check_AInScan_Args(int lowChan, int highChan, AiInputMode inputMode, Range range, int samplesPerChan, double rate, ScanOption options, AInScanFlag flags, double data[]) const; + virtual void check_AInLoadQueue_Args(const AiQueueElement queue[], unsigned int numElements) const; + virtual void check_AInSetTrigger_Args(TriggerType trigtype, int trigChan, double level, double variance, unsigned int retriggerCount) const; + virtual void check_TIn_Args(int channel, TempScale scale, TInFlag flags) const; + virtual void check_TInArray_Args(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) const; bool isValidChanQueue(const AiQueueElement queue[], unsigned int numElements) const; bool isValidGainQueue(const AiQueueElement queue[], unsigned int numElements) const; @@ -92,6 +101,8 @@ protected: void initCustomScales(); std::vector getCustomScales(int lowChan, int highChan) const; + void initTempUnits(); + void enableCalMode(bool enable) { mCalModeEnabled = enable;} bool calModeEnabled() const { return mCalModeEnabled;} @@ -104,7 +115,8 @@ protected: std::vector mCustomScales; std::vector mAQueue; - std::vector mChanTempUnit; + bool mScanTempChanSupported; + std::vector mScanChanTempUnit; unsigned long long mCalDate; // cal date in sec diff --git a/src/AiInfo.cpp b/src/AiInfo.cpp index beb98bc..540e634 100644 --- a/src/AiInfo.cpp +++ b/src/AiInfo.cpp @@ -37,6 +37,9 @@ AiInfo::AiInfo() mChanQueueLimitations = (AiChanQueueLimitation) 0; mTriggerTypes = TRIG_NONE; mTypes = (AiChanType) 0; + mTInFlags = 0; + mTInArrayFlags = 0; + mNumCjcChans = 0; } AiInfo::~AiInfo() @@ -210,9 +213,38 @@ long long AiInfo::getAInScanFlags() const return mAInScanFlags; } +void AiInfo::setTInFlags(long long flags) +{ + mTInFlags = flags; +} + +long long AiInfo::getTInFlags() const +{ + return mTInFlags; +} + +void AiInfo::setTInArrayFlags(long long flags) +{ + mTInArrayFlags = flags; +} + +long long AiInfo::getTInArrayFlags() const +{ + return mTInArrayFlags; +} + +void AiInfo::setNumCjcChans(int numChans) +{ + mNumCjcChans = numChans; +} + +int AiInfo::getNumCjcChans() const +{ + return mNumCjcChans; +} + void AiInfo::addRange(AiInputMode mode, Range range) { - if(mode == AI_SINGLE_ENDED) { mSERanges.push_back(range); diff --git a/src/AiInfo.h b/src/AiInfo.h index 1999de9..2e4cb30 100644 --- a/src/AiInfo.h +++ b/src/AiInfo.h @@ -54,6 +54,10 @@ public: long long getAInFlags() const; void setAInScanFlags(long long flags); long long getAInScanFlags() const; + void setTInFlags(long long flags); + long long getTInFlags() const; + void setTInArrayFlags(long long flags); + long long getTInArrayFlags() const; void addInputMode(AiInputMode mode); std::vector getInputModes() const; @@ -93,6 +97,9 @@ public: bool isInputModeSupported(AiInputMode inputMode) const; bool isRangeSupported(AiInputMode inputMode, Range range) const; + void setNumCjcChans(int numChans); + int getNumCjcChans() const; + private: std::vector mAiChanInfo; std::vector mSERanges; @@ -125,6 +132,11 @@ private: long long mAInFlags; long long mAInScanFlags; + long long mTInFlags; + long long mTInArrayFlags; + + int mNumCjcChans; + }; } /* namespace ul */ diff --git a/src/AoConfig.cpp b/src/AoConfig.cpp index 362fad1..c55abd6 100644 --- a/src/AoConfig.cpp +++ b/src/AoConfig.cpp @@ -3,7 +3,7 @@ * * Author: Measurement Computing Corporation */ - +#include "AoDevice.h" #include "AoConfig.h" namespace ul @@ -20,4 +20,14 @@ AoConfig::~AoConfig() } +void AoConfig::setSyncMode(AOutSyncMode mode) +{ + mAoDevice.setCfg_SyncMode(mode); +} + +AOutSyncMode AoConfig::getSyncMode() +{ + return mAoDevice.getCfg_SyncMode(); +} + } /* namespace ul */ diff --git a/src/AoConfig.h b/src/AoConfig.h index f3a7608..2433c7d 100644 --- a/src/AoConfig.h +++ b/src/AoConfig.h @@ -20,6 +20,9 @@ public: AoConfig(AoDevice& aoDevice); virtual ~AoConfig(); + virtual void setSyncMode(AOutSyncMode mode); + virtual AOutSyncMode getSyncMode(); + private: AoDevice& mAoDevice; }; diff --git a/src/AoDevice.cpp b/src/AoDevice.cpp index 278b3fe..55a7ea4 100644 --- a/src/AoDevice.cpp +++ b/src/AoDevice.cpp @@ -34,6 +34,18 @@ void AoDevice::aOut(int channel, Range range, AOutFlag flags, double dataValue) throw UlException(ERR_BAD_DEV_TYPE); } +void AoDevice::aOutArray(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) +{ + check_AOutArray_Args(lowChan, highChan, range, flags, data); + + int i; + for(int chan = lowChan; chan <= highChan; chan++) + { + i = chan - lowChan; + aOut(chan, range[i], (AOutFlag) flags, data[i]); + } +} + double AoDevice::aOutScan(int lowChan, int highChan, Range range, int samplesPerChan, double rate, ScanOption options, AOutScanFlag flags, double data[]) { throw UlException(ERR_BAD_DEV_TYPE); @@ -274,6 +286,59 @@ void AoDevice::check_AOut_Args(int channel, Range range, AOutFlag flags, double throw UlException(ERR_NO_CONNECTION_ESTABLISHED); } +void AoDevice::check_AOutArray_Args(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) const +{ + int numOfScanChan = 0; + double maxDataValue; + bool scaled = true; + + if(flags & NOSCALEDATA) + scaled = false; + + if(getScanState() == SS_RUNNING) + throw UlException(ERR_ALREADY_ACTIVE); + + if(lowChan < 0 || highChan < 0 || lowChan > mAoInfo.getNumChans() || highChan >= mAoInfo.getNumChans() || lowChan > highChan ) + throw UlException(ERR_BAD_AO_CHAN); + + if(data == NULL || range == NULL) + throw UlException(ERR_BAD_BUFFER); + + if(~mAoInfo.getAOutArrayFlags() & flags) + throw UlException(ERR_BAD_FLAG); + + numOfScanChan = highChan - lowChan + 1; + + for(int i = 0; i < numOfScanChan; i++) + { + if(!mAoInfo.isRangeSupported(range[i])) + throw UlException(ERR_BAD_RANGE); + + maxDataValue = getMaxOutputValue(range[i], scaled); + + if (data[i] > maxDataValue) + throw UlException(ERR_BAD_DA_VAL); + + if(flags & NOSCALEDATA) + { + if(data[0] < 0) + throw UlException(ERR_BAD_DA_VAL); + } + else + { + double offset = 0; + double scale = 0; + mDaqDevice.getEuScaling(range[i], scale, offset); + + if(data[i] < offset) + throw UlException(ERR_BAD_DA_VAL); + } + } + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); +} + void AoDevice::check_AOutScan_Args(int lowChan, int highChan, Range range, int samplesPerChan, double rate, ScanOption options, AOutScanFlag flags, double data[]) const { int numOfScanChan = 0; @@ -342,4 +407,16 @@ void AoDevice::check_AOutSetTrigger_Args(TriggerType trigType, int trigChan, do } +////////////////////// Configuration functions ///////////////////////////////// + +void AoDevice::setCfg_SyncMode(AOutSyncMode mode) +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} +AOutSyncMode AoDevice::getCfg_SyncMode() const +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} + + } /* namespace ul */ diff --git a/src/AoDevice.h b/src/AoDevice.h index 87ae648..b49c205 100644 --- a/src/AoDevice.h +++ b/src/AoDevice.h @@ -28,12 +28,17 @@ public: virtual UlAoConfig& getAoConfig() { return *mAoConfig;} virtual void aOut(int channel, Range range, AOutFlag flags, double dataValue); + virtual void aOutArray(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]); virtual double aOutScan(int lowChan, int highChan, Range range, int samplesPerChan, double rate, ScanOption options, AOutScanFlag flags, double data[]); virtual void setTrigger(TriggerType type, int trigChan, double level, double variance, unsigned int retriggerCount); virtual UlError getStatus(ScanStatus* status, TransferStatus* xferStatus); virtual void stopBackground(); + ////////////////////// Configuration functions ///////////////////////////////// + virtual void setCfg_SyncMode(AOutSyncMode mode); + virtual AOutSyncMode getCfg_SyncMode() const; + protected: virtual void loadDacCoefficients() = 0; virtual int getCalCoefIndex(int channel, Range range) const = 0; @@ -49,6 +54,7 @@ protected: double toEngUnits(unsigned int counts, Range range) const; void check_AOut_Args(int channel, Range range, AOutFlag flags, double dataValue) const; + void check_AOutArray_Args(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) const; void check_AOutScan_Args(int lowChan, int highChan, Range range, int samplesPerChan, double rate, ScanOption options, AOutScanFlag flags, double data[]) const; void check_AOutSetTrigger_Args(TriggerType trigType, int trigChan, double level, double variance, unsigned int retriggerCount) const; diff --git a/src/AoInfo.cpp b/src/AoInfo.cpp index 2151242..399aa9e 100644 --- a/src/AoInfo.cpp +++ b/src/AoInfo.cpp @@ -25,6 +25,7 @@ AoInfo::AoInfo() mCalDateAddr = -1; mSampleSize = 0; mAOutFlags = 0; + mAOutArrayFlags = 0; mAOutScanFlags = 0; mTriggerTypes = TRIG_NONE; } @@ -114,6 +115,16 @@ long long AoInfo::getAOutFlags() const return mAOutFlags; } +void AoInfo::setAOutArrayFlags(long long flags) +{ + mAOutArrayFlags = flags; +} + +long long AoInfo::getAOutArrayFlags() const +{ + return mAOutArrayFlags; +} + void AoInfo::setAOutScanFlags(long long flags) { mAOutScanFlags = flags; diff --git a/src/AoInfo.h b/src/AoInfo.h index c8879f4..08d51e4 100644 --- a/src/AoInfo.h +++ b/src/AoInfo.h @@ -41,6 +41,8 @@ public: void setAOutFlags(long long flags); long long getAOutFlags() const; + void setAOutArrayFlags(long long flags); + long long getAOutArrayFlags() const; void setAOutScanFlags(long long flags); long long getAOutScanFlags() const; @@ -88,6 +90,7 @@ private: int mSampleSize; long long mAOutFlags; + long long mAOutArrayFlags; long long mAOutScanFlags; }; diff --git a/src/CtrDevice.cpp b/src/CtrDevice.cpp index 7dba0f3..e6bcae9 100644 --- a/src/CtrDevice.cpp +++ b/src/CtrDevice.cpp @@ -189,11 +189,21 @@ void CtrDevice::check_CInScan_Args(int lowCtrNum, int highCtrNum, int samplesPer throw UlException(ERR_BAD_FLAG); if(((flags & CINSCAN_FF_CTR16_BIT) && (flags & CINSCAN_FF_CTR32_BIT)) || + ((flags & CINSCAN_FF_CTR16_BIT) && (flags & CINSCAN_FF_CTR48_BIT)) || ((flags & CINSCAN_FF_CTR16_BIT) && (flags & CINSCAN_FF_CTR64_BIT)) || + ((flags & CINSCAN_FF_CTR32_BIT) && (flags & CINSCAN_FF_CTR48_BIT)) || ((flags & CINSCAN_FF_CTR32_BIT) && (flags & CINSCAN_FF_CTR64_BIT))) throw UlException(ERR_BAD_FLAG); - double throughput = rate * numOfScanCtr; + int sampleSize = 2; + + if(flags & CINSCAN_FF_CTR32_BIT) + sampleSize = 4; + else if ((flags & CINSCAN_FF_CTR48_BIT) || (flags & CINSCAN_FF_CTR64_BIT)) + sampleSize = 8; + + + double throughput = rate * numOfScanCtr * (sampleSize / 2); if(!(options & SO_EXTCLOCK)) { diff --git a/src/DaqDevice.cpp b/src/DaqDevice.cpp index d45fd9a..8f5872e 100644 --- a/src/DaqDevice.cpp +++ b/src/DaqDevice.cpp @@ -26,7 +26,7 @@ namespace ul pthread_mutex_t DaqDevice::mDeviceNumberMutex = PTHREAD_MUTEX_INITIALIZER; unsigned long long DaqDevice::mNextAvailableDeviceNumber = 1; -DaqDevice::DaqDevice(DaqDeviceDescriptor daqDeviceDescriptor): mDaqDeviceDescriptor(daqDeviceDescriptor), mConnected(false), +DaqDevice::DaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor): mDaqDeviceDescriptor(daqDeviceDescriptor), mConnected(false), mAiDevice(NULL), mAoDevice(NULL), mDioDevice(NULL), mCtrDevice(NULL), mTmrDevice(NULL), mDaqIDevice(NULL), mDaqODevice(NULL) { mEventHandler = new DaqEventHandler(*this); @@ -477,7 +477,7 @@ IoDevice* DaqDevice::getIoDevice(FunctionType functionType) const TriggerConfig DaqDevice::getTriggerConfig(FunctionType functionType) const { - TriggerConfig trigCfg; + TriggerConfig trigCfg = {(TriggerType) 0, 0, 0, 0, 0}; if(functionType == FT_AI && mAiDevice) trigCfg = mAiDevice->getTrigConfig(); diff --git a/src/DaqDevice.h b/src/DaqDevice.h index 0c4034f..ff95ce8 100644 --- a/src/DaqDevice.h +++ b/src/DaqDevice.h @@ -8,6 +8,7 @@ #ifndef DAQDEVICE_H_ #define DAQDEVICE_H_ +#include "DaqDeviceId.h" #include "DaqDeviceInfo.h" #include "DaqDeviceConfig.h" #include "interfaces/UlDaqDevice.h" @@ -30,7 +31,7 @@ class DaqEventHandler; class UL_LOCAL DaqDevice: public UlDaqDevice { public: - DaqDevice(DaqDeviceDescriptor daqDeviceDescriptor); + DaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor); virtual ~DaqDevice(); virtual void connect()= 0; @@ -98,6 +99,8 @@ public: virtual void setCalOutput (unsigned int index) const { }; + unsigned short getRawFwVer() const { return mRawFwVersion;} + // public interface functions UlAiDevice& getAiDevice() const; UlAoDevice& getAoDevice() const; diff --git a/src/DaqDeviceId.h b/src/DaqDeviceId.h index db5bdd3..f83545c 100644 --- a/src/DaqDeviceId.h +++ b/src/DaqDeviceId.h @@ -19,7 +19,32 @@ class DaqDeviceId public: enum { + USB_1024LS = 0x76, + USB_1024HLS = 0x7f, + USB_SSR24 = 0x85, + USB_SSR08 = 0x86, + USB_ERB24 = 0x8a, + USB_ERB08 = 0x8b, + USB_PDISO8 = 0x8c, + USB_TEMP = 0x8d, USB_TC = 0x90, + USB_DIO96H = 0x92, + USB_DIO24 = 0x93, + USB_DIO24H = 0x94, + USB_DIO96H_50 = 0x95, + USB_PDISO8_40 = 0x96, + USB_3101 = 0x9a, + USB_3102 = 0x9b, + USB_3103 = 0x9c, + USB_3104 = 0x9d, + USB_3105 = 0x9e, + USB_3106 = 0x9f, + USB_3110 = 0xa2, + USB_3112 = 0xa3, + USB_3114 = 0xa4, + USB_TC_AI = 0xbb, + USB_TEMP_AI = 0xbc, + USB_1208FS_PLUS = 0xe8, USB_1408FS_PLUS = 0xe9, USB_1608FS_PLUS = 0xea, diff --git a/src/DaqDeviceManager.cpp b/src/DaqDeviceManager.cpp index b8dc338..fdffe88 100644 --- a/src/DaqDeviceManager.cpp +++ b/src/DaqDeviceManager.cpp @@ -6,7 +6,6 @@ */ #include "DaqDeviceManager.h" -#include "DaqDeviceId.h" #include "./utility/FnLog.h" #include @@ -57,7 +56,31 @@ void DaqDeviceManager::addSupportedDaqDevice() mSupportedDevices.insert(std::pair(DaqDeviceId::USB_CTR08, "USB-CTR08")); // HID devices - //mSupportedDevices.insert(std::pair(DaqDeviceId::USB_TC, "USB-TC")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_DIO96H, "USB-DIO96H")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_DIO96H_50, "USB-DIO96H/50")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_1024LS, "USB-1024LS")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_1024HLS, "USB-1024HLS")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_DIO24, "USB-DIO24/37")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_DIO24H, "USB-DIO24H/37")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_PDISO8, "USB-PDISO8")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_PDISO8_40, "USB-PDISO8/40")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_SSR24, "USB-SSR24")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_SSR08, "USB-SSR08")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_ERB24, "USB-ERB24")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_ERB08, "USB-ERB08")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3101, "USB-3101")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3102, "USB-3102")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3103, "USB-3103")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3104, "USB-3104")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3105, "USB-3105")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3106, "USB-3106")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3110, "USB-3110")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3112, "USB-3112")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_3114, "USB-3114")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_TEMP, "USB-TEMP")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_TC, "USB-TC")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_TEMP_AI, "USB-TEMP_AI")); + mSupportedDevices.insert(std::pair(DaqDeviceId::USB_TC_AI, "USB-TC_AI")); } bool DaqDeviceManager::isDaqDeviceSupported(int productId) @@ -132,7 +155,7 @@ DaqDevice* DaqDeviceManager::getActualDeviceHandle(long long deviceNumber) return daqDevice; } -DaqDevice* DaqDeviceManager::getDaqDevice(DaqDeviceDescriptor daqDeviceDescriptor) +DaqDevice* DaqDeviceManager::getDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor) { DaqDevice *daqDevice = NULL; diff --git a/src/DaqDeviceManager.h b/src/DaqDeviceManager.h index 989f83a..c4552f0 100644 --- a/src/DaqDeviceManager.h +++ b/src/DaqDeviceManager.h @@ -31,7 +31,7 @@ public: static void releaseDevice(long long deviceNumber); static void releaseDevices(); static DaqDevice* getActualDeviceHandle(long long deviceNumber); - static DaqDevice* getDaqDevice(DaqDeviceDescriptor daqDeviceDescriptor); + static DaqDevice* getDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor); private: static void addSupportedDaqDevice(); diff --git a/src/DaqEventHandler.cpp b/src/DaqEventHandler.cpp index 4df22a7..58a5721 100644 --- a/src/DaqEventHandler.cpp +++ b/src/DaqEventHandler.cpp @@ -286,9 +286,9 @@ void DaqEventHandler::check_DisableEvent_Args(DaqEventType eventTypes) // throw UlException(ERR_BAD_EVENT_TYPE); } -int DaqEventHandler::getEventIndex(DaqEventType eventType) +unsigned int DaqEventHandler::getEventIndex(DaqEventType eventType) { - int index = -1; + int index = 0; switch(eventType) { case DE_ON_DATA_AVAILABLE: @@ -307,8 +307,8 @@ int DaqEventHandler::getEventIndex(DaqEventType eventType) index = 4; break; default: + std::cout << "**** getEventIndex(), Invalid event type specified"; break; - } return index; } diff --git a/src/DaqEventHandler.h b/src/DaqEventHandler.h index 1dd9609..71c5cff 100644 --- a/src/DaqEventHandler.h +++ b/src/DaqEventHandler.h @@ -44,7 +44,7 @@ private: static void* eventThread(void* arg); void waitForEvent(); - int getEventIndex(DaqEventType eventType); + unsigned int getEventIndex(DaqEventType eventType); void addEnabledEvents(DaqEventType eventTypes, unsigned long long eventParameter, DaqEventCallback eventCalbackFunc, void* userData); void getCurrentEventsAndData(DaqEvent* daqEvents, int& eventCount); diff --git a/src/DioConfig.cpp b/src/DioConfig.cpp index 1dbe918..f32e476 100644 --- a/src/DioConfig.cpp +++ b/src/DioConfig.cpp @@ -26,4 +26,24 @@ unsigned long long DioConfig::getPortDirectionMask(int portNum) return mDioDevice.getCfg_PortDirectionMask(portNum); } +void DioConfig::setPortInitialOutputVal(int portNum, unsigned long long val) +{ + mDioDevice.setCfg_PortInitialOutputVal(portNum, val); +} + +void DioConfig::setPortIsoMask(int portNum, unsigned long long mask) +{ + mDioDevice.setCfg_PortIsoMask(portNum, mask); +} + +unsigned long long DioConfig::getPortIsoMask(int portNum) +{ + return mDioDevice.getCfg_PortIsoMask(portNum); +} + +unsigned long long DioConfig::getPortLogic(int portNum) +{ + return mDioDevice.getCfg_PortLogic(portNum); +} + } /* namespace ul */ diff --git a/src/DioConfig.h b/src/DioConfig.h index 6998b72..b0c9f0b 100644 --- a/src/DioConfig.h +++ b/src/DioConfig.h @@ -21,6 +21,12 @@ public: virtual unsigned long long getPortDirectionMask(int portNum); + virtual void setPortInitialOutputVal(int portNum, unsigned long long val); + + virtual void setPortIsoMask(int portNum, unsigned long long mask); + virtual unsigned long long getPortIsoMask(int portNum); + virtual unsigned long long getPortLogic(int portNum); + private: DioDevice& mDioDevice; }; diff --git a/src/DioDevice.cpp b/src/DioDevice.cpp index b9acd63..65407fb 100644 --- a/src/DioDevice.cpp +++ b/src/DioDevice.cpp @@ -25,7 +25,6 @@ DioDevice::DioDevice(const DaqDevice& daqDevice) : IoDevice(daqDevice), UlDioDev memset(&mDoTrigCfg, 0, sizeof(mDoTrigCfg)); mDoTrigCfg.type = TRIG_POS_EDGE; - } DioDevice::~DioDevice() @@ -58,6 +57,37 @@ void DioDevice::dOut(DigitalPortType portType, unsigned long long data) throw UlException(ERR_BAD_DEV_TYPE); } +void DioDevice::dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DInArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + int i = 0; + + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) + { + data[i] = dIn(mDioInfo.getPortType(portNum)); + i++; + } +} + +void DioDevice::dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DOutArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + dOut(mDioInfo.getPortType(portNum), data[i]); + i++; + } +} + bool DioDevice::dBitIn(DigitalPortType portType, int bitNum) { throw UlException(ERR_BAD_DEV_TYPE); @@ -181,7 +211,7 @@ void DioDevice::check_DConfigBit_Args(DigitalPortType portType, int bitNum, Digi if(mDioInfo.isPortSupported(portType) == false) throw UlException(ERR_BAD_PORT_TYPE); - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); int bitCount = mDioInfo.getNumBits(portNum); if(bitCount <= bitNum) @@ -196,7 +226,7 @@ void DioDevice::check_DIn_Args(DigitalPortType portType) if(mDioInfo.isPortSupported(portType) == false) throw UlException(ERR_BAD_PORT_TYPE); - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); DigitalPortIoType ioType = mDioInfo.getPortIoType(portNum); if(ioType == DPIOT_OUT) @@ -211,13 +241,13 @@ void DioDevice::check_DOut_Args(DigitalPortType portType, unsigned long long dat if(mDioInfo.isPortSupported(portType) == false) throw UlException(ERR_BAD_PORT_TYPE); - int portNum = mDioInfo.getPortNum(portType); - int bitCount = mDioInfo.getNumBits(portNum); + unsigned int portNum = mDioInfo.getPortNum(portType); + unsigned int bitCount = mDioInfo.getNumBits(portNum); DigitalPortIoType ioType = mDioInfo.getPortIoType(portNum); if(ioType == DPIOT_IN) - throw UlException(ERR_WRONG_DIG_CONFIG); + throw UlException(ERR_BAD_DIG_OPERATION); else if((ioType == DPIOT_IO || ioType == DPIOT_BITIO) && !mDisableCheckDirection) { if(mPortDirectionMask[portNum].any()) @@ -233,16 +263,84 @@ void DioDevice::check_DOut_Args(DigitalPortType portType, unsigned long long dat throw UlException(ERR_NO_CONNECTION_ESTABLISHED); } +void DioDevice::check_DInArray_Args(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + if(!mDioInfo.isPortSupported(lowPort) || !mDioInfo.isPortSupported(highPort)) + throw UlException(ERR_BAD_PORT_TYPE); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + if(lowPortNum > highPortNum ) + throw UlException(ERR_BAD_PORT_TYPE); + + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) + { + if(mDioInfo.getPortIoType(portNum) == DPIOT_OUT) + throw UlException(ERR_WRONG_DIG_CONFIG); + } + + if(data == NULL) + throw UlException(ERR_BAD_BUFFER); + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); +} + +void DioDevice::check_DOutArray_Args(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + if(!mDioInfo.isPortSupported(lowPort) || !mDioInfo.isPortSupported(highPort)) + throw UlException(ERR_BAD_PORT_TYPE); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + if(lowPortNum > highPortNum ) + throw UlException(ERR_BAD_PORT_TYPE); + + if(data == NULL) + throw UlException(ERR_BAD_BUFFER); + + DigitalPortIoType ioType; + int bitCount; + unsigned long maxVal; + int i = 0; + + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) + { + ioType = mDioInfo.getPortIoType(portNum); + bitCount = mDioInfo.getNumBits(portNum); + + if(ioType == DPIOT_IN) + throw UlException(ERR_BAD_DIG_OPERATION); + else if((ioType == DPIOT_IO || ioType == DPIOT_BITIO) && !mDisableCheckDirection) + { + if(mPortDirectionMask[portNum].any()) + throw UlException(ERR_WRONG_DIG_CONFIG); + } + + maxVal = (1 << bitCount) - 1; + + if(data[i] > maxVal) + throw UlException(ERR_BAD_PORT_VAL); + + i++; + } + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); +} + void DioDevice::check_DBitIn_Args(DigitalPortType portType, int bitNum) { if(mDioInfo.isPortSupported(portType) == false) throw UlException(ERR_BAD_PORT_TYPE); - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); int bitCount = mDioInfo.getNumBits(portNum); - if(bitNum < 0 || bitNum >= bitCount) + if(bitNum >= bitCount) throw UlException(ERR_BAD_BIT_NUM); if(!mDaqDevice.isConnected()) @@ -254,16 +352,16 @@ void DioDevice::check_DBitOut_Args(DigitalPortType portType, int bitNum) if(mDioInfo.isPortSupported(portType) == false) throw UlException(ERR_BAD_PORT_TYPE); - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); int bitCount = mDioInfo.getNumBits(portNum); - if(bitNum < 0 || bitNum >= bitCount) + if(bitNum >= bitCount) throw UlException(ERR_BAD_BIT_NUM); DigitalPortIoType ioType = mDioInfo.getPortIoType(portNum); if(ioType == DPIOT_IN) - throw UlException(ERR_WRONG_DIG_CONFIG); + throw UlException(ERR_BAD_DIG_OPERATION); else if((ioType == DPIOT_IO || ioType == DPIOT_BITIO) && !mDisableCheckDirection) { if(mPortDirectionMask[portNum][bitNum]) @@ -279,8 +377,8 @@ void DioDevice::check_DInScan_Args(DigitalPortType lowPort, DigitalPortType high if(!mDioInfo.isPortSupported(lowPort) || !mDioInfo.isPortSupported(highPort)) throw UlException(ERR_BAD_PORT_TYPE); - int lowPortNum = mDioInfo.getPortNum(lowPort); - int highPortNum = mDioInfo.getPortNum(highPort); + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); int numOfScanPorts = highPortNum - lowPortNum + 1; @@ -296,7 +394,7 @@ void DioDevice::check_DInScan_Args(DigitalPortType lowPort, DigitalPortType high if(lowPortNum > highPortNum ) throw UlException(ERR_BAD_PORT_TYPE); - for(int portNum = lowPortNum; portNum <=highPortNum; portNum++) + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) { if(mDioInfo.getPortIoType(portNum) == DPIOT_OUT) throw UlException(ERR_WRONG_DIG_CONFIG); @@ -334,8 +432,8 @@ void DioDevice::check_DOutScan_Args(DigitalPortType lowPort, DigitalPortType hig if(!mDioInfo.isPortSupported(lowPort) || !mDioInfo.isPortSupported(highPort)) throw UlException(ERR_BAD_PORT_TYPE); - int lowPortNum = mDioInfo.getPortNum(lowPort); - int highPortNum = mDioInfo.getPortNum(highPort); + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); int numOfScanPorts = highPortNum - lowPortNum + 1; @@ -352,12 +450,12 @@ void DioDevice::check_DOutScan_Args(DigitalPortType lowPort, DigitalPortType hig throw UlException(ERR_BAD_PORT_TYPE); DigitalPortIoType ioType; - for(int portNum = lowPortNum; portNum <=highPortNum; portNum++) + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) { ioType = mDioInfo.getPortIoType(portNum); if(ioType == DPIOT_IN) - throw UlException(ERR_WRONG_DIG_CONFIG); + throw UlException(ERR_BAD_DIG_OPERATION); else if((ioType == DPIOT_IO || ioType == DPIOT_BITIO) && !mDisableCheckDirection) { if(mPortDirectionMask[portNum].any()) @@ -417,14 +515,14 @@ void DioDevice::check_SetTrigger_Args(ScanDirection direction, TriggerType trigT std::bitset<32> DioDevice::getPortDirection(DigitalPortType portType) const { - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); return mPortDirectionMask[portNum]; } void DioDevice::setPortDirection(DigitalPortType portType, DigitalDirection direction) { - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); unsigned int bitCount = mDioInfo.getNumBits(portNum); if(direction == DD_OUTPUT) @@ -438,7 +536,7 @@ void DioDevice::setPortDirection(DigitalPortType portType, DigitalDirection dire void DioDevice::setBitDirection(DigitalPortType portType, int bitNum, DigitalDirection direction) { - int portNum = mDioInfo.getPortNum(portType); + unsigned int portNum = mDioInfo.getPortNum(portType); if(direction == DD_OUTPUT) mPortDirectionMask[portNum].reset(bitNum); @@ -512,14 +610,62 @@ unsigned long long DioDevice::getCfg_PortDirectionMask(unsigned int portNum) con DigitalPortType portType = mDioInfo.getPortType(portNum); std::bitset<32> bitsetMask = getPortDirection(portType); - int portNum = mDioInfo.getPortNum(portType); - int bitCount = mDioInfo.getNumBits(portNum); + unsigned int portNum = mDioInfo.getPortNum(portType); + unsigned int bitCount = mDioInfo.getNumBits(portNum); unsigned long long bits = (1ULL << bitCount) - 1; dirMask = bitsetMask.flip().to_ulong() & bits; } + else + throw UlException(ERR_BAD_PORT_INDEX); return dirMask; } +void DioDevice::setCfg_PortInitialOutputVal(unsigned int portNum, unsigned long long val) +{ + DigitalPortType portType = mDioInfo.getPortType(portNum); + + if(portType) + { + DigitalPortIoType portIoType = mDioInfo.getPortIoType(portNum); + + if(portIoType == DPIOT_IO || portIoType == DPIOT_BITIO) + { + bool currenState = mDisableCheckDirection; + mDisableCheckDirection = true; + + try + { + dOut(portType, val); + } + catch(UlException& e) + { + throw UlException(e.getError()); + } + + mDisableCheckDirection = currenState; + } + else + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + } + else + throw UlException(ERR_BAD_PORT_INDEX); +} + +void DioDevice::setCfg_PortIsoMask(unsigned int portNum, unsigned long long mask) +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} + +unsigned long long DioDevice::getCfg_PortIsoMask(unsigned int portNum) +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} + +unsigned long long DioDevice::getCfg_PortLogic(unsigned int portNum) +{ + throw UlException(ERR_CONFIG_NOT_SUPPORTED); +} + } /* namespace ul */ diff --git a/src/DioDevice.h b/src/DioDevice.h index 38a4c95..62faf37 100644 --- a/src/DioDevice.h +++ b/src/DioDevice.h @@ -32,6 +32,8 @@ public: virtual void dConfigBit(DigitalPortType portType, int bitNum, DigitalDirection direction); virtual unsigned long long dIn(DigitalPortType portType); virtual void dOut(DigitalPortType portType, unsigned long long data); + virtual void dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + virtual void dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); virtual bool dBitIn(DigitalPortType portType, int bitNum); virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); @@ -59,8 +61,14 @@ public: virtual UlError dOutGetStatus(ScanStatus* status, TransferStatus* xferStatus); virtual void dInStopBackground(); virtual void dOutStopBackground(); + ////////////////////// Configuration functions ///////////////////////////////// virtual unsigned long long getCfg_PortDirectionMask(unsigned int portNum) const; + virtual void setCfg_PortInitialOutputVal(unsigned int portNum, unsigned long long val); + virtual void setCfg_PortIsoMask(unsigned int portNum, unsigned long long mask); + virtual unsigned long long getCfg_PortIsoMask(unsigned int portNum); + virtual unsigned long long getCfg_PortLogic(unsigned int portNum); + protected: void initPortsDirectionMask(); @@ -70,6 +78,8 @@ protected: void check_DConfigBit_Args(DigitalPortType portType, int bitNum, DigitalDirection direction); void check_DIn_Args(DigitalPortType portType); void check_DOut_Args(DigitalPortType portType, unsigned long long data); + void check_DInArray_Args(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + void check_DOutArray_Args(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); void check_DBitIn_Args(DigitalPortType portType, int bitNum); void check_DBitOut_Args(DigitalPortType portType, int bitNum); void check_DInScan_Args(DigitalPortType lowPort, DigitalPortType highPort, int samplesPerPort, double rate, ScanOption options, DInScanFlag flags, unsigned long long data[]) const; diff --git a/src/DioInfo.cpp b/src/DioInfo.cpp index d315db4..da65a32 100644 --- a/src/DioInfo.cpp +++ b/src/DioInfo.cpp @@ -301,15 +301,25 @@ bool DioInfo::isPortSupported(DigitalPortType portType) const { bool supported = false; - if(getPortNum(portType) != -1) - supported = true; + DigitalPortType type; + + for(unsigned int i = 0; i < getNumPorts(); i++) + { + type = getPortType(i); + + if(type == portType) + { + supported = true; + break; + } + } return supported; } -int DioInfo::getPortNum(DigitalPortType portType) const +unsigned int DioInfo::getPortNum(DigitalPortType portType) const { - int portIndex = -1; + unsigned int portIndex = 0; DigitalPortType type; for(unsigned int i = 0; i < getNumPorts(); i++) diff --git a/src/DioInfo.h b/src/DioInfo.h index 9f2133a..f9a7963 100644 --- a/src/DioInfo.h +++ b/src/DioInfo.h @@ -55,7 +55,7 @@ public: bool supportsTrigger(DigitalDirection direction) const; bool isPortSupported(DigitalPortType portType) const; - int getPortNum(DigitalPortType portType) const; + unsigned int getPortNum(DigitalPortType portType) const; private: std::vector mPortInfo; diff --git a/src/IoDevice.cpp b/src/IoDevice.cpp index 1922809..90c1284 100644 --- a/src/IoDevice.cpp +++ b/src/IoDevice.cpp @@ -84,14 +84,16 @@ void IoDevice::setScanInfo(FunctionType functionType, int chanCount, int samples mScanInfo.dataBuffer = dataBuffer; mScanInfo.dataBufferType = dataBufferType; mScanInfo.fullScale = (1ULL << analogResolution) - 1; - mScanInfo.currentCalCoefIdx = 0; - mScanInfo.currentDataBufferIdx = 0; - mScanInfo.totalSampleTransferred = 0; - mScanInfo.allSamplesTransferred = false; mScanInfo.dataBufferSize = mScanInfo.chanCount * mScanInfo.samplesPerChanCount; mScanInfo.stoppingScan = false; mScanDoneWaitEvent.reset(); + + UlLock lock(mProcessScanDataMutex); + mScanInfo.currentCalCoefIdx = 0; + mScanInfo.currentDataBufferIdx = 0; + mScanInfo.totalSampleTransferred = 0; + mScanInfo.allSamplesTransferred = false; } void IoDevice::setScanInfo(FunctionType functionType, int chanCount, int samplesPerChanCount, int sampleSize, unsigned int analogResolution, ScanOption options, long long flags, std::vector calCoefs, void* dataBuffer) { @@ -112,23 +114,26 @@ void IoDevice::getXferStatus(TransferStatus* xferStatus) const } else { - unsigned long long count = mScanInfo.totalSampleTransferred; // return current sample count - unsigned long long idx = -1; + //unsigned long long count = mScanInfo.totalSampleTransferred; // return current sample count + //unsigned long long idx = -1; - if(mScanInfo.chanCount > 0 && count >= mScanInfo.chanCount) + if(mScanInfo.chanCount > 0 && mScanInfo.totalSampleTransferred >= mScanInfo.chanCount) { - idx = count; - + unsigned long long idx = mScanInfo.totalSampleTransferred; idx -= (idx % mScanInfo.chanCount); - idx -= mScanInfo.chanCount; - idx = idx % mScanInfo.dataBufferSize; - } - xferStatus->currentIndex = idx; - xferStatus->currentTotalCount = count; - xferStatus->currentScanCount = count / mScanInfo.chanCount; + xferStatus->currentIndex = idx; + xferStatus->currentTotalCount = mScanInfo.totalSampleTransferred; + xferStatus->currentScanCount = mScanInfo.totalSampleTransferred / mScanInfo.chanCount; + } + else + { + xferStatus->currentIndex = -1; + xferStatus->currentTotalCount = mScanInfo.totalSampleTransferred; + xferStatus->currentScanCount = 0; + } } } /* diff --git a/src/Makefile.am b/src/Makefile.am index 9035c52..a0781a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,7 +5,7 @@ AM_CPPFLAGS = -DDEBUG endif lib_LTLIBRARIES = libuldaq.la -libuldaq_la_SOURCES = CtrInfo.cpp DaqODevice.h TmrDevice.h DioPortInfo.cpp UlDaqDeviceManager.cpp AoInfo.h ulc.cpp DaqEventHandler.h UlException.cpp CtrDevice.cpp DaqDevice.h main.cpp DaqDevice.cpp TmrInfo.cpp DaqDeviceManager.h TmrInfo.h AiConfig.cpp AoInfo.cpp UlException.h DaqODevice.cpp AoConfig.cpp DaqEvent.h AiDevice.h AiInfo.cpp DaqIInfo.cpp DaqEventHandler.cpp DaqDeviceConfig.cpp CtrDevice.h DaqDeviceConfig.h DaqIDevice.cpp AiChanInfo.cpp DaqDeviceManager.cpp AiInfo.h AoDevice.h DioPortInfo.h DioInfo.h UlDaqDeviceManager.h AoConfig.h AiChanInfo.h DioDevice.h DaqDeviceInfo.cpp CtrInfo.h DaqOInfo.cpp DaqOInfo.h DioInfo.cpp MemRegionInfo.h DaqIInfo.h AiDevice.cpp DevMemInfo.h DaqDeviceInfo.h DioConfig.cpp DaqDeviceId.h IoDevice.cpp interfaces/UlAiConfig.h interfaces/UlDioPortInfo.h interfaces/UlAiInfo.h interfaces/UlDioConfig.h interfaces/UlDaqDevice.h interfaces/UlTmrDevice.h interfaces/UlDaqODevice.h interfaces/UlDaqDeviceInfo.h interfaces/UlDaqDeviceConfig.h interfaces/UlCtrDevice.h interfaces/UlDevMemInfo.h interfaces/UlDioDevice.h interfaces/UlDaqOInfo.h interfaces/UlTmrInfo.h interfaces/UlDaqIDevice.h interfaces/UlAiDevice.h interfaces/UlAoDevice.h interfaces/UlMemRegionInfo.h interfaces/UlDaqIInfo.h interfaces/UlAoInfo.h interfaces/UlAoConfig.h interfaces/UlDioInfo.h interfaces/UlCtrInfo.h interfaces/UlAiChanInfo.h DevMemInfo.cpp AoDevice.cpp ul_internal.h DioConfig.h DioDevice.cpp usb/Usb1608g.cpp usb/UsbFpgaDevice.h usb/ctr/CtrUsbCtrx.cpp usb/ctr/CtrUsb1208hs.h usb/ctr/CtrUsbCtrx.h usb/ctr/CtrUsb1208hs.cpp usb/ctr/CtrUsbBase.cpp usb/ctr/CtrUsb1808.cpp usb/ctr/CtrUsb1808.h usb/ctr/CtrUsbBase.h usb/Usb1608fsPlus.cpp usb/tmr/TmrUsb1208hs.cpp usb/tmr/TmrUsb1208hs.h usb/tmr/TmrUsbBase.cpp usb/tmr/TmrUsbBase.h usb/tmr/TmrUsb1808.h usb/tmr/TmrUsb1808.cpp usb/UsbDio32hs.h usb/UsbDio32hs.cpp usb/Usb20x.h usb/UsbDaqDevice.h usb/dio/DioUsb1608g.cpp usb/dio/DioUsb1208fsPlus.cpp usb/dio/DioUsb1608g.h usb/dio/DioUsbDio32hs.h usb/dio/UsbDOutScan.h usb/dio/DioUsbBase.cpp usb/dio/DioUsbDio32hs.cpp usb/dio/DioUsb26xx.cpp usb/dio/DioUsbBase.h usb/dio/DioUsb1208hs.cpp usb/dio/UsbDOutScan.cpp usb/dio/UsbDInScan.h usb/dio/DioUsbCtrx.cpp usb/dio/DioUsb1208fsPlus.h usb/dio/DioUsb1208hs.h usb/dio/UsbDInScan.cpp usb/dio/DioUsbCtrx.h usb/dio/DioUsb1808.h usb/dio/DioUsb1808.cpp usb/dio/DioUsb26xx.h usb/Usb1608fsPlus.h usb/Usb1208fsPlus.cpp usb/daqi/DaqIUsb1808.cpp usb/daqi/DaqIUsbBase.h usb/daqi/DaqIUsb1808.h usb/daqi/DaqIUsbCtrx.cpp usb/daqi/DaqIUsbBase.cpp usb/daqi/DaqIUsbCtrx.h usb/Usb1808.h usb/Usb26xx.h usb/ai/AiUsb1208hs.h usb/ai/AiUsb1608g.cpp usb/ai/AiUsb1808.h usb/ai/AiUsb1608fsPlus.h usb/ai/AiUsb1808.cpp usb/ai/AiUsbBase.cpp usb/ai/AiUsb26xx.cpp usb/ai/AiUsb1208hs.cpp usb/ai/AiUsb1608g.h usb/ai/AiUsb1608fsPlus.cpp usb/ai/AiUsbBase.h usb/ai/AiUsb1208fsPlus.h usb/ai/AiUsb1208fsPlus.cpp usb/ai/AiUsb20x.cpp usb/ai/AiUsb20x.h usb/ai/AiUsb26xx.h usb/UsbEndpoint.h usb/ao/AoUsb26xx.h usb/ao/AoUsb20x.cpp usb/ao/AoUsb1608g.cpp usb/ao/AoUsb1208hs.h usb/ao/AoUsb1808.h usb/ao/AoUsb26xx.cpp usb/ao/AoUsbBase.h usb/ao/AoUsb1208fsPlus.h usb/ao/AoUsbBase.cpp usb/ao/AoUsb1808.cpp usb/ao/AoUsb20x.h usb/ao/AoUsb1208fsPlus.cpp usb/ao/AoUsb1208hs.cpp usb/ao/AoUsb1608g.h usb/daqo/DaqOUsbBase.h usb/daqo/DaqOUsb1808.h usb/daqo/DaqOUsb1808.cpp usb/daqo/DaqOUsbBase.cpp usb/Usb1608g.h usb/Usb1208hs.h usb/Usb20x.cpp usb/UsbEndpoint.cpp usb/UsbScanTransferOut.cpp usb/UsbScanTransferIn.h usb/Usb1208fsPlus.h usb/Usb1208hs.cpp usb/Usb1808.cpp usb/UsbDaqDevice.cpp usb/UsbScanTransferIn.cpp usb/UsbCtrx.cpp usb/UsbCtrx.h usb/Usb26xx.cpp usb/UsbScanTransferOut.h usb/UsbFpgaDevice.cpp utility/ErrorMap.cpp utility/ThreadEvent.cpp utility/UlLock.cpp utility/Endian.cpp utility/EuScale.h utility/FnLog.h utility/Nist.cpp utility/Endian.h utility/EuScale.cpp utility/ErrorMap.h utility/Nist.h utility/SuspendMonitor.cpp utility/FnLog.cpp utility/ThreadEvent.h utility/SuspendMonitor.h utility/UlLock.h IoDevice.h uldaq.h TmrDevice.cpp AiConfig.h DaqIDevice.h +libuldaq_la_SOURCES = CtrInfo.cpp DaqODevice.h TmrDevice.h DioPortInfo.cpp UlDaqDeviceManager.cpp AoInfo.h ulc.cpp DaqEventHandler.h UlException.cpp CtrDevice.cpp DaqDevice.h main.cpp DaqDevice.cpp TmrInfo.cpp DaqDeviceManager.h TmrInfo.h AiConfig.cpp AoInfo.cpp UlException.h DaqODevice.cpp AoConfig.cpp hid/hid_mac.cpp hid/HidDaqDevice.cpp hid/ctr/CtrHid.h hid/ctr/CtrUsbDio24.cpp hid/ctr/CtrHid.cpp hid/ctr/CtrHidBase.h hid/ctr/CtrUsbDio24.h hid/ctr/CtrHidBase.cpp hid/UsbDio96h.cpp hid/dio/DioUsbDio96h.h hid/dio/DioHidBase.cpp hid/dio/DioHidAux.h hid/dio/DioHidAux.cpp hid/dio/DioUsbSsrxx.h hid/dio/DioUsbDio24.h hid/dio/DioUsbDio96h.cpp hid/dio/DioUsbSsrxx.cpp hid/dio/DioUsbErbxx.cpp hid/dio/DioUsbPdiso8.cpp hid/dio/DioUsbDio24.cpp hid/dio/DioUsbPdiso8.h hid/dio/DioHidBase.h hid/dio/DioUsbErbxx.h hid/UsbDio24.h hid/UsbTempAi.cpp hid/UsbTemp.h hid/UsbDio96h.h hid/Usb3100.cpp hid/ai/AiUsbTempAi.h hid/ai/AiUsbTemp.h hid/ai/AiUsbTemp.cpp hid/ai/AiUsbTempAi.cpp hid/ai/AiHidBase.cpp hid/ai/AiHidBase.h hid/hidapi.h hid/UsbSsrxx.h hid/ao/AoHidBase.h hid/ao/AoHidBase.cpp hid/ao/AoUsb3100.h hid/ao/AoUsb3100.cpp hid/UsbTemp.cpp hid/UsbPdiso8.cpp hid/hid_linux.cpp hid/UsbSsrxx.cpp hid/UsbErbxx.cpp hid/UsbErbxx.h hid/UsbPdiso8.h hid/UsbTempAi.h hid/UsbDio24.cpp hid/Usb3100.h hid/HidDaqDevice.h DaqEvent.h AiDevice.h AiInfo.cpp DaqIInfo.cpp DaqEventHandler.cpp DaqDeviceConfig.cpp CtrDevice.h DaqDeviceConfig.h DaqIDevice.cpp AiChanInfo.cpp DaqDeviceManager.cpp AiInfo.h AoDevice.h DioPortInfo.h DioInfo.h UlDaqDeviceManager.h AoConfig.h AiChanInfo.h DioDevice.h DaqDeviceInfo.cpp CtrInfo.h DaqOInfo.cpp DaqOInfo.h DioInfo.cpp MemRegionInfo.h DaqIInfo.h AiDevice.cpp DevMemInfo.h DaqDeviceInfo.h DioConfig.cpp DaqDeviceId.h IoDevice.cpp interfaces/UlAiConfig.h interfaces/UlDioPortInfo.h interfaces/UlAiInfo.h interfaces/UlDioConfig.h interfaces/UlDaqDevice.h interfaces/UlTmrDevice.h interfaces/UlDaqODevice.h interfaces/UlDaqDeviceInfo.h interfaces/UlDaqDeviceConfig.h interfaces/UlCtrDevice.h interfaces/UlDevMemInfo.h interfaces/UlDioDevice.h interfaces/UlDaqOInfo.h interfaces/UlTmrInfo.h interfaces/UlDaqIDevice.h interfaces/UlAiDevice.h interfaces/UlAoDevice.h interfaces/UlMemRegionInfo.h interfaces/UlDaqIInfo.h interfaces/UlAoInfo.h interfaces/UlAoConfig.h interfaces/UlDioInfo.h interfaces/UlCtrInfo.h interfaces/UlAiChanInfo.h DevMemInfo.cpp AoDevice.cpp ul_internal.h DioConfig.h DioDevice.cpp usb/Usb1608g.cpp usb/UsbFpgaDevice.h usb/ctr/CtrUsbCtrx.cpp usb/ctr/CtrUsb1208hs.h usb/ctr/CtrUsbCtrx.h usb/ctr/CtrUsb1208hs.cpp usb/ctr/CtrUsbBase.cpp usb/ctr/CtrUsb1808.cpp usb/ctr/CtrUsb1808.h usb/ctr/CtrUsbBase.h usb/Usb1608fsPlus.cpp usb/tmr/TmrUsb1208hs.cpp usb/tmr/TmrUsb1208hs.h usb/tmr/TmrUsbBase.cpp usb/tmr/TmrUsbBase.h usb/tmr/TmrUsb1808.h usb/tmr/TmrUsb1808.cpp usb/UsbDio32hs.h usb/UsbDio32hs.cpp usb/Usb20x.h usb/UsbDaqDevice.h usb/dio/DioUsb1608g.cpp usb/dio/DioUsb1208fsPlus.cpp usb/dio/DioUsb1608g.h usb/dio/DioUsbDio32hs.h usb/dio/UsbDOutScan.h usb/dio/DioUsbBase.cpp usb/dio/DioUsbDio32hs.cpp usb/dio/DioUsb26xx.cpp usb/dio/DioUsbBase.h usb/dio/DioUsb1208hs.cpp usb/dio/UsbDOutScan.cpp usb/dio/UsbDInScan.h usb/dio/DioUsbCtrx.cpp usb/dio/DioUsb1208fsPlus.h usb/dio/DioUsb1208hs.h usb/dio/UsbDInScan.cpp usb/dio/DioUsbCtrx.h usb/dio/DioUsb1808.h usb/dio/DioUsb1808.cpp usb/dio/DioUsb26xx.h usb/Usb1608fsPlus.h usb/Usb1208fsPlus.cpp usb/daqi/DaqIUsb1808.cpp usb/daqi/DaqIUsbBase.h usb/daqi/DaqIUsb1808.h usb/daqi/DaqIUsbCtrx.cpp usb/daqi/DaqIUsbBase.cpp usb/daqi/DaqIUsbCtrx.h usb/Usb1808.h usb/Usb26xx.h usb/ai/AiUsb1208hs.h usb/ai/AiUsb1608g.cpp usb/ai/AiUsb1808.h usb/ai/AiUsb1608fsPlus.h usb/ai/AiUsb1808.cpp usb/ai/AiUsbBase.cpp usb/ai/AiUsb26xx.cpp usb/ai/AiUsb1208hs.cpp usb/ai/AiUsb1608g.h usb/ai/AiUsb1608fsPlus.cpp usb/ai/AiUsbBase.h usb/ai/AiUsb1208fsPlus.h usb/ai/AiUsb1208fsPlus.cpp usb/ai/AiUsb20x.cpp usb/ai/AiUsb20x.h usb/ai/AiUsb26xx.h usb/UsbEndpoint.h usb/ao/AoUsb26xx.h usb/ao/AoUsb20x.cpp usb/ao/AoUsb1608g.cpp usb/ao/AoUsb1208hs.h usb/ao/AoUsb1808.h usb/ao/AoUsb26xx.cpp usb/ao/AoUsbBase.h usb/ao/AoUsb1208fsPlus.h usb/ao/AoUsbBase.cpp usb/ao/AoUsb1808.cpp usb/ao/AoUsb20x.h usb/ao/AoUsb1208fsPlus.cpp usb/ao/AoUsb1208hs.cpp usb/ao/AoUsb1608g.h usb/daqo/DaqOUsbBase.h usb/daqo/DaqOUsb1808.h usb/daqo/DaqOUsb1808.cpp usb/daqo/DaqOUsbBase.cpp usb/Usb1608g.h usb/Usb1208hs.h usb/Usb20x.cpp usb/UsbEndpoint.cpp usb/UsbScanTransferOut.cpp usb/UsbScanTransferIn.h usb/Usb1208fsPlus.h usb/Usb1208hs.cpp usb/Usb1808.cpp usb/UsbDaqDevice.cpp usb/UsbScanTransferIn.cpp usb/UsbCtrx.cpp usb/UsbCtrx.h usb/Usb26xx.cpp usb/UsbScanTransferOut.h usb/UsbFpgaDevice.cpp utility/ErrorMap.cpp utility/ThreadEvent.cpp utility/UlLock.cpp utility/Endian.cpp utility/EuScale.h utility/FnLog.h utility/Nist.cpp utility/Endian.h utility/EuScale.cpp utility/ErrorMap.h utility/Nist.h utility/SuspendMonitor.cpp utility/FnLog.cpp utility/ThreadEvent.h utility/SuspendMonitor.h utility/UlLock.h IoDevice.h uldaq.h TmrDevice.cpp AiConfig.h DaqIDevice.h libuldaq_la_LDFLAGS = $(LTLDFLAGS) diff --git a/src/UlDaqDeviceManager.cpp b/src/UlDaqDeviceManager.cpp index ae70ca4..f2cc49d 100644 --- a/src/UlDaqDeviceManager.cpp +++ b/src/UlDaqDeviceManager.cpp @@ -9,7 +9,7 @@ #include "DaqDeviceId.h" #include "DaqDeviceManager.h" #include "./usb/UsbDaqDevice.h" -//#include "./hid/HidDaqDevice.h" +#include "./hid/HidDaqDevice.h" #include "./usb/Usb1208fsPlus.h" #include "./usb/Usb1608fsPlus.h" #include "./usb/Usb20x.h" @@ -20,7 +20,14 @@ #include "./usb/Usb26xx.h" #include "./usb/UsbDio32hs.h" #include "./usb/UsbCtrx.h" -//#include "./hid/UsbTc.h" +#include "./hid/UsbDio96h.h" +#include "./hid/UsbDio24.h" +#include "./hid/UsbPdiso8.h" +#include "./hid/UsbSsrxx.h" +#include "./hid/UsbErbxx.h" +#include "./hid/Usb3100.h" +#include "./hid/UsbTemp.h" +#include "./hid/UsbTempAi.h" #include #include @@ -48,18 +55,18 @@ std::vector UlDaqDeviceManager::getDaqDeviceInventory(DaqDe std::vector usbDaqDeviceList = UsbDaqDevice::findDaqDevices(); - //std::vector hidDaqDeviceList = HidDaqDevice::findDaqDevices(); + std::vector hidDaqDeviceList = HidDaqDevice::findDaqDevices(); for(unsigned int i = 0; i < usbDaqDeviceList.size(); i++) daqDeviceList.push_back(usbDaqDeviceList[i]); - /*for(unsigned int i = 0; i < hidDaqDeviceList.size(); i++) - daqDeviceList.push_back(hidDaqDeviceList[i]);*/ + for(unsigned int i = 0; i < hidDaqDeviceList.size(); i++) + daqDeviceList.push_back(hidDaqDeviceList[i]); return daqDeviceList; } -UlDaqDevice& UlDaqDeviceManager::createDaqDevice(DaqDeviceDescriptor daqDevDescriptor) +UlDaqDevice& UlDaqDeviceManager::createDaqDevice(const DaqDeviceDescriptor& daqDevDescriptor) { DaqDevice* daqDev = DaqDeviceManager::getDaqDevice(daqDevDescriptor); // Don't recreate a new DaqDevice object if it already exists for the specified descriptor @@ -123,9 +130,53 @@ UlDaqDevice& UlDaqDeviceManager::createDaqDevice(DaqDeviceDescriptor daqDevDescr daqDev = new UsbCtrx(daqDevDescriptor, "USB_CTR.bin"); break; - /*case DaqDeviceId::USB_TC: - daqDev = new UsbTc(daqDevDescriptor); - break;*/ + case DaqDeviceId::USB_DIO96H: + case DaqDeviceId::USB_DIO96H_50: + daqDev = new UsbDio96h(daqDevDescriptor); + break; + + case DaqDeviceId::USB_1024LS: + case DaqDeviceId::USB_1024HLS: + case DaqDeviceId::USB_DIO24: + case DaqDeviceId::USB_DIO24H: + daqDev = new UsbDio24(daqDevDescriptor); + break; + + case DaqDeviceId::USB_PDISO8: + case DaqDeviceId::USB_PDISO8_40: + daqDev = new UsbPdiso8(daqDevDescriptor); + break; + + case DaqDeviceId::USB_SSR24: + case DaqDeviceId::USB_SSR08: + daqDev = new UsbSsrxx(daqDevDescriptor); + break; + + case DaqDeviceId::USB_ERB24: + case DaqDeviceId::USB_ERB08: + daqDev = new UsbErbxx(daqDevDescriptor); + break; + + case DaqDeviceId::USB_3101: + case DaqDeviceId::USB_3102: + case DaqDeviceId::USB_3103: + case DaqDeviceId::USB_3104: + case DaqDeviceId::USB_3105: + case DaqDeviceId::USB_3106: + case DaqDeviceId::USB_3110: + case DaqDeviceId::USB_3112: + case DaqDeviceId::USB_3114: + daqDev = new Usb3100(daqDevDescriptor); + break; + + case DaqDeviceId::USB_TEMP: + case DaqDeviceId::USB_TC: + daqDev = new UsbTemp(daqDevDescriptor); + break; + case DaqDeviceId::USB_TEMP_AI: + case DaqDeviceId::USB_TC_AI: + daqDev = new UsbTempAi(daqDevDescriptor); + break; } if(daqDev) diff --git a/src/UlDaqDeviceManager.h b/src/UlDaqDeviceManager.h index be0e7db..a1d7604 100644 --- a/src/UlDaqDeviceManager.h +++ b/src/UlDaqDeviceManager.h @@ -23,7 +23,7 @@ public: virtual ~UlDaqDeviceManager(); static std::vector getDaqDeviceInventory(DaqDeviceInterface InterfaceType); - static UlDaqDevice& createDaqDevice(DaqDeviceDescriptor daqDevDescriptor); + static UlDaqDevice& createDaqDevice(const DaqDeviceDescriptor& daqDevDescriptor); static void releaseDaqDevice(UlDaqDevice& daqDevice); }; diff --git a/src/hid/HidDaqDevice.cpp b/src/hid/HidDaqDevice.cpp new file mode 100644 index 0000000..887c320 --- /dev/null +++ b/src/hid/HidDaqDevice.cpp @@ -0,0 +1,783 @@ +/* + * HidDaqDevice.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "HidDaqDevice.h" +#include "../DaqDeviceManager.h" +#include "../utility/Endian.h" + +#include + +#define NO_PERMISSION_STR "NO PERMISSION" + +namespace ul +{ +bool HidDaqDevice::mInitialized = false; + +HidDaqDevice::HidDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor) : DaqDevice(daqDeviceDescriptor) +{ + FnLog log("HidDaqDevice::HidDaqDevice"); + + mDevHandle = NULL; + mConnected = false; + + UlLock::initMutex(mConnectionMutex, PTHREAD_MUTEX_RECURSIVE); + UlLock::initMutex(mIoMutex, PTHREAD_MUTEX_RECURSIVE); +} + +HidDaqDevice::~HidDaqDevice() +{ + FnLog log("HidDaqDevice::~HidDaqDevice"); + + disconnect(); + + UlLock::destroyMutex(mIoMutex); + UlLock::destroyMutex(mConnectionMutex); +} + +void HidDaqDevice::hidapi_init() +{ + if(!mInitialized) + { + int status = hid_init(); + + if(status != 0) + { + UL_LOG("hid_init() failed"); + } + else + mInitialized = true; + } +} + +void HidDaqDevice::hidapi_exit() +{ + if(mInitialized) + { + hid_exit(); + + mInitialized = false; + } +} + +std::vector HidDaqDevice::findDaqDevices() +{ + std::vector descriptorList; + + FnLog log("HidDaqDevice::getDaqDeviceDescriptorList"); + + struct hid_device_info *devs, *cur_dev; + + // Flush the input pipe of all mcc hid devices attached to the host + // If the input pipe's buffer was not emptied for any reason, i.e, application crash or SIGINT + // then sending setup request during device discovery will cause the device to hang + hid_flush_input_pipe(MCC_USB_VID); + + devs = hid_enumerate(MCC_USB_VID, 0x0); + cur_dev = devs; + + while (cur_dev) + { + if(DaqDeviceManager::isDaqDeviceSupported(cur_dev->product_id)) + { + DaqDeviceDescriptor daqDevDescriptor; + memset(&daqDevDescriptor, 0,sizeof(DaqDeviceDescriptor)); + + daqDevDescriptor.productId = cur_dev->product_id; + daqDevDescriptor.devInterface = USB_IFC; + std::string productName = DaqDeviceManager::getDeviceName(cur_dev->product_id); + + strncpy(daqDevDescriptor.productName, productName.c_str(), sizeof(daqDevDescriptor.productName) - 1); + strncpy(daqDevDescriptor.devString, productName.c_str(), sizeof(daqDevDescriptor.devString) - 1); + + if(cur_dev->serial_number) + { + if(wcslen(cur_dev->serial_number)) + { + char serial[128] = {0}; + wcstombs(serial,cur_dev->serial_number, sizeof(serial)); + strcpy(daqDevDescriptor.uniqueId, serial); + } + else + strcpy(daqDevDescriptor.uniqueId, NO_PERMISSION_STR); + } + + UL_LOG("-----------------------"); + UL_LOG("Product ID : 0x" << std::hex << daqDevDescriptor.productId << std::dec); + UL_LOG("Product Name: "<< daqDevDescriptor.productName); + UL_LOG("Serial Number : "<< daqDevDescriptor.uniqueId); + UL_LOG("-----------------------"); + + descriptorList.push_back(daqDevDescriptor); + + /*printf("Device Found\n type: %04hx %04hx\n path: %s\n serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number); + printf("\n"); + printf(" Manufacturer: %ls\n", cur_dev->manufacturer_string); + printf(" Product: %ls\n", cur_dev->product_string); + printf(" Release: %hx\n", cur_dev->release_number); + printf(" Interface: %d\n", cur_dev->interface_number); + printf("\n");*/ + } + cur_dev = cur_dev->next; + } + + hid_free_enumeration(devs); + + return descriptorList; +} + +void HidDaqDevice::connect() +{ + FnLog log("UsbDaqDevice::connect"); + + UlLock lock(mConnectionMutex); + + if(mConnected) + { + UL_LOG("Device is already connected, disconnecting..."); + + disconnect(); + } + + establishConnection(); + + mConnected = true; + + //mCurrentSuspendCount = SuspendMonitor::getCurrentSystemSuspendCount(); + + initilizeHardware(); + + initializeIoDevices(); +} + +void HidDaqDevice::disconnect() +{ + FnLog log("UsbDaqDevice::disconnect"); + + if(mConnected) + { + DaqDevice::disconnect(); + + releaseHidResources(); + } +} + +void HidDaqDevice::establishConnection() +{ + FnLog log("HidDaqDevice::establishConnection"); + + if(std::strcmp(mDaqDeviceDescriptor.uniqueId, NO_PERMISSION_STR) == 0) + throw UlException(ERR_USB_DEV_NO_PERMISSION); + + wchar_t serial[128]; + memset(serial, 0, sizeof(serial)); + mbstowcs( serial, mDaqDeviceDescriptor.uniqueId, strlen(mDaqDeviceDescriptor.uniqueId)); + + UlError err = ERR_NO_ERROR; + hid_device_info devInfo; + mDevHandle = hid_open(MCC_USB_VID, mDaqDeviceDescriptor.productId, serial, &devInfo, &err); + + if(mDevHandle) + { + mRawFwVersion = devInfo.release_number; + } + else + { + if(err) + throw UlException(err); + else + throw UlException(ERR_DEV_NOT_FOUND); + } +} + + +void HidDaqDevice::releaseHidResources() +{ + FnLog log("UsbDaqDevice::releaseUsbResources"); + + if(mDevHandle) + { + UlLock lock(mIoMutex); + + hid_close(mDevHandle); + + mDevHandle = NULL; + } +} + +void HidDaqDevice::sendCmd(unsigned char cmd) const +{ + size_t outLength = 1; + + sendRawCmd(&cmd, &outLength); +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned char param) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param; + }outData; +#pragma pack() + + outData.cmd = cmd; + outData.param = param; + + size_t outLength = sizeof(outData); + + sendRawCmd((unsigned char*) &outData, &outLength); +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned char param1, unsigned char param2) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned char param2; + }outData; +#pragma pack() + + outData.cmd = cmd; + outData.param1 = param1; + outData.param2 = param2; + + size_t outLength = sizeof(outData); + + sendRawCmd((unsigned char*) &outData, &outLength); +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char param3) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned char param2; + unsigned char param3; + }outData; +#pragma pack() + + outData.cmd = cmd; + outData.param1 = param1; + outData.param2 = param2; + outData.param3 = param3; + + size_t outLength = sizeof(outData); + + sendRawCmd((unsigned char*) &outData, &outLength); +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned char param1, unsigned short param2, unsigned char param3) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned short param2; + unsigned char param3; + }outData; +#pragma pack() + + outData.cmd = cmd; + outData.param1 = param1; + outData.param2 = param2; + outData.param3 = param3; + + size_t outLength = sizeof(outData); + + sendRawCmd((unsigned char*) &outData, &outLength); +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned char* dataBuffer, unsigned int dataBufferSize) const +{ + size_t outLength = dataBufferSize + 1; + unsigned char* outData = new unsigned char[outLength]; + + outData[0] = cmd; + + memcpy(&outData[1], dataBuffer, dataBufferSize); + + sendRawCmd(outData, &outLength); + + delete [] outData; +} + +void HidDaqDevice::sendCmd(unsigned char cmd, unsigned short param, unsigned char* dataBuffer, unsigned int dataBufferSize) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned short param; + }outParams; +#pragma pack() + + size_t outLength = sizeof(outParams) + dataBufferSize; + + unsigned char* outData = new unsigned char[outLength]; + + outParams.cmd = cmd; + outParams.param = Endian::cpu_to_le_ui16(param); + + memcpy(&outData[0], &outParams, sizeof(outParams)); + + int dataIdx = sizeof(outParams); + memcpy(&outData[dataIdx], dataBuffer, dataBufferSize); + + sendRawCmd(outData, &outLength); + + delete [] outData; +} + +int HidDaqDevice::sendCmd(unsigned char cmd, unsigned short param1, unsigned char param2, unsigned char* dataBuffer, unsigned int dataBufferSize) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned short param1; + unsigned char param2; + }outParams; +#pragma pack() + + size_t outLength = sizeof(outParams) + dataBufferSize; + + unsigned char* outData = new unsigned char[outLength]; + + outParams.cmd = cmd; + outParams.param1 = Endian::cpu_to_le_ui16(param1); + outParams.param2 = param2; + + memcpy(&outData[0], &outParams, sizeof(outParams)); + + int dataIdx = sizeof(outParams); + memcpy(&outData[dataIdx], dataBuffer, dataBufferSize); + + sendRawCmd(outData, &outLength); + + delete [] outData; + + int sent = outLength - sizeof(outParams); + + return sent; +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned char* data, unsigned int timeout) const +{ + size_t outLength = 1; + +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char data; + }indata; +#pragma pack() + + size_t inLength = sizeof(indata); + + queryRawCmd(&cmd, outLength, (unsigned char*) &indata, &inLength, timeout); + + *data = indata.data; +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned int* data, unsigned int timeout) const +{ + size_t outLength = 1; + +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned int data; + }indata; +#pragma pack() + + size_t inLength = sizeof(indata); + + queryRawCmd(&cmd, outLength, (unsigned char*) &indata, &inLength, timeout); + + *data = Endian::le_ui32_to_cpu(indata.data); +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned char param, unsigned char* data, unsigned int timeout) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param; + }outData; + + struct + { + unsigned char cmd; + unsigned char data; + }inData; +#pragma pack() + + outData.cmd = cmd; + outData.param = param; + + size_t outLength = sizeof(outData); + size_t inLength = sizeof(inData); + + queryRawCmd((unsigned char*) &outData, outLength, (unsigned char*) &inData, &inLength, timeout); + + *data = inData.data; +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char* data, unsigned int timeout) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned char param2; + }outData; + + struct + { + unsigned char cmd; + unsigned char data; + }inData; +#pragma pack() + + outData.cmd = cmd; + outData.param1 = param1; + outData.param2 = param2; + + size_t outLength = sizeof(outData); + size_t inLength = sizeof(inData); + + queryRawCmd((unsigned char*) &outData, outLength, (unsigned char*) &inData, &inLength, timeout); + + *data = inData.data; +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned short* data, unsigned int timeout) const +{ + size_t outLength = 1; + +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned short data; + }inData; +#pragma pack() + + size_t inLength = sizeof(inData); + + queryRawCmd(&cmd, outLength, (unsigned char*) &inData, &inLength, timeout); + + *data = Endian::le_ui16_to_cpu(inData.data); +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout) const +{ + size_t outLength = 1; + + size_t inLength = dataBufferSize + 1; + + unsigned char* inData = new unsigned char[inLength]; + + queryRawCmd(&cmd, outLength, inData, &inLength, timeout); + + memcpy(dataBuffer, &inData[1], dataBufferSize); + + delete [] inData; +} + +unsigned int HidDaqDevice::queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char param3, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned char param2; + unsigned char param3; + }outData; +#pragma pack() + + unsigned int bytesRead = 0; + + outData.cmd = cmd; + outData.param1 = Endian::cpu_to_le_ui16(param1); + outData.param2 = param2; + outData.param3 = param3; + + size_t outLength = sizeof(outData); + + size_t inLength = dataBufferSize + 1; + + unsigned char* inData = new unsigned char[inLength]; + + queryRawCmd((unsigned char*) &outData, outLength, inData, &inLength, timeout); + + if(inLength > 0) + { + bytesRead = inLength - 1; + memcpy(dataBuffer, &inData[1], bytesRead); + } + + delete [] inData; + + return bytesRead; +} + +unsigned int HidDaqDevice::queryCmd(unsigned char cmd, unsigned short param1, unsigned char param2, unsigned char param3, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned short param1; + unsigned char param2; + unsigned char param3; + }outData; +#pragma pack() + + unsigned int bytesRead = 0; + + outData.cmd = cmd; + outData.param1 = Endian::cpu_to_le_ui16(param1); + outData.param2 = param2; + outData.param3 = param3; + + size_t outLength = sizeof(outData); + + size_t inLength = dataBufferSize + 1; + + unsigned char* inData = new unsigned char[inLength]; + + queryRawCmd((unsigned char*) &outData, outLength, inData, &inLength, timeout); + + if(inLength > 0) + { + bytesRead = inLength - 1; + memcpy(dataBuffer, &inData[1], bytesRead); + } + + delete [] inData; + + + return bytesRead; +} + +void HidDaqDevice::queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, float* data, unsigned int timeout) const +{ +#pragma pack(1) + struct + { + unsigned char cmd; + unsigned char param1; + unsigned char param2; + }outData; + + struct + { + unsigned char cmd; + unsigned char data[4]; + }inData; +#pragma pack() + + outData.cmd = cmd; + outData.param1 = param1; + outData.param2 = param2; + + size_t outLength = sizeof(outData); + size_t inLength = sizeof(inData); + + queryRawCmd((unsigned char*) &outData, outLength, (unsigned char*) &inData, &inLength, timeout); + + *data = Endian::Instance().le_ptr_to_cpu_f32(inData.data); +} + + +void HidDaqDevice::sendRawCmd(const unsigned char *data, size_t* length) const +{ + UlLock lock(mIoMutex); + + //size_t len = length; + //int sent = 0; + + UlError err = send(data, length); + + //sent = len; + + if(err) + throw UlException(err); + + //return sent; +} + +UlError HidDaqDevice::send(const unsigned char *data, size_t* length) const +{ + UlError err = ERR_NO_ERROR; + int sent = 0; + + if(mConnected) + { + if(mDevHandle) + { + sent = hid_write(mDevHandle, data, *length); + + if (sent == -1) + { + UL_LOG("#### hid_write failed"); + + err = ERR_DEV_NOT_CONNECTED; + } + else + *length = sent; + } + else + err = ERR_DEV_NOT_FOUND; + } + else + err = ERR_NO_CONNECTION_ESTABLISHED; + + return err; +} + +void HidDaqDevice::queryRawCmd(const unsigned char *outdata, size_t outLength, unsigned char *indata, size_t* inLength, unsigned int timeout) const +{ + UlLock lock(mIoMutex); + + UlError err = query(outdata, outLength, indata, inLength, timeout); + + if(err) + throw UlException(err); +} + +UlError HidDaqDevice::query(const unsigned char *outdata, size_t outLength, unsigned char *indata, size_t* inLength, unsigned int timeout) const +{ + UlError err = ERR_NO_ERROR; + int sent = 0; + + if(mConnected) + { + if(mDevHandle) + { + sent = hid_write(mDevHandle, outdata, outLength); + + if(sent == (int) outLength) + { + int received = 0; + received = hid_read_timeout(mDevHandle, indata, *inLength, timeout); + + if(received == -1) + { + UL_LOG("#### hid_read failed"); + + err = ERR_DEV_NOT_CONNECTED; + } + else + { + if (received == 0) + err = ERR_DEAD_DEV; + + *inLength = received; + } + } + else if (sent == -1) + { + UL_LOG("#### hid_write failed"); + + err = ERR_DEV_NOT_CONNECTED; + } + } + else + err = ERR_DEV_NOT_FOUND; + } + else + err = ERR_NO_CONNECTION_ESTABLISHED; + + return err; +} + +void HidDaqDevice::flashLed(int flashCount) const +{ + sendCmd(CMD_FLASH_LED); +} + +int HidDaqDevice::memRead(MemoryType memType, MemRegion memRegionType, unsigned int address, unsigned char* buffer, unsigned int count) const +{ + check_MemRW_Args(memRegionType, MA_READ, address, buffer, count, false); + + unsigned char bytesToRead; + int totalBytesRead = 0; + int bytesRead = 0; + int remaining = count; + + if(buffer == NULL) + throw UlException(ERR_BAD_BUFFER); + + int maxTransfer = 62; + + unsigned char cmd = CMD_MEM_READ; + unsigned char* readBuff = buffer; + unsigned short addr = address; + + do + { + bytesToRead = (remaining > maxTransfer ? maxTransfer : remaining); + + bytesRead = queryCmd(cmd, addr, 0, bytesToRead, readBuff, bytesToRead); + + remaining-= bytesRead; + totalBytesRead += bytesRead; + addr += bytesRead; + readBuff += bytesRead; + } + while(remaining > 0); + + + return totalBytesRead; +} + +int HidDaqDevice::memWrite(MemoryType memType, MemRegion memRegionType, unsigned int address, unsigned char* buffer, unsigned int count) const +{ + check_MemRW_Args(memRegionType, MA_WRITE, address, buffer, count, false); + + unsigned char bytesToWrite; + int totalBytesWritten = 0; + int bytesWritten = 0; + int bytesRemaining = count; + + if(buffer == NULL) + throw UlException(ERR_BAD_BUFFER); + + int maxTransfer = 59; + + unsigned char cmd = CMD_MEM_WRITE; + unsigned char* writeBuff = buffer; + unsigned short addr = address; + + while(bytesRemaining > 0) + { + bytesToWrite = bytesRemaining > maxTransfer ? maxTransfer : bytesRemaining; + + bytesWritten = sendCmd(cmd, addr, bytesToWrite, writeBuff, bytesToWrite); + + bytesRemaining -= bytesWritten; + totalBytesWritten += bytesWritten; + addr += bytesWritten; + writeBuff += bytesWritten; + } + + return totalBytesWritten; +} + +} /* namespace ul */ diff --git a/src/hid/HidDaqDevice.h b/src/hid/HidDaqDevice.h new file mode 100644 index 0000000..7efec73 --- /dev/null +++ b/src/hid/HidDaqDevice.h @@ -0,0 +1,83 @@ +/* + * HidDaqDevice.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_HIDDAQDEVICE_H_ +#define HID_HIDDAQDEVICE_H_ + +#include + +#include "hidapi.h" +#include "../DaqDevice.h" +#include "../uldaq.h" +#include "../UlException.h" +#include "../utility/UlLock.h" + +namespace ul +{ + +class UL_LOCAL HidDaqDevice: public DaqDevice +{ +public: + HidDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~HidDaqDevice(); + + static void hidapi_init(); + static void hidapi_exit(); + + static std::vector findDaqDevices(); + + virtual void connect(); + virtual void disconnect(); + + void sendCmd(unsigned char cmd) const; + void sendCmd(unsigned char cmd, unsigned char param) const; + void sendCmd(unsigned char cmd, unsigned char param1, unsigned char param2) const; + void sendCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char param3) const; + void sendCmd(unsigned char cmd, unsigned char param1, unsigned short param2, unsigned char param3) const; + void sendCmd(unsigned char cmd, unsigned char* dataBuffer, unsigned int dataBufferSize) const; + void sendCmd(unsigned char cmd, unsigned short param, unsigned char* dataBuffer, unsigned int dataBufferSize) const; + int sendCmd(unsigned char cmd, unsigned short param1, unsigned char param2, unsigned char* dataBuffer, unsigned int dataBufferSize) const; + + void queryCmd(unsigned char cmd, unsigned char* data, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned int* data, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned char param, unsigned char* data, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char* data, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned short* data, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout = 2000) const; + unsigned int queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, unsigned char param3, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout = 2000) const; + unsigned int queryCmd(unsigned char cmd, unsigned short param1, unsigned char param2, unsigned char param3, unsigned char* dataBuffer, unsigned int dataBufferSize, unsigned int timeout = 2000) const; + void queryCmd(unsigned char cmd, unsigned char param1, unsigned char param2, float* data, unsigned int timeout = 2000) const; + void sendRawCmd(const unsigned char *data, size_t* length) const; + void queryRawCmd(const unsigned char *outdata, size_t outLength, unsigned char *indata, size_t* inLength, unsigned int timeout = 2000) const; + + virtual void flashLed(int flashCount) const; + + int memRead(MemoryType memType, MemRegion memRegionType, unsigned int address, unsigned char* buffer, unsigned int count) const; + int memWrite(MemoryType memType, MemRegion memRegionType, unsigned int address, unsigned char* buffer, unsigned int count) const; + +private: + void establishConnection(); + virtual void initilizeHardware() const {}; + void releaseHidResources(); + + virtual UlError send(const unsigned char *data, size_t* length) const; + virtual UlError query(const unsigned char *outdata, size_t outLength, unsigned char *indata, size_t* inLength, unsigned int timeout) const; + +public: + enum { MCC_USB_VID = 0x09db }; + enum { CMD_MEM_READ = 0x30, CMD_MEM_WRITE = 0x31, CMD_FLASH_LED = 0x40 }; + +private: + hid_device* mDevHandle; + static bool mInitialized; + mutable pthread_mutex_t mConnectionMutex; + mutable pthread_mutex_t mIoMutex; + +}; + +} /* namespace ul */ + +#endif /* HID_HIDDAQDEVICE_H_ */ diff --git a/src/hid/Usb3100.cpp b/src/hid/Usb3100.cpp new file mode 100644 index 0000000..01aa2d7 --- /dev/null +++ b/src/hid/Usb3100.cpp @@ -0,0 +1,35 @@ +/* + * Usb3100.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "Usb3100.h" +#include "./ao/AoUsb3100.h" +#include "./dio/DioHidAux.h" +#include "./ctr/CtrHid.h" + +namespace ul +{ + +Usb3100::Usb3100(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setAoDevice(new AoUsb3100(*this)); + setDioDevice(new DioHidAux(*this)); + setCtrDevice(new CtrHid(*this, 1)); + + addMemRegion(MR_USER, 0, 256 , MA_READ | MA_WRITE); + addMemRegion(MR_CAL, 0x100, 1024, MA_READ); +} + +Usb3100::~Usb3100() +{ + +} + +void Usb3100::flashLed(int flashCount) const +{ + sendCmd(CMD_FLASH_LED, (unsigned char) flashCount); +} + +} /* namespace ul */ diff --git a/src/hid/Usb3100.h b/src/hid/Usb3100.h new file mode 100644 index 0000000..276c423 --- /dev/null +++ b/src/hid/Usb3100.h @@ -0,0 +1,29 @@ +/* + * Usb3100.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USB3100_H_ +#define HID_USB3100_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL Usb3100: public HidDaqDevice +{ +public: + Usb3100(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~Usb3100(); + + virtual void flashLed(int flashCount) const; + +private: + enum {CMD_FLASH_LED = 0x40 }; +}; + +} /* namespace ul */ + +#endif /* HID_USB3100_H_ */ diff --git a/src/hid/UsbDio24.cpp b/src/hid/UsbDio24.cpp new file mode 100644 index 0000000..748c874 --- /dev/null +++ b/src/hid/UsbDio24.cpp @@ -0,0 +1,36 @@ +/* + * UsbDio24.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbDio24.h" +#include "./dio/DioUsbDio24.h" +#include "./ctr/CtrUsbDio24.h" + +namespace ul +{ + +UsbDio24::UsbDio24(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setDioDevice(new DioUsbDio24(*this)); + setCtrDevice(new CtrUsbDio24(*this, 1)); + +} + +UsbDio24::~UsbDio24() +{ + +} + +void UsbDio24::flashLed(int flashCount) const +{ + unsigned char buffer[MAX_PACKET_SIZE] = {0}; + + buffer[1] = CMD_FLASH_LED; + size_t length = sizeof(buffer); + + sendRawCmd(buffer, &length); +} + +} /* namespace ul */ diff --git a/src/hid/UsbDio24.h b/src/hid/UsbDio24.h new file mode 100644 index 0000000..e0f2233 --- /dev/null +++ b/src/hid/UsbDio24.h @@ -0,0 +1,33 @@ +/* + * UsbDio24.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBDIO24_H_ +#define HID_USBDIO24_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbDio24: public HidDaqDevice +{ +public: + UsbDio24(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbDio24(); + + virtual void flashLed(int flashCount) const; + +public: + enum { MAX_PACKET_SIZE = 8 }; // do not set the packet size to 9. if set to 9 on macOS the hid driver sends a packet with no data which causes the device to hang + +private: + + enum { CMD_FLASH_LED = 0x0B }; +}; + +} /* namespace ul */ + +#endif /* HID_USBDIO24_H_ */ diff --git a/src/hid/UsbDio96h.cpp b/src/hid/UsbDio96h.cpp new file mode 100644 index 0000000..8624c1e --- /dev/null +++ b/src/hid/UsbDio96h.cpp @@ -0,0 +1,29 @@ +/* + * UsbDio96h.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbDio96h.h" +#include "./dio/DioUsbDio96h.h" +#include "./ctr/CtrHid.h" + +namespace ul +{ + +UsbDio96h::UsbDio96h(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setDioDevice(new DioUsbDio96h(*this)); + + if(getDeviceType() != DaqDeviceId::USB_DIO96H_50) + setCtrDevice(new CtrHid(*this, 1)); + + addMemRegion(MR_USER, 0x080, 3968 , MA_READ | MA_WRITE); +} + +UsbDio96h::~UsbDio96h() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbDio96h.h b/src/hid/UsbDio96h.h new file mode 100644 index 0000000..e811646 --- /dev/null +++ b/src/hid/UsbDio96h.h @@ -0,0 +1,24 @@ +/* + * UsbDio96h.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBDIO96H_H_ +#define HID_USBDIO96H_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbDio96h: public HidDaqDevice +{ +public: + UsbDio96h(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbDio96h(); +}; + +} /* namespace ul */ + +#endif /* HID_USBDIO96H_H_ */ diff --git a/src/hid/UsbErbxx.cpp b/src/hid/UsbErbxx.cpp new file mode 100644 index 0000000..3caa495 --- /dev/null +++ b/src/hid/UsbErbxx.cpp @@ -0,0 +1,25 @@ +/* + * UsbErbxx.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbErbxx.h" +#include "./dio/DioUsbErbxx.h" + +namespace ul +{ + +UsbErbxx::UsbErbxx(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setDioDevice(new DioUsbErbxx(*this)); + + addMemRegion(MR_USER, 0x080, 3968 , MA_READ | MA_WRITE); +} + +UsbErbxx::~UsbErbxx() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbErbxx.h b/src/hid/UsbErbxx.h new file mode 100644 index 0000000..837cb77 --- /dev/null +++ b/src/hid/UsbErbxx.h @@ -0,0 +1,24 @@ +/* + * UsbErbxx.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBERBXX_H_ +#define HID_USBERBXX_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbErbxx: public HidDaqDevice +{ +public: + UsbErbxx(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbErbxx(); +}; + +} /* namespace ul */ + +#endif /* HID_USBERBXX_H_ */ diff --git a/src/hid/UsbPdiso8.cpp b/src/hid/UsbPdiso8.cpp new file mode 100644 index 0000000..f51b34b --- /dev/null +++ b/src/hid/UsbPdiso8.cpp @@ -0,0 +1,25 @@ +/* + * UsbPdiso8.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbPdiso8.h" +#include "./dio/DioUsbPdiso8.h" + +namespace ul +{ + +UsbPdiso8::UsbPdiso8(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setDioDevice(new DioUsbPdiso8(*this)); + + addMemRegion(MR_USER, 0x080, 3968 , MA_READ | MA_WRITE); +} + +UsbPdiso8::~UsbPdiso8() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbPdiso8.h b/src/hid/UsbPdiso8.h new file mode 100644 index 0000000..f7e77ce --- /dev/null +++ b/src/hid/UsbPdiso8.h @@ -0,0 +1,24 @@ +/* + * UsbPdiso8.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBPDISO8_H_ +#define HID_USBPDISO8_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbPdiso8: public HidDaqDevice +{ +public: + UsbPdiso8(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbPdiso8(); +}; + +} /* namespace ul */ + +#endif /* HID_USBPDISO8_H_ */ diff --git a/src/hid/UsbSsrxx.cpp b/src/hid/UsbSsrxx.cpp new file mode 100644 index 0000000..d43c7bc --- /dev/null +++ b/src/hid/UsbSsrxx.cpp @@ -0,0 +1,25 @@ +/* + * UsbSsrxx.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbSsrxx.h" +#include "./dio/DioUsbSsrxx.h" + +namespace ul +{ + +UsbSsrxx::UsbSsrxx(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setDioDevice(new DioUsbSsrxx(*this)); + + addMemRegion(MR_USER, 0x080, 3968 , MA_READ | MA_WRITE); +} + +UsbSsrxx::~UsbSsrxx() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbSsrxx.h b/src/hid/UsbSsrxx.h new file mode 100644 index 0000000..b42bf42 --- /dev/null +++ b/src/hid/UsbSsrxx.h @@ -0,0 +1,24 @@ +/* + * UsbSsrxx.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBSSRXX_H_ +#define HID_USBSSRXX_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbSsrxx: public HidDaqDevice +{ +public: + UsbSsrxx(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbSsrxx(); +}; + +} /* namespace ul */ + +#endif /* HID_USBSSRXX_H_ */ diff --git a/src/hid/UsbTemp.cpp b/src/hid/UsbTemp.cpp new file mode 100644 index 0000000..68ad5e9 --- /dev/null +++ b/src/hid/UsbTemp.cpp @@ -0,0 +1,28 @@ +/* + * UsbTemp.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbTemp.h" +#include "./ai/AiUsbTemp.h" +#include "./dio/DioHidAux.h" + +namespace ul +{ + +UsbTemp::UsbTemp(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setAiDevice(new AiUsbTemp(*this)); + setDioDevice(new DioHidAux(*this)); + + addMemRegion(MR_USER, 0, 256 , MA_READ | MA_WRITE); + addMemRegion(MR_CAL, 0xF0, 6, MA_READ); +} + +UsbTemp::~UsbTemp() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbTemp.h b/src/hid/UsbTemp.h new file mode 100644 index 0000000..475f8f2 --- /dev/null +++ b/src/hid/UsbTemp.h @@ -0,0 +1,24 @@ +/* + * UsbTemp.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBTEMP_H_ +#define HID_USBTEMP_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbTemp: public HidDaqDevice +{ +public: + UsbTemp(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbTemp(); +}; + +} /* namespace ul */ + +#endif /* HID_USBTEMP_H_ */ diff --git a/src/hid/UsbTempAi.cpp b/src/hid/UsbTempAi.cpp new file mode 100644 index 0000000..4a9cbd2 --- /dev/null +++ b/src/hid/UsbTempAi.cpp @@ -0,0 +1,31 @@ +/* + * UsbTempAi.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "UsbTempAi.h" +#include "./ai/AiUsbTempAi.h" +#include "./dio/DioHidAux.h" +#include "./ctr/CtrHid.h" + +namespace ul +{ + +UsbTempAi::UsbTempAi(const DaqDeviceDescriptor& daqDeviceDescriptor) : HidDaqDevice(daqDeviceDescriptor) +{ + setAiDevice(new AiUsbTempAi(*this)); + setDioDevice(new DioHidAux(*this)); + setCtrDevice(new CtrHid(*this, 1)); + + addMemRegion(MR_USER, 0, 256 , MA_READ | MA_WRITE); + addMemRegion(MR_CAL, 0xF0, 6, MA_READ); + +} + +UsbTempAi::~UsbTempAi() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/UsbTempAi.h b/src/hid/UsbTempAi.h new file mode 100644 index 0000000..7e938e8 --- /dev/null +++ b/src/hid/UsbTempAi.h @@ -0,0 +1,24 @@ +/* + * UsbTempAi.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_USBTEMPAI_H_ +#define HID_USBTEMPAI_H_ + +#include "HidDaqDevice.h" + +namespace ul +{ + +class UL_LOCAL UsbTempAi: public HidDaqDevice +{ +public: + UsbTempAi(const DaqDeviceDescriptor& daqDeviceDescriptor); + virtual ~UsbTempAi(); +}; + +} /* namespace ul */ + +#endif /* HID_USBTEMPAI_H_ */ diff --git a/src/hid/ai/AiHidBase.cpp b/src/hid/ai/AiHidBase.cpp new file mode 100644 index 0000000..d4a9352 --- /dev/null +++ b/src/hid/ai/AiHidBase.cpp @@ -0,0 +1,64 @@ +/* + * AiHidBase.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "AiHidBase.h" + +namespace ul +{ + +AiHidBase::AiHidBase(const HidDaqDevice& daqDevice): AiDevice(daqDevice), mHidDevice(daqDevice) +{ + +} + +AiHidBase::~AiHidBase() +{ + +} + +void AiHidBase::readCalDate() +{ + unsigned char calDateBuf[6]; + int calDateAddr = mAiInfo.getCalDateAddr(); + + if(calDateAddr != -1 && getScanState() == SS_IDLE) + { + int bytesReceived = daqDev().memRead(MT_EEPROM, MR_CAL, calDateAddr, (unsigned char*)calDateBuf, sizeof(calDateBuf)); + + if(bytesReceived == sizeof(calDateBuf)) + { + tm time; + memset(&time, 0, sizeof(time)); + + time.tm_year = calDateBuf[0] + 100; + time.tm_mon = calDateBuf[1] - 1; + time.tm_mday = calDateBuf[2]; + time.tm_hour = calDateBuf[3]; + time.tm_min = calDateBuf[4]; + time.tm_sec = calDateBuf[5]; + time.tm_isdst = -1; + + // make sure the date is valid, mktime does not validate the range + if(time.tm_mon <= 11 && time.tm_mday <= 31 && time.tm_hour <= 23 && time.tm_min <= 59 && time.tm_sec <= 60) + { + time_t cal_date_sec = mktime(&time); // seconds since unix epoch + + if(cal_date_sec != -1) // mktime returns -1 if cal date is invalid + mCalDate = cal_date_sec; + + // convert seconds to string + + /*struct tm *timeinfo; + timeinfo = localtime(&cal_date_sec); + char b[100]; + strftime(b, 100, "%Y-%m-%d %H:%M:%S", timeinfo); + std::cout << b << std::endl;*/ + } + } + } +} + +} /* namespace ul */ diff --git a/src/hid/ai/AiHidBase.h b/src/hid/ai/AiHidBase.h new file mode 100644 index 0000000..aefb9a1 --- /dev/null +++ b/src/hid/ai/AiHidBase.h @@ -0,0 +1,33 @@ +/* + * AiHidBase.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_AI_AIHIDBASE_H_ +#define HID_AI_AIHIDBASE_H_ + +#include "../HidDaqDevice.h" +#include "../../AiDevice.h" + +namespace ul +{ + +class UL_LOCAL AiHidBase: public AiDevice +{ +public: + AiHidBase(const HidDaqDevice& daqDevice); + virtual ~AiHidBase(); + + const HidDaqDevice& daqDev() const {return mHidDevice;} + +protected: + virtual void readCalDate(); + +private: + const HidDaqDevice& mHidDevice; +}; + +} /* namespace ul */ + +#endif /* HID_AI_AIHIDBASE_H_ */ diff --git a/src/hid/ai/AiUsbTemp.cpp b/src/hid/ai/AiUsbTemp.cpp new file mode 100644 index 0000000..599ec61 --- /dev/null +++ b/src/hid/ai/AiUsbTemp.cpp @@ -0,0 +1,394 @@ +/* + * AiUsbTemp.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "AiUsbTemp.h" + +#include +#include + +namespace ul +{ + +AiUsbTemp::AiUsbTemp(const HidDaqDevice& daqDevice) : AiHidBase(daqDevice) +{ + mAiInfo.setNumChans(8); + mAiInfo.setNumCjcChans(2); + mAiInfo.hasTempChan(true); + mAiInfo.setNumChansByMode(AI_DIFFERENTIAL, 8); + mAiInfo.setResolution(24); + mAiInfo.setTInFlags(TIN_FF_DEFAULT); + mAiInfo.setTInArrayFlags(TINARRAY_FF_DEFAULT); + + if(daqDev().getDeviceType() == DaqDeviceId::USB_TEMP) + { + mAiInfo.setChanTypes(AI_TC | AI_RTD | AI_THERMISTOR | AI_SEMICONDUCTOR); + mAiInfo.setChanTypes(0, 7, AI_TC | AI_RTD | AI_THERMISTOR | AI_SEMICONDUCTOR); + } + else + { + mAiInfo.setChanTypes(AI_TC); + mAiInfo.setChanTypes(0, 7, AI_TC); + } + + mAiInfo.addInputMode(AI_DIFFERENTIAL); + mAiInfo.addRange(AI_DIFFERENTIAL, BIPPT078VOLTS); + + mAiInfo.setCalDateAddr(0xF0); + + initCustomScales(); +} + +AiUsbTemp::~AiUsbTemp() +{ + +} + +void AiUsbTemp::initialize() +{ + try + { + readCalDate(); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +void AiUsbTemp::tIn(int channel, TempScale scale, TInFlag flags, double* data) +{ + check_TIn_Args(channel, scale, flags); + + float tempValue = 0; + + unsigned char chan = channel; + unsigned char units = 0; + + if(scale == TS_VOLTS || scale == TS_NOSCALE) + units = 1; + + float fData; + + daqDev().queryCmd(CMD_TIN, chan, units, &fData); + + tempValue = mEndian.le_ptr_to_cpu_f32((unsigned char*) &fData); + + switch((int)(tempValue)) + { + case -8888: // if the temp Value is -8888.0 TC open connection is detected + *data=-9999.0f; + throw UlException(ERR_OPEN_CONNECTION); + break; + case -9000: // if the temp Value is -9000.0 device is not ready yet + throw UlException(ERR_DEV_NOT_READY); + break; + default: + tempValue = convertTempUnit(tempValue, (TempUnit)scale); + *data = mCustomScales[channel].slope * tempValue + mCustomScales[channel].offset; + break; + } +} +void AiUsbTemp::tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) +{ + check_TInArray_Args(lowChan, highChan, scale, flags, data); + + int chanCount = highChan - lowChan + 1; + unsigned char startChan = lowChan; + unsigned char endChan = highChan; + unsigned char units = 0; + + bool openConnection = false; + + if(scale == TS_VOLTS || scale == TS_NOSCALE) + units = 1; + + float fData[8]; + memset(fData, 0 , 8 * sizeof(float)); + + daqDev().queryCmd(CMD_TINSCAN, startChan, endChan, units, (unsigned char*) fData, chanCount * sizeof(float)); + + float tempValue = 0; + int channel = 0; + + for(int i = 0; i < chanCount; i++) + { + tempValue = mEndian.le_ptr_to_cpu_f32((unsigned char*) &fData[i]); + channel = lowChan + i; + + switch((int)(tempValue)) + { + case -8888: // if the temp Value is -8888.0 TC open connection is detected + data[i] = -9999.0f; + openConnection = true; + break; + case -9000: // if the temp Value is -9000.0 device is not ready yet + throw UlException(ERR_DEV_NOT_READY); + break; + case -9999: + break; + default: + data[i] = convertTempUnit(tempValue, (TempUnit)scale); + data[i] = mCustomScales[channel].slope * data[i] + mCustomScales[channel].offset; + break; + } + } + + if(openConnection) + throw UlException(ERR_OPEN_CONNECTION); + +} + +unsigned char AiUsbTemp::tcCode(TcType tcType) const +{ + unsigned char tcCode = 0; + switch(tcType) + { + case TC_J: + tcCode = 0; + break; + case TC_K: + tcCode = 1; + break; + case TC_T: + tcCode = 2; + break; + case TC_E: + tcCode = 3; + break; + case TC_R: + tcCode = 4; + break; + case TC_S: + tcCode = 5; + break; + case TC_B: + tcCode = 6; + break; + case TC_N: + tcCode = 7; + break; + } + + return tcCode; +} + +TcType AiUsbTemp::tcType(unsigned char tcCode) const +{ + TcType tcType; + + switch(tcCode) + { + case 0: + tcType = TC_J; + break; + case 1: + tcType = TC_K; + break; + case 2: + tcType = TC_T; + break; + case 3: + tcType = TC_E; + break; + case 4: + tcType = TC_R; + break; + case 5: + tcType = TC_S; + break; + case 6: + tcType = TC_B; + break; + case 7: + tcType = TC_N; + break; + default: + tcType = (TcType) 0; + break; + } + + return tcType; +} + +AiChanType AiUsbTemp::getCfg_ChanType(int channel) const +{ + AiChanType chanType; + + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + unsigned char adc = channel / 2; + unsigned char subItem = SUBITEM_SENSOR_TYPE; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char sensorType = buf[0]; + + switch(sensorType) + { + case ST_RTD: + chanType = AI_RTD; + break; + case ST_THERMISTOR: + chanType = AI_THERMISTOR; + break; + case ST_THERMOCOUPLE: + chanType = AI_TC; + break; + case ST_SEMICONDUCTOR: + chanType = AI_SEMICONDUCTOR; + break; + default: + chanType = AI_DISABLED; + break; + } + + return chanType; +} + +SensorConnectionType AiUsbTemp::getCfg_SensorConnectionType(int channel) const +{ + SensorConnectionType connectionType = (SensorConnectionType) 0; + + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + AiChanType chanType = getCfg_ChanType(channel); + + if(chanType == AI_RTD || chanType == AI_THERMISTOR) + { + unsigned char adc = channel / 2; + unsigned char subItem = SUBITEM_CONNECTION_TYPE; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char sensorType = buf[0]; + + switch(sensorType) + { + case 0: + connectionType = SCT_2_WIRE_1; + break; + case 1: + connectionType = SCT_2_WIRE_2; + break; + case 2: + connectionType = SCT_3_WIRE; + break; + case 3: + connectionType = SCT_4_WIRE; + break; + } + } + + return connectionType; +} + +void AiUsbTemp::getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const +{ + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + if(!coefsStr) + throw UlException(ERR_BAD_BUFFER); + + AiChanType chanType = getCfg_ChanType(channel); + + int coefCount = 0; + if(chanType == AI_RTD) + coefCount = 4; + else if (chanType == AI_THERMISTOR) + coefCount = 3; + else if (chanType == AI_SEMICONDUCTOR) + coefCount = 2; + + if(coefCount) + { + std::ostringstream coefs; + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_COEF0 + adcChan; + float coef; + + for(int i = 0; i < coefCount; i++) + { + daqDev().queryCmd(CMD_GETITEM, adc, subItem, &coef); + + coefs << coef; + + if(i != (coefCount -1)) + coefs << ", "; + + subItem += 2; + } + + if(*len > coefs.str().length()) + { + strcpy(coefsStr, coefs.str().c_str()); + *len = coefs.str().length() + 1; + } + else + { + *len = coefs.str().length() + 1; + throw UlException(ERR_BAD_BUFFER); + } + } + else + *len = 0; +} + +void AiUsbTemp::setCfg_ChanTcType(int channel, TcType tcType) +{ + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + if(tcType < TC_J || tcType > TC_N) + throw UlException(ERR_BAD_TC_TYPE); + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_TC_TYPE + adcChan; + unsigned char tcCodeVal = tcCode(tcType); + + daqDev().sendCmd(CMD_SETITEM, adc, subItem, tcCodeVal); +} + +TcType AiUsbTemp::getCfg_ChanTcType(int channel) const +{ + TcType tcTypeVal; + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_TC_TYPE + adcChan; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char tcCodeVal = buf[0]; + + tcTypeVal = tcType(tcCodeVal); + + return tcTypeVal; +} + +} /* namespace ul */ diff --git a/src/hid/ai/AiUsbTemp.h b/src/hid/ai/AiUsbTemp.h new file mode 100644 index 0000000..582823d --- /dev/null +++ b/src/hid/ai/AiUsbTemp.h @@ -0,0 +1,52 @@ +/* + * AiUsbTemp.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_AI_AIUSBTEMP_H_ +#define HID_AI_AIUSBTEMP_H_ + +#include "AiHidBase.h" + +namespace ul +{ + +class UL_LOCAL AiUsbTemp: public AiHidBase +{ +public: + AiUsbTemp(const HidDaqDevice& daqDevice); + virtual ~AiUsbTemp(); + + virtual void initialize(); + + virtual void tIn(int channel, TempScale scale, TInFlag flags, double* data); + virtual void tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]); + + ////////////////////// Configuration functions ///////////////////////////////// + virtual AiChanType getCfg_ChanType(int channel) const; + virtual SensorConnectionType getCfg_SensorConnectionType(int channel) const; + + virtual void getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const; + + virtual void setCfg_ChanTcType(int channel, TcType tcType); + virtual TcType getCfg_ChanTcType(int channel) const; + +protected: + virtual void loadAdcCoefficients() {}; + virtual int getCalCoefIndex(int channel, AiInputMode inputMode, Range range) const { return 0; } + + unsigned char tcCode(TcType tcType) const; + TcType tcType(unsigned char tcCode) const; + +private: + enum {CMD_TIN = 0x18, CMD_TINSCAN = 0x19, CMD_SETITEM = 0x49, CMD_GETITEM = 0x4A}; + enum {SUBITEM_SENSOR_TYPE = 0, SUBITEM_CONNECTION_TYPE = 1, SUBITEM_TC_TYPE = 0x10, SUBITEM_COEF0 = 0x14}; + + enum {ST_RTD = 0, ST_THERMISTOR = 1, ST_THERMOCOUPLE = 2, ST_SEMICONDUCTOR = 3, ST_DISABLED = 4}; + enum {CT_2W_1S = 0, CT_2W_2S = 1, CT_3W = 2, CT_4W = 3 }; +}; + +} /* namespace ul */ + +#endif /* HID_AI_AIUSBTEMP_H_ */ diff --git a/src/hid/ai/AiUsbTempAi.cpp b/src/hid/ai/AiUsbTempAi.cpp new file mode 100644 index 0000000..9f534b4 --- /dev/null +++ b/src/hid/ai/AiUsbTempAi.cpp @@ -0,0 +1,624 @@ +/* + * AiUsbTempAi.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "AiUsbTempAi.h" +#include +#include +#include + +namespace ul +{ + +AiUsbTempAi::AiUsbTempAi(const HidDaqDevice& daqDevice) : AiHidBase(daqDevice) +{ + mAiInfo.setNumChans(8); + mAiInfo.setNumCjcChans(2); + mAiInfo.hasTempChan(true); + mAiInfo.setNumChansByMode(AI_DIFFERENTIAL, 8); + mAiInfo.setNumChansByMode(AI_SINGLE_ENDED, 4); + mAiInfo.setResolution(24); + mAiInfo.setAInFlags(AIN_FF_NOSCALEDATA); + mAiInfo.setTInFlags(TIN_FF_DEFAULT); + mAiInfo.setTInArrayFlags(TINARRAY_FF_DEFAULT); + + + if(daqDev().getDeviceType() == DaqDeviceId::USB_TEMP_AI) + { + mAiInfo.setChanTypes(AI_VOLTAGE | AI_TC | AI_RTD | AI_THERMISTOR | AI_SEMICONDUCTOR); + mAiInfo.setChanTypes(0, 3, AI_TC | AI_RTD | AI_THERMISTOR | AI_SEMICONDUCTOR); + mAiInfo.setChanTypes(4, 7, AI_VOLTAGE); + } + else + { + mAiInfo.setChanTypes(AI_VOLTAGE | AI_TC); + mAiInfo.setChanTypes(0, 3, AI_TC); + mAiInfo.setChanTypes(4, 7, AI_VOLTAGE); + } + + mAiInfo.addInputMode(AI_SINGLE_ENDED); + mAiInfo.addInputMode(AI_DIFFERENTIAL); + + mAiInfo.setCalDateAddr(0xF0); + + addSupportedRanges(); + + initTempUnits(); + initCustomScales(); + + memset(mCurrentChanCfg, 0, sizeof(mCurrentChanCfg)); +} + +AiUsbTempAi::~AiUsbTempAi() +{ + +} + +void AiUsbTempAi::initialize() +{ + try + { + readCalDate(); + + unsigned char adc, adcChan, subItem, modeCode, rangeCode; + + // read and store voltage channels mode and range + for(int chan = 4; chan < mAiInfo.getNumChans(); chan++) + { + adc = chan / 2; + adcChan = chan % 2; + subItem = SUBITEM_CHAN_MODE + adcChan; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, &modeCode); + + mCurrentChanCfg[chan].inputMode = (AiInputMode) 0; + + if(modeCode == 0) + mCurrentChanCfg[chan].inputMode = AI_DIFFERENTIAL; + else if(modeCode == 1) + mCurrentChanCfg[chan].inputMode = AI_SINGLE_ENDED; + + + subItem = SUBITEM_CHAN_RANGE + adcChan; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, &rangeCode); + + mCurrentChanCfg[chan].range = (Range) 0; + + switch(rangeCode) + { + case 2: + mCurrentChanCfg[chan].range = BIP10VOLTS; + break; + case 3: + mCurrentChanCfg[chan].range = BIP5VOLTS; + break; + case 4: + mCurrentChanCfg[chan].range = BIP2PT5VOLTS; + break; + case 5: + mCurrentChanCfg[chan].range = BIP1PT25VOLTS; + break; + } + } + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +double AiUsbTempAi::aIn(int channel, AiInputMode inputMode, Range range, AInFlag flags) +{ + check_AIn_Args(channel, inputMode, range, flags); + + double data = 0.0; + bool chanCfgChanged = false; + + if(channel >= 4) // voltage channels + { + if(mCurrentChanCfg[channel].inputMode != inputMode) + { + setInputMode(channel, inputMode); + chanCfgChanged = true; + } + + if(mCurrentChanCfg[channel].range != range) + { + setRange(channel, range); + chanCfgChanged = true; + } + + if(chanCfgChanged) + usleep(1000000); + + unsigned char chan = channel; + unsigned char units = 0; + + if(flags & AIN_FF_NOSCALEDATA) + units = 1; + + float fData; + daqDev().queryCmd(CMD_AIN, chan, units, &fData); + + data = mEndian.le_ptr_to_cpu_f32((unsigned char*) &fData); + data = mCustomScales[channel].slope * data + mCustomScales[channel].offset; + + } + else + throw UlException(ERR_BAD_AI_CHAN); + + return data; +} + +void AiUsbTempAi::tIn(int channel, TempScale scale, TInFlag flags, double* data) +{ + check_TIn_Args(channel, scale, flags); + + float tempValue = 0; + + unsigned char chan = channel; + unsigned char units = 0; + + // remove if we need TIn to read voltage channels + if(channel > 3) + throw UlException(ERR_BAD_AI_CHAN); + + if(channel < 4 && (scale == TS_VOLTS || scale == TS_NOSCALE)) + units = 1; + + float fData; + daqDev().queryCmd(CMD_AIN, chan, units, &fData); + + tempValue = mEndian.le_ptr_to_cpu_f32((unsigned char*) &fData); + + if(channel < 4) // perform conversion if a temp channel + { + switch((int)(tempValue)) + { + case -8888: // if the temp Value is -8888.0 TC open connection is detected + *data =-9999.0f; + throw UlException(ERR_OPEN_CONNECTION); + break; + case -9000: // if the temp Value is -9000.0 device is not ready yet + throw UlException(ERR_DEV_NOT_READY); + break; + default: + tempValue = convertTempUnit(tempValue, (TempUnit)scale); + *data = mCustomScales[channel].slope * tempValue + mCustomScales[channel].offset; + break; + } + } +} +void AiUsbTempAi::tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) +{ + check_TInArray_Args(lowChan, highChan, scale, flags, data); + + int chanCount = highChan - lowChan + 1; + unsigned char startChan = lowChan; + unsigned char endChan = highChan; + unsigned char units = 0; + + bool openConnection = false; + + if(scale == TS_VOLTS || scale == TS_NOSCALE) + units = 1; + + float fData[8]; + float tempValue = 0; + int channel = 0; + + memset(fData, 0 , 8 * sizeof(float)); + + daqDev().queryCmd(CMD_AINSCAN, startChan, endChan, units, (unsigned char*) fData, chanCount * sizeof(float)); + + for(int i = 0; i < chanCount; i++) + { + tempValue = mEndian.le_ptr_to_cpu_f32((unsigned char*)&fData[i]); + channel = lowChan + i; + + if((lowChan + i) < 4) // perform conversion if a temp channel + { + switch((int)(tempValue)) + { + case -8888: // if the temp Value is -8888.0 TC open connection is detected + data[i] = -9999.0f; + openConnection = true; + break; + case -9000: // if the temp Value is -9000.0 device is not ready yet + throw UlException(ERR_DEV_NOT_READY); + break; // if the temp Value is -9000.0 device is not ready yet + case -9999: + data[i] = tempValue; + break; + default: + data[i] = convertTempUnit(tempValue, (TempUnit)scale); + data[i] = mCustomScales[channel].slope * data[i] + mCustomScales[channel].offset; + break; + } + } + } + + if(openConnection) + throw UlException(ERR_OPEN_CONNECTION); +} + +void AiUsbTempAi::setInputMode(int channel, AiInputMode mode) +{ + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_CHAN_MODE + adcChan; + unsigned char modeCode = (mode == AI_SINGLE_ENDED) ? 1 : 0; + + daqDev().sendCmd(CMD_SETITEM, adc, subItem, modeCode); + + mCurrentChanCfg[channel].inputMode = mode; +} +void AiUsbTempAi::setRange(int channel, Range range) +{ + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_CHAN_RANGE + adcChan; + unsigned char rangeCode = getRangeCode(range); + + daqDev().sendCmd(CMD_SETITEM, adc, subItem, rangeCode); + + mCurrentChanCfg[channel].range = range; +} + +unsigned char AiUsbTempAi::tcCode(TcType tcType) const +{ + unsigned char tcCode = 0; + switch(tcType) + { + case TC_J: + tcCode = 0; + break; + case TC_K: + tcCode = 1; + break; + case TC_T: + tcCode = 2; + break; + case TC_E: + tcCode = 3; + break; + case TC_R: + tcCode = 4; + break; + case TC_S: + tcCode = 5; + break; + case TC_B: + tcCode = 6; + break; + case TC_N: + tcCode = 7; + break; + } + + return tcCode; +} + +TcType AiUsbTempAi::tcType(unsigned char tcCode) const +{ + TcType tcType; + + switch(tcCode) + { + case 0: + tcType = TC_J; + break; + case 1: + tcType = TC_K; + break; + case 2: + tcType = TC_T; + break; + case 3: + tcType = TC_E; + break; + case 4: + tcType = TC_R; + break; + case 5: + tcType = TC_S; + break; + case 6: + tcType = TC_B; + break; + case 7: + tcType = TC_N; + break; + default: + tcType = (TcType) 0; + break; + } + + return tcType; +} + +unsigned char AiUsbTempAi::getRangeCode(Range range) const +{ + unsigned char code; + + switch(range) + { + case BIP10VOLTS: + code = 2; + break; + case BIP5VOLTS: + code = 3; + break; + case BIP2PT5VOLTS: + code = 4; + break; + case BIP1PT25VOLTS: + code = 5; + break; + default: + code = 2; + break; + } + + return code; +} + +AiChanType AiUsbTempAi::getCfg_ChanType(int channel) const +{ + AiChanType chanType; + + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP_AI) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + if(channel < 4) + { + unsigned char adc = channel / 2; + unsigned char subItem = SUBITEM_SENSOR_TYPE; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char sensorType = buf[0]; + + switch(sensorType) + { + case 0: + chanType = AI_RTD; + break; + case 1: + chanType = AI_THERMISTOR; + break; + case 2: + chanType = AI_TC; + break; + case 3: + chanType = AI_SEMICONDUCTOR; + break; + default: + chanType = AI_DISABLED; + break; + } + } + else + { + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_CHAN_MODE + adcChan; + unsigned char modeCode = 0; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, &modeCode); + + if(modeCode == 0x02) + chanType = AI_DISABLED; + else + chanType = AI_VOLTAGE; + } + + return chanType; +} + +SensorConnectionType AiUsbTempAi::getCfg_SensorConnectionType(int channel) const +{ + SensorConnectionType connectionType = (SensorConnectionType) 0; + + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP_AI) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + AiChanType chanType = getCfg_ChanType(channel); + + if(chanType == AI_RTD || chanType == AI_THERMISTOR) + { + unsigned char adc = channel / 2; + unsigned char subItem = SUBITEM_CONNECTION_TYPE; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char sensorType = buf[0]; + + switch(sensorType) + { + case 0: + connectionType = SCT_2_WIRE_1; + break; + case 1: + connectionType = SCT_2_WIRE_2; + break; + case 2: + connectionType = SCT_3_WIRE; + break; + case 3: + connectionType = SCT_4_WIRE; + break; + } + } + + return connectionType; +} + +void AiUsbTempAi::getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const +{ + if(daqDev().getDeviceType() != DaqDeviceId::USB_TEMP_AI) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + if(channel < 0 || channel >= mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + if(!coefsStr) + throw UlException(ERR_BAD_BUFFER); + + AiChanType chanType = getCfg_ChanType(channel); + + int coefCount = 0; + if(chanType == AI_RTD) + coefCount = 4; + else if (chanType == AI_THERMISTOR) + coefCount = 3; + else if (chanType == AI_SEMICONDUCTOR) + coefCount = 2; + + if(coefCount) + { + std::ostringstream coefs; + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_COEF0 + adcChan; + float coef; + + for(int i = 0; i < coefCount; i++) + { + daqDev().queryCmd(CMD_GETITEM, adc, subItem, &coef); + + coefs << coef; + + if(i != (coefCount -1)) + coefs << ", "; + + subItem += 2; + } + + if(*len > coefs.str().length()) + { + strcpy(coefsStr, coefs.str().c_str()); + *len = coefs.str().length() + 1; + } + else + { + *len = coefs.str().length() + 1; + throw UlException(ERR_BAD_BUFFER); + } + } + else + *len = 0; +} + + +void AiUsbTempAi::setCfg_ChanTcType(int channel, TcType tcType) +{ + if(channel < 0 || channel > 3) + throw UlException(ERR_BAD_AI_CHAN); + + if(tcType < TC_J || tcType > TC_N) + throw UlException(ERR_BAD_TC_TYPE); + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_TC_TYPE + adcChan; + unsigned char tcCodeVal = tcCode(tcType); + + daqDev().sendCmd(CMD_SETITEM, adc, subItem, tcCodeVal); +} + +TcType AiUsbTempAi::getCfg_ChanTcType(int channel) const +{ + TcType tcTypeVal; + if(channel < 0 || channel > 3) + throw UlException(ERR_BAD_AI_CHAN); + + unsigned char adc = channel / 2; + unsigned char adcChan = channel % 2; + unsigned char subItem = SUBITEM_TC_TYPE + adcChan; + + unsigned char buf[4]; + + daqDev().queryCmd(CMD_GETITEM, adc, subItem, buf); + + unsigned char tcCodeVal = buf[0]; + + tcTypeVal = tcType(tcCodeVal); + + return tcTypeVal; +} + +void AiUsbTempAi::check_AIn_Args(int channel, AiInputMode inputMode, Range range, AInFlag flags) const +{ + if(channel < 0 || channel > mAiInfo.getNumChans()) + throw UlException(ERR_BAD_AI_CHAN); + + if(!mAiInfo.isInputModeSupported(inputMode)) + throw UlException(ERR_BAD_INPUT_MODE); + + if(!mAiInfo.isRangeSupported(inputMode, range)) + throw UlException(ERR_BAD_RANGE); + + if(channel > 3 && range == BIPPT078VOLTS) + throw UlException(ERR_BAD_RANGE); + + if(~mAiInfo.getAInFlags() & flags) + throw UlException(ERR_BAD_FLAG); + + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); + + if((int) mCustomScales.size() < mAiInfo.getNumChans()) + throw UlException(ERR_INTERNAL); +} + +TempScale AiUsbTempAi::getTempScale(TempUnit unit) +{ + TempScale scale; + + switch(unit) + { + case TU_FAHRENHEIT: + scale = TS_FAHRENHEIT; + break; + case TU_KELVIN: + scale = TS_KELVIN; + break; + default: + scale = TS_CELSIUS; + } + + return scale; +} + + +void AiUsbTempAi::addSupportedRanges() +{ + mAiInfo.addRange(AI_SINGLE_ENDED, BIP10VOLTS); + mAiInfo.addRange(AI_SINGLE_ENDED, BIP5VOLTS); + mAiInfo.addRange(AI_SINGLE_ENDED, BIP2PT5VOLTS); + mAiInfo.addRange(AI_SINGLE_ENDED, BIP1PT25VOLTS); + + mAiInfo.addRange(AI_DIFFERENTIAL, BIP10VOLTS); + mAiInfo.addRange(AI_DIFFERENTIAL, BIP5VOLTS); + mAiInfo.addRange(AI_DIFFERENTIAL, BIP2PT5VOLTS); + mAiInfo.addRange(AI_DIFFERENTIAL, BIP1PT25VOLTS); + mAiInfo.addRange(AI_DIFFERENTIAL, BIPPT078VOLTS); +} + + +} /* namespace ul */ diff --git a/src/hid/ai/AiUsbTempAi.h b/src/hid/ai/AiUsbTempAi.h new file mode 100644 index 0000000..8f92989 --- /dev/null +++ b/src/hid/ai/AiUsbTempAi.h @@ -0,0 +1,65 @@ +/* + * AiUsbTempAi.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_AI_AIUSBTEMPAI_H_ +#define HID_AI_AIUSBTEMPAI_H_ + +#include "AiHidBase.h" + +namespace ul +{ + +class UL_LOCAL AiUsbTempAi: public AiHidBase +{ +public: + AiUsbTempAi(const HidDaqDevice& daqDevice); + virtual ~AiUsbTempAi(); + + virtual void initialize(); + + virtual double aIn(int channel, AiInputMode inputMode, Range range, AInFlag flags); + virtual void tIn(int channel, TempScale scale, TInFlag flags, double* data); + virtual void tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]); + + ////////////////////// Configuration functions ///////////////////////////////// + virtual AiChanType getCfg_ChanType(int channel) const; + virtual SensorConnectionType getCfg_SensorConnectionType(int channel) const; + virtual void getCfg_ChanCoefsStr(int channel, char* coefsStr, unsigned int* len) const; + + virtual void setCfg_ChanTcType(int channel, TcType tcType); + virtual TcType getCfg_ChanTcType(int channel) const; + +protected: + virtual void loadAdcCoefficients() {}; + virtual int getCalCoefIndex(int channel, AiInputMode inputMode, Range range) const { return 0; } + + unsigned char tcCode(TcType tcType) const; + TcType tcType(unsigned char tcCode) const; + + unsigned char getRangeCode(Range range) const; + + void setInputMode(int channel, AiInputMode mode); + void setRange(int channel, Range range); + + void addSupportedRanges(); + + TempScale getTempScale(TempUnit unit); + + virtual void check_AIn_Args(int channel, AiInputMode inputMode, Range range, AInFlag flags) const; + +private: + enum {CMD_AIN = 0x18, CMD_AINSCAN = 0x19, CMD_SETITEM = 0x49, CMD_GETITEM = 0x4A}; + enum {SUBITEM_SENSOR_TYPE = 0, SUBITEM_CONNECTION_TYPE = 1, SUBITEM_TC_TYPE = 0x10, SUBITEM_CHAN_RANGE = 0x12, SUBITEM_COEF0 = 0x14, SUBITEM_CHAN_MODE = 0x1C}; + + enum {ST_RTD = 0, ST_THERMISTOR = 1, ST_THERMOCOUPLE = 2, ST_SEMICONDUCTOR = 3, ST_DISABLED = 4}; + enum {CT_2W_1S = 0, CT_2W_2S = 1, CT_3W = 2, CT_4W = 3 }; + + AiQueueElement mCurrentChanCfg[8]; +}; + +} /* namespace ul */ + +#endif /* HID_AI_AIUSBTEMPAI_H_ */ diff --git a/src/hid/ao/AoHidBase.cpp b/src/hid/ao/AoHidBase.cpp new file mode 100644 index 0000000..59f2904 --- /dev/null +++ b/src/hid/ao/AoHidBase.cpp @@ -0,0 +1,23 @@ +/* + * AoHidBase.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "AoHidBase.h" + +namespace ul +{ + +AoHidBase::AoHidBase(const HidDaqDevice& daqDevice): AoDevice(daqDevice), mHidDevice(daqDevice) +{ + + +} + +AoHidBase::~AoHidBase() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/ao/AoHidBase.h b/src/hid/ao/AoHidBase.h new file mode 100644 index 0000000..c9547a3 --- /dev/null +++ b/src/hid/ao/AoHidBase.h @@ -0,0 +1,30 @@ +/* + * AoHidBase.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_AO_AOHIDBASE_H_ +#define HID_AO_AOHIDBASE_H_ + +#include "../HidDaqDevice.h" +#include "../../AoDevice.h" + +namespace ul +{ + +class UL_LOCAL AoHidBase: public AoDevice +{ +public: + AoHidBase(const HidDaqDevice& daqDevice); + virtual ~AoHidBase(); + + const HidDaqDevice& daqDev() const {return mHidDevice;} + +private: + const HidDaqDevice& mHidDevice; +}; + +} /* namespace ul */ + +#endif /* HID_AO_AOHIDBASE_H_ */ diff --git a/src/hid/ao/AoUsb3100.cpp b/src/hid/ao/AoUsb3100.cpp new file mode 100644 index 0000000..417486c --- /dev/null +++ b/src/hid/ao/AoUsb3100.cpp @@ -0,0 +1,268 @@ +/* + * AoUsb3100.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "AoUsb3100.h" + +namespace ul +{ + +AoUsb3100::AoUsb3100(const HidDaqDevice& daqDevice) : AoHidBase(daqDevice) +{ + mAoInfo.setAOutFlags(AOUT_FF_NOSCALEDATA | AOUT_FF_NOCALIBRATEDATA); + mAoInfo.setAOutArrayFlags(AOUTARRAY_FF_NOSCALEDATA | AOUTARRAY_FF_NOCALIBRATEDATA | AOUTARRAY_FF_SIMULTANEOUS); + + mAoInfo.setNumChans(numChans()); + mAoInfo.setResolution(16); + + mAoInfo.setCalCoefsStartAddr(0x100); + mAoInfo.setSampleSize(2); + + mAoInfo.addRange(BIP10VOLTS); + mAoInfo.addRange(UNI10VOLTS); + int rangeCount = 2; + + if(hasCurrentOutput()) + { + mAoInfo.addRange(MA0TO20); + rangeCount++; + } + + mAoInfo.setCalCoefCount(rangeCount * mAoInfo.getNumChans()); + + mSyncMode = AOSM_SLAVE; + + memset(mChanCurrentRange, 0, sizeof(mChanCurrentRange)); +} + +AoUsb3100::~AoUsb3100() +{ + +} + +void AoUsb3100::initialize() +{ + memset(mChanCurrentRange, 0, sizeof(mChanCurrentRange)); + + try + { + mSyncMode = readSyncMode(); + + loadDacCoefficients(); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +void AoUsb3100::aOut(int channel, Range range, AOutFlag flags, double dataValue) +{ + check_AOut_Args(channel, range, flags, dataValue); + + writeData(channel, range, flags, dataValue, UPDATE_IMMEDIATE); +} + +void AoUsb3100::aOutArray(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) +{ + check_AOutArray_Args(lowChan, highChan, range, flags, data); + + unsigned char updateMode = (flags & AOUTARRAY_FF_SIMULTANEOUS) ? UPDATE_ON_SYNC : UPDATE_IMMEDIATE; + + int i = 0; + for(int chan = lowChan; chan <= highChan; chan++) + { + writeData(chan, range[i], (AOutFlag) flags, data[i], updateMode); + i++; + } + + if(flags & AOUTARRAY_FF_SIMULTANEOUS) + { + if(mSyncMode == AOSM_MASTER) + daqDev().sendCmd(CMD_AOUTSYNC); + } +} + +void AoUsb3100::writeData(int channel, Range range, AOutFlag flags, double dataValue, unsigned char updateMode) +{ + if(mChanCurrentRange[channel] != range) + configChanRange(channel, range); + + unsigned char chan = channel; + unsigned short calData = calibrateData(channel, range, flags, dataValue); + + if(range == MA0TO20 && dataValue == 0.0) // don't apply cal factors if output value is zero, from the windows code + calData = 0; + + calData = Endian::cpu_to_le_ui16(calData); + + daqDev().sendCmd(CMD_AOUT, chan, calData, updateMode); +} + + +void AoUsb3100::configChanRange(int channel, Range range) +{ + unsigned char rangeCode = mapRangeCode(range); + + daqDev().sendCmd(CMD_AOUTCONFIG, (unsigned char) channel , rangeCode); + + mChanCurrentRange[channel] = range; +} + +AOutSyncMode AoUsb3100::readSyncMode() const +{ + unsigned char cmd = CMD_STATUS; + unsigned char status = 0; + + daqDev().queryCmd(cmd, &status); + + AOutSyncMode mode = (AOutSyncMode)(status & 0x01); + + return mode; +} + +void AoUsb3100::writeSyncMode(AOutSyncMode mode) const +{ + unsigned char cmd = CMD_SETSYNC; + unsigned char type = (mode == 0) ? 1 : 0; + + daqDev().sendCmd(cmd, type); +} + +int AoUsb3100::mapRangeCode(Range range) const +{ + int rangeCode; + + switch(range) + { + case UNI10VOLTS: + rangeCode = 0; + break; + case BIP10VOLTS: + rangeCode = 1; + break; + case MA0TO20: + rangeCode = 0; + break; + default: + throw UlException(ERR_BAD_RANGE); + } + + return rangeCode; +} + +int AoUsb3100::getCalCoefIndex(int channel, Range range) const +{ + int index = 0; + + if(range == UNI10VOLTS) + index = channel * 2; + else if(range == BIP10VOLTS) + index = channel * 2 + 1; + else if(range == MA0TO20) + index = mAoInfo.getNumChans() * 2 + channel; + + return index; +} + +void AoUsb3100::loadDacCoefficients() +{ +#pragma pack(1) + typedef struct + { + unsigned char slope[4]; + unsigned char offset[4]; + } coef; +#pragma pack() + + CalCoef calCoef; + + mCalCoefs.clear(); + + bool readCurOutCoefs = false; + int calCoefCount = mAoInfo.getNumChans() * 2; + int calBlockSize = calCoefCount * sizeof(coef); + int address = mAoInfo.getCalCoefsStartAddr(); + + coef* buffer = new coef[calCoefCount]; + +start: + + int bytesReceived = daqDev().memRead(MT_EEPROM, MR_CAL, address, (unsigned char*)buffer, calBlockSize); + + if(bytesReceived == calBlockSize) + { + for(int i = 0; i < calCoefCount; i++) + { + calCoef.slope = mEndian.le_ptr_to_cpu_f32(buffer[i].slope); + calCoef.offset = mEndian.le_ptr_to_cpu_f32(buffer[i].offset); + + mCalCoefs.push_back(calCoef); + } + } + + if(hasCurrentOutput() && !readCurOutCoefs) + { + calCoefCount = mAoInfo.getNumChans(); + calBlockSize = calCoefCount * sizeof(coef); + address = 0x200; + readCurOutCoefs = true; + goto start; + } + + delete [] buffer; +} + + +int AoUsb3100::numChans() const +{ + int numChans; + switch(daqDev().getDeviceType()) + { + case DaqDeviceId::USB_3103: + case DaqDeviceId::USB_3104: + case DaqDeviceId::USB_3112: + numChans = 8; + break; + case DaqDeviceId::USB_3105: + case DaqDeviceId::USB_3106: + case DaqDeviceId::USB_3114: + numChans = 16; + break; + default: + numChans = 4; + break; + } + + return numChans; +} + +bool AoUsb3100::hasCurrentOutput() const +{ + bool hasCurOut = false; + + if(daqDev().getDeviceType() == DaqDeviceId::USB_3102 || + daqDev().getDeviceType() == DaqDeviceId::USB_3104 || + daqDev().getDeviceType() == DaqDeviceId::USB_3106) + { + hasCurOut = true; + } + + return hasCurOut; +} + +void AoUsb3100::setCfg_SyncMode(AOutSyncMode mode) +{ + writeSyncMode(mode); + + mSyncMode = readSyncMode(); +} +AOutSyncMode AoUsb3100::getCfg_SyncMode() const +{ + return readSyncMode(); +} + + +} /* namespace ul */ diff --git a/src/hid/ao/AoUsb3100.h b/src/hid/ao/AoUsb3100.h new file mode 100644 index 0000000..983b056 --- /dev/null +++ b/src/hid/ao/AoUsb3100.h @@ -0,0 +1,54 @@ +/* + * AoUsb3100.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_AO_AOUSB3100_H_ +#define HID_AO_AOUSB3100_H_ + +#include "AoHidBase.h" + +namespace ul +{ + +class UL_LOCAL AoUsb3100: public AoHidBase +{ +public: + AoUsb3100(const HidDaqDevice& daqDevice); + virtual ~AoUsb3100(); + + virtual void initialize(); + + virtual void aOut(int channel, Range range, AOutFlag flags, double dataValue); + virtual void aOutArray(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]); + + void configChanRange(int channel, Range range); + + virtual void setCfg_SyncMode(AOutSyncMode mode); + virtual AOutSyncMode getCfg_SyncMode() const; + +protected: + void writeData(int channel, Range range, AOutFlag flags, double dataValue, unsigned char updateMode); + int mapRangeCode(Range range) const; + virtual int getCalCoefIndex(int channel, Range range) const; + +private: + int numChans() const; + bool hasCurrentOutput() const; + void loadDacCoefficients(); + + AOutSyncMode readSyncMode() const; + void writeSyncMode(AOutSyncMode mode) const; + +private: + enum { CMD_AOUT = 0x14, CMD_AOUTSYNC = 0x15, CMD_AOUTCONFIG = 0x1C, CMD_SETSYNC = 0x43, CMD_STATUS = 0x44}; + enum { UPDATE_IMMEDIATE = 0, UPDATE_ON_SYNC = 1 }; + + Range mChanCurrentRange[16]; + AOutSyncMode mSyncMode; +}; + +} /* namespace ul */ + +#endif /* HID_AO_AOUSB3100_H_ */ diff --git a/src/hid/ctr/CtrHid.cpp b/src/hid/ctr/CtrHid.cpp new file mode 100644 index 0000000..f73a2ff --- /dev/null +++ b/src/hid/ctr/CtrHid.cpp @@ -0,0 +1,64 @@ +/* + * CtrHid.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "CtrHid.h" + +namespace ul +{ + +CtrHid::CtrHid(const HidDaqDevice& daqDevice, int numCtrs) : CtrHidBase(daqDevice) +{ + mCtrInfo.hasPacer(false); + mCtrInfo.setResolution(32); + + for(int ctr = 0; ctr < numCtrs; ctr++) + mCtrInfo.addCtr(CMT_COUNT); + + mCtrInfo.setRegisterTypes(CRT_COUNT | CRT_LOAD); +} + +CtrHid::~CtrHid() +{ + +} + +unsigned long long CtrHid::cIn(int ctrNum) +{ + unsigned int count = 0; + + check_CIn_Args(ctrNum); + + daqDev().queryCmd(CMD_CIN, &count); + + return count; +} + +void CtrHid::cLoad(int ctrNum, CounterRegisterType regType, unsigned long long loadValue) +{ + check_CLoad_Args(ctrNum, regType, loadValue); + + if(loadValue != 0) + throw UlException(ERR_BAD_CTR_VAL); + + daqDev().sendCmd(CMD_CINIT); + +} + +void CtrHid::cClear(int ctrNum) +{ + cLoad(ctrNum, CRT_LOAD, 0); +} + +unsigned long long CtrHid::cRead(int ctrNum, CounterRegisterType regType) +{ + check_CRead_Args(ctrNum, regType); + + return cIn(ctrNum); +} + + + +} /* namespace ul */ diff --git a/src/hid/ctr/CtrHid.h b/src/hid/ctr/CtrHid.h new file mode 100644 index 0000000..1369f34 --- /dev/null +++ b/src/hid/ctr/CtrHid.h @@ -0,0 +1,32 @@ +/* + * CtrHid.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_CTR_CTRHID_H_ +#define HID_CTR_CTRHID_H_ + +#include "CtrHidBase.h" + +namespace ul +{ + +class UL_LOCAL CtrHid: public CtrHidBase +{ +public: + CtrHid(const HidDaqDevice& daqDevice, int numCtrs); + virtual ~CtrHid(); + + virtual unsigned long long cIn(int ctrNum); + void cLoad(int ctrNum, CounterRegisterType regType, unsigned long long loadValue); + virtual void cClear(int ctrNum); + virtual unsigned long long cRead(int ctrNum, CounterRegisterType regType); + +private: + enum { CMD_CINIT = 0x20, CMD_CIN = 0x21 }; +}; + +} /* namespace ul */ + +#endif /* HID_CTR_CTRHID_H_ */ diff --git a/src/hid/ctr/CtrHidBase.cpp b/src/hid/ctr/CtrHidBase.cpp new file mode 100644 index 0000000..77e9d88 --- /dev/null +++ b/src/hid/ctr/CtrHidBase.cpp @@ -0,0 +1,23 @@ +/* + * CtrHidBase.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "CtrHidBase.h" + +namespace ul +{ + +CtrHidBase::CtrHidBase(const HidDaqDevice& daqDevice): CtrDevice(daqDevice), mHidDevice(daqDevice) +{ + + +} + +CtrHidBase::~CtrHidBase() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/ctr/CtrHidBase.h b/src/hid/ctr/CtrHidBase.h new file mode 100644 index 0000000..ebf9ddc --- /dev/null +++ b/src/hid/ctr/CtrHidBase.h @@ -0,0 +1,30 @@ +/* + * CtrHidBase.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_CTR_CTRHIDBASE_H_ +#define HID_CTR_CTRHIDBASE_H_ + +#include "../HidDaqDevice.h" +#include "../../CtrDevice.h" + +namespace ul +{ + +class UL_LOCAL CtrHidBase: public CtrDevice +{ +public: + CtrHidBase(const HidDaqDevice& daqDevice); + virtual ~CtrHidBase(); + + const HidDaqDevice& daqDev() const {return mHidDevice;} + +private: + const HidDaqDevice& mHidDevice; +}; + +} /* namespace ul */ + +#endif /* HID_CTR_CTRHIDBASE_H_ */ diff --git a/src/hid/ctr/CtrUsbDio24.cpp b/src/hid/ctr/CtrUsbDio24.cpp new file mode 100644 index 0000000..9261d3c --- /dev/null +++ b/src/hid/ctr/CtrUsbDio24.cpp @@ -0,0 +1,79 @@ +/* + * CtrUsbDio24.cpp + * + * Created on: Oct 10, 2018 + * Author: mcc + */ + +#include "CtrUsbDio24.h" +#include "../UsbDio24.h" + +namespace ul +{ +CtrUsbDio24::CtrUsbDio24(const HidDaqDevice& daqDevice, int numCtrs) : CtrHidBase(daqDevice) +{ + mCtrInfo.hasPacer(false); + mCtrInfo.setResolution(32); + + for(int ctr = 0; ctr < numCtrs; ctr++) + mCtrInfo.addCtr(CMT_COUNT); + + mCtrInfo.setRegisterTypes(CRT_COUNT | CRT_LOAD); +} + +CtrUsbDio24::~CtrUsbDio24() +{ + +} + +unsigned long long CtrUsbDio24::cIn(int ctrNum) +{ + unsigned int count = 0; + + check_CIn_Args(ctrNum); + + unsigned char outBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + unsigned char inBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + outBuffer[1] = CMD_CIN; + + size_t outBufLen = sizeof(outBuffer); + size_t inBufLen = sizeof(inBuffer); + + daqDev().queryRawCmd(outBuffer, outBufLen, inBuffer, &inBufLen); + + count = Endian::Instance().le_ptr_to_cpu_ui32(inBuffer); + + return count; +} + +void CtrUsbDio24::cLoad(int ctrNum, CounterRegisterType regType, unsigned long long loadValue) +{ + check_CLoad_Args(ctrNum, regType, loadValue); + + if(loadValue != 0) + throw UlException(ERR_BAD_CTR_VAL); + + unsigned char buffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + buffer[1] = CMD_CINIT; + + size_t length = sizeof(buffer); + + daqDev().sendRawCmd(buffer, &length); + +} + +void CtrUsbDio24::cClear(int ctrNum) +{ + cLoad(ctrNum, CRT_LOAD, 0); +} + +unsigned long long CtrUsbDio24::cRead(int ctrNum, CounterRegisterType regType) +{ + check_CRead_Args(ctrNum, regType); + + return cIn(ctrNum); +} + +} /* namespace ul */ diff --git a/src/hid/ctr/CtrUsbDio24.h b/src/hid/ctr/CtrUsbDio24.h new file mode 100644 index 0000000..874e058 --- /dev/null +++ b/src/hid/ctr/CtrUsbDio24.h @@ -0,0 +1,33 @@ +/* + * CtrUsbDio24.h + * + * Created on: Oct 10, 2018 + * Author: mcc + */ + +#ifndef HID_CTR_CTRUSBDIO24_H_ +#define HID_CTR_CTRUSBDIO24_H_ + +#include "CtrHidBase.h" + +namespace ul +{ + +class UL_LOCAL CtrUsbDio24: public CtrHidBase +{ +public: + CtrUsbDio24(const HidDaqDevice& daqDevice, int numCtrs); + virtual ~CtrUsbDio24(); + + virtual unsigned long long cIn(int ctrNum); + void cLoad(int ctrNum, CounterRegisterType regType, unsigned long long loadValue); + virtual void cClear(int ctrNum); + virtual unsigned long long cRead(int ctrNum, CounterRegisterType regType); + +private: + enum { CMD_CIN = 0x04, CMD_CINIT = 0x05}; +}; + +} /* namespace ul */ + +#endif /* HID_CTR_CTRUSBDIO24_H_ */ diff --git a/src/hid/dio/DioHidAux.cpp b/src/hid/dio/DioHidAux.cpp new file mode 100644 index 0000000..68d3eb2 --- /dev/null +++ b/src/hid/dio/DioHidAux.cpp @@ -0,0 +1,117 @@ +/* + * DioHidAux.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioHidAux.h" + +namespace ul +{ + +DioHidAux::DioHidAux(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + mDioInfo.addPort(0, AUXPORT0, 8, DPIOT_BITIO); +} + +DioHidAux::~DioHidAux() +{ + +} + +void DioHidAux::initialize() +{ + try + { + initPortsDirectionMask(); + + dConfigPort(AUXPORT0, DD_INPUT); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +void DioHidAux::dConfigPort(DigitalPortType portType, DigitalDirection direction) +{ + check_DConfigPort_Args(portType, direction); + + unsigned char dir; + + if (direction == DD_OUTPUT) + dir = 0; + else + dir = 1; + + daqDev().sendCmd(CMD_DCONFIG_PORT, dir); + + setPortDirection(portType, direction); +} + +void DioHidAux::dConfigBit(DigitalPortType portType, int bitNum, DigitalDirection direction) +{ + check_DConfigBit_Args(portType, bitNum, direction); + + unsigned char dir; + + if(direction == DD_OUTPUT) + dir = 0; + else + dir = 1; + + daqDev().sendCmd(CMD_DCONFIG_BIT, (unsigned char) bitNum, dir); + + setBitDirection(portType, bitNum, direction); +} + +unsigned long long DioHidAux::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + daqDev().queryCmd(CMD_DIN, &portValue); + + return portValue; +} + +void DioHidAux::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + unsigned char val = data; + + daqDev().sendCmd(CMD_DOUT, val); +} + +bool DioHidAux::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + daqDev().queryCmd(CMD_DBITIN, (unsigned char) bitNum, &bitValue); + + return bitValue? true : false; +} + +void DioHidAux::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + unsigned char bitVal = bitValue ? 1 : 0; + + daqDev().sendCmd(CMD_DBITOUT, (unsigned char) bitNum, bitVal); +} + +unsigned long DioHidAux::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + + mask.set(); + + return mask.to_ulong(); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioHidAux.h b/src/hid/dio/DioHidAux.h new file mode 100644 index 0000000..ac4bfab --- /dev/null +++ b/src/hid/dio/DioHidAux.h @@ -0,0 +1,41 @@ +/* + * DioHidAux.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DioHidAux_H_ +#define HID_DIO_DioHidAux_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioHidAux: public DioHidBase +{ +public: + DioHidAux(const HidDaqDevice& daqDevice); + virtual ~DioHidAux(); + + virtual void initialize(); + + virtual void dConfigPort(DigitalPortType portType, DigitalDirection direction); + virtual void dConfigBit(DigitalPortType portType, int bitNum, DigitalDirection direction); + + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + +private: + enum {CMD_DCONFIG_PORT = 0x01, CMD_DCONFIG_BIT = 0x02, CMD_DIN = 0x03, CMD_DOUT = 0x04, CMD_DBITIN = 0x05, CMD_DBITOUT = 0x06}; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DioHidAux_H_ */ diff --git a/src/hid/dio/DioHidBase.cpp b/src/hid/dio/DioHidBase.cpp new file mode 100644 index 0000000..8a328c6 --- /dev/null +++ b/src/hid/dio/DioHidBase.cpp @@ -0,0 +1,23 @@ +/* + * DioHidBase.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioHidBase.h" + +namespace ul +{ + +DioHidBase::DioHidBase(const HidDaqDevice& daqDevice) : DioDevice(daqDevice), mHidDevice(daqDevice) +{ + + +} + +DioHidBase::~DioHidBase() +{ + +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioHidBase.h b/src/hid/dio/DioHidBase.h new file mode 100644 index 0000000..dc048da --- /dev/null +++ b/src/hid/dio/DioHidBase.h @@ -0,0 +1,30 @@ +/* + * DioHidBase.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOHIDBASE_H_ +#define HID_DIO_DIOHIDBASE_H_ + +#include "../HidDaqDevice.h" +#include "../../DioDevice.h" + +namespace ul +{ + +class UL_LOCAL DioHidBase: public DioDevice +{ +public: + DioHidBase(const HidDaqDevice& daqDevice); + virtual ~DioHidBase(); + + const HidDaqDevice& daqDev() const {return mHidDevice;} + +private: + const HidDaqDevice& mHidDevice; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOHIDBASE_H_ */ diff --git a/src/hid/dio/DioUsbDio24.cpp b/src/hid/dio/DioUsbDio24.cpp new file mode 100644 index 0000000..45ef1ba --- /dev/null +++ b/src/hid/dio/DioUsbDio24.cpp @@ -0,0 +1,219 @@ +/* + * DioUsbDio24.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioUsbDio24.h" +#include "../UsbDio24.h" + +namespace ul +{ + +DioUsbDio24::DioUsbDio24(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + mDioInfo.addPort(0, FIRSTPORTA, 8, DPIOT_IO); + mDioInfo.addPort(1, FIRSTPORTB, 8, DPIOT_IO); + mDioInfo.addPort(2, FIRSTPORTCL, 4, DPIOT_IO); + mDioInfo.addPort(3, FIRSTPORTCH, 4, DPIOT_IO); + + mPortCLVal = 0; + mPortCHVal = 0; +} + +DioUsbDio24::~DioUsbDio24() +{ + +} + +void DioUsbDio24::initialize() +{ + try + { + initPortsDirectionMask(); + + for(unsigned int portNum = 0; portNum < mDioInfo.getNumPorts(); portNum++) + dConfigPort(mDioInfo.getPortType(portNum), DD_INPUT); + + mPortCLVal = 0; + mPortCHVal = 0; + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +void DioUsbDio24::dConfigPort(DigitalPortType portType, DigitalDirection direction) +{ + check_DConfigPort_Args(portType, direction); + + unsigned char buffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + buffer[1] = CMD_DCONFIG; + buffer[2] = getPortCode(portType); + buffer[3] = (direction == DD_INPUT)? 1 : 0; + + size_t length = sizeof(buffer); + + daqDev().sendRawCmd(buffer, &length); + + setPortDirection(portType, direction); + + if(direction == DD_INPUT) + { + if(portType == FIRSTPORTCL) + mPortCLVal = 0; + else if(portType == FIRSTPORTCH) + mPortCHVal = 0; + } +} + +unsigned long long DioUsbDio24::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + unsigned char outBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + unsigned char inBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + outBuffer[1] = CMD_DIN; + outBuffer[2] = getPortCode(portType); + + size_t outBufLen = sizeof(outBuffer); + size_t inBufLen = sizeof(inBuffer); + + daqDev().queryRawCmd(outBuffer, outBufLen, inBuffer, &inBufLen); + + portValue = inBuffer[0]; + + if(portType == FIRSTPORTCL) + portValue &= 0x0f; + else if(portType == FIRSTPORTCH) + portValue >>= 4; + + return portValue; +} + +void DioUsbDio24::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + unsigned char val = data & 0xff; + + if(portType == FIRSTPORTCL) + { + mPortCLVal = val & 0x0f; + val = val | (mPortCHVal << 4); + } + else if(portType == FIRSTPORTCH) + { + mPortCHVal = val & 0x0f; + val = (val << 4) | mPortCLVal; + } + + unsigned char buffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + buffer[1] = CMD_DOUT; + buffer[2] = getPortCode(portType); + buffer[3] = val; + + size_t length = sizeof(buffer); + + daqDev().sendRawCmd(buffer, &length); +} + +bool DioUsbDio24::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + unsigned char outBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + unsigned char inBuffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + unsigned char bit = bitNum; + if(portType == FIRSTPORTCH) + bit +=4; + + outBuffer[1] = CMD_BITIN; + outBuffer[2] = getPortCode(portType); + outBuffer[3] = bit; + + size_t outBufLen = sizeof(outBuffer); + size_t inBufLen = sizeof(inBuffer); + + daqDev().queryRawCmd(outBuffer, outBufLen, inBuffer, &inBufLen); + + bitValue = inBuffer[0] & 0x01; + + return bitValue? true : false; +} + + +void DioUsbDio24::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + unsigned char bit = bitNum; + if(portType == FIRSTPORTCH) + bit +=4; + + unsigned char bitVal = bitValue ? 1 : 0; + + unsigned char buffer[UsbDio24::MAX_PACKET_SIZE] = {0}; + + buffer[1] = CMD_BITOUT; + buffer[2] = getPortCode(portType); + buffer[3] = bit; + buffer[4] = bitVal; + + size_t length = sizeof(buffer); + + daqDev().sendRawCmd(buffer, &length); +} + +unsigned char DioUsbDio24::getPortCode(DigitalPortType portType) const +{ + unsigned char code = 0; + + switch(portType) + { + case FIRSTPORTA: + code = 1; + break; + case FIRSTPORTB: + code = 4; + break; + case FIRSTPORTCL: + code = 8; + break; + case FIRSTPORTCH: + code = 2; + break; + default: + code = 0; + break; + } + + return code; +} + +void DioUsbDio24::setCfg_PortInitialOutputVal(unsigned int portNum, unsigned long long val) +{ + if(daqDev().getDeviceType() == DaqDeviceId::USB_1024LS || daqDev().getDeviceType() == DaqDeviceId::USB_DIO24) + throw UlException(ERR_CONFIG_NOT_SUPPORTED); + + DioDevice::setCfg_PortInitialOutputVal(portNum, val); +} + +unsigned long DioUsbDio24::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + mask.set(); + + return mask.to_ulong(); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioUsbDio24.h b/src/hid/dio/DioUsbDio24.h new file mode 100644 index 0000000..c3925ad --- /dev/null +++ b/src/hid/dio/DioUsbDio24.h @@ -0,0 +1,45 @@ +/* + * DioUsbDio24.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOUSBDIO24_H_ +#define HID_DIO_DIOUSBDIO24_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioUsbDio24: public DioHidBase +{ +public: + DioUsbDio24(const HidDaqDevice& daqDevice); + virtual ~DioUsbDio24(); + + virtual void initialize(); + + virtual void dConfigPort(DigitalPortType portType, DigitalDirection direction); + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + + ////////////////////// Configuration functions ///////////////////////////////// + virtual void setCfg_PortInitialOutputVal(unsigned int portNum, unsigned long long val); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + unsigned char getPortCode(DigitalPortType portType) const; + +private: + enum {CMD_DIN = 0, CMD_DOUT = 1, CMD_BITIN = 0x02, CMD_BITOUT = 0x03, CMD_DCONFIG = 0x0D}; + unsigned char mPortCLVal; + unsigned char mPortCHVal; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOUSBDIO24_H_ */ diff --git a/src/hid/dio/DioUsbDio96h.cpp b/src/hid/dio/DioUsbDio96h.cpp new file mode 100644 index 0000000..1cb6e52 --- /dev/null +++ b/src/hid/dio/DioUsbDio96h.cpp @@ -0,0 +1,217 @@ +/* + * DioUsbDio96h.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioUsbDio96h.h" + +namespace ul +{ + +DioUsbDio96h::DioUsbDio96h(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + mNewMicro = false; + addPorts(); +} + +DioUsbDio96h::~DioUsbDio96h() +{ + +} + +void DioUsbDio96h::initialize() +{ + unsigned short rawFwVer = daqDev().getRawFwVer(); + + if(rawFwVer < 0x200) + mNewMicro = false; + else + mNewMicro = true; + + try + { + initPortsDirectionMask(); + + // set the ports to input mode if fw version is less than 2.0 + if(!mNewMicro) + { + for(unsigned int portNum = 0; portNum < mDioInfo.getNumPorts(); portNum++) + dConfigPort(mDioInfo.getPortType(portNum), DD_INPUT); + } + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +void DioUsbDio96h::dConfigPort(DigitalPortType portType, DigitalDirection direction) +{ + check_DConfigPort_Args(portType, direction); + + unsigned char portNum = mDioInfo.getPortNum(portType); + + unsigned char dir; + + if (direction == DD_OUTPUT) + dir = 0; + else + dir = 1; + + daqDev().sendCmd(CMD_DCONFIG_PORT, portNum, dir); + + setPortDirection(portType, direction); +} + +unsigned long long DioUsbDio96h::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + unsigned char portNum = mDioInfo.getPortNum(portType); + + daqDev().queryCmd(CMD_DIN, portNum, &portValue); + + return portValue; +} + +void DioUsbDio96h::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + unsigned short portNum = mDioInfo.getPortNum(portType); + + unsigned char val = data; + + daqDev().sendCmd(CMD_DOUT, portNum, val); +} + +void DioUsbDio96h::dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DInArray_Args(lowPort, highPort, data); + + unsigned char portVals[16]; + + daqDev().queryCmd(CMD_GET_ALL, portVals, sizeof(portVals), 2000); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + data[i] = portVals[portNum]; + i++; + } +} + +void DioUsbDio96h::dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DOutArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + if(mNewMicro) + { + unsigned char portVals[16] = {0}; + unsigned short portsMask = 0; + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + portVals[portNum] = data[i]; + portsMask |= 1 << portNum; + i++; + } + + daqDev().sendCmd(CMD_DOUT_MULTIPLE, portsMask, portVals, sizeof(portVals)); + } + else + { + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + dOut(mDioInfo.getPortType(portNum), data[i]); + i++; + } + } +} + + +bool DioUsbDio96h::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + unsigned char portNum = mDioInfo.getPortNum(portType); + + daqDev().queryCmd(CMD_DBITIN, portNum, bitNum, &bitValue); + + return bitValue? true : false; +} + +void DioUsbDio96h::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + unsigned char portNum = mDioInfo.getPortNum(portType); + + unsigned char bitVal = bitValue ? 1 : 0; + + daqDev().sendCmd(CMD_DBITOUT, portNum, (unsigned char) bitNum, bitVal); +} + + + + +unsigned long DioUsbDio96h::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + mask.set(); + + if(mNewMicro) + { + unsigned char cmd = CMD_DCONFIG_PORT_R; + unsigned short portsDir = 0; + + daqDev().queryCmd(cmd, &portsDir); + + std::bitset<16> portsDirMask(portsDir); + + if(portsDirMask[portNum]) + mask.set(); + else + mask.reset(); + } + + return mask.to_ulong(); +} + +void DioUsbDio96h::addPorts() +{ + mDioInfo.addPort(0, FIRSTPORTA, 8, DPIOT_IO); + mDioInfo.addPort(1, FIRSTPORTB, 8, DPIOT_IO); + mDioInfo.addPort(2, FIRSTPORTCL, 4, DPIOT_IO); + mDioInfo.addPort(3, FIRSTPORTCH, 4, DPIOT_IO); + + mDioInfo.addPort(4, SECONDPORTA, 8, DPIOT_IO); + mDioInfo.addPort(5, SECONDPORTB, 8, DPIOT_IO); + mDioInfo.addPort(6, SECONDPORTCL, 4, DPIOT_IO); + mDioInfo.addPort(7, SECONDPORTCH, 4, DPIOT_IO); + + mDioInfo.addPort(8, THIRDPORTA, 8, DPIOT_IO); + mDioInfo.addPort(9, THIRDPORTB, 8, DPIOT_IO); + mDioInfo.addPort(10, THIRDPORTCL, 4, DPIOT_IO); + mDioInfo.addPort(11, THIRDPORTCH, 4, DPIOT_IO); + + mDioInfo.addPort(12, FOURTHPORTA, 8, DPIOT_IO); + mDioInfo.addPort(13, FOURTHPORTB, 8, DPIOT_IO); + mDioInfo.addPort(14, FOURTHPORTCL, 4, DPIOT_IO); + mDioInfo.addPort(15, FOURTHPORTCH, 4, DPIOT_IO); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioUsbDio96h.h b/src/hid/dio/DioUsbDio96h.h new file mode 100644 index 0000000..3589f42 --- /dev/null +++ b/src/hid/dio/DioUsbDio96h.h @@ -0,0 +1,48 @@ +/* + * DioUsbDio96h.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOUSBDIO96H_H_ +#define HID_DIO_DIOUSBDIO96H_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioUsbDio96h: public DioHidBase +{ +public: + DioUsbDio96h(const HidDaqDevice& daqDevice); + virtual ~DioUsbDio96h(); + + virtual void initialize(); + + virtual void dConfigPort(DigitalPortType portType, DigitalDirection direction); + + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + virtual void dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + virtual void dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + +private: + void addPorts(); + +private: + enum {CMD_DCONFIG_PORT = 0x01, CMD_DIN = 0x03, CMD_DOUT = 0x04, CMD_DBITIN = 0x05, CMD_DBITOUT = 0x06, + CMD_DCONFIG_PORT_R = 0x09, CMD_DOUT_MULTIPLE = 0x0C, CMD_GET_ALL = 0x46}; + + bool mNewMicro; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOUSBDIO96H_H_ */ diff --git a/src/hid/dio/DioUsbErbxx.cpp b/src/hid/dio/DioUsbErbxx.cpp new file mode 100644 index 0000000..303dac4 --- /dev/null +++ b/src/hid/dio/DioUsbErbxx.cpp @@ -0,0 +1,129 @@ +/* + * DioUsbErbxx.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioUsbErbxx.h" + +namespace ul +{ + +DioUsbErbxx::DioUsbErbxx(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + if(daqDevice.getDeviceType() == DaqDeviceId::USB_ERB08) + { + mDioInfo.addPort(0, FIRSTPORTCL, 4, DPIOT_NONCONFIG); + mDioInfo.addPort(1, FIRSTPORTCH, 4, DPIOT_NONCONFIG); + + mPortOffset = 2; + } + else + { + mDioInfo.addPort(0, FIRSTPORTA, 8, DPIOT_NONCONFIG); + mDioInfo.addPort(1, FIRSTPORTB, 8, DPIOT_NONCONFIG); + mDioInfo.addPort(2, FIRSTPORTCL, 4, DPIOT_NONCONFIG); + mDioInfo.addPort(3, FIRSTPORTCH, 4, DPIOT_NONCONFIG); + + mPortOffset = 0; + } + +} + +DioUsbErbxx::~DioUsbErbxx() +{ + +} + +void DioUsbErbxx::initialize() +{ + try + { + initPortsDirectionMask(); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +unsigned long long DioUsbErbxx::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + daqDev().queryCmd(CMD_DIN, portNum, &portValue); + + return portValue; +} + +void DioUsbErbxx::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + unsigned short portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + unsigned char val = data; + + daqDev().sendCmd(CMD_DOUT, portNum, val); +} + +bool DioUsbErbxx::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + daqDev().queryCmd(CMD_DBITIN, portNum, bitNum, &bitValue); + + return bitValue? true : false; +} + +void DioUsbErbxx::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + unsigned char bitVal = bitValue ? 1 : 0; + + daqDev().sendCmd(CMD_DBITOUT, portNum, (unsigned char) bitNum, bitVal); +} + +unsigned long long DioUsbErbxx::getCfg_PortLogic(unsigned int portNum) +{ + if(portNum >= mDioInfo.getNumPorts()) + throw UlException(ERR_BAD_PORT_INDEX); + + unsigned long long logic = 0; // non-invert + + unsigned char cmd = CMD_STATUS; + unsigned short status = 0; + + daqDev().queryCmd(cmd, &status); + + std::bitset<16> statusMask(status); + + int bitIdx = portNum + mPortOffset; + + if(statusMask[bitIdx] == 0) + logic = 1; + + return logic; +} + +unsigned long DioUsbErbxx::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + + mask.reset(); + + return mask.to_ulong(); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioUsbErbxx.h b/src/hid/dio/DioUsbErbxx.h new file mode 100644 index 0000000..2dc83bc --- /dev/null +++ b/src/hid/dio/DioUsbErbxx.h @@ -0,0 +1,42 @@ +/* + * DioUsbErbxx.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOUSBERBXX_H_ +#define HID_DIO_DIOUSBERBXX_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioUsbErbxx: public DioHidBase +{ +public: + DioUsbErbxx(const HidDaqDevice& daqDevice); + virtual ~DioUsbErbxx(); + + virtual void initialize(); + + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + + virtual unsigned long long getCfg_PortLogic(unsigned int portNum); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + +private: + enum {CMD_DIN = 0x03, CMD_DOUT = 0x04, CMD_DBITIN = 0x05, CMD_DBITOUT = 0x06, CMD_STATUS = 0x44}; + + unsigned char mPortOffset; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOUSBERBXX_H_ */ diff --git a/src/hid/dio/DioUsbPdiso8.cpp b/src/hid/dio/DioUsbPdiso8.cpp new file mode 100644 index 0000000..1ce6c8c --- /dev/null +++ b/src/hid/dio/DioUsbPdiso8.cpp @@ -0,0 +1,124 @@ +/* + * DioUsbPdiso8.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioUsbPdiso8.h" + +namespace ul +{ + +DioUsbPdiso8::DioUsbPdiso8(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + mDioInfo.addPort(0, AUXPORT0, 8, DPIOT_NONCONFIG); + mDioInfo.addPort(1, AUXPORT1, 8, DPIOT_IN); + +} + +DioUsbPdiso8::~DioUsbPdiso8() +{ + +} + +void DioUsbPdiso8::initialize() +{ + try + { + initPortsDirectionMask(); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +unsigned long long DioUsbPdiso8::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + unsigned char portNum = mDioInfo.getPortNum(portType); + + daqDev().queryCmd(CMD_DIN, portNum, &portValue); + + return portValue; +} + +void DioUsbPdiso8::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + unsigned short portNum = mDioInfo.getPortNum(portType); + + unsigned char val = data; + + daqDev().sendCmd(CMD_DOUT, portNum, val); +} + +bool DioUsbPdiso8::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + unsigned char portNum = mDioInfo.getPortNum(portType); + + daqDev().queryCmd(CMD_DBITIN, portNum, bitNum, &bitValue); + + return bitValue? true : false; +} + +void DioUsbPdiso8::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + unsigned char portNum = mDioInfo.getPortNum(portType); + + unsigned char bitVal = bitValue ? 1 : 0; + + daqDev().sendCmd(CMD_DBITOUT, portNum, (unsigned char) bitNum, bitVal); +} + +unsigned long long DioUsbPdiso8::getCfg_PortIsoMask(unsigned int portNum) +{ + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); + + unsigned char mask = 0; + + portNum = FILTER_PORT; + + daqDev().queryCmd(CMD_DIN, portNum, &mask); + + mask = ~mask; + + return mask; +} + +void DioUsbPdiso8::DioUsbPdiso8::setCfg_PortIsoMask(unsigned int portNum, unsigned long long mask) +{ + if(!mDaqDevice.isConnected()) + throw UlException(ERR_NO_CONNECTION_ESTABLISHED); + + portNum = FILTER_PORT; + + unsigned char val = ~mask; + + daqDev().sendCmd(CMD_DOUT, portNum, val); +} + +unsigned long DioUsbPdiso8::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + + if(portNum == 0) + mask.reset(); + else + mask.set(); + + return mask.to_ulong(); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioUsbPdiso8.h b/src/hid/dio/DioUsbPdiso8.h new file mode 100644 index 0000000..820ebfc --- /dev/null +++ b/src/hid/dio/DioUsbPdiso8.h @@ -0,0 +1,42 @@ +/* + * DioUsbPdiso8.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOUSBPDISO8_H_ +#define HID_DIO_DIOUSBPDISO8_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioUsbPdiso8: public DioHidBase +{ +public: + DioUsbPdiso8(const HidDaqDevice& daqDevice); + virtual ~DioUsbPdiso8(); + + virtual void initialize(); + + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + + virtual unsigned long long getCfg_PortIsoMask(unsigned int portNum); + virtual void setCfg_PortIsoMask(unsigned int portNum, unsigned long long mask); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + +private: + enum {RELAY_PORT = 0x00, INPUT_PORT = 0x01, FILTER_PORT = 0x02}; + enum {CMD_DIN = 0x03, CMD_DOUT = 0x04, CMD_DBITIN = 0x05, CMD_DBITOUT = 0x06}; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOUSBPDISO8_H_ */ diff --git a/src/hid/dio/DioUsbSsrxx.cpp b/src/hid/dio/DioUsbSsrxx.cpp new file mode 100644 index 0000000..1b3bb0c --- /dev/null +++ b/src/hid/dio/DioUsbSsrxx.cpp @@ -0,0 +1,222 @@ +/* + * DioUsbSsrxx.cpp + * + * Author: Measurement Computing Corporation + */ + +#include "DioUsbSsrxx.h" + +namespace ul +{ + +DioUsbSsrxx::DioUsbSsrxx(const HidDaqDevice& daqDevice) : DioHidBase(daqDevice) +{ + if(daqDevice.getDeviceType() == DaqDeviceId::USB_SSR08) + { + mDioInfo.addPort(0, FIRSTPORTCL, 4, DPIOT_NONCONFIG); + mDioInfo.addPort(1, FIRSTPORTCH, 4, DPIOT_NONCONFIG); + + mPortOffset = 2; + } + else + { + mDioInfo.addPort(0, FIRSTPORTA, 8, DPIOT_NONCONFIG); + mDioInfo.addPort(1, FIRSTPORTB, 8, DPIOT_NONCONFIG); + mDioInfo.addPort(2, FIRSTPORTCL, 4, DPIOT_NONCONFIG); + mDioInfo.addPort(3, FIRSTPORTCH, 4, DPIOT_NONCONFIG); + + mPortOffset = 0; + } + + mNewMicro = false; + +} + +DioUsbSsrxx::~DioUsbSsrxx() +{ + +} + +void DioUsbSsrxx::initialize() +{ + unsigned short rawFwVer = daqDev().getRawFwVer(); + + if(rawFwVer < 0x200) + mNewMicro = false; + else + mNewMicro = true; + + try + { + initPortsDirectionMask(); + } + catch(UlException& e) + { + UL_LOG("Ul exception occurred: " << e.what()); + } +} + +unsigned long long DioUsbSsrxx::dIn(DigitalPortType portType) +{ + unsigned char portValue = 0; + + check_DIn_Args(portType); + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + daqDev().queryCmd(CMD_DIN, portNum, &portValue); + + return portValue; +} + +void DioUsbSsrxx::dOut(DigitalPortType portType, unsigned long long data) +{ + check_DOut_Args(portType, data); + + std::bitset<32> portDirectionMask = getPortDirection(portType); + + if(portDirectionMask.any()) + throw UlException(ERR_WRONG_DIG_CONFIG); + + unsigned short portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + unsigned char val = data; + + daqDev().sendCmd(CMD_DOUT, portNum, val); +} + +void DioUsbSsrxx::dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DInArray_Args(lowPort, highPort, data); + + unsigned char portVals[4]; + + daqDev().queryCmd(CMD_GET_ALL, portVals, sizeof(portVals), 2000); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + data[i] = portVals[portNum + mPortOffset]; + i++; + } +} + +void DioUsbSsrxx::dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DOutArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + std::bitset<32> portDirectionMask; + + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + portDirectionMask = getPortDirection(mDioInfo.getPortType(portNum)); + + if(portDirectionMask.any()) + throw UlException(ERR_WRONG_DIG_CONFIG); + } + + if(mNewMicro) + { + unsigned char portVals[4] = {0}; + unsigned short portsMask = 0; + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + portVals[portNum + mPortOffset] = data[i]; + portsMask |= 1 << (portNum + mPortOffset); + i++; + } + + daqDev().sendCmd(CMD_DOUT_MULTIPLE, portsMask, portVals, sizeof(portVals)); + } + else + { + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + dOut(mDioInfo.getPortType(portNum), data[i]); + i++; + } + } +} + +bool DioUsbSsrxx::dBitIn(DigitalPortType portType, int bitNum) +{ + check_DBitIn_Args(portType, bitNum); + + unsigned char bitValue = 0; + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + daqDev().queryCmd(CMD_DBITIN, portNum, bitNum, &bitValue); + + return bitValue? true : false; +} + +void DioUsbSsrxx::dBitOut(DigitalPortType portType, int bitNum, bool bitValue) +{ + check_DBitOut_Args(portType, bitNum); + + std::bitset<32> portDirectionMask = getPortDirection(portType); + + if(portDirectionMask[bitNum]) + throw UlException(ERR_WRONG_DIG_CONFIG); + + unsigned char portNum = mDioInfo.getPortNum(portType) + mPortOffset; + + unsigned char bitVal = bitValue ? 1 : 0; + + daqDev().sendCmd(CMD_DBITOUT, portNum, (unsigned char) bitNum, bitVal); +} + +unsigned long long DioUsbSsrxx::getCfg_PortLogic(unsigned int portNum) +{ + if(portNum >= mDioInfo.getNumPorts()) + throw UlException(ERR_BAD_PORT_INDEX); + + unsigned long long logic = 0; // non-invert + + unsigned char cmd = CMD_STATUS; + unsigned short status = 0; + + daqDev().queryCmd(cmd, &status); + + std::bitset<16> statusMask(status); + + int bitIdx = portNum + mPortOffset + 4; + + if(statusMask[bitIdx] == 0) + logic = 1; + + return logic; +} + +unsigned long DioUsbSsrxx::readPortDirMask(unsigned int portNum) const +{ + std::bitset<8> mask; + + unsigned char cmd = CMD_STATUS; + unsigned short status = 0; + + daqDev().queryCmd(cmd, &status); + + std::bitset<16> statusMask(status); + + int bitIdx = portNum + mPortOffset; + + if(statusMask[bitIdx]) + mask.set(); + else + mask.reset(); + + return mask.to_ulong(); +} + +} /* namespace ul */ diff --git a/src/hid/dio/DioUsbSsrxx.h b/src/hid/dio/DioUsbSsrxx.h new file mode 100644 index 0000000..ff47646 --- /dev/null +++ b/src/hid/dio/DioUsbSsrxx.h @@ -0,0 +1,46 @@ +/* + * DioUsbSsrxx.h + * + * Author: Measurement Computing Corporation + */ + +#ifndef HID_DIO_DIOUSBSSRXX_H_ +#define HID_DIO_DIOUSBSSRXX_H_ + +#include "DioHidBase.h" + +namespace ul +{ + +class UL_LOCAL DioUsbSsrxx: public DioHidBase +{ +public: + DioUsbSsrxx(const HidDaqDevice& daqDevice); + virtual ~DioUsbSsrxx(); + + virtual void initialize(); + + virtual unsigned long long dIn(DigitalPortType portType); + virtual void dOut(DigitalPortType portType, unsigned long long data); + virtual void dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + virtual void dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + + virtual bool dBitIn(DigitalPortType portType, int bitNum); + virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); + + virtual unsigned long long getCfg_PortLogic(unsigned int portNum); + +protected: + virtual unsigned long readPortDirMask(unsigned int portNum) const; + +private: + enum {CMD_DIN = 0x03, CMD_DOUT = 0x04, CMD_DBITIN = 0x05, CMD_DBITOUT = 0x06, CMD_DOUT_MULTIPLE = 0x0C, + CMD_STATUS = 0x44, CMD_GET_ALL = 0x46}; + + unsigned char mPortOffset; + bool mNewMicro; +}; + +} /* namespace ul */ + +#endif /* HID_DIO_DIOUSBSSRXX_H_ */ diff --git a/src/hid/hid_linux.cpp b/src/hid/hid_linux.cpp new file mode 100644 index 0000000..a53d03f --- /dev/null +++ b/src/hid/hid_linux.cpp @@ -0,0 +1,1687 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + Linux Version - 6/2/2010 + Libusb Version - 8/13/2010 + FreeBSD Version - 11/1/2011 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +#ifndef __APPLE__ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */ +#endif + +/* C */ +#include +#include +#include +#include +#include +#include + +/* Unix */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* GNU / LibUSB */ +//#include +#include +#ifndef __ANDROID__ +#include +#endif + +#include "hidapi.h" + +//#define NO_THREAD 1 + +#ifdef __ANDROID__ + +/* Barrier implementation because Android/Bionic don't have pthread_barrier. + This implementation came from Brent Priddy and was posted on + StackOverflow. It is used with his permission. */ +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int trip_count; +} pthread_barrier_t; + +static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + if(count == 0) { + errno = EINVAL; + return -1; + } + + if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->trip_count) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else + { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG_PRINTF +#define LOG(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG(...) do {} while (0) +#endif + +#ifndef __FreeBSD__ +#define DETACH_KERNEL_DRIVER +#endif + +/* Uncomment to enable the retrieval of Usage and Usage Page in +hid_enumerate(). Warning, on platforms different from FreeBSD +this is very invasive as it requires the detach +and re-attach of the kernel driver. See comments inside hid_enumerate(). +libusb HIDAPI programs are encouraged to use the interface number +instead to differentiate between interfaces on a composite HID device. */ +/*#define INVASIVE_GET_USAGE*/ + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + + +struct hid_device_ { + /* Handle to the actual device. */ + libusb_device_handle *device_handle; + + /* Endpoint information */ + int input_endpoint; + int output_endpoint; + int input_ep_max_packet_size; + + /* The interface number of the HID */ + int interface; + + /* Indexes of Strings */ + int manufacturer_index; + int product_index; + int serial_index; + + /* Whether blocking reads are used */ + int blocking; /* boolean */ + + /* Read thread objects */ + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ + int shutdown_thread; + int cancelled; + struct libusb_transfer *transfer; + + /* List of received input reports. */ + struct input_report *input_reports; +}; + +static libusb_context *usb_context = NULL; + +uint16_t UL_LOCAL get_usb_code_for_current_locale(void); +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +static hid_device *new_hid_device(void) +{ + hid_device *dev = (hid_device *) calloc(1, sizeof(hid_device)); + dev->blocking = 1; + + pthread_mutex_init(&dev->mutex, NULL); + pthread_cond_init(&dev->condition, NULL); + pthread_barrier_init(&dev->barrier, NULL, 2); + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + /* Clean up the thread objects */ + pthread_barrier_destroy(&dev->barrier); + pthread_cond_destroy(&dev->condition); + pthread_mutex_destroy(&dev->mutex); + + /* Free the device itself */ + free(dev); +} + +#if 0 +/*TODO: Implement this funciton on hidapi/libusb.. */ +static void register_error(hid_device *device, const char *op) +{ + +} +#endif + +#ifdef INVASIVE_GET_USAGE +/* Get bytes from a HID Report Descriptor. + Only call with a num_bytes of 0, 1, 2, or 4. */ +static uint32_t get_bytes(uint8_t *rpt, size_t len, size_t num_bytes, size_t cur) +{ + /* Return if there aren't enough bytes. */ + if (cur + num_bytes >= len) + return 0; + + if (num_bytes == 0) + return 0; + else if (num_bytes == 1) { + return rpt[cur+1]; + } + else if (num_bytes == 2) { + return (rpt[cur+2] * 256 + rpt[cur+1]); + } + else if (num_bytes == 4) { + return (rpt[cur+4] * 0x01000000 + + rpt[cur+3] * 0x00010000 + + rpt[cur+2] * 0x00000100 + + rpt[cur+1] * 0x00000001); + } + else + return 0; +} + +/* Retrieves the device's Usage Page and Usage from the report + descriptor. The algorithm is simple, as it just returns the first + Usage and Usage Page that it finds in the descriptor. + The return value is 0 on success and -1 on failure. */ +static int get_usage(uint8_t *report_descriptor, size_t size, + unsigned short *usage_page, unsigned short *usage) +{ + unsigned int i = 0; + int size_code; + int data_len, key_size; + int usage_found = 0, usage_page_found = 0; + + while (i < size) { + int key = report_descriptor[i]; + int key_cmd = key & 0xfc; + + //printf("key: %02hhx\n", key); + + if ((key & 0xf0) == 0xf0) { + /* This is a Long Item. The next byte contains the + length of the data section (value) for this key. + See the HID specification, version 1.11, section + 6.2.2.3, titled "Long Items." */ + if (i+1 < size) + data_len = report_descriptor[i+1]; + else + data_len = 0; /* malformed report */ + key_size = 3; + } + else { + /* This is a Short Item. The bottom two bits of the + key contain the size code for the data section + (value) for this key. Refer to the HID + specification, version 1.11, section 6.2.2.2, + titled "Short Items." */ + size_code = key & 0x3; + switch (size_code) { + case 0: + case 1: + case 2: + data_len = size_code; + break; + case 3: + data_len = 4; + break; + default: + /* Can't ever happen since size_code is & 0x3 */ + data_len = 0; + break; + }; + key_size = 1; + } + + if (key_cmd == 0x4) { + *usage_page = get_bytes(report_descriptor, size, data_len, i); + usage_page_found = 1; + //printf("Usage Page: %x\n", (uint32_t)*usage_page); + } + if (key_cmd == 0x8) { + *usage = get_bytes(report_descriptor, size, data_len, i); + usage_found = 1; + //printf("Usage: %x\n", (uint32_t)*usage); + } + + if (usage_page_found && usage_found) + return 0; /* success */ + + /* Skip over this key and it's associated data */ + i += data_len + key_size; + } + + return -1; /* failure */ +} +#endif /* INVASIVE_GET_USAGE */ + +#if defined(__FreeBSD__) && __FreeBSD__ < 10 +/* The libusb version included in FreeBSD < 10 doesn't have this function. In + mainline libusb, it's inlined in libusb.h. This function will bear a striking + resemblance to that one, because there's about one way to code it. + + Note that the data parameter is Unicode in UTF-16LE encoding. + Return value is the number of bytes in data, or LIBUSB_ERROR_*. + */ +static inline int libusb_get_string_descriptor(libusb_device_handle *dev, + uint8_t descriptor_index, uint16_t lang_id, + unsigned char *data, int length) +{ + return libusb_control_transfer(dev, + LIBUSB_ENDPOINT_IN | 0x0, /* Endpoint 0 IN */ + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_STRING << 8) | descriptor_index, + lang_id, data, (uint16_t) length, 1000); +} + +#endif + + +/* Get the first language the device says it reports. This comes from + USB string #0. */ +static uint16_t get_first_language(libusb_device_handle *dev) +{ + uint16_t buf[32]; + int len; + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + 0x0, /* String ID */ + 0x0, /* Language */ + (unsigned char*)buf, + sizeof(buf)); + if (len < 4) + return 0x0; + + return buf[1]; /* First two bytes are len and descriptor type. */ +} + +static int is_language_supported(libusb_device_handle *dev, uint16_t lang) +{ + uint16_t buf[32]; + int len; + int i; + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + 0x0, /* String ID */ + 0x0, /* Language */ + (unsigned char*)buf, + sizeof(buf)); + if (len < 4) + return 0x0; + + + len /= 2; /* language IDs are two-bytes each. */ + /* Start at index 1 because there are two bytes of protocol data. */ + for (i = 1; i < len; i++) { + if (buf[i] == lang) + return 1; + } + + return 0; +} + + +/* This function returns a newly allocated wide string containing the USB + device string numbered by the index. The returned string must be freed + by using free(). */ +static wchar_t *get_usb_string(libusb_device_handle *dev, uint8_t idx) +{ + char buf[512]; + int len; + wchar_t *str = NULL; + +#ifndef __ANDROID__ /* we don't use iconv on Android */ + wchar_t wbuf[256]; + /* iconv variables */ + iconv_t ic; + size_t inbytes; + size_t outbytes; + size_t res; +#ifdef __FreeBSD__ + const char *inptr; +#else + char *inptr; +#endif + char *outptr; +#endif + + /* Determine which language to use. */ + uint16_t lang; + lang = get_usb_code_for_current_locale(); + if (!is_language_supported(dev, lang)) + lang = get_first_language(dev); + + /* Get the string from libusb. */ + len = libusb_get_string_descriptor(dev, + idx, + lang, + (unsigned char*)buf, + sizeof(buf)); + if (len < 0) + return NULL; + +#ifdef __ANDROID__ + + /* Bionic does not have iconv support nor wcsdup() function, so it + has to be done manually. The following code will only work for + code points that can be represented as a single UTF-16 character, + and will incorrectly convert any code points which require more + than one UTF-16 character. + + Skip over the first character (2-bytes). */ + len -= 2; + str = malloc((len / 2 + 1) * sizeof(wchar_t)); + int i; + for (i = 0; i < len / 2; i++) { + str[i] = buf[i * 2 + 2] | (buf[i * 2 + 3] << 8); + } + str[len / 2] = 0x00000000; + +#else + + /* buf does not need to be explicitly NULL-terminated because + it is only passed into iconv() which does not need it. */ + + /* Initialize iconv. */ + ic = iconv_open("WCHAR_T", "UTF-16LE"); + if (ic == (iconv_t)-1) { + LOG("iconv_open() failed\n"); + return NULL; + } + + /* Convert to native wchar_t (UTF-32 on glibc/BSD systems). + Skip the first character (2-bytes). */ + inptr = buf+2; + inbytes = len-2; + outptr = (char*) wbuf; + outbytes = sizeof(wbuf); + res = iconv(ic, &inptr, &inbytes, &outptr, &outbytes); + if (res == (size_t)-1) { + LOG("iconv() failed\n"); + goto err; + } + + /* Write the terminating NULL. */ + wbuf[sizeof(wbuf)/sizeof(wbuf[0])-1] = 0x00000000; + if (outbytes >= sizeof(wbuf[0])) + *((wchar_t*)outptr) = 0x00000000; + + /* Allocate and copy the string. */ + str = wcsdup(wbuf); + +err: + iconv_close(ic); + +#endif + + return str; +} + +static char *make_path(libusb_device *dev, int interface_number) +{ + char str[64]; + snprintf(str, sizeof(str), "%04x:%04x:%02x", + libusb_get_bus_number(dev), + libusb_get_device_address(dev), + interface_number); + str[sizeof(str)-1] = '\0'; + + return strdup(str); +} + + +int HID_API_EXPORT hid_init(void) +{ + if (!usb_context) { + const char *locale; + + if (libusb_init(&usb_context)) + return -1; + + locale = setlocale(LC_CTYPE, NULL); + if (!locale) + setlocale(LC_CTYPE, ""); + } + + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + if (usb_context) { + libusb_exit(usb_context); + usb_context = NULL; + } + + return 0; +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + libusb_device **devs; + libusb_device *dev; + libusb_device_handle *handle; + ssize_t num_devs; + int i = 0; + + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + + if(hid_init() < 0) + return NULL; + + num_devs = libusb_get_device_list(usb_context, &devs); + if (num_devs < 0) + return NULL; + while ((dev = devs[i++]) != NULL) { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int j, k; + int interface_num = 0; + + int res = libusb_get_device_descriptor(dev, &desc); + unsigned short dev_vid = desc.idVendor; + unsigned short dev_pid = desc.idProduct; + + res = libusb_get_active_config_descriptor(dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(dev, 0, &conf_desc); + if (conf_desc) { + for (j = 0; j < conf_desc->bNumInterfaces; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + interface_num = intf_desc->bInterfaceNumber; + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 || vendor_id == dev_vid) && + (product_id == 0x0 || product_id == dev_pid)) { + struct hid_device_info *tmp; + + /* VID/PID match. Create the record. */ + tmp = (hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Fill out the record */ + cur_dev->next = NULL; + cur_dev->path = make_path(dev, interface_num); + + res = libusb_open(dev, &handle); + + if (res >= 0) { + /* Serial Number */ + if (desc.iSerialNumber > 0) + cur_dev->serial_number = + get_usb_string(handle, desc.iSerialNumber); + + /* Manufacturer and Product strings */ + if (desc.iManufacturer > 0) + cur_dev->manufacturer_string = + get_usb_string(handle, desc.iManufacturer); + if (desc.iProduct > 0) + cur_dev->product_string = + get_usb_string(handle, desc.iProduct); + +#ifdef INVASIVE_GET_USAGE +{ + /* + This section is removed because it is too + invasive on the system. Getting a Usage Page + and Usage requires parsing the HID Report + descriptor. Getting a HID Report descriptor + involves claiming the interface. Claiming the + interface involves detaching the kernel driver. + Detaching the kernel driver is hard on the system + because it will unclaim interfaces (if another + app has them claimed) and the re-attachment of + the driver will sometimes change /dev entry names. + It is for these reasons that this section is + #if 0. For composite devices, use the interface + field in the hid_device_info struct to distinguish + between interfaces. */ + unsigned char data[256]; +#ifdef DETACH_KERNEL_DRIVER + int detached = 0; + /* Usage Page and Usage */ + res = libusb_kernel_driver_active(handle, interface_num); + if (res == 1) { + res = libusb_detach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't detach kernel driver, even though a kernel driver was attached."); + else + detached = 1; + } +#endif + res = libusb_claim_interface(handle, interface_num); + if (res >= 0) { + /* Get the HID Report Descriptor. */ + res = libusb_control_transfer(handle, LIBUSB_ENDPOINT_IN|LIBUSB_RECIPIENT_INTERFACE, LIBUSB_REQUEST_GET_DESCRIPTOR, (LIBUSB_DT_REPORT << 8)|interface_num, 0, data, sizeof(data), 5000); + if (res >= 0) { + unsigned short page=0, usage=0; + /* Parse the usage and usage page + out of the report descriptor. */ + get_usage(data, res, &page, &usage); + cur_dev->usage_page = page; + cur_dev->usage = usage; + } + else + LOG("libusb_control_transfer() for getting the HID report failed with %d\n", res); + + /* Release the interface */ + res = libusb_release_interface(handle, interface_num); + if (res < 0) + LOG("Can't release the interface.\n"); + } + else + LOG("Can't claim interface %d\n", res); +#ifdef DETACH_KERNEL_DRIVER + /* Re-attach kernel driver if necessary. */ + if (detached) { + res = libusb_attach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't re-attach kernel driver.\n"); + } +#endif +} +#endif /* INVASIVE_GET_USAGE */ + + libusb_close(handle); + } + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Release Number */ + cur_dev->release_number = desc.bcdDevice; + + /* Interface Number */ + cur_dev->interface_number = interface_num; + } + } + } /* altsettings */ + } /* interfaces */ + libusb_free_config_descriptor(conf_desc); + } + } + + libusb_free_device_list(devs, 1); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number, struct hid_device_info* dev_info, UlError* err) +{ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (cur_dev->serial_number && + wcscmp(serial_number, cur_dev->serial_number) == 0) { + + if(dev_info) + { + memcpy(dev_info, cur_dev, sizeof(hid_device_info)); + dev_info->next = NULL; + } + + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open, err); + } + else + *err = ERR_DEV_NOT_FOUND; + + hid_free_enumeration(devs); + + return handle; +} + +#ifndef NO_THREAD +static void read_callback(struct libusb_transfer *transfer) +{ + hid_device *dev = (hid_device *) transfer->user_data; + int res; + + if (transfer->status == LIBUSB_TRANSFER_COMPLETED) { + + struct input_report *rpt = (input_report*) malloc(sizeof(*rpt)); + + if(rpt == 0) { + + std::cout << "### read_callback(), Unable to allocate rpt buffer" << std::endl; + dev->shutdown_thread = 1; + dev->cancelled = 1; + return; + } + + rpt->data = (uint8_t*) malloc(transfer->actual_length); + + if(rpt->data == 0) { + + std::cout << "### read_callback(), Unable to allocate rpt->data buffer" << std::endl; + + free(rpt); + rpt = 0; + + dev->shutdown_thread = 1; + dev->cancelled = 1; + return; + } + + memcpy(rpt->data, transfer->buffer, transfer->actual_length); + rpt->len = transfer->actual_length; + rpt->next = NULL; + + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + pthread_cond_signal(&dev->condition); + } + else { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) { + return_data(dev, NULL, 0); + } + } + pthread_mutex_unlock(&dev->mutex); + } + else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { + dev->shutdown_thread = 1; + dev->cancelled = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_NO_DEVICE) { + dev->shutdown_thread = 1; + dev->cancelled = 1; + return; + } + else if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT) { + //LOG("Timeout (normal)\n"); + } + else { + LOG("Unknown transfer code: %d\n", transfer->status); + } + + /* Re-submit the transfer object. */ + res = libusb_submit_transfer(transfer); + if (res != 0) { + LOG("Unable to submit URB. libusb error code: %d\n", res); + dev->shutdown_thread = 1; + dev->cancelled = 1; + } +} + +static void *read_thread(void *param) +{ + hid_device *dev = (hid_device*) param; + unsigned char *buf; + const size_t length = dev->input_ep_max_packet_size; + + /* Set up the transfer object. */ + buf = (unsigned char*) malloc(length); + if(buf == 0) { + std::cout << "### read_thread(), Unable to allocate transfer buffer" << std::endl; + } + else { + dev->transfer = libusb_alloc_transfer(0); + libusb_fill_interrupt_transfer(dev->transfer, + dev->device_handle, + dev->input_endpoint, + buf, + length, + read_callback, + dev, + 5000/*timeout*/); + + /* Make the first submission. Further submissions are made + from inside read_callback() */ + libusb_submit_transfer(dev->transfer); + + /* Notify the main thread that the read thread is up and running. */ + pthread_barrier_wait(&dev->barrier); + + /* Handle all the events. */ + while (!dev->shutdown_thread) { + int res; + res = libusb_handle_events(usb_context); + if (res < 0) { + /* There was an error. */ + LOG("read_thread(): libusb reports error # %d\n", res); + + /* Break out of this loop only on fatal error.*/ + if (res != LIBUSB_ERROR_BUSY && + res != LIBUSB_ERROR_TIMEOUT && + res != LIBUSB_ERROR_OVERFLOW && + res != LIBUSB_ERROR_INTERRUPTED) { + break; + } + } + } + + /* Cancel any transfer that may be pending. This call will fail + if no transfers are pending, but that's OK. */ + libusb_cancel_transfer(dev->transfer); + } + + while (!dev->cancelled) + libusb_handle_events_completed(usb_context, &dev->cancelled); + + /* Now that the read thread is stopping, Wake any threads which are + waiting on data (in hid_read_timeout()). Do this under a mutex to + make sure that a thread which is about to go to sleep waiting on + the condition actually will go to sleep before the condition is + signaled. */ + pthread_mutex_lock(&dev->mutex); + pthread_cond_broadcast(&dev->condition); + pthread_mutex_unlock(&dev->mutex); + + /* The dev->transfer->buffer and dev->transfer objects are cleaned up + in hid_close(). They are not cleaned up here because this thread + could end either due to a disconnect or due to a user + call to hid_close(). In both cases the objects can be safely + cleaned up after the call to pthread_join() (in hid_close()), but + since hid_close() calls libusb_cancel_transfer(), on these objects, + they can not be cleaned up here. */ + + return NULL; +} +#endif + +hid_device * HID_API_EXPORT hid_open_path(const char *path, UlError* err) +{ + hid_device *dev = NULL; + + libusb_device **devs; + libusb_device *usb_dev; + int res; + int d = 0; + int good_open = 0; + ssize_t __attribute__((unused)) num_devs; + + if(hid_init() < 0) + return NULL; + + dev = new_hid_device(); + + num_devs = libusb_get_device_list(usb_context, &devs); + while ((usb_dev = devs[d++]) != NULL) { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + int i,j,k; + libusb_get_device_descriptor(usb_dev, &desc); + + if (libusb_get_active_config_descriptor(usb_dev, &conf_desc) < 0) + continue; + for (j = 0; j < conf_desc->bNumInterfaces; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[k]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) { + char *dev_path = make_path(usb_dev, intf_desc->bInterfaceNumber); + if (!strcmp(dev_path, path)) { + /* Matched Paths. Open this device */ + + /* OPEN HERE */ + res = libusb_open(usb_dev, &dev->device_handle); + if (res < 0) { + LOG("can't open device\n"); + free(dev_path); + break; + } + good_open = 1; +#ifdef DETACH_KERNEL_DRIVER + /* Detach the kernel driver, but only if the + device is managed by the kernel */ + if (libusb_kernel_driver_active(dev->device_handle, intf_desc->bInterfaceNumber) == 1) { + res = libusb_detach_kernel_driver(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + libusb_close(dev->device_handle); + LOG("Unable to detach Kernel Driver\n"); + free(dev_path); + good_open = 0; + break; + } + } +#endif + res = libusb_claim_interface(dev->device_handle, intf_desc->bInterfaceNumber); + if (res < 0) { + LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res); + free(dev_path); + libusb_close(dev->device_handle); + good_open = 0; + *err = ERR_USB_INTERFACE_CLAIMED; + break; + } + + /* Store off the string descriptor indexes */ + dev->manufacturer_index = desc.iManufacturer; + dev->product_index = desc.iProduct; + dev->serial_index = desc.iSerialNumber; + + /* Store off the interface number */ + dev->interface = intf_desc->bInterfaceNumber; + + /* Find the INPUT and OUTPUT endpoints. An + OUTPUT endpoint is not required. */ + for (i = 0; i < intf_desc->bNumEndpoints; i++) { + const struct libusb_endpoint_descriptor *ep + = &intf_desc->endpoint[i]; + + /* Determine the type and direction of this + endpoint. */ + int is_interrupt = + (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT; + int is_output = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_OUT; + int is_input = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN; + + /* Decide whether to use it for input or output. */ + if (dev->input_endpoint == 0 && + is_interrupt && is_input) { + /* Use this endpoint for INPUT */ + dev->input_endpoint = ep->bEndpointAddress; + dev->input_ep_max_packet_size = ep->wMaxPacketSize; + } + if (dev->output_endpoint == 0 && + is_interrupt && is_output) { + /* Use this endpoint for OUTPUT */ + dev->output_endpoint = ep->bEndpointAddress; + } + } + +#ifndef NO_THREAD + int __attribute__((unused)) status = pthread_create(&dev->thread, NULL, read_thread, dev); + + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); +#endif + + } + free(dev_path); + } + } + } + libusb_free_config_descriptor(conf_desc); + + } + + libusb_free_device_list(devs, 1); + + /* If we have a good handle, return it. */ + if (good_open) { + return dev; + } + else { + /* Unable to open any devices. */ + free_hid_device(dev); + return NULL; + } +} + + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + int res; + int report_number = data[0]; + int skipped_report_id = 0; + + if (report_number == 0x0) { + data++; + length--; + skipped_report_id = 1; + } + + + if (dev->output_endpoint <= 0) { + /* No interrupt out endpoint. Use the Control Endpoint */ + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID Set_Report*/, + (2/*HID output*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + length++; + + return length; + } + else { + /* Use the interrupt out endpoint */ + int actual_length; + res = libusb_interrupt_transfer(dev->device_handle, + dev->output_endpoint, + (unsigned char*)data, + length, + &actual_length, 5000); + + if (res < 0) + return -1; + + if (skipped_report_id) + actual_length++; + + return actual_length; + } +} + +/* Helper function, to simplify hid_read(). + This should be called with dev->mutex locked. */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + if (len > 0) + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + +#ifndef NO_THREAD +static void cleanup_mutex(void *param) +{ + hid_device *dev = (hid_device*)param; + pthread_mutex_unlock(&dev->mutex); +} +#endif + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + int bytes_read = -1; + +#ifdef NO_THREAD + //int transferred; + int __attribute__((unused)) res = libusb_interrupt_transfer(dev->device_handle, dev->input_endpoint, data, length, &bytes_read, milliseconds); + LOG("transferred: %d\n", transferred); + return bytes_read; +#else + + pthread_mutex_lock(&dev->mutex); + pthread_cleanup_push(&cleanup_mutex, dev); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + if (dev->shutdown_thread) { + /* This means the device has been disconnected. + An error code of -1 should be returned. */ + bytes_read = -1; + goto ret; + } + + if (milliseconds == -1) { + /* Blocking */ + while (!dev->input_reports && !dev->shutdown_thread) { + pthread_cond_wait(&dev->condition, &dev->mutex); + } + if (dev->input_reports) { + bytes_read = return_data(dev, data, length); + } + } + else if (milliseconds > 0) { + /* Non-blocking, but called with timeout. */ + int res; + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + + while (!dev->input_reports && !dev->shutdown_thread) { + res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts); + if (res == 0) { + if (dev->input_reports) { + bytes_read = return_data(dev, data, length); + break; + } + + /* If we're here, there was a spurious wake up + or the read thread was shutdown. Run the + loop again (ie: don't break). */ + } + else if (res == ETIMEDOUT) { + /* Timed out. */ + bytes_read = 0; + break; + } + else { + /* Error. */ + bytes_read = -1; + break; + } + } + } + else { + /* Purely non-blocking */ + bytes_read = 0; + } + +ret: + pthread_mutex_unlock(&dev->mutex); + pthread_cleanup_pop(0); + + return bytes_read; +#endif +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, dev->blocking ? -1 : 0); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + + return 0; +} + + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) { + data++; + length--; + skipped_report_id = 1; + } + + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, + 0x09/*HID set_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + /* Account for the report ID */ + if (skipped_report_id) + length++; + + return length; +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + int res = -1; + int skipped_report_id = 0; + int report_number = data[0]; + + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + data++; + length--; + skipped_report_id = 1; + } + res = libusb_control_transfer(dev->device_handle, + LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_IN, + 0x01/*HID get_report*/, + (3/*HID feature*/ << 8) | report_number, + dev->interface, + (unsigned char *)data, length, + 1000/*timeout millis*/); + + if (res < 0) + return -1; + + if (skipped_report_id) + res++; + + return res; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + +#ifndef NO_THREAD + /* Cause read_thread() to stop. */ + dev->shutdown_thread = 1; + libusb_cancel_transfer(dev->transfer); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* Clean up the Transfer objects allocated in read_thread(). */ + free(dev->transfer->buffer); + libusb_free_transfer(dev->transfer); +#endif + + /* release the interface */ + libusb_release_interface(dev->device_handle, dev->interface); + + /* Close the handle */ + libusb_close(dev->device_handle); + + /* Clear out the queue of received reports. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + + free_hid_device(dev); +} + + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->manufacturer_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->product_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return hid_get_indexed_string(dev, dev->serial_index, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + wchar_t *str; + + str = get_usb_string(dev->device_handle, string_index); + if (str) { + wcsncpy(string, str, maxlen); + string[maxlen-1] = L'\0'; + free(str); + return 0; + } + else + return -1; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return NULL; +} + + +struct lang_map_entry { + const char *name; + const char *string_code; + uint16_t usb_code; +}; + +#define LANG(name,code,usb_code) { name, code, usb_code } +static struct lang_map_entry lang_map[] = { + LANG("Afrikaans", "af", 0x0436), + LANG("Albanian", "sq", 0x041C), + LANG("Arabic - United Arab Emirates", "ar_ae", 0x3801), + LANG("Arabic - Bahrain", "ar_bh", 0x3C01), + LANG("Arabic - Algeria", "ar_dz", 0x1401), + LANG("Arabic - Egypt", "ar_eg", 0x0C01), + LANG("Arabic - Iraq", "ar_iq", 0x0801), + LANG("Arabic - Jordan", "ar_jo", 0x2C01), + LANG("Arabic - Kuwait", "ar_kw", 0x3401), + LANG("Arabic - Lebanon", "ar_lb", 0x3001), + LANG("Arabic - Libya", "ar_ly", 0x1001), + LANG("Arabic - Morocco", "ar_ma", 0x1801), + LANG("Arabic - Oman", "ar_om", 0x2001), + LANG("Arabic - Qatar", "ar_qa", 0x4001), + LANG("Arabic - Saudi Arabia", "ar_sa", 0x0401), + LANG("Arabic - Syria", "ar_sy", 0x2801), + LANG("Arabic - Tunisia", "ar_tn", 0x1C01), + LANG("Arabic - Yemen", "ar_ye", 0x2401), + LANG("Armenian", "hy", 0x042B), + LANG("Azeri - Latin", "az_az", 0x042C), + LANG("Azeri - Cyrillic", "az_az", 0x082C), + LANG("Basque", "eu", 0x042D), + LANG("Belarusian", "be", 0x0423), + LANG("Bulgarian", "bg", 0x0402), + LANG("Catalan", "ca", 0x0403), + LANG("Chinese - China", "zh_cn", 0x0804), + LANG("Chinese - Hong Kong SAR", "zh_hk", 0x0C04), + LANG("Chinese - Macau SAR", "zh_mo", 0x1404), + LANG("Chinese - Singapore", "zh_sg", 0x1004), + LANG("Chinese - Taiwan", "zh_tw", 0x0404), + LANG("Croatian", "hr", 0x041A), + LANG("Czech", "cs", 0x0405), + LANG("Danish", "da", 0x0406), + LANG("Dutch - Netherlands", "nl_nl", 0x0413), + LANG("Dutch - Belgium", "nl_be", 0x0813), + LANG("English - Australia", "en_au", 0x0C09), + LANG("English - Belize", "en_bz", 0x2809), + LANG("English - Canada", "en_ca", 0x1009), + LANG("English - Caribbean", "en_cb", 0x2409), + LANG("English - Ireland", "en_ie", 0x1809), + LANG("English - Jamaica", "en_jm", 0x2009), + LANG("English - New Zealand", "en_nz", 0x1409), + LANG("English - Phillippines", "en_ph", 0x3409), + LANG("English - Southern Africa", "en_za", 0x1C09), + LANG("English - Trinidad", "en_tt", 0x2C09), + LANG("English - Great Britain", "en_gb", 0x0809), + LANG("English - United States", "en_us", 0x0409), + LANG("Estonian", "et", 0x0425), + LANG("Farsi", "fa", 0x0429), + LANG("Finnish", "fi", 0x040B), + LANG("Faroese", "fo", 0x0438), + LANG("French - France", "fr_fr", 0x040C), + LANG("French - Belgium", "fr_be", 0x080C), + LANG("French - Canada", "fr_ca", 0x0C0C), + LANG("French - Luxembourg", "fr_lu", 0x140C), + LANG("French - Switzerland", "fr_ch", 0x100C), + LANG("Gaelic - Ireland", "gd_ie", 0x083C), + LANG("Gaelic - Scotland", "gd", 0x043C), + LANG("German - Germany", "de_de", 0x0407), + LANG("German - Austria", "de_at", 0x0C07), + LANG("German - Liechtenstein", "de_li", 0x1407), + LANG("German - Luxembourg", "de_lu", 0x1007), + LANG("German - Switzerland", "de_ch", 0x0807), + LANG("Greek", "el", 0x0408), + LANG("Hebrew", "he", 0x040D), + LANG("Hindi", "hi", 0x0439), + LANG("Hungarian", "hu", 0x040E), + LANG("Icelandic", "is", 0x040F), + LANG("Indonesian", "id", 0x0421), + LANG("Italian - Italy", "it_it", 0x0410), + LANG("Italian - Switzerland", "it_ch", 0x0810), + LANG("Japanese", "ja", 0x0411), + LANG("Korean", "ko", 0x0412), + LANG("Latvian", "lv", 0x0426), + LANG("Lithuanian", "lt", 0x0427), + LANG("F.Y.R.O. Macedonia", "mk", 0x042F), + LANG("Malay - Malaysia", "ms_my", 0x043E), + LANG("Malay – Brunei", "ms_bn", 0x083E), + LANG("Maltese", "mt", 0x043A), + LANG("Marathi", "mr", 0x044E), + LANG("Norwegian - Bokml", "no_no", 0x0414), + LANG("Norwegian - Nynorsk", "no_no", 0x0814), + LANG("Polish", "pl", 0x0415), + LANG("Portuguese - Portugal", "pt_pt", 0x0816), + LANG("Portuguese - Brazil", "pt_br", 0x0416), + LANG("Raeto-Romance", "rm", 0x0417), + LANG("Romanian - Romania", "ro", 0x0418), + LANG("Romanian - Republic of Moldova", "ro_mo", 0x0818), + LANG("Russian", "ru", 0x0419), + LANG("Russian - Republic of Moldova", "ru_mo", 0x0819), + LANG("Sanskrit", "sa", 0x044F), + LANG("Serbian - Cyrillic", "sr_sp", 0x0C1A), + LANG("Serbian - Latin", "sr_sp", 0x081A), + LANG("Setsuana", "tn", 0x0432), + LANG("Slovenian", "sl", 0x0424), + LANG("Slovak", "sk", 0x041B), + LANG("Sorbian", "sb", 0x042E), + LANG("Spanish - Spain (Traditional)", "es_es", 0x040A), + LANG("Spanish - Argentina", "es_ar", 0x2C0A), + LANG("Spanish - Bolivia", "es_bo", 0x400A), + LANG("Spanish - Chile", "es_cl", 0x340A), + LANG("Spanish - Colombia", "es_co", 0x240A), + LANG("Spanish - Costa Rica", "es_cr", 0x140A), + LANG("Spanish - Dominican Republic", "es_do", 0x1C0A), + LANG("Spanish - Ecuador", "es_ec", 0x300A), + LANG("Spanish - Guatemala", "es_gt", 0x100A), + LANG("Spanish - Honduras", "es_hn", 0x480A), + LANG("Spanish - Mexico", "es_mx", 0x080A), + LANG("Spanish - Nicaragua", "es_ni", 0x4C0A), + LANG("Spanish - Panama", "es_pa", 0x180A), + LANG("Spanish - Peru", "es_pe", 0x280A), + LANG("Spanish - Puerto Rico", "es_pr", 0x500A), + LANG("Spanish - Paraguay", "es_py", 0x3C0A), + LANG("Spanish - El Salvador", "es_sv", 0x440A), + LANG("Spanish - Uruguay", "es_uy", 0x380A), + LANG("Spanish - Venezuela", "es_ve", 0x200A), + LANG("Southern Sotho", "st", 0x0430), + LANG("Swahili", "sw", 0x0441), + LANG("Swedish - Sweden", "sv_se", 0x041D), + LANG("Swedish - Finland", "sv_fi", 0x081D), + LANG("Tamil", "ta", 0x0449), + LANG("Tatar", "tt", 0X0444), + LANG("Thai", "th", 0x041E), + LANG("Turkish", "tr", 0x041F), + LANG("Tsonga", "ts", 0x0431), + LANG("Ukrainian", "uk", 0x0422), + LANG("Urdu", "ur", 0x0420), + LANG("Uzbek - Cyrillic", "uz_uz", 0x0843), + LANG("Uzbek – Latin", "uz_uz", 0x0443), + LANG("Vietnamese", "vi", 0x042A), + LANG("Xhosa", "xh", 0x0434), + LANG("Yiddish", "yi", 0x043D), + LANG("Zulu", "zu", 0x0435), + LANG(NULL, NULL, 0x0), +}; + +uint16_t get_usb_code_for_current_locale(void) +{ + char *locale; + char search_string[64]; + char *ptr; + struct lang_map_entry *lang; + + /* Get the current locale. */ + locale = setlocale(0, NULL); + if (!locale) + return 0x0; + + /* Make a copy of the current locale string. */ + strncpy(search_string, locale, sizeof(search_string)); + search_string[sizeof(search_string)-1] = '\0'; + + /* Chop off the encoding part, and make it lower case. */ + ptr = search_string; + while (*ptr) { + *ptr = tolower(*ptr); + if (*ptr == '.') { + *ptr = '\0'; + break; + } + ptr++; + } + + /* Find the entry which matches the string code of our locale. */ + lang = lang_map; + while (lang->string_code) { + if (!strcmp(lang->string_code, search_string)) { + return lang->usb_code; + } + lang++; + } + + /* There was no match. Find with just the language only. */ + /* Chop off the variant. Chop it off at the '_'. */ + ptr = search_string; + while (*ptr) { + *ptr = tolower(*ptr); + if (*ptr == '_') { + *ptr = '\0'; + break; + } + ptr++; + } + +#if 0 /* TODO: Do we need this? */ + /* Find the entry which matches the string code of our language. */ + lang = lang_map; + while (lang->string_code) { + if (!strcmp(lang->string_code, search_string)) { + return lang->usb_code; + } + lang++; + } +#endif + + /* Found nothing. */ + return 0x0; +} + +void hid_flush_input_pipe(unsigned short vendor_id) +{ + libusb_device **devs; + libusb_device *dev; + libusb_device_handle *handle; + ssize_t num_devs; + int i = 0; + + if(hid_init() < 0) + return; + + num_devs = libusb_get_device_list(usb_context, &devs); + + if (num_devs < 0) + return; + + while ((dev = devs[i++]) != NULL) + { + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *conf_desc = NULL; + //int j, k; + int interface_num = 0; + + int res = libusb_get_device_descriptor(dev, &desc); + unsigned short dev_vid = desc.idVendor; + + res = libusb_get_active_config_descriptor(dev, &conf_desc); + if (res < 0) + libusb_get_config_descriptor(dev, 0, &conf_desc); + if (conf_desc) + { + const struct libusb_interface *intf = &conf_desc->interface[0]; + const struct libusb_interface_descriptor *intf_desc; + intf_desc = &intf->altsetting[0]; + if (intf_desc->bInterfaceClass == LIBUSB_CLASS_HID) + { + interface_num = intf_desc->bInterfaceNumber; + + if ((/*vendor_id == 0x0 ||*/ vendor_id == dev_vid)) + { + res = libusb_open(dev, &handle); + + if (res >= 0) + { + unsigned char data[256]; + +#ifdef DETACH_KERNEL_DRIVER + //int detached = 0; + /* Usage Page and Usage */ + res = libusb_kernel_driver_active(handle, interface_num); + if (res == 1) + { + res = libusb_detach_kernel_driver(handle, interface_num); + if (res < 0) + { + LOG("Couldn't detach kernel driver, even though a kernel driver was attached."); + } + /*else + detached = 1;*/ + } +#endif + res = libusb_claim_interface(handle, interface_num); + + if (res >= 0) { + + for (int idx = 0; idx < intf_desc->bNumEndpoints; idx++) { + const struct libusb_endpoint_descriptor *ep + = &intf_desc->endpoint[idx]; + + int is_interrupt = + (ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) + == LIBUSB_TRANSFER_TYPE_INTERRUPT; + + int is_input = + (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) + == LIBUSB_ENDPOINT_IN; + + if (is_interrupt && is_input) + { + int transferred = 0; + + do + { + transferred = 0; + libusb_interrupt_transfer(handle, ep->bEndpointAddress, data, ep->wMaxPacketSize, &transferred, 10); + } + while(transferred > 0); + + break; + } + } + + /* Release the interface */ + res = libusb_release_interface(handle, interface_num); + if (res < 0) + LOG("Can't release the interface.\n"); + } + else + LOG("Can't claim interface %d\n", res); + +#ifdef DETACH_KERNEL_DRIVER + /* Re-attach kernel driver if necessary. */ +/* if (detached) + { + res = libusb_attach_kernel_driver(handle, interface_num); + if (res < 0) + LOG("Couldn't re-attach kernel driver.\n"); + }*/ +#endif + + libusb_close(handle); + } + } + } + + libusb_free_config_descriptor(conf_desc); + } + } + + libusb_free_device_list(devs, 1); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/hid/hid_mac.cpp b/src/hid/hid_mac.cpp new file mode 100644 index 0000000..9e35c94 --- /dev/null +++ b/src/hid/hid_mac.cpp @@ -0,0 +1,1126 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 2010-07-03 + + Copyright 2010, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/* See Apple Technical Note TN2187 for details on IOHidManager. */ + +#ifdef __APPLE__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hidapi.h" + +/* Barrier implementation because Mac OSX doesn't have pthread_barrier. + It also doesn't have clock_gettime(). So much for POSIX and SUSv2. + This implementation came from Brent Priddy and was posted on + StackOverflow. It is used with his permission. */ +typedef int pthread_barrierattr_t; +typedef struct pthread_barrier { + pthread_mutex_t mutex; + pthread_cond_t cond; + int count; + int trip_count; +} pthread_barrier_t; + +static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count) +{ + if(count == 0) { + errno = EINVAL; + return -1; + } + + if(pthread_mutex_init(&barrier->mutex, 0) < 0) { + return -1; + } + if(pthread_cond_init(&barrier->cond, 0) < 0) { + pthread_mutex_destroy(&barrier->mutex); + return -1; + } + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static int pthread_barrier_destroy(pthread_barrier_t *barrier) +{ + pthread_cond_destroy(&barrier->cond); + pthread_mutex_destroy(&barrier->mutex); + return 0; +} + +static int pthread_barrier_wait(pthread_barrier_t *barrier) +{ + pthread_mutex_lock(&barrier->mutex); + ++(barrier->count); + if(barrier->count >= barrier->trip_count) + { + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return 1; + } + else + { + pthread_cond_wait(&barrier->cond, &(barrier->mutex)); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +static int return_data(hid_device *dev, unsigned char *data, size_t length); + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + +struct hid_device_ { + IOHIDDeviceRef device_handle; + int blocking; + int uses_numbered_reports; + int disconnected; + CFStringRef run_loop_mode; + CFRunLoopRef run_loop; + CFRunLoopSourceRef source; + uint8_t *input_report_buf; + CFIndex max_input_report_len; + struct input_report *input_reports; + + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ + pthread_barrier_t shutdown_barrier; /* Ensures correct shutdown sequence */ + int shutdown_thread; +}; + +static hid_device *new_hid_device(void) +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + dev->device_handle = NULL; + dev->blocking = 1; + dev->uses_numbered_reports = 0; + dev->disconnected = 0; + dev->run_loop_mode = NULL; + dev->run_loop = NULL; + dev->source = NULL; + dev->input_report_buf = NULL; + dev->input_reports = NULL; + dev->shutdown_thread = 0; + + /* Thread objects */ + pthread_mutex_init(&dev->mutex, NULL); + pthread_cond_init(&dev->condition, NULL); + pthread_barrier_init(&dev->barrier, NULL, 2); + pthread_barrier_init(&dev->shutdown_barrier, NULL, 2); + + return dev; +} + +static void free_hid_device(hid_device *dev) +{ + if (!dev) + return; + + /* Delete any input reports still left over. */ + struct input_report *rpt = dev->input_reports; + while (rpt) { + struct input_report *next = rpt->next; + free(rpt->data); + free(rpt); + rpt = next; + } + + /* Free the string and the report buffer. The check for NULL + is necessary here as CFRelease() doesn't handle NULL like + free() and others do. */ + if (dev->run_loop_mode) + CFRelease(dev->run_loop_mode); + if (dev->source) + CFRelease(dev->source); + free(dev->input_report_buf); + + /* Clean up the thread objects */ + pthread_barrier_destroy(&dev->shutdown_barrier); + pthread_barrier_destroy(&dev->barrier); + pthread_cond_destroy(&dev->condition); + pthread_mutex_destroy(&dev->mutex); + + /* Free the structure itself. */ + free(dev); +} + +static IOHIDManagerRef hid_mgr = 0x0; + + +#if 0 +static void register_error(hid_device *device, const char *op) +{ + +} +#endif + + +static int32_t get_int_property(IOHIDDeviceRef device, CFStringRef key) +{ + CFTypeRef ref; + int32_t value; + + ref = IOHIDDeviceGetProperty(device, key); + if (ref) { + if (CFGetTypeID(ref) == CFNumberGetTypeID()) { + CFNumberGetValue((CFNumberRef) ref, kCFNumberSInt32Type, &value); + return value; + } + } + return 0; +} + +static unsigned short get_vendor_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDVendorIDKey)); +} + +static unsigned short get_product_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDProductIDKey)); +} + +static int32_t get_max_report_length(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDMaxInputReportSizeKey)); +} + + +static int get_string_property(IOHIDDeviceRef device, CFStringRef prop, wchar_t *buf, size_t len) +{ + CFStringRef str; + + if (!len) + return 0; + + str = (CFStringRef) IOHIDDeviceGetProperty(device, prop); + + buf[0] = 0; + + if (str) { + CFIndex str_len = CFStringGetLength(str); + CFRange range; + CFIndex used_buf_len; + CFIndex chars_copied; + + len --; + + range.location = 0; + range.length = ((size_t)str_len > len)? len: (size_t)str_len; + chars_copied = CFStringGetBytes(str, + range, + kCFStringEncodingUTF32LE, + (char)'?', + FALSE, + (UInt8*)buf, + len * sizeof(wchar_t), + &used_buf_len); + + if (chars_copied == (CFIndex) len) + buf[len] = 0; /* len is decremented above */ + else + buf[chars_copied] = 0; + + return 0; + } + else + return -1; + +} + +static int get_serial_number(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDSerialNumberKey), buf, len); +} + +static int get_manufacturer_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDManufacturerKey), buf, len); +} + +static int get_product_string(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDProductKey), buf, len); +} + + +/* Implementation of wcsdup() for Mac. */ +static wchar_t *dup_wcs(const wchar_t *s) +{ + size_t len = wcslen(s); + wchar_t *ret = (wchar_t*) malloc((len+1)*sizeof(wchar_t)); + wcscpy(ret, s); + + return ret; +} + +/* hidapi_IOHIDDeviceGetService() + * + * Return the io_service_t corresponding to a given IOHIDDeviceRef, either by: + * - on OS X 10.6 and above, calling IOHIDDeviceGetService() + * - on OS X 10.5, extract it from the IOHIDDevice struct + */ +static io_service_t hidapi_IOHIDDeviceGetService(IOHIDDeviceRef device) +{ + static void *iokit_framework = NULL; + static io_service_t (*dynamic_IOHIDDeviceGetService)(IOHIDDeviceRef device) = NULL; + + /* Use dlopen()/dlsym() to get a pointer to IOHIDDeviceGetService() if it exists. + * If any of these steps fail, dynamic_IOHIDDeviceGetService will be left NULL + * and the fallback method will be used. + */ + if (iokit_framework == NULL) { + iokit_framework = dlopen("/System/Library/IOKit.framework/IOKit", RTLD_LAZY); + + if (iokit_framework != NULL) + dynamic_IOHIDDeviceGetService = (io_service_t (*)(__IOHIDDevice*)) dlsym(iokit_framework, "IOHIDDeviceGetService"); + } + + if (dynamic_IOHIDDeviceGetService != NULL) { + /* Running on OS X 10.6 and above: IOHIDDeviceGetService() exists */ + return dynamic_IOHIDDeviceGetService(device); + } + else + { + /* Running on OS X 10.5: IOHIDDeviceGetService() doesn't exist. + * + * Be naughty and pull the service out of the IOHIDDevice. + * IOHIDDevice is an opaque struct not exposed to applications, but its + * layout is stable through all available versions of OS X. + * Tested and working on OS X 10.5.8 i386, x86_64, and ppc. + */ + struct IOHIDDevice_internal { + /* The first field of the IOHIDDevice struct is a + * CFRuntimeBase (which is a private CF struct). + * + * a, b, and c are the 3 fields that make up a CFRuntimeBase. + * See http://opensource.apple.com/source/CF/CF-476.18/CFRuntime.h + * + * The second field of the IOHIDDevice is the io_service_t we're looking for. + */ + uintptr_t a; + uint8_t b[4]; +#if __LP64__ + uint32_t c; +#endif + io_service_t service; + }; + struct IOHIDDevice_internal *tmp = (struct IOHIDDevice_internal *)device; + + return tmp->service; + } +} + +/* Initialize the IOHIDManager. Return 0 for success and -1 for failure. */ +static int init_hid_manager(void) +{ + /* Initialize all the HID Manager Objects */ + hid_mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (hid_mgr) { + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + IOHIDManagerScheduleWithRunLoop(hid_mgr, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + return 0; + } + + return -1; +} + +/* Initialize the IOHIDManager if necessary. This is the public function, and + it is safe to call this function repeatedly. Return 0 for success and -1 + for failure. */ +int HID_API_EXPORT hid_init(void) +{ + if (!hid_mgr) { + return init_hid_manager(); + } + + /* Already initialized. */ + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ + if (hid_mgr) { + /* Close the HID manager. */ + IOHIDManagerClose(hid_mgr, kIOHIDOptionsTypeNone); + CFRelease(hid_mgr); + hid_mgr = NULL; + } + + return 0; +} + +static void process_pending_events(void) { + SInt32 res; + do { + res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, FALSE); + } while(res != kCFRunLoopRunFinished && res != kCFRunLoopRunTimedOut); +} + +struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + struct hid_device_info *root = NULL; /* return object */ + struct hid_device_info *cur_dev = NULL; + CFIndex num_devices; + int i; + + /* Set up the HID Manager if it hasn't been done */ + if (hid_init() < 0) + return NULL; + + /* give the IOHIDManager a chance to update itself */ + process_pending_events(); + + /* Get a list of the Devices */ + IOHIDManagerSetDeviceMatching(hid_mgr, NULL); + CFSetRef device_set = IOHIDManagerCopyDevices(hid_mgr); + + /* Convert the list into a C array so we can iterate easily. */ + num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = (IOHIDDeviceRef *) calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + + /* Iterate over each device, making an entry for it. */ + for (i = 0; i < num_devices; i++) { + unsigned short dev_vid; + unsigned short dev_pid; + #define BUF_LEN 256 + wchar_t buf[BUF_LEN]; + + IOHIDDeviceRef dev = device_array[i]; + + if (!dev) { + continue; + } + dev_vid = get_vendor_id(dev); + dev_pid = get_product_id(dev); + + /* Check the VID/PID against the arguments */ + if ((vendor_id == 0x0 || vendor_id == dev_vid) && + (product_id == 0x0 || product_id == dev_pid)) { + struct hid_device_info *tmp; + io_object_t iokit_dev; + kern_return_t res; + io_string_t path; + + /* VID/PID match. Create the record. */ + tmp = (struct hid_device_info *) malloc(sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + /* Get the Usage Page and Usage for this device. */ + cur_dev->usage_page = get_int_property(dev, CFSTR(kIOHIDPrimaryUsagePageKey)); + cur_dev->usage = get_int_property(dev, CFSTR(kIOHIDPrimaryUsageKey)); + + /* Fill out the record */ + cur_dev->next = NULL; + + /* Fill in the path (IOService plane) */ + iokit_dev = hidapi_IOHIDDeviceGetService(dev); + res = IORegistryEntryGetPath(iokit_dev, kIOServicePlane, path); + if (res == KERN_SUCCESS) + cur_dev->path = strdup(path); + else + cur_dev->path = strdup(""); + + /* Serial Number */ + get_serial_number(dev, buf, BUF_LEN); + cur_dev->serial_number = dup_wcs(buf); + + /* Manufacturer and Product strings */ + get_manufacturer_string(dev, buf, BUF_LEN); + cur_dev->manufacturer_string = dup_wcs(buf); + get_product_string(dev, buf, BUF_LEN); + cur_dev->product_string = dup_wcs(buf); + + /* VID/PID */ + cur_dev->vendor_id = dev_vid; + cur_dev->product_id = dev_pid; + + /* Release Number */ + cur_dev->release_number = get_int_property(dev, CFSTR(kIOHIDVersionNumberKey)); + + /* Interface Number (Unsupported on Mac)*/ + cur_dev->interface_number = -1; + } + } + + free(device_array); + CFRelease(device_set); + + return root; +} + +void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + +hid_device * HID_API_EXPORT hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number, struct hid_device_info* dev_info, UlError* err) +{ + /* This function is identical to the Linux version. Platform independent. */ + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device * handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + + if(dev_info) + { + memcpy(dev_info, cur_dev, sizeof(struct hid_device_info)); + dev_info->next = NULL; + } + + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open, err); + } + else + *err = ERR_DEV_NOT_FOUND; + + hid_free_enumeration(devs); + + return handle; +} + +static void hid_device_removal_callback(void *context, IOReturn result, + void *sender) +{ + /* Stop the Run Loop for this device. */ + hid_device *d = (hid_device *) context; + + d->disconnected = 1; + CFRunLoopStop(d->run_loop); +} + +/* The Run Loop calls this function for each input report received. + This function puts the data into a linked list to be picked up by + hid_read(). */ +static void hid_report_callback(void *context, IOReturn result, void *sender, + IOHIDReportType report_type, uint32_t report_id, + uint8_t *report, CFIndex report_length) +{ + struct input_report *rpt; + hid_device *dev = (hid_device *) context; + + /* Make a new Input Report object */ + rpt = (struct input_report *) calloc(1, sizeof(struct input_report)); + rpt->data = (uint8_t*) calloc(1, report_length); + memcpy(rpt->data, report, report_length); + rpt->len = report_length; + rpt->next = NULL; + + /* Lock this section */ + pthread_mutex_lock(&dev->mutex); + + /* Attach the new report object to the end of the list. */ + if (dev->input_reports == NULL) { + /* The list is empty. Put it at the root. */ + dev->input_reports = rpt; + } + else { + /* Find the end of the list and attach. */ + struct input_report *cur = dev->input_reports; + int num_queued = 0; + while (cur->next != NULL) { + cur = cur->next; + num_queued++; + } + cur->next = rpt; + + /* Pop one off if we've reached 30 in the queue. This + way we don't grow forever if the user never reads + anything from the device. */ + if (num_queued > 30) { + return_data(dev, NULL, 0); + } + } + + /* Signal a waiting thread that there is data. */ + pthread_cond_signal(&dev->condition); + + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + +} + +/* This gets called when the read_thread's run loop gets signaled by + hid_close(), and serves to stop the read_thread's run loop. */ +static void perform_signal_callback(void *context) +{ + hid_device *dev = (hid_device *) context; + CFRunLoopStop(dev->run_loop); /*TODO: CFRunLoopGetCurrent()*/ +} + +static void *read_thread(void *param) +{ + hid_device *dev = (hid_device *) param; + SInt32 code; + + /* Move the device's run loop to this thread. */ + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetCurrent(), dev->run_loop_mode); + + /* Create the RunLoopSource which is used to signal the + event loop to stop when hid_close() is called. */ + CFRunLoopSourceContext ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.version = 0; + ctx.info = dev; + ctx.perform = &perform_signal_callback; + dev->source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0/*order*/, &ctx); + CFRunLoopAddSource(CFRunLoopGetCurrent(), dev->source, dev->run_loop_mode); + + /* Store off the Run Loop so it can be stopped from hid_close() + and on device disconnection. */ + dev->run_loop = CFRunLoopGetCurrent(); + + /* Notify the main thread that the read thread is up and running. */ + pthread_barrier_wait(&dev->barrier); + + /* Run the Event Loop. CFRunLoopRunInMode() will dispatch HID input + reports into the hid_report_callback(). */ + while (!dev->shutdown_thread && !dev->disconnected) { + code = CFRunLoopRunInMode(dev->run_loop_mode, 1000/*sec*/, FALSE); + /* Return if the device has been disconnected */ + if (code == kCFRunLoopRunFinished) { + dev->disconnected = 1; + break; + } + + + /* Break if The Run Loop returns Finished or Stopped. */ + if (code != kCFRunLoopRunTimedOut && + code != kCFRunLoopRunHandledSource) { + /* There was some kind of error. Setting + shutdown seems to make sense, but + there may be something else more appropriate */ + dev->shutdown_thread = 1; + break; + } + } + + /* Now that the read thread is stopping, Wake any threads which are + waiting on data (in hid_read_timeout()). Do this under a mutex to + make sure that a thread which is about to go to sleep waiting on + the condition actually will go to sleep before the condition is + signaled. */ + pthread_mutex_lock(&dev->mutex); + pthread_cond_broadcast(&dev->condition); + pthread_mutex_unlock(&dev->mutex); + + /* Wait here until hid_close() is called and makes it past + the call to CFRunLoopWakeUp(). This thread still needs to + be valid when that function is called on the other thread. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + return NULL; +} + +/* hid_open_path() + * + * path must be a valid path to an IOHIDDevice in the IOService plane + * Example: "IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/EHC1@1D,7/AppleUSBEHCI/PLAYSTATION(R)3 Controller@fd120000/IOUSBInterface@0/IOUSBHIDDriver" + */ +hid_device * HID_API_EXPORT hid_open_path(const char *path, UlError* err) +{ + hid_device *dev = NULL; + io_registry_entry_t entry = MACH_PORT_NULL; + IOReturn ret; + + dev = new_hid_device(); + + /* Set up the HID Manager if it hasn't been done */ + if (hid_init() < 0) + return NULL; + + /* Get the IORegistry entry for the given path */ + entry = IORegistryEntryFromPath(kIOMasterPortDefault, path); + if (entry == MACH_PORT_NULL) { + /* Path wasn't valid (maybe device was removed?) */ + goto return_error; + } + + /* Create an IOHIDDevice for the entry */ + dev->device_handle = IOHIDDeviceCreate(kCFAllocatorDefault, entry); + if (dev->device_handle == NULL) { + /* Error creating the HID device */ + goto return_error; + } + + /* Open the IOHIDDevice */ + ret = IOHIDDeviceOpen(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + if (ret == kIOReturnSuccess) { + char str[32]; + + /* Create the buffers for receiving data */ + dev->max_input_report_len = (CFIndex) get_max_report_length(dev->device_handle); + dev->input_report_buf = (uint8_t *) calloc(dev->max_input_report_len, sizeof(uint8_t)); + + /* Create the Run Loop Mode for this device. + printing the reference seems to work. */ + sprintf(str, "HIDAPI_%p", dev->device_handle); + dev->run_loop_mode = + CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); + + /* Attach the device to a Run Loop */ + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + &hid_report_callback, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, hid_device_removal_callback, dev); + + /* Start the read thread */ + pthread_create(&dev->thread, NULL, read_thread, dev); + + /* Wait here for the read thread to be initialized. */ + pthread_barrier_wait(&dev->barrier); + + IOObjectRelease(entry); + return dev; + } + else { + goto return_error; + } + +return_error: + if (dev->device_handle != NULL) + CFRelease(dev->device_handle); + + if (entry != MACH_PORT_NULL) + IOObjectRelease(entry); + + free_hid_device(dev); + return NULL; +} + +static int set_report(hid_device *dev, IOHIDReportType type, const unsigned char *data, size_t length) +{ + const unsigned char *data_to_send; + size_t length_to_send; + IOReturn res; + + /* Return if the device has been disconnected. */ + if (dev->disconnected) + return -1; + + if (data[0] == 0x0) { + /* Not using numbered Reports. + Don't send the report number. */ + data_to_send = data+1; + length_to_send = length-1; + } + else { + /* Using numbered Reports. + Send the Report Number */ + data_to_send = data; + length_to_send = length; + } + + if (!dev->disconnected) { + res = IOHIDDeviceSetReport(dev->device_handle, + type, + data[0], /* Report ID*/ + data_to_send, length_to_send); + + if (res == kIOReturnSuccess) { + return length; + } + else + return -1; + } + + return -1; +} + +int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeOutput, data, length); +} + +/* Helper function, so that this isn't duplicated in hid_read(). */ +static int return_data(hid_device *dev, unsigned char *data, size_t length) +{ + /* Copy the data out of the linked list item (rpt) into the + return buffer (data), and delete the liked list item. */ + struct input_report *rpt = dev->input_reports; + size_t len = (length < rpt->len)? length: rpt->len; + memcpy(data, rpt->data, len); + dev->input_reports = rpt->next; + free(rpt->data); + free(rpt); + return len; +} + +static int cond_wait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + while (!dev->input_reports) { + int res = pthread_cond_wait(cond, mutex); + if (res != 0) + return res; + + /* A res of 0 means we may have been signaled or it may + be a spurious wakeup. Check to see that there's acutally + data in the queue before returning, and if not, go back + to sleep. See the pthread_cond_timedwait() man page for + details. */ + + if (dev->shutdown_thread || dev->disconnected) + return -1; + } + + return 0; +} + +static int cond_timedwait(const hid_device *dev, pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) +{ + while (!dev->input_reports) { + int res = pthread_cond_timedwait(cond, mutex, abstime); + if (res != 0) + return res; + + /* A res of 0 means we may have been signaled or it may + be a spurious wakeup. Check to see that there's acutally + data in the queue before returning, and if not, go back + to sleep. See the pthread_cond_timedwait() man page for + details. */ + + if (dev->shutdown_thread || dev->disconnected) + return -1; + } + + return 0; + +} + +int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + int bytes_read = -1; + + /* Lock the access to the report list. */ + pthread_mutex_lock(&dev->mutex); + + /* There's an input report queued up. Return it. */ + if (dev->input_reports) { + /* Return the first one */ + bytes_read = return_data(dev, data, length); + goto ret; + } + + /* Return if the device has been disconnected. */ + if (dev->disconnected) { + bytes_read = -1; + goto ret; + } + + if (dev->shutdown_thread) { + /* This means the device has been closed (or there + has been an error. An error code of -1 should + be returned. */ + bytes_read = -1; + goto ret; + } + + /* There is no data. Go to sleep and wait for data. */ + + if (milliseconds == -1) { + /* Blocking */ + int res; + res = cond_wait(dev, &dev->condition, &dev->mutex); + if (res == 0) + bytes_read = return_data(dev, data, length); + else { + /* There was an error, or a device disconnection. */ + bytes_read = -1; + } + } + else if (milliseconds > 0) { + /* Non-blocking, but called with timeout. */ + int res; + struct timespec ts; + struct timeval tv; + gettimeofday(&tv, NULL); + TIMEVAL_TO_TIMESPEC(&tv, &ts); + ts.tv_sec += milliseconds / 1000; + ts.tv_nsec += (milliseconds % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000L; + } + + res = cond_timedwait(dev, &dev->condition, &dev->mutex, &ts); + if (res == 0) + bytes_read = return_data(dev, data, length); + else if (res == ETIMEDOUT) + bytes_read = 0; + else + bytes_read = -1; + } + else { + /* Purely non-blocking */ + bytes_read = 0; + } + +ret: + /* Unlock */ + pthread_mutex_unlock(&dev->mutex); + return bytes_read; +} + +int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock) +{ + /* All Nonblocking operation is handled by the library. */ + dev->blocking = !nonblock; + + return 0; +} + +int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + return set_report(dev, kIOHIDReportTypeFeature, data, length); +} + +int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + CFIndex len = length; + IOReturn res; + + /* Return if the device has been unplugged. */ + if (dev->disconnected) + return -1; + + res = IOHIDDeviceGetReport(dev->device_handle, + kIOHIDReportTypeFeature, + data[0], /* Report ID */ + data, &len); + if (res == kIOReturnSuccess) + return len; + else + return -1; +} + + +void HID_API_EXPORT hid_close(hid_device *dev) +{ + if (!dev) + return; + + /* Disconnect the report callback before close. */ + if (!dev->disconnected) { + IOHIDDeviceRegisterInputReportCallback( + dev->device_handle, dev->input_report_buf, dev->max_input_report_len, + NULL, dev); + IOHIDDeviceRegisterRemovalCallback(dev->device_handle, NULL, dev); + IOHIDDeviceUnscheduleFromRunLoop(dev->device_handle, dev->run_loop, dev->run_loop_mode); + IOHIDDeviceScheduleWithRunLoop(dev->device_handle, CFRunLoopGetMain(), kCFRunLoopDefaultMode); + } + + /* Cause read_thread() to stop. */ + dev->shutdown_thread = 1; + + /* Wake up the run thread's event loop so that the thread can exit. */ + CFRunLoopSourceSignal(dev->source); + CFRunLoopWakeUp(dev->run_loop); + + /* Notify the read thread that it can shut down now. */ + pthread_barrier_wait(&dev->shutdown_barrier); + + /* Wait for read_thread() to end. */ + pthread_join(dev->thread, NULL); + + /* Close the OS handle to the device, but only if it's not + been unplugged. If it's been unplugged, then calling + IOHIDDeviceClose() will crash. */ + if (!dev->disconnected) { + IOHIDDeviceClose(dev->device_handle, kIOHIDOptionsTypeSeizeDevice); + } + + /* Clear out the queue of received reports. */ + pthread_mutex_lock(&dev->mutex); + while (dev->input_reports) { + return_data(dev, NULL, 0); + } + pthread_mutex_unlock(&dev->mutex); + CFRelease(dev->device_handle); + + free_hid_device(dev); +} + +int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_manufacturer_string(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_product_string(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + return get_serial_number(dev->device_handle, string, maxlen); +} + +int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + /* TODO: */ + + return 0; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + /* TODO: */ + + return NULL; +} + +void hid_flush_input_pipe(unsigned short vendor_id) +{ + // no implemented yet +} + + + + + +#if 0 +static int32_t get_location_id(IOHIDDeviceRef device) +{ + return get_int_property(device, CFSTR(kIOHIDLocationIDKey)); +} + +static int32_t get_usage(IOHIDDeviceRef device) +{ + int32_t res; + res = get_int_property(device, CFSTR(kIOHIDDeviceUsageKey)); + if (!res) + res = get_int_property(device, CFSTR(kIOHIDPrimaryUsageKey)); + return res; +} + +static int32_t get_usage_page(IOHIDDeviceRef device) +{ + int32_t res; + res = get_int_property(device, CFSTR(kIOHIDDeviceUsagePageKey)); + if (!res) + res = get_int_property(device, CFSTR(kIOHIDPrimaryUsagePageKey)); + return res; +} + +static int get_transport(IOHIDDeviceRef device, wchar_t *buf, size_t len) +{ + return get_string_property(device, CFSTR(kIOHIDTransportKey), buf, len); +} + +int main(void) +{ + IOHIDManagerRef mgr; + int i; + + mgr = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + IOHIDManagerSetDeviceMatching(mgr, NULL); + IOHIDManagerOpen(mgr, kIOHIDOptionsTypeNone); + + CFSetRef device_set = IOHIDManagerCopyDevices(mgr); + + CFIndex num_devices = CFSetGetCount(device_set); + IOHIDDeviceRef *device_array = calloc(num_devices, sizeof(IOHIDDeviceRef)); + CFSetGetValues(device_set, (const void **) device_array); + + for (i = 0; i < num_devices; i++) { + IOHIDDeviceRef dev = device_array[i]; + printf("Device: %p\n", dev); + printf(" %04hx %04hx\n", get_vendor_id(dev), get_product_id(dev)); + + wchar_t serial[256], buf[256]; + char cbuf[256]; + get_serial_number(dev, serial, 256); + + + printf(" Serial: %ls\n", serial); + printf(" Loc: %ld\n", get_location_id(dev)); + get_transport(dev, buf, 256); + printf(" Trans: %ls\n", buf); + make_path(dev, cbuf, 256); + printf(" Path: %s\n", cbuf); + + } + + return 0; +} +#endif + +#endif diff --git a/src/hid/hidapi.h b/src/hid/hidapi.h new file mode 100644 index 0000000..f490762 --- /dev/null +++ b/src/hid/hidapi.h @@ -0,0 +1,398 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU General Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +#include "../uldaq.h" +#include "../ul_internal.h" + +#ifdef _WIN32 + #define HID_API_EXPORT __declspec(dllexport) + #define HID_API_CALL +#else + #define HID_API_EXPORT /**< API export macro */ + #define HID_API_CALL /**< API call macro */ +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. Valid on both Linux implementations + in all cases, and valid on the Windows implementation + only if the device contains more than one interface. */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id is set to 0 then any vendor matches. + If @p product_id is set to 0 then any product matches. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info UL_LOCAL HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void UL_LOCAL HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + UL_LOCAL UL_LOCAL HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number, struct hid_device_info* dev_info, UlError* err); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + UL_LOCAL HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path, UlError* err); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first OUT endpoint, if + one exists. If it does not, it will send the data through + the Control Endpoint (Endpoint 0). + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read within + the timeout period, this function returns 0. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. If no packet was available to be read and + the handle is in non-blocking mode, this function returns 0. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param device A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Set the first byte of @p data[] to the Report ID of the + report to be read. Make sure to allow space for this + extra byte in @p data[]. Upon return, the first byte will + still contain the Report ID, and the report data will + start in data[1]. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read, or set it to zero + if your device does not use numbered reports. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read plus + one for the report ID (which is still in the first + byte), or -1 on error. + */ + int UL_LOCAL HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + */ + void UL_LOCAL HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int UL_LOCAL HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a string describing the last error which occurred. + + @ingroup API + @param device A device handle returned from hid_open(). + + @returns + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + UL_LOCAL HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + + // this function was added to flush the input pipe of the mcc hid devices + void UL_LOCAL HID_API_EXPORT hid_flush_input_pipe(unsigned short vendor_id); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/interfaces/UlAiConfig.h b/src/interfaces/UlAiConfig.h index 0b01156..458a3a9 100644 --- a/src/interfaces/UlAiConfig.h +++ b/src/interfaces/UlAiConfig.h @@ -23,9 +23,9 @@ public: virtual void setChanTcType(int channel, TcType tcType) = 0; virtual TcType getChanTcType(int channel) = 0; - virtual void setChanTempUnit(int channel, TempUnit unit) = 0; - virtual TempUnit getChanTempUnit(int channel) = 0; - virtual void setTempUnit(TempUnit unit) = 0; + virtual void setScanChanTempUnit(int channel, TempUnit unit) = 0; + virtual TempUnit getScanChanTempUnit(int channel) = 0; + virtual void setScanTempUnit(TempUnit unit) = 0; virtual void setAutoZeroMode(AutoZeroMode mode) = 0; virtual AutoZeroMode getAutoZeroMode() = 0; @@ -50,6 +50,10 @@ public: virtual unsigned long long getCalDate() = 0; // returns number of seconds since unix epoch virtual void getCalDateStr(char* calDate, unsigned int* maxStrLen) = 0; + + virtual void getChanCoefsStr(int channel, char* coefs, unsigned int* maxStrLen) = 0; + + virtual SensorConnectionType getChanSensorConnectionType(int channel) = 0; }; } /* namespace ul */ diff --git a/src/interfaces/UlAiDevice.h b/src/interfaces/UlAiDevice.h index 5a49219..73987b0 100644 --- a/src/interfaces/UlAiDevice.h +++ b/src/interfaces/UlAiDevice.h @@ -29,6 +29,9 @@ public: virtual UlError getStatus(ScanStatus* status, TransferStatus* xferStatus) = 0; virtual void stopBackground() = 0; + + virtual void tIn(int channel, TempScale scale, TInFlag flags, double* data) = 0; + virtual void tInArray(int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) = 0; }; } /* namespace ul */ diff --git a/src/interfaces/UlAiInfo.h b/src/interfaces/UlAiInfo.h index 8b28f78..7ccca4b 100644 --- a/src/interfaces/UlAiInfo.h +++ b/src/interfaces/UlAiInfo.h @@ -42,6 +42,10 @@ public: virtual AiQueueType getQueueTypes() const = 0; virtual AiChanQueueLimitation getChanQueueLimitations() const = 0; + + virtual long long getTInFlags() const = 0; + virtual long long getTInArrayFlags() const = 0; + virtual int getNumCjcChans() const = 0; }; } /* namespace ul */ diff --git a/src/interfaces/UlAoConfig.h b/src/interfaces/UlAoConfig.h index 217099f..a1c111c 100644 --- a/src/interfaces/UlAoConfig.h +++ b/src/interfaces/UlAoConfig.h @@ -7,6 +7,8 @@ #ifndef INTERFACES_ULAOCONFIG_H_ #define INTERFACES_ULAOCONFIG_H_ +#include "../uldaq.h" + namespace ul { @@ -14,6 +16,9 @@ class UlAoConfig { public: virtual ~UlAoConfig() {}; + + virtual void setSyncMode(AOutSyncMode mode) = 0; + virtual AOutSyncMode getSyncMode() = 0; }; } /* namespace ul */ diff --git a/src/interfaces/UlAoDevice.h b/src/interfaces/UlAoDevice.h index 1a9d28b..e189b8a 100644 --- a/src/interfaces/UlAoDevice.h +++ b/src/interfaces/UlAoDevice.h @@ -23,6 +23,7 @@ public: virtual UlAoConfig& getAoConfig() = 0; virtual void aOut(int channel, Range range, AOutFlag flags, double dataValue) = 0; + virtual void aOutArray(int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) = 0; virtual double aOutScan(int lowChan, int highChan, Range range, int samplesPerChan, double rate, ScanOption options, AOutScanFlag flags, double data[]) = 0; virtual void setTrigger(TriggerType type, int trigChan, double level, double variance, unsigned int retriggerCount) = 0; diff --git a/src/interfaces/UlDaqDevice.h b/src/interfaces/UlDaqDevice.h index 4839003..54521a5 100644 --- a/src/interfaces/UlDaqDevice.h +++ b/src/interfaces/UlDaqDevice.h @@ -1,7 +1,6 @@ /* * UlDaqDevice.h * - * Created on: Jul 29, 2015 * Author: Measurement Computing Corporation */ diff --git a/src/interfaces/UlDioConfig.h b/src/interfaces/UlDioConfig.h index b4424e5..89f9043 100644 --- a/src/interfaces/UlDioConfig.h +++ b/src/interfaces/UlDioConfig.h @@ -16,6 +16,10 @@ public: virtual ~UlDioConfig() {}; virtual unsigned long long getPortDirectionMask(int portNum) = 0; + virtual void setPortInitialOutputVal(int portNum, unsigned long long val) = 0; + virtual void setPortIsoMask(int portNum, unsigned long long mask) = 0; + virtual unsigned long long getPortIsoMask(int portNum) = 0; + virtual unsigned long long getPortLogic(int portNum) = 0; }; } /* namespace ul */ diff --git a/src/main.cpp b/src/main.cpp index 7b8c5c5..db06599 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,7 @@ #include "./ul_internal.h" #include "./DaqDeviceManager.h" #include "./usb/UsbDaqDevice.h" -//#include "./hid/HidDaqDevice.h" +#include "./hid/HidDaqDevice.h" void __attribute__ ((constructor)) lib_load(void); void __attribute__ ((destructor)) lib_unload(void); @@ -31,7 +31,7 @@ void lib_load(void) ErrorMap::init(); UsbDaqDevice::usb_init(); - //HidDaqDevice::hidapi_init(); + HidDaqDevice::hidapi_init(); SuspendMonitor::init(); } @@ -41,7 +41,7 @@ void lib_unload(void) { DaqDeviceManager::releaseDevices(); - //HidDaqDevice::hidapi_exit(); + HidDaqDevice::hidapi_exit(); UsbDaqDevice::usb_exit(); diff --git a/src/ul_internal.h b/src/ul_internal.h index 5703b3d..58a7286 100644 --- a/src/ul_internal.h +++ b/src/ul_internal.h @@ -22,7 +22,7 @@ namespace ul { -#define UL_VERSION "0.01" +#define UL_VERSION "0.02" #ifdef DEBUG //#define TRACE diff --git a/src/ulc.cpp b/src/ulc.cpp index 4b04a64..3e99315 100644 --- a/src/ulc.cpp +++ b/src/ulc.cpp @@ -49,6 +49,7 @@ UlError ulGetDaqDeviceInventory(DaqDeviceInterface interfaceTypes, DaqDeviceDesc return err; } +// coverity[pass_by_value] DaqDeviceHandle ulCreateDaqDevice(DaqDeviceDescriptor daqDevDescriptor) { int __attribute__((unused)) error = ERR_NO_ERROR; @@ -73,6 +74,33 @@ DaqDeviceHandle ulCreateDaqDevice(DaqDeviceDescriptor daqDevDescriptor) return virtualHandle; } +DaqDeviceHandle ulCreateDaqDevicePtr(DaqDeviceDescriptor* daqDevDescriptor) +{ + int __attribute__((unused)) error = ERR_NO_ERROR; + + DaqDeviceHandle virtualHandle = 0; + + if(daqDevDescriptor) + { + try + { + DaqDevice& daqDev = (DaqDevice&) UlDaqDeviceManager::createDaqDevice(*daqDevDescriptor); + + virtualHandle = daqDev.getDeviceNumber(); + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + + return virtualHandle; +} + UlError ulGetDaqDeviceDescriptor(DaqDeviceHandle daqDeviceHandle, DaqDeviceDescriptor* daqDeviceDescriptor) { UlError error = ERR_NO_ERROR; @@ -475,6 +503,81 @@ UlError ulAInSetTrigger(DaqDeviceHandle daqDeviceHandle, TriggerType type, int t return error; } +UlError ulTIn(DaqDeviceHandle daqDeviceHandle, int channel, TempScale scale, TInFlag flags, double* data) +{ + FnLog log("ulTIn()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + AiDevice* aiDev = pDaqDevice->aiDevice(); + + if(aiDev) + { + if(data) + aiDev->tIn(channel, scale, flags, data); + else + error = ERR_BAD_ARG; + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + +UlError ulTInArray(DaqDeviceHandle daqDeviceHandle, int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]) +{ + FnLog log("ulTInArray()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + AiDevice* aiDev = pDaqDevice->aiDevice(); + + if(aiDev) + { + aiDev->tInArray(lowChan, highChan, scale, flags, data); + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + UlError ulAOut(DaqDeviceHandle daqDeviceHandle, int channel, Range range, AOutFlag flags, double data) { FnLog log("ulAOut()"); @@ -509,6 +612,42 @@ UlError ulAOut(DaqDeviceHandle daqDeviceHandle, int channel, Range range, AOutFl return error; } +UlError ulAOutArray(DaqDeviceHandle daqDeviceHandle, int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]) +{ + FnLog log("ulAOutArray()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + AoDevice* aoDev = pDaqDevice->aoDevice(); + + if(aoDev) + { + aoDev->aOutArray(lowChan, highChan, range, flags, data); + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + UlError ulAOutScan(DaqDeviceHandle daqDeviceHandle, int lowChan, int highChan, Range range, int samplesPerChan, double* rate, ScanOption options, AOutScanFlag flags, double data[]) { FnLog log("ulAOutScan()"); @@ -825,6 +964,79 @@ UlError ulDOut(DaqDeviceHandle daqDeviceHandle, DigitalPortType portType, unsign return error; } +UlError ulDInArray(DaqDeviceHandle daqDeviceHandle, DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + FnLog log("ulDInArray()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + DioDevice* dioDev = pDaqDevice->dioDevice(); + + if(dioDev) + { + if(data) + dioDev->dInArray(lowPort, highPort, data); + else + error = ERR_BAD_ARG; + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + +UlError ulDOutArray(DaqDeviceHandle daqDeviceHandle, DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + FnLog log("ulDOutArray()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + DioDevice* dioDev = pDaqDevice->dioDevice(); + + if(dioDev) + dioDev->dOutArray(lowPort, highPort, data); + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + UlError ulDBitIn(DaqDeviceHandle daqDeviceHandle, DigitalPortType portType, int bitNum, unsigned int* bitValue) { FnLog log("ulDIn()"); @@ -2260,18 +2472,22 @@ UlError ulGetInfoStr(UlInfoItemStr infoItem, unsigned int index, char* infoStr, case UL_INFO_VER_STR: if(infoStr) + { infoStr[0] = '\0'; - if(strlen(UL_VERSION) < *maxConfigLen) - { - memcpy(infoStr, UL_VERSION, strlen(UL_VERSION) + 1); - *maxConfigLen = strlen(UL_VERSION) + 1; + if(strlen(UL_VERSION) < *maxConfigLen) + { + memcpy(infoStr, UL_VERSION, strlen(UL_VERSION) + 1); + *maxConfigLen = strlen(UL_VERSION) + 1; + } + else + { + *maxConfigLen = strlen(UL_VERSION) + 1; + error = ERR_BAD_BUFFER_SIZE; + } } else - { - *maxConfigLen = strlen(UL_VERSION) + 1; - error = ERR_BAD_BUFFER_SIZE; - } + error = ERR_BAD_BUFFER; break; @@ -2398,7 +2614,7 @@ UlError ulDevGetInfo(DaqDeviceHandle daqDeviceHandle, DevInfoItem infoItem, unsi break; default: - error = ERR_BAD_CONFIG_ITEM; + error = ERR_BAD_INFO_ITEM; } } catch(UlException& e) @@ -2484,11 +2700,11 @@ UlError ulAISetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, case AI_CFG_CHAN_TC_TYPE: aiConfig.setChanTcType(index, (TcType) configValue); break; - case AI_CFG_CHAN_TEMP_UNIT: - aiConfig.setChanTempUnit(index, (TempUnit) configValue); + case AI_CFG_SCAN_CHAN_TEMP_UNIT: + aiConfig.setScanChanTempUnit(index, (TempUnit) configValue); break; - case AI_CFG_TEMP_UNIT: - aiConfig.setTempUnit((TempUnit) configValue); + case AI_CFG_SCAN_TEMP_UNIT: + aiConfig.setScanTempUnit((TempUnit) configValue); break; case AI_CFG_ADC_TIMING_MODE: aiConfig.setAdcTimingMode((AdcTimingMode) configValue); @@ -2554,8 +2770,8 @@ UlError ulAIGetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, case AI_CFG_CHAN_TC_TYPE: *configValue = aiConfig.getChanTcType(index); break; - case AI_CFG_CHAN_TEMP_UNIT: - *configValue = aiConfig.getChanTempUnit(index); + case AI_CFG_SCAN_CHAN_TEMP_UNIT: + *configValue = aiConfig.getScanChanTempUnit(index); break; case AI_CFG_ADC_TIMING_MODE: *configValue = aiConfig.getAdcTimingMode(); @@ -2572,6 +2788,9 @@ UlError ulAIGetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, case AI_CFG_CHAN_COUPLING_MODE: *configValue = aiConfig.getChanCouplingMode(index); break; + case AI_CFG_CHAN_SENSOR_CONNECTION_TYPE: + *configValue = aiConfig.getChanSensorConnectionType(index); + break; default: error = ERR_BAD_CONFIG_ITEM; @@ -2625,7 +2844,7 @@ UlError ulAISetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl config case AI_CFG_CHAN_OFFSET: aiConfig.setChanOffset(index, configValue); break; - case AI_CFG_CHAN_SENSOR_SENSIVITY: + case AI_CFG_CHAN_SENSOR_SENSITIVITY: aiConfig.setChanSensorSensitivity(index, configValue); break; default: @@ -2679,7 +2898,7 @@ UlError ulAIGetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl config case AI_CFG_CHAN_OFFSET: *configValue = aiConfig.getChanOffset(index); break; - case AI_CFG_CHAN_SENSOR_SENSIVITY: + case AI_CFG_CHAN_SENSOR_SENSITIVITY: *configValue = aiConfig.getChanSensorSensitivity(index); break; @@ -2733,6 +2952,9 @@ UlError ulAIGetConfigStr(DaqDeviceHandle daqDeviceHandle, AiConfigItemStr config case AI_CFG_CAL_DATE_STR: aiConfig.getCalDateStr(configStr, maxConfigLen); break; + case AI_CFG_CHAN_COEFS_STR: + aiConfig.getChanCoefsStr(index, configStr, maxConfigLen); + break; default: error = ERR_BAD_CONFIG_ITEM; @@ -2917,6 +3139,105 @@ UlError ulAIGetInfoDbl(DaqDeviceHandle daqDeviceHandle, AiInfoItemDbl infoItem, return error; } + +UlError ulAOSetConfig(DaqDeviceHandle daqDeviceHandle, AoConfigItem configItem, unsigned int index, long long configValue) +{ + FnLog log("ulAOSetConfig()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + AoDevice* aoDev = pDaqDevice->aoDevice(); + + if(aoDev) + { + UlAoConfig& aoConfig = aoDev->getAoConfig(); + + switch(configItem) + { + case AO_CFG_SYNC_MODE: + aoConfig.setSyncMode((AOutSyncMode) configValue); + break; + default: + error = ERR_BAD_CONFIG_ITEM; + } + + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + + +UlError ulAOGetConfig(DaqDeviceHandle daqDeviceHandle, AoConfigItem configItem, unsigned int index, long long* configValue) +{ + FnLog log("ulAOGetConfig()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + if(configValue) + { + try + { + AoDevice* aoDev = pDaqDevice->aoDevice(); + + if(aoDev) + { + UlAoConfig& aoConfig = aoDev->getAoConfig(); + + switch(configItem) + { + case AO_CFG_SYNC_MODE: + *configValue = aoConfig.getSyncMode(); + break; + default: + error = ERR_BAD_CONFIG_ITEM; + } + + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + } + else + error = ERR_BAD_ARG; + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + UlError ulAOGetInfo(DaqDeviceHandle daqDeviceHandle, AoInfoItem infoItem, unsigned int index, long long* infoValue) { FnLog log("ulAOGetInfo()"); @@ -3200,6 +3521,13 @@ UlError ulDIOGetConfig(DaqDeviceHandle daqDeviceHandle, DioConfigItem configItem case DIO_CFG_PORT_DIRECTION_MASK: *configValue = dioConfig.getPortDirectionMask(index); break; + case DIO_CFG_PORT_ISO_FILTER_MASK: + *configValue = dioConfig.getPortIsoMask(index); + break; + case DIO_CFG_PORT_LOGIC: + *configValue = dioConfig.getPortLogic(index); + break; + default: error = ERR_BAD_CONFIG_ITEM; } @@ -3226,6 +3554,56 @@ UlError ulDIOGetConfig(DaqDeviceHandle daqDeviceHandle, DioConfigItem configItem return error; } +UlError ulDIOSetConfig(DaqDeviceHandle daqDeviceHandle, DioConfigItem configItem, unsigned int index, long long configValue) +{ + FnLog log("ulDIOSetConfig()"); + + UlError error = ERR_NO_ERROR; + + DaqDevice* pDaqDevice = DaqDeviceManager::getActualDeviceHandle(daqDeviceHandle); + + if(pDaqDevice) + { + try + { + DioDevice* dioDev = pDaqDevice->dioDevice(); + + if(dioDev) + { + UlDioConfig& dioConfig = dioDev->getDioConfig(); + + switch(configItem) + { + case DIO_CFG_PORT_INITIAL_OUTPUT_VAL: + dioConfig.setPortInitialOutputVal(index, configValue); + break; + case DIO_CFG_PORT_ISO_FILTER_MASK: + dioConfig.setPortIsoMask(index, configValue); + break; + default: + error = ERR_BAD_CONFIG_ITEM; + } + + } + else + error = ERR_BAD_DEV_TYPE; + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + + } + else + error = ERR_BAD_DEV_HANDLE; + + return error; +} + UlError ulCtrGetInfo(DaqDeviceHandle daqDeviceHandle, CtrInfoItem infoItem, unsigned int index, long long* infoValue) { diff --git a/src/uldaq.h b/src/uldaq.h index 522cb0b..3030b81 100644 --- a/src/uldaq.h +++ b/src/uldaq.h @@ -338,7 +338,25 @@ typedef enum ERR_DEV_UNAVAILABLE = 80, /** Re-trigger option is not supported for the specified trigger type */ - ERR_BAD_RETRIG_TRIG_TYPE = 81 + ERR_BAD_RETRIG_TRIG_TYPE = 81, + + /** This function cannot be used with this version of the device */ + ERR_BAD_DEV_VER = 82, + + /** This digital operation is not supported on the specified port */ + ERR_BAD_DIG_OPERATION = 83, + + /** Invalid digital port index specified */ + ERR_BAD_PORT_INDEX = 84, + + /** Temperature input has open connection */ + ERR_OPEN_CONNECTION = 85, + + /** Device is not ready to send data */ + ERR_DEV_NOT_READY = 86, + + /** Pacer overrun, external clock rate too fast. */ + ERR_PACER_OVERRUN = 87 } UlError; @@ -410,6 +428,22 @@ typedef enum TC_N = 8 }TcType; +/** Sensor connection types */ +typedef enum +{ + /** 2-wire with a single sensor per differential channel pair **/ + SCT_2_WIRE_1 = 1, + + /** 2-wire with two sensors per differential channel pair **/ + SCT_2_WIRE_2 = 2, + + /** 3-wire with a single sensor per differential channel pair **/ + SCT_3_WIRE = 3, + + /** 4-wire with a single sensor per differential channel pair **/ + SCT_4_WIRE = 4 +}SensorConnectionType; + /** Used with many analog input and output functions, as well as a return value for the \p infoValue argument * to ulAIGetInfo() when used with ::AI_INFO_DIFF_RANGE or ::AI_INFO_SE_RANGE,
and the \p infoValue argument * to ulAOGetInfo() when used with ::AO_INFO_RANGE. */ @@ -539,7 +573,10 @@ typedef enum UNIPT01VOLTS = 1020, /** 0 to +.005 Volts */ - UNIPT005VOLTS = 1021 + UNIPT005VOLTS = 1021, + + /** 0 to 20 Milliamps */ + MA0TO20 = 2000 }Range; /** Temperature units */ @@ -961,6 +998,7 @@ typedef enum #ifndef doxy_skip #define NOSCALEDATA 1 << 0 #define NOCALIBRATEDATA 1 << 1 +#define SIMULTANEOUS 1 << 2 #define NOCLEAR 1 << 3 #endif /*doxy_skip */ @@ -1040,6 +1078,20 @@ typedef enum AOUTSCAN_FF_NOCALIBRATEDATA = NOCALIBRATEDATA }AOutScanFlag; +/** Use as the \p flags argument value for ulTIn() to set the properties of data returned; reserved for future use. */ +typedef enum +{ + /** Placeholder value. Standard functionality. */ + TIN_FF_DEFAULT = 0, +}TInFlag; + +/** Use as the \p flags argument value for ulTInArray() to set the properties of data returned; reserved for future use. */ +typedef enum +{ + /** Placeholder value. Standard functionality. */ + TINARRAY_FF_DEFAULT = 0, +}TInArrayFlag; + /** Use as the \p flags argument value for ulAOut() to set the properties of data supplied to the function. */ typedef enum { @@ -1053,10 +1105,36 @@ typedef enum AOUT_FF_NOCALIBRATEDATA = NOCALIBRATEDATA }AOutFlag; +/** Use as the \p flags argument value for ulAOutArray() to set the properties of data supplied to the function. */ +typedef enum +{ + /** Scaled data is supplied and calibration factors are applied to output. */ + AOUTARRAY_FF_DEFAULT = 0, + + /** Data is supplied in native format (usually, values ranging from 0 to 2resolution - 1). */ + AOUTARRAY_FF_NOSCALEDATA = NOSCALEDATA, + + /** Data is output without calibration factors applied. */ + AOUTARRAY_FF_NOCALIBRATEDATA = NOCALIBRATEDATA , + + /** All of the specified channels will be updated simultaneously. */ + AOUTARRAY_FF_SIMULTANEOUS = SIMULTANEOUS +}AOutArrayFlag; + +/** Use with #AoConfigItem to set configuration options at runtime. */ +typedef enum +{ + /** Receive the D/A Load signal from an external source */ + AOSM_SLAVE = 0, + + /** Output the internal D/A Load signal */ + AOSM_MASTER = 1 +}AOutSyncMode; + /** Use as the \p flags argument value for ulCInScan() to set counter properties. */ typedef enum { - /** Default counter behavior. */ + /** Default counter behavior */ CINSCAN_FF_DEFAULT = 0, /** Sets up the counter as a 16-bit counter channel */ @@ -1069,13 +1147,16 @@ typedef enum CINSCAN_FF_CTR64_BIT = 1 << 2, /** Does not clear the counter to 0 at the start of each scan. */ - CINSCAN_FF_NOCLEAR = NOCLEAR + CINSCAN_FF_NOCLEAR = NOCLEAR, + + /** Sets up the counter as a 48-bit counter channel */ + CINSCAN_FF_CTR48_BIT = 1 << 4 }CInScanFlag; /** Use as the \p flags argument value for ulDInScan() to set the properties of data returned. */ typedef enum { - /** Standard scan properties; placeholder for future values */ + /** Standard scan properties. Placeholder for future values */ DINSCAN_FF_DEFAULT = 0, }DInScanFlag; @@ -1708,7 +1789,6 @@ typedef enum AI_INFO_MAX_BURST_THROUGHPUT = 1004 }AiInfoItemDbl; -#ifndef doxy_skip /** Use with ulAISetConfig() and ulAIGetConfig() to configure the AI subsystem. */ typedef enum { @@ -1718,21 +1798,20 @@ typedef enum /** The thermocouple type of the specified channel. Set with #TcType. */ AI_CFG_CHAN_TC_TYPE = 2, - /** The temperature unit of the specified channel. Set with #TempUnit. */ - AI_CFG_CHAN_TEMP_UNIT = 3, +#ifndef doxy_skip + /** The temperature unit of the specified analog input scan channel. Set with #TempUnit. */ + AI_CFG_SCAN_CHAN_TEMP_UNIT = 3, - /** The temperature unit. Set with #TempUnit. */ - AI_CFG_TEMP_UNIT = 4, + /** The analog input scan temperature unit. Set with #TempUnit. */ + AI_CFG_SCAN_TEMP_UNIT = 4, - #ifndef doxy_skip /** The timing mode. Set with #AdcTimingMode. */ AI_CFG_ADC_TIMING_MODE = 5, - #endif /* doxy_skip */ +#endif /* doxy_skip */ - #ifndef doxy_skip +#ifndef doxy_skip /** The auto zero mode. Set with #AutoZeroMode. */ AI_CFG_AUTO_ZERO_MODE = 6, - #endif /* doxy_skip */ /** The date when the device was calibrated last in UNIX Epoch time. */ AI_CFG_CAL_DATE = 7, @@ -1741,9 +1820,14 @@ typedef enum AI_CFG_CHAN_IEPE_MODE = 8, /** The coupling mode for the specified device. Set with #CouplingMode. */ - AI_CFG_CHAN_COUPLING_MODE = 9 + AI_CFG_CHAN_COUPLING_MODE = 9, +#endif /* doxy_skip */ + + /** The connection type of the sensor connected to the specified channel. */ + AI_CFG_CHAN_SENSOR_CONNECTION_TYPE = 10 }AiConfigItem; +#ifndef doxy_skip /** Use with ulAISetConfigDbl() and ulAIGetConfigDbl() to configure the AI subsystem. */ typedef enum { @@ -1754,15 +1838,19 @@ typedef enum AI_CFG_CHAN_OFFSET = 1001, /** The sensitivity of the sensor connected to the specified channel. */ - AI_CFG_CHAN_SENSOR_SENSIVITY = 1002 + AI_CFG_CHAN_SENSOR_SENSITIVITY = 1002 }AiConfigItemDbl; -/** Calibration date */ +#endif /* doxy_skip */ + typedef enum { - AI_CFG_CAL_DATE_STR = 2000 + /** Calibration date */ + AI_CFG_CAL_DATE_STR = 2000, + + /** The channel coefficients used for the configured sensor. */ + AI_CFG_CHAN_COEFS_STR = 2001 }AiConfigItemStr; -#endif /* doxy_skip */ /** Use with ulAOGetInfo() to obtain information about the analog output subsystem for the specified device * as an \p infoItem argument value. */ @@ -1808,6 +1896,13 @@ typedef enum AO_INFO_MAX_THROUGHPUT = 1002 }AoInfoItemDbl; +/** Use with ulAOSetConfig() and ulAOGetConfig() to configure the AO subsystem. */ +typedef enum +{ + /** The sync mode. Set with #AOutSyncMode. */ + AO_CFG_SYNC_MODE = 1 +}AoConfigItem; + /** Use with ulDIOGetInfo() to obtain information about the DIO subsystem for the specified device as an \p infoItem argument value. */ typedef enum { @@ -1869,7 +1964,7 @@ typedef enum DIO_INFO_MAX_THROUGHPUT = 1002 }DioInfoItemDbl; -/** Use with ulDIOGetConfig() as a \p configItem argument value to get the current configuration +/** Use with ulDIOGetConfig() and/or ulDIOSetConfig() as a \p configItem argument value to get the current configuration * of the specified digital port on the specified device. */ typedef enum @@ -1880,6 +1975,19 @@ typedef enum * bits 0 and 1 are output, and any other bits in the port are input. */ DIO_CFG_PORT_DIRECTION_MASK = 1, + + /** Writes a value to the specified port number. This allows writing a value when the port is in + * input mode so that when the port is switched to output mode, the state of the bits is known. + */ + DIO_CFG_PORT_INITIAL_OUTPUT_VAL = 2, + + /** Returns or writes the low-pass filter setting. A 0 indicates that the filter is disabled for + * the corresponding bit. + */ + DIO_CFG_PORT_ISO_FILTER_MASK = 3, + + /** Returns the port logic. A 0 indicates non-invert mode, and a non-zero value indicates output inverted. */ + DIO_CFG_PORT_LOGIC = 4 }DioConfigItem; /** Use with ulCtrGetInfo() to obtain information about the counter subsystem for the specified device @@ -2196,7 +2304,7 @@ UlError ulAInScanWait(DaqDeviceHandle daqDeviceHandle, WaitType waitType, long l UlError ulAInLoadQueue(DaqDeviceHandle daqDeviceHandle, AiQueueElement queue[], unsigned int numElements); /** - * Configures the trigger parameters that will be used when #ulAInScan() is called with the ::SO_RETRIGGER or ::SO_EXTTRIGGER ScanOption + * Configures the trigger parameters that will be used when #ulAInScan() is called with the ::SO_RETRIGGER or ::SO_EXTTRIGGER ScanOption. * @param daqDeviceHandle the handle to the DAQ device * @param type type of trigger * @param trigChan the trigger channel; ignored if \p type is set to ::TRIG_POS_EDGE, ::TRIG_NEG_EDGE, ::TRIG_HIGH, ::TRIG_LOW, ::GATE_HIGH, @@ -2211,6 +2319,30 @@ UlError ulAInLoadQueue(DaqDeviceHandle daqDeviceHandle, AiQueueElement queue[], */ UlError ulAInSetTrigger(DaqDeviceHandle daqDeviceHandle, TriggerType type, int trigChan, double level, double variance, unsigned int retriggerSampleCount); +/** + * Returns a temperature value read from an A/D channel. + * @param daqDeviceHandle the handle to the DAQ device + * @param channel A/D channel number + * @param scale temperature unit + * @param flags reserved for future use + * @param data temperature value; if an ::ERR_OPEN_CONNECTION error occurs, the value returned will be -9999. + * @return The UL error code. + */ +UlError ulTIn(DaqDeviceHandle daqDeviceHandle, int channel, TempScale scale, TInFlag flags, double* data); + +/** + * Scans a range of A/D temperature channels, and stores the samples in an array. + * @param daqDeviceHandle the handle to the DAQ device + * @param lowChan first A/D channel in the scan + * @param highChan last A/D channel in the scan + * @param scale temperature unit + * @param flags reserved for future use + * @param data[] a pointer to an array that stores the data; if an ::ERR_OPEN_CONNECTION error occurs, + * the value will be -9999 in the array element associated with the channel causing the error. + * @return The UL error code. + */ +UlError ulTInArray(DaqDeviceHandle daqDeviceHandle, int lowChan, int highChan, TempScale scale, TInArrayFlag flags, double data[]); + /** @}*/ /** @@ -2230,6 +2362,18 @@ UlError ulAInSetTrigger(DaqDeviceHandle daqDeviceHandle, TriggerType type, int t */ UlError ulAOut(DaqDeviceHandle daqDeviceHandle, int channel, Range range, AOutFlag flags, double data); +/** + * Writes values to a range of D/A channels. + * @param daqDeviceHandle the handle to the DAQ device + * @param lowChan first D/A channel + * @param highChan last D/A channel + * @param range D/A ranges + * @param flags bit mask that specifies whether to scale and/or calibrate the data + * @param data[] a pointer to an array that stores the data + * @return The UL error code. + */ +UlError ulAOutArray(DaqDeviceHandle daqDeviceHandle, int lowChan, int highChan, Range range[], AOutArrayFlag flags, double data[]); + /** * Writes values to a range of D/A channels. * @param daqDeviceHandle the handle to the DAQ device @@ -2333,6 +2477,26 @@ UlError ulDIn(DaqDeviceHandle daqDeviceHandle, DigitalPortType portType, unsigne */ UlError ulDOut(DaqDeviceHandle daqDeviceHandle, DigitalPortType portType, unsigned long long data); +/** + * Reads the specified digital ports, and Returns the data in an array. + * @param daqDeviceHandle the handle to the DAQ device + * @param lowPort the first port in the scan + * @param highPort the last port in the scan + * @param data input data array + * @return The UL error code. + */ +UlError ulDInArray(DaqDeviceHandle daqDeviceHandle, DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + +/** + * Sets the values of the specified digital ports. + * @param daqDeviceHandle the handle to the DAQ device + * @param lowPort the first port in the scan + * @param highPort the last port in the scan + * @param data output data array + * @return The UL error code. + */ +UlError ulDOutArray(DaqDeviceHandle daqDeviceHandle, DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + /** * Returns the value of a digital bit. * @param daqDeviceHandle the handle to the DAQ device @@ -2843,7 +3007,7 @@ UlError ulGetInfoStr(UlInfoItemStr infoItem, unsigned int index, char* infoStr, * Use with UlConfigItem to change device configuration options at runtime. * @param configItem the type of information to write to the device * @param index either ignored or an index into the \p configValue - * @param configValue the specified configuration value is returned to this variable + * @param configValue the value to set the specified configuration item to * @return The UL error code. * */ UlError ulSetConfig(UlConfigItem configItem, unsigned int index, long long configValue); @@ -2870,7 +3034,7 @@ UlError ulGetConfig(UlConfigItem configItem, unsigned int index, long long* conf UlError ulDevGetInfo(DaqDeviceHandle daqDeviceHandle, DevInfoItem infoItem, unsigned int index, long long* infoValue); /** - * Use with #DevConfigItemStr to retrieve configuration information as a null-terminated string. + * Use with #DevConfigItemStr to retrieve the current configuration as a null-terminated string. * @param daqDeviceHandle the handle to the DAQ device * @param configItem the configuration item to retrieve from the device * @param index specifies the version type to return as an index into \p configItem (::DevVersionType) @@ -2915,7 +3079,7 @@ UlError ulAIGetInfoDbl(DaqDeviceHandle daqDeviceHandle, AiInfoItemDbl infoItem, * @param daqDeviceHandle the handle to the DAQ device * @param configItem the configuration item to set * @param index either ignored or an index into the \p configValue - * @param configValue the specified configuration value is returned to this variable + * @param configValue the value to set the specified configuration item to * @return The UL error code. */ UlError ulAISetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, unsigned int index, long long configValue); @@ -2937,7 +3101,7 @@ UlError ulAIGetConfig(DaqDeviceHandle daqDeviceHandle, AiConfigItem configItem, * @param daqDeviceHandle the handle to the DAQ device * @param configItem the configuration item to set * @param index either ignored or an index into the \p configValue - * @param configValue the specified configuration value is returned to this variable + * @param configValue the value to set the specified configuration item to * @return The UL error code. */ UlError ulAISetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl configItem, unsigned int index, double configValue); @@ -2953,6 +3117,8 @@ UlError ulAISetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl config */ UlError ulAIGetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl configItem, unsigned int index, double* configValue); +#endif /* doxy_skip */ + /** * \ingroup AnalogInput * Use with #DevConfigItemStr to retrieve configuration information as a null-terminated string. @@ -2964,7 +3130,6 @@ UlError ulAIGetConfigDbl(DaqDeviceHandle daqDeviceHandle, AiConfigItemDbl config * @return The UL error code. */ UlError ulAIGetConfigStr(DaqDeviceHandle daqDeviceHandle, AiConfigItemStr configItem, unsigned int index, char* configStr, unsigned int* maxConfigLen); -#endif /* doxy_skip */ /** * \ingroup AnalogOutput @@ -2988,6 +3153,28 @@ UlError ulAOGetInfo(DaqDeviceHandle daqDeviceHandle, AoInfoItem infoItem, unsign */ UlError ulAOGetInfoDbl(DaqDeviceHandle daqDeviceHandle, AoInfoItemDbl infoItem, unsigned int index, double* infoValue); +/** + * \ingroup AnalogOutput + * Use with #AoConfigItem to set configuration options at runtime. + * @param daqDeviceHandle the handle to the DAQ device + * @param configItem the configuration item to set + * @param index either ignored or an index into the \p configValue + * @param configValue the value to set the specified configuration item to + * @return The UL error code. + */ +UlError ulAOSetConfig(DaqDeviceHandle daqDeviceHandle, AoConfigItem configItem, unsigned int index, long long configValue); + +/** + * \ingroup AnalogOutput + * Use with #AoConfigItem to retrieve configuration options set for a device. + * @param daqDeviceHandle the handle to the DAQ device + * @param configItem the configuration item to retrieve + * @param index either ignored or an index into the \p configValue + * @param configValue the specified configuration value is returned to this variable + * @return The UL error code. + */ +UlError ulAOGetConfig(DaqDeviceHandle daqDeviceHandle, AoConfigItem configItem, unsigned int index, long long* configValue); + /** * \ingroup DigitalIO * Use with #DioInfoItem to retrieve information about the DIO subsystem. @@ -3017,6 +3204,17 @@ UlError ulDIOGetInfoDbl(DaqDeviceHandle daqDeviceHandle, DioInfoItemDbl infoItem * @param daqDeviceHandle the handle to the DAQ device * @param configItem the configuration item to retrieve * @param index the port index + * @param configValue the value to set the specified configuration item to + * @return The UL error code. + */ +UlError ulDIOSetConfig(DaqDeviceHandle daqDeviceHandle, DioConfigItem configItem, unsigned int index, long long configValue); + +/** + * \ingroup DigitalIO + * Use with #DioConfigItem to retrieve the current configuration about the DIO subsystem. + * @param daqDeviceHandle the handle to the DAQ device + * @param configItem the configuration item to retrieve + * @param index the port index * @param configValue the specified configuration value is returned to this variable * @return The UL error code. */ @@ -3121,6 +3319,16 @@ UlError ulDaqOGetInfoDbl(DaqDeviceHandle daqDeviceHandle, DaqOInfoItemDbl infoIt UlError ulMemGetInfo(DaqDeviceHandle daqDeviceHandle, MemRegion memRegion, MemDescriptor* memDescriptor); +#ifndef doxy_skip +/** + * Create a device object within the Universal Library for the DAQ device specified by the descriptor. This function intended to be + * used by languages that don't have capability to pass structures by value. i.e LabVIEW, VB, ... + * @param daqDevDescriptor DaqDeviceDescriptor struct containing fields that describe the device + * @return The device handle. + */ +DaqDeviceHandle ulCreateDaqDevicePtr(DaqDeviceDescriptor* daqDevDescriptor); + +#endif /* doxy_skip */ /** \mainpage *

Introduction

@@ -3211,6 +3419,7 @@ ulDBitIn(). ulDInScanStop(). DOutConfigures a port for output and writes a specified value. Demonstrates the use of ulDConfigPort() and ulDOut(). DOutScanConfigures the port direction and outputs digital data. Demonstrates the use of ulDConfigPort(), ulDOutScan(), and ulDOutScanStop(). +TInReads a temperature input channel. Demonstrates the use of ulTIn(). TmrPulseOutGenerates an output pulse at a specified duty cycle and frequency. Demonstrates the use of ulTmrPulseOutStart() and ulTmrPulseOutStop().
diff --git a/src/usb/Usb1208fsPlus.cpp b/src/usb/Usb1208fsPlus.cpp index a7dd7b0..7eb967a 100644 --- a/src/usb/Usb1208fsPlus.cpp +++ b/src/usb/Usb1208fsPlus.cpp @@ -13,7 +13,7 @@ namespace ul { -Usb1208fs_Plus::Usb1208fs_Plus(DaqDeviceDescriptor daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) +Usb1208fs_Plus::Usb1208fs_Plus(const DaqDeviceDescriptor& daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) { FnLog log("Usb1208fs_Plus::Usb1208fs_Plus"); diff --git a/src/usb/Usb1208fsPlus.h b/src/usb/Usb1208fsPlus.h index c373126..d38dd8d 100644 --- a/src/usb/Usb1208fsPlus.h +++ b/src/usb/Usb1208fsPlus.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb1208fs_Plus: public UsbDaqDevice { public: - Usb1208fs_Plus(DaqDeviceDescriptor daqDeviceDescriptor); + Usb1208fs_Plus(const DaqDeviceDescriptor& daqDeviceDescriptor); virtual ~Usb1208fs_Plus(); }; diff --git a/src/usb/Usb1208hs.cpp b/src/usb/Usb1208hs.cpp index 6f9ff8a..70b315c 100644 --- a/src/usb/Usb1208hs.cpp +++ b/src/usb/Usb1208hs.cpp @@ -5,7 +5,6 @@ */ #include "Usb1208hs.h" -#include "./../DaqDeviceId.h" #include "./ai/AiUsb1208hs.h" #include "./ao/AoUsb1208hs.h" #include "./dio/DioUsb1208hs.h" @@ -15,7 +14,7 @@ namespace ul { -Usb1208hs::Usb1208hs(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +Usb1208hs::Usb1208hs(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/Usb1208hs.h b/src/usb/Usb1208hs.h index 01c3716..6180811 100644 --- a/src/usb/Usb1208hs.h +++ b/src/usb/Usb1208hs.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb1208hs: public UsbFpgaDevice { public: - Usb1208hs(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + Usb1208hs(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~Usb1208hs(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/Usb1608fsPlus.cpp b/src/usb/Usb1608fsPlus.cpp index 7fa9efa..c2141f4 100644 --- a/src/usb/Usb1608fsPlus.cpp +++ b/src/usb/Usb1608fsPlus.cpp @@ -13,7 +13,7 @@ namespace ul { -Usb1608fs_Plus::Usb1608fs_Plus(DaqDeviceDescriptor daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) +Usb1608fs_Plus::Usb1608fs_Plus(const DaqDeviceDescriptor& daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) { FnLog log("Usb1608fs_Plus::Usb1608fs_Plus"); diff --git a/src/usb/Usb1608fsPlus.h b/src/usb/Usb1608fsPlus.h index dc5cf9a..d9919cc 100644 --- a/src/usb/Usb1608fsPlus.h +++ b/src/usb/Usb1608fsPlus.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb1608fs_Plus : public UsbDaqDevice { public: - Usb1608fs_Plus(DaqDeviceDescriptor daqDeviceDescriptor); + Usb1608fs_Plus(const DaqDeviceDescriptor& daqDeviceDescriptor); virtual ~Usb1608fs_Plus(); }; diff --git a/src/usb/Usb1608g.cpp b/src/usb/Usb1608g.cpp index f40a228..240288a 100644 --- a/src/usb/Usb1608g.cpp +++ b/src/usb/Usb1608g.cpp @@ -5,7 +5,6 @@ */ #include "Usb1608g.h" -#include "./../DaqDeviceId.h" #include "./ai/AiUsb1608g.h" #include "./ao/AoUsb1608g.h" #include "./dio/DioUsb1608g.h" @@ -15,7 +14,7 @@ namespace ul { -Usb1608g::Usb1608g(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +Usb1608g::Usb1608g(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/Usb1608g.h b/src/usb/Usb1608g.h index f45e213..175c1c2 100644 --- a/src/usb/Usb1608g.h +++ b/src/usb/Usb1608g.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb1608g: public UsbFpgaDevice { public: - Usb1608g(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + Usb1608g(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~Usb1608g(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/Usb1808.cpp b/src/usb/Usb1808.cpp index 82d64d0..b28c8f9 100644 --- a/src/usb/Usb1808.cpp +++ b/src/usb/Usb1808.cpp @@ -15,7 +15,7 @@ namespace ul { -Usb1808::Usb1808(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +Usb1808::Usb1808(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/Usb1808.h b/src/usb/Usb1808.h index e897c6e..9ef2dbc 100644 --- a/src/usb/Usb1808.h +++ b/src/usb/Usb1808.h @@ -16,7 +16,7 @@ namespace ul class UL_LOCAL Usb1808: public UsbFpgaDevice { public: - Usb1808(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + Usb1808(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~Usb1808(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/Usb20x.cpp b/src/usb/Usb20x.cpp index 2d3ccdd..9e07da9 100644 --- a/src/usb/Usb20x.cpp +++ b/src/usb/Usb20x.cpp @@ -5,7 +5,6 @@ */ #include "Usb20x.h" -#include "./../DaqDeviceId.h" #include "./ai/AiUsb20x.h" #include "./ao/AoUsb20x.h" #include "./dio/DioUsb1608g.h" @@ -14,7 +13,7 @@ namespace ul { -Usb20x::Usb20x(DaqDeviceDescriptor daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) +Usb20x::Usb20x(const DaqDeviceDescriptor& daqDeviceDescriptor) : UsbDaqDevice(daqDeviceDescriptor) { setCmdValue(CMD_FLASH_LED_KEY, 0x41); setCmdValue(CMD_RESET_KEY, 0x42); diff --git a/src/usb/Usb20x.h b/src/usb/Usb20x.h index bb868ee..8237b3c 100644 --- a/src/usb/Usb20x.h +++ b/src/usb/Usb20x.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb20x: public UsbDaqDevice { public: - Usb20x(DaqDeviceDescriptor daqDeviceDescriptor); + Usb20x(const DaqDeviceDescriptor& daqDeviceDescriptor); virtual ~Usb20x(); }; diff --git a/src/usb/Usb26xx.cpp b/src/usb/Usb26xx.cpp index a0619ed..3112e3d 100644 --- a/src/usb/Usb26xx.cpp +++ b/src/usb/Usb26xx.cpp @@ -5,7 +5,6 @@ */ #include "Usb26xx.h" -#include "./../DaqDeviceId.h" #include "./ai/AiUsb26xx.h" #include "./ao/AoUsb26xx.h" #include "./dio/DioUsb26xx.h" @@ -15,7 +14,7 @@ namespace ul { -Usb26xx::Usb26xx(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +Usb26xx::Usb26xx(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/Usb26xx.h b/src/usb/Usb26xx.h index 61515e1..ca54c14 100644 --- a/src/usb/Usb26xx.h +++ b/src/usb/Usb26xx.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL Usb26xx: public UsbFpgaDevice { public: - Usb26xx(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + Usb26xx(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~Usb26xx(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/UsbCtrx.cpp b/src/usb/UsbCtrx.cpp index 3fe5997..e519799 100644 --- a/src/usb/UsbCtrx.cpp +++ b/src/usb/UsbCtrx.cpp @@ -12,7 +12,7 @@ namespace ul { -UsbCtrx::UsbCtrx(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +UsbCtrx::UsbCtrx(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/UsbCtrx.h b/src/usb/UsbCtrx.h index a6ec336..6c9671f 100644 --- a/src/usb/UsbCtrx.h +++ b/src/usb/UsbCtrx.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL UsbCtrx: public UsbFpgaDevice { public: - UsbCtrx(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + UsbCtrx(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~UsbCtrx(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/UsbDaqDevice.cpp b/src/usb/UsbDaqDevice.cpp index ab52c42..d74eb59 100644 --- a/src/usb/UsbDaqDevice.cpp +++ b/src/usb/UsbDaqDevice.cpp @@ -10,11 +10,14 @@ #include "UsbDaqDevice.h" #include "../DaqDeviceManager.h" -#include "../DaqDeviceId.h" #include "../utility/UlLock.h" #include "UsbScanTransferIn.h" #include "UsbScanTransferOut.h" +#if LIBUSBX_API_VERSION < 0x01000102 +#error libusb version 1.0.16 or later is required to compile this package. +#endif + #define NO_PERMISSION_STR "NO PERMISSION" namespace ul @@ -31,13 +34,14 @@ bool UsbDaqDevice::mTerminateUsbEventThread = false; pid_t UsbDaqDevice::mUsbEventHandlerThreadId = 0; int UsbDaqDevice::mUsbEventHandlerThreadNiceValue = 0; -UsbDaqDevice::UsbDaqDevice(DaqDeviceDescriptor daqDeviceDescriptor) : DaqDevice(daqDeviceDescriptor) +UsbDaqDevice::UsbDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor) : DaqDevice(daqDeviceDescriptor) { FnLog log("UsbDaqDevice::UsbDaqDevice"); mDevHandle = NULL; mConnected = false; + mScanDoneMask = 0; mOverrunBitMask = 0; mUnderrunBitMask = 0; memset(mScanRunningMask, 0, sizeof(mScanRunningMask)); @@ -49,6 +53,8 @@ UsbDaqDevice::UsbDaqDevice(DaqDeviceDescriptor daqDeviceDescriptor) : DaqDevice( mScanTransferIn = new UsbScanTransferIn(*this); mScanTransferOut = new UsbScanTransferOut(*this); + mMultiCmdMem = false; + setCmdValue(CMD_FLASH_LED_KEY, 0x40); setCmdValue(CMD_RESET_KEY, 0x41); setCmdValue(CMD_STATUS_KEY, 0x44); @@ -140,8 +146,10 @@ std::vector UsbDaqDevice::findDaqDevices() daqDevDescriptor.productId = desc.idProduct; daqDevDescriptor.devInterface = USB_IFC; std::string productName = DaqDeviceManager::getDeviceName(desc.idProduct); - strcpy(daqDevDescriptor.productName, productName.c_str()); - strcpy(daqDevDescriptor.devString, productName.c_str()); + + strncpy(daqDevDescriptor.productName, productName.c_str(), sizeof(daqDevDescriptor.productName) - 1); + strncpy(daqDevDescriptor.devString, productName.c_str(), sizeof(daqDevDescriptor.devString) - 1); + readSerialNumber(dev, desc, daqDevDescriptor.uniqueId); UL_LOG("-----------------------"); @@ -1009,20 +1017,25 @@ bool UsbDaqDevice::hasMultiCmdMem() const void UsbDaqDevice::clearFifo(unsigned char epAddr) const { - unsigned char* buffer = new unsigned char [getBulkEndpointMaxPacketSize(epAddr)]; + int maxPacketSize = getBulkEndpointMaxPacketSize(epAddr); - int transferred = 0; - int ret = ERR_NO_ERROR; - - do + if(maxPacketSize > 0) { - ret = syncBulkTransfer(epAddr, buffer, getBulkEndpointMaxPacketSize(epAddr), &transferred, 1); + unsigned char* buffer = new unsigned char [getBulkEndpointMaxPacketSize(epAddr)]; + + int transferred = 0; + int ret = ERR_NO_ERROR; + + do + { + ret = syncBulkTransfer(epAddr, buffer, getBulkEndpointMaxPacketSize(epAddr), &transferred, 1); + } + while(ret == ERR_NO_ERROR); + + delete[] buffer; } - while(ret == ERR_NO_ERROR); - - delete[] buffer; - - + else + std::cout << "*** invalid endpoint" << std::endl; } int UsbDaqDevice::getBulkEndpointMaxPacketSize(int epAddr) const diff --git a/src/usb/UsbDaqDevice.h b/src/usb/UsbDaqDevice.h index 7067fc9..007c7bc 100644 --- a/src/usb/UsbDaqDevice.h +++ b/src/usb/UsbDaqDevice.h @@ -15,7 +15,6 @@ #include "../uldaq.h" #include "../DaqDevice.h" #include "../UlException.h" -#include "../DaqDeviceId.h" #include "../utility/SuspendMonitor.h" namespace ul @@ -35,7 +34,7 @@ public: enum {MAX_CMD_READ_TRANSFER = 256, MAX_CMD_WRITE_TRANSFER = 256}; public: - UsbDaqDevice(DaqDeviceDescriptor daqDeviceDescriptor); + UsbDaqDevice(const DaqDeviceDescriptor& daqDeviceDescriptor); virtual ~UsbDaqDevice(); static std::vector findDaqDevices(); diff --git a/src/usb/UsbDio32hs.cpp b/src/usb/UsbDio32hs.cpp index 71e2e59..adf216c 100644 --- a/src/usb/UsbDio32hs.cpp +++ b/src/usb/UsbDio32hs.cpp @@ -9,7 +9,7 @@ namespace ul { -UsbDio32hs::UsbDio32hs(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) +UsbDio32hs::UsbDio32hs(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbFpgaDevice(daqDeviceDescriptor, fpgaFileName) { setCmdValue(CMD_STATUS_KEY, 0x40); setCmdValue(CMD_FLASH_LED_KEY, 0x41); diff --git a/src/usb/UsbDio32hs.h b/src/usb/UsbDio32hs.h index 51a9c8e..1f915ea 100644 --- a/src/usb/UsbDio32hs.h +++ b/src/usb/UsbDio32hs.h @@ -15,7 +15,7 @@ namespace ul class UL_LOCAL UsbDio32hs: public UsbFpgaDevice { public: - UsbDio32hs(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + UsbDio32hs(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~UsbDio32hs(); virtual void setupTrigger(FunctionType functionType, ScanOption options) const; diff --git a/src/usb/UsbFpgaDevice.cpp b/src/usb/UsbFpgaDevice.cpp index d469ba2..fd5eb03 100644 --- a/src/usb/UsbFpgaDevice.cpp +++ b/src/usb/UsbFpgaDevice.cpp @@ -14,7 +14,6 @@ #include "UsbFpgaDevice.h" -#include "../DaqDeviceId.h" #include "../utility/UlLock.h" #define FPGA_FILES_PATH "/etc/uldaq/fpga/" @@ -22,7 +21,7 @@ namespace ul { -UsbFpgaDevice::UsbFpgaDevice(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName) : UsbDaqDevice(daqDeviceDescriptor) +UsbFpgaDevice::UsbFpgaDevice(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName) : UsbDaqDevice(daqDeviceDescriptor) { mFpgaFileName = fpgaFileName; } @@ -123,57 +122,61 @@ void UsbFpgaDevice::loadFpga() const size = fpgaFileStream.tellg(); unsigned char* fpgaImage = new unsigned char[size]; - fpgaFileStream.seekg (0, std::ios::beg); - fpgaFileStream.read ((char*)fpgaImage, size); - fpgaFileStream.close(); - - // enter config mode - unsigned char unlockCode = 0xAD; - - try - { - unsigned long num_bytes = sizeof(unlockCode); - UsbDaqDevice::sendCmd(CMD_FPGA_CFG, 0, 0, &unlockCode, num_bytes); - - // transfer data - - int remaining = size; - unsigned char* ptr = fpgaImage; - do - { - if(remaining > 64) - num_bytes = 64; - else - num_bytes = remaining; - - UsbDaqDevice::sendCmd(CMD_FPGA_DATA, 0, 0, ptr, num_bytes); - - ptr += num_bytes; - remaining -= num_bytes; - - } while (remaining > 0); - - if(isSpartanFpga()) - { - unsigned char dummyData[2] = {0 , 0}; - - UsbDaqDevice::sendCmd(CMD_FPGA_DATA, 0, 0, dummyData, sizeof(dummyData)); - } - } - catch(UlException& e) - { - error = e.getError(); - } - catch(...) - { - error = ERR_UNHANDLED_EXCEPTION; - } - if(fpgaImage) + { + fpgaFileStream.seekg (0, std::ios::beg); + fpgaFileStream.read ((char*)fpgaImage, size); + fpgaFileStream.close(); + + // enter config mode + unsigned char unlockCode = 0xAD; + + try + { + unsigned long num_bytes = sizeof(unlockCode); + UsbDaqDevice::sendCmd(CMD_FPGA_CFG, 0, 0, &unlockCode, num_bytes); + + // transfer data + + int remaining = size; + unsigned char* ptr = fpgaImage; + do + { + if(remaining > 64) + num_bytes = 64; + else + num_bytes = remaining; + + UsbDaqDevice::sendCmd(CMD_FPGA_DATA, 0, 0, ptr, num_bytes); + + ptr += num_bytes; + remaining -= num_bytes; + + } while (remaining > 0); + + if(isSpartanFpga()) + { + unsigned char dummyData[2] = {0 , 0}; + + UsbDaqDevice::sendCmd(CMD_FPGA_DATA, 0, 0, dummyData, sizeof(dummyData)); + } + } + catch(UlException& e) + { + error = e.getError(); + } + catch(...) + { + error = ERR_UNHANDLED_EXCEPTION; + } + delete[] fpgaImage; - if(error) - throw UlException(error); + if(error) + throw UlException(error); + } + else + std::cout << "**** insufficient memory to load the fpga image" << std::endl; } else { diff --git a/src/usb/UsbFpgaDevice.h b/src/usb/UsbFpgaDevice.h index 7765720..d99cbcd 100644 --- a/src/usb/UsbFpgaDevice.h +++ b/src/usb/UsbFpgaDevice.h @@ -16,7 +16,7 @@ namespace ul class UL_LOCAL UsbFpgaDevice: public UsbDaqDevice { public: - UsbFpgaDevice(DaqDeviceDescriptor daqDeviceDescriptor, std::string fpgaFileName); + UsbFpgaDevice(const DaqDeviceDescriptor& daqDeviceDescriptor, std::string fpgaFileName); virtual ~UsbFpgaDevice(); virtual int sendCmd(uint8_t request, uint16_t wValue, uint16_t wIndex, unsigned char *buff, uint16_t buffLen, unsigned int timeout = 1000) const; diff --git a/src/usb/UsbScanTransferIn.cpp b/src/usb/UsbScanTransferIn.cpp index 4fd11bd..a6b744e 100644 --- a/src/usb/UsbScanTransferIn.cpp +++ b/src/usb/UsbScanTransferIn.cpp @@ -11,7 +11,6 @@ #include "UsbScanTransferIn.h" -#include "../DaqDeviceId.h" #include "../utility/UlLock.h" #define STAGE_RATE 0.010 @@ -215,12 +214,11 @@ void UsbScanTransferIn::stopTransfers() { FnLog log("UsbScanTransferIn::stopTransfers"); - UlLock lock(mStopXferMutex); - mResubmit = false; - usleep(1000); + UlLock lock(mStopXferMutex); + for(int i = 0; i < MAX_XFER_COUNT; i++) { if(mXfer[i].transfer) diff --git a/src/usb/UsbScanTransferOut.cpp b/src/usb/UsbScanTransferOut.cpp index bb4c88b..0bbc88d 100644 --- a/src/usb/UsbScanTransferOut.cpp +++ b/src/usb/UsbScanTransferOut.cpp @@ -93,7 +93,7 @@ void UsbScanTransferOut::initilizeTransfers(IoDevice* ioDevice, int endpointAddr if(err) { if(mNumXferPending) - stopTransfers(); + stopTransfers(false); throw(UlException(err)); } @@ -169,15 +169,16 @@ void LIBUSB_CALL UsbScanTransferOut::tarnsferCallback(libusb_transfer* transfer) This->mXferEvent.signal(); } -void UsbScanTransferOut::stopTransfers() +void UsbScanTransferOut::stopTransfers(bool delay) { FnLog log("UsbScanTransferOut::stopTransfers"); - UlLock lock(mStopXferMutex); - mResubmit = false; - usleep(1000); + if(delay) + usleep(1000); + + UlLock lock(mStopXferMutex); for(int i = 0; i < MAX_XFER_COUNT; i++) { diff --git a/src/usb/UsbScanTransferOut.h b/src/usb/UsbScanTransferOut.h index a28ec0a..0a50344 100644 --- a/src/usb/UsbScanTransferOut.h +++ b/src/usb/UsbScanTransferOut.h @@ -26,7 +26,7 @@ public: void initilizeTransfers(IoDevice* ioDevice, int endpointAddress, int stageSize); void initilizeOnDemandTransfer(IoDevice* ioDevice, int endpointAddress, int stageSize); - void stopTransfers(); + void stopTransfers(bool delay = true); inline XferState getXferState() const { return mXferState;} inline UlError getXferError() const { return mXferError;} diff --git a/src/usb/ai/AiUsb1208fsPlus.cpp b/src/usb/ai/AiUsb1208fsPlus.cpp index 0bf621c..f81e608 100644 --- a/src/usb/ai/AiUsb1208fsPlus.cpp +++ b/src/usb/ai/AiUsb1208fsPlus.cpp @@ -172,7 +172,7 @@ void AiUsb1208fs_Plus::aInConfig(int lowChan, int highChan, AiInputMode inputMod AiUsb1208fs_Plus::TAINSCAN_CFG AiUsb1208fs_Plus::scanConfig(int lowChan, int highChan, AiInputMode inputMode, unsigned int scanCount, double rate, ScanOption options) { - TAINSCAN_CFG scanCfg; + TAINSCAN_CFG scanCfg = {0, 0, 0 ,0 ,0} ; int chanCount = queueEnabled() ? queueLength() : highChan - lowChan + 1; diff --git a/src/usb/ai/AiUsb1208hs.cpp b/src/usb/ai/AiUsb1208hs.cpp index 4509dd0..77ffff7 100644 --- a/src/usb/ai/AiUsb1208hs.cpp +++ b/src/usb/ai/AiUsb1208hs.cpp @@ -124,7 +124,8 @@ double AiUsb1208hs::aInScan(int lowChan, int highChan, AiInputMode inputMode, Ra loadAInConfigs(inputMode, range, lowChan, highChan, queueEnabled()); //daqDev().clearFifo(epAddr); - //daqDev().clearHalt(epAddr); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_AINSCAN_CLEAR_FIFO); setScanInfo(FT_AI, chanCount, samplesPerChan, mAiInfo.getSampleSize(), mAiInfo.getResolution(), options, flags, calCoefs, customScales, data); diff --git a/src/usb/ai/AiUsb1608fsPlus.cpp b/src/usb/ai/AiUsb1608fsPlus.cpp index 64eb2b2..c0b6f06 100644 --- a/src/usb/ai/AiUsb1608fsPlus.cpp +++ b/src/usb/ai/AiUsb1608fsPlus.cpp @@ -91,7 +91,7 @@ double AiUsb1608fs_Plus::aIn(int channel, AiInputMode inputMode, Range range, AI double AiUsb1608fs_Plus::aInScan(int lowChan, int highChan, AiInputMode inputMode, Range range, int samplesPerChan, double rate, ScanOption options, AInScanFlag flags, double data[]) { int chanCount = queueEnabled() ? queueLength() : highChan - lowChan + 1; - long long totalCount = samplesPerChan * chanCount; + long long totalCount = (long long) samplesPerChan * chanCount; //If no i/o mode is specified and scan meets the requirements for burst i/o mode then enable burst i/o mode, if(!(options & (SO_SINGLEIO | SO_BLOCKIO | SO_BURSTIO | SO_CONTINUOUS)) && diff --git a/src/usb/ai/AiUsb1608fsPlus.h b/src/usb/ai/AiUsb1608fsPlus.h index a91cf4e..0ac8d53 100644 --- a/src/usb/ai/AiUsb1608fsPlus.h +++ b/src/usb/ai/AiUsb1608fsPlus.h @@ -1,7 +1,6 @@ /* * AiUsb1608fsPlus.h * - * Created on: Aug 29, 2015 * Author: Measurement Computing Corporation */ diff --git a/src/usb/ai/AiUsb1608g.cpp b/src/usb/ai/AiUsb1608g.cpp index 639d874..afadac5 100644 --- a/src/usb/ai/AiUsb1608g.cpp +++ b/src/usb/ai/AiUsb1608g.cpp @@ -133,7 +133,8 @@ double AiUsb1608g::aInScan(int lowChan, int highChan, AiInputMode inputMode, Ran loadAInConfigs(inputMode, range, lowChan, highChan, queueEnabled()); - //daqDev().clearHalt(epAddr); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_AINSCAN_CLEAR_FIFO); setScanInfo(FT_AI, chanCount, samplesPerChan, mAiInfo.getSampleSize(), mAiInfo.getResolution(), options, flags, calCoefs, customScales, data); diff --git a/src/usb/ai/AiUsb1808.cpp b/src/usb/ai/AiUsb1808.cpp index 5bc519e..b47a1cd 100644 --- a/src/usb/ai/AiUsb1808.cpp +++ b/src/usb/ai/AiUsb1808.cpp @@ -143,32 +143,37 @@ double AiUsb1808::aInScan(int lowChan, int highChan, AiInputMode inputMode, Rang { check_AInScan_Args(lowChan, highChan, inputMode, range, samplesPerChan, rate, options, flags, data); + double actualRate = 0; + DaqIUsb1808* daqIDev = dynamic_cast(mDaqDevice.daqIDevice()); - int numChans = (queueEnabled() ? queueLength() : highChan - lowChan + 1); - - DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numChans]; - - for(int i = 0; i < numChans; i++) + if(daqIDev) { - if(queueEnabled()) + int numChans = (queueEnabled() ? queueLength() : highChan - lowChan + 1); + + DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numChans]; + + for(int i = 0; i < numChans; i++) { - chanDescriptors[i].channel = mAQueue[i].channel; - chanDescriptors[i].type = (mAQueue[i].inputMode == AI_DIFFERENTIAL) ? DAQI_ANALOG_DIFF : DAQI_ANALOG_SE; - chanDescriptors[i].range = mAQueue[i].range; - } - else - { - chanDescriptors[i].channel = lowChan + i; - chanDescriptors[i].type = (inputMode == AI_DIFFERENTIAL) ? DAQI_ANALOG_DIFF : DAQI_ANALOG_SE; - chanDescriptors[i].range = range; + if(queueEnabled()) + { + chanDescriptors[i].channel = mAQueue[i].channel; + chanDescriptors[i].type = (mAQueue[i].inputMode == AI_DIFFERENTIAL) ? DAQI_ANALOG_DIFF : DAQI_ANALOG_SE; + chanDescriptors[i].range = mAQueue[i].range; + } + else + { + chanDescriptors[i].channel = lowChan + i; + chanDescriptors[i].type = (inputMode == AI_DIFFERENTIAL) ? DAQI_ANALOG_DIFF : DAQI_ANALOG_SE; + chanDescriptors[i].range = range; + } } + + actualRate = daqIDev->daqInScan(FT_AI, chanDescriptors, numChans, samplesPerChan, rate, options, (DaqInScanFlag) flags, data); + + delete [] chanDescriptors; } - double actualRate = daqIDev->daqInScan(FT_AI, chanDescriptors, numChans, samplesPerChan, rate, options, (DaqInScanFlag) flags, data); - - delete [] chanDescriptors; - return actualRate; } diff --git a/src/usb/ai/AiUsb20x.cpp b/src/usb/ai/AiUsb20x.cpp index efeef70..173f2a1 100644 --- a/src/usb/ai/AiUsb20x.cpp +++ b/src/usb/ai/AiUsb20x.cpp @@ -113,6 +113,8 @@ double AiUsb20x::aInScan(int lowChan, int highChan, AiInputMode inputMode, Range std::vector calCoefs = getScanCalCoefs(lowChan, highChan, inputMode, range, flags); std::vector customScales = getCustomScales(lowChan, highChan); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_AINSCAN_CLEAR_FIFO); setScanInfo(FT_AI, chanCount, samplesPerChan, mAiInfo.getSampleSize(), mAiInfo.getResolution(), options, flags, calCoefs, customScales, data); diff --git a/src/usb/ai/AiUsb26xx.cpp b/src/usb/ai/AiUsb26xx.cpp index 2a5fcac..3b04e11 100644 --- a/src/usb/ai/AiUsb26xx.cpp +++ b/src/usb/ai/AiUsb26xx.cpp @@ -141,7 +141,8 @@ double AiUsb26xx::aInScan(int lowChan, int highChan, AiInputMode inputMode, Rang loadAInConfigs(range, lowChan, highChan, queueEnabled()); //daqDev().clearFifo(epAddr); - //daqDev().clearHalt(epAddr); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_AINSCAN_CLEAR_FIFO); setScanInfo(FT_AI, chanCount, samplesPerChan, mAiInfo.getSampleSize(), mAiInfo.getResolution(), options, flags, calCoefs, customScales, data); diff --git a/src/usb/ai/AiUsbBase.cpp b/src/usb/ai/AiUsbBase.cpp index aa47a41..81f40f8 100644 --- a/src/usb/ai/AiUsbBase.cpp +++ b/src/usb/ai/AiUsbBase.cpp @@ -367,18 +367,22 @@ void AiUsbBase::readCalDate() time.tm_sec = calDateBuf[5]; time.tm_isdst = -1; - time_t cal_date_sec = mktime(&time); // seconds since unix epoch + // make sure the date is valid, mktime does not validate the range + if(time.tm_mon <= 11 && time.tm_mday <= 31 && time.tm_hour <= 23 && time.tm_min <= 59 && time.tm_sec <= 60) + { + time_t cal_date_sec = mktime(&time); // seconds since unix epoch - if(cal_date_sec != -1) // mktime returns -1 if cal date is invalid - mCalDate = cal_date_sec; + if(cal_date_sec != -1) // mktime returns -1 if cal date is invalid + mCalDate = cal_date_sec; - // convert seconds to string + // convert seconds to string - /*struct tm *timeinfo; - timeinfo = localtime(&cal_date_sec); - char b[100]; - strftime(b, 100, "%Y-%m-%d %H:%M:%S", timeinfo); - std::cout << b << std::endl;*/ + /*struct tm *timeinfo; + timeinfo = localtime(&cal_date_sec); + char b[100]; + strftime(b, 100, "%Y-%m-%d %H:%M:%S", timeinfo); + std::cout << b << std::endl;*/ + } } } } diff --git a/src/usb/ao/AoUsb1208fsPlus.cpp b/src/usb/ao/AoUsb1208fsPlus.cpp index d0e8c69..ba1caf7 100644 --- a/src/usb/ao/AoUsb1208fsPlus.cpp +++ b/src/usb/ao/AoUsb1208fsPlus.cpp @@ -14,6 +14,7 @@ AoUsb1208fs_Plus::AoUsb1208fs_Plus(const UsbDaqDevice& daqDevice, int numChans) double minRate = daqDev().getClockFreq() / UINT_MAX; mAoInfo.setAOutFlags(AOUT_FF_NOSCALEDATA | AOUT_FF_NOCALIBRATEDATA); + mAoInfo.setAOutArrayFlags(AOUTARRAY_FF_NOSCALEDATA | AOUTARRAY_FF_NOCALIBRATEDATA); mAoInfo.setAOutScanFlags(AOUTSCAN_FF_NOSCALEDATA | AOUTSCAN_FF_NOCALIBRATEDATA); mAoInfo.setScanOptions(SO_DEFAULTIO | SO_CONTINUOUS | SO_SINGLEIO |SO_BLOCKIO); diff --git a/src/usb/ao/AoUsb1208hs.cpp b/src/usb/ao/AoUsb1208hs.cpp index a1ac161..4ee3a60 100644 --- a/src/usb/ao/AoUsb1208hs.cpp +++ b/src/usb/ao/AoUsb1208hs.cpp @@ -15,6 +15,7 @@ AoUsb1208hs::AoUsb1208hs(const UsbDaqDevice& daqDevice, int numChans) : AoUsbBas double minRate = daqDev().getClockFreq() / UINT_MAX; mAoInfo.setAOutFlags(AOUT_FF_NOSCALEDATA | AOUT_FF_NOCALIBRATEDATA); + mAoInfo.setAOutArrayFlags(AOUTARRAY_FF_NOSCALEDATA | AOUTARRAY_FF_NOCALIBRATEDATA); mAoInfo.setAOutScanFlags(AOUTSCAN_FF_NOSCALEDATA | AOUTSCAN_FF_NOCALIBRATEDATA); mAoInfo.setScanOptions(SO_DEFAULTIO|SO_CONTINUOUS|SO_EXTTRIGGER|SO_EXTCLOCK|SO_SINGLEIO|SO_BLOCKIO|SO_RETRIGGER); diff --git a/src/usb/ao/AoUsb1808.cpp b/src/usb/ao/AoUsb1808.cpp index f6613aa..6cf56b3 100644 --- a/src/usb/ao/AoUsb1808.cpp +++ b/src/usb/ao/AoUsb1808.cpp @@ -7,7 +7,6 @@ #include "AoUsb1808.h" #include "./../daqo/DaqOUsb1808.h" #include "./../../DaqODevice.h" -#include "./../../DaqDeviceId.h" namespace ul { @@ -16,6 +15,7 @@ AoUsb1808::AoUsb1808(const UsbDaqDevice& daqDevice, int numChans) : AoUsbBase(da double minRate = daqDev().getClockFreq() / UINT_MAX; mAoInfo.setAOutFlags(AOUT_FF_NOSCALEDATA | AOUT_FF_NOCALIBRATEDATA); + mAoInfo.setAOutArrayFlags(AOUTARRAY_FF_NOSCALEDATA | AOUTARRAY_FF_NOCALIBRATEDATA); mAoInfo.setAOutScanFlags(AOUTSCAN_FF_NOSCALEDATA | AOUTSCAN_FF_NOCALIBRATEDATA); mAoInfo.setScanOptions(SO_DEFAULTIO | SO_CONTINUOUS | SO_EXTTRIGGER | SO_EXTCLOCK | SO_SINGLEIO | SO_BLOCKIO | SO_RETRIGGER); @@ -79,23 +79,28 @@ double AoUsb1808::aOutScan(int lowChan, int highChan, Range range, int samplesPe { check_AOutScan_Args(lowChan, highChan, range, samplesPerChan, rate, options, flags, data); + double actualRate = 0; + DaqOUsb1808* daqODev = dynamic_cast(mDaqDevice.daqODevice()); - int numChans = highChan - lowChan + 1; - - DaqOutChanDescriptor* chanDescriptors = new DaqOutChanDescriptor[numChans]; - - for(int i = 0; i < numChans; i++) + if(daqODev) { - chanDescriptors[i].type = DAQO_ANALOG; - chanDescriptors[i].channel = lowChan + i; - chanDescriptors[i].range = range; + int numChans = highChan - lowChan + 1; + + DaqOutChanDescriptor* chanDescriptors = new DaqOutChanDescriptor[numChans]; + + for(int i = 0; i < numChans; i++) + { + chanDescriptors[i].type = DAQO_ANALOG; + chanDescriptors[i].channel = lowChan + i; + chanDescriptors[i].range = range; + } + + actualRate = daqODev->daqOutScan(FT_AO, chanDescriptors, numChans, samplesPerChan, rate, options, (DaqOutScanFlag) flags, data); + + delete [] chanDescriptors; } - double actualRate = daqODev->daqOutScan(FT_AO, chanDescriptors, numChans, samplesPerChan, rate, options, (DaqOutScanFlag) flags, data); - - delete [] chanDescriptors; - return actualRate; } diff --git a/src/usb/ao/AoUsbBase.cpp b/src/usb/ao/AoUsbBase.cpp index a4f0f24..99527d4 100644 --- a/src/usb/ao/AoUsbBase.cpp +++ b/src/usb/ao/AoUsbBase.cpp @@ -192,7 +192,7 @@ int AoUsbBase::calcStageSize(int epAddr, double rate, int chanCount, int sampleC else { double aggRate = chanCount * rate * mAoInfo.getSampleSize(); // bytes per second - long long bufferBytesCount = sampleCount * mAoInfo.getSampleSize(); + long long bufferBytesCount = (long long) sampleCount * mAoInfo.getSampleSize(); double stageRate = daqDev().scanTranserOut()->getStageRate(); stageSize = (int)(aggRate * stageRate); @@ -315,18 +315,22 @@ void AoUsbBase::readCalDate() time.tm_sec = calDateBuf[5]; time.tm_isdst = -1; - time_t cal_date_sec = mktime(&time); // seconds since unix epoch + // make sure the date is valid, mktime does not validate the range + if(time.tm_mon <= 11 && time.tm_mday <= 31 && time.tm_hour <= 23 && time.tm_min <= 59 && time.tm_sec <= 60) + { + time_t cal_date_sec = mktime(&time); // seconds since unix epoch - if(cal_date_sec != -1) // mktime returns -1 if cal date is invalid - mCalDate = cal_date_sec; + if(cal_date_sec != -1) // mktime returns -1 if cal date is invalid + mCalDate = cal_date_sec; - // convert seconds to string + // convert seconds to string - /*struct tm *timeinfo; - timeinfo = localtime(&cal_date_sec); - char b[100]; - strftime(b, 100, "%Y-%m-%d %H:%M:%S", timeinfo); - std::cout << b << std::endl;*/ + /*struct tm *timeinfo; + timeinfo = localtime(&cal_date_sec); + char b[100]; + strftime(b, 100, "%Y-%m-%d %H:%M:%S", timeinfo); + std::cout << b << std::endl;*/ + } } } } diff --git a/src/usb/ao/AoUsbBase.h b/src/usb/ao/AoUsbBase.h index a671a0d..2ffdbbf 100644 --- a/src/usb/ao/AoUsbBase.h +++ b/src/usb/ao/AoUsbBase.h @@ -1,7 +1,6 @@ /* * AoUsbBase.h * - * Created on: Oct 26, 2017 * Author: Measurement Computing Corporation */ diff --git a/src/usb/ctr/CtrUsb1808.cpp b/src/usb/ctr/CtrUsb1808.cpp index 90075e8..4826c97 100644 --- a/src/usb/ctr/CtrUsb1808.cpp +++ b/src/usb/ctr/CtrUsb1808.cpp @@ -119,22 +119,27 @@ double CtrUsb1808::cInScan(int lowCtrNum, int highCtrNum, int samplesPerCounter, { check_CInScan_Args(lowCtrNum, highCtrNum, samplesPerCounter, rate, options, flags, data); + double actualRate = 0; + DaqIUsb1808* daqIDev = dynamic_cast(mDaqDevice.daqIDevice()); - int numCtrs = highCtrNum - lowCtrNum + 1; - - DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numCtrs]; - - for(int i = 0; i < numCtrs; i++) + if(daqIDev) { - chanDescriptors[i].channel = lowCtrNum + i; - chanDescriptors[i].type = DAQI_CTR32; + int numCtrs = highCtrNum - lowCtrNum + 1; + + DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numCtrs]; + + for(int i = 0; i < numCtrs; i++) + { + chanDescriptors[i].channel = lowCtrNum + i; + chanDescriptors[i].type = DAQI_CTR32; + } + + actualRate = daqIDev->daqInScan(FT_CTR, chanDescriptors, numCtrs, samplesPerCounter, rate, options, (DaqInScanFlag) flags, data); + + delete [] chanDescriptors; } - double actualRate = daqIDev->daqInScan(FT_CTR, chanDescriptors, numCtrs, samplesPerCounter, rate, options, (DaqInScanFlag) flags, data); - - delete [] chanDescriptors; - return actualRate; } diff --git a/src/usb/ctr/CtrUsbCtrx.cpp b/src/usb/ctr/CtrUsbCtrx.cpp index 5d1dfc1..06f0089 100644 --- a/src/usb/ctr/CtrUsbCtrx.cpp +++ b/src/usb/ctr/CtrUsbCtrx.cpp @@ -147,29 +147,34 @@ double CtrUsbCtrx::cInScan(int lowCtrNum, int highCtrNum, int samplesPerCounter, { check_CInScan_Args(lowCtrNum, highCtrNum, samplesPerCounter, rate, options, flags, data); + double actualRate = 0; + DaqIUsbCtrx* daqIDev = dynamic_cast(mDaqDevice.daqIDevice()); - int numCtrs = highCtrNum - lowCtrNum + 1; - - DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numCtrs]; - - DaqInChanType daqIChanType = DAQI_CTR16; - - if(flags == CINSCAN_FF_CTR32_BIT) - daqIChanType = DAQI_CTR32; - else if(flags == CINSCAN_FF_CTR64_BIT) - daqIChanType = (DaqInChanType) DAQI_CTR64_INTERNAL; - - for(int i = 0; i < numCtrs; i++) + if(daqIDev) { - chanDescriptors[i].channel = lowCtrNum + i; - chanDescriptors[i].type = daqIChanType; + int numCtrs = highCtrNum - lowCtrNum + 1; + + DaqInChanDescriptor* chanDescriptors = new DaqInChanDescriptor[numCtrs]; + + DaqInChanType daqIChanType = DAQI_CTR16; + + if(flags == CINSCAN_FF_CTR32_BIT) + daqIChanType = DAQI_CTR32; + else if(flags == CINSCAN_FF_CTR64_BIT) + daqIChanType = (DaqInChanType) DAQI_CTR64_INTERNAL; + + for(int i = 0; i < numCtrs; i++) + { + chanDescriptors[i].channel = lowCtrNum + i; + chanDescriptors[i].type = daqIChanType; + } + + actualRate = daqIDev->daqInScan(FT_CTR, chanDescriptors, numCtrs, samplesPerCounter, rate, options, (DaqInScanFlag) flags, data); + + delete [] chanDescriptors; } - double actualRate = daqIDev->daqInScan(FT_CTR, chanDescriptors, numCtrs, samplesPerCounter, rate, options, (DaqInScanFlag) flags, data); - - delete [] chanDescriptors; - return actualRate; } diff --git a/src/usb/ctr/CtrUsbCtrx.h b/src/usb/ctr/CtrUsbCtrx.h index 2f7f1a8..3f6f7b7 100644 --- a/src/usb/ctr/CtrUsbCtrx.h +++ b/src/usb/ctr/CtrUsbCtrx.h @@ -9,7 +9,6 @@ #define USB_CTR_CTRUSBCTRX_H_ #include "CtrUsbBase.h" -#include "./../../DaqDeviceId.h" namespace ul { diff --git a/src/usb/daqi/DaqIUsb1808.cpp b/src/usb/daqi/DaqIUsb1808.cpp index 4e69b12..43fd8ae 100644 --- a/src/usb/daqi/DaqIUsb1808.cpp +++ b/src/usb/daqi/DaqIUsb1808.cpp @@ -66,36 +66,41 @@ double DaqIUsb1808::daqInScan(FunctionType functionType, DaqInChanDescriptor cha AiUsb1808* aiDev = dynamic_cast(mDaqDevice.aiDevice()); - int sampleSize = 4; - int aiResolution = aiDev->getAiInfo().getResolution(); - int chanCount = numChans; - int stageSize = calcStageSize(epAddr, rate, chanCount, samplesPerChan, sampleSize); - - std::vector calCoefs = getScanCalCoefs(chanDescriptors, numChans, flags); - std::vector customScales = getCustomScales(chanDescriptors, numChans); - - daqDev().setupTrigger(functionType, options); - - loadScanConfigs(chanDescriptors, numChans); - - daqDev().sendCmd(CMD_SCAN_CLEARFIFO); - - setScanInfo(functionType, chanCount, samplesPerChan, sampleSize, aiResolution, options, flags, calCoefs, customScales, data); - - setScanConfig(functionType, chanCount, samplesPerChan, rate, options, flags); - - daqDev().scanTranserIn()->initilizeTransfers(this, epAddr, stageSize); - - try + if(aiDev) { - daqDev().sendCmd(CMD_IN_SCAN_START, 0, 0, (unsigned char*) &mScanConfig, sizeof(mScanConfig), 1000); + int sampleSize = 4; + int aiResolution = aiDev->getAiInfo().getResolution(); + int chanCount = numChans; + int stageSize = calcStageSize(epAddr, rate, chanCount, samplesPerChan, sampleSize); - setScanState(SS_RUNNING); - } - catch(UlException& e) - { - stopBackground(); - throw e; + std::vector calCoefs = getScanCalCoefs(chanDescriptors, numChans, flags); + std::vector customScales = getCustomScales(chanDescriptors, numChans); + + daqDev().setupTrigger(functionType, options); + + loadScanConfigs(chanDescriptors, numChans); + + daqDev().clearHalt(epAddr); + + daqDev().sendCmd(CMD_SCAN_CLEARFIFO); + + setScanInfo(functionType, chanCount, samplesPerChan, sampleSize, aiResolution, options, flags, calCoefs, customScales, data); + + setScanConfig(functionType, chanCount, samplesPerChan, rate, options, flags); + + daqDev().scanTranserIn()->initilizeTransfers(this, epAddr, stageSize); + + try + { + daqDev().sendCmd(CMD_IN_SCAN_START, 0, 0, (unsigned char*) &mScanConfig, sizeof(mScanConfig), 1000); + + setScanState(SS_RUNNING); + } + catch(UlException& e) + { + stopBackground(); + throw e; + } } return actualScanRate(); @@ -216,7 +221,9 @@ void DaqIUsb1808::loadScanConfigs(DaqInChanDescriptor chanDescriptors[], int num if(aiChanCount > 0) { const AiUsb1808* aiDev = dynamic_cast(mDaqDevice.aiDevice()); - aiDev->loadAInConfigs(aichanDescs, aiChanCount); + + if(aiDev) + aiDev->loadAInConfigs(aichanDescs, aiChanCount); } daqDev().sendCmd(CMD_IN_SCAN_CONFIG, 0, idx - 1, (unsigned char*) &scanQueue, sizeof(scanQueue)); @@ -234,21 +241,24 @@ std::vector DaqIUsb1808::getScanCalCoefs(DaqInChanDescriptor chanDescri const AiUsb1808* aiDev = dynamic_cast(mDaqDevice.aiDevice()); - for(int idx = 0; idx < numChans; idx++) + if(aiDev) { - if(chanDescriptors[idx].type == DAQI_ANALOG_SE || chanDescriptors[idx].type == DAQI_ANALOG_DIFF) + for(int idx = 0; idx < numChans; idx++) { - inputMode = chanDescriptors[idx].type == DAQI_ANALOG_SE ? AI_SINGLE_ENDED : AI_DIFFERENTIAL; + if(chanDescriptors[idx].type == DAQI_ANALOG_SE || chanDescriptors[idx].type == DAQI_ANALOG_DIFF) + { + inputMode = chanDescriptors[idx].type == DAQI_ANALOG_SE ? AI_SINGLE_ENDED : AI_DIFFERENTIAL; - calCoef = aiDev->getChanCalCoef(chanDescriptors[idx].channel, inputMode, chanDescriptors[idx].range, flags); - } - else - { - calCoef.slope = 1; - calCoef.offset = 0; - } + calCoef = aiDev->getChanCalCoef(chanDescriptors[idx].channel, inputMode, chanDescriptors[idx].range, flags); + } + else + { + calCoef.slope = 1; + calCoef.offset = 0; + } - calCoefs.push_back(calCoef); + calCoefs.push_back(calCoef); + } } return calCoefs; @@ -262,19 +272,22 @@ std::vector DaqIUsb1808::getCustomScales(DaqInChanDescriptor chanDe const AiUsb1808* aiDev = dynamic_cast(mDaqDevice.aiDevice()); - for(int idx = 0; idx < numChans; idx++) + if(aiDev) { - if(chanDescriptors[idx].type == DAQI_ANALOG_SE || chanDescriptors[idx].type == DAQI_ANALOG_DIFF) + for(int idx = 0; idx < numChans; idx++) { - customScale = aiDev->getChanCustomScale(chanDescriptors[idx].channel); - } - else - { - customScale.slope = 1; - customScale.offset = 0; - } + if(chanDescriptors[idx].type == DAQI_ANALOG_SE || chanDescriptors[idx].type == DAQI_ANALOG_DIFF) + { + customScale = aiDev->getChanCustomScale(chanDescriptors[idx].channel); + } + else + { + customScale.slope = 1; + customScale.offset = 0; + } - customScales.push_back(customScale); + customScales.push_back(customScale); + } } return customScales; diff --git a/src/usb/daqi/DaqIUsbBase.cpp b/src/usb/daqi/DaqIUsbBase.cpp index d52b9e7..7e14888 100644 --- a/src/usb/daqi/DaqIUsbBase.cpp +++ b/src/usb/daqi/DaqIUsbBase.cpp @@ -131,7 +131,7 @@ int DaqIUsbBase::calcStageSize(int epAddr, double rate, int chanCount, int sampl else { double aggRate = chanCount * rate * sampleSize; // bytes per second - long long bufferBytesCount = sampleCount * sampleSize; + long long bufferBytesCount = (long long) sampleCount * sampleSize; double stageRate = daqDev().scanTranserIn()->getStageRate(); stageSize = (int)(aggRate * stageRate); diff --git a/src/usb/daqi/DaqIUsbBase.h b/src/usb/daqi/DaqIUsbBase.h index ae966f9..fec565a 100644 --- a/src/usb/daqi/DaqIUsbBase.h +++ b/src/usb/daqi/DaqIUsbBase.h @@ -11,7 +11,6 @@ #include "../UsbDaqDevice.h" #include "../../DaqIDevice.h" #include "../UsbScanTransferIn.h" -#include "./../../DaqDeviceId.h" namespace ul { diff --git a/src/usb/daqi/DaqIUsbCtrx.cpp b/src/usb/daqi/DaqIUsbCtrx.cpp index 9db2dc3..f3bb2aa 100644 --- a/src/usb/daqi/DaqIUsbCtrx.cpp +++ b/src/usb/daqi/DaqIUsbCtrx.cpp @@ -87,6 +87,8 @@ double DaqIUsbCtrx::daqInScan(FunctionType functionType, DaqInChanDescriptor cha loadScanConfigs(chanDescriptors, numChans); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_SCAN_CLEARFIFO); setScanInfo(functionType, chanCount, samplesPerChan, sampleSize, analogResolution, options, flags, calCoefs, customScales, data); diff --git a/src/usb/daqo/DaqOUsb1808.cpp b/src/usb/daqo/DaqOUsb1808.cpp index 929ebf9..cbad6b6 100644 --- a/src/usb/daqo/DaqOUsb1808.cpp +++ b/src/usb/daqo/DaqOUsb1808.cpp @@ -66,37 +66,40 @@ double DaqOUsb1808::daqOutScan(FunctionType functionType, DaqOutChanDescriptor c AoUsb1808* aoDev = dynamic_cast(mDaqDevice.aoDevice()); - int sampleSize = 2; - int aoBitness = aoDev->getAoInfo().getResolution(); - int chanCount = numChans; - int stageSize = calcStageSize(epAddr, rate, chanCount, samplesPerChan, sampleSize); - - std::vector calCoefs = getScanCalCoefs(chanDescriptors, numChans, flags); - - daqDev().setupTrigger(functionType, options); - - loadScanConfigs(chanDescriptors, numChans); - - daqDev().sendCmd(CMD_SCAN_CLEARFIFO); - - setChanDescriptors(chanDescriptors, chanCount); - - setScanInfo(functionType, chanCount, samplesPerChan, sampleSize, aoBitness, options, flags, calCoefs, data); - - setScanConfig(functionType, chanCount, samplesPerChan, rate, options, flags); - - daqDev().scanTranserOut()->initilizeTransfers(this, epAddr, stageSize); - - try + if(aoDev) { - daqDev().sendCmd(CMD_OUT_SCAN_START, 0, 0, (unsigned char*) &mScanConfig, sizeof(mScanConfig), 1000); + int sampleSize = 2; + int aoBitness = aoDev->getAoInfo().getResolution(); + int chanCount = numChans; + int stageSize = calcStageSize(epAddr, rate, chanCount, samplesPerChan, sampleSize); - setScanState(SS_RUNNING); - } - catch(UlException& e) - { - stopBackground(); - throw e; + std::vector calCoefs = getScanCalCoefs(chanDescriptors, numChans, flags); + + daqDev().setupTrigger(functionType, options); + + loadScanConfigs(chanDescriptors, numChans); + + daqDev().sendCmd(CMD_SCAN_CLEARFIFO); + + setChanDescriptors(chanDescriptors, chanCount); + + setScanInfo(functionType, chanCount, samplesPerChan, sampleSize, aoBitness, options, flags, calCoefs, data); + + setScanConfig(functionType, chanCount, samplesPerChan, rate, options, flags); + + daqDev().scanTranserOut()->initilizeTransfers(this, epAddr, stageSize); + + try + { + daqDev().sendCmd(CMD_OUT_SCAN_START, 0, 0, (unsigned char*) &mScanConfig, sizeof(mScanConfig), 1000); + + setScanState(SS_RUNNING); + } + catch(UlException& e) + { + stopBackground(); + throw e; + } } return actualScanRate(); @@ -205,19 +208,22 @@ std::vector DaqOUsb1808::getScanCalCoefs(DaqOutChanDescriptor chanDescr const AoUsb1808* aoDev = dynamic_cast(mDaqDevice.aoDevice()); - for(int idx = 0; idx < numChans; idx++) + if(aoDev) { - if(chanDescriptors[idx].type == DAQO_ANALOG) + for(int idx = 0; idx < numChans; idx++) { - calCoef = aoDev->getChanCalCoef(chanDescriptors[idx].channel, flags); - } - else - { - calCoef.slope = 1; - calCoef.offset = 0; - } + if(chanDescriptors[idx].type == DAQO_ANALOG) + { + calCoef = aoDev->getChanCalCoef(chanDescriptors[idx].channel, flags); + } + else + { + calCoef.slope = 1; + calCoef.offset = 0; + } - calCoefs.push_back(calCoef); + calCoefs.push_back(calCoef); + } } return calCoefs; diff --git a/src/usb/daqo/DaqOUsbBase.cpp b/src/usb/daqo/DaqOUsbBase.cpp index ff2984b..a8a6cf2 100644 --- a/src/usb/daqo/DaqOUsbBase.cpp +++ b/src/usb/daqo/DaqOUsbBase.cpp @@ -130,7 +130,7 @@ int DaqOUsbBase::calcStageSize(int epAddr, double rate, int chanCount, int sampl else { double aggRate = chanCount * rate * sampleSize; // bytes per second - long long bufferBytesCount = sampleCount * sampleSize; + long long bufferBytesCount = (long long) sampleCount * sampleSize; double stageRate = daqDev().scanTranserOut()->getStageRate(); stageSize = (int)(aggRate * stageRate); diff --git a/src/usb/daqo/DaqOUsbBase.h b/src/usb/daqo/DaqOUsbBase.h index 209211b..fd4fb28 100644 --- a/src/usb/daqo/DaqOUsbBase.h +++ b/src/usb/daqo/DaqOUsbBase.h @@ -10,7 +10,6 @@ #include "../UsbDaqDevice.h" #include "../../DaqODevice.h" -#include "./../../DaqDeviceId.h" #include "../UsbScanTransferOut.h" namespace ul diff --git a/src/usb/dio/DioUsb1808.cpp b/src/usb/dio/DioUsb1808.cpp index dc8f6fd..105a0a7 100644 --- a/src/usb/dio/DioUsb1808.cpp +++ b/src/usb/dio/DioUsb1808.cpp @@ -7,7 +7,6 @@ #include "DioUsb1808.h" #include "../daqi/DaqIUsb1808.h" #include "../daqo/DaqOUsb1808.h" -#include "./../../DaqDeviceId.h" namespace ul { @@ -169,14 +168,19 @@ double DioUsb1808::dInScan(DigitalPortType lowPort, DigitalPortType highPort, in { check_DInScan_Args(lowPort, highPort, samplesPerPort, rate, options, flags, data); + double actualRate = 0; + DaqIUsb1808* daqIDev = dynamic_cast(mDaqDevice.daqIDevice()); - DaqInChanDescriptor chanDescriptors; + if(daqIDev) + { + DaqInChanDescriptor chanDescriptors; - chanDescriptors.channel = AUXPORT; - chanDescriptors.type = DAQI_DIGITAL; + chanDescriptors.channel = AUXPORT; + chanDescriptors.type = DAQI_DIGITAL; - double actualRate = daqIDev->daqInScan(FT_DI, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqInScanFlag) flags, data); + actualRate = daqIDev->daqInScan(FT_DI, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqInScanFlag) flags, data); + } return actualRate; } @@ -185,14 +189,19 @@ double DioUsb1808::dOutScan(DigitalPortType lowPort, DigitalPortType highPort, i { check_DOutScan_Args(lowPort, highPort, samplesPerPort, rate, options, flags, data); + double actualRate = 0; + DaqOUsb1808* daqODev = dynamic_cast(mDaqDevice.daqODevice()); - DaqOutChanDescriptor chanDescriptors; + if(daqODev) + { + DaqOutChanDescriptor chanDescriptors; - chanDescriptors.channel = AUXPORT; - chanDescriptors.type = DAQO_DIGITAL; + chanDescriptors.channel = AUXPORT; + chanDescriptors.type = DAQO_DIGITAL; - double actualRate = daqODev->daqOutScan(FT_DO, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqOutScanFlag) flags, data); + actualRate = daqODev->daqOutScan(FT_DO, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqOutScanFlag) flags, data); + } return actualRate; } diff --git a/src/usb/dio/DioUsbCtrx.cpp b/src/usb/dio/DioUsbCtrx.cpp index 23f90fe..7fdd904 100644 --- a/src/usb/dio/DioUsbCtrx.cpp +++ b/src/usb/dio/DioUsbCtrx.cpp @@ -7,7 +7,6 @@ #include "DioUsbCtrx.h" #include "../daqi/DaqIUsbCtrx.h" -#include "./../../DaqDeviceId.h" namespace ul { @@ -40,14 +39,19 @@ double DioUsbCtrx::dInScan(DigitalPortType lowPort, DigitalPortType highPort, in { check_DInScan_Args(lowPort, highPort, samplesPerPort, rate, options, flags, data); + double actualRate = 0; + DaqIUsbCtrx* daqIDev = dynamic_cast(mDaqDevice.daqIDevice()); - DaqInChanDescriptor chanDescriptors; + if(daqIDev) + { + DaqInChanDescriptor chanDescriptors; - chanDescriptors.channel = AUXPORT; - chanDescriptors.type = DAQI_DIGITAL; + chanDescriptors.channel = AUXPORT; + chanDescriptors.type = DAQI_DIGITAL; - double actualRate = daqIDev->daqInScan(FT_DI, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqInScanFlag) flags, data); + actualRate = daqIDev->daqInScan(FT_DI, &chanDescriptors, 1, samplesPerPort, rate, options, (DaqInScanFlag) flags, data); + } return actualRate; } diff --git a/src/usb/dio/DioUsbDio32hs.cpp b/src/usb/dio/DioUsbDio32hs.cpp index 16d6e3d..58a7048 100644 --- a/src/usb/dio/DioUsbDio32hs.cpp +++ b/src/usb/dio/DioUsbDio32hs.cpp @@ -132,6 +132,49 @@ void DioUsbDio32hs::dOut(DigitalPortType portType, unsigned long long data) daqDev().sendCmd(CMD_DLATCH, 0, portNum, (unsigned char*) &portValue, sizeof(portValue)); } +void DioUsbDio32hs::dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DInArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + unsigned short portValue[2] = {0, 0}; + + daqDev().queryCmd(CMD_DPORT, 0, 0, (unsigned char*) &portValue, sizeof(portValue)); + + int i = 0; + + for(unsigned int portNum = lowPortNum; portNum <=highPortNum; portNum++) + { + data[i] = Endian::le_ui16_to_cpu(portValue[portNum]); + i++; + } +} + +void DioUsbDio32hs::dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]) +{ + check_DOutArray_Args(lowPort, highPort, data); + + unsigned int lowPortNum = mDioInfo.getPortNum(lowPort); + unsigned int highPortNum = mDioInfo.getPortNum(highPort); + + unsigned short portValue[2] = {0, 0}; + + int i = 0; + for(unsigned int portNum = lowPortNum; portNum <= highPortNum; portNum++) + { + portValue[portNum] = Endian::cpu_to_le_ui16(data[i]); + i++; + } + + int ports = lowPortNum; + if (i > 1) + ports = 2; + + daqDev().sendCmd(CMD_DLATCH, 0, ports, (unsigned char*) &portValue, sizeof(portValue)); +} + unsigned long DioUsbDio32hs::readPortDirMask(unsigned int portNum) const { unsigned short dirMask; diff --git a/src/usb/dio/DioUsbDio32hs.h b/src/usb/dio/DioUsbDio32hs.h index bfe6ff7..c3dd03c 100644 --- a/src/usb/dio/DioUsbDio32hs.h +++ b/src/usb/dio/DioUsbDio32hs.h @@ -28,6 +28,9 @@ public: virtual unsigned long long dIn(DigitalPortType portType); virtual void dOut(DigitalPortType portType, unsigned long long data); + virtual void dInArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + virtual void dOutArray(DigitalPortType lowPort, DigitalPortType highPort, unsigned long long data[]); + virtual bool dBitIn(DigitalPortType portType, int bitNum); virtual void dBitOut(DigitalPortType portType, int bitNum, bool bitValue); diff --git a/src/usb/dio/UsbDInScan.cpp b/src/usb/dio/UsbDInScan.cpp index 27c6de1..e402726 100644 --- a/src/usb/dio/UsbDInScan.cpp +++ b/src/usb/dio/UsbDInScan.cpp @@ -49,6 +49,8 @@ double UsbDInScan::dInScan(DigitalPortType lowPort, DigitalPortType highPort, in daqDev().setupTrigger(FT_DI, options); + daqDev().clearHalt(epAddr); + daqDev().sendCmd(CMD_DIN_SCAN_CLEARFIFO); setScanInfo(FT_DI, portCount, samplesPerPort, sampleSize, resolution, options, flags, calCoefs, customScales, data); @@ -262,7 +264,7 @@ int UsbDInScan::calcStageSize(int epAddr, double rate, int chanCount, int sample else { double aggRate = chanCount * rate * sampleSize; // bytes per second - long long bufferBytesCount = sampleCount * sampleSize; + long long bufferBytesCount = (long long) sampleCount * sampleSize; double stageRate = daqDev().scanTranserIn()->getStageRate(); stageSize = (int)(aggRate * stageRate); diff --git a/src/usb/dio/UsbDOutScan.cpp b/src/usb/dio/UsbDOutScan.cpp index df551cd..90abfa6 100644 --- a/src/usb/dio/UsbDOutScan.cpp +++ b/src/usb/dio/UsbDOutScan.cpp @@ -281,7 +281,7 @@ int UsbDOutScan::calcStageSize(int epAddr, double rate, int chanCount, int sampl else { double aggRate = chanCount * rate * sampleSize; // bytes per second - long long bufferBytesCount = sampleCount * sampleSize; + long long bufferBytesCount = (long long) sampleCount * sampleSize; double stageRate = daqDev().scanTranserOut()->getStageRate(); stageSize = (int)(aggRate * stageRate); diff --git a/src/usb/tmr/TmrUsb1208hs.cpp b/src/usb/tmr/TmrUsb1208hs.cpp index e0611b3..3454519 100644 --- a/src/usb/tmr/TmrUsb1208hs.cpp +++ b/src/usb/tmr/TmrUsb1208hs.cpp @@ -114,6 +114,8 @@ void TmrUsb1208hs::tmrPulseOutStop(int timerNum) void TmrUsb1208hs::tmrPulseOutStatus(int timerNum, TmrStatus* status) { + check_TmrOutStatus_Args(timerNum); + unsigned char state = 0; daqDev().queryCmd(CMD_TMR_CTRL, 0, timerNum, &state, sizeof(state)); diff --git a/src/usb/tmr/TmrUsb1808.cpp b/src/usb/tmr/TmrUsb1808.cpp index e856b31..2d121a6 100644 --- a/src/usb/tmr/TmrUsb1808.cpp +++ b/src/usb/tmr/TmrUsb1808.cpp @@ -138,6 +138,8 @@ void TmrUsb1808::tmrPulseOutStop(int timerNum) void TmrUsb1808::tmrPulseOutStatus(int timerNum, TmrStatus* status) { + check_TmrOutStatus_Args(timerNum); + unsigned char state = 0; // Note: this command currently does not read the counter state correctly. FW/FPGA change required diff --git a/src/utility/Endian.h b/src/utility/Endian.h index e4ea019..9b376b9 100644 --- a/src/utility/Endian.h +++ b/src/utility/Endian.h @@ -119,6 +119,26 @@ public: return _tmp.b32; } + inline unsigned int le_ptr_to_cpu_ui32(const unsigned char x[4]) + { + union + { + unsigned char b8[4]; + unsigned int b32; + } _tmp; + + if(mLittleEndian) + _tmp.b32 = *((int*)x); + else + { + _tmp.b8[3] = x[0]; + _tmp.b8[2] = x[1]; + _tmp.b8[1] = x[2]; + _tmp.b8[0] = x[3]; + } + return _tmp.b32; + } + inline int be_ptr_to_cpu_i32(const unsigned char x[4]) { union @@ -138,7 +158,7 @@ public: } return _tmp.b32; } - inline unsigned int be_ptr_to_cpu_u32(const unsigned char x[4]) + inline unsigned int be_ptr_to_cpu_ui32(const unsigned char x[4]) { union { diff --git a/src/utility/ErrorMap.cpp b/src/utility/ErrorMap.cpp index 3be9045..6920ac0 100644 --- a/src/utility/ErrorMap.cpp +++ b/src/utility/ErrorMap.cpp @@ -95,8 +95,12 @@ ErrorMap::ErrorMap() mErrMap.insert(std::pair(ERR_MEM_ACCESS_DENIED, "Memory access denied")); // 79 mErrMap.insert(std::pair(ERR_DEV_UNAVAILABLE, "Device is not available at time of request")); // 80 mErrMap.insert(std::pair(ERR_BAD_RETRIG_TRIG_TYPE, "Re-trigger option is not supported for the specified trigger type")); // 81 - - + mErrMap.insert(std::pair(ERR_BAD_DEV_VER, "This function cannot be used with this version of this device")); // 82 + mErrMap.insert(std::pair(ERR_BAD_DIG_OPERATION, "This digital operation is not supported on the specified port")); // 83 + mErrMap.insert(std::pair(ERR_BAD_PORT_INDEX, "Invalid digital port index specified")); // 84 + mErrMap.insert(std::pair(ERR_OPEN_CONNECTION, "Temperature input has open connection")); // 85 + mErrMap.insert(std::pair(ERR_DEV_NOT_READY, "Device is not ready to send data")); // 86 + mErrMap.insert(std::pair(ERR_PACER_OVERRUN, "Pacer overrun, external clock rate too fast")); // 87 } std::string ErrorMap::getErrorMsg(int errNum) diff --git a/src/utility/EuScale.cpp b/src/utility/EuScale.cpp index 73fb850..4955f2d 100644 --- a/src/utility/EuScale.cpp +++ b/src/utility/EuScale.cpp @@ -176,6 +176,11 @@ void EuScale::getEuScaling(Range range, double &scale, double &offset) scale = 0.005; offset = 0.0; break; + + case MA0TO20: + scale = 20.0; + offset = 0.0; + break; } } } /* namespace ul */