Wishing you all a Happy and Prosperous New Year 2009


Nov 22, 2008

Interacting with Microchip Full-Speed USB Demo Board using Visual Studio Tools - VC++ 6.0

While going through the USB forums I found out that there are many questions on how to develop PC applications for Microchip Full-Speed Demo boards by using MPUSBAPI.DLL. To answer one of the questions on Microchip USB forum, I developed a small application in VC++ which uses MPUSBAPI.DLL.

In this article, I will explain the technical aspects associated behind this application. And in subsequent articles I will explain how to use the MPUSBAPI.DLL library in Visual Basic.NET and C#.NET.

The Microchip Full Speed USB Demo Board is a great tool to learn USB and even for developing prototypes. The board comes with a PIC18F4550 device which is a USB part from Microchip. The board can be self-powered or bus-powered. It has a temperature sensor and potentio meter. It also comes with an expandable bus architecture (PIC-Tail Plus), which helps in prototype development.

Apart from these hardware features the board also comes along with a set of firmware examples and PC application examples. And the famous Demo Tool GUI which supports Bootloader features and Demo features. The board comes with a default factory hex file which has these two operations implemented. The Bootloader feature is always tempting for the end users like me. By using Bootloader I can reduce the cost of buying an ICD. The Demo Tool GUI is good enough to start with. But, I wanted to develop my own GUI and integrate the Boot example in my application. I am still on the learning curve on how to use it and develop GUI for the bootloader. In future I will post an article on it once I am done :-).

But, before that, I will explain how to interact with the Demo portion of the default factory hex file. Which is nothing but Demo option in the PICDEMFS Demo Tool.

How to develop an application that interacts with Demo firmware?

1. Download the MCHPFSUSB.ZIP from Microchip USB site and install it. This installation will have following directory structure.
Microchip Fullspeed USB Directory Structure

2. The PICDEM FS board should have been programmed with either the factory HEX (which is available in C:\MCHPFSUSB\fw\_factory_hex) or with the HEX file from Demo firmware example (which will be available in C:\MCHPFSUSB\fw\Demo\_output after compiling the Demo project).

3. The functionality provided in the C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\mpusbapi.dll need to be understood and learnt. To learn this file, refer to the file C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\Source\_mpusbapi.cpp. The developer of this DLL had put lot of effort in explaining each of function and how to use it. We will see more about these functions later.

4. And finally you should understand the code provided in the C:\MCHPFSUSB\fw\Demo folder. This code has two parts, USB Stack(All other files related to USB) and the USER application (user.c and user.h in C:\MCHPFSUSB\fw\Demo\user\) code. If you go through the code little more, you can see how the endpoints are defined, how the hardware is configured etc.. But, as this article is concentrating only on the PC application development with Visual Studio we will be learning more about user.c.

Ok, it is time to learn some theory.

Windows Basics:
1. Libraries in Windows Operating System

A Software Library is a collection of functions, data and resources(bitmaps, string tables etc..) in object code format. The libraries provide modularization and re-usability of code. So, by developing a library we can group related code together and use this code whenever any application requires that kind of functionality. The code in the libraries are added to application code during the process of LINKING.

The compiler tool chains used by Embedded Systems developers will implement a technique called, Static Linking to link libraries to the application. In this technique, all the libraries + source code object files are combined together and a final single HEX file will be generated, which will be used to program the device or board.

But, in a OS like Windows where Multi-tasking is implemented, two or more copies of same application can be executed. And more than two copies of applications that use same library code can also be executed.

What this means is, if Static Linking technique is used then, there is redundant code and data in the memory at the same time causing lot of memory wastage, and also the operating system has to lot of work loading unloading applications with larger in size into memory which can consume more time.

To avoid these problems, Multi-tasking OSs like Windows came out with another technique of for linking libraries to the applications, which is known as Dynamic-Linking. Those libraries that used for Dynamic-Linking are called as Dynamic-Link Libraries. And they will have an extension of .DLL.

So, The C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\mpusbapi.dll is a DLL, or Dynamic Link Library.

When two applications are using the same DLL, then there will only one code copy of the DLL in memory which will be shared by both applications. This will eventually reduce the size of user application and as the library code is not combined to the application, it will speed up loading of applications. For more information on DLLs, goto Microsoft MSDN site. Alternatively, you can read the books on Windows Programming.

One note is, even though Windows supports DLLs extensively, it still supports usage of Static Link Libraries, but Microchip's DLL(MPUSBAPI.DLL) is a Dynamic Link Library.


2. Techniques for invoking functions from the library in Windows Operating Systems


When a library project is built two files will be created. One is Library reference file(.LIB) and the other is Object Code file( .OBJ, .DLL). Object code is Machine Code with unresolved memory map and references. An Executable normally will have the references and memory map resolved.

Once the .LIB file and .OBJ file are generated, for using them in C or C++ programs, a Header File (.H) will also be provided/required to be created. The Header file will be included into the source files using #include in the project and .LIB file will be added to Files to be Linked list of the project. The .OBJ will be placed in the source file folder. Sometimes, the .LIB file itself contains the Object Code also, depending on the compiler chain, and there will not be any .OBJ file generated.

Now, if the generated files are .LIB and .OBJ, or only .LIB file, then these files will be used in Static linking,and if the generated files are .DLL and .LIB files then they will be used in Dynamic-Linking.

In Static Linking, the method of calling the included library functions is straight forward, and it is as good as calling any other function in the source files which are part of the project. The .H file need to be included in the sources files at required locations.

In Dynamic Linking, again there are two methods to access the functions available in the library(DLL). They are,
2.1. Load-Time Linking: In this method, the library is linked to the application using a .LIB in the same manner, how it is done with Static Linking. The only difference is the .DLL file associated with the .LIB file will be loaded at the time of execution. i.e when the application is loaded into the memory. In this technique, the .LIB file will be added to the project library list and the .DLL file will be placed in the system PATH. Functions will be called in the same manner as Static Linking.
The example given in the following path of C:\MCHPFSUSB\Pc\Mpusbapi\Example Applications\Borland_C\Example 01 - Load-time Linking demonstrate this technique.

2.2. Run-time Linking: In this technique, the .LIB file will not be used. The application assumes that the .DLL file is available in the system PATH. The application uses LoadLibrary(), GetProcAddress() and FreeLibrary() Win32 API calls for invoking the functions from the DLL.
The .DLL file is supposed to have the exported public symbols for using these functions. This technique is primarily helpful, when trying to access a DLL library for which a .LIB and/or a .H file are not available. We will use this technique for accessing the MPUSBAPI.DLL file as we don't have proper .LIB for Visual Studio.

The example given in the following path of C:\MCHPFSUSB\Pc\Mpusbapi\Example Applications\Borland_C\Example 01 - Load-time Linking demonstrate this technique.

3. Identifying the Function Names in a .DLL file with Dependency Walker.

The Dependency Walker tool comes with Visual Studio 6.0 . This tool displays what all the functions available in a .DLL file. When the MPUSBAPI.DLL file is opened with Dependency Walker, it looks like this.


View of MPUSBAPI.DLL in Dependency Walker

So, from this view we can make out that there are 7 functions available in the DLL and, all of them have underscore ("_") prefixed to them. The same functions along with comments can found out in the files.
C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\Source\_mpusbapi.cpp,
C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\Source\_mpusbapi.h.

The folder, C:\MCHPFSUSB\Pc\Mpusbapi\Dll\Borland_C\Source\ has two important header files. _mpusbapi.h and mpusbapi.h(without underscore).

Now we know what functions are available in MPUSBAPI.DLL file. Next step is understand how to use these functions for application development. The _mpusbapi.h (with underscore) file is the actual header file. What it means is it declares the actual functions. This file can only be used during Load Time Linking.

As we know that Load Time Linking is not possible with Visual Studio and MPUSBAPI.DLL, We need to use the Run Time Linking Technique.

4. Introduction to LoadLibrary(), GetProcAddress() and FreeLibrary()

4.1 LoadLibrary():
This function is used to load any .DLL at run-time of the application.

SYNTAX:
HMODULE LoadLibrary( LPCTSTR lpFileName);
This function takes the .DLL file name or a file name including full path as argument. Then it loads the DLL into the memory for use by application and returns HANDLE (HANDLE is a unique unsigned long number given by Windows OS to each object when it is loaded/created. The object can be application, DLL, Bitmap etc...). If the .DLL is already loaded this function returns the an existing handle.
If the DLL can not be loaded due to incorrect path or any other reason, this function returns NULL. So, the user application need to check for this condition.

4.2 GetProcAddress():
This function is used to get a pointer to a function in the .DLL file.

SYNTAX:
FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName );
This function takes two arguments. The HANDLE returned by the LoadLibrary function for hModule, and the name or ordinal of the function. So, if you take a look at the Dependency Walker picture shown earlier, _MPUSBGetDLLVersion will be name of the function. And 1 is it's ordinal number.
If the specified function with the name given is not found in the .DLL then it returns NULL.
The returns value from the function need to be collected in to appropriate pointer variable. This is when the mpusbapi.h (without underscore) comes to use. Microchip has done all the work in this case. The author of MPUSBAPI.DLL created this file keeping in mind the situation. This file has all the required pointer declarations for all the functions supported by DLL and this file need to be included in to the appropriate source files as required so that those declarations need not be made again.

4.3 FreeLibrary():
This function is used to close the .DLL library that is opened with LoadLibrary.

SYNTAX:
BOOL FreeLibrary( HMODULE hModule );
The hModule argument what is returned by LoadLibrary is used as argument here. This function need to be called at the end of the program or for unloading the library from memory.


MPUSBAPI.DLL:


5. MPUSBAPI.DLL

This is the PC side driver file, that communicates with the Demo Firmware on PICDEM FS Demo Board. A custom driver file is required when the a Custom Device Class or Vendor Specific Device Class are implemented on the device. For understanding more on device classes, please refer to USB Basics - Part II (How USB Devices are designed and developed?) It communicates with lower level driver mchpusb.sys which in turn communicates with the bus drivers of USB for USB data transfers.

The MPUSBAPI.DLL provides functions that communicate over an
end-point
. Again to understand what is end-point refer to USB Basics - Part II (How USB Devices are designed and developed?). For now we can safely assume end-point as a buffer to which both PC application as well as the device has access.

The MPUSBAPI.DLL refers the end points with string names. Example: "\\MCHP_EP1" refers to end-point 1 buffer. An end point can be IN end-point or OUT end-point or both.

The terms IN and OUT are with respect to the HOST or PC. So, always a PC application writes to OUT end-point and the devices read from an OUT end-point. Similarly, the HOST reads from IN end-point and devices write to an IN end-point. The Demo firmware implemented 2 end-points in it. One is Control end-point which will be used by the OS and BIOS for configuring the device. The other end-point implemented is "MCHP_EP1" which is both IN and OUT end-point. The second end-point uses Bulk transfer.

This DLL provides following functions:
1. DWORD _MPUSBGetDLLVersion(void)
This function will retrieve the version number of the DLL.
2. DWORD _MPUSBGetDeviceCount(PCHAR pVID_PID)

This function takes the pVID_PID (Vendor ID and Product ID) string as an argument and returns the number of devices with that specific Vendor ID and Product ID.
If the VID is 0x04d8 and PID is 0x000c, the pVID_PID string will need to have a the value as "vid_04d8&pid_000c"
3. HANDLE _MPUSBOpen(DWORD instance, // Input PCHAR pVID_PID, // Input PCHAR pEP, // Input DWORD dwDir, // Input DWORD dwReserved); // Input

This function returns a handle to the end-point. The argument instance is used for passing a zero based index. This index value is based on the number of devices returned by MPUSBGetDeviceCount().


pVID_PID is the VID and PID string as explained above.
pEP is the name of end-point something like "\\MCHP_EP1".
dwDir based on the end-point IN or OUT direction, MP_READ or MP_WRITE need to be passed as argument. dwReserved For future use.

This function returns a HANDLE to the end point opened which will be used by other functions. If no device is found, returns NULL. Check for NULL.
4. DWORD _MPUSBRead(HANDLE handle, // Input PVOID pData, // Output DWORD dwLen, // Input PDWORD pLength, // Output DWORD dwMilliseconds); // Input

This function is used to read from an IN end-point. It uses the handle returned by _MPUSBOpen function. The _MPUSBOpen function need to open this end point, with MP_READ as Direction (dwDir) argument.
pData is pointer to buffer.
dwLen size of data to be read.
pLength size of data actually read.
dwMilliseconds Total wait time.

Returns 1 on SUCCESS.
5. DWORD _MPUSBWrite(HANDLE handle, // Input PVOID pData, // Input DWORD dwLen, // Input PDWORD pLength, // Output DWORD dwMilliseconds); // Input

This function is used to write to OUT end-point. It uses the handle returned by _MPUSBOpen function. The _MPUSBOpen function need to open this end point, with MP_WRITE as Direction (dwDir) argument.
pData is pointer to buffer.
dwLen size of data to be written.
pLength size of data actually written.
dwMilliseconds Total wait time.

Returns 1 on SUCCESS.
6. DWORD _MPUSBReadInt(HANDLE handle, // Input PVOID pData, // Output DWORD dwLen, // Input PDWORD pLength, // Output DWORD dwMilliseconds); // Input
Used in Isochronous transfer/ Interrupt transfer. Refer to _mpusbapi.cpp for more details.
7. BOOL _MPUSBClose(HANDLE handle);
Used to close the handle opened by the _MPUSBOpen function. Need to be called before application exits. Otherwise the device may not respond properly, if the application is re-opened.


NOTE: The functions in the DLL has underscores prefixed to them.

6. A look at the Demo Firmware Source Code


Now, we know what are all the functions on PC side are available in Microchip DLL(MPUSBAPI.DLL). So, lets take look at the firmware code in Demo example which is in C:\MCHPFSUSB\fw\Demo\ folder. The files which are of interest now are main.c and user.c.

The main() function in the main.c is starting point of the application. This function has an infinite while loop which inturn calls the function ProcessIO(). The ProcessIO() function is implemented in user.c and it calls another function called ServiceRequests(). This is a general structure adopted by Microchip Firmware library for USB.

The ServiceRequests function handles the commands sent by the HOST (PC) application, using the _MPUSBRead and _MPUSBWrite calls. There is a union called DATA_PACKET in user.h. This union comprises many structures that help in interpreting the data received over USB.

When using the Demo application, the format of data transfer seems to be,

1. CMDByte , DATA LENGTH , [BYTES...]

2. CMDByte , LED NO. , ON(1)/OFF(0)


So, in the above format the data will be filled by the Host application and transmitted. The firmware interprets the commands received from host, and it will perform actions depending on what the host sends. The commands are listed in C:\MCHPFSUSB\fw\Demo\user\user.h file. The Host side application need to match these commands. More commands can be added and the Demo application can extended by adding more code in ServiceRequests() function.


7. VC++ Example

Download the VC++ Example by clicking here.

In this example there are two files that need to be looked at.

7.1. PICConnector.h and PICConnector.cpp: In these 2 files I implemented a simple wrapper class which invokes the functions from MPUSBAPI.DLL. The LoadDLL() member function of PICConnector class need to be called and it uses the Win32 API LoadLibrary(), GetProcAddress(), and FreeLibrary() to load the MPUSBAPI.DLL. Take a look at the source code while reading this portion. When the GetProcAddress call is made, note that the function names are passed with an underscore and the return value is assigned to corresponding function pointer. The function pointers are declared in mpusbapi.h file which is in the project folder.

Once this DLL is loaded, the Open() and Close() functions can be called to open the pipes on end-point 1 of device with index 0. These functions use the MPUSBOpen() and MPUSBClose() functions internally. Also, take a look at the error checking that is done in these files. This code is designed to work only with the first connected device, which will have an index of 0. But by modifying the code, more than one device can be handled.

In the Open() function, before calling MPUSBOpen(), the function MPUSBGetDeviceCount() is called with the vid_pid argument. This function returns the count, and if the count is 0, then it means no devices are connected to the PC. This is another error checking mechanism that can be used.

The SendReceivePacket() function is responsible for actual data transmission. This function makes calls to both MPUSBWrite() as well as MPUSBRead(). The MPUSBRead() is called as soon as MPUSBWrite() is invoked. What it means is, as soon as the PC application sends a command, it is also expecting a response. You can see in the firmware that, after receiving the command and data, there is a response packet sent by the firmware which includes the command bytes in it. It is a kind of acknowledgement implemented in the Demo application. And all applications does not require to implement this feature. So, there can be independent calls to MPUSBRead and MPUSBWrite functions.


7.2. VCPICUSBDemoDlg.cpp and VCPICUSBDemoDlg.h: The cpp file, implements 3 event handler functions. They are, OnGetVersion(), OnLed3() and OnLed4(). Take a look at the code of these functions.

Here I will explain the OnGetVersion() function.


The m_PICConnector is a object of PICConenctor class which loads the MPUSBAPI.DLL, created in the CVCPICUSBDemoDlg class.

Step A: In this step the pipes on end-point 1(MCHP_EP1) are opened by calling the Open() function. This function inturn calls MPUSBOpen on MCHP_EP1 for both OUT and IN pipes and stores the handler values in two variables, myOutPipe and myInPipe. Take a look at how the calls are made in Open function.

Step B: In this step two buffers are declared. The length of an bulk endpoint can not exceed 64 bytes in Full-Speed USB. So, the buffer length for both OUT and IN pipes is 64 bytes. But actually we will be transmitting less number of bytes compared to the buffer size.

Step C: In this step, the Demo Application command(READ_VERSION) is defined. Take a look at the user.h file in the Demo firmware. The value of the command READ_VERSION is mentioned as 0. Then the send_buf is being filled with the command and the data length. When firmware receives this command, based on the command it interprets remaining fields.

Step D: SendReceivePacket is called in this step. The send_buf which is populated in the previous step with the command and data fields is transmitted in this step, at the same time, as this command is retrieving version information, the pointer to receive_buf and expected length which is 4 are passed as arguments.

The ServiceRequests() function in the firmware processes this command and fills the response data. When the firmware sets the counter to a non-zero value, the USB stack will transmit the buffer back to Host.

Step E: In this step, the received data is verified if it is generated by the firmware correctly.

Step F: As the operation is complete, both IN and OUT pipes are closed in this function.

Here I am stopping this article. And this article showed how to interact with Demo firmware on Microchip PICDEM FS USB Board using VC++.

Source Code for this article can be found here.


1 comment:

JAVIER said...

when you explain the difference between load-time and run-time linking.
you give the same path for finding both projects. it causes confuse.
maybe u can correct it. thanks. good information!
The example given in the following path of
C:\MCHPFSUSB\Pc\Mpusbapi\Example Applications\Borland_C\Example 01 - Load-time Linking demonstrate this technique.