| Composite Devices - Part 3 |
|
|
|
| Monday, 07 February 2011 10:32 |
|
In our last installment you learned, step by step, how to build up a USB Composite Device in HIDmaker FS. Along the way, you saw that the set of data items that is used by one of the USB Interfaces in your Composite device (which we call an Interface Data Set) can be either made from scratch, read from a file that was saved from another HIDmaker project, or some mixture of the two. You also read about why both the Name and the Usage (= the combination of Usage Page and Usage ID) for each USB transfer variable must be unique across your whole project, so both your compiler and the PC's HID device drivers can always distinguish each variable from all other variables in your project. This time we'll talk about how HIDmaker's generated PC code talks to multiple Interaces in a USB Composite Device, and how (and why) you can modify that behavior.
How HIDmaker's Generated PC Program Normally Works with a USB Composite DeviceIn the usual situation, the PC program that HIDmaker generates for a USB Composite Device expects to find more than one single USB Interface for the peripheral device it is matched to. When Windows finds tells the program that your device is attached, then the PC program will open all of the USB Interfaces in this Composite device, and find all the Reports and variables that it expects to find in each Interface. For the 2-Interface BInOut project we created last time (which is one of the sample projects in your HIDmaker FS installation), the class that represents your whole device is called TBothInOut. The file that defines the TBothInOut class has a filename that is always BInOut_L.*. The extension (which we showed as "*" here) depends on which PC compiler it was generated for: it would be .vb for a Visual Basic .NET project, .cs for a C# .NET project, .pas for a Delphi project, and so on. The "BInOut" part of the file name is just the project name. The "_L" part tells us that this is the "library" for all the variables and Reports that are in the entire device. The class that is defined in this file contains properties, methods, and events. Among the properties of this class you will find a separate object that represents each USB transfer variable. In fact, a custom generated comment block at the top of this file tells us what variables and reports are in this class for this particular project. Here's what it looks like for a Delphi file that was generated for the BInOut project: <snip> // This HID has the following Reports, which contain the following variables: // // OutputRptA : // OutData1 - 5 bit simple variable // OutArray1 - array variable, 9 elements, each 8 bits long // You may write these variables by first setting the properties OutData1 etc., // then calling function WriteOutputRptA to send the data to the device. // For array variables, see the special instructions below. // // InputRptB : // InData1 - 3 bit simple variable // InArray1 - array variable, 9 elements, each 8 bits long // You may read simple variables by first calling function ReadInputRptB to get // the report from the device, then accessing properties like OutData1.UnscaledValue etc. // For array variables, see the special instructions below. // // OutputRptC : // OutData2 - 7 bit simple variable // OutArray2 - array variable, 10 elements, each 8 bits long // You may write these variables by first setting the properties OutData1 etc., // then calling function WriteOutputRptC to send the data to the device. // For array variables, see the special instructions below. // // InputRptD : // InData2 - 4 bit simple variable // InArray2 - array variable, 16 elements, each 8 bits long // You may read simple variables by first calling function ReadInputRptD to get // the report from the device, then accessing properties like OutData1.UnscaledValue etc. // For array variables, see the special instructions below. // // // This class also provides methods like ReadAllInputRpts and // WriteAllOutputRpts to simultaneously update all reports of each time // with a single function call. // //*****************************************************************************
</snip> Note that this device has two USB Interfaces, each with an Input Report and an Output Report. When the PC program is notified that the device it is matched to is connected to the PC via USB, then the program will (normally) tell the operating system to open each one of those Interfaces in the device. If you only want to communicate with one USB Interface at times, HIDmaker's generated source code provides a separate routine to read the Input Report from each USB Interface: for this particular project, these routines are named ReadInputRptB() and ReadInputRptD(). Similarly, the generated code provides a separate routine to write the Output Report to each individual USB Interface: these are named WriteOutputRptA() and WriteOutputRptC() in this particular project. Alternatively, you can call ReadAllInputReports() to read the data from all USB Interfaces in the device at the same time if you prefer, and you can call WriteAllOutputReports() to send data to both Interfaces at the same time if you want.
Special Considerations for Mouse and Keyboard InterfacesWe mentioned that the example project we created last time did not contain any USB Interfaces that identify themselves as either an official Keyboard or an official Mouse, to avoid certain complications. The complications we are speaking of have to do with the fact that whenever Windows finds a USB Interface that is a keyboard or a mouse, Windows will automatically open that Interface for exclusive use by the operating system. That means no normal "User Mode" PC application (as opposed to a "Kernal Mode" device driver) can read from or write to a keyboard or a mouse directly. Any attempt to open a keyboard or mouse device for direct I/O will produce an error message from the operating system. This affects us because the PC program that HIDmaker creates for a USB Composite Device tries to open each of the USB Interfaces for I/O. If at least one of these USB Interfaces is a keyboard or a mouse, then the generated program wouldn't work. That's OK, we have a really easy workaround, which is made possible by HIDmaker's flexible architecture, the Interface Data Set concept, and the .rdi data files that HIDmaker uses. Here's the trick: To make a PC program that will ignore a keyboard or mouse Interface, follow these simple steps:
This Opens Up Interesting Possibilities...Even if you don't have a keyboard or mouse Interface in your device, you can generate a separate PC program that talks to each one of your device's Interfaces individually. Then, you can run all those separate programs at the same time if you want, or you can run each program at a different time. Let's see how we can do that with our BInOut example program, which contains 2 USB Interfaces. In creating this project, we have followed the rules and have given each Interface a unique Top Level Usage, like so:
Remember that the Top Level Usage of each USB Interface is the combination of Usage Page and Usage ID of the top level Application Collection. Even though the Usage Page is the same for both Interfaces, it is enough that the Usage IDs of these Interfaces are different. Now we have HIDmaker FS generate two separate PC programs, one for each Interface. The code that HIDmaker FS has generated for each of these two projects is almost good enough, but not quite. Here's the catch: Suppose we try to use the PC program that only knows how to talk to the first USB Interface in the device, Interface 0. If you compile the code exactly the way HIDmaker generated it, the program will try to open all matching USB Interfaces - that is, of Interfaces, in all HID devices that are currently connected to the PC, that have the expected Vendor ID and Product ID. The problem is, in a USB Composite Device, both Interfaces are part of the same device, so they both have the same Vendor ID and Product ID. If you try this, the PC program will mistakenly open both Interfaces, because it was only told to look any Interface that has the right Vendor ID and Product ID. It will open both Interfaces successfully, but it will fail when it tries to read the data from the second Interface in the device - the data will have a different format, and probably a different length, than it expected. (Remember that this is the PC program that only knows about the data in the first Interface, right?) Luckily, there is a very simple fix. Our HIDagent object can be told to narrow down its search criteria, to match other factors in addition to Vendor ID and Product ID. We just need to add a few lines of code to ALSO look for the right Top Level Usage. We can do that by adding the following 4 lines of code (shown in red) to the OpenOurHID routine in the generated code:
VB .NET sample code:<snip> Private Function OpenOurHid() As Boolean Dim Success As Boolean Dim GiveUp As Boolean Dim Choice As Integer Dim CrLf As String Dim Prompt As String ' Do HIDagent.VID_To_Match = MY_VID HIDagent.PID_to_Match = MY_PID HIDagent.SearchOnPID = True HIDagent.SearchOnVID = True ' Added by hand HIDagent.SearchOnUsagePage = True HIDagent.UsagePage_To_Match = &HFF00S ' &HFF00 or 65280 decimal HIDagent.SearchOnUsage = True HIDagent.Usage_To_Match = &HFFF1S ' &HFFF1I or 65521 GiveUp = False Success = HIDagent.OpenAllMatchingIntfs If Not Success Then Beep() CrLf = Chr(13) & Chr(10) Prompt = "Unable to open USB device: " & CrLf & CrLf & MY_PROD & CrLf & CrLf & "Please attach the device and click OK," & CrLf & " or click Cancel to exit" Choice = MsgBox(Prompt, MsgBoxStyle.OKCancel + MsgBoxStyle.Information + MsgBoxStyle.ApplicationModal)
' etc...
</snip>
The 4 lines we showed above are for a Visual Basic .NET version of the PC program. If you prefer to use a different one of the many PC compilers we support, the lines you add will be very similar to those shown above. For example, here are the same 4 lines (shown in red) added to a Delphi version of the same generated program:
Delphi sample code:<snip> function TMainForm.OpenOurHID: Boolean; var Success, GiveUp : Boolean; Choice : Integer; begin repeat HIDagent.VID_To_Match := MY_VID; HIDagent.PID_To_Match := MY_PID; HIDagent.SearchOnPID := True; HIDagent.SearchOnVID := True;
// Added by hand HIDagent.SearchOnUsagePage := True; HIDagent.UsagePage_To_Match := 65280; HIDagent.SearchOnUsage := True; HIDagent.Usage_To_Match := 65521;
GiveUp := False; Success := HIDagent.OpenAllMatchingIntfs; if not Success then begin Beep; Choice := MessageDlg('Unable to open USB device: '+#13+#10 +#13+#10
// etc... </snip>
Here's how this looks in action:
The screen shot below shows 3 different PC programs (all generated by HIDmaker FS) that are talking to the same USB Composite Device at the same time.
It is important to understand several things:
How To Use This Technique To Make More ProfitsThink about what this kind of capability can let you do. Here are some possibilities:
We recognize that these revenue enhancing approaches might not appeal to everybody. You might be inclined to think that you are doing something sneaky - trying to force customers to pay extra to be able to use capabilities that are already there, in the device he already purchased. Yes, it might seem unfair if you did it this way. The applicability of these techniques to your product will depend on your customers and your market, and how you phrase your offer. If you can offer to sell them a separate PC program that talks to that second USB Interface, and that separate PC program truly adds value, then there is nothing wrong with asking them to pay for that extra capability. Look at it this way. Your development time has value. You can offer customers a product with less functionality at a lower price, which will increase your sales by making your product affordable to more people. Then later, if a customer decides he wants the extra capability, he can pay for it then. This is just a way of letting your customers pay for only the functions that they need. It's kind of like cable television. Every subscriber has the same cable box, and gets the same signals coming in on the cable. If you want to view premium channels, though, you have to pay for them.
Got a comment on what you just read? Let us know - just add a comment below. |
Composite Devices 3





0 Comments