PORTS Forum
Ports and Interfaces => USB => Topic started by: Pointy on February 11, 2014, 07:01:17 am
-
Hi,
I am trying to read a USB Joystick via rawInput in VB.Net.
Using this (http://www.codeproject.com/Articles/185522/Using-the-Raw-Input-API-to-Process-Joystick-Input) code as a guide.
My code is nearly there but I don't get the correct data, when I call HidP_GetUsages. Comparing the c++ code linked and mine side by side in debug mode the returned data matches up until the above call.
This is how I am declaring HidP_GetUsages...
<DllImport("hid.dll")>
Shared Function HidP_GetUsages(ByVal ReportType As HIDP_REPORT_TYPE, ByVal UsagePage As Short, _
ByVal LinkCollection As UShort, ByVal UsageList As IntPtr, ByRef UsageLength As UInt32, _
ByVal PreparsedData As IntPtr, ByRef Report As Byte, ByVal ReportLength As UInt32) As Integer
End Function
and this is how I am defining the structure...
<StructLayout(LayoutKind.Sequential)> _
Public Structure HIDP_BUTTON_CAPS
Public UsagePage As Short
Public ReportID As Byte
Public IsAlias As Byte
Public BitField As UShort
Public LinkCollection As UShort
Public LinkUsage As Short
Public LinkUsagePage As Short
Public IsRange As Byte
Public IsStringRange As Byte
Public IsDesignatorRange As Byte
Public IsAbsolute As Byte
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=10)>
Public Reserved() As UInt32
Public RangeNotRange As HIDP_BUTTON_CAPS_2
End Structure
<StructLayout(LayoutKind.Explicit)> _
Public Structure HIDP_BUTTON_CAPS_2
<FieldOffset(0)> Public Range As HIDP_BUTTON_CAPS_RANGE
<FieldOffset(0)> Public NotRange As HIDP_BUTTON_CAPS_NOTRANGE
End Structure
Public Structure HIDP_BUTTON_CAPS_RANGE
Public UsageMin As Short
Public UsageMax As Short
Public StringMin As UShort
Public StringMax As UShort
Public DesignatorMin As UShort
Public DesignatorMax As UShort
Public DataIndexMin As UShort
Public DataIndexMax As UShort
End Structure
Public Structure HIDP_BUTTON_CAPS_NOTRANGE
Public Usage As Short
Public Reserved1 As Short
Public StringIndex As UShort
Public Reserved2 As UShort
Public DesignatorIndex As UShort
Public Reserved3 As UShort
Public DataIndex As UShort
Public Reserved4 As UShort
End Structure
It's driving me mad! Please tell me what I am doing wrong!
Regards,
Les
PS. I can paste the whole code if it helps.
-
Where is it going wrong?
-
Where is it going wrong?
The HidP_GetUsages isn't returning what it should.
I have a joystick that has 128 buttons , my test firmware just sets each button on and then off one at a time. Using the c++ code and my VB directx code it loops through all 128 buttons no problem.
With my rawInput code it goes up to 120, and the last few button seem to get a random value.
As a test I dumped both the c++ pPreparsedData to file along with my VB version and both files are identical. The HidP_GetCaps returned data is identical to the c++ code. The HidP_GetButtonCaps returned data is the same in both programs other than the 10 reserved ULONGs (not sure if that is significant though).
HidP_GetUsages is supposed to return the buttons which are on and match the UsagePage, but it not working in my VB app.
As another test in my joystick firmware I have only button 128 on, in my app I am getting random results with the last few buttons, as if I am missing a byte somewhere, and it is just showing random memory for that last byte.
I just can't figure out where I have gone wrong.
Regards,
Les
-
The returned UsageList buffer has incorrect data in it?
Is the data in the buffer correct except for the "last few" buttons?
How do you declare the buffer and assign the UsageList IntPtr to it?
-
The returned UsageList buffer has incorrect data in it?
Is the data in the buffer correct except for the "last few" buttons?
The UsageList buffer should return a list of buttons that are on. So with button 128 on, HidP_GetUsages should return a usageLength of 1, and the buffer should contain an array of 128 uShorts. The First item in that array should be 128 (the button that is pressed) and the rest should be 0.
I just ran my program 6 times and got the following results (obviously the USB device is sending the same data all the time for testing)...
usageLength: 1, pUsageBuffer: 126 followed by all 0
usageLength: 1, pUsageBuffer: 128 followed by all 0
usageLength: 2, pUsageBuffer: 125,126 followed by all 0
usageLength: 2, pUsageBuffer: 127,128 followed by all 0
usageLength: 0, pUsageBuffer: all 0
usageLength: 1, pUsageBuffer: 127 followed by all 0
I can't figure out why it appears to work up to button 120.
How do you declare the buffer and assign the UsageList IntPtr to it?
Here's pretty much the whole Sub (pRawInput is a RAWINPUT structure passed to the sub)...
' get the preparseddata buffer size
Dim pcbSize As System.UInt32 = Convert.ToUInt32(0)
GetRawInputDeviceInfo(pRawInput.header.hDevice, RIDI_PREPARSEDDATA, IntPtr.Zero, pcbSize)
' get the actual preparseddata
pPreparsedData = Marshal.AllocHGlobal(Convert.ToInt32(pcbSize))
GetRawInputDeviceInfo(pRawInput.header.hDevice, RIDI_PREPARSEDDATA, pPreparsedData, pcbSize)
Dim bData(CInt(pcbSize)) As Byte
Marshal.Copy(pPreparsedData, bData, 0, CInt(pcbSize))
' get hid capabilities
Dim Capabilities As New HIDP_CAPS
HidP_GetCaps(pPreparsedData, Capabilities)
' create buffer for button data
pButtonBuffer = Marshal.AllocHGlobal(Convert.ToInt32(Marshal.SizeOf(GetType(HIDP_BUTTON_CAPS)) * Capabilities.NumberInputButtonCaps))
' get the number of button capabilities
Dim capsLength As Integer = Capabilities.NumberInputButtonCaps
' get button capabilities into buffer
HidP_GetButtonCaps(HIDP_REPORT_TYPE.HidP_Input, pButtonBuffer, capsLength, pPreparsedData)
' marshal buffer to HIDP_BUTTON_CAPS structure
Dim pButtonCaps As HIDP_BUTTON_CAPS = CType(Marshal.PtrToStructure(pButtonBuffer, GetType(HIDP_BUTTON_CAPS)), HIDP_BUTTON_CAPS)
' calc number of buutons from usagemax & usagemin
Dim g_NumberOfButtons As Integer = pButtonCaps.RangeNotRange.Range.UsageMax - pButtonCaps.RangeNotRange.Range.UsageMin + 1
' set class variable buttoncount
ButtonCount = g_NumberOfButtons
' create buffer for return button usage count
Dim usageLength As UInt32 = CUInt(g_NumberOfButtons)
' create buffer for button values
pUsageBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(UInt16)) * 128)
' get button values into buffer
If HidP_GetUsages(HIDP_REPORT_TYPE.HidP_Input, pButtonCaps.UsagePage, 0, pUsageBuffer, usageLength, pPreparsedData, pRawInput.hid.bRawData, pRawInput.hid.dwSizHid) <> HIDP_STATUS_SUCCESS Then
Throw New ApplicationException("An error occurred while retrieving the list of button usages!")
End If
' copy button usage back to array
Dim button(127) As Short
Marshal.Copy(pUsageBuffer, button, 0, 128)
I really appreciate you taking the time to look at this, it is driving me insane.
Regards,
Les
-
Just as a follow up I just did another test...
This time in the USB firmware I switch the 1st 64 buttons on and the last 64 button off, and then wait 100ms and then do the opposite so that the first 64 buttons are off and the last 64 are on.
The first 120 buttons always show correctly and the last 8 will be random, but the last 8 don't flash and change, they stay static. They are just different each time I start the program.
Looking at the info passed in and out of HidP_GetUsages I think we can rule out the following...
ReportType: I can't see this being the problem and here is the declaration...
Public Enum HIDP_REPORT_TYPE
HidP_Input
HidP_Output
HidP_Feature
End Enum
UsagePage: This must be correct otherwise the first 120 buttons wouldn't work.
LinkCollection: This is not used.
UsageList: I am sure the buffer is correct ,otherwise again, why do the first 120 buttons work?
UsageLength: This is definitely being set to 128 before the call.
PreparsedData: I am sure the data is correct, otherwise the capabilities would be wrong, amongst other things.
By the process of elimination I think we can rule out the the above.
This leaves us Report & ReportLength, which leads me to a part I don't quite understand...
The RAWHID structure from MSDN...Members
dwSizeHid
Type: DWORD
The size, in bytes, of each HID input in bRawData.
dwCount
Type: DWORD
The number of HID inputs in bRawData.
bRawData
Type: BYTE[1]
The raw input data, as an array of bytes.
Remarks
Each WM_INPUT can indicate several inputs, but all of the inputs come from the same HID. The size of the bRawData array is dwSizeHid * dwCount.
I don't get the purpose of bRawData , even in the c++ code the bRawData is always 1 byte. My dwSizeHid is 65 bytes, and dwCount is 1 so shouldn't bRawData be an array of 65 bytes?
Regards,
Les
-
Some comments about bRawData here:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645549%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645596%28v=vs.85%29.aspx
-
Some comments about bRawData here:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645549%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645596%28v=vs.85%29.aspx
I looked at the pages several times but never noticed the comments.
After using some of the code posted by Đonny, I was able to get it working, so many thanks Jan for pointing the comments out.
I was correct in that it was the bRawData that was causing the problem, I just needed the right structure declaration (http://msdn.microsoft.com/en-us/library/windows/desktop/ms645549%28v=vs.85%29.aspx).
Now I just have one teensy problem, I can't figure out how to get multiple values of the same type using HidP_GetUsageValue. I have 4 hats switches but it always seems to return the data for the 1st. Same thing for my sliders.
Best I go do some more reading.
Regards,
Les
-
Best I go do some more reading.
Looks like I need to use HidP_GetUsageValueArray, but again it's not working for me.
This is turning out to be much harder than i thought, but at least I am slowly getting there.
Regards,
Les
-
Ok it would seem that HidP_GetUsageValueArray is returning the HIDP_STATUS_NOT_VALUE_ARRAY error.
This is my USB descriptor...
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x04, // Usage (Joystick)
0xA1, 0x01, // Collection (Application)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x80, // Report Count (128)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (Button #1)
0x29, 0x80, // Usage Maximum (Button #128)
0x81, 0x02, // Input (variable,absolute)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection ()
0x15, 0x00, // Logical Minimum (0)
0x27, 0xFF, 0xFF, 0, 0, // Logical Maximum (65535)
0x75, 0x10, // Report Size (16)
0x95, 23, // Report Count (23)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x32, // Usage (Z)
0x09, 0x33, // Usage (Rx)
0x09, 0x34, // Usage (Ry)
0x09, 0x35, // Usage (Rz)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x09, 0x36, // Usage (Slider)
0x81, 0x02, // Input (variable,absolute)
0xC0, // End Collection
0x15, 0x00, // Logical Minimum (0)
0x25, 0x07, // Logical Maximum (7)
0x35, 0x00, // Physical Minimum (0)
0x46, 0x3B, 0x01, // Physical Maximum (315)
0x75, 0x04, // Report Size (4)
0x95, 0x04, // Report Count (4)
0x65, 0x14, // Unit (20)
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x39, // Usage (Hat switch)
0x09, 0x39, // Usage (Hat switch)
0x09, 0x39, // Usage (Hat switch)
0x09, 0x39, // Usage (Hat switch)
0x81, 0x42, // Input (variable,absolute,null_state)
0xC0 // End Collection
Surely if I call HidP_GetUsageValueArray with a UsagePage of 1 and a Usage of 0x39 it should return and array of 4?
I have also noticed that in both my program and the vc++ sample that ReportCount is always 1 for every HIDP_VALUE_CAPS returned by HidP_GetValueCaps.
After reading this (http://msdn.microsoft.com/en-us/library/windows/hardware/ff543494(v=vs.85).aspx) page, I thought I would also have a look at doing it a slightly different way by using HidP_GetData (http://msdn.microsoft.com/en-us/library/windows/hardware/ff539718(v=vs.85).aspx) but HidP_MaxDataListLength (http://msdn.microsoft.com/en-us/library/windows/hardware/ff539768(v=vs.85).aspx) always returns 0.
Am I missing something again?
Regards,
Les
-
Show your code that declares and calls HidP_GetUsageValueArray and/or HidP_GetData.
-
I gave up with this after reading this (http://msdn.microsoft.com/en-us/library/windows/hardware/ff542426(v=vs.85).aspx), which said I should be using ReadFile anyway.
I just use some of the above code to get the details about the Hid joystick, such as number of buttons and axes and then use ReadFile to get the data from it. It's all working beautifully up to the point where I grab the report. I just need to convert that report into actual values.
Regards,
Les
-
Show your code that declares and calls HidP_GetUsageValueArray and/or HidP_GetData.
Just out of interest I would like to know where I was going wrong.
<DllImport("hid.dll")> _
Shared Function HidP_GetData(ByVal ReportType As HIDP_REPORT_TYPE, ByRef DataList As IntPtr, ByRef DataLength As UInteger, ByRef PreparsedData As IntPtr, ByRef Report As Byte, ByVal ReportLength As UInteger) As Integer
End Function
<DllImport("hid.dll")> _
Shared Function HidP_MaxDataListLength(ByVal ReportType As HIDP_REPORT_TYPE, ByRef PreparsedData As IntPtr) As Integer
End Function
Public Structure HIDP_DATA
Public DataIndex As UShort
Public Reserved As UShort
Public Value As UnionValue
End Structure
<StructLayout(LayoutKind.Explicit)> _
Public Structure UnionValue
<FieldOffset(0)> Public RawValue As UInteger
<FieldOffset(0)> Public State As Byte
End Structure
Public Enum HIDP_REPORT_TYPE
HidP_Input
HidP_Output
HidP_Feature
End Enum
Dim pPreparsedData, pDataBuffer As IntPtr
If Not HidD_GetPreparsedData(CurrentJoystick.Handle, pPreparsedData) Then
Debug.Print("HidD_GetPreparsedData failed")
End If
Dim Capabilities As New HIDP_CAPS
If Not HidP_GetCaps(pPreparsedData, Capabilities) Then
Debug.Print("HidP_GetCaps failed")
CloseHandle(CurrentJoystick.Handle)
End If
Dim count As Integer = HidP_MaxDataListLength(HIDP_REPORT_TYPE.HidP_Input, pPreparsedData)
pDataBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(HIDP_DATA)) * count)
If Not HidP_GetData(HIDP_REPORT_TYPE.HidP_Input, pDataBuffer, pPreparsedData, count, pRawInput.hid.bRawData(0), pRawInput.hid.dwSizeHid) Then
Debug.Print("HidP_GetData failed")
End If
HidP_MaxDataListLength returns 0 and HidP_GetData returns HIDP_STATUS_INVALID_PREPARSED_DATA
Surely if the PreparsedData was invalid then the HidP_GetCaps would fail?
Regards,
Les
-
I gave up with this after reading this (http://msdn.microsoft.com/en-us/library/windows/hardware/ff542426(v=vs.85).aspx), which said I should be using ReadFile anyway.
That's just telling you to use ReadFile (or you can use FileStreams), which retrieves reports obtained using interrupt transfers, instead of HidD_GetInputReport, which requests an Input report using a control transfer.
-
Have you tried using breakpoints and examining what you are passing to the API calls and what they are returning? Sometimes that can offer a clue. For example, is the report data returned by ReadFile what you expect? If so, work forward from there to find a parameter that is suspicious.
-
Have you tried using breakpoints and examining what you are passing to the API calls and what they are returning? Sometimes that can offer a clue. For example, is the report data returned by ReadFile what you expect? If so, work forward from there to find a parameter that is suspicious.
I have debugged this until I am blue in the face.
If I move the HidP_GetCaps in the code above to after the part that's giving me an error it still works, so surely that means the PreparsedData is OK.
I added SetLastError:=True to the 2 functions and get the following errors...
HidP_MaxDataListLength failed with Error: 87
HidP_GetData failed with Error: 1008
I don't know how that helps me though.
Lastly if I add capsLength =HidP_MaxDataListLength(HidP_Input,pPreparsedData);
to the vc++ code linked in the first post, it works as expected with the same USB device which makes me think there is not a problem with the descriptor.
Regards,
Les
-
I am such a doofuss at times!
Sometimes you look a code for so long you don't see the obvious. Both of the declares had ByRef instead of ByVal for pPreparsedData and the HidP_GetData call I had swapped pPresparsedData and DataLength.
DOH!
By George I think I have finally cracked it!
Regards,
Les
-
Excellent!