14.6 USB Debugger – Implementation

Your Ad Here

In the last section we have seen how to implemented a simple communications protocol. In this section we will implement this communications device as a debugging tool on the very same circuit on which we have build our USB Simple HID. If the schematic and bill of materials of the USB circuit is required, please refer to the USB HID Joystick section. Let’s get right down to it. We will be modifying some files.

“usb_descriptors.c”

Recall that this file handles the descriptors that are acquired during the enumeration stage of a USB connection. We will need to change this file in order to configure our device into a debugger. Since our communications protocol requires 12 bytes of data transferred on the IN and OUT interrupts, we will create a new report descriptor to reflect this. We will also declare our device as a “vendor defined” class, instead of a joystick.

ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={{
    0x06, 0x00, 0xFF,              // USAGE_PAGE (Vendor Defined)
    0x09, 0x01,                    // USAGE (Vendor)
    0xa1, 0x01,                    //   COLLECTION (Application)
    0x05, 0x01,                    //   USAGE_PAGE (Generic Desktop)
    0x09, 0x00,                    //   USAGE (Undefined)
    0x15, 0x80,                    //   LOGICAL_MINIMUM (-128)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x0C,                    //   REPORT_COUNT (12)
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    0x09, 0x00,                    //   USAGE (Undefined)
    0x15, 0x80,                    //   LOGICAL_MINIMUM (-128)
    0x25, 0x7f,                    //   LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //   REPORT_SIZE (8)
    0x95, 0x0C,                    //   REPORT_COUNT (12)
    0x91, 0x02,                    //	OUTPUT (Data, Var, Abs)
    0xc0                           //	End Collection
}
};

And then of course, for cosmetic reasons, we will want to change the name of the device appropriately.

//Product string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[12];}sd002={
sizeof(sd002),USB_DESCRIPTOR_STRING,
{'H','I','D',' ','D','e','b','u','g','g','e','r'
}};

“usb_config.h”

This file handles global symbols for our USB device. We need to change the PID of our device. This is to make sure that the operating system (in my case Windows) add a new registry listing for the device. If we use an existing PID value, the registry entry might get mixed up and the drivers may not load properly.

#define MY_VID 0x04D8
#define MY_PID 0x0101

In addition, the size of our report descriptor has change (we just modified the report descriptor in the file “usb_descriptors.c”). We’ll need to change the symbol defining the size of the report descriptor.

/* HID */
#define HID_INTF_ID             0x00
#define HID_EP 			1
#define HID_INT_OUT_EP_SIZE     64
#define HID_INT_IN_EP_SIZE      64
#define HID_NUM_OF_DSC          1
#define HID_RPT01_SIZE          34

At this point, our USB configuration is complete. We have defined a brand new device and when we plug our device into a host computer, the host will load a new set of drivers and recognize that the device has two interrupt endpoints (one IN and one OUT) requiring 12 bytes. Next we need to integrate the code we write in the previous section.

“nlib_uni.h”

The header file for our debugger needs to declare the data structure that we need during our data transfers. It also lists the various function prototypes used in the source file. This file is pretty straight forward and I won’t delve deeper into any more explanations.

//packet typedef
typedef union _USB_CONTROLS_TYPEDEF
{
	struct
	{
	    BYTE cmd;
		BYTE mod[3];
	    BYTE data[8];
	}	packet;
} USB_CONTROLS;

//main processing function for usb
void UniProcessRx(USB_CONTROLS *in, USB_CONTROLS const *out);

//main USB rx/tx processing function
void UniProcess(void);

//initialize handlers
void UniInit(int ep);

void cmd_wr (USB_CONTROLS const *cmd);
void cmd_wrPage (USB_CONTROLS const *cmd);
void cmd_rd (USB_CONTROLS *res, USB_CONTROLS const *cmd);
void cmd_rdPage (USB_CONTROLS *res, USB_CONTROLS const *cmd);

“nlib_uni.c”

The source file for the debugger is a bit more involved. Let’s take a look:

Your Ad Here
#include "GenericTypeDefs.h"
#include "bsp.h"
#include "./USB/usb.h"
#include "./USB/usb_device.h"
#include "./USB/usb_function_hid.h"
#include "nlib_uni.h"

#define CMD_WR		0xA0
#define CMD_WRPAGE	0xA1
#define CMD_RD		0xB0
#define CMD_RDPAGE	0xB1

#define CMD_SUCCESS	0x10
#define CMD_ERROR	0x20

#define CMD_RESET	0x80

//make the functions youself, these are only pointers
extern void (*ctrlWrPtr)(unsigned int const addr, unsigned char const data);
extern unsigned char (*ctrlRdPtr)(unsigned int const addr);

//make a variable to remember the endpoint, set on init
static int endPoint;

//transmission handle for the usb transmission, packets
USB_HANDLE USBOutHandle = 0;
USB_HANDLE USBInHandle = 0;
USB_CONTROLS in_packet;
USB_CONTROLS out_packet;

//THE FUNCTIONS BELOW ARE LISTED IN THE PREVIOUS SECTION!
//main processing function for usb
void UniProcessRx(USB_CONTROLS *in, USB_CONTROLS const *out)
//control register write functions
void cmd_wr (USB_CONTROLS const *cmd)
void cmd_wrPage (USB_CONTROLS const *cmd)
void cmd_rd (USB_CONTROLS *res, USB_CONTROLS const *cmd)
void cmd_rdPage (USB_CONTROLS *res, USB_CONTROLS const *cmd)
//END NON-LISTED FUNCTIONS

//this is the Initiation routine for custom user code
void UniInit(int ep)
{
	endPoint = ep;

	//initialize the variable holding the handle to zero
	//for bot transmission and receive
	USBOutHandle = 0;
	USBInHandle = 0;
}//end UserInit

//function takes IO and pocoesses
void UniProcess(void)
{
	// User Application USB tasks
	if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) return;

	//receive
	if(!HIDRxHandleBusy(USBOutHandle))	//Check if data was received from the host.
	{
		UniProcessRx(&in_packet, &out_packet);

		//Re-arm the OUT endpoint for the next packet
		USBOutHandle = HIDRxPacket(endPoint,(BYTE*)&out_packet, sizeof(out_packet));
	}

	//transmit, if there is something to transmit, reset if not busy
	if(!HIDTxHandleBusy(USBInHandle))
	{
		//Send the packet over USB to the host.
		USBInHandle = HIDTxPacket(endPoint, (BYTE*)&in_packet, sizeof(in_packet));
	}

	return;
}//end ProcessIO

The first thing to take note is that most of the important functions are already listed in the previous section and so I have omitted to list them here. The full implementation is available at the end of this post in a zip file anyways. In any case, the important part is that we’ve added a read and write function pointer (*ctrlWrPtr and *ctrlRdPtr). This is the key to our debugger. We will use these two pointers and point them to the data we wish to debug in our BSP files. This way we can simply drop this source file into any project and we will be able to debug our project at run time.

“bsp.c”

The debugger is complete, so now we need to integrate it into the main program. As with all of my projects, the board level difference are declared and implemented in the BSP files. The “bsp.h” file remains unchanged for the most part, except the symbols for the buttons were removed. In the “bsp.c” source file, we need to link the data object that we want to debug to the debugger.

#include "dev.h"

/******************************************************************************
 Debug Read/Write, Variables
******************************************************************************/
unsigned char debugValue[256];
unsigned char debugRead(unsigned int addr){
	if (addr < 256)
		return debugValue[addr];
	else
		return 0;
}
void debugWrite(unsigned int addr, unsigned char data){
	if (addr < 256)
		debugValue[addr] = data;
}

/******************************************************************************
Link up Function pointers
******************************************************************************/
unsigned char (*ctrlRdPtr)(unsigned int const addr) = debugRead;
void (*ctrlWrPtr)(unsigned int const addr, unsigned char const data) = debugWrite;

/******************************************************************************
Timer Interrupt definition
******************************************************************************/
#define TIMER2_ISR_PRIO         4

void  __attribute__((__interrupt__, auto_psv)) _T2Interrupt(void) {
	//clear interrupt
	_T2IF = 0;

	UniProcess();
}

/******************************************************************************
 Board specific functions
******************************************************************************/
void BSP_init(void) {
	//Disable Watchdog
	RCONbits.SWDTEN = 0;

	//configure for digital
	AD1PCFG = 0xFFFF;

	//set up timer
	T2CON  = 0;
	TMR2   = 0;
	PR2    = BSP_TMR2_PERIOD - 1;
	_T2IP  = 4;
	_T2IF  = 0;
	_T2IE  = 1;
	T2CONbits.TON = 1;

	//initialize HID USB Interface for joystick
	UniInit(HID_EP);
}

At the beginning of the code listing we declare an array called “debugValue[256]“. This is the array that will be accessed through our debugger. In addition, we create two functions that gives us access to this array, aptly called “debugRead” and “debugWrite”. Note that these two functions are created with the same parameter and return requirements as the read and write pointers we declared in our debugger. The reason for this is so that we can link these functions with the function pointers in “nlib_uni.c”. Now that we have linked the data and the accessing functions with our debugger, the integration is complete.

Testing the Debugger

We are ready to test our device. Before we do, here is the full MPLAB.X project with the debugger that has all the files needed to compile and download the program into the joystick circuit.

Engscope HID Debugger Project
Please read the disclaimer before downloading.

I have written some code in C# that communicates with the debugger. The functionality is EXACTLY the same as the Simple HID Write program discussed in the previous sections of the tutorial, except I filter out all the non-relevant communications transfers. Here’s a look at read and writing to the “debugValue” array.

The program also filters out reset commands. In the first write command, the writePage command is used to edit the data array at index 0×00 through 0×03 with some incremental values. Then, a readPage command was used to read the data back. Next, a write command was issued to write the value 0xA5 to index 0×06. Finally, another readPage to look at 8 bytes of data starting at index 0×00. This is a simple example where the debugValue variable is only manipulated through the debugger. In a read project, the relevant data would be manipulated in the background, and accessed through the debugger. I will not be posting the source code of this program as this is an embedded systems tutorial, and not a C# related website. However, communications with the debugger is still possible using the Simple HID Write tool. There are much better tutorials out there on C# and host-side USB programming than can be provided by this author.

Your Ad Here

Usage Ideas

So what can wedo with a debugger like this? Well, we are not limited to linking an “unsigned char” array. The access functions “debugRead” and “debugWrite” can be written in any manner and form. For example, if we are only interested in two variables in a project and we would like to read their values during run time, we can rewrite the function as something like this:


unsigned char val0;
unsigned char val1;
unsigned char debugRead(unsigned int addr){
	switch(addr){
	case 0x0000: return val0;
	case 0x0001: return val1;
	default: return 0;
	}
}

While the rest of our program is manipulating val0 or val1, we will be able to read what values are being written to the variables through the USB port. Another use of the debugger is to link the access functions to the I2C or SPI ports. This way you can use the USB port to issue I2C and SPI commands. All that is needed is to create a wrapper function so that the parameter and return values of the I2C/SPI functions are congruent with the debugger access function pointers. The function pointer nature of the debugger means that it is very easy to drop the code into any project, and link the function pointers in the “bsp.c” file without modifying the debugger code. This is an important concept and the starting point for modular programming in C.

I use this piece of code in almost every embedded project. It is probably the most used piece of code I have ever written (to date) because of how versatile and easily it can integrate with existing projects. In the next section, we will look at how to create a USB device that has BOTH the HID joystick as well as the debugger in a composite USB configuration.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>