Let's take a look at parts of the actual source code that HIDmaker FS has generated for our Varty16 sample project. You will notice that HIDmaker's USB Advisor tends to leave helpful notes at various places in the generated source code, to aid your understanding and to suggest places where you might want to put code for certain important operations.

This page shows what the generated code for Microchip C18 looks like:

 

Test value declarations

HIDmaker cannot know what I/O pins your device is going to use. So, to give you maximum flexibility, HIDmaker's generated code does no hardware dependent I/O other than USB. The generated code will compile and run and send data back and forth between the PC and the PIC device, but the generated code only sends test values: numeric constants.

(That's OK, you'll see below how you can fix that by adding a little bit of "real I/O" code. )

Toward the top of your C18 program, you will see place where these test value constants are declared in your project:

 

Test Value Declarations in C18
 
// Test values for Endpoint 1 IN Variables:
// =======================================
#define TEST_BYTECOUNT 65
#define TEST_DATA3_0 1
#define TEST_DATA3_1 2
#define TEST_DATA3_2 3
#define TEST_DATA3_3 4
#define TEST_DATA3_4 5
#define TEST_DATA3_5 6
#define TEST_DATA3_6 7
#define TEST_DATA3_7 8
#define TEST_DATA3_8 9
#define TEST_DATA3_9 10
#define TEST_DATA3_10 11

and so on, for all the data items and array elements in your project.

 

Data item declararations

Since HIDmaker FS works on a Principle of Direct Transfer of Variables , for every data item that you define in HIDmaker's Visual Data Designer , you will see a variable declared in the generated source code on both sides.

Recall that this project uses a mixture of odd-sized data items:

 

Input Report A :

In8bit - 8 bit simple variable

In9bit - 9 bit simple variable

In16bitArray - array variable, 15 elements, each 16 bits long

In13bit - 13 bit simple variable

In5bit - 5 bit simple variable

In8bitArray - array variable, 63 elements, each 8 bits long

 

Output Report B :

Out8bitArray - array variable, 6 elements, each 8 bits long

Out7bit - 7 bit simple variable

 

Here are the actual declarations for these variables in C18, as generated for this specific project by HIDmaker FS :

USB Transfer Variable Declarations
// ************************************************************************
// Report Variables, which you defined in HIDmaker's Visual Data Designer ,
// to be sent to/from PC via USB:
// ************************************************************************
//
// For your convenience, multi-bit data items are declared as byte or word
// size variables, even if only a few bits are needed. Single-bit data items,
// while not natively supported by Microchip C18, are packed into holding bytes.
// Subroutines are provided by HIDmaker and called by this program to pack this
// data into the small packets needed for USB transmission.
 
 
union AsByte
{
unsigned char AsChar;
struct As_Bits
{
unsigned b0 : 1;
unsigned b1 : 1;
unsigned b2 : 1;
unsigned b3 : 1;
unsigned b4 : 1;
unsigned b5 : 1;
unsigned b6 : 1;
unsigned b7 : 1;
} bits;
};
 
// Declare the actual storage for the Report variables
 
// Endpoint 1 IN variables:
// ========================
unsigned char Bytecount;
// This is an ARRAY: Data3[19]
unsigned char Data3[19];
 
// Endpoint 1 OUT variables:
// =========================
// This is an ARRAY: Data1[10]
unsigned char Data1[10];
 
 

 

Initializing USB variables

HIDmaker creates a special routine where you can initialize your USB data items if you need to. (It is especially important for you to initialize any bi-directional Feature Report variables that your project may have.)

Initializing USB Transfer Variables
// ************************************************************************
// InitUSBVars:
// Initializes all data items that will be transferred over USB. This is
// especially important for Feature items, that can be read and written
// by the PC.
//
// HIDmaker will automatically initialize certain variables here, if needed.
// You may add other initialization code here if you choose.
// ************************************************************************
 
void InitUSBVars(void)
{
USB_Enum_Complete = 0;
}
 
// ************************************************************************
// Main program, including USB Init Code:
// ************************************************************************
 
void main()
{
unsigned char ReportInProgress; // Set to true if more data needs to be sent or received
unsigned char EP1XmtDataReady; // Set to true if data needs to be sent again
unsigned char EP2XmtDataReady; // Set to true if data needs to be sent again
unsigned char HandleEp1Rcv; // We need to handle the data received from EP1Out
unsigned char HandleEp2Rcv; // We need to handle the data received from EP2Out
 
 
 
InitUSBVars();
 
 

 

I/O Pin declarations

HIDmaker FS has no way of knowing what I/O pins you plan to use in your project, which ones will be digital, which will be analog, or whether some pins will be dedicated for use by PIC modules such as the USART, PWM, or whatever.

As a convenience to you, HIDmaker's generated C18 code provides, in commented out form, some lines initialization codes for the PIC registers that set these choices:

Initializing PIC Registers
 
// USB Advisor: You may wish to initialize some things here, before the
// USB initialization happens. Initialization code placed here is sure
// to run, even if the USB initialization fails (perhaps because the
// device was never connected to a USB cable).
// For example:
 
// The following lines initialize real I/O on a Pic Proto USB board
// INTCON2bits.NOT_RBPU = 0; // Enable PortB Pullups
// TRISB = 0b11111100; // PORTB to all inputs except LED1 and LED2
// TRISA = 0b11111111; // PORTA to all inputs, esp POT1 and POT2
// ADCON0 = 0;
// ADCON1 = 0b00001101; // Only A/D channels 0 and 1 are enabled
// ADCON2 = 0b00100101; // Left justified, 8TAD, FOSC/16
 
 

 

These lines are placed close to the beginning of the executable part of the C18 program, as shown above. Simply uncomment these lines and modify them according to the needs of your project.

 

Sending and Using Data with HIDmaker's USB Variables

Remember that HIDmaker's USB variables are transfer variables, each one being sort of like a shipping create with a label on it, showing the variable's name.

This is just like the situation when you receive a package containing the copy of HIDmaker FS that you ordered. You can't DO much with it until you actually take it out of the package and use it: install it on your PC and run it.

That's the way it is with HIDmaker's USB variables. A HID class Report (an Input Report, an Output Report, or a Feature Report) is like a truckload of packages that you send or receive. Each HIDmaker variable is like one of the packages. If a truckload of packages arrives, you want to open each package and use whatever was inside.

If an Output Report arrives, you want to make use of whatever data was contained in each of the USB variables in that Output Report. HIDmaker FS makes that really simple.

Suppose one of the data items in an Output Report (of a different HIDmaker project) is a 1-bit variable that is supposed to turn on an LED on your board. We'll call that data item LED1 in HIDmaker's Visual Data Item. For our project, we expect that when the PC sets LED1 = 1 and sends it to our peripheral, the peripheral is supposed to turn the LED on. Sending LED1 = 0 should turn off the LED on our board.

If the LED on your board is connected to pin RB0 or PORTBbits.RB0, all you need to do to turn the LED on or off is to assign the value in transfer variable LED1 to the I/O pin, like this:

 

PORTBbits.RB0 = LED1

 

That copies the 1 or 0 value from the LED1 transfer variable to the digital pin connected to the LED on your board, turning it on or off.

Over time, if you haven't looked at your project software for a few months, you may find it hard to remember just what PORTB.0 is connected to. As a suggestion to make your code easier to read and maintain, HIDmaker FS creates a commented section for defining variables that you may want to use help:

Declaring Real I/O Variables
 
// ************************************************************************
// Declare other global variables you will need HERE:
// ************************************************************************
 
// USB Advisor: This would be a good place to define app-specific variables
// for your particular device. For example:
 
// Defines for "Real I/O" on a Pic Proto USB board
//#define realLED1 PORTBbits.RB0
//#define realLED2 PORTBbits.RB1
//#define realButton1 PORTBbits.RB4
//#define realButton2 PORTBbits.RB5
 
// Enable the next line to declare a buffer for USBwatch user defined messages:
//static unsigned char MsgBuf[6] = {0,0,0,0,0,0};
 
 

 

The variable realLED1 is the same as PORTB.0, but a lot more helpful in telling us what it actually DOES. Then, our assignment statement that turns the LED on or off becomes

realLED1 = LED1

which is a little easier to read and understand.

This comment block shown above is just placed in the generated code as a suggestion for your optional use. Just add some similar lines if you want to make your code more readable and maintainable.

 

Main loop

The Developers Guide to USB HID Peripherals, in the HIDmaker FS Users Guide, shows that HIDmaker's peripheral side code can be set up to operate in several different Operating Models. The code that is generated by HIDmaker FS uses Peripheral Model, which assumes that the device is powered by, and always connected to, the USB cable. (Other operating models are explained in the Developers Guide, such as the Data Logger Operating Model, for devices that are self powered and only connected to the USB occasionally. The Developers Guide to USB HID Peripherals shows you how to convert HIDmaker's generated peripheral code to these other operating models.)

The main loop of HIDmaker's generated C18 code is where the USB I/O occurs.

Top of Main Loop
 
// ************************************************************************
// Main loop:
// This code is structured so that it looks for data to receive, or send,
// without getting stuck on any one of the endpoints until a report
// from or to the PC has been started. Once a report HAS been started,
// we stay with that endpoint until the entire report has been completed.
// ************************************************************************
 
// Main Loop:
while (1)
{
MainLoop:
USBService(); // Service USB functions periodically
 
// This is a NORMAL or "simple" device. It has a single
// USB Configuration, which contains a single USB Interface .
 
 
// If a Report is coming to us via Endpoint 1 Out, get it all...
//
HandleEp1Rcv = 0;
 

 

Where you handle Output Variables

For a device that only has a single USB Interface , the top half of the main loop contains code that:

  • receives an Output Report from the PC whenever one is sent,
  • unpacks it to the individual HIDmaker USB transfer variables you have defined for your project,
  • and provides a well marked area for you to handle the data that has just arrived.

Here is how that well-marked area looks in C18 for our Varty16 project:

Where you handle Output Variables
        // --------------- ADD USER "EP1 RCV" CODE HERE: ------------------
 
// Add code here to handle the newly-received data from Endpoint 1 Out
// C1 I0
 
// USB Advisor: Here is where you add code to make use of the data that
// has arrived from the PC.
// For example:
 
// The following 2 lines provide real I/O for a PicProto USB board
//realLED1 = LED1;
//realLED2 = LED2;
 
 
 
// "Real I/O" code for a particular project using a PicProto USB board
//realLED1 = LED1;
//realLED2 = LED2;
 
// --------------- END USER "EP1 RCV" CODE -----------------
 

This is where you add your own code to USE all the variables that have just been sent to your peripheral device in the Output Report.

 

Example: How to light an LED

Our Varty16 project doesn't have a variable called LED1, but if it did, this is where we would place a line of code to use it to light up an LED on our board. If we also had defined a local variable realLED1 that sets which I/O pin is actually connected to our hardware LED, as discussed above, we could just use the same code as shown in the comment, like so:

How To Light An LED Over The USB
        // --------------- ADD USER "EP1 RCV" CODE HERE: ------------------
 
// Add code here to handle the newly-received data from Endpoint 1 Out
// C1 I0
 
// USB Advisor: Here is where you add code to make use of the data that
// has arrived from the PC.
// For example:
 
// The following 2 lines provide real I/O for a PicProto USB board
//realLED1 = LED1;
//realLED2 = LED2;
 
 
 
// "Real I/O" code for a particular project using a PicProto USB board
realLED1 = LED1;
 
 
// --------------- END USER "EP1 RCV" CODE -----------------
 

 

It's that simple in HIDmaker FS !

 

Where you prepare Input Variables

In a HIDmaker FS project that has a single USB Interface , the bottom half of the main loop contains code that

  • prepares data,
  • and then packs it together and sends it to the PC in an Input Report.

"Preparing data" is done by you: it is where you would add your own code to read buttons on your board, or read A/D channels, read sensors, or do whatever you need to do to get a fresh set of data to send to the PC. There is a clearly marked section in the generated main loop, where you should add this code:

Where you prepare Input Variables
    // --------------- ADD USER "EP1 XMT" CODE HERE: ------------------
 
// C1 I0
 
// Add code here to generate data to transmit to PC via Endpoint 1 In
// This code should:
// set flag EP1XmtDataReady = 1 if there is new data to send, or
// set flag EP1XmtDataReady = 0 if data has NOT changed yet
 
 
 
// USB Advisor: This is where you add code to generate data that gets
// send to the PC over the USB. Use the variables you defined in HIDmaker's
// Visual Data Designer . Variables having the correct names and convenient
// sizes (e.g. word, byte, bit) have been allocated for your use.
// Here is an example:
//
// The following lines provide real I/O for a PicProto USB board
//adc_read(0);
//Pot1 = ADRESH;
//adc_read(1);
//Pot2 = ADRESH;
//Button1 = realButton1;
//Button2 = realButton2;
 
// Test values for EP1 In (In to PC host):
Bytecount = TEST_BYTECOUNT;
Data3[0] = TEST_DATA3_0;
Data3[1] = TEST_DATA3_1;
Data3[2] = TEST_DATA3_2;
Data3[3] = TEST_DATA3_3;
Data3[4] = TEST_DATA3_4;
Data3[5] = TEST_DATA3_5;
Data3[6] = TEST_DATA3_6;
Data3[7] = TEST_DATA3_7;
Data3[8] = TEST_DATA3_8;
Data3[9] = TEST_DATA3_9;
Data3[10] = TEST_DATA3_10;
Data3[11] = TEST_DATA3_11;
Data3[12] = TEST_DATA3_12;
Data3[13] = TEST_DATA3_13;
Data3[14] = TEST_DATA3_14;
Data3[15] = TEST_DATA3_15;
Data3[16] = TEST_DATA3_16;
Data3[17] = TEST_DATA3_17;
Data3[18] = TEST_DATA3_18;
 
 
EP1XmtDataReady = 1;
 
// --------------- END USER "EP1 XMT" CODE ------------------
 
 

Since HIDmaker FS cannot know what real I/O resources your project will use, the generated code only sends those test values we saw defined above.

You should replace these test value assignments with your own "real I/O" code, that reads data from the sensors used by your own hardware.

 

Example: How to read a pot and a button & send values to the PC

If your USB project contained a pot and a button that you wanted to read and send to the PC, you could use the sort of code that is shown in the comment block:

 

How to read a pot and a button & send values to the PC
    // --------------- ADD USER "EP1 XMT" CODE HERE: ------------------
 
// C1 I0
 
// Add code here to generate data to transmit to PC via Endpoint 1 In
// This code should:
// set flag EP1XmtDataReady = 1 if there is new data to send, or
// set flag EP1XmtDataReady = 0 if data has NOT changed yet
 
 
 
// USB Advisor: This is where you add code to generate data that gets
// send to the PC over the USB. Use the variables you defined in HIDmaker's
// Visual Data Designer . Variables having the correct names and convenient
// sizes (e.g. word, byte, bit) have been allocated for your use.
// Here is an example:
//
// The following lines provide real I/O for a PicProto USB board
//adc_read(0);
Pot1 = ADRESH;
 
Button1 = realButton1;
 
 
EP1XmtDataReady = 1;
 
// --------------- END USER "EP1 XMT" CODE ------------------
 
 

 

Feature Reports

USB HID class defines 3 different types of data report:

Output Reports only send data items OUT from the PC, toward your device.
Input Reports only send data items IN to the PC, from your device
Feature Reports can send the data items in either direction

In HIDmaker FS , you set the direction of each data item (Input, Output, or Feature) when you define the data item in the HIDmaker's Visual Data Designer .

Then the collection of all Output data items becomes the Output Report, and so on for Input and Feature Reports.

HIDmaker's data items are identified to the PC in the Report Descriptor, so a data item's direction cannot be changed on the fly.

You almost never see support for bidirectional Feature Reports in those demo programs you find on the web, or from semiconductor manufacturers or compiler makers.

HIDmaker supports Feature Reports fully, and can pack and unpack Feature Reports that have any mixture of odd sized data items, and which can span multiple packets.

When your HIDmaker FS project uses Feature Reports (which our Varty16 example project does not), it generates some special functions to handle them. These functions contain specially marked sections, similar to the ones shown above for Output Reports and Input Reports, where you handle Feature Report data sent from the PC, and where you send data to the PC.

Feature Reports are often used for parameter settings like a volume control, that you want to occasionally check and maybe send a new value from the PC.