You are on page 1of 8

INTERRUPT DRIVEN USB INTERFACE LIBRARY for

PIC18F4550
Based on Microchips CDC Library, written by Elco Jacobs, Feb 2007.
Last revision Sept 2008.

Getting started
The library provides 4 basic functions:

voi d I ni t i al i zeUSB( voi d) ;
This function initializes the USB service. You should call it at the start of your
main().

voi d RamSt r i ng2USB( aut o char *SendSt r i ng) ;
This function sends a string from the RAM memory to the PC. All variable strings
are in RAM memory. The string should be null terminated: the last character of
the string should be \0.

voi d RomSt r i ng2USB( aut o const r omchar *SendSt r i ng) ;
This function sends a string from the ROM (instruction) memory to the PC. All
constant strings are in ROM memory.

unsi gned char St r i ngFr omUSB( aut o r amchar *Recei veSt r i ng) ;
This function receives a string from the PC. When used with hyperterminal, the
strings usually contain just 1 character because of the slow typing speed.
When one ore more characters have been received, the function returns 1,
otherwise 0.

How to use the libray.
1. In the project manager, add the library file InterruptUSB.lib to your
project.

2. In main.c (or in any other file in which you want to use the USB functions)
include InterruptUSB.h. This header file contains the prototypes for the
USB functions.

#i ncl ude " I nt er r upt USB. h"

3. In your high interrupt service routine add the following code:

voi d _hi gh_I SR ( voi d)
{
i f ( I NTCONbi t s. TMR0I F==1) { / * <- - - - - - - - */
_asmgot o USBI nt er r upt _endasm; / * <- - - - - - - - */
} / * <- - - - - - - - */
}
#pr agma code

This code checks if an interrupt is caused by timer0, and jumps to the
USBInterrupt function.

4. In the beginning of your main() function, call InitializeUSB():

voi d mai n( voi d) {

/ / Decl ar e var i abl es

/ / End of decl ar e var i abl es

I ni t i al i zeUSB( ) ;

/ / Your pr ogr amst ar t s her e
whi l e( 1) {


}
}

5. To send a constant string to the PC, use RomString2USB()

RomSt r i ng2USB( " Hel l o Wor l d! \ r \ n" ) ;

\r\n tells hyperterminal to start a new line.

6. To send a variable string to the PC, use RamString2USB().
Note that here the string is declared as a variable in the program.
The strings should always be null terminated. (sprintf() does this
automatically).

char TempSt r i ng[ 40] ; / / i f t he si ze i s l ar ger ( t est ed: 64)
/ / communi cat i on wi l l st al l .
/ / Thi s i s r el at ed t o spr i nt f . Pr eci se
/ / cause i s unknown so f ar .
i nt Fi r st Number , SecondNumber , Resul t ;

Fi r st Number = 3;
SecondNumber = 4;
Resul t = Fi r st Number *SecondNumber ;

spr i nt f ( TempSt r i ng, " %d X %d = %d\ r \ n" , Fi r st Number ,
SecondNumber , Fi r st Number *SecondNumber ) ;
RamSt r i ng2USB( TempSt r i ng) ;

This will display:

3 X 4 =12
7. To receive a string from the PC to the microcontroller, use
StringFromUSB():

i nt TSl engt h, i ;
i nt I Sl engt h = 0;
char TempSt r i ng[ 3] ; / / User cannot t ype f ast er , so 3
/ / i s enough f or hyper t er mi nal
i f ( St r i ngFr omUSB( TempSt r i ng) ) {
TSl engt h = st r l en( TempSt r i ng) ;
f or ( i =0; i <TSl engt h; i ++) {
/ / pr ocess r ecei ved char act er s her e
}
}


8. Finally in the Build options, on the tab MPLINK Linker, check Suppress
COD file generation.


Terminal functions
When you are using this library with a PC running hyperterminal, take a look at
the functions in terminal.h and terminal.c

There a four functions in terminal.h/terminal.c:
ClearScreenToUSB(), UserInput(), AnyKey(), String2Decimal() and
WelcomeScreen().

Include terminal.h in your *.c file and add terminal.h and terminal.c to your
project. See terminal.c for more instructions on how to use these functions.
Technical Stuff
The Interrupt Driven USB Interface is a modification of Microchips CDC library.
The CDC library connects the microcontroller to a PC through a virtual COM port.

In the original CDC library from microchip the main() function looks like this:

voi d mai n( voi d)
{
I ni t i al i zeSyst em( ) ;
whi l e( 1)
{
USBTasks( ) ; / / USB Tasks
Pr ocessI O( ) ; / / See user \ user . c & . h
}/ / end whi l e
}/ / end mai n

Here USBTasks() services the USB functions and ProcessIO() is a function
written by the user. Because USBTasks() should be called about every 1 ms to
keep the USB communication alive, there are some restrictions on ProcessIO():
The time ProcessIO() can take is very limited
There can be no blocking functions in ProcessIO()
ProcessIO() must therefore be implemented as a finite state machine, which can
be difficult and limiting.

The modified USB library uses a timer0 interrupt to keep the USB communication
alive. When a timer0 overflow occurs (about every 683 us, f=1.46 kHz), the high
interrupt service routine (ISR) will be called.
In this routine the USB tasks are serviced and a pending read from USB or write
to USB is serviced. Because the USB communication is now interrupt driven, this
leaves no restrictions on the main(). The main program will simply be interrupted
each time to service the USB tasks.


Architecture
The send and receive functions do not write or read directly to or from the USB
interface, but to two circular buffers.
RamString2USB() and RomString2USB() copy a string to the SendBuffer.
If the SendBuffer is full, the function will wait at most 16 interrupt cycles for
the buffer to empty. For RamString2USB() a maximum of 255 characters
will be copied, to prevent an infinite loop if a string is not null-terminated.
RomString2USB() does not have this limit, because ROM strings are
usually correctly terminated.
StringFromUSB(char * ReceiveString) copies a string from the
ReceiveBuffer to ReceiveString. It stops at a NULL character or at time-
out (just 1 interrupt cycle).

SendBuffer and ReceiveBuffer are both 128 byte ring buffers. In a ring buffer you
keep track of a write position and a read position in each buffer. Your write
position is ahead of your read position if there is data in the buffer.
After writing to the buffer, your write position moves up 1 byte, after reading from
the buffer, your read position moves up 1 byte.
After writing to the last position (255), your next write position will be 0 again.
Therefore the name ring buffer.

The USBInterrupt() function in the high ISR has 3 tasks:
It checks if data has been received from the PC and it copies it to the
ReceiveBuffer.
If there is data in the SendBuffer, it reads a chunk of data from the buffer
and sends it to the PC.
It calles USBTasks(), to keep the USB communication alive.

So data will NOT be sent immediately if you use the function RamStringToUSB()
or RomString2USB(), but in the next timer0 interrupt (if there is no previous data
in the queue) . Also, data received from the PC will not be immediately available.
It will be copied to the ReceiveBuffer in the next interrupt.

Timer0 is configured as a 8 bit timer, using the internal clock with a 1:8 prescaler.
With an internal oscillator frequency of 48 MHz (so a clock cycle of 48 MHz / 4 =
12 MHz), this gives an interrupt frequency of 12 MHz / (2^8 * 8) =5859 Hz.

FAQ

How can I see if the microcontroller is recognized by the PC?
o Go to the Device Manager and expand ports. Here the
microcontroller is listed as Microchip CDC Communications Port
(COMxx).

On which COM port is the CDC communications driver?
o See question above.

What should be the port settings for the CDC driver (in device manager)?
o Baud Rate: 19200
o Data bits: 8
o Parity: none
o Stop bits: 1
o Flow control: none
o Advanced
Use FIFO buffers: on
Use highest settings, lower them when problems occur.
COM port: own preference.

I get a notification USB device not recognized
o If you are using the microcontroller behind a USB hub, this can
cause problems. Try connecting it to your PC without a hub. Try to
reset the microcontroller. This might also occur if you are drawing
too much current from the microcontroller/usb. Try powering your
microcontroller from an external supply.

The microcontroller is not listed in the Device Manager
o Check if the high interrupt service routine contains the code stated
in point 3 of How to use the library.
o Check if InitializeUSB() is called in the beginning of your main().

I seem to receive random trash from the PC.
o Make sure that you use ReceiveFromUSB(..) with a pointer to a
char as argument. Using a char as argument will cause the function
to write to the wrong location. See point 6 of How to use the
library.

I send a value to the microcontroller, but another value is received.
o Try debugging with Terminal by Br@y++instead of Windows
HyperTerminal. Here you can send hex value 1 for example, by
sending $01. Also there is a separate window for send and receive.
You can download it here: http://bray.velenje.cx/avr/terminal/
This terminal can only use COM1-7, so you might need to change
the COM port used by the CDC driver.
My program runs slower than before using the USB library.
o This is normal. Remember that your program will be interrupted for
the USB communication every 170 us. When no data is sent or
received, the Interrupt routine takes 10 us. This is without the
context save/restore that comes with an interrupt. When writing
your program, keep in mind that a part of the processor time is
used for the USB communication.

When I simulate my program using MPLAB SIM, the interrupt does not
seem to work.
o The cause of this problem is the boot loader that is used in the
microcontroller. The boot loader is located in the memory of the
microcontroller at address 0x000-0x800. The interrupt routines are
placed after the boot loader at location 0x808 (high ISR) and 0x816
(low ISR).
Normally, when no boot loader is used, the interrupts are placed at
0x008 and 0x016. MPLAB SIM still expects the interrupt routines to
be at this location, and therefore interrupts do not work in MPLAB
SIM.
There is a workaround for this problem. Add the following code to
your project, under your boot loader code.

/ / DUMMY i nt er r upt j umps, wi l l not be wr i t t en t o PI C,
because ar ea i s pr ot ect ed.
#pr agma code _DUMMY_HI GH_I NTERRUPT_VECTOR = 0x000008
voi d dummy_hi gh_i sr ( voi d)
{
_asmgot o 0x000808 _endasm;
}
#pr agma code
#pr agma code _DUMMY_LOW_I NTERRUPT_VECTOR = 0x000018
voi d dummy_l ow_i sr ( voi d)
{
_asmgot o 0x000818 _endasm;
}
/ / end dummy i nt er r upt j umps

This code writes a jump to 0x808 at address 0x008 and a jump to
0x816 at address 0x016. Now when an interrupt occurs and
MPLAB SIM jumps to 0x008 or 0x016, it will be forwarded to the
right interrupt addresses and simulation works.

When building a hex file to program the microcontroller, the dummy
interrupts will not be programmed, because the area 0x000-0x800
is protected in the linker file to prevent overwriting the boot loader.
Using these dummy interrupt jumps the code will work in MPLAB
SIM and when running on the microcontroller.

I get an error when building starting with:
MP2COD 4. 02, COFF t o COD Fi l e Conver t er
Copyr i ght ( c) 2006 Mi cr ochi p Technol ogy I nc.
o In Build options, tab MPLINK Linker, check Suppress COD file
generation. (step 8)

You might also like