repo time

Dependencies:   mbed MAX14720 MAX30205 USBDevice

Revision:
20:6d2af70c92ab
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/HspGuiSourceV301/HSPGui/HID.cs	Tue Apr 06 06:41:40 2021 +0000
@@ -0,0 +1,1788 @@
+using System;
+using System.Collections;
+using System.Threading;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using Microsoft.Win32.SafeHandles;
+
+//------------------------------------------------------------------------------------------
+// OS24EVK-59 split into HeartRateApp EXE and MAX30101 DLL.
+// Moved all MAX30101 DLL classes into namespace Maxim.MAX30101GUI
+// Moved all HeartRateApp GUI classes into namespace Maxim.MAX30101
+// OS24EVK-59 Create separate project that builds Maxim.MAX30101GUI DLL library
+
+// OS24EVK-59 moved class HID into namespace Maxim.MAX30101 instead of namespace HeartRateApp
+namespace Maxim.MAX30101
+{
+#pragma warning disable 1574
+    /// <summary>
+    /// USB Human Interface Device functions to connect to EV kit without requiring a custom device driver
+    /// </summary>
+#pragma warning restore 1574
+    public class HID
+    {
+        private IntPtr EventObject;
+        private System.Threading.NativeOverlapped managedOverlapped;
+        private IntPtr nonManagedOverlapped;
+        private IntPtr nonManagedBuffer;
+        public SafeFileHandle writeHandle;
+        public ArrayList writeHandleArray = new ArrayList();
+        public SafeFileHandle readHandle;
+        public ArrayList readHandleArray = new ArrayList();
+        public ArrayList desiredHIDPathNameArray = new ArrayList();
+        public String deviceID = System.String.Format("Vid_{0:x4}&Pid_{1:x4}", support.DEFAULT_VENDOR_ID, support.DEFAULT_PRODUCT_ID);
+
+        private bool explicit_report_id = true;
+        private const byte DEFAULT_REPORT_ID = 0;
+        private const byte SHORT_REPORT_ID = 1;
+        public const byte HID_REPORT_ID_1 = 1; // MAX30101 optical data, 3 bytes per channel, up to 3 channels per sample
+        public const byte HID_REPORT_ID_2 = 2; // LIS2DH accelerometer data, 2 bytes per channel, 3 channels per sample
+        public const byte HID_REPORT_ID_3 = 3; // reserved
+        private const byte USB_OVERFLOW = 8;
+        private const byte USB_WRITE_ERROR = 4;
+        private const byte USB_READ_ERROR = 2;
+        public const byte I2C_NACK_ERROR = 1;
+        private const byte API_FAIL = 0;     //API functions return non-0 upon success, to easily denote 'true' for C programs
+        private const byte I2C_SUCCESS = 3;  //we'll also use a non-0 value to denote success, since returning a '0' would lead to trouble when mixed with API's returns.
+        private const byte GP_SUCCESS = 3;
+        private const byte GP_FAIL = 0;
+        private const byte REPORT_SIZE = 64;
+
+        // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] before FlushQueue()
+        // mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+        // mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        public static Mutex mutexGuardIOBuf = new Mutex();
+        // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
+        public byte[] IOBuf = new byte[REPORT_SIZE];
+
+        private const int IOtimeout = 10000;
+
+        // API declarations relating to device management (SetupDixxx and 
+        // RegisterDeviceNotification functions).   
+
+        // from dbt.h
+
+        internal const Int32 DBT_DEVNODES_CHANGED = 7; 
+        internal const Int32 DBT_DEVICEARRIVAL = 0X8000;
+        internal const Int32 DBT_DEVICEREMOVECOMPLETE = 0X8004;
+        internal const Int32 DBT_DEVTYP_DEVICEINTERFACE = 5;
+        internal const Int32 DBT_DEVTYP_HANDLE = 6;
+        internal const Int32 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
+        internal const Int32 DEVICE_NOTIFY_SERVICE_HANDLE = 1;
+        internal const Int32 DEVICE_NOTIFY_WINDOW_HANDLE = 0;
+        internal const Int32 WM_DEVICECHANGE = 0X219;
+        internal const Int32 SPDRP_HARDWAREID = 1;
+
+        // from setupapi.h
+
+        internal const Int32 DIGCF_PRESENT = 2;
+        internal const Int32 DIGCF_DEVICEINTERFACE = 0X10;
+
+        // Two declarations for the DEV_BROADCAST_DEVICEINTERFACE structure.
+
+        // Use this one in the call to RegisterDeviceNotification() and
+        // in checking dbch_devicetype in a DEV_BROADCAST_HDR structure:
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal class DEV_BROADCAST_DEVICEINTERFACE
+        {
+            internal Int32 dbcc_size;
+            internal Int32 dbcc_devicetype;
+            internal Int32 dbcc_reserved;
+            internal Guid dbcc_classguid;
+            internal Int16 dbcc_name;
+        }
+
+        // Use this to read the dbcc_name String and classguid:
+
+        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+        internal class DEV_BROADCAST_DEVICEINTERFACE_1
+        {
+            internal Int32 dbcc_size;
+            internal Int32 dbcc_devicetype;
+            internal Int32 dbcc_reserved;
+            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
+            internal Byte[] dbcc_classguid;
+            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
+            internal Char[] dbcc_name;
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal class DEV_BROADCAST_HDR
+        {
+            internal Int32 dbch_size;
+            internal Int32 dbch_devicetype;
+            internal Int32 dbch_reserved;
+        }
+
+        internal struct SP_DEVICE_INTERFACE_DATA        
+        {
+            internal Int32 cbSize;
+            internal System.Guid InterfaceClassGuid;
+            internal Int32 Flags;
+            internal IntPtr Reserved;
+        }
+
+        //[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+        //internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
+        //{
+        //    internal Int32 cbSize;
+        //    //internal String DevicePath;            
+        //    internal Char[] DevicePath;
+        //}
+
+// warning CS0649: Field 'Maxim.MAX30101.HID.SP_DEVINFO_DATA.cbSize' is never assigned to, and will always have its default value 0
+#pragma warning disable 0649
+        internal struct SP_DEVINFO_DATA
+        {
+            internal Int32 cbSize;
+            internal System.Guid ClassGuid;
+            internal Int32 DevInst;
+            internal Int32 Reserved;
+        }
+#pragma warning restore 0649
+
+        //HDEVINFO SetupDiGetClassDevs(const GUID *ClassGuid, PCTSTR Enumerator, HWND hwndParent, DWORD Flags);
+        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        internal static extern IntPtr SetupDiGetClassDevs(ref System.Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, Int32 Flags);
+
+        //BOOL SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet);
+        [DllImport("setupapi.dll", SetLastError = true)]
+        internal static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
+
+        //BOOL SetupDiEnumDeviceInterfaces(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, const GUID *InterfaceClassGuid, DWORD MemberIndex, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
+        [DllImport("setupapi.dll", SetLastError = true)]
+        internal static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref System.Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
+        [DllImport("setupapi.dll", SetLastError = true)]
+        // required to pass DeviceInfoData=null
+        internal static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref System.Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
+
+        //BOOL SetupDiGetDeviceInterfaceDetail(HDEVINFO DeviceInfoSet, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, DWORD DeviceInterfaceDetailDataSize, PDWORD RequiredSize, PSP_DEVINFO_DATA DeviceInfoData);
+        //[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        //internal  extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData);
+        //[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        //// required to pass DeviceInfoData=null
+        //internal  extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData);
+        // cannot get SP_DEVICE_INTERFACE_DETAIL_DATA's DevicePath field to work properly, so use IntPtr instead of ref SP_DEVICE_INTERFACE_DATA
+        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        // required to pass DeviceInterfaceDetailData=null
+        internal static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData); 
+        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        // required to pass DeviceInterfaceDetailData=null, DeviceInfoData=null
+        internal static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData); 
+
+        //BOOL SetupDiEnumDeviceInfo(HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData);
+        [DllImport("setupapi.dll", SetLastError = true)]
+        internal static extern Boolean SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, Int32 MemberIndex, ref SP_DEVINFO_DATA DevInfoData);
+
+        //BOOL SetupDiGetDeviceRegistryProperty(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize);
+        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
+        internal static extern Boolean SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DevInfoData, Int32 Property, IntPtr PropertyRegDataType, IntPtr PropertyBuffer, Int32 PropertyBufferSize, ref Int32 RequiredSize);
+        
+        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
+        internal static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, Int32 Flags);
+
+        [DllImport("user32.dll", SetLastError = true)]
+        internal static extern Boolean UnregisterDeviceNotification(IntPtr Handle);
+
+        //  API declarations for HID communications.
+
+        //  from hidpi.h
+        //  Typedef enum defines a set of integer constants for HidP_Report_Type
+
+        internal const Int16 HidP_Input = 0;
+        internal const Int16 HidP_Output = 1;
+        internal const Int16 HidP_Feature = 2;
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct HIDD_ATTRIBUTES
+        {
+            internal Int32 Size;
+            internal UInt16 VendorID;
+            internal UInt16 ProductID;
+            internal UInt16 VersionNumber;
+        }
+
+        [DllImport("hid.dll", SetLastError = true)]
+        internal static extern Boolean HidD_FlushQueue(SafeFileHandle HidDeviceObject);
+
+        [DllImport("hid.dll", SetLastError = true)]
+        internal static extern Boolean HidD_GetAttributes(SafeFileHandle HidDeviceObject, ref HIDD_ATTRIBUTES Attributes);
+
+        [DllImport("hid.dll", SetLastError = true)]
+        internal static extern void HidD_GetHidGuid(ref System.Guid HidGuid);
+
+        public void getHidGuid(ref System.Guid hidGuid)
+        {
+            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+#if DEBUG
+            HidD_GetHidGuid(ref hidGuid);
+#else
+            try
+            {
+                HidD_GetHidGuid(ref hidGuid);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+#endif
+            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+        }
+
+        public string DebugMessage;
+        //public void TraceMessage(string message,
+        //[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
+        //[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
+        //[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
+        //{
+        //    Console.WriteLine("message: " + message);
+        //    Console.WriteLine("member name: " + memberName);
+        //    Console.WriteLine("source file path: " + sourceFilePath);
+        //    Console.WriteLine("source line number: " + sourceLineNumber);
+        //}
+
+        /// <summary>
+        /// FindDesiredHIDPathNamesFromGuid() only appends to desiredHIDPathNameArray. 
+        /// Desired HIDs that have been removed from the system are removed from desiredHIDPathNameArray in openHIDhandles
+        /// </summary>
+        /// <param name="myGuid"></param>
+        private void FindDesiredHIDPathNamesFromGuid(System.Guid myGuid)
+        {
+            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+            // But I never see this kind of exception thrown from within the C# GUI.
+            // HID.findHIDs() still raises the exception in Matlab, 
+            // Message: Arithmetic operation resulted in an overflow.
+            //
+            //    % Required: connect to MAX30101EVKIT hardware
+            //    fprintf('Connecting to MAX30101EVKIT hardware...\n');
+            //    for trial=0:2000
+            //        try
+            //            %pause(1) % delay at least 1 second
+            //            myMAX30101.myHID.findHIDs();
+            //            % Sometimes we get Error using MAX30101Example
+            //            %   If this happens, try clearing the workspace and run again.
+            //            % Message: Arithmetic operation resulted in an overflow.
+            //            % FindDesiredHIDPathNamesFromGuid
+            //            % findHIDs
+            //            % Source: MAX30101
+            //            % HelpLink:    
+            //            if (myMAX30101.myHID.isConnected())
+            //                break
+            //            end
+            //        catch me
+            //            % disp(me)
+            //        end
+            //    end
+            //
+            // If matlab does successfully connect to USB, it is able to get 
+            // streaming data through the PartialArrayIntAvailable event 
+            // handler -- even though it can't understand 
+            // System.Collections.ArrayList data, it does at least understand 
+            // Array<System.Int32> or int[] data.
+            //
+            Int32 memberIndex = 0;
+            Int32 bufferSize = 0;
+            IntPtr deviceInfoSet = new System.IntPtr();
+            SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
+            IntPtr deviceInterfaceDetailDataBuffer = IntPtr.Zero;
+
+            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+            // diagnostic: trying to avoid "Arithmetic overflow" from matlab. Limit the number of HID devices to be checked.
+            const int memberIndexLimit = 100;
+            
+#if DEBUG
+#else
+            try
+            {
+#endif
+            DebugMessage = string.Format("{0} first deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+                memberIndex = 0;
+
+                // The cbSize element of the DeviceInterfaceData structure must be set to the structure's size in bytes. 
+                // The size is 28 bytes for 32-bit code and 32 bits for 64-bit code.
+                DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);
+
+                while (memberIndex < memberIndexLimit)
+                {
+                    // Begin with memberIndex = 0 and increment through the device information set until no more devices are available.
+                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiEnumDeviceInterfaces ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref myGuid, memberIndex, ref DeviceInterfaceData))
+                    {
+                        break;
+                    }
+
+                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero);
+                    //tempLastError = GetLastError();
+                    //if (tempLastError != ERROR_INSUFFICIENT_BUFFER)  // ERROR_INSUFFICIENT_BUFFER is expected on this first call
+                    //    break;
+                    //FIXME add error check
+
+                    // Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.AllocHGlobal(bufferSize) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    deviceInterfaceDetailDataBuffer = Marshal.AllocHGlobal(bufferSize);
+                    // Returns a System.IntPtr pointer to the newly allocated global heap memory.
+                    // This memory must be released using the Marshal.FreeHGlobal method.
+                    // Marshal.AllocHGlobal(numBytes) could throw OutOfMemoryException ?
+
+                    // Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.WriteInt32 ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    Marshal.WriteInt32(deviceInterfaceDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
+
+                    // Call SetupDiGetDeviceInterfaceDetail again.
+                    // This time, pass a pointer to DetailDataBuffer and the returned required buffer size.
+                    DebugMessage = string.Format("{0} memberIndex={1} second SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, deviceInterfaceDetailDataBuffer, bufferSize, ref bufferSize, IntPtr.Zero))
+                        break;
+
+                    // Skip over cbsize (4 bytes) to get the address of the devicePathName.
+                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
+                    DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4);
+                    // DebugMessage = string.Format("{0} memberIndex={1} new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    // BAD CODE. IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4);
+                    // BAD CODE. assumes the pointer is a 32-bit address, intermittently fails from 64-bit matlab client.
+
+                    // Get the String containing the devicePathName.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.PtrToStringAuto(pDevicePathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    string tempPathName = Marshal.PtrToStringAuto(pDevicePathName);
+
+                    // match any device pathname that contains deviceID (case-insensitive match) "Vid_{0:x4}&Pid_{1:x4}"
+                    DebugMessage = string.Format("{0} memberIndex={1} if (tempPathName.ToLower().IndexOf(deviceID.ToLower()) != -1) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    if (tempPathName.ToLower().IndexOf(deviceID.ToLower()) != -1)
+                    {
+                        DebugMessage = string.Format("{0} memberIndex={1} desiredHIDPathNameArray.Add(tempPathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                        desiredHIDPathNameArray.Add(tempPathName);
+                    }
+
+                    DebugMessage = string.Format("{0} memberIndex={1} memberIndex = memberIndex + 1; ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+
+                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
+                    // why didn't they call Marshal.FreeHGlobal here, inside the while loop? Isn't this a memory leak?
+                    // Free the memory allocated previously by AllocHGlobal.
+                    if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
+                    {
+                        Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
+                        deviceInterfaceDetailDataBuffer = IntPtr.Zero;
+                    }
+                    
+                    memberIndex = memberIndex + 1;
+                }
+#if DEBUG
+                // Free the memory allocated previously by AllocHGlobal.
+                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
+                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
+
+                if (deviceInfoSet != IntPtr.Zero)
+                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
+#else
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+            finally
+            {
+                // Free the memory allocated previously by AllocHGlobal.
+                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
+                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
+
+                if (deviceInfoSet != IntPtr.Zero)
+                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
+            }
+#endif
+                DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+        }
+
+        private void FindAllHIDPathNamesFromGuid(System.Guid myGuid, ArrayList allHIDPathNameArray)
+        {
+            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+            // But I never see this kind of exception thrown from within the C# GUI.
+            // HID.findHIDs() still raises the exception in Matlab, 
+            // Message: Arithmetic operation resulted in an overflow.
+            Int32 memberIndex = 0; 
+            Int32 bufferSize = 0;
+            IntPtr deviceInfoSet = new System.IntPtr();
+            SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); 
+            IntPtr deviceInterfaceDetailDataBuffer = IntPtr.Zero;
+
+            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+            // diagnostic: trying to avoid "Arithmetic overflow" from matlab. Limit the number of HID devices to be checked.
+            const int memberIndexLimit = 100;
+            
+            
+#if DEBUG
+#else
+            try
+            {
+#endif
+            DebugMessage = string.Format("{0} first deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+
+                memberIndex = 0;
+
+                // The cbSize element of the DeviceInterfaceData structure must be set to the structure's size in bytes. 
+                // The size is 28 bytes for 32-bit code and 32 bits for 64-bit code.
+                DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);
+
+                // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+                while (memberIndex < memberIndexLimit)
+                {
+                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiEnumDeviceInterfaces ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref myGuid, memberIndex, ref DeviceInterfaceData))
+                    {
+                        break;
+                    }
+
+                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero);
+
+                    // Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.AllocHGlobal(bufferSize) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    deviceInterfaceDetailDataBuffer = Marshal.AllocHGlobal(bufferSize);
+
+                    // Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.WriteInt32 ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    Marshal.WriteInt32(deviceInterfaceDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
+
+                    // Call SetupDiGetDeviceInterfaceDetail again.
+                    // This time, pass a pointer to deviceInterfaceDetailDataBuffer and the returned required buffer size.
+                    DebugMessage = string.Format("{0} memberIndex={1} second SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, deviceInterfaceDetailDataBuffer, bufferSize, ref bufferSize, IntPtr.Zero))
+                        break;
+
+                    // Skip over cbsize (4 bytes) to get the address of the devicePathName.
+                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
+                    DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    // example: IntPtr right way to add an offset (portable to 64-bit clients)
+                    IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4);
+                    // DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    // BAD CODE. IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4);
+                    // BAD CODE. assumes the pointer is a 32-bit address, intermittently fails from 64-bit matlab client.
+
+                    // Get the String containing the devicePathName.
+                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.PtrToStringAuto(pDevicePathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
+                    allHIDPathNameArray.Add(Marshal.PtrToStringAuto(pDevicePathName));
+
+                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
+                    // why didn't they call Marshal.FreeHGlobal here, inside the while loop? Isn't this a memory leak?
+                    // Free the memory allocated previously by AllocHGlobal.
+                    if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
+                    {
+                        Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
+                        deviceInterfaceDetailDataBuffer = IntPtr.Zero;
+                    }
+                   
+                    memberIndex = memberIndex + 1;
+                }
+#if DEBUG
+                // Free the memory allocated previously by AllocHGlobal.
+                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
+                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
+
+                if (deviceInfoSet != IntPtr.Zero)
+                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
+#else
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+            finally
+            {
+                // Free the memory allocated previously by AllocHGlobal.
+                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)                    
+                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);                
+
+                if (deviceInfoSet != IntPtr.Zero)
+                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
+            }
+#endif
+            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+        }
+
+        public bool isConnected()
+        {
+            try
+            {
+                return desiredHIDPathNameArray.Count != 0 ? true : false;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+
+        /// <summary>
+        /// Called when a WM_DEVICECHANGE message has arrived,
+        /// indicating that a device has been attached or removed.
+        /// 
+        /// </summary>
+        /// <param name="m"> a message with information about the device </param>
+        /// <returns>true on HID device arrival or remove complete</returns>
+        public bool HandleWMDeviceChangeMessage(Message m)
+        {
+            // Example code:
+            // 
+            // <code>
+            // protected override void WndProc(ref Message m)
+            // {
+            //     if (myHID != null)
+            //     {
+            //         if (myHID.HandleWMDeviceChangeMessage(m) /* m.Msg == HID.WM_DEVICECHANGE */ )
+            //         {
+            //             // optional: handle newly arrived connection or surprise disconnect
+            //         }
+            //     }
+            //     // Let the base form process the message.
+            //     base.WndProc(ref m);
+            // }
+            // </code>
+            // 
+            // https://jira.maxim-ic.com/browse/OS24EVK-59 WndProc if HID.WM_DEVICECHANGE do HID.OnDeviceChange(Message m)
+            try
+            {
+                if (m.Msg == HID.WM_DEVICECHANGE)
+                {
+                    //if ((int)m.WParam == HID.DBT_DEVNODES_CHANGED)  //this always occurs when any USB device is attached/detached. Use this if not registering for device notifications.
+                    if (
+                            (   m.WParam.ToInt32() == HID.DBT_DEVICEARRIVAL 
+                            ||  m.WParam.ToInt32() == HID.DBT_DEVICEREMOVECOMPLETE
+                            ) 
+                        && 
+                            (m.LParam.ToInt32() != 0) 
+                        && 
+                            DeviceIDMatch(m)
+                       )
+                    {
+                        closeHIDhandles();
+                        findHIDs();
+                        // cboEVB_init();
+                        return true;
+                    }
+                }
+                return false;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        /// <summary>
+        /// findHIDs() called upon startup or if a desired HID has been inserted or removed
+        /// </summary>
+        public void findHIDs()
+        {
+            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            ArrayList allHIDPathNameArray = new ArrayList();
+            System.Guid hidGuid = new System.Guid();
+
+            // https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
+#if DEBUG
+#else
+            try
+            {
+#endif
+                HidD_GetHidGuid(ref hidGuid);
+                // FindDesiredHIDPathNamesFromGuid() builds desiredHIDPathNameArray which is a list of all desired HIDs in the system
+                // desiredHIDPathNameArray is a global list and is used to maintain the order of desired HIDs in the selection list. 
+                // USB ports have different priorities, and the user could have attached the first desired HID to a lower priority port. 
+                // Hence, when another desired HID is attached to a higher priority port, it will come earlier in allHIDPathNameArray, but it will be maintained in the same attachment order in desiredHIDPathNameArray.
+                // Note that desiredHIDPathNameArray is only appended to or deleted from; it is never recreated in whole.
+                // FIXME desiredHIDPathNameArray will get duplicate pathnames for desired HIDs that were already attached, but these will be removed by openHIDhandles() since they're in allHIDPathNameArray only once. 
+                // These duplicates pathnames are appended after the initial list, so they won't affect order.
+                FindDesiredHIDPathNamesFromGuid(hidGuid);
+                // FindAllHIDPathNamesFromGuid() builds allHIDPathNameArray which is a list of all HIDs in the system. It is recreated every time a desired HID is attached or removed.
+                FindAllHIDPathNamesFromGuid(hidGuid, allHIDPathNameArray);
+                // openHIDhandles() gets handles for all desired HIDs
+                // openHIDhandles() loops through all attached HIDs and checks for a match of each item in desiredHIDPathNameArray. This maintains the attachement order.
+                // If a previously attached HID has been removed, it won't be found in allHIDPathNameArray and it will be removed from desiredHIDPathNameArray.
+                openHIDhandles(allHIDPathNameArray);
+                if (desiredHIDPathNameArray.Count != 0)
+                {
+                    prepareForOverlappedTransfer();
+                }
+#if DEBUG
+#else
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+#endif
+            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+        }
+
+        private void openHIDhandles(ArrayList allHIDPathNameArray)
+        {
+            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+            int desiredHIDPathNameArrayCounter;
+            int allHIDPathNameArrayCounter;
+            bool found_installed_device;
+            try
+            {
+                desiredHIDPathNameArrayCounter = 0;
+                while (desiredHIDPathNameArrayCounter < desiredHIDPathNameArray.Count) // count will change if a previously installed device has been removed, so don't use a for loop
+                {
+                    found_installed_device = false;
+                    allHIDPathNameArrayCounter = 0;
+                    while (allHIDPathNameArrayCounter < allHIDPathNameArray.Count)
+                    {
+                        if ((string)allHIDPathNameArray[allHIDPathNameArrayCounter] == (string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter])
+                        {
+                            writeHandle = FileIO.CreateFile((string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter], FileIO.GENERIC_READ | FileIO.GENERIC_WRITE, FileIO.FILE_SHARE_READ | FileIO.FILE_SHARE_WRITE, IntPtr.Zero, FileIO.OPEN_EXISTING, 0, 0);
+                            if (!(writeHandle.IsInvalid))
+                            {
+                                readHandle = FileIO.CreateFile((string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter], FileIO.GENERIC_READ | FileIO.GENERIC_WRITE, FileIO.FILE_SHARE_READ | FileIO.FILE_SHARE_WRITE, IntPtr.Zero, FileIO.OPEN_EXISTING, FileIO.FILE_FLAG_OVERLAPPED, 0);
+                                if (!(readHandle.IsInvalid))
+                                {
+                                    writeHandleArray.Add(writeHandle);
+                                    readHandleArray.Add(readHandle);
+                                    allHIDPathNameArray.RemoveAt(allHIDPathNameArrayCounter); // remove it so we don't repeatedly add it when we check for new devices (after we finish checking for previously installed devices)
+                                    found_installed_device = true;
+                                }
+                                else
+                                {
+                                    writeHandle.Close();
+                                    writeHandle = null;
+                                    readHandle = null;
+                                }
+                            }
+                            else
+                            {
+                                writeHandle = null;
+                                readHandle = null; 
+                            }
+                            break;
+                        }
+                        allHIDPathNameArrayCounter++;
+                    }
+                    if (found_installed_device == false)    // no match in all allHIDPathNameArray elements; the device has been removed so remove its pathname from desiredHIDPathNameArray. Don't use counter at max count to check if not found, since max count can change.
+                        desiredHIDPathNameArray.RemoveAt(desiredHIDPathNameArrayCounter);   // decrements count by 1; don't increment desiredHIDPathNameArrayCounter
+                    else
+                        desiredHIDPathNameArrayCounter++;
+                }
+            }
+            catch (Exception ex)
+            {
+                DebugMessage = string.Format("{0} exception {1}", System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
+        }
+
+        public void closeHIDhandles()
+        {
+            try
+            {
+                if (desiredHIDPathNameArray.Count != 0)
+                {
+                    foreach (Object obj in writeHandleArray)
+                        ((SafeFileHandle)obj).Close();
+                    writeHandleArray.Clear();
+                    writeHandle = null;
+                    foreach (Object obj in readHandleArray)
+                        ((SafeFileHandle)obj).Close();
+                    readHandleArray.Clear();
+                    readHandle = null;
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        private void prepareForOverlappedTransfer()
+        {
+            try
+            {
+                EventObject = FileIO.CreateEvent(IntPtr.Zero, false, false, String.Empty);
+                managedOverlapped.OffsetLow = 0;
+                managedOverlapped.OffsetHigh = 0;
+                managedOverlapped.EventHandle = EventObject; // HIDOverlapped is the overlapped structure used in ReadFile; EventObject will be signaled upon completion of ReadFile
+                nonManagedOverlapped = Marshal.AllocHGlobal(Marshal.SizeOf(managedOverlapped));
+                Marshal.StructureToPtr(managedOverlapped, nonManagedOverlapped, false);
+
+                nonManagedBuffer = Marshal.AllocHGlobal(REPORT_SIZE);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void FlushQueue()
+        {
+            try
+            {
+                if (writeHandle == null)
+                {
+                    return;
+                }
+                if (readHandle == null)
+                {
+                    return;
+                }
+
+                HidD_FlushQueue(writeHandle);
+                HidD_FlushQueue(readHandle);
+            }
+            catch
+            {
+                throw new Exception(new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void writeReadHID()
+        {
+            try
+            {
+                if (writeHandle == null)
+                {
+                    return;
+                }
+                if (readHandle == null)
+                {
+                    return;
+                }
+
+                writeHID();
+                readHID();
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void writeHID()
+        {
+            if (writeHandle == null)
+            {
+                return;
+            }
+            if (readHandle == null)
+            {
+                return;
+            }
+
+            int BytesSucceed;
+            bool api_status;
+
+            try
+            {
+                BytesSucceed = 0;
+                Marshal.Copy(IOBuf, 0, nonManagedBuffer, REPORT_SIZE);
+                api_status = FileIO.WriteFile(writeHandle, nonManagedBuffer, REPORT_SIZE, ref BytesSucceed, IntPtr.Zero);
+                //endpoint in interrupt at uC occurs after this WriteFile call since the in data is ready and the host takes it; endpoint in interrupt does not occur after ReadFile call
+
+                if (api_status == false)
+                {
+                    //MessageBox.Show(Err.LastDllError);
+                    throw new Exception(support.ResultOfAPICall("API WriteFile error"));
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void readHID()
+        {
+            if (writeHandle == null)
+            {
+                return;
+            }
+            if (readHandle == null)
+            {
+                return;
+            }
+
+            int BytesSucceed;
+            bool api_status;
+            int status;
+
+            try
+            {
+                Array.Clear(IOBuf, 0, IOBuf.Length);
+
+                BytesSucceed = 0;
+
+                api_status = FileIO.ReadFile(readHandle, nonManagedBuffer, REPORT_SIZE, ref BytesSucceed, nonManagedOverlapped);
+                
+                if (api_status == false) 
+                {
+                    //MsgBox(Err.LastDllError)
+                    status = FileIO.WaitForSingleObject(EventObject, IOtimeout);                    
+
+                    if (status != FileIO.WAIT_OBJECT_0) 
+                    {
+                        api_status = FileIO.CancelIo(readHandle);
+                        throw new Exception(support.ResultOfAPICall("API ReadFile error"));
+                    }
+                    FileIO.GetOverlappedResult(readHandle, nonManagedOverlapped, ref BytesSucceed, false);
+                }
+
+                // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
+                if (BytesSucceed > IOBuf.Length)
+                {
+                    IOBuf = new byte[BytesSucceed];
+                    Array.Clear(IOBuf, 0, IOBuf.Length);
+                }
+                Marshal.Copy(nonManagedBuffer, IOBuf, 0, BytesSucceed);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void readHID2() // this function is for testing multiple report read on a single ReadFile()
+        {
+            if (writeHandle == null)
+            {
+                return;
+            }
+            if (readHandle == null)
+            {
+                return;
+            }
+
+            int BytesSucceed;
+            bool api_status;
+            int status;
+
+            try
+            {
+                int size = 128;
+                //byte[] buf = new byte[size];
+                IntPtr nonManagedBuf = Marshal.AllocHGlobal(size);
+                //Array.Clear(buf, 0, size);
+
+                BytesSucceed = 0;
+
+                api_status = FileIO.ReadFile(readHandle, nonManagedBuf, size, ref BytesSucceed, nonManagedOverlapped);
+
+                if (api_status == false)
+                {
+                    //MsgBox(Err.LastDllError)
+                    status = FileIO.WaitForSingleObject(EventObject, IOtimeout);
+
+                    if (status != FileIO.WAIT_OBJECT_0)
+                    {
+                        api_status = FileIO.CancelIo(readHandle);
+                        throw new Exception(support.ResultOfAPICall("API ReadFile error"));
+                    }
+                    FileIO.GetOverlappedResult(readHandle, nonManagedOverlapped, ref BytesSucceed, false);
+                }
+
+                // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
+                if (BytesSucceed > IOBuf.Length)
+                {
+                    IOBuf = new byte[BytesSucceed];
+                    Array.Clear(IOBuf, 0, IOBuf.Length);
+                }
+                Marshal.Copy(nonManagedBuf, IOBuf, 0, BytesSucceed);
+                
+                if (nonManagedBuf != IntPtr.Zero)
+                    Marshal.FreeHGlobal(nonManagedBuffer);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+        public void freeHeap()
+        {
+            try
+            {
+                if (nonManagedBuffer != IntPtr.Zero)
+                    Marshal.FreeHGlobal(nonManagedBuffer);
+                if (nonManagedOverlapped != IntPtr.Zero)
+                    Marshal.FreeHGlobal(nonManagedOverlapped);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        ///  <summary>
+        ///  Requests to receive a notification when a device is attached or removed.
+        ///  </summary>
+        ///  
+        ///  <param name="formHandle"> handle to the window that will receive device events. </param>
+        ///  <param name="classGuid"> device interface GUID. </param>
+        ///  <param name="deviceNotificationHandle"> returned device notification handle. </param>
+        ///  
+        ///  <returns>
+        ///  True on success.
+        ///  </returns>
+        ///  
+        public Boolean RegisterForDeviceNotifications(IntPtr formHandle, Guid classGuid, ref IntPtr deviceNotificationHandle)
+        {
+            DEV_BROADCAST_DEVICEINTERFACE devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE();
+            IntPtr devBroadcastDeviceInterfaceBuffer = IntPtr.Zero;
+            Int32 size = 0;
+
+            try
+            {
+                size = Marshal.SizeOf(devBroadcastDeviceInterface);
+                devBroadcastDeviceInterface.dbcc_size = size;
+                devBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+                devBroadcastDeviceInterface.dbcc_reserved = 0;
+                devBroadcastDeviceInterface.dbcc_classguid = classGuid;
+                
+                // Allocate memory for the buffer that holds the DEV_BROADCAST_DEVICEINTERFACE structure.
+                devBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size);
+
+                // Copy the DEV_BROADCAST_DEVICEINTERFACE structure to the buffer.
+                // Set fDeleteOld True to prevent memory leaks.
+                Marshal.StructureToPtr(devBroadcastDeviceInterface, devBroadcastDeviceInterfaceBuffer, true);
+
+                // ***
+                //  API function
+
+                //  summary
+                //  Request to receive notification messages when a device in an interface class
+                //  is attached or removed.
+
+                //  parameters 
+                //  Handle to the window that will receive device events.
+                //  Pointer to a DEV_BROADCAST_DEVICEINTERFACE to specify the type of 
+                //  device to send notifications for.
+                //  DEVICE_NOTIFY_WINDOW_HANDLE indicates the handle is a window handle.
+
+                //  Returns
+                //  Device notification handle or NULL on failure.
+                // ***
+
+                deviceNotificationHandle = RegisterDeviceNotification(formHandle, devBroadcastDeviceInterfaceBuffer, DEVICE_NOTIFY_WINDOW_HANDLE);
+
+                // Marshal data from the unmanaged block devBroadcastDeviceInterfaceBuffer to the managed object devBroadcastDeviceInterface
+                // why?
+                Marshal.PtrToStructure(devBroadcastDeviceInterfaceBuffer, devBroadcastDeviceInterface);
+
+                return deviceNotificationHandle.ToInt32() == IntPtr.Zero.ToInt32() ? false : true;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+            finally
+            {
+                // Free the memory allocated previously by AllocHGlobal.
+                if (devBroadcastDeviceInterfaceBuffer != IntPtr.Zero)
+                    Marshal.FreeHGlobal(devBroadcastDeviceInterfaceBuffer);                
+            }
+        }
+
+        ///  <summary>
+        ///  Requests to stop receiving notification messages when a device in an
+        ///  interface class is attached or removed.
+        ///  </summary>
+        ///  
+        ///  <param name="deviceNotificationHandle"> handle returned previously by
+        ///  RegisterDeviceNotification. </param>
+
+        public void StopReceivingDeviceNotifications(IntPtr deviceNotificationHandle)
+        {
+            try
+            {
+               UnregisterDeviceNotification(deviceNotificationHandle);
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public Boolean DeviceIDMatch(Message m)
+        {
+            Int32 stringSize;
+
+            try
+            {
+                DEV_BROADCAST_HDR devBroadcastHeader = new DEV_BROADCAST_HDR(); 
+                DEV_BROADCAST_DEVICEINTERFACE_1 devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE_1();
+
+                // The LParam parameter of Message is a pointer to a DEV_BROADCAST_HDR structure.
+                Marshal.PtrToStructure(m.LParam, devBroadcastHeader);
+                if ((devBroadcastHeader.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE))
+                {
+                    // The dbch_devicetype parameter indicates that the event applies to a device interface.
+                    // So the structure in LParam is actually a DEV_BROADCAST_INTERFACE structure, 
+                    // which begins with a DEV_BROADCAST_HDR.
+
+                    // Obtain the number of characters in dbch_name by subtracting the 32 bytes
+                    // in the strucutre that are not part of dbch_name and dividing by 2 because there are 
+                    // 2 bytes per character.
+
+                    stringSize = System.Convert.ToInt32((devBroadcastHeader.dbch_size - 32) / 2);
+
+                    // The dbcc_name parameter of devBroadcastDeviceInterface contains the device name. 
+                    // Trim dbcc_name to match the size of the String.         
+
+                    devBroadcastDeviceInterface.dbcc_name = new Char[stringSize + 1];
+
+                    // Marshal data from the unmanaged block pointed to by m.LParam 
+                    // to the managed object devBroadcastDeviceInterface.
+
+                    Marshal.PtrToStructure(m.LParam, devBroadcastDeviceInterface);
+
+                    // Store the device name in a String.
+
+                    String DeviceNameString = new String(devBroadcastDeviceInterface.dbcc_name, 0, stringSize);
+
+                    // Compare the name of the newly attached device with the name of the device 
+                    // the application is accessing (deviceID).
+                    // Set ignorecase True.
+
+                    return (DeviceNameString.ToLower().IndexOf(deviceID.ToLower()) == -1) ? false : true;
+                }
+                else
+                    return false;
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void reportID()
+        {
+            try
+            {
+                if (explicit_report_id)
+                    IOBuf[0] = SHORT_REPORT_ID;     // explicit out report ID in HID's descriptor
+                else
+                    IOBuf[0] = DEFAULT_REPORT_ID;   // default report ID; this byte is dropped in transfer, so we don't really waste a byte transmitting it
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public int readI2C(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] readData, byte[] reg, bool ignoreNACK = false)
+        {
+            //readData is passed back to caller with the read data
+            int status;
+            int i;
+
+            try
+            {
+                if (numDataBytes > REPORT_SIZE - 2) {
+                    throw new Exception("USB buffer overflow");
+                }
+
+                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in readI2C
+                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+                FlushQueue();
+                Array.Clear(IOBuf, 0, IOBuf.Length);
+
+                reportID();
+                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+                IOBuf[2] = (byte)(deviceAddress | 1);     // set read bit
+                IOBuf[3] = numDataBytes;
+                IOBuf[4] = numRegBytes;
+                // TODO1: OS24EVK-57 HID readI2C no validation that numRegBytes == reg.Length ?
+                for (i = 0; i < numRegBytes; i++) {
+                    IOBuf[i + 5] = reg[i];
+                }
+
+                writeHID();
+                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+
+                status = checkNACK();
+
+                // checkNACK also reads the data, assuming NACK didn't occur
+                if (status == I2C_NACK_ERROR && ignoreNACK == false) {
+                    throw new Exception("invalid I2C address");
+                }
+
+                for (i = 0; i < numDataBytes; i++) {
+                    readData[i] = IOBuf[i + 2];
+                }
+
+                return status;      // the caller will not need the return value if an exception is thrown
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        public void writeI2C(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] data, byte[] reg, bool ignoreNACK = false)
+        {
+            int i;
+
+            try
+            {
+                if (numDataBytes > REPORT_SIZE - 5) {
+                    throw new Exception("USB buffer overflow");
+                }
+
+                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in writeI2C
+                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+                FlushQueue();
+                Array.Clear(IOBuf, 0, IOBuf.Length);
+
+                //send data to tell uC to do I2C read from slave
+                reportID();
+                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+                IOBuf[2] = deviceAddress;
+                IOBuf[3] = numDataBytes;
+                IOBuf[4] = numRegBytes;
+                for (i = 0; i < numRegBytes; i++) {
+                    IOBuf[i + 5] = reg[i];
+                }
+                for (i = 0; i < numDataBytes; i++) {
+                    IOBuf[i + 5 + numRegBytes] = data[i];
+                }
+
+                writeHID();
+                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+
+                if (checkNACK() == I2C_NACK_ERROR && ignoreNACK == false) {
+                    throw new Exception("invalid I2C address");
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        private int checkNACK()
+        {
+            // Check if I2C write was really succesful or a NACK occurred, since this is independent of USB success
+            // This function also reads all the data from the report. If NACK occured, the data is invalid.
+            try
+            {
+                readHID();
+                return (IOBuf[1] == 0 ? I2C_SUCCESS : I2C_NACK_ERROR);    // the caller will not need the return value if an exception is thrown
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        // only allows 2 byte data
+        public void setBitI2C(byte deviceAddress, int reg, byte range, int data, bool ignoreNACK = false)
+        {
+            int i;
+            
+            try
+            {
+                int LSb = range & 0xF;
+                int MSb = (range & 0xF0) >> 4;
+                if (MSb < LSb)
+                    throw new Exception("invalid bit range");
+                //if (numDataBytes > REPORT_SIZE - 5)
+                //    throw new Exception("USB buffer overflow");
+
+                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in setBitI2C
+                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+                FlushQueue();
+
+                Array.Clear(IOBuf, 0, IOBuf.Length);
+
+                //send data to tell uC to do I2C read from slave
+                reportID();
+                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+                IOBuf[2] = deviceAddress;
+                byte numDataBytes = (byte)(MSb > 7 ? 2 : 1);
+                IOBuf[3] = numDataBytes;
+                byte numRegBytes = (byte)Math.Ceiling(reg / 255.0);
+                IOBuf[4] = numRegBytes;
+                for (i = 0; i < numRegBytes; i++)                
+                    IOBuf[i + 5] = (byte)((reg>>(8*i)) & 0xFF);                
+                //for (i = 0; i < numDataBytes; i++)
+                //    IOBuf[i + 5 + numRegBytes] = data[i];
+
+                writeHID();
+                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+
+                if (checkNACK() == I2C_NACK_ERROR && ignoreNACK == false)
+                    throw new Exception("invalid I2C address");
+            }
+            catch (Exception ex)
+            {
+                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
+            }
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 0 IN = Firmware version</para>
+        /// 
+        /// </summary>
+        /// <param name="VerMajor"></param>
+        /// <param name="VerMinor"></param>
+        /// <param name="verYearHundreds"></param>
+        /// <param name="verYear"></param>
+        /// <param name="verMonth"></param>
+        /// <param name="verDay"></param>
+        public void FirmwareVersion(out byte VerMajor, out byte VerMinor, out byte verYearHundreds, out byte verYear, out byte verMonth, out byte verDay)
+        {
+            if (FirmwareINT0Enabled != 0)
+            {
+                // firmware is busy streaming out HID reports,
+                // so return a previously retrieved firmware version.
+                VerMajor = _VerMajor;
+                VerMinor = _VerMinor;
+                verYearHundreds = _verYearHundreds;
+                verYear = _verYear;
+                verMonth = _verMonth;
+                verDay = _verDay;
+                return;
+            }
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in FirmwareVersion
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 0; // Firmware version -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            writeReadHID();
+            VerMajor = IOBuf[2];
+            VerMinor = IOBuf[3];
+            verYearHundreds = IOBuf[4];
+            verYear = IOBuf[5];
+            verMonth = IOBuf[6];
+            verDay = IOBuf[7];
+            //
+            // save the recently read values
+            _VerMajor = VerMajor;
+            _VerMinor = VerMinor;
+            _verYearHundreds = verYearHundreds;
+            _verYear = verYear;
+            _verMonth = verMonth;
+            _verDay = verDay;
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+        private byte _VerMajor = 0;
+        private byte _VerMinor = 0;
+        private byte _verYearHundreds = 0;
+        private byte _verYear = 0;
+        private byte _verMonth = 0;
+        private byte _verDay = 0;
+
+        /// <summary>
+        /// <para>Firmware command code 1 OUT = LED (C51F321 P2.2=red, P2.1=green) </para>
+        /// 
+        /// </summary>
+        /// <param name="xxxxxxP22P21">0x02=P2.2 Red, 0x01=P2.1 Green</param>
+        public void LEDSet(byte xxxxxxP22P21)
+        {
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers LEDSet(byte xxxxxxP22P21)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in LEDSet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 1; // case (1): //LED (C51F321 P2.2=red, P2.1=green) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 0; //    case (0):	// write
+            IOBuf[3] = xxxxxxP22P21; 
+            // Led2 = IO_BUFFER.Ptr[2+gOffset] & 1; // sbit Led2 = P2^1;
+            // Led1 = (IO_BUFFER.Ptr[2+gOffset] & 2) >> 1; // sbit Led1 = P2^2;
+            writeHID();
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 1 IN = GPIOP0Get read I/O pins P0.6, P0.7</para>
+        /// 
+        /// </summary>
+        /// <param name="xxxxxxP06P07"></param>
+        public void GPIOP0Get(out byte xxxxxxP06P07)
+        {
+            // todo: verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP0Get(out byte xxxxxxP06P07)
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: Rename LEDGet(out byte xxxxxxP06P07) to GPIOP0Get(out byte xxxxxxP06P07)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP0Get
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 1; // case (1): //LED (C51F321 P2.2=red, P2.1=green) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 1; //    case (1):	// read
+            writeReadHID();
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0 + gOffset] = 0; // error status
+            // IO_BUFFER.Ptr[1 + gOffset] = ((P0 & 0x80) >> 7) + ((P0 & 0x40) >> 5);
+            xxxxxxP06P07 = IOBuf[2];
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 2 OUT = GPIO configuration (C51F321 P1 and P2) </para>
+        /// 
+        /// </summary>
+        /// <param name="P1MDOUT"></param>
+        /// <param name="P2MDOUT"></param>
+        /// <param name="weakPullupDisable"></param>
+        public void GPIOP1P2ConfigSet(byte P1MDOUT, byte P2MDOUT, byte weakPullupDisable)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2ConfigSet(byte P1MDOUT, byte P2MDOUT, byte weakPullupDisable)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2ConfigSet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 2; // case (2): //GPIO configuration (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 0; //    case (0):	// write
+            IOBuf[3] = P1MDOUT; // P1MDOUT = IO_BUFFER.Ptr[2+gOffset];	// P1 push-pull (1) or open-collector (0)
+            IOBuf[4] = P2MDOUT; // P2MDOUT = IO_BUFFER.Ptr[3 + gOffset];	// P2 push-pull (1) or open-collector (0)
+            IOBuf[5] = weakPullupDisable; // (IO_BUFFER.Ptr[4 + gOffset] & 1) ? (XBR1 |= 0x80) : (XBR1 &= 0x7F);	// weak pull up disable (open collector only); 1 disabled, 0 enabled
+            writeHID();
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 2 IN = GPIO configuration (C51F321 P1 and P2) </para>
+        /// 
+        /// </summary>
+        /// <param name="P1MDOUT"></param>
+        /// <param name="P2MDOUT"></param>
+        /// <param name="weakPullupDisable"></param>
+        public void GPIOP1P2ConfigGet(out byte P1MDOUT, out byte P2MDOUT, out byte weakPullupDisable)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2ConfigGet(out byte P1MDOUT, out byte P2MDOUT, out byte weakPullupDisable)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2ConfigGet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 2; // case (2): //GPIO configuration (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 1; //    case (1):	// read
+            writeReadHID();
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0 + gOffset] = 0; // error status					
+            // IO_BUFFER.Ptr[1 + gOffset] = P1MDOUT;	// P1 push-pull (1) or open-collector (0)
+            // IO_BUFFER.Ptr[2 + gOffset] = P2MDOUT;	// P2 push-pull (1) or open-collector (0)
+            // IO_BUFFER.Ptr[3 + gOffset] = (XBR1 & 0x80) >> 7; // weakPullupDisable
+            P1MDOUT = IOBuf[2];
+            P2MDOUT = IOBuf[3];
+            weakPullupDisable = IOBuf[4];
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 3 OUT = GPIO value (C51F321 P1 and P2) </para>
+        /// 
+        /// </summary>
+        /// <param name="P1"></param>
+        /// <param name="P2"></param>
+        public void GPIOP1P2Out(byte P1, byte P2)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2Out(byte P1, byte P2)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2Out
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 3; // case (3): //GPIO value (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 0; //    case (0):	// write
+            IOBuf[3] = P1; // P1 = IO_BUFFER.Ptr[2 + gOffset];		// P1 HI (1) or LO (0); set P1==1 and P1MDOUT==1 for HI-Z
+            IOBuf[4] = P2; // P2 = IO_BUFFER.Ptr[3 + gOffset];		// P2 HI (1) or LO (0); set P2==1 and P1MDOUT==2 for HI-Z
+            writeHID();
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 3 IN = GPIO value (C51F321 P1 and P2) </para>
+        /// 
+        /// </summary>
+        /// <param name="P1"></param>
+        /// <param name="P2"></param>
+        public void GPIOP1P2In(out byte P1, out byte P2)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2In(out byte P1, out byte P2)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2In
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 3; // case (3): //GPIO value (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 1; //    case (1):	// read
+            writeReadHID();
+            // temp = XBR1;
+            // if (IO_BUFFER.Ptr[2+gOffset] & 1)	// enable weak pullups in case GP pins are open-collector and not connected to anything (which would falsely give '0')
+            // {
+            // 	XBR1 &= 0x7F;
+            // 	Timer0_Init(HALFMS);
+            // 	T0_Wait(2);
+            // }
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0+gOffset] = 0; 		// error status
+            // IO_BUFFER.Ptr[1+gOffset] = P1;		// P1 HI (1) or LO (0)
+            // IO_BUFFER.Ptr[2+gOffset] = P2;		// P2 HI (1) or LO (0)
+            // XBR1 = temp;
+            // SendPacket(); // no need to check for internal call since only the GUI will request a read through this function
+            P1 = IOBuf[2];
+            P2 = IOBuf[3];
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 4 OUT = I2C configuration </para>
+        ///  (IO_BUFFER.Ptr[3+gOffset] &amp; 1) ? (SMB0CF |= 0x10) : (SMB0CF &amp;= 0xEF)
+        /// </summary>
+        /// <param name="gI2Cflags">bit0 == 1: repeated start</param>
+        /// <param name="EXTHOLD">set EXTHOLD bit (SMBus setup / hold time extension)</param>
+        /// <param name="ClearSDAbyTogglingSCL">clear SDA by toggling SCL</param>
+        public void I2CConfigSet(byte gI2Cflags, byte EXTHOLD, byte ClearSDAbyTogglingSCL)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CConfigSet(byte gI2Cflags, byte EXTHOLD, byte ClearSDAbyTogglingSCL)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigSet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 4; // case (4): //I2C configuration -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 0; //    case (0):	// write
+            IOBuf[3] = gI2Cflags;   // gI2Cflags = IO_BUFFER.Ptr[2+gOffset];
+                                    // // bit0 == 1: repeated start (versus stop/start) after write before read (applies to random read only)
+                                    // // bit1 == 1: start random read with a write, but end the write right away without sending reg address (emulate Jungo dongle for debug purposes)
+                                    // // bit2 == 1: repeat transaction if slave NACKs (suggest not to use this)
+                                    // // all flags are OR'd
+            IOBuf[4] = EXTHOLD;    // (IO_BUFFER.Ptr[3+gOffset] & 1) ? (SMB0CF |= 0x10) : (SMB0CF &= 0xEF);	// set EXTHOLD bit (SMBus setup / hold time extension)
+            IOBuf[5] = ClearSDAbyTogglingSCL;   // if (IO_BUFFER.Ptr[4+gOffset] & 1)	// clear SDA by toggling SCL
+                                // {s
+                                // 	IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+                                // 	IO_BUFFER.Ptr[0+gOffset] = 0; 	// transaction error status
+                                // 	IO_BUFFER.Ptr[1+gOffset] = clearSDA();	// clearSDA error status
+                                // 	SendPacket();			// send status of clearing SDA to host
+                                // }
+            writeHID();
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 4 IN = I2C configuration </para>
+        /// (IO_BUFFER.Ptr[3+gOffset] &amp; 1) ? (SMB0CF |= 0x10) : (SMB0CF &amp;= 0xEF)
+        /// </summary>
+        /// <param name="gI2Cflags">bit0 == 1: repeated start</param>
+        /// <param name="EXTHOLD">SMB0CF &amp; 0x10</param>
+        public void I2CConfigGet(out byte gI2Cflags, out byte EXTHOLD)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CConfigGet(out byte gI2Cflags, out byte EXTHOLD)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigGet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 4; // case (4): //I2C configuration -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 1; //    case (1):	// read
+            writeReadHID();
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status					
+            // IO_BUFFER.Ptr[1+gOffset] = gI2Cflags;
+            // IO_BUFFER.Ptr[2+gOffset] = (SMB0CF & 0x10) >> 4; // EXTHOLD
+            gI2Cflags = IOBuf[2];
+            EXTHOLD = IOBuf[3];
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 5 OUT = I2C clock rate (number of counts to overflow) </para>
+        /// <para>SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</para>
+        /// </summary>
+        /// <param name="ReloadTH1">SMBus timer's count value: ReloadTH1 = (byte)(0.5 + (TimerClockMHz * 1000 / SCL_kHz))</param>
+        public void I2CClockSet(byte ReloadTH1)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CClockSet(byte ReloadTH1)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CClockSet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 5; // case (5): //I2C clock rate (number of counts to overflow) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 0; //    case (0):	// write
+            IOBuf[3] = ReloadTH1;
+            // gSMBusClkFreq = gTimer1ClkFreq / 3.0 / IO_BUFFER.Ptr[2+gOffset];	//GUI sends the number of counts to overflow; HID must calculate the desired SMBus clock frequency
+            // TR1 = 0;
+            // Timer1_Init();
+            writeHID();
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 5 IN = I2C clock rate (number of counts to overflow) </para>
+        /// <para>SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</para>
+        /// </summary>
+        /// <param name="TimerClockMHz">SMBus timer's clock frequency (in MHz); expect constant 8</param>
+        /// <param name="ReloadTH1">SMBus timer's count value: SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</param>
+        public void I2CClockGet(out byte TimerClockMHz, out byte ReloadTH1)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CClockGet(out byte TimerClockMHz, out byte ReloadTH1)
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CClockGet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 5; // case (5): //I2C clock rate (number of counts to overflow) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = 1; //    case (1):	// read
+            writeReadHID();
+            // case (1): 	// read -- myHID.I2CClockGet(out TimerClockMHz, out ReloadTH1)
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status
+            // IO_BUFFER.Ptr[1+gOffset] = gTimer1ClkFreq / 1000000 / 3;	//return the SMBus timer's clock frequency (in MHz)
+            // IO_BUFFER.Ptr[2+gOffset] = 256-TH1;						//and return the SMBus timer's count value in order to calculate the SMBus clock frequency; TH1 is the reload value that gets loaded into TL0 upon overflow; the reload value is 256-TH1 since (0)-TH1 gives the proper number of counts to overflow
+            // SendPacket(); // no need to check for internal call since only the GUI will request a read through this function
+            // break;
+            TimerClockMHz = IOBuf[2];
+            ReloadTH1 = IOBuf[3];
+            // todo: OS24EVK-24 how determine SCL from TimerClockMHZ? Observed I2CClockGet() TimerClockMHz = 8 when SCL = 400kHz.
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 6 OUT = I2C transaction </para>
+        /// 
+        /// </summary>
+        /// <param name="deviceAddress">I2C device address, 8-bit left-justified (LSB=R/w bit)</param>
+        /// <param name="numDataBytes"></param>
+        /// <param name="numRegBytes"></param>
+        /// <param name="data"></param>
+        /// <param name="reg"></param>
+        /// <param name="ignoreNACK"></param>
+        public void I2CWrite(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] data, byte[] reg, bool ignoreNACK = false)
+        {
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
+            // alias of existing function
+            // IOBuf[1] = 6; // case (6):	// I2C transaction -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            writeI2C(deviceAddress, numDataBytes, numRegBytes, data, reg, ignoreNACK);
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 6 IN = I2C transaction </para>
+        /// 
+        /// </summary>
+        /// <param name="deviceAddress">I2C device address, 8-bit left-justified (LSB=R/w bit)</param>
+        /// <param name="numDataBytes"></param>
+        /// <param name="numRegBytes"></param>
+        /// <param name="readData"></param>
+        /// <param name="reg"></param>
+        /// <param name="ignoreNACK"></param>
+        /// <returns>status value; may be HID.I2C_NACK_ERROR if ignoreNACK parameter is true</returns>
+        public int I2CRead(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] readData, byte[] reg, bool ignoreNACK = false)
+        {
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
+            // alias of existing function
+            // IOBuf[1] = 6; // case (6):	// I2C transaction -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            return readI2C(deviceAddress, numDataBytes, numRegBytes, readData, reg, ignoreNACK);
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 7 OUT = SPI config </para>
+        /// ((SPI0CFG &amp; 0x30) &gt;&gt; 4) + (SPI0CN &amp; 4)
+        /// </summary>
+        /// <param name="config"></param>
+        public void SPIConfigSet(byte config)
+        {
+            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIConfigSet(byte config)
+            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
+            // IOBuf[1] = 7; // case (7):	// SPI config -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 7 IN = SPI config </para>
+        /// ((SPI0CFG &amp; 0x30) &gt;&gt; 4) + (SPI0CN &amp; 4)
+        /// </summary>
+        /// <param name="config"></param>
+        public void SPIConfigGet(out byte config)
+        {
+            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIConfigGet(out byte config)
+            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
+            // IOBuf[1] = 7; // case (7):	// SPI config -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            config = 0;
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 8 OUT = SPI clock rate </para>
+        /// </summary>
+        /// <param name="SPI0CKR"></param>
+        public void SPIClockSet(byte SPI0CKR)
+        {
+            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIClockSet(byte SPI0CKR)
+            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
+            // IOBuf[1] = 8; // case (8): 	// SPI clock rate -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 8 IN = SPI clock rate </para>
+        /// </summary>
+        /// <param name="SPI0CKR"></param>
+        public void SPIClockGet(out byte SPI0CKR)
+        {
+            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIClockGet(out byte SPI0CKR)
+            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
+            // IOBuf[1] = 8; // case (8): 	// SPI clock rate -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            SPI0CKR = 0;
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 9 = SPI transaction </para>
+        /// </summary>
+        /// <param name="mosiData">SPI MOSI (Master-Out, Slave-In) data to write into slave device</param>
+        /// <param name="misoBuffer">SPI MISO (Master-In, Slave-Out) data buffer containing data bytes received from slave device
+        /// (size will be allocated same size as mosiData)</param>
+        public void SPITransfer(byte[] mosiData, out byte[] misoBuffer)
+        {
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 SPITransfer(byte[] mosiData, out byte[] misoBuffer)
+            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigSet
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            // assign default out values in case of failure
+            int byteCount = mosiData.Length;
+            byte num_bytes = (byte)(byteCount & 0xFF);
+            misoBuffer = new byte[byteCount];
+            for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
+            {
+                // initial dummy data
+                misoBuffer[byteIndex] = 0x55;
+            }
+            IOBuf[1] = 9; // case (9):	// SPI transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            //IOBuf[8] = x; // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
+            for (uint byteIndex = 0; byteIndex < num_bytes; byteIndex++)
+            {
+                IOBuf[8 + byteIndex] = (byte)(mosiData[byteIndex]);
+            }
+            //switch (SPImode)
+            //{
+            //    case 0:
+                    IOBuf[2] = 0; // IO_BUFFER.Ptr[1+gOffset] -- 0 for SPI_mode0: multi-byte SPI transfer.
+                    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- not used
+                    IOBuf[4] = 0; // IO_BUFFER.Ptr[3+gOffset] -- phase_change // !=0 enable changing the clock phase. some slaves change phase between write/read
+                    IOBuf[5] = 0; // IO_BUFFER.Ptr[4+gOffset] -- phase_change_byte // byte index where phase change should happen
+                    IOBuf[6] = num_bytes; // IO_BUFFER.Ptr[5+gOffset] -- num_bytes
+                    IOBuf[7] = 0; // IO_BUFFER.Ptr[6+gOffset] -- not used
+                    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
+                    // SPI_mode0(IO_BUFFER.Ptr[3 + gOffset], IO_BUFFER.Ptr[4 + gOffset], IO_BUFFER.Ptr[5 + gOffset], IO_BUFFER.Ptr);
+                //    break;
+                //case 1:
+                //    IOBuf[2] = 1; // IO_BUFFER.Ptr[1+gOffset] -- 1 for SPI_mode1: two-byte SPI transfer. read flag: enable changing the clock phase on first byte and changing phase between first and second byte
+                //    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- read flag
+                //    // IO_BUFFER.Ptr[3+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[4+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[5+gOffset] -- not used; num_bytes = 2
+                //    // IO_BUFFER.Ptr[6+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
+                //    // SPI_mode1(temp, IO_BUFFER.Ptr);
+                //    break;
+                //case 2:
+                //    IOBuf[2] = 2; // IO_BUFFER.Ptr[1+gOffset] --2 for SPI_mode2: two-byte SPI transfer. read flag: enable changing the clock phase on first byte and sampling the LSb after the second byte read
+                //    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- read flag
+                //    // IO_BUFFER.Ptr[3+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[4+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[5+gOffset] -- not used; num_bytes = 2
+                //    // IO_BUFFER.Ptr[6+gOffset] -- not used
+                //    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
+                //    // SPI_mode2(temp, IO_BUFFER.Ptr);
+                //    break;
+            //}
+            writeHID();
+            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
+            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status					
+            // IO_BUFFER.Ptr[1+gOffset] = gI2Cflags;
+            // IO_BUFFER.Ptr[2+gOffset] = (SMB0CF & 0x10) >> 4; // EXTHOLD
+            //gI2Cflags = IOBuf[2];
+            //EXTHOLD = IOBuf[3];
+            for (uint byteIndex = 0; byteIndex < byteCount; byteIndex++)
+            {
+                misoBuffer[byteIndex] = IOBuf[byteIndex + 2];
+            }
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+
+        /// <summary>
+        /// <para>Firmware command code 23 OUT = enable INT0 (or Mock HID FIFO data diagnostic) </para>
+        /// <para>
+        /// - INT0Enable(0) disabled (EX0=0)
+        /// - INT0Enable(1) real hardware (EX0=1)
+        /// - INT0Enable(2) Mock HID x1 channel    101, 102, 103, ...
+        /// - INT0Enable(3) Mock HID x2 channels   101, 201, 102, 202, 103, 203, ...
+        /// - INT0Enable(4) Mock HID x3 channels   101, 201, 301, 102, 202, 302, 103, 203, 303, ...
+        /// - INT0Enable(5) Mock HID x4 channels   101, 201, 301, 401, 102, 202, 302, 402, 103, 203, 303, 403, ...
+        /// </para>
+        /// </summary>
+        /// <param name="EX0"></param>
+        public void INT0Enable(byte EX0)
+        {
+            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
+            // reset report counter in the HID and enable INT0
+            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in INT0Enable
+            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
+            FlushQueue();
+            Array.Clear(IOBuf, 0, IOBuf.Length);
+            reportID();
+            IOBuf[1] = 23; // enable INT0 -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
+            IOBuf[2] = EX0; // reportTypeFlag; // 1;
+            writeHID();
+            _firmwareINT0Enabled = EX0;
+            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
+        }
+        private byte _firmwareINT0Enabled = 0;
+        public byte FirmwareINT0Enabled { get { return _firmwareINT0Enabled; } }
+
+        /// <summary>
+        /// Search for a device attached to I2C bus, 
+        /// given a list of poossible device addresses, 
+        /// and a constant device ID register to test.
+        /// </summary>
+        /// <param name="I2C_DeviceAddressList_8bitLeftJustified">List of possible I2C device addresses to test. I2C device addresses are 8-bit left-justified (LSB=R/w bit)</param>
+        /// <param name="DeviceId_RegAddress">device register address of a constant "Device ID" register</param>
+        /// <param name="DeviceId_RegValue_Expect">register value of the constant "Device ID" register</param>
+        /// <returns>I2C device addresses, or 0 if not found</returns>
+        public byte SearchI2CdeviceAddressList(byte[] I2C_DeviceAddressList_8bitLeftJustified, byte DeviceId_RegAddress, byte DeviceId_RegValue_Expect)
+        {
+            // https://jira.maxim-ic.com/browse/OS24EVK-57 accelerometer support: SearchI2CdeviceAddressList optional I2C device
+            foreach (byte test_I2C_Address in I2C_DeviceAddressList_8bitLeftJustified)
+            {
+                //try
+                //{
+                    byte[] data = new byte[2];
+                    byte[] reg = new byte[1];
+                    reg[0] = (byte)DeviceId_RegAddress;
+                    // readI2C should already take care of mutex lock / unlock
+                    // https://jira.maxim-ic.com/browse/OS24EVK-59 avoid NACK exception in SearchI2CdeviceAddressList
+                    bool ignoreNACK = true;
+                    int status = readI2C(test_I2C_Address, 2, 1, data, reg, ignoreNACK);
+                    if (status == HID.I2C_NACK_ERROR)
+                    {
+                        continue;
+                    }
+                    else
+                    {
+                        byte DeviceId_RegValue_Actual = data[0];
+                        if (DeviceId_RegValue_Actual == DeviceId_RegValue_Expect)
+                        {
+                            return test_I2C_Address;
+                        }
+                    }
+                //}
+                //catch (Exception)
+                //{
+                //    // myHID.readI2C can throw Exception("invalid I2C address");
+                //}
+            }
+            return 0;
+        }
+
+    }
+}