100% found this document useful (1 vote)
478 views173 pages

Curso Pic24j60

This document provides an introduction and overview of programming the PIC24 microcontroller. It discusses the author's experience learning to use the PIC24, outlines some key features of the chip, and describes the MPLAB IDE and C30 compiler that are used for programming. The document then gives details on setting up a basic circuit to interface the PIC24 with the ICD 2 debugger before presenting a simple first program to verify the oscillator is working properly.

Uploaded by

Diojan Torres
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
478 views173 pages

Curso Pic24j60

This document provides an introduction and overview of programming the PIC24 microcontroller. It discusses the author's experience learning to use the PIC24, outlines some key features of the chip, and describes the MPLAB IDE and C30 compiler that are used for programming. The document then gives details on setting up a basic circuit to interface the PIC24 with the ICD 2 debugger before presenting a simple first program to verify the oscillator is working properly.

Uploaded by

Diojan Torres
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 173

01 Introduction

I wrote this guide because I could not find many resources online on the PIC24. Granted,
the chip set only came out in 2006, and there was a datasheet for each family of
microcontrollers, I had a lot of trouble finding sample code, or learning how the different
modules functioned. The Microchip forums were very helpful, as well as some of the calls
to Microchips technical support (though some of the tickets were not answered at all).
I became very familiar with the PIC24 at work. I graduated in the discipline of electrical
engineering, and during my first job in this field, I had to learn to use the
PIC24JF64GA002 for one of the projects needed to design an electronics control system
with the microcontroller from the ground up. In case one of you out there who are in the
same situation, I hope this tutorial will give you a heads start on the learning process.
This guide assumes that you have some background in assembly programming. While you
can get by with just knowing how to program C, very low level programming, assembly, is
more or less a hardware process. C on the other hand can be both hardware and software,
but most of the time, programmers tend to think of the language as the mother of all
software programming. There are many concepts in this guide that will be very easy to
grasp if youve ever done any work on the old Intel 8080 or the Motorola 68000, but for
those of you whove only dealt with software, some of the notions may seem foreign.
The PIC24 is a very versatile microcontroller. It is a new generation of 16 bit
microcontrollers that is based on a completely different architecture. This is the main
distinction between the PIC24s and Microchips previous line of 16 bit chips (such as the
PIC18). Personally, I like using the PIC24s because they are very powerful and versatile.
Though the PIC18s are a very good set of processors, the PIC24s architecture is almost the
same as the digital signal processors, dsPIC30s and dsPIC33s. This allows for easy
transitions when project calls for DSP. These powerful set of processors are almost the
same as the PIC24s, with the exception that they have a series of instructions that allows for
quick digital signal processing. The processors allows for easy, and unbelievably fast
implementation of functions that are somewhat awkward on a conventional microprocessor.
These include fast Fourier transforms (FFT), divisions and multiplications, as well as a
bunch of other instructions with dedicated silicon. I mention this, because as most assembly
programmer would probably know, writing a multiplication function on the 8051 or the
6800 was a big pain in the ass, and was extremely slow to execute. But, enough about the
dsPIC3x line of products. The PIC24 enjoys all the benefits of the new 16 bit architecture,
but without the fancy digital processing circuitry. They are built for versatility, more or
less. I wont go into the specifics of these chips as they may change in the future, but I am
always very impressed with how much stuff they can squeeze into the chip plastic
packages.
Microchips gracious engineering department gives away the MPLAB Integrated
Development Environment (IDE) for free. The program comes with the assembler, ASM30.
For all those programmers who are purists, or the sadist software engineers who likes to
bang their forehead on the keyboard, this is the compiler software suite for you. However, I
like to spend my work time practically. If Im going to write 32 kb of code, I would
definitely consider using C along with an assembly compiler. I know there are purist
assembly programmers out there who would probably put down this guide right this
second. The arguments are usually something like writing in C and compiling to assembly
makes the code execute too slow or the code doesnt optimize very well or the code
takes too much space. However, the PIC24s nowhere near the dinosaur ICs of yesteryears.
They are lightning fast and have plenty of space. In addition, Microchips engineers did a
great job of making sure the compiler optimization of code is robust and streamlined. For
the purists assembly programmers out there, it is definitely a good time to join the Dark
side. Cause really, at up to 120 MHz, and up to 256 Kb of programming memory, the Dark
side really isnt so bad. In all seriousness though, the C30 C compiler is a great tool, and
makes me want to puke in disgust when I look back at the programming process of those
old DOS-based assembly programming suites.
Theres the introduction, now lets get to some programming.

02 Getting Started
The PIC24 is divided into two families. The PIC24F series emphasizes cost, but can only
go up to around 40 MHz, while the PIC24H series is targeted towards applications where
performance is generally more important. The cost/performance factor only comes into
consideration if you need to mass produce your product. For in house development, both
are very good microcontrollers performing similar functions. The best part of it all is that
they cost dirt cheap. Online retailers such as Digikey, Mouser and Microchip Direct tags
the cheapest PIC24F chip at around $2.50, while the PIC24H is somewhere around maybe
$4-5 depending on the model.
Because the chips are very similar, the rest of the guide will all be written for the
PIC24FJ64GA002 or the PIC24H32GP202. I am particularly fond of these two models
because they have most of the features of the high-end chips in the PIC24 group, but at
reduced pin out counts. Both have 28 pins, and their footprints are interchangeable.

However, the PIC24FJ64GA002 has a few extra features when compared to its
performance oriented cousin. For example, the PIC24FJ64GA002 has two UART modules
and two I2C modules as opposed to only one on the PIC24H series brethren. The former
also has an extra double 16 bit/single 32 bit timer module. There are a few more minor
differences, but they will be discussed a bit further in the guide.

The MPLAB IDE and the ICD 2
Microchip provides a very good programming platform to program, compile and even
debug the PICs. It is not an industrial programmer such as the JTAG interface, even though
many of Microchips controllers have pins reserved for the protocol. Since the program is
very user friendly, and is my preferred way of working with PIC, this is a little introduction
for people who are not familiar with the program.
The MPLAB IDE is an integrated development environment in which the user can write
code, program PICs and even debug them in-circuit. Together with the in circuit debugger,
the user can pretty much do everything the microcontrollers can offer. The program also
allows you to pick compiler suites, and integrates the use of both C programming and
assembly. It is a power tool, and greatly increases the effectiveness of a programmer from
the heydays of DOS based assembly mayhem. Since the guide focuses on the PIC24s, the
rest of the guide is written with the assumption that you are using MPLAB 8, with an ICD
2, and a student version of the C30 compiler. Both the MPLAB 8 and the student version of
the C30 compiler is free, but the ICD 2 will set you back 180 dollars. Though its quite a bit
of money to put down for hobbyists, buying the tool allows you to program the whole range
of Microchips products. If you are a company, the entry cost of the tool is a drop in the
bucket.

C30
C30 refers to the C compiler used for the dsPIC and PIC24 line of products. There is a
student version, which costs nothing, but does not allow code optimization beyond 1, and
there is a commercial version that Microchip sells for about $2000. This version of the
compiler has higher optimization levels. At optimization S, the code is more compact and
executes faster. However, considering the myriad of oscillator options and the size of the
program memory, its a waste of 2000 dollars if you ask me. The two versions are similar
in every other way.
Making your first program
The first program that I always write when I am working with a new processor is to find the
oscillator-out pin (OSCO), and make sure the oscillator works. What I am looking for is a
relatively clean clock signal whose frequency corresponds to the configuration that set by
the configuration bit. However, before such a program can be loaded into the PICs, a circuit
must be made so that the ICD 2 can interface with the microcontroller. It is always the first
thing that I do because finding the OSCO means that the circuit is working correctly.
Typical circuit
Every chip in Microchips huge line of controllers is compatible with the ICD 2. On these
chips, you will always find 5 pins named Vdd (power), Vss (GND), PGC (Program clock),
PGD (Program data) and /MCL (Master clear bar, active low). In order the program a
device, these five pins must be connected to the corresponding pins on the ICD 2. The
following is a typical circuit that I use. I have a RJ11 jack to a 5 pin 1.25 mm header
connector converter, which is then connected to my circuit though a set of discreet wires.
Please note that the Vdd pin from the ICD 2 does NOT provide power. The Vdd supply,
which is 3.3 volts for the PIC24 line of chips must be provided externally. Note the bypass
capacitor on the VddCore pin and the pull up resistor on the /MCL. These are needed for a
stable power supply and program restarts, respectively.
Basic Circuit

After building the circuit, you are ready to test your circuit. Power your circuit and hope
that nothing burns. If you feel that the chip is getting extremely hot, or that your external
voltage regulator is burning up, then you probably shorted something. If not, youre ready
to go to the next step.
03 First Program
Power Regulation
The very first thing that I do when I have a circuit running is verify the power consumption.
The reason this is important is because you want to know if anything on your circuit is
shorted out. In my lab, I have a BK Precision DC regulated power supply. I can fix either
the current or the voltage of the output. If I run the power supply at a fixed voltage, it will
tell me the current drain that my circuit is taking. The circuit I have shown in the previous
tutorial section should drain less than 10 mA. If you see that the ampere readings oscillate
in large jumps or is draining a lot of power, say 100 mA or more, you probably shorted
something.
It is important to have proper power regulation on any digital circuit. This is because digital
circuits have a large number of switching electronics. Switching electronics tend to drain
current erratically. The result, if you have ever measured a Vdd line on digital circuit, it is
very noisy. To avoid a glitch on the power line that could possibly reset the
microcontroller, it is important to do two things:
1. Proper bypass capacitors on power pins The capacitors on a digital circuit act as
tiny little temporary batteries. This allows the erratic drain of current to be
smoothed out so that inadvertent resets can be avoided. Remember to place the
capacitors as close to the power pins as possible.
2. Onboard voltage regulator with proper bypass capacitors This should be pretty
straight forward if youve ever done any electronic designs. A voltage regulator is
essential to ensuring that your power line retains the proper potential during
operations. Typically if my total circuit drain for 3.3V is less than 150 mA, I use the
LP-2985-3.3 by National Semiconductor. They are cheap, have a very low dropout
voltage, and only require 3 external capacitors. However it is a linear regulator, and
should probably be avoided if you are using it for any mobile applications where
power consumption is a priority.
Using MPLAB IDE
Before we can check out the microcontroller, we need to learn some basic functions in
MPLAB IDE. The MPLAB IDE is a FREE integrated development environment (IDE)
distributed by Microchip. It has some neat features that Ill get into later in the tutorial.
The latest version is v8.00, which has support for the new 32 bit microcontrollers that just
came out in November of 2007. However, downloading this latest version does not
guarantee that you have the latest version of the compiler. You will need to download the
latest version of the C30 compiler and properly install it before proceeding.
Starting a project
Now you can start a project. Click on the project wizard to proceed.

Pick the device you have on your circuit, and when the suite selection comes up, click on
the C30 C compiler that you just installed. Click on the directory you want your files to lay.

Next, you will be asked which files you want to be included. This is like the import
keyword in java, or the #include directive in C++. You dont need to add it now. I
usually leave this blank and add it later on manually. Now you are done with your project
template.You will now have an empty project.
You will first need to link the header definitions for your particular chip, and then the linker
definitions. To do this, first make sure your project view is turned on.

Next right click on header files, and add the header file that corresponds to the
microcontroller that you have chosen. These files are usually located in C:\Program
Files\Microchip\MPLAB C30\support\h\[name of device].h or whichever directory the
MPLAB program was installed.


You will then need to do the same for the linker script, which is usually located at
C:\Program Files\Microchip\MPLAB C30\support\gld\[name of device].gld or whichever
directory the MPLAB program was installed. You are now ready to do a device ID check
on your microcontroller.

Checking out the Microcontroller
Before preceding it is a good idea to check if your programming connections to PGD and
PGC are properly connected, and to see if the power voltages are correct. To do so, perform
the following. Click on Debugger and select the ICD 2.

If you connected everything properly, you will see that the ICD will do a check on the Vdd,
and the device ID check to see if the device detected matches the device you selected for
the project. If there is a mismatch, or if the power is not on, you will get an error. Once you
have a working circuit, you can start programming.
The first thing that I like to do is to verify that the oscillator is working. The first program
that I write for any new circuit is an empty infinite loop. This may sound kind of stupid, but
it is wise to have your configurations correct before doing any actual coding.
You will need to add a new file to the Source Files section of the Project View. Create a
new .c file and add it to the source files.
Sample Program
This is what the bare bone program looks like:

/*
Engscope
Author: J. L.
Date: Feb 1, 2008
Test program
*/

//include basic header definition
#include p24FJ64GA004.h

//config
_CONFIG2(0xF9FC);
_CONFIG1(0x377F);

//main loop
int main(void)
{
//Set up Clock
OSCCON = 0x11C0; //select INTERNAL RC, Post Scale PPL
//Loop forever
while(1)
{
}
}
As you can see, I put in a #include directive to tell the compiler to include the file I added
to the Header Files section of the Project View. I have two _CONFIGx functions that
configure certain things for the microcontroller. I will talk more about it in the next tutorial
section. After the configuration functions there is a main loop in the main body function.
In it, I have a while loop that is always true. Before going into the loop however, I set the
OSCCON register to some hex value.
If you selected the correct devices and the correct linker file, the code should compile. In
MPLAB IDE, compile is marked as MAKE and is the icon that looks like layers of paper
with an arrow on top. If the compilation goes through, you will get a compilation summary
with the last line saying BUILD SUCCEEDED.
Next, click on the icon PROGRAM TARGET DEVICE, which is an icon of a microchip
with an arrow pointing to it. If there are no errors, you will get a screen saying the
programming was successful.

Now, the reason I programmed the empty code was to evaluate the oscillator out. In the
_CONFIG1 and _CONFIG2 functions, I have set the oscillator to be internally generated,
and to output the result of the oscillator to the OSCO pin. If you have an oscilloscope,
clamp the probe to the OSCO pin and see if you have a square wave at 8 Mhz. If you dont
have an oscillation at that frequency for this particular device, then youve done something
wrong. Check the datasheet for your particular device to see which pin OSCO outputs.
Congratulations, youve just programmed your first PIC microcontroller. Well look into
some of the configurations available on the next section of the tutorial.
Table of Contents
04 Configurations
Configure What?
All 16 bit microcontrollers from Microchip contain a series of configurable parameters that
are arranged outside of programming. These are more or less special directives built into
the program instructions that tells the microcontroller what to do on the most basic level.
These may include the parameters for selecting which programming pins to use, alternate
pin configurations, watch dog timers, JTAG programming configurations, oscillator
selection and many more, depending on the chip. This section will give a general overview
of how to use and set the correct configuration for your chip.
For this demonstration, I will be using the PIC24HJ32GP204; its got a few more
configurations than the PIC24FJ series since it is more performance oriented. However,
generally I like using the FJ series because they have more features. Its really up to the
application of the project. All the PICs usually work in the same manner so its not that
much work going from one set of hardware to another.
Here, I have a project set up as per my previous tutorial. This is an old project, but its a
good example of how to use configurations.

Every chip in the PIC24 series has a few special registers called the Configuration Bits.
In the datasheets available from Microchip, their functions are usually listed under the
section Special Features. The datasheets also list the addresses where these registers can
be accessed. However, those address are not of any real importance since the header files
provided by MPLABs have macros already built in for the programmer to configure the
device. In the above example the, section highlighted in red display the functions used to
set the configuration bits.

Through the Code
So how do you know what functions to use and what options there exist for a particular
device? You will need to first look at the datasheets for your particular device. You will
need to do this step because the datasheets usually have a detailed explanation of every
option available on the device. Once you know which options are needed for the project,
its time to configure.
The macros are stored in the header file that you have added for your project on a particular
device. In this case, my project uses a PIC24HJ32GP204. If you look in the header file for
the device, there is a section of the code entitled Macros for setting device configuration
registers. These are the macros used to set the configuration bits. Below the macro
declarations there is the list of options. These options will correspond to the options in the
datasheets.

Example
Here is a common example. Lets say I want to use an external clock for my
PIC24HJ32GP204. First I find the option in the datasheet. I now know which options are
available, and which options I would like to select. Next I go to the file
p24HJ32GP204.h, the header file corresponding to my device. The relevant section
defines the macro as follows:
(in the file p24HJ32GP204.h)

#define _FOSCSEL(x)
__attribute__((section("__FOSCSEL.sec,code"))) int _FOSCSEL = (x);

/*
** Only one invocation of FOSCSEL should appear in a project,
** at the top of a C source file (outside of any function).
**
** The following constants can be used to set FOSCSEL.
** Multiple options may be combined, as shown:
**
** _FOSCSEL( OPT1_ON & OPT2_OFF & OPT3_PLL )
**
** Oscillator Source Selection:
** FNOSC_FRC Fast RC oscillator
** FNOSC_FRCPLL Fast RC oscillator w/ divide and PLL
** FNOSC_PRI Primary oscillator (XT, HS, EC)
** FNOSC_PRIPLL Primary oscillator (XT, HS, EC) w/ PLL
** FNOSC_SOSC Secondary oscillator
** FNOSC_LPRC Low power RC oscillator
** FNOSC_FRCDIV16 Fast RC oscillator w/ divide by 16
** FNOSC_LPRCDIVN Low power Fast RC oscillator w/divide by N
**
** Two-speed Oscillator Startup :
** IESO_OFF Disabled
** IESO_ON Enabled
**
*/

#define FNOSC_FRC 0xFFF8
#define FNOSC_FRCPLL 0xFFF9
#define FNOSC_PRI 0xFFFA
#define FNOSC_PRIPLL 0xFFFB
#define FNOSC_SOSC 0xFFFC
#define FNOSC_LPRC 0xFFFD
#define FNOSC_FRCDIV16 0xFFFE
#define FNOSC_LPRCDIVN 0xFFFF

#define IESO_OFF 0xFF7F
#define IESO_ON 0xFFFF
The options Im interested in are FNOSC_PRI (because EC stands for external clock),
and I want to disable the two speed start-up, option IESO_OFF. Now to set the
configurations, in my main program body, after the compiler directives, but before the main
function, I call the _FOSCSEL macro:
(in the file main.c)

//compiler directives
#include ../h/system.h
#include timer.h
#include pic.h

//variable declaration
unsigned int state = 0;
unsigned char temp1;
char flag1 = 0;

//Configs, EC clock, No protect, Watchdog Off
_FBS (BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
_FGS (GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_PRI & IESO_OFF);
_FOSC (FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
_FWDT (FWDTEN_OFF);
_FPOR (FPWRT_PWR1 & ALTI2C_ON);

int main(void)
{
OSCCON = 0x2200; //Use primary, no divide, FCY = 10Mhz/2 = 5Mhz
(rest of main function)
Similarly, the other configuration bits need to be declared. There are default values, but I
often find the need to change them.

Through the GUI
There is an easier to way to change these configuration bits. There is a built in program in
MPLAB with drop down menus associated with every device from which you can select
the options you want. However, I like to have my configuration bits embedded into the
code. This decreases the chance that youll make a mistake when you copy a set of code
over to another project. In addition, embedding the configuration in the code increases the
readability of your program. I always know how a project is configured by just looking at
the code, instead of having to go into MPLABs bit configuration program. However, if
you still prefer a graphical user interface, you can do the following. Note that you only need
to set the configurations with one method or the other. If you do both, one will take
precedent over the other. I believe the macros in the code defer to the configuration from
the program, but I am not 100% sure (since assembly programmers dont do stupid things
like that duh).
First go to Configure, then Configuration Bits.

Next uncheck the box that says Configuration bits set in code.

Now it is just a matter of selecting the option in the drop down. It is very tempting to do it
this way since the task is significantly easier. However I strongly urge you to configure it in
your code. One of the main reasons is that, you will probably have to share code in the
future (with others, or with yourself, in future projects). If the configurations are not in the
code, the task becomes that much more complicated, and is more prone to error.
Thats all there is to it; as Carlin use to say, Easy as pie.
05 Inputs and Outputs
Definition
By definition, a CPU will always have some sort of an IO. In the very least, a program must
be somehow delivered to the CPU. In the most common case however, the term inputs and
outputs (IOs) are usually used to refer to inputs and outputs during the run-time of the
circuit/program.

IOs operations on the PIC24 are done through the pins. There are several modes of usage
on the PIC24s, depending on whether the IOs are used in digital or analog mode. First
determine what the microcontroller needs to do. If for example, you want to measure a
voltage using the PIC24s ADC, then youll need to configure that pin into an analog input.
If you want to end a signal to an open drain NPN transistor, then youll want to configure
that pin into a digital output.
Pin Capabilities
You must be careful however, when assigning pins because not all pins can be configure
any which way desired. Certain pins can only be configured as digital IOs, while other may
be configured as analog or digital. In addition, the voltages which can be applied to the pins
depend on the model of the chip. You must read the datasheet carefully before applying
anything more than the Vdd voltage used to power the chip. What I mean by this is that
certain models, even though they might be powered by a 3.3V power source, will be able to
accept 5V as an input on digital IO pins, and can output 5V with an open drain
configuration. Most of the PIC24s fall into this category. I havent tested the 5V open drain
output myself, but this is because I usually add an external open drain circuit outside of the
controller itself. This is mostly done as a superstition, or just precaution, but whatever is
listed on the datasheet should work. However, I can attest to the fact that 5V on any of the
digital pins will not damage any of the specified PIC24s.
Finally, on the low pin count models, the peripheral abilities of the pins are not fixed. This
makes assigning pins to peripheral modules a bit trickier than the higher number pin
models. We will discuss these issues in just a little bit.
Configurations
Before configuring the pins, you must first be able to identify the pins on the diagram. In
the diagram below taken directly from Microchips datasheet, youll notice several things.

1. Banks of analog pins have the prefix AN (ex. AN0, AN1 AN16).
2. There are several banks of pins with levels RA (ex. RA0, RA1 ) and RB (ex. RB0, RB1 ).
These are your main input and output banks. Depending on the model of the chip, there
might even be a C bank (ex. RC0, RC1 ), and even up to a G pin bank.
3. On the low pin count models, there are pins with the prefix RP (ex. RP0, RP1 RP16).
These are the configurable pins for the peripheral modules.
As an example, I have put down a script from one of my old projects. This is how a typical
pin configuration code might look like. I will explain what each of the lines mean after. I
have also taken out any other configurations so that we can concentrate on the IO portion of
the code. The was done on the PIC24HJ32PG204, so your chips configuration registers
might be a bit different.
(IO configuration example)

(#include directives go here)
(Configurations go here)

//main function start
int main(void)
{

(Oscillator Configuration go here)
//Set up I/O Port
AD1PCFGL = 0xFFFF; //set to all digital I/O
TRISB = 0xFFFF; //configure all PortB as input
TRISC = 0xFFFF; //configure all PortC as input,
RPINR18bits.U1RXR = 2; //UART1 receive set to RB2
RPOR1bits.RP3R = 3; //UART1 transmit set to RB3
(other initializations go here)
//Main Program Loop, Loop forever
while(1)
{

(rest of the program)

Analog or Digital
The register AD1PCFGL controls analog/digital pin selection. As you can see from the
diagram, some of the pins have dual functions. When this is the case, the programmer must
specify whether they will be as analog or digital pins. By setting it to 0xFFFF, all the pins
from AN0 to AN16 will be digital. If for example, I want to use AN5 as an analog pin, and
the rest as digital, my assignment line would look like this:

AD1PCFGL = 0b1111111111011111; //assign analog to AN5, rest
digital
Or
AD1PCFGL = 0xFFDF; //assign analog to AN5, rest digital
Inputs or Outputs
If the pin is not a shared analog/digital pin, or if the pin is configured as a digital pin, then
the pin must be assigned as an input or an output. This function is controlled by the register
TRISx, where x is the bank identifier. In the above example, I assigned both banks, RB,
and RC as all inputs. In another example, if I want my RB0 RB3 to be inputs and RB4
RB15 to be outputs, the code would look like the following:

TRISB = 0b0000000000001111; //pin RB3-0 as inputs, rest outputs
Or
TRISB = 0x000F //pin RB3-0 as inputs, rest outputs
Special Peripherals
The RPx set of pins can be set to any number of special peripherals. When this occurs, any
other configurations such as the input/output selection and the analog/digital selection are
overridden. Not all chips have this feature. Usually on the low pin count chips rely on this
method of assigning peripheral modules because it allows for more flexible pin placement.
There are several peripheral modules available on the PIC24. These include UART, SPI
and several other modes. The inputs functions (ex. UART receive), are configured
differently from the output functions (ex. UART transmit).
To configure the peripheral inputs, refer to the peripheral input mapping section of the
datasheet.

On the PIC24HJ32PG202, there is a bank of registers named RPINRx. Each of these
registers is assigned a function. To assign them to a pin, the programmer puts the RPx pin
number into those registers. For example, in the above image, the external interrupt is
located in the register bank RPINR0. To assign pin RP2 to that pin, the code would look
like the following:

RPINR0.INT1 = 2; //assign RP2 to interrupt 1
In a similar fashion, in the original example taken from my old project, the same is true.

RPINR18bits.U1RXR = 2; //UART1 receive set to RB2
The assignment of the function U1RXR, UART receive is assigned to pin RP2/RB2 (its
the same physical pin).
Configuring an output function is different. This time, each of the pins are assigned a
register (as opposed to a function assigned to a register). These are listed as follows on the
PIC24HJ32PG202.

The assignment is done by tying the function to the register. The register RPOR5.RP11R
for example, is the register assigned to pin RP11. If I want to make it a UART transmit pin,
then the code looks like the following:

RPOR5.RP11R = 3; //UART TX to RP11
That should allow you to do most of all inputs and outputs. Im sure you can figure out the
rest on your own.
Reading and Writing to Pin Banks
While configuring pins might be useful, to actually use them, the pins must be read and
written to. This is a very simple task.
To write to a pin, first, make sure that the pin is configured as digital (you cannot output to
an analog configured pin). Next, make sure that the pin is configured as an output. Lastly,
to write to the pin, use the register LATx, where x is the pin bank identifier. For example, if
I want to write a 1 to RB0, I could use the following code:

LATB = 0x0001; //RB0 = 1
Or
LATB = 0b0000000000000001;
However, since the header file already has assignments for individual pins, theres no need
to write the full 16 bit information to LATB. You can do the following:

LATBbits.LATB0 = 0; //RB0 = 0
To read a pin, make sure that the pin is configured as digital and as an input. Use the
PORTx register to read their values:

int x = 0;
x = PORTBbits.PORTB0; //assign pin RB0 to x
Of course, you can read the whole bank if you wish, with the following assignment:

int x = 0;
x = PORTB; //assign pin RB0-16 to x
I will not cover the analog inputs in this section. Those goodies will be discussed in future
updates. However, this should get you started for now.
06 Oscillator and Timing
It is widely known that microcontrollers usually need some sort of external oscillator such
that it may function. This is mainly because the hardware in the controller is more likely
than not, based on sequential logic. Without a consistent external stimulus, the controller
would cannot consistently execute instructions, and as a result, the outputs may become
unpredictable in terms of timing and value. This is why it is very important to understand
the fundamental nature of oscillators and timing settings, and how they affect the
microcontrollers connected to them.

Terminology
Every Microchip products datasheet has a section with the oscillator diagram. This is a
very good starting point, but first some terminology.
An electronic oscillator refers to a device that displays a repetitive variation, with respect to
time. Typically the electronic response has amplitude and frequency, as well as a
quantitative measurement of the frequency error. These values determine whether the signal
produced by an oscillator is suitable for the application in which the device will be used.
The amplitude simply refers to the difference of electric potential between the peaks and
troughs. Typically, if the inputs of the microcontroller can only take 3.3V, an oscillator
with a 5V output will not do. The maximum DC characteristics for every microcontroller
are listed in the respective datasheet and should be carefully looked at.
The AC characteristics however are defined by the frequency rating of the oscillator.
Typically these are in the range of MHz, to hundreds of MHz in value. The PIC24 family
usually can take upwards of 10-20 Mhz, but they have an internal phase lock loop (PLL)
that can give the controller an effective million instruction per second (MIPS) value
upwards of 120 (thats like having a 120 MHz clock!).
Lastly if the application needs to be very accurate, naturally anything that has to do with
precise measurement of time or speed, the frequency error value for that oscillator needs to
be minimized. This value is usually expressed as parts-per-million or PPM. For example, 8
MHz oscillator with a 50 PPM value might actually be an oscillator with a frequency value
anywhere from 7 999 600 to 8 000 400 Hz.
The PIC24s are very versatile in terms of their oscillator input. There are three main
categories of timing devices compatible with most PIC24s. Direct piezoelectric crystals
can be applied (with corresponding capacitors circuits), as well as IC external clocks. In
addition, for applications where timing isnt that important, Microchip even provided an
internal fast RC oscillator (FRC), so you can run the controller without an external input.

Primary and Secondary
There are two sets of IOs on the PIC24s that can be used for clocking purposes. The
primary IOs, usually labeled OSCI/OSCIO are just like what they sound like, primary
oscillators. This is your main input/output, and the oscillator connected to OSCI will run
the main processor on the PIC. Next there is a secondary clock, aptly labeled
SOSCI/SOSCO. Usually, for time keeping applications such as an alarm clocks and time
sensitive measurements applications, a secondary timing oscillator (such as 32.768 kHz
crystals) can be applied here.
Selection
There are seven main modes of oscillator selection compatible with most PIC24s. These are
controlled by the configuration bits FOSC and POSCMD. The selection process is fairly
trivial, as Microchip provides a useful table with the values required. See the configurations
section to learn how to set the configuration bits.

Once selected, the user will also need to put in the corresponding selection to the OSCCON
register. Once this is done, test your OSCO pin to see if you are getting an oscillating clock
signal. If you see a signal, it means that your connections work. However, the desired
frequency might not be correct.
Generally the dividing the clock is done with the CLKDIV register. This register has
several bits that dictate the manner in which the clock will be interpreted. The
CLKDIV.DOZE<2:0> is the division taken before the clock is sent to the core processor.
The minimum value is 2. As an example, if I connect and select an 8MHz clock to the
PIC24, without PLL, and I set the DOZE<2:0> value to divide by 2, then the processor will
be clocked at 4MHz.
General Usage
Generally if Im writing a program for an application that does not require any precise
timing or interrupts, I avoid using external oscillators. It saves me the time of soldering and
the cost of the oscillator IC. In these situations, I will generally use the FRC mode.
Like I mentioned before, the FRC is not a very accurate clock. However, it is relatively
flexible and can be scaled using the CLKDIV register. The FRCDIV<2:0> allows the user
to adjust the postscaler to divide the clock before being sent to the DOZE<2:0> divider.
In addition to the division, you can tune your FRC to a certain degree (about +/-3% of the
listed frequency) with the register OSCTUN. Of course, you will need to hook the OSCO
pin to the oscilloscope to actually know what kind of effect the OSCTUN register is having
on the FRC clock.
PLL Makes Things Go Fast, Very Fast
If you require some extreme amount of processing power, or you need to run an application
at some high frequency, the PIC24s have a built in PLL to enhance the external clock
inputs. To turn on the PLL, the correct selection on FOSC and POSCMD must first be
made. Then set the register OSCCON to any of the corresponding settings with PLL. The
PLL will allow you to multiply your clock input, and get a faster oscillator input to clock
the processor. The PLL scaling factor is controlled by the bits CLKDIV.PLLPOST(N2),
CLKDIV.PLLPRE(N2) and PLLFBD.PLLDIV(M). These bits affects the how the input
clock is perceived. The underlying equation becomes K = M/(N2*N1), where K is the
multiplier to the input clock frequency. As you can see, this architecture allows the user to
get many different values to clock the core processor, while featuring flexibility in the types
of inputs required.
I generally stay away from PLL usage. It is a way for me to avoid dealing with the
multiplying and dividing. However, I have needed the PLL modules in the past where pure
processing speed was a requirement.
Examples
I have included several examples. Fin is the clock frequency coming in from the clock
source, and Fcy is the clock frequency going into the processor.
External Clock, Primary without PLL, No Divide
Fcy = Fin/2 (always get divided by 2, no matter the selection)

//Configs, EC clock, No protect, Watchdog Off
_FBS (BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
_FGS (GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_PRI & IESO_OFF);
_FOSC (FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
_FWDT (FWDTEN_OFF);
_FPOR (FPWRT_PWR1 & ALTI2C_ON);

int main(void)
{
//Use primary, no divide FCY = 10Mhz/2 = 5Mhz
OSCCON = 0x2200;
CLKDIV = 0x0000; //do not divide
.. Rest of main function
FRC with PLL, Tuned, No Divide, DOZE = 2, N2 = 2, N1 = 2, M = 8
Fcy = Fin*K/2 where K = M/(N2*N1)

//Configs, FRC with PLL, No protect, Watchdog Off
_FBS (BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
_FGS (GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_FRCPLL & IESO_OFF);
_FOSC (FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
_FWDT (FWDTEN_OFF);
_FPOR (FPWRT_PWR1 & ALTI2C_ON);

int main(void)
{
OSCCON = 0x1100;
OSCTUN = 0x0012; //SET FRC at 8 Mhz
PLLFBD = 0x0006; //PLL = 8, bring FOSC to 16 MHz
CLKDIV = 0x0000; //do not divide
.. Rest of main function
Credit to Xo Wang, Jose V for corrections.
Interrupts
The idea of an interrupt is both cool and weird at the same time. If you remember from the
old says of Sound Blaster 16 audio cards, Im sure youve had the unfortunate experience
of setting up interrupts, and getting a headache in the process. If you still dont know what
they are, dont fret. It is a concept rarely used outside of computing. Then again, if Im
assuming that youre reading this tutorial because youre some sort of an engineer, you
should already know what they are.
Pardon the
Interrupts are exactly what the word means: interruptions in the soon-to-be-executed thread
of instructions to the controller. It is basically lines of code that is executed on command
when an interrupt signal occurs. These lines of codes, executed after an interrupt is called
the interrupt service routine (ISR). This interruption signal can come from a variety of
sources. It can come from an external signal, generated internally, generated from an
internal timer, or turned on manually through code.
The reason that interrupts are important is because there are times when a set of code
absolutely must be executed on queue. Since microcontrollers keep relative time with
clocks, and it is impossible to know exactly how many clocks it takes to get to a certain
portion of the code. In other words, time critical execution of code is impossible to do with
sequential code. What this means is that if I want my microcontroller to toggle an LED on
and off at exactly every second, it is impossible for me to write a loop in the main()
function. I need another way to do the job. For this particular LED application, I need a
timer to keep track of time, which will interrupt the microcontroller at exactly every
second, which will start the executing of the code which will toggle the LED. In this
manner, I can assure the turning on and off of the LED at exactly the desired frequency.
Of course, interrupts are not limited to external timer generated signals. Any TTL signal
can be used as an external interrupt. There are several ways for the microcontroller to detect
internally generated interrupts such as when a piece of data is received on the UART, or
when an internal timer has reached its preset count. We will revisit the LED example on the
next portion of the tutorial. For now, we will deal exclusively with external interrupts.
On to the PIC
There are usually 3 external interrupts on a PIC24. They are aptly named External Interrupt
0, External Interrupt 1 and External Interrupt 2. For the sake of simplicity, I will name them
INT0, INT1, INT2 respectively.
INT0 is usually on a fixed pin. In the case of a PIC24HFJ32GP202, it is pin 16.

INT1 and INT2 are controlled through the peripheral IOs. This was covered in the IO
section of the tutorial. Basically, you have the ability to map INT1 and INT2 to any of the
RPx pins on the chip. This allows for great versatility. These pins are the locations where I
would need to connect a device, or a signal that will send the signal to interrupt the
processors current thread of instructions. There is a flip-flop inside the PIC, so that the
microcontroller is not actually listening for a 1 or a 0, but rather looking for signal edge
changes.
Before delving into how to control an external interrupts, a few words on the interrupt
controllers control registers. There are a few important registers that control certain things
such as traps, exceptions, priorities of CPU level interrupts and interrupt errors. They are
controlled by the registers SR, CORCON, INTCON1 and INTCON2. Generally I find that
the default settings work very well. Ive had only one or two instances where changing one
of these settings were necessary. If you dont understand what one of the functions do,
leave it alone. The defaults work just fine for general purposes. However, in this tutorial on
interrupts, we will be looking at one of the settings in INTCON2.
Now, lets move on to the meat of the matter, so to speak. There are three main aspects of
the interrupts to understand. These are the FLAG, the ENABLE, and the PRIORITY. All of
these aspects of an interrupt are controlled by the interrupt registers. On all the datasheets
for the PICs, there is usually a table that maps all the interrupts and their respective
controlling registers. Let us look at one of them.

The ENABLE bit turns the interrupt on or off. When the enable bit is set to a 1, the
microcontroller starts listening for either a positive edge or a negative edge. The FLAG bit
indicates whether an interrupt has occurred. If an interrupt has occurred, the PIC will turn
the FLAG bit from a 0 to a 1. This bit has to be turned back to a 0 manually. Lastly,
there is a PRIORITY setting. This setting is usually 3 bits wide. The reason for the
presence of a PRIORITY setting is the ability to prioritize interrupts from different sources.
If two different devices report an interrupt, the microcontroller needs to know which ISR to
execute first. For the PIC, there are 8 levels of interrupt priority, with 7 being the highest
priority (hence, almost always executes on queue), and 0 as the interrupt disabled.
Interrupt Vectors
Interrupt vector are addresses that the microcontroller go to, to fetch the address of the start
of the ISR. Normally this would have to be set manually, but microchip is generous enough
to have mapped them all out for us. If to look at the h files for a particular chip that
comes with C30, youll find a section on how to declare interrupt vectors without the need
to list absolute addresses. Here is a sample section from the p24FJ64GA004.h file.

//The following macros can be used to declare interrupt
// service routines (ISRs). For example, to declare an ISR
// for the timer1 interrupt:
//
// void _ISR _T1Interrupt(void);
Basically we will need to declare these interrupt vectors for INT0 through INT2 in order to
carve out a section of the programming memory to write the interrupt service routines. This
is a critical step, and must be thoroughly understood. I will reinforce this concept with an
example.
Toggle Device Example
Let us take for example, the application of detection edge transitions of a signal. We have a
signal that goes from a TTL low to high, and back to low, over a certain amount of time,
and we would like to toggle an output pin every time we detect a high to low transition
(negative edge). In a pictograph way, we want an output to do the following: when an edge
is detected, toggle the output.

We will be using INT1, so we must first configure INT1 to an RP10 peripheral pin, and
then make a physical connection between the interrupt signal and RP10. Next, we need to
initiate the interrupts on the PIC, declare the interrupt vector, and submit the
microcontroller into an infinite loop. The ISR itself needs to include two things. It needs to
toggle RB9, which is the output pin, and it needs to reset the interrupt FLAG for INT1. The
complete program would look like the following:

/*
Engscope Tutorial
Interrupts
March 27, 2008
Author: JL
*/

//compiled for a PIC24FJ64GA002, your mileage may vary
#include "../h/system.h"

_CONFIG2(0xFBFD); //use XL external clock, at PPL x 4, and Fosc/2
(osc/2)
_CONFIG1(0x3F7F); //use in Programming Mode

int main(void)
{
//OSCCON = 0x77C0; //select Internal Clock
OSCCON = 0x33C0; //select Primary Oscillator, External XL,
PPL
CLKDIV = 0x0000; //do not divide//Set up I/O Port
AD1PCFG = 0xFFFF; //set to all digital I/O
TRISB = 0xFDFF; //configure all PortB as input, RB9 as
output

//Set up External Interrupt
RPINR0 = 0x0A00; //set RP10 to external interrupt 1
IntInit(); //init interrupts

//Main Program Loop, Loop forever
while(1)
{
}

return(0);
}

//setup external pins
void IntInit(void);
void IntInit(void)
{
INTCON2 = 0x0000; /*Setup INT0, INT1, INT2, interupt on falling
edge*/
IFS1bits.INT1IF = 0; /*Reset INT1 interrupt flag */
IEC1bits.INT1IE = 1; /*Enable INT1 Interrupt Service Routine */
IPC5bits.INT1IP = 1; /*set low priority*/
}

//_INT1Interrupt() is the INT1 interrupt service routine (ISR).
void __attribute__((__interrupt__)) _INT1Interrupt(void);
void __attribute__((__interrupt__, auto_psv)) _INT1Interrupt(void)
{
LATBbits.LATB9 = ~LATBbits.LATB9; //toggle through
IFS1bits.INT1IF = 0; //Clear the INT1 interrupt flag or else
//the CPU will keep vectoring back to the ISR
}
I will go over each section. First the configurations are pretty standard. I have included
several a system.h file, the contents of which does not affect this program. The next lines
configure my clocks and programming modes.
Next I start my main() function with several oscillator and clock configuration. After that, I
set up my IO ports. I want all IO to be digital, and configure all of the bank B pins as input,
except for RB9, which will be an output.

//Set up I/O Port
AD1PCFG = 0xFFFF; //set to all digital I/O
TRISB = 0xFDFF; //configure all PortB as input, RB9 as
output
Next I need to set up my peripheral pins. In particular, the pin RP10 needs to be declared as
the source of INT1:

//Set up External Interrupt
RPINR0 = 0x0A00; //set RP10 to external interrupt 1
Next I initiate my interrupt with the function call to IntInit(), and lastly, the program loops
indefinitely. The reason that there is no code in the main loop is because all the hard work
is done through the ISR.
The initiation process is fairly simple. The INTCON2 register has a bit that can set or reset
to detect a positive edge, or a negative edge. In this case, I have selected the negative edge.

INTCON2 = 0x0000; /*Setup INT0, INT1, INT2, interupt on falling
edge*/
The IntInit() function resets the FLAG, sets the ENABLE bit to turn on the interrupt
channel, and lastly I set the priority to low (1).

IFS1bits.INT1IF = 0; /*Reset INT1 interrupt flag */
IEC1bits.INT1IE = 1; /*Enable INT1 Interrupt Service Routine */
IPC5bits.INT1IP = 1; /*set low priority*/
Of course the priority can be set to high (7) if need be, but this is just a menial
demonstration.
The main part of the interrupt is the ISR. Every time RP10 has a negative edge transition,
the vector INT1Interrupt(void) is called. There is a cumbersome declaration process, which
I have included in this example. Theres no real reason for this declaration, but in order for
the compiler to recognize that this is where I want the interrupt vector to point to, I need to
declare my ISR in the manner shown. Next I toggle my output pin with the line:

LATBbits.LATB9 = ~LATBbits.LATB9; //toggle through ISR
And then reset the flag with

IFS1bits.INT1IF = 0; //Clear the INT1 interrupt flag or else
//the CPU will keep vectoring back to the ISR
Note that you absolutely need to reset the FLAG bit manually. Otherwise youll be stuck in
the ISR forever. Basically, the program structure is such that the ISR is doing the bulk of
the work. This is critical because we dont really know when an edge transition will occur.
However, now are able to set up the controller so that when a transition does occur, we are
ready to execute some instructions.
Now you have a device that counts transitions, and toggles a pin every time a count is
made.
Table of Contents
08 Timers
Timers
Timers are exactly what they sound like. They keep track of time, and send you a signal
when the time is up. Sounds pretty simple right?
The PIC24 has a variety of way to use the timers. On the F series, there are usually 5
timers, one 16 bit timers, and 4 more timers that can be used in two modes: as 4 individual
16 bit timers, or as 2 linked 32 bit timers. On the PIC24H series, there are usually just 3
(only two timers that can be used as two 16 bit timers, or one linked 32 bit timer).
Most Obvious Use: Delays
So why do we need timers? Well, lets say you need a delay in your program. The easiest
and most obvious way to create the delay is to write a delay function that loops around with
a bunch of increments and decrements, and maybe a few no-ops depending on the
compiler:

//loop nops for delay
void DelayuSec(unsigned int N)
{
unsigned int j;
while(N > 0)
{
for(j=0;j < 10; j++);
N;
}
}
However, youre never really sure how many instructions the loops will take. In other
words, your ability to control the accuracy of your delay is limited since it is impossible to
figure out just how the compiler will turn the code into assembly. There are rough ways to
estimate these things, but as the word implies, it is merely an estimate.
A programmer can circumvent this clumsy, but frequently used solution by using timers.
Again, if youve read the oscillator and timing section of the tutorial, youd know that
microcontrollers do not keep track of absolute time. They keep track of relative time with
the use of a clock signal, whether it is externally generated or created internally. Therefore,
the concept of a timer isnt so much a counting clock, but more of a counter of clock cycles.
This is a concept that might be a little strange at first. If you want an exact 1 second delay,
youre going to have to do some math because depending on the frequency clocking your
controller, youll need to count to a certain number (hence, the timers count relative
time).
The simplest way to imagine the timer is to think of it as a counter. You put in the number
you want to count to, and it starts to count the clock cycles. Once youve reached the
desired number, you get a nudge on the shoulder to tell you that the desired number of
clock cycles has been counted.
Using Timers
Even though there are two types of timers on the PIC24s, they both work the same way. I
will only show examples of 16 bit timer, because it is used most often, and because once
you know how it works, its just a matter of turning one or two bits on or off to change the
linked timers into a 32 bit timer. There are several registers to be concerned with. The most
notable is TxCON, where x is the number of the timer. This register controls all aspects
of the timer except its interrupt abilities. If you need a refresher course on interrupts, here is
the link to the previous section of the tutorial.

The general procedure of using a timer is something like the following: First, set the preset
time using the PRx register. This register is the number to count to, before the timer issues
and interrupt signal. Another way of saying this would be, if I set my oscillator clock to
1000 Hz, and my preset PRx register to 1000, then I will get an interrupt every 1 second.
Makes sense right?
Next, using with TxCON register, we can turn the timer modules on and off, as well as
configure other settings. I have included a copy of the datasheet page describing the
TxCON register of Timer1 of a typical PIC24, so that you can take a look at some of the
other settings. Timer1 only allows 16 bit configurations, so the hybrid timers may have
some other settings (such as configuring it into a 32 bit timer. Lastly, we will still need to
configure the interrupts, in order to interrupt the main program when the preset time has
been reached.


Example: Blinking LED
By far, the most frequent college project is to write a program that blinks an LED. This is
usually one of the first projects done in a microcontroller class. Usually I like to write my
interrupts and timer initiations on separate files, but for clarity, Ive included them in the
main body. So to reiterate again, this program blinks an LED.
Considerations
First of all, there are several considerations before we get to the programming. Number one
amongst them is the current drawn by the LED. LEDs usually draw 7 mA to 20 mA for
peak brightness. However, care must be taken when trying to power an LED. The LEDs
have a very low resistance between the anode and the cathode, so putting 5 volts across the
two leads will just burn them out. You need to manually restrict the amount of current
going into the device.

The most obvious way to get around this restriction is to put a resistor is series. However,
even though a resistor in series will restrict the current through the LED, there is another
problem. The amount of current needed to power that LED is significantly large. So large
in fact that the PIC might not be able to supply it. The PIC24 can supply 20 mA per digital
pin, which is not to say that if it has 10 digital pins, it will supply 200 mA. I find that the
total amount that the chip can put out is something like 30-40 mA (so at most two LEDs).
To get around this restriction, an LED driver must be created so that the LEDs are powered
from an external source. An NPN BJT in an open drain configuration does just that. Im
sure youve all dealt with 2N3904s before (even most mechanical engineers I know have
seen that at one point in their lives).

Here the circuit above uses the digital outputs as the input to the base. The current drawn by
the BJT is in the micro-amps range, several orders of magnitude smaller than the amount
required to power the LED. However the current through the LED must still be done
through the resistor at the emitter. This ensures that 7 mA goes through the LED, assuming
a 1.5 V drop across the diode.
Hence: 5V (Vcc) 1.5V (Vdiode) / 0.5KOhm = 7mA
Easy math right? Least circuits 101 was good for something.
Anyways, youll need to build a driver for each of the LEDs in order to turn them on and
off.
Timing
A second consideration is how the timers will be clocked. Typically for precise timing
applications a 32.768 kHz clock is used. This frequency is special because it makes for a
very easy division to achieve a divided clock of 1 Hz (1 Hz = 32.768 kHz * 2^15). Most
alarm clocks, radios clocks and your digital watch probably has one of these inside, in some
form or another. If we can use one of these as the primary clock source to our
microcontroller, we can easily make an LED blink at 1 Hz. You can find them on digikey
for cheap, usually under oscillators.

Programming
In order to blink the LED at 1 Hz, we need to do some divisions. Please review the
oscillator section if you are not familiar with some of the terms. First off, our CLK input is
at 32.768 kHz. Since the instruction clock runs at half the clock rate, then the Fcy = 16.384
kHz. We want to figure out what our preset PR1 value should be (i.e., how many clock
does the timer count before signaling an interrupt?). The answer is simple. To blink at 1 Hz,
the timer must interrupt at 2 Hz (half a second on, half a second off, for a total of 1 Hz, but
the interrupt occurs ever half a second, or 2Hz).
To get from 16 385 Hz to 2 Hz is a division of 2^13 (2Hz = 16 385Hz / 2^13), or in hex:
02000.
Now we have all the calculations we need to start the program. By looking up the timer1
interrupts on the datasheet, I am able to locate which register bits to configure and turn on
the timer1 interrupts. I have selected RB9 to be the output to the base of the LED driver.
Now, the program:

Example Code

/*
Engscope Tutorial
Timers
April 6
Author: JL
*/

//compiled for a PIC24FJ64GA002, you milage may vary
#include ../h/system.h

_CONFIG2(0xFAFD); //use XL external clock, and Fosc/2 (osc/2) at
32.768kHz
_CONFIG1(0x3F7F); //use in Programming Mode

//prototypes
void TimerInit(void);

int main(void)
{
OSCCON = 0x22C0; //select Primary Oscillator, External XL
CLKDIV = 0x0000; //do not divide //Set up I/O Port
AD1PCFG = 0xFFFF; //set to all digital I/O
TRISB = 0xFDFF; //configure all PortB as input, RB9 as
output
TimerInit();

//Main Program Loop, Loop forever
while(1)
{
}

return(0);
}

//Set up Timer, target 2Hz interrupts
void TimerInit(void)
{
//set to (2^13), since 32.768kHz / 2^13 = 4, toggles at 2Hz
PR1 = 0x2000;
IPC0bits.T1IP = 5; //set interrupt priority
T1CON = 0b1000000000000000; //turn on the timer
IFS0bits.T1IF = 0; //reset interrupt flag
IEC0bits.T1IE = 1; //turn on the timer1 interrupt
}

//_T1Interrupt() is the T1 interrupt service routine (ISR).
void __attribute__((__interrupt__)) _T1Interrupt(void);
void __attribute__((__interrupt__, auto_psv)) _T1Interrupt(void)
{
LATBbits.LATB9 = ~LATBbits.LATB9; //Toggle output to LED
IFS0bits.T1IF = 0;
}
The main body of the program is relatively simple. I have configured the oscillators to be
primary, without PLL, and used OSCON to select these settings.

_CONFIG2(0xFAFD); //use XL external clock, and Fosc/2 (osc/2) at
32.768kHz
_CONFIG1(0x3F7F); //use in Programming Mode

//prototypes
void TimerInit(void);

int main(void)
{
OSCCON = 0x22C0; //select Primary Oscillator, External XL
CLKDIV = 0x0000; //do not divide
Since this is an interrupt driven program, I dont need anything in the main loop. The
interrupts will signal the controller to execute the ISRs and toggle my LED.
The timer initiation starts by setting the preset PR1 value to the calculated 02000. Next I
set the timer1 interrupt settings, and turn on the timer to allow the PIC to start receiving
interrupts.

//Set up Timer, target 2Hz interrupts
void TimerInit(void)
{
PR1 = 0x2000; //set to (2^13), since 32.768kHz / 2^13 = 2
IPC0bits.T1IP = 5; //set interrupt priority
T1CON = 0b1000000000000000; //turn on the timer
IFS0bits.T1IF = 0; //reset interrupt flag
IEC0bits.T1IE = 1; //turn on the timer1 interrupt
}
As you can see here, the interrupt settings for timer1 is just like the settings of the external
interrupts of the previous section of the tutorial. Now I can write my ISR for timer1.

void __attribute__((__interrupt__, auto_psv)) _T1Interrupt(void)
{
LATBbits.LATB9 = ~LATBbits.LATB9; //Toggle output to LED
IFS0bits.T1IF = 0;
}
The ISR is as simple as can be. I toggle the input to the base of the open drain LED driver,
in this case RB9. Then I turn the interrupt flag back to 0, to reset the interrupt and wait for
the next one. If all goes well, my PIC24 will interrupt at 2Hz, and will blink the LED at 1
Hz. Done!
09.1 UART Setup
UART Setup
As many of you may know (or may not know, depending on whether you are an ber nerd),
UART stands for Universal Asynchronous Receiver Transmitter. It is a way to transmit
data from one device to another. The 9 pin serial port that are still standard on most
computers is an example of a UART. The serial port on your computer uses a standard
known as RS-232, which is a special UART that sends out its messages at specific voltages,
and not at TTL levels.

In case you dont have a background on transmission theory, Ill touch on some aspects of
the UART below. If you do know a thing or two, well you can just skip to the
programming. But just keep in mind that reading the section below might help in
understanding the programming section.
Asynchronous Transmission
The reason this type of communications is called asynchronous is because a clock signal is
NOT required. This may be a little weird when you think about it. For example, how will
both parties know when to sample the data? How will the timing be established? How does
the transmitter establish the moment to send the next bit and etc.? It is actually quite simple
how they managed to engineer the solution to these problems: both the transmitter and the
receiver agree to the communication speed, or baud rate, ahead of time. However, this is
also where most of the headache comes from when dealing with microcontrollers.
Whenever absolute time is involved, headache ensues.

Definitions
A transmitter is the party sending the message (duh). A receiver is the party receiving the
message. It sounds stupid right now, but when you have a computer and a microcontroller
both sending and receiving, it gets confusing, trust me.
The baud rate is the number of bits that can be sent in one second. This is NOT the same as
the amount of data that can be sent. Due to overhead data such as transmission start/stop
bits, and parity bits, the data rate is always less than the maximum transmission rate. Most
of the time, you need to send 11 bits to be able to receiver one byte of data.
The parity bit is a bit used to check the integrity of the data. Usually it counts the number of
ones (or zeros its the same thing), and indicates whether the number is even or odd. This
ensures that if there is an error in ONE bit, the receiver will be able to detect it. However if
two errors exist, then youre screwed. Of course the probability of having two errors is
exponentially high (Ptotal = P1error^2). The parity bit is strictly optional. I usually dont
bother with it.

Details, Details
I wont get into bit banging action of the UART transmission since that is well
documented. However, I will say that the UART on the PIC24 is implemented on TTL
levels, and NOT on RS-232 levels. To talk to a computer, you will need to use a
transmission level changed such as the MAX200 series from Maxim Integrated Products.
Personally I like to use the MAX208. Its got 4 full duplex lines on there, but I usually use
only one. However for mobile applications it tends to hog quite a bit of power.
Overall, the system works like this (one way transmission): A wire connects from the
transmission output pin of the transmitter to the reception input pin of the receiver. The
ground must also be equalized, so another wire must connect the GND levels of each
device. Once the wires are established and the baud rate is determined on BOTH parties,
the transmission can begin.
The transmission looks like the following:

Each section of the grid represents one time unit equal to the inverse of the baud rate. For
example, if we were at 2400 bps, then each section of the grid in the above picture
represents 1sec/2400 = 416.7 usec. I will designate this time unit as one baud period (baud
period = 1/baud rate).
The transmission line is always pulled up to the TTL high level when not in use. When a
transmission begins, the transmitter must first pull the line down to a zero, for one baud
period. Then it sends the data, with the least significant bit (lsb) of the byte first. Next the
transmitter sends the parity, and lastly, pulls the line back to a high, the stop bit, to indicate
that the transmission has ended. Another transmission cannot begin until at least the end of
the stop bit. Depending on the type of transmission agreed up, the stop bit can be one, or
several baud units long. Most of the time, I want my transmissions as fast as possible, so
usually set one stop bit.
Since both the transmitter and the receiver know the speed the transmission is going take
place, the best possible time to sample the data is at half baud unit intervals.

Obviously if the there is a timing difference between the two parties, theres going to be
some problems with the transmission. If the sampling of the data coming in is will not be
timed correctly and the data received will be incorrect. It is imperative that the timing on
both ends is calculated precisely so that the sampling of the incoming data is timed at
exactly in the middle of the bit transitions.
UART on the PIC24
So how much of this stuff we have to take into account when implementing it into a PIC?
Not much at all. There are UART modules on the PIC24 that takes care of the timing and
translations for you. All you need to know is how to program them.
On the PIC24F series of controllers, there are usually 2 modules, aptly named UART1 and
UART2. On the H series, most of them have only one module. There are no major bugs
with the UART, but there are some issues with advanced features. These bugs are
documented in the Silicon Errata in the documentations page of each of the PIC processors.
You can access them through Microchips website.
The modules are controlled by several registers. They are the UxBRG, the UxMODE, and
the UxSTA. The UxBRG is a register that contains a clock divisor, which is used to
establish the baud rate of the incoming and outgoing message. The UxMODE is the main
control register for the module. This register controls turning the module on and off. Lastly
UxSTA is the status register, which you can probe for current conditions of the module.
The last thing you really need to know is that there is a data buffer for both transmission
and reception. For the transmission buffer, you can load up for 4 bytes of data, and the
module will send them, one at a time, when possible, with the correct timing. On the
receiving end, the buffer is always 4 bytes in size, so youll have to start emptying your
buffer before the 5th byte is loaded or youll start losing received data.
Timing
The hardest part is establishing the timing. If you need a refresher on oscillators and timers,
check out the sections in the tutorial. You will need to figure out your instruction clock
frequency (Fcy) before proceeding. Once you figure out your Fcy, you need to update a
register called to the UxBRG register, which determines the baud rate of your UART
module. In addition to calculating the UxBRG value, there is an additional setting that
allows for the UART to be set in high speed mode, or standard speed mode. There is
usually no need for the high speed mode in most applications, so I will not touch on the
subject in this tutorial. The speed mode is determined by the BRGH bit, located in the
UxMODE register. Since I almost always use standard speed, it is usually set to zero.
According to the datasheet, the UxBRG value is calculated using the following equation,
with BRGH = 0 (standard speed).

As an example, lets say I have an input CLKI running at 10MHz, with no PPL, and no
peripheral clock divide CLKDIV = 00000. I want to communicate with another device at
19200 bps. Then:
Fcy = Focs/2 = 5 MHz.
BRGx = 5*10^6/(16*19200) 1 = 15.276
Since the register UxBRG only takes integers, well have to settle for 15. It doesnt really
matter though. The value of 15 only means that well be off by a little bit with our timing,
but not enough to consistently get an error.
Turning it on and other Configurations
Once we have established the baud rate, next we look at how to turn the UART module on.

It is quite simple, just set that bit to one. All the other settings are rarely used, so leave them
all at zero for now. Do look them over just once however, since these settings are
sometimes application specific. For most of my applications, I have not had to use them.
I will not include a copy of the UxSTA register description in the tutorial since youll have
to read it anyways. But I will go over some of the settings. The UxSTA.UTXISEL<1:0>
bits control how an interrupt is handled during transmission. In most cases, I set the value
to 0b10. I want my interrupts to tell me that the transmission is completed, and that my
buffer is empty. I usually like to transmit one byte at a time. Next I want to control how the
PIC will notify me, when a byte has been received. This is controlled by the
UxSTA.URXISEL<1:0> bits. I usually process a value as soon as it is received so I leave
bits set to 0b00: interrupt when ANY character is received. The only other setting I have to
worry about is the UxSTA.TXEN bit, which turns the transmission on and off.
During transmissions and receptions, there are some other registers I need to be aware of. I
need to know how many slots are left in my transmission buffer, and wait for an empty spot
before sending my next byte. This is indicated by the UxSTA.UTXBF bit. I also need to
know how many values I have received in my reception buffer so that I empty them out to
allow for subsequent transmissions. This is indicated by my UART receive interrupt. Since
I have set the value UxSTA.URXISEL<1:0> to interrupt every time a byte is received, my
UART receive interrupt FLAG, IFS0.U1RXIF, will be a 1 when I receive a byte. You can
find out which interrupt flags correspond to which function on the interrupt table, located in
the interrupt controller section of the datasheet. Also if you need to know what interrupts
do, please go to the interrupt section of the tutorial.

Programming
Now you are ready to do some programming. I usually put all my UART functions in a
separate file so that I can reuse them for multiple projects. They are separated into the
header file uart1.h, and the main UART file, uart1.c. The header file only contains the
prototypes for the functions.
This is what they look like:
uart1.h

/*
Engscope
UART
April 16, 2008
Author: JL
*/

//prototypes

//Initiation
extern void UART1Init(int BAUDRATEREG1);

//UART transmit function
extern void UART1PutChar(char Ch);

//UART receive function
extern char UART1GetChar();
uart1.c

/*
Engscope
UART
April 16, 2008
Author: JL
*/

#include system.h //see tutorial below!
#include uart1.h

//Initiation function
//parameter BAUDRATEREG1 determines baud speed
void UART1Init(int BAUDRATEREG1)
{
//Set up registers
U1BRG = BAUDRATEREG1; //set baud speed
U1MODE = 0x8000; //turn on module
U1STA = 0x8400; //set interrupts
//reset RX interrupt flag
IFS0bits.U1RXIF = 0;
}

//UART transmit function, parameter Ch is the character to send
void UART1PutChar(char Ch)
{
//transmit ONLY if TX buffer is empty
while(U1STAbits.UTXBF == 1);
U1TXREG = Ch;
}

//UART receive function, returns the value received.
char UART1GetChar()
{
char Temp;
//wait for buffer to fill up, wait for interrupt
while(IFS0bits.U1RXIF == 0);
Temp = U1RXREG;
//reset interrupt
IFS0bits.U1RXIF = 0;
//return my received byte
return Temp;
}
The program is pretty self explanatory, so I wont go into detail in explaining them. I think
I comment enough so that most monkeys can put the pieces together. However, I do want to
explain the include directive. First and foremost, youll need to include the uart header
file. However the curious line:

#include "system.h" //see tutorial below!
needs some explanation. As I have mentioned before, I use these files on different projects,
and on different types of PICs. HOWEVER, each PIC needs to have its own specific
definition file. For example, if I want to use my UART file with a PIC24FJ64GA002, I will
need to write the line:

#include "p24FJ64GA002.h"
However, if on another project, I want to use the PIC24HJ12GP202, I will need to delete
the previous line, and add the line:

#include "p24HJ12GP201.h"
This is because you cannot have multiple definitions. I want to add an #include directive
once, and ONLY once, but I have to select the correct file according to the PIC processor
that I am currently using. For this purpose, I use the #if defined directive to figure out
which processor I am currently using. The system.h file does just this. I use the #if
defined directive to figure out which processor I have set up in my MPLAB IDE, and
include the definition files accordingly. This is what my system.h file looks like:

/*
Engscope
Author: JL
July 16, 2007
System file, include all headers
*/

#if defined(__PIC24FJ64GA002__)
#include "p24FJ64GA002.h"
#include "pic_i2c.h"
#include "LCD.h"

#elif defined(__PIC24FJ32GA002__)
#include "p24FJ32GA002.h"
#include "pic_i2c.h"
#include "LCD.h"

#elif defined(__PIC24HJ32GP204__)
#include "p24HJ32GP204.h"
#include "pic24H_i2c.h"
#include "LCDLite.h"
#include "encoder.h"

#elif defined(__dsPIC30F3012__)
#include "p30f3012.h"
#endif

#include "accel.h"
#include "adc.h"
#include "uart1.h"
I have shortened the file quite a bit, because it is only here to demonstrate the procedure. In
this manner, I never have to change my include directives since I have a case statement that
automatically includes the correct file.
Part 2 will deal with how to actually use these files.
09.2 UART Usage
UART Usage
In part 1 of the UART tutorial, I showed you the basic building blocks of the UART
module. Included were three functions to initiate the module, and to send a receive data on
the serial line.
So now what? Well, theres tons of stuff you can do. The limitations are boundless,
since there are so many products and chips with a UART controller on them. Your
computer for example has a serial port, and it already has an application that can read and
write in UART.
The limitation however is that serial port on your computer is implemented in RS-232, and
the UART module on the PIC is in TTL levels. How can you get around this? Maxim IC
sells a series of RS-232 to TTL transceivers that translate between the RS-232 levels to
TTL levels. I usually use the MAX208; you can find the datasheet on their website.
I wont go into how the circuit looks, but if I get enough demand for a circuit, I might
eventually post an example. However, once you have it hooked up, you can see if the
UART functions work. The following example shows how to use the functions discussed in
the part 1 of this UART tutorial.
Example: UART Repeater
Okay, so this is like a stupid device. Theres no useful application for a UART repeater, but
Ill include it anyways since it demonstrates well the send and receive capabilities of the
PIC24. The device functions thusly: it waits for a signal on the RX line, and when it
receives a signal, sends the exact data back on the TX line.
The UART RX pin is of course connected to some signal source that can produce a UART
signal, at TTL levels, and the UART TX signal coming out of the PIC24, you can do
whatever you feel like with it (it doesnt matter, its just a demonstration of how to use the
UART functions).
The project contains 4 files. The main file main.c contains the initializations and the main
loop. The uart1.c and uart1.h files contain the functions for the UART module. The
system.h file contains the #include directives for the specific PIC model that is selected
by MPLAB IDE. For a review of why I use a system.h file, go to part 1 of the UART
tutorial. My OSCI is running at 10 MHz (review oscillator functions here).
system.h

Engscope
Author: JL
July 16, 2007
System file, include all headers
*/

#if defined(__PIC24FJ64GA002__)
#include p24FJ64GA002.h
#include pic_i2c.h
#include LCD.h

#elif defined(__PIC24FJ32GA002__)
#include p24FJ32GA002.h
#include pic_i2c.h
#include LCD.h

#elif defined(__PIC24HJ32GP204__)
#include p24HJ32GP204.h
#include pic24H_i2c.h
#include LCDLite.h
#include encoder.h

#elif defined(__dsPIC30F3012__)
#include p30f3012.h
#endif

#include accel.h
#include adc.h
#include uart1.h
main.c

/*
Engscope Tutorial
UART Repeater
Author: JL
April 29, 2008
*/

#include ../h/system.h
#include timer.h
#include pic.h

//prototype
void RepeaterProcessEvents();

unsigned int state = 0;
unsigned char temp1;

char flag1 = 0;

//Configs, EC clock, No protect, Watchdog Off
_FBS (BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
_FGS (GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_PRI & IESO_OFF);
_FOSC (FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
_FWDT (FWDTEN_OFF);
_FPOR (FPWRT_PWR1 & ALTI2C_ON);

int main(void)
{
OSCCON = 0x2200; //Use primary, no divide FCY = 10Mhz/2 = 5Mhz
CLKDIV = 0x0000; //do not divide

//Disable Watch Dog Timer
RCONbits.SWDTEN = 0;

//Set up I/O Port
AD1PCFGL = 0xFFFF; //set to all digital I/O
TRISB = 0xF3FF; //configure all PortB as input
RPINR18bits.U1RXR = 2; //UART1 receive set to RB2
RPOR1bits.RP3R = 3; //UART1 transmit set to RB3

UART1Init(15); //Initiate UART1 to 19200 at 10MHz OSCI
DelaymSec(1000);

//Main Program Loop, Loop forever
while(1)
{
//process data
RepeaterProcessEvents();
}

return(0);
}

//Repeater main function
void RepeaterProcessEvents()
{
unsigned char data = 0;

//wait for data to be received
data = UART1GetChar();

//send data back on UART TX line
UART1PutChar(data);
}
uart1.h

/*
Engscope
UART
April 16, 2008
Author: JL
*/

//prototypes

//Initiation
extern void UART1Init(int BAUDRATEREG1);

//UART transmit function
extern void UART1PutChar(char Ch);

//UART receive function
extern char UART1GetChar();
uart1.c

/*
Engscope
UART
April 16, 2008
Author: JL
*/

#include system.h //see tutorial below!
#include uart1.h

//Initiation function, parameter BAUDRATEREG1 determines baud speed
void UART1Init(int BAUDRATEREG1)
{
//Set up registers
U1BRG = BAUDRATEREG1; //set baud speed
U1MODE = 08000; //turn on module
U1STA = 08400; //set interrupts

//reset RX interrupt flag
IFS0bits.U1RXIF = 0;
}

//UART transmit function, parameter Ch is the character to send
void UART1PutChar(char Ch)
{
//transmit ONLY if TX buffer is empty
while(U1STAbits.UTXBF == 1);
U1TXREG = Ch;
}

//UART receive function, returns the value received.
char UART1GetChar()
{
char Temp;

//wait for buffer to fill up, wait for interrupt
while(IFS0bits.U1RXIF == 0);
Temp = U1RXREG;

//reset interrupt
IFS0bits.U1RXIF = 0;

//return my received byte
return Temp;
}
Since I have gone through explaining the uart1.c, uart1.h and system.h files already
in the first section of the tutorial, I will only go over the main.c file. The main file starts
with all the regular initiations:

#include ../h/system.h
#include timer.h
#include pic.h

//prototype
void RepeaterProcessEvents();

unsigned int state = 0;
unsigned char temp1;

char flag1 = 0;

//Configs, EC clock, No protect, Watchdog Off
_FBS (BWRP_WRPROTECT_OFF & BSS_NO_FLASH);
_FGS (GSS_OFF & GCP_OFF & GWRP_OFF);
_FOSCSEL(FNOSC_PRI & IESO_OFF);
_FOSC (FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);
_FWDT (FWDTEN_OFF);
_FPOR (FPWRT_PWR1 & ALTI2C_ON);

int main(void)
{
OSCCON = 0x2200; //Use primary, no divide FCY = 10Mhz/2 = 5Mhz
CLKDIV = 0x0000; //do not divide

//Disable Watch Dog Timer
RCONbits.SWDTEN = 0;

//Set up I/O Port
AD1PCFGL = 0xFFFF; //set to all digital I/O
TRISB = 0xFFFF; //configure all PortB as input
Note that my CLKI input is running at 10 MHz, so the instruction clock is running at 10
MHz/2 = 5 MHz. Now I set up my TX and RX pins.

RPINR18bits.U1RXR = 2; //UART1 receive set to RB2
RPOR1bits.RP3R = 3; //UART1 transmit set to RB3
At Fcy = 5MHz, and a desired baud rate of 19200 bps, I have calculated my BRG to be 15,
as discussed in UART part 1. I put a delay after the initiation because certain components
of my circuit might not have reset yet. This is just an arbitrary delay.

UART1Init(15); //Initiate UART1 to 19200 at 10MHz OSCI
DelaymSec(1000);
Lastly, I loop forever.

//Main Program Loop, Loop forever
while(1)
{
//process data
RepeaterProcessEvents();
}

return(0);
The function that gets called during the main loop is called RepeaterProcessEvents().
This function is very simple:

//Repeater main function
void RepeaterProcessEvents()
{
unsigned char data = 0;

//wait for data to be received
data = UART1GetChar();

//send data back on UART TX line
UART1PutChar(data);
}
I create a temporary variable named data, which I store the received character. The
function UART1GetChar() will only exit when a data is received. So if you dont sent
anything on the RX line, your program is going to loop in the UART1GetChar(); function.
When the function does receive a byte, it then calls the function UART1PutChar(data).
This function sends the received data and repeats it on the TX pin.
Done!
10.1 I2C Basics

10.1 I2C Basics
Ive gotten quite a few requests for the I2C bus section of the PIC24 tutorial. This is
understandable since most digital electronics these days, both to reduce design cost and pin
count, uses the I2C bus for inter-IC (get it? IIC = I2C, yeah engineers are smart like
that) communications.
Just in case you have no clue what I2C is, it is a communications bus invented by Philips.
Although there are provisions in the I2C standard for multi-master environments, as well as
10 bit addressing, the most simple and commonly used configuration is the single master, 7
bit addressing. This configuration will be the only one addressed in this PIC24 tutorial.

Unlike the UART, which is an asynchronous form of communication, the I2C is
synchronous. The master device sends both the data signal, as well as the clock signal, with
the exception during data reads. The receiver syncs up with the clock signal, and samples
the data on the rising edge of the clock. Ultimately, this means that the transmitter and
receiver do not need to know the clock rate because a clock signal is sent along with data. If
you remember from the UART tutorial, a predetermined clock speed must be known on
both the transmitter and the receiver. This is great news for engineers, because you dont
have to do clock calculations (for the most part). There are upper limits to how fast you can
send the clock signal, but as long as you are below the upper limits, the bus will work fine.
The I2C bus requires two wires to be connected between the master and slaves, as well as a
return (GND) wire. This usually means that at least three wires needs to be connected from
the master to the slave. However, there are several advantages over other forms of buses for
communications between chips. Personally I prefer using the I2C for the ease of use and
reliability of communications. The minimal clock calculations, most of the time as an
afterthought is a huge advantage over the UART. In addition, you can also establish
communications between more than just two chips. There are provisions in the I2C
standard that allows for multiple devices, and layers of software and hardware to decode
the transmission so that the packet sent by the master device gets accepted by the intended
slave device, and only by the intended slave device. This is NOT the case in the UART. In
the UART communications model, there is a transmitter, and a receiver. If you have
multiple receivers, well, I dont know what would happen. The convention is to name the
data line SDA, and the clock line SCL, and as such, I will use this convention in the
tutorial.
I will not explain every single section of the I2C standard, but I will provide diagrams for
the most basic communications.
Hardware
Obviously, youll need to have two devices: a master I2C device and a slave I2C device.
For most applications, the master device is obviously the PIC24. We want to use it as a
programmable controller, to control one or several slave devices. Second, the I2C bus is an
open drain pull-down bus. What this means is that the bus needs to be pulled up to a
nominal voltage (Vdd) when no devices are occupying the wires. Because it is an open
drain bus, this nominal voltage is not strictly specified. Usually I use 3.3V, but applications
such as the DDC in your monitor use 5V. A set of pull-up resistors need to be tied from the
data and clock lines to Vdd. This assures that when the bus is idle, the voltages on data and
clock are always at Vdd. In such a configuration, the devices on the I2C bus pull DOWN
on data and clock. This way the devices dont have to generate the nominal Vdd voltage if
it wants to send a 1, which might be somewhat electrically difficult because it is
arbitrarily fixed (i.e. it is very hard for a 3.3V powered device to generate a 5V signal on a
5V I2C bus). A device only needs to pull the data line to a GND if it wants to send a 0.
Since GND is a reference point that all devices must have, it is technically possible to have
a 5V device communicate with a 3.3V device on the same I2C bus!
Generally speaking, the pull up resistor values must be big enough so that it doesnt
overpower the pull down ability of weakest pull down device (i.e., if I pull up the SDA line
to Vdd with a 10 Ohm resistor, can my device pull the SDA line to GND hard enough to
overcome that resister when it wants to send a 0? The device must overcome the resistor
in order to manipulate the SDA and SCL lines). However the pull up resistors must also be
small enough such that they can pull enough current to return the SDA and SCL lines back
to Vdd in a very short amount of time. Small enough such that the rise time of the pull-up is
short compared to the clock period.
In technical terms, if you like EE speak, it means that the impedance created by the pull up
resistors on the transmission lines must be such that a clock signal can be sent through with
crisp edges. If the resistor is too large, your rising edges will be damped. If you resistor is
too small, your chips gets cant dissipate enough current to pull down, and your falling
edges get damped.
In practical terms, stick with 2.5K Ohms. That should satisfy most situations. Whatever you
do, only put ONE set of pull up resistors for each line. DO NOT put one set of pull up
resistors for each device. This would put the resistors in parallel. And we all remember
from circuits 101 what resistors in parallel do.
Basic I2C Communications
On the electrical level, the I2C communications functions as follows:

Image credit goes to Wikipedia. In a hypothetical situation, lets say there is one master
device and one slave device on an I2C bus line with pull-up resistors pulling to 3.3V. When
the bus is idle, both the SCL and SDA lines are pulled high by the pull-up resistors. If the
master wants to send a byte of data, it must first initiate a start bit (Label S), where the SDA
line is pulled to a logical 0, while maintaining a logical 1 on the SCL line. Next it starts
sending a clock signal. Because the SCL is pulled high at the start bit, the master must pull
SCL down for half a clock cycle, before the first rising edge. Since the data is only sampled
on the rising edge, at all other times, the master is allowed to transition the SDA between a
logical 0 and a logical 1. On the first rising edge, the SDA line must have stabilized. The
slave device samples the data (Label B1). This process is repeated until the 8th rising edge,
or the 8th bit. Right after the 8th rising edge, the master device releases control of the SDA
line. It has finished sending its data, and is now awaiting a response from the slave device.
The slave device has one clock cycle to transition the SDA clock to a logical 0 to
acknowledge the reception of the data byte. This is referred to the /ACK bit. The slash in
front of the ACK indicates that it is an active low signal. The master then sends a 9th
rising edge on the SCL line, and samples the SDA line. If it is a logical 0, then the slave
device has successfully received the byte. If it is a logical 1, then something wrong has
occurred (under most circumstances, the /ACK is not required during the last byte of a read
command). Lastly the SCL is held high, and subsequently the SDA line is pulled high to
signal a stop bit (P).
In the subsequent explanation of I2C packets, I will refer to the ACK bit with these
notations: /ACK refers to an acknowledge bit. Since the signal is an active low signal, when
a device wants to communicate an acknowledgment, it must send a logical 0 (i.e., 0 V).
However, when the device wants to indicate that there is something wrong, or that it did not
correctly receive a piece of data, it sends a /NACK as an acknowledgement, represented by
a logical 1 (i.e. 3.3 V or 5 V).
Basic I2C Packet
There are two main types of operations the I2C bus. These are the random write command,
and the random read command. In general, a packet sent by the master for a write command
looks something like this:

This picture is taken from the datasheet of the AT24C02B EEPROM chip from Amtel.
Three bytes are sent from the master device, and received by the slave if he is present on
the bus. The first byte is the slave address and the read/write indicator. The first 7 bits is the
slave device address. This value is always indicated in the data sheet of the slave address.
In order for the I2C bus to allow for multiple slaves, the master must indicate which device
the message is intended. The slave address, which begins every I2C transaction is the
address that corresponds to a devices on the bus. Obviously there cannot be multiple slaves
with the same address. This would create a collision on the I2C bus. The 8th bit of the first
byte is the read/write indicator. If the bit is a 0, the master wants to write a byte, if it is a 1,
the master intends to read. Next, on the 9th bit of the first byte, the master waits for an
acknowledge bit from the slave. If the address sent by the master corresponds to the slave
address of a slave device, the slave device MUST acknowledge on the 9th bit. If the /ACK
is a 1, a non-acknowledge, then the master thinks there is no slave with the sent address. It
terminates the packet with a STOP bit. However, if a slave does send back a /ACK with
logical 0, then the master device begins sending the second byte. The second byte is usually
the slaves internal address (referred to as the WORD ADDRESS in the above drawing).
For example, on the AT24C02B EEPROM, there are 256 memory locations of 8 bits each.
Whenever I want to write to the device, I must specify which one of the 256 memory
locations I wish to write to. This is what the second byte of the packet is usually used for.
On the 9th byte of the second byte the slave device must acknowledge that the specified
address exists as one of the slave devices internal addresses. Lastly, the master device
sends the data it intends to write. This is usually referred to as the data byte. Finally the
slave device acknowledges the received byte, and the communications is terminated by a
stop bit.
The second most commonly used command is the random read. It is called a random read
because this is the ability to read any address at will, as opposed to a sequential read,
everything must be read, until the correct piece of data is located. Think of it as a DVD,
where you can access anything instantly, versus a VCR, where you need to wined the tape
to the correct spot before watching a particular scene.

A random read must be done in the following manner. The master device must first act like
it intends to do a random write by sending the slave device address with a write command
bit (recall that the slave address is 7 bits long, NOT 8 bits long, the last bit of the first byte
is reserved for the read/write command indicator). It then sends the internal address just as
if it intends to do a device write. The slave address must send a /ACK on 9 th bit of both
the bytes to indicate to the master device of its presence. However, at the end of the 2nd
byte, the master device resents a start bit. For the 3rd byte, the master device resends the
slave address, but with the read command bit for the 8th bit of the 3rd byte. At this point,
after the slave address sends a /ACK back to the master, the roles are reversed between the
slave and master. The master device is still in control of the SDA line, but the slave device
takes over the SDA line and waits for /ACKs from the master device. After the slave device
takes over the SDA line, it starts transmitting the byte located at the internal address. The
slave device starts manipulating the SDA line according to the clock. On each of the rising
edge of the SCL line, sent by the master device, the slave device must be ready with the bit
it intends the master device to read. After the 8th bit is sent, the master device must indicate
a /NACK, the inverse of the inverse-ACK (logical 1) to tell the slave device to stop taking
control of the SDA line. It ends the transmission with a stop bit.
On the Subject of Writing
Be careful when writing to EEPROMS, or devices with permanent memory. It usually takes
about 10 ms or so before the device successfully finishes writing the data to memory. The
AT24C02B for example, requires you to wait a full 5 ms before allowing a subsequent
write operation:

You must take into account these write cycle times when using a microcontroller. This is
why it is important to take note of the /ACK signals sent by the slave device. When a slave
device is NOT READY for a subsequent write operation, it will reply with a /NACK,
logical 1, on every byte sent by the master.
Advanced I2C packets
The two advanced packet formats are the sequential write and the sequential read. As you
can see from the two basic packet formats, there are a lot of overhead data required to write
one or two bytes to a device. What I mean by this is that, if I want to write 8 bytes to an
EEPROM device with my PIC24, I will need to actually send 24 bytes of data using
random write commands. This is because 8 bytes of the data is used to send the slave
address, 8 bytes of the data is used to send the internal address, and finally 8 bytes of data is
the actual data that I wish to write. In addition, EEPROMs have long write cycles of several
milliseconds. To get around these limitations, most EEPROM have the ability to write 8 or
so bytes in one sequential write. In addition, most EEPROMs allow you to read all its
data in one sequential read.
A sequential write is very similar to a random write. The following diagram from the
AT24C02B datasheet does a great job of illustrating the point.

The master device sends the exact same packet as a single write. However, after the /ACK
bit of the 3rd byte is sent, it DOES NOT send a stop bit. It continues to send data, expecting
a /ACK from the slave after each byte. After each transmitted byte, the internal address of
the slave device is automatically incremented, and therefore the master device does not
have to manually select the next internal address. Usually sequential writes are limited to
about 8 bytes before the internal buffer of the slave device is full, in which case it will stop
sending /ACKs, indicating that something is wrong. After the last byte that the master
wishes to send, and waiting for the corresponding /ACK signal from the slave device, the
master device sends a stop bit to terminate the transmission.
A sequential read is also very similar to a random read.

The master device sends the exact same packet as a single read. But whereas in a random
read the master sends a /NACK to stop the transmission, this time it send a /ACK to
indicate to the slave device that it wants more data. The internal address is automatically
incremented and the next byte of data is sent by the slave. The slave will keep on sending
data until it receives a /NACK, at which point the master device terminates the transmission
with a stop bit. There are usually no limitations on how many bytes the slave is limited to
sending. The slave device just keeps on sending data on the SDA line unit it receives a
/NACK from the master.
In the next section of the tutorial, Ill show you the basic functions of the I2C module on
the PIC24.
10.2 I2C Basic Functions
I2C Basic Functions
In this section, I will outline some of the basic functions for the I2C module on a PIC24.
Since the bulk of the electrical functions were explained in section 1 of the tutorial, this
section mainly focuses on the I2C module of the PIC24.
Word of Caution: Silicon Errata
There is a bug in the PIC24FJ64GA004 series of chips (16GA002, 32GA002, 48GA002,
64GA002, 16GA004, 32GA004, 48GA004, 64GA004). The bug is listed in the silicon
errata section on Mircochips website. Go there and search for Silicon Errata, and select the
chip you are using. If the chip you are using is not affected by the error then you are good
to go. However, if the error is present youll need to try to get around the problem. In the
PIC24F series of chips, there are two I2C modules. The I2C1 module does NOT work
correctly in the master mode. You can read more about it in my post on Microchips
developer forums.
There is a work around to making the I2C1 module work, but it is clumsy and does not
always guarantee success. For this reason, if you are thinking about using the I2C module, I
would suggest using only the I2C2 module on the PIC24F64 family, or just avoid the whole
PIC24FJ64 family all together. I much prefer the PIC24H family anyways.
The Registers
As always, the starting place to understanding how to a module is with the registers. In this
case, there are mainly 3 registers of great concern.

The first is I2CxCON. This register is the main control register. With it, you can initiate
start bit, stop bits, and test to see if acknowledge bits were received. The next one is the
I2CxSTAT. This register allows you to see what the current status of the I2C module. This
may include polling to see if there are errors, bit collisions, receive buffer overflows and
other occurrences. Lastly there is the I2CxBRG, which determines the bit rate at which the
module operates.
There are other minor registers such as I2CxRCV, I2CxTRN and I2CxMSK. The
I2CxRCV register is used to retrieve received data. The I2CxTRN register is used to send
data. The I2CxMSK is not used in our example since it is only used when the module is set
to Slave mode.
When using the I2CxCON register, you must be careful with some of the initiation bits.
When setting a Start or Stop bit, the hardware will automatically clear the set bit when it is
ready to proceed with processing data. This means that it is not enough to just initiate a start
bit and then proceed to send or receive data. You must keep polling that bit until the PIC
automatically clears the bit, and only then proceed with processing data. There will be more
on this matter later in the basic functions.
Using the I2C Module
The way I approach the I2C module is analogous to making sentences. I start with letters,
which adds up to words, and combine them to make sentences. The individual base
commands and the fiddling with the registers are kind of like the letters. I use these
commands to form basic functions, functions that write or read one byte (words), and
finally I combine these functions into full packets, with address, sub-address, and data (full
sentences).
Recall that a basic write packet is composed of a chip (or slave) address, a sub-address,
and the data. A read packet is composed of a chip address and a sub-address in the write
configuration, followed by a chip address and the data sent by the slave chip.
Each of the individual bits and bytes must be sent is a specific order. The whole packet
must be sent correctly for the slave to understand the intention of the master.
There are quite a few functions in this tutorial. I dont think I need to go through every one
of them, but I will annotate here and there if things are not very clear.
Initiation
Several things must be initiated before using the module. I use the following function.
void i2c_init(int BRG)

//function initiates I2C1 module to baud rate BRG
void i2c_init(int BRG)
{
int temp;

// I2CBRG = 194 for 10Mhz OSCI with PPL with 100kHz I2C clock
I2C1BRG = BRG;
I2C1CONbits.I2CEN = 0; // Disable I2C Mode
I2C1CONbits.DISSLW = 1; // Disable slew rate control
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2C1CONbits.I2CEN = 1; // Enable I2C Mode
temp = I2CRCV; // read buffer to clear buffer full
reset_i2c_bus(); // set bus to idle
}
The reset_i2c_bus() function is a basic function that set the I2C bus to an idle state. This
way the bus is ready to be used immediately after initiation.
Usually, slave devices are rated for up to 100 kHz. The fast specifications require that
devices function up to 400 kHz. In the newest spec for the I2C bus, the ultra fast allows
devices to function up to 1 Mhz, which is really fast. I rarely need anything that fast, and
generally just stick to something slow like 40 kHz or so. It doesnt really matter what baud
rate you pick since the controller is sending the clock signal anyways. With a 10 MHz
oscillator, I usually set the BRG value to about 100. This works for most applications.
Basic Functions
In order to initiate a start bit or a stop bit, there are some intricacies that a user must be
aware. First, as I mentioned earlier, the hardware automatically clears certain bits in the
I2CxCON register. Again, I cannot emphasize how important it is to read and
UNDERSTAND the datasheet. Because of the way these bits work, when I want to initiate
a start or stop condition, I must write a loop that keeps on polling the bit until the hardware
clear, after which I can proceeding to do my sending and receiving. Another way to do this
would be to put a long delay, long enough that assure the occurrence of the hardware clear.
I usually take the former approach, as it requires more coding, but is more efficient. The
following is how I write the function to initiate a start bit and restart bit:
void i2c_start(void)

//function iniates a start condition on bus
void i2c_start(void)
{
int x = 0;
I2C1CONbits.ACKDT = 0; //Reset any previous Ack
DelayuSec(10);
I2C1CONbits.SEN = 1; //Initiate Start condition
Nop();

//the hardware will automatically clear Start Bit
//wait for automatic clear before proceding
while (I2C1CONbits.SEN)
{
DelayuSec(1);
x++;
if (x > 20)
break;
}
DelayuSec(2);
}
void i2c_restart(void)

//Resets the I2C bus to Idle
void reset_i2c_bus(void)
{
int x = 0;

//initiate stop bit
I2C1CONbits.PEN = 1;

//wait for hardware clear of stop bit
while (I2C1CONbits.PEN)
{
DelayuSec(1);
x ++;
if (x > 20) break;
}
I2C1CONbits.RCEN = 0;
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2C1STATbits.IWCOL = 0;
I2C1STATbits.BCL = 0;
DelayuSec(10);
}
I also need a function to initiate a stop bit. However, I realize that when I initiate a stop bit,
what I really want to do is put the I2C bus in an idle state. For this reason, I reset a whole
bunch of registers every time I initiate a stop bit to clear them of any previous errors and
ACKs. Again beware of automatic hardware clears as in the case of I2C1CONbits.PEN.
void reset_i2c_bus(void)

//basic I2C byte send
char send_i2c_byte(int data)
{
int i;

while (I2C1STATbits.TBF) { }
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2CTRN = data; // load the outgoing data byte

// wait for transmission
for (i=0; i<500; i++)
{
if (!I2C1STATbits.TRSTAT) break;
DelayuSec(1);

}
if (i == 500) {
return(1);
}

// Check for NO_ACK from slave, abort if not found
if (I2C1STATbits.ACKSTAT == 1)
{
reset_i2c_bus();
return(1);
}

DelayuSec(2);
return(0);
}
Send and Receive, Write and Read
The basic send function is written as follows:
char send_i2c_byte(int data)

//basic I2C byte send
char send_i2c_byte(int data)
{
int i;

while (I2C1STATbits.TBF) { }
IFS1bits.MI2C1IF = 0; // Clear Interrupt
I2CTRN = data; // load the outgoing data byte

// wait for transmission
for (i=0; i<500; i++)
{
if (!I2C1STATbits.TRSTAT) break;
DelayuSec(1);

}
if (i == 500) {
return(1);
}

// Check for NO_ACK from slave, abort if not found
if (I2C1STATbits.ACKSTAT == 1)
{
reset_i2c_bus();
return(1);
}

DelayuSec(2);
return(0);
}
The return of the function sends back a 1 if there is an error, and a 0 if the transmission
went through correctly.
I use two different read functions. This is because a sequential read requires the use of an
ACK sent by the master to initiate the next byte. A random read of a single byte of data
does not require an ACK to be sent.
char i2c_read(void)

//function reads data, returns the read data, no ack
char i2c_read(void)
{
int i = 0;
char data = 0;

//set I2C module to receive
I2C1CONbits.RCEN = 1;

//if no response, break
while (!I2C1STATbits.RBF)
{
i ++;
if (i > 2000) break;
}

//get data from I2CRCV register
data = I2CRCV;

//return data
return data;
}
char i2c_read_ack(void)

//function reads data, returns the read data, with ack
char i2c_read_ack(void) //does not reset bus!!!
{
int i = 0;
char data = 0;

//set I2C module to receive
I2C1CONbits.RCEN = 1;

//if no response, break
while (!I2C1STATbits.RBF)
{
i++;
if (i > 2000) break;
}

//get data from I2CRCV register
data = I2CRCV;

//set ACK to high
I2C1CONbits.ACKEN = 1;

//wait before exiting
DelayuSec(10);

//return data
return data;
}
Putting it Together
There you have it. Those are the basic words in a sentence. To use my basic I2C
functions, I need to follow the I2C standard.
The I2C standard requires that a random write be sent in the following manner:

The PIC (master) must send a start bit, followed by a device address, with the WRITE
command. The slave then sends an ACK to acknowledge the byte. Next the master sends
the sub-address, followed by a slave ACK. Lastly it sends the data, followed by a slave
ACK, and finishes with a stop bit.
If you look closely at the send_i2c_byte(int data) function, youll see that it waits for an
ACK from the slave. If a /ACK is detected (no acknowledge detected), then it returns an
error and sets the I2C bus back to an IDLE state. This is exactly what we need.
A random write function is composed of words of basic functions in the exact manner
required by the I2C specifications:
void I2Cwrite(char addr, char subaddr, char value)

void I2Cwrite(char addr, char subaddr, char value)
{
i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
send_i2c_byte(value);
reset_i2c_bus();
}
In the same way, a random read must be sent in the following manner:

The random read function starts off exactly the same way as a random write. However,
after the 2nd byte, the master must initiate a RESTART bit, followed by the device address
with a read command. The slave sends and ACK, followed by the data requested by the
master. The master then sends a /ACK, before issuing a stop bit. I wrote the following
function to emulate the I2C requirements:
char I2Cread(char addr, char subaddr)

char I2Cread(char addr, char subaddr)
{
char temp;

i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
DelayuSec(10);

i2c_restart();
send_i2c_byte(addr | 0x01);
temp = i2c_read();

reset_i2c_bus();
return temp;
}
The function returns the data received.
Polling
Polling is an important function during I2C operations. I usually use a poll function before
any read or writes because I want to make sure that the device I think is on my I2C bus is
actually on my I2C bus. I eliminates communication errors that otherwise might escape the
regular functions error handling abilities. The premise of the polling function is to wait for
the ACK from a slave device. If a slave device sends back an ACK, it means that it is on
the I2C bus and functioning properly.
unsigned char I2Cpoll(char addr)

unsigned char I2Cpoll(char addr)
{
unsigned char temp = 0;

i2c_start();
temp = send_i2c_byte(addr);
reset_i2c_bus();

return temp;
}
There you have it. Thats how I2C works on an PIC24 I2C module. The last section of the
I2C tutorial will deal with usage, examples, and advanced functions.
10.3 I2C Advanced Functions
I2C Advanced Functions
In part 1 of the I2C tutorial, I showed how the I2C standard works at the electrical level and
at the physical level. In part 2, I showed how the I2C module functions on the PIC24. In
addition, the basic I2C functions were listed and explained. In this portion of the I2C
tutorial, I will demonstrate two advanced read and write functions.
Sequential Write and Read
Similar to the random read and write, the sequential read and write is a way to transfer large
amounts of data with as little protocol overhead as possible. Recall that in a random write, 3
bytes needs to be transferred for a single piece of data to be written. In a random read, 4
bytes must be transferred for a single piece of data to be read. The only way to reduce this
over head is to use the sequential read and write. This is where the ACK signal plays a huge
part of how the data get transferred.
In a random write, three bytes are sent to the slave device. After each byte, the slave must
respond with an ACK. After the last byte, the master device must send a stop bit.

A sequential write is very similar to a random write. However, after the third byte (the data
byte) is sent to the slave, the master does not send a stop bit. It sends the byte it wishes to
write to the subsequent memory address.
For example, lets say I want my PIC24 to write to a slave device with the address 0xA0. I
want to write three bytes of data to the memory address 010, 011, 012. I want to write
044, 055 and 066, respectively, to those memory addresses on the slave device.

The packet I must create on my PIC24 would be like this:
1. Start bit
2. A0 (slave address + write command)
3. ACK from slave
4. 010 (start of chip sub-address)
5. ACK from slave
6. 044 (first data byte, written to address 010)
7. ACK from slave
8. 055 (second data byte, written to address 011)
9. ACK from slave
10. 066 (last data byte, written to address 012)
11. AC from slave
12. Stop bit
As you can see, we only specified the start address of the sequential write at 010. After
each data byte that we send to the device, internally, the device increments the chip sub-
address. In this manner, a large number of bytes can be written in one packet. Note that the
same operation can also be completed with a series of individual packets, albeit with a
slower total access time. Note that after every byte, the slave device MUST return an ACK
bit to indicate a successful write.
On most EEPROM chips however, there is a limit to the number of bytes you can write per
packet. Usually this number is 8 byes. This is because the EEPROM has to erase the
previously written data, and then load the current data. The write cycles on EEPROMs are
usually on the order of several milliseconds. The only way to know if the EEPROM is
ready or not to receive the next byte is to look at the ACK after every byte sent by the
master device. For example, on Microchips 128 bit EEPROM 24LC22A, there is an 8 byte
page buffer. This means that I can use a page write to write up to 8 bytes. On the 9th byte
written to the device, it will no longer return an ACK bit back to the master, signaling an
error. Therefore, the programmer MUST terminate the packet with a STOP bit after the 8th
data byte.

In a sequential read, the mechanism is very similar. However there are usually no read
buffer limitations because there are no electrical cycles to limit how fast you can read data.
Contrary to the sequential write however, instead of the slave device giving an ACK bit to
indicate a successful write, the master device signals the ACK to the slave device to
indicate that it is ready to receive the next byte.

In a similar manner to the sequential write example above, if I want to read the data in the
addresses 010, 011, 012 on the slave device 0xA0, the packet that I need to send would
be something like the following:
1. Start bit
2. A0 (slave address + write command)
3. ACK from slave
4. 010 (start of chip sub-address)
5. ACK from slave
6. Restart bit
7. A1 (slave address + read command)
8. ACK from slave
9. First data read from slave (read from address 010)
10. ACK from master (signal to slave that the master device wants more data)
11. Second data read from slave (read from address 011)
12. ACK from master (signal to slave that the master device wants more data)
13. Third data read from slave (read from address 012)
14. NOACK from master (signal to slave that the master device wants no more data)
15. Stop bit
As you can see the ACK and NOACK plays an important role in signifying when to end the
transmission.

The Functions
Typically, in my standard I2C API, I dont keep a sequential read/write function of variable
lengths. When an application require such a function, I find the most efficient way to
implement the solution is to write a custom function with a specific length of sequential
read/write.
I do use a sequential read/write function of length 2 very often, and I will share the code on
this tutorial. The functions I show here use the basic functions found in part 2 of the
tutorial. The following function writes two bytes in a row to the slave device:
void I2Cwritedouble(char addr, char subaddr, char valuelow, char valuehigh)

void I2Cwritedouble(char addr, char subaddr, char valuelow, char
valuehigh)
{
i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
send_i2c_byte(valuelow);
send_i2c_byte(valuehigh);
reset_i2c_bus();
}
Similarly, the following function reads two bytes in a row from the slave device:
struct doublechar I2Creaddouble(char addr, char subaddr)

struct doublechar I2Creaddouble(char addr, char subaddr)
{
dchar temp;

i2c_start();
send_i2c_byte(addr);
send_i2c_byte(subaddr);
DelayuSec(10);

i2c_restart();
send_i2c_byte(addr | 0x01);
temp.x = i2c_read_ack();
temp.y = i2c_read();

reset_i2c_bus();
return temp;
}
You can see the problem with a function that wants to return two bytes of data. I need a
way to pass two bytes of data, but a function returns only allow one. To get around this
problem, I create my own data type with two chars in it, inserted into the header file:

typedef struct doublechar
{
char x, y;
}dchar;
When my I2Creaddbouble() function returns a value, I can now read both pieces of data.
Similarly you can create loops for longer length sequential transitions. It should be obvious
that optimizing the loops for read and write speed is the key to having robust I2C
transactions. Im sure you can figure out how read and write larger lengths of data from
these examples.
The last part of the tutorial will be an example of how to use all of these functions, along
with the #include directives and file structures.
11 ADC

Conversions and Conversions
So the point of the ADC is that there are a lot of ICs out there that give out an analog
output. Why would you use such an IC in todays digital age? Well, doing so will save you
pins at the expense of noise immunity. Many thermal-couple ICs, for example, measure
temperature as a voltage output. Many gyroscopes and accelerometers also work with
similar outputs. When designing with analog signals, it always better to be cautious because
crosstalk and interference can, and will, make your readings inaccurate (giving you big
headaches). Measuring the voltage of a device over a 5 meter cable for example, will
probably give you a voltage drop depending on the resistance of the cable. These are factors
that need to be taken into account, depending on the accuracy demands of the application.
There are several pins that must be configured correctly before the ADC will work. If you
look at the PIC24 datasheet there are pins with the ANxx prefix. These are the analog input
pins. In addition, the Vref+ and Vref- pins also need to be connected accordingly if you opt
to use them as your reference voltages (you can also use AVdd and AVss). The reference
voltages are used as the high and low voltage, against which the analog inputs will be
compared.
There are several registers that you need to be familiar with. One of the more important
register is AD1PCFGH. This register switches the ANxx pin into analog mode (otherwise
those pins are controlled by the TRISx, LATx, and PORTx registers).
One feature that needs to be understood is that although there are many analog pins, there is
only one or two ADC module (depending on the PIC model) on the IC itself. The sampling
of different input pins is done through a series of multiplexors that engages/disengages the
proper pins to and from the internal ADC. The ADC itself can sample 4 inputs at the same
time at 10 bit sampling on the PIC24H series (only one input at a time on the PIC24F). This
means that sampling 4 individual inputs on the PIC24H is usually pretty straight forward,
but anything above 4, a bit more programming and ingenious timing is required. Youll
need to manual turn these multiplexors on and off to get your data. If further accuracy is
needed the ADC can be configured as a 12 bit converter but can only sample one input at a
time. The 12 bit ADC is only found on the PIC24H series.
A lot of the features on the ADC are useful for specific applications. This said, the ADC is
so versatile that many of the features are rarely used for low frequency sampling. For this
tutorials purpose, I will set up the ADC in its simplest form. For this reason, the code was
written for the PIC24F.

The AD1CON1 register is the main control register and has several bits that turn the
module on and off. It also has bits that tell whether the sampling of a particular input is
completed. Needless to say, loops will probably be used to sample this DONE bit in your
sampling functions. The other two control registers AD1CON2 and AD1CON3 controls the
interrupt settings, the multiplexors involved in the sampling as well as the sampling clock
used for the conversion process. Lastly the AD1CHS0 register controls how the inputs are
set up, and against which voltage levels it will be compared. On the PIC24H series, there
are 4 ADC channels in one ADC module, which can be configured with the various
AD1CHSxxx registers.
The following program samples the voltage input from AN4. It was configured on a
PIC24FJ64GA002. It uses the ADC in the simplest form possible. It uses the following
settings just for reference:

AD1CON1
- Turn ADC module on
- Turn on during idle
- Data in integer form
- Auto convert
- Auto sample start
AD1CON2
- Use vref+ and vref-
- do not scan inputs
- ignore interrupts
- 16 word buffer
- use MUX A

AD1CON3
- sample using internal RC clock
- auto sample set to the most amount of time
- clock division for sampling clock
(main.c)

/*
Engscope Tutorial
JL
May 6, 2009
ADC module PIC24F
*/

#include ../h/system.h
#include timer.h

//config
_CONFIG2(0xFBFD);
_CONFIG1(0x377F);

long value;

//main loop
int main(void)
{
//select Primary Oscillator, External XL, PPL
OSCCON = 0x33C0;
CLKDIV = 0x0000; //do not divide

//Initiate
ADCInit();

while(1)
{
//process events
DelaymSec(10);
ADCProcessEvents();

value = (long) voltage;
value = (value * 3300) / 1024;
}
}
The high and low level header files look like the following:
(adc.h)

/*
Engscope Tutorial
JL
May 6, 2009
ADC module PIC24F
*/

//some variables
extern unsigned int voltage;

//init function
extern void ADCInit();

//main process function
extern void ADCProcessEvents();
(adc.c)

/*
Engscope Tutorial
JL
May 6, 2009
ADC module PIC24F
*/

#include ../h/system.h

#define ADC_VOLTAGE 4 //select input

//declare variables
unsigned int voltage;

//init function
void ADCInit()
{
//Turn on, auto sample start, auto-convert
AD1CON1 = 0x80E4;

//Vref+, Vref-, int every conversion, MUXA only
AD1CON2 = 0x6000;

//31 Tad auto-sample, Tad = 5*Tcy
AD1CON3 = 0x1F05;

AD1CHS = ADC_VOLTAGE;
AD1PCFGbits.PCFG4 = 0; //Disable digital input on AN5
AD1CSSL = 0; //No scanned inputs
}

//main process function
void ADCProcessEvents()
{
while (!AD1CON1bits.DONE);
voltage = ADC1BUF0;
}
In the high level header, I configured the module with the ADCInit() function. Next,
sampling is simply a matter of reading out the values.
Regardless of how the module is configured, ADC in general is a tricky endeavor. Many
factors can affect the accuracy of the sampling, as well as the results desired. Frequency of
sampling, for example, can be a huge factor in how successful the conversion is
implemented. The PIC24H will allow for higher performance in the frequency domain, and
allows for up to 4 channels of sampling. However, for simple low frequency sampling, the
PIC24F might suffice. In addition, the length of the trace or wire, and the susceptibility to
noise of that trace will affect the conversion results. All these factors must be considered
when designing a circuit.
12.1 SPI Basics
SPI Basics
The other big communications protocol between integrated ICs is SPI. It is usually
pronounced spy, and it is the cool sounding synchronous protocol (as opposed to the
awkwardly spoken I2C). If you are not familiar with I2C you can check it out in the
previous portions of the tutorial. It might be a good idea since there are many comparisons
and similar concepts involved between both protocols.
With respect to the overall pictures, it is used in a similar fashion to the I2C. However,
there is no set protocol written on top of the physical layer. What I mean by this is that for
example, in I2C, access to the registers almost always follows the order:
1. Chip address with read/write
2. Device address
3. Read/write data
So according to this protocol, you are basically limited to 127 devices, and a memory
device address space of 256 (remember that 1 bit in the chip address byte is used for
read/write, and the device address is one byte wide). In SPI, there is no set order on how the
data is accessed. This allows for protocol abuse! What I mean by abuse is that, if I am
creating my own SPI interface in an FPGA or ASIC, I can set up the data access in any
which way I want. I can have the memory address width to be 8 or 16 bits for example, and
set the data width to be 8 or 16 or even 32 bits. Generally you will find that each device
type on the market has a similar access style. For example SPI EEPROMs from Atmel and
Microchip are usually both accessed in the same way; however, they will differ
significantly when accessing an ADC type IC with a SPI interface.
In Comparison to I2C
There is a big discussion as to when to use I2C and when to use SPI. Given that many
devices with similar functions are sold with either an I2C interface or a SPI interface, which
device should I choose? Well the answer can depend on many factors. Generally, I like to
get the most results for the least amount of work (dont we all?). I will usually choose a
device for which I am familiar with, or have drivers written already.
There is one big advantages going for the SPI protocol. It is fast. Any times you have a
large amount of access, for example storing/retrieving a large table, I prefer to use SPI. The
protocol usually allows up to 10Mb/s whereas the I2C usually tapers out at around
400Kb/s. There is also less protocol overhead as you will see later. However, this speed
comes at a cost. You will usually need to use 3 individual traces to connect the devices
together, with one additional trace for each device. This means that for a system totaling 5
peripheral devices in addition to the microcontroller (which is not uncommon) you will
need 3+5=8 individual traces. The implication is that using SPI up pins on the
microcontroller as well as the real estate needed to connect the devices on the PCB itself.
I2C requires only 2 traces and 2 pins, regardless of how many devices are connected.
Physical Connections
Before any programming can begin, you need to create a circuit that contains all the
necessary connections for the SPI protocol to work. In general, a master and multi-slave
configuration looks like this:

As you can see, there are 3 common signals to all devices, as well as one device specific
signal. SCLK is the serial clock given by the microcontroller. It is used by the slave devices
to sample the incoming data and output the outgoing data. There are two data lines going to
and from the microcontroller. These two lines can be named differently depending
convention. From the microcontrollers perspective, the data going out is called the SDO
(master data out). From the peripheral perspective however, this is data going in (so be
careful what you call in and out, because it is always coming out of something and going
into something). For the data going into the microcontroller the complementary notation is
used. SDI (master data in) denotes data from the peripherals to the controller, which
happens to be data going out of the slave. To avoid any confusion the figure above is
labeled MOSI (master out, slave in) or MISO (master in, slave out). Confused yet?
Unlike I2C, which selects the device according to the chip address in the first byte of a data
packet, device selection in SPI is accomplished by the CSB/SS signal. The signals name is
an abbreviation for chip select bar (CSB) or slave select (SS), but both denotes the
same function. The signal is active low. When the signal for a particular device is lowered,
that device is selected and considered active. Needless to say that because multiple output
drivers are on the SDI line, the device outputs needs to be in a high-impedance state when
their respective SS lines are high (please remember this when implementing SPI in an
FPGA! Dont say I didnt warn you).
Timing
In my humble opinion, SPI timing is more confusing than I2C. The primary reason for this
opinion is that there are many mode of operation that is available on the market. In
addition, something happens on every clock edge (rising and falling). You need to make
sure you have your device and controller configured properly. As with most things, the
price paid for flexibility is time and frustration spent configuring devices. However the bus
is easier to debug in most cases, mostly because all the signals are unidirectional (I2Cs
SDA line is bi-directional, which makes it a bitch to debug).
Here is the timing diagram from Wikipedias website.

When configuring your microcontroller, there are usually two settings that you need to take
into account: clock polarity and clock phase. Given these settings there are 4 possible
outcomes:
CPHA = 0, CPOL = 0 first bit starts as soon as SS is lowered, data sampled at rising edge,
data changes on falling edge.
CPHA = 0, CPOL = 1 first bit starts as soon as SS is lowered, data sampled at falling
edge, data changes on rising edge.
CPHA = 1, CPOL = 0 first bit starts on first clock edge, data sampled at rising edge, data
changes on falling edge.
CPHA = 1, CPOL = 1 first bit starts on first clock edge, data sampled at falling edge, data
changes on rising edge.
Different vendors will use different timing, so aware when looking at the datasheet. This
datasheet from a microchip SPI EEPROM can serve as an example.

I can see that the first bit is already active even before the first clock edge. Also the data
changes on the falling edge and is sampled at the rising edge. In order to communicate
successfully with this device, I will need to configure my SPI module to CPHA = 0,
CPOL=0. Depending on the microcontroller you are using, there might be different names
to these clock configurations, but the general concept is the same. In Microchips PIC24
series, these configurations are called CKE and CKP.

That is the general overview of the protocol; the next section will deal with configuring the
PIC24 and some subroutines.
12.2 SPI Master Usage

Generic Driver
In the first part of this tutorial, we looked at a general overview of the SPI communications
interface. In this section we take a look at how the PIC24s SPI module can be used in its
master mode.
There are several ways to set the SPI master up. However this is how I do it.
The code is taken from a driver I wrote that is written is a generic manner such that I may
be used in both PIC24F and PIC24H. If you are just using it for a small project and have no
intention of encapsulating any of the code, just keep the main functions and delete the #if
defined directives. I am also using the Microchips peripheral library. For those that dont
know, the peripheral library is installed with your version of the C30 compiler; another
reason to upgrade from the assembler.
(code from spi.h)
/*
Engscope.com
JL
Created Jul 1, 2010
Modified Jul 1, 2010
headers for spi device drivers
*/

//define i2c ports so that it can be
//called using constants instead of numbers
//declare only if ports are available
enum SPIPorts{
SPIPORT1 = 1
#if defined(spi_v1_1) || defined (spi_v1_3)
, SPIPORT2
#endif
#if defined (spi_v1_3)
, SPIPORT3
#endif
};

//with selectable ports

unsigned char spiWrite( unsigned port, unsigned char i);

unsigned char spi1Write( unsigned char i );
void spi1Init(unsigned int prescale);

//spi port 2
#if defined(i2c_v1_2) || defined (i2c_v1_3)
unsigned char spi2Write( unsigned char i );
void spi2Init(unsigned int prescale);
#endif

//spi port 3
#if defined (i2c_v1_3)
unsigned char spi3Write( unsigned char i );
void spi3Init(unsigned int prescale);
#endif
(code from spi.c)
/*
NVIS Inc.
Jianyi Liu
Created Jul 1, 2010
Modified Jul 1, 2010
source for spi device drivers
*/
#include "spi.h"
#include "nlib_spi.h"

//write using selectable ports
unsigned char spiWrite( unsigned port, unsigned char i)
{
switch(port){
default:
case 1: return spi1Write(i);
#if defined(spi_v1_1) || defined (spi_v1_3)
case 2: return spi2Write(i);
#endif
#if defined (spi_v1_3)
case 3: return spi3Write(i);
#endif
}
}

//init Spis
void spi1Init(unsigned int prescale){
OpenSPI1(0x0120 | prescale, 0x0000, 0x8000);
}

// send one byte of data and receive one back at the same time
unsigned char spi1Write( unsigned char i )
{
// write to buffer for TX, wait for transfer, read
SPI1BUF = i;
while(!SPI1STATbits.SPIRBF);
return SPI1BUF;
}//spiWrite2

#if defined(spi_v1_1) || defined (spi_v1_3)
void spi2Init(unsigned int prescale){
OpenSPI2(0x0120 | prescale, 0x0000, 0x8000);
}

// send one byte of data and receive one back at the same time
unsigned char spi2Write( unsigned char i )
{
// write to buffer for TX, wait for transfer, read
SPI2BUF = i;
while(!SPI2STATbits.SPIRBF);
return SPI2BUF;
}//spiWrite2
#endif

#if defined (spi_v1_3)
void spi3Init(unsigned int prescale){
OpenSPI3(0x0120 | prescale, 0x0000, 0x8000);
}

// send one byte of data and receive one back at the same time
unsigned char spi3Write( unsigned char i )
{
// write to buffer for TX, wait for transfer, read
SPI3BUF = i;
while(!SPI3STATbits.SPIRBF);
return SPI3BUF;
}//spiWrite3
#endif
Several things are happening here. The spi.h file is located in the installation folder of
your C30 compiler. This folder should already be included in the compilation path, but if it
is not, you will need to find it and added to the include search path parameter under
project options.

Most likely you will not need to do this step. There are two basic functions included in the
file; spixInit() and spixWrite(). You will use the spixInit() function to initialize the master
SPI module, and both read and write will be completed with the spixWrite() function.
Pretty easy huh?
spixInit() is set up to use several very specific options, so your mileage may vary. You may
need to modify the init function to your liking, but in its current state it uses the following
options:
-Serial output data changes on transition from active clock state to Idle clock state
-Idle state for clock is a low level; active state is a high level
-Master mode
The prescale parameter in the init refers to this tidbit in the datasheet:


It determines how fast you will be sending your data out of the SPI master module. If your
Fcy is running at 16MHz, and you scale it to 1:1, then you will be sending the SPI clock at
16Mhz. However, note that the SPI protocol usually only goes up to 10Mhz, so prescale
appropriately.
Anyways, after the initiation function is called, you can pretty much just call the
spixWrite() function to read or write. How is this accomplished? Well, reading and writing
under the SPI protocol is basically the same function. Usually, the hardware
implementation of a SPI slave or master has two internal bit shift registers. One shifts the
incoming bits, and one register shifts out the communicated data. Below is a diagram of
how the shift registers are arranged in a SPI connection.

This is fortunate because we can actually send data and receive data within the same
function, since the two buffers function separately.
Example EEPROM Interface
These are basic functions. However, the package on its own is still not very useful. If we
want to interact with an EEPROM for example, we need to make higher level functions that
take into account the intricacies of the specific EEPROM.
In this next example, I will be interfacing with a Microchip SPI EEPROM 25AA160A/B. A
packet sent to the EEPROM has to have the following actions. First the slave select (SS)
line must be lowered. Next an 8 bit instruction must be sent to the slave, followed by a 16
bit address. During a write, the master finishes the operation by sending the data. During a
read, the master finishes the operation by sending clock pulses, and reading the incoming
serial bits. Here is the instruction set and sequences taken directly from the datasheet.



Well what does this mean? It means that we can write a driver set specific to this device, or
any device that follows this convention. Here is my version.
(nlib_spi_ee.h)
/*
Engscope.com
JL
Created Jul 1, 2010
Modified Jul 1, 2010
header for eeproms, spi
*/

//instruction set
#define EEPROM_CMD_READ (unsigned)0b00000011
#define EEPROM_CMD_WRITE (unsigned)0b00000010
#define EEPROM_CMD_WRDI (unsigned)0b00000100
#define EEPROM_CMD_WREN (unsigned)0b00000110
#define EEPROM_CMD_RDSR (unsigned)0b00000101
#define EEPROM_CMD_WRSR (unsigned)0b00000001

//struct for the status register
struct STATREG{
unsigned WIP:1;
unsigned WEL:1;
unsigned BP0:1;
unsigned BP1:1;
unsigned RESERVED:3;
unsigned WPEN:1;
};

union _EEPROMStatus_{
struct STATREG Bits;
unsigned char Char;
};

//initiate a port for this eeprom
void spiEeInit(unsigned char p);

//read the status regsiter
extern union _EEPROMStatus_ EEPROMReadStatus(void);

//set the macro for active SPI
#define spiEeSsLow() SPIEE_SS = 0;

//set the macro for inactive SPI
#define spiEeSsHigh() SPIEE_SS = 1;

//writes to the eeprom device
extern void spiEeByteWrite(unsigned int, unsigned char);

//reads from the eeprom device
extern unsigned char spiEeByteRead(unsigned int);

//enable write by changing status register
extern void spiEeWriteEnable(void);

//disable write by changing status register
extern void spiEeWriteDisable(void);

//polls to see if the SPI EEPROM is present
unsigned char spiEePoll();
(nlib_spi_ee.c)

/*
Engscope.com
JL
Created Jul 1, 2010
Modified Jul 1, 2010
source for eeproms, spi
*/

//include correct headers
#ifdef __PIC24F__
#include "p24fxxxx.h"
#elif defined __PIC24H__
#include "p24hxxxx.h"

#else
#error No valid target device
#endif

#include "nlib_sys.h"
#include "nlib_spi.h"
#include "nlib_spi_ee.h"
#include "bsp.h"

//make sure user defines chip select in bsp.h
#ifndef SPIEE_SS
#error Must define symbol SPIEE_SS, chip enable bar, \
make sure associated TRIS is high
#endif

//variable keeps track of which port to use
static unsigned char port;

//initiate a port for this eeprom
void spiEeInit(unsigned char p)
{
//assign port
port = p;

// Set IOs directions for EEPROM SPI
//disable SS signal
SPIEE_SS = 1;
}

//writes to the eeprom device
void spiEeByteWrite(unsigned int Address, unsigned char Data)
{
unsigned char var;
spiEeWriteEnable();
spiEeSsLow();

var = spiWrite(port, EEPROM_CMD_WRITE);

var = spiWrite(port, INTHI(Address));
var = spiWrite(port, INTLO(Address));

var = spiWrite(port, Data);

spiEeSsHigh();

// wait for completion of previous write operation
while(EEPROMReadStatus().Bits.WIP);

spiEeWriteDisable();
}

//reads from the eeprom device
unsigned char spiEeByteRead(unsigned int Address)
{
unsigned char var;

spiEeSsLow();

var = spiWrite(port, EEPROM_CMD_READ);

var = spiWrite(port, INTHI(Address));
var = spiWrite(port, INTLO(Address));

var = spiWrite(port, 0);

spiEeSsHigh();
return var;
}

//enable write by changing status register
void spiEeWriteEnable()
{
unsigned char var;
spiEeSsLow();
var = spiWrite(port, EEPROM_CMD_WREN);
spiEeSsHigh();
}

//disable write by changing status register
void spiEeWriteDisable()
{
unsigned char var;
spiEeSsLow();
var = spiWrite(port, EEPROM_CMD_WRDI);
spiEeSsHigh();
}

//read the status regsiter
union _EEPROMStatus_ EEPROMReadStatus()
{
unsigned char var;

spiEeSsLow();
var = spiWrite(port, EEPROM_CMD_RDSR);
var = spiWrite(port, 0);
spiEeSsHigh();

return (union _EEPROMStatus_)var;
}

//polls to see if the SPI EEPROM is present
unsigned char spiEePoll()
{
unsigned char temp = 0;
spiEeWriteEnable();
temp = EEPROMReadStatus().Bits.WEL;
spiEeWriteDisable();
return temp;
}
Here you have all the higher level functions to interact with a SPI EEPROM device. You
will need to use the #define directive to define the pin that acts as the SS signal, also make
sure the pin is set as an output, not an input by using the appropriate TRIS register. Next set
the SPI port. This is in association with the PIC24F set of microcontrollers, which usually
has multiple SPI ports. Then you can just call the read and write functions when needed.
Have fun.
13 PWM DAC

If you look up PWM on the Wikipedia page, there are some explanations about what it is an
its origins. Instead of forcing you read through this heap of information, Ill give you the
condensed version. Simply put, PWM is a way to use a one bit digital output to product a
multiple bit digital output. The reason you can do this is because you are adding a time
component to the output.
There are many different flavors of PWM. These include center fixed, leading edge fixed,
trailing edge fixed and more. For all intents and purposes, they all work on the same
principle, so if you are able to understand one, you should be able to figure out the other. In
this tutorial, I will show you how to build a circuit and create a low speed DAC from a
single PWM output on a PIC24F.
A Closer Look
Essentially, PWM is a way to encode digital information in the time domain using a single
output. Imagine a fixed frequency clock signal with a variable duty cycle. These variations
in duty cycle can be used to encode digital information. The following examples will
illustrate this point

The first waveform is just a 50% duty cycle clock. The signal is posted as a reference to a 1
MHz frequency. The second wavefrom show a two state PWM. Since digital signals can
have an off and on state (high and low), the simplest PWM is also just on and off. When a 0
is required, the PWM signal is constantly off (0% duty cycle), when a 1 is required, the
PWM signal is constantly on (100% duty cycle).
The third waveform shows a three state PWM. This requires 3 different possible duty
cycles. We can pick 0%, 50% and 100% to represent these 3 states.
Following the same principle, the fourth waveform is a PWM with 10 different states. We
are dividing a 100% duty cycle into 10 different pieces and each one of those time
differential can be used to encode a different PWM output.
When connecting to a device that accepts a PWM signal, there might be additional
requirements. The most obvious of these is the frequency. Hobby level servo motors for
example, typically accepts signals with a 40 ms period, depending on the model. In
addition, the servos might require a minimal and maximal duty cycle. This means that the
PWM output with the lowest duty cycle cannot be 0%, and the PWM output with the
highest duty cycle cannot be 100%. For typical hobby servo motors, the minimal pulse is
about 1 or 2 ms, and the maximum varies depending on the model (typically 10 ms or so).
For an application such as LED dimming, there are no restrictions on minimal and maximal
duty cycles.
Thats the gist of it. Now lets get programming.
PIC24F PWM Module
The PIC24F is equipped with output compare modules. These modules can be set up in
PWM mode and used to produce a PWM output. Setting up the module is extremely easy.
However, in order to use the PWM module, one timer will be used. Below is the functional
diagram.

As you can see, the PWM module takes advantage of the timers, and compares a PWM
countdown value against the PWM period value. When a PWM module is reset, the PWM
output is set to high or low depending on how the module is set up. When the countdown
value is reached, the PWM output signal is inverted to create the duty cycle. This means
that the PWM period value determines the frequency of the PWM, and the countdown
value divided by the timer period count value determines the PWM duty cycle. If you need
a refresher on how to set up timers, you can read about it here.
The registers in the PWM provides the basic functions to control the PWM such as turning
the modules on and off, setting up the leading edge/trailing edge settings and other
advanced options. You must also associate the output compare module with a timer. In
addition the output pins must be set up through the peripheral pin output table. Before you
will see an output however, the timer associated with the PWM module must be started and
allowed to run. The period of the PWM is set by the OCxRS (which must be smaller than
the period for the associated timer, set using register PRx). Changing the duty cycle of the
PWM output is now simply a matter or writing to the register OCxR. The countdown value
written to this register must not be smaller than the timer period. If this condition is not
satisfied, the behavior of the module is not guaranteed, although I think the module just
outputs a 100% duty cycle signal (a signal that is always high).
PWM DAC

If you are using one of the fancier microcontrollers for projects, they might come with a
digital to analog converter. These hardware based DACs can be extremely useful in mixed
signal applications. With PICs however, this is not the case. As of the writing of this article,
I do not know of a PIC24F that comes with a digital to analog converter. However, if the
application requires a very slow, low performance DAC, a PWM signal output can be
converted into a DAC using the passive properties of resistors and capacitors.

The PWM signal is passed through a simple low pass RC filter. The filter will cause the
PWM output to be smoothed out, and if you picked your RC values correctly, the out of the
filter will begin to look like analog signal. The following waveform illustrates the point
(there is a slight time domain offset between the yellow and green waveforms for some
reason).

Code example
In order to illustrate a PWM in action, I have decided to create a sine wave using a PWM
output from a PIC24F device. The following images is the circuit used.
Overall Subcircuit Connections and Analog Filter
Microcontroller Circuit
Voltage Regulator
Oscillator
The output of the PWM is on the RP7. This signal is then put through the RC filter R2 and
C4. The values are chosen so that the RC time constant is extremely large (I think about 12
seconds or something). For this example, I wanted a sine wave with a frequency of 0.5 to 1
Hz or so (in orders of magnitude, I dont remember exactly what the frequency was), so I
chose the RC time constant to be 5 to 10 times the period. Your application may vary. The
code below shows how the sine wave is formed.
main.c

/*
NVIS Inc.
Jianyi Liu
Created Sep 29, 2009
Modified Sep 29, 2009
sxoled dumb controller
*/

//define clock speed
#define Fcy 5000000
#define OC1CON_init 0b0000000000000110
#define configoc1_init 0b1011

#include "p24hxxxx .h"
#include "generic .h"
#include "../lib/NvisTypeDef.h"
#include "buttons .h"
#include "../lib/delay.h"
#include "timer1 .h"
#include "timer2 .h"
#include "outcompare .h"

//configuration bits
//no flash, no write protect
_FBS(BWRP_WRPROTECT_OFF & BSS_NO_FLASH);

//no protection
_FGS(GSS_OFF & GCP_OFF & GWRP_OFF);

//external oscillator, single oscillator start
_FOSCSEL(FNOSC_PRI & IESO_OFF);

//monitor, switching off, allow peripheral reconfig
//osc2 as osco, use external clock
_FOSC(FCKSM_CSDCMD & IOL1WAY_OFF & OSCIOFNC_OFF & POSCMD_EC);

//watch dog off
_FWDT(FWDTEN_OFF & WINDIS_OFF);

//turn of POR, use standard I2C pins
_FPOR(FPWRT_PWR1 & ALTI2C_OFF);

//main loop
int main(void)
{
OSCCON = 0x2200; //select Fast RC, no PLL
CLKDIV = 0x0000; //do not divide

//Set up I/O Port
AD1PCFGL = 0xFFFF; //set to all digital I/O
//configure all PortB as input, except RB7 as reset
TRISB = 0xFFFF;

Timer1Init();

RPOR3bits.RP7R = 0b10010;

OpenOC1(OC1CON_init, 10, 10);
ConfigIntOC1(configoc1_init);

while(1)
{
}
}
In the main loop, theres nothing significant happening except for initializing the modules.
Several things to note here. The Microchip peripheral library has been around for quite a
while and their code is getting to a point where it is very good for certain modules. There
are still bugs here and there, but they are getting ironed out. I use their library code for the
output compare. The source code is included with the compiler and is usually located at:
C:\Program Files (x86)\Microchip\mplabc30\v3.25\src on my machine. This is the reason
for including the outcompare.h file. I call the initialize function OpenOC1() which sets
up the Output compare module. For more information about this function, consult the
header file associated with the module. Next, I setup the timer, and make sure that the
peripheral IO pin is set to an output and configured for an output compare. Then is off to
the forever while loop construct.
timer1.c

/*
NVIS Inc.
Jianyi Liu
Created Sep 29, 2009
Modified Sep 29, 2009
timer main
*/

#include "p24hxxxx.h"
#include "timer1.h"
#include "buttons.h"
#include "outcompare.h"
#include "math.h"

//prototype functions
#define amp1_1 100
#define frq1_1 25
#define frq1_2 25
#define dcb1_1 2
#define dcb1_2 3
#define dcb1_3 4

double const pi = 3.1415926535;

double sinarg1 = 0;
double dacdouble1 = 0;
unsigned int timecount1 = 0;
unsigned int amp1 = amp1_1;
unsigned int frq1 = frq1_1;
unsigned int dcb1 = dcb1_1;

void Timer1Init(void)
{
PR1 = 5000;

IPC0bits.T1IP = 5;
T1CON = 0b1000000000000000;
IFS0bits.T1IF = 0;
IEC0bits.T1IE = 1;

}

unsigned char Timer1IsOverflowEvent(void)
{
if (IFS0bits.T1IF)
{
IFS0bits.T1IF = 0;
return(1);
}
return(0);
}

//Interrupt vector, if needed
void __attribute__((__interrupt__, auto_psv)) _T1Interrupt(void)
{
//calculate DAC output
//get increment current time
if (timecount1 < 2*frq1)
timecount1 ++;
else
timecount1 = 0;

//calculate sin value
dacdouble1 = amp1*sin(timecount1*pi/frq1) + amp1 + dcb1;

//load to dac
SetDCOC1PWM((int)dacdouble1);

//reset interrupt flag
IFS0bits.T1IF = 0;
}
I didnt include the timer1.h header files as they are all just variable declarations and
function prototypes. I used the standard math header math.h for this example to calculate
the amplitude for the sine wave. The interrupt routine is very standard. It calculates the sine
values and posts them to the OCxR register by using the function SetDCOC1PWM(int).
Ive taken the liberty to show you the final output on an oscilloscope. There are several
things to notice about PWM DACs. First, the granularity of the DAC conversion depends
highly on how fine you can discretely modify your duty cycle. As an example, if your timer
period only counts to 16, you cannot have a D-to-A conversion of greater than 4 bits.
Conversely however, in the same scenario, the conversion frequency can be set to
extremely high since the timer period only has to count to 16. For an internal instruction
clock running at 16 MHz, with the timer counting to 16, the DAC can operate at 1 MHz
with a conversion resolution of 4 bits. If you want to improve the resolution to 8 bits, then
the timer counter will have to be set to count to 256. If the internal instruction clock is still
running at 16 MHz, then the DAC can only operate at 1/16 MHz.


The yellow trace is the analog output and the green is the PWM output. And thats how you
use the PWM module.






14.1 USB, An Introduction

Whenever someone asks me to explain the subject of USB, I am petrified. The protocol is
an absolute monster. And as if this was not bad enough, specialty devices such as HID or
Audio class devices have their own communication rules on top of the vanilla USB
protocol. I would say that the USB protocol is akin to the US tax code; it is very bloated
and overly complex. However the difference is that USB actually works quite well,
whereas the USB tax code well thats another subject matter all together. So the question
is: how do you tackle a subject as broad as USB?
Setting the Scope
After years of playing around with USB, I still dont think I fully understand how the thing
works. The seminal literature on the subject matter, surprisingly is NOT the USB standard
2.0 which is available here, but a book called USB Complete by Jan Axelson. It is now in
its 4th edition, and Im sure Ms. Axelson can attest that every time the USB-IF creates a
new standard, he gets a whole bunch of extra book sales. All in all, it is a pretty good book,
but not perfect. I feel that it lacks some full examples from design to implementation. After
reading the book, I still needed a lot of research on my own before I was capable of making
a working device. Ms. Axelsons website is a wealth of information on the subject of USB
and is required viewing for all interested on the subject of USB.
With that out of the way, we need a baseline on what to expect in this tutorial. First, the
tutorial is by no means a COMPLETE overview of the USB implementations with the
PIC24 series of devices. I do not understand half the stuff on USB, and I have never tried
doing complicated stuff such as USB audio or USB host. However, I do have plenty of
experience in making USB human interface devices (HIDs). At the time of the writing of
this article, I work for a company that makes joysticks as part of their product line. You can
imagine that we deal with HIDs on a regular basis. The truth of the matter is that an HID
will satisfy the majority of all USB device requirements. Moreover, the real joy of HID
type devices is that drivers on the host side is already implemented regardless of whether
you are on Windows, Mac or Linux (half the work is already done before you even
started!).
The Implementation Approach
Im sure none of my readers want to hear me babble about USB. As with most of you,
when Im reading a technical article, I just want to get my information and move on. So,
heres the gist of USB in one short sentence: the protocol is a complex mess and dont
expect to understand it.
Fortunately, you dont have to understand USB with the USB software stacks offered by
most microcontroller vendors. This is exactly the implementation approach that we will
take to make our own USB device. I would still recommend reading Ms. Axelsons book,
as some of the concepts may include references to terminology used in the USB protocol.
Microchip developed a USB software stack across their entire microcontroller lines when
they released the USB based PIC18s a few years ago. The USB stack has become relatively
stable over the years, and is very mature as this point in time. I will attempt to make a
custom USB device by taking one of Microchips examples, and modifying the code. At
that point, any good engineer will be able to take the same approach and create their own
custom devices. Only the PIC24F series of controllers are used in the examples as no
PIC24H controllers support USB.
The USB stack is included in the Microchip Application Library. You will need to install
the library before proceeding any further. The library can be found here.
A Look at the Microchip USB Framework
After installing the library, take a look at its contents. There is a huge list of premade USB
projects already. You just need to make slight modifications to these projects to make them
your own.
The folder ../[Microchip application library]/Mircrochip/USB/ contains the actual USB
stack. The other directories whose names begin with Device contain just the USB project
and implementation files that use the USB stack (eg. ../[library]/USB/Device HID
Joystick/). Thats all we need to know about the application library for now.
The USB Enumeration
Enumeration is the process by which a host accepts a USB connection from a device. When
you plug device into a Windows based computer for the first time, it has to install some
drivers. If a driver is found, it will make a noise saying that a device has been successfully
connected to the host. Once this has occurred, you will be able to find the device under
Device Manager.


Enumeration is by far the hardest part of implementing USB. So many things must occur
just right and if one of them goes wrong, the enumeration fails. Youll end up with an
improperly configured device.

The USB HID Class
The HID Class specification document describes a set of devices used to interact with
humans. Mice, keyboards and joysticks all fit into this category. The HID class is unique in
that the driver on the host side is widely adopted and very flexible. This group of devices
has a report descriptor that gets read during enumeration. This report descriptor contains
important information about how the data is read from the device. The host reads the
descriptor and loads the appropriate driver configuration required for the device to function.
When you plug a joystick into a computer, how does it know that the device is a joystick?
How does the computer know the number of buttons to map or the number of axis
supported by the device? All of this information is contained in the report descriptors.

The beauty of the system is that the mapping information is contained within the device
(more specifically, within the report descriptors). This allows the host drivers to be
extremely flexible in the range of acceptable devices. Needless to say, your imagination is
the limit when it comes to creating USB HID devices, and youll never have to write a
drivers on the host side.
Some USB HID Tools
There are several tools that any USB developer must learn to use. If we are to create custom
HID devices, then the following tools are essential. The first is the SimpleHIDWrite, which
allows you to read and write simple descriptor packets to a USB HID device.

The second is the HID Descriptor tool, which allows you to construct descriptors that
describe how your reports will be formatted.
Get your typing fingers ready, we are going on a crazy programming binge and creating
some funky custom devices.
14.2 More on USB

Originally I had planned to show an example on the second part of the USB tutorial.
However, as I was writing the example, I realized that there are so many aspects of USB
that requires more explanation. Therefore, before we begin any code, we need to take a
closer look at some of the details of USB.
Vendor ID and Product ID
The consortium that runs the whole USB show is called the USB-IF, which is composed of
major and minor players in the silicon and software business. Intel, Microsoft and HP are
some of these major players. To uniquely identify each and every USB device, there are
two 16 bit codes called the Vendor ID (VID) and Product ID (PID). Every USB device is
required to have a VID and a PID. The VID identifies the manufacturer of the device and
the PID identifies the product model. USB-IF REQUIRES all developers to register a
proper VID with the implementers forum. The cost of such a venture is about $2000 for a
onetime registration fee or, if you wish to become a USB-IF member and get benefits such
as having your voice heard in the next USB standard revisions, then you can join the forum
for a low annual fee of $4000. Personally, I have nothing to say about the next USB
versions so I would just register the VID for a onetime fee.
So what would happen if you didnt get registered? Well, for one, your device would never
be deemed USB compliant. Secondly, there might be a device conflict in the real world.
Many of the device drivers are written specifically for a particular VID and PID
combination. If two different devices with the same VID and PID combination are plugged
at the same time, a blue screen of death is a real possibility.
In any case, unless you are planning to sell a USB device, I would not recommend spending
that money. You can use Microchips VID for development purposes. As for the PID, the
assignment is arbitrary. There are no specific requirements in the USB standard that
dictates how the PIDs should be assigned. However, make sure that the PID does not
conflict with other Microchip devices that might be connected to your test PC. For
example, dont go out of your way to make a device with the same PID as the ICD3 or
PicKit3 programmers.
Device -> Configuration -> Interface -> Endpoint
For a USB device, there is a hierarchy of how a communication message is sent. There are
four levels in this hierarchy called the device, configuration, interface and endpoint. A
device refers to the physical device that the USB connection is attached. Within the device,
there can be one or more configurations. A device might have two functional purposes that
are mutually exclusive. If specific drivers are implemented in the host, then the host can
actively switch the device between configurations. Most devices only have one
configuration. Within a configuration, there can be one or several interfaces. If there is only
one interface within the device, it is considered a normal device. If there is more than one
interface within the device, it is considered a composite device (e.g. a mouse-keyboard
combo device). Each interface must have at least one endpoint. The required endpoint is the
control endpoint, which, coincidentally transmit the description of the device to the host. In
addition to the control endpoint, a USB device usually has at least one endpoint for data
transfer during normal use. An endpoint only goes in one direction. For example, in the
implementation of a generic USB mouse, the host only needs to read mouse pointer and
click data. The host never needs to send any information to the mouse itself. Therefore,
only one endpoint (on top of the required control endpoint) would be used to implement the
mouse.
Endpoint Types

On top of the abstract communications layer hierarchy, there are also 4 flavors of
endpoints. Control endpoints are used to extract configuration and descriptor information
from the USB device. For our implementation, the control endpoints are almost completely
automated by the USB stack and we dont need to worry too much about it.
Bulk endpoints are used to transfer large amounts of data. USB hard drives, for example,
use bulk endpoints to read and write from the hard drive disk. The data being transferred
must not be time sensitive information (i.e. it doesnt really matter when a file copy
operation finishes). However data integrity is guaranteed since the host and device can
request a resend of the data packet should the packet fail a CRC check. These requirements
make bulk endpoints perfect for mass storage type applications.
Isochronous endpoints are used to transfer time sensitive information that is not critical,
and does not require data integrity. For example, USB audio speakers require that the audio
samples arrive at the speakers at a fixed frequency. However, if a sample fails to arrive at
the speaker, there is no need to resend the audio sample. In applications where the
information is being streamed live to the user (or to the host, as in the case of a
microphone), isochronous endpoints are the ideal candidate.
Interrupt endpoints are time sensitive information that are extracted on a regular time
interval. Your USB keyboard for example, is constantly sending information, even when no
keys are pressed. Your USB mouse is constantly sending information to the host about
whether a click has occurred or whether the pointer has moved. The information must be
sent to the host on a tight schedule on the order of a few milliseconds (usually 1 to 5 ms).
Even if the mouse pointer is delayed by a mere 100 ms or so, the mouse would feel
sluggish, and would probably cost you many miss-clicks during its operation. HID devices
only use interrupt type endpoints.
The reason for having these different types of endpoints is to allow a large amount of
flexibility in the variety of USB devices. In addition, because USB connections are
packetized into time frames, there can be an enormous amount of traffic demands on a
single connection. With different endpoints, the priorities can be filtered. Obviously, the
time-sensitive critical information must arrive at the host on a regular schedule. For this
reason, control endpoints and interrupt endpoints take the first bite out of the USB
connections available bandwidth. If there is any available bandwidth left, it is then
distributed amongst the bulk and isochronous endpoints.

The descriptors transmitted by the device to the host describe which non-control endpoints
are available within the interface. During the enumeration process, the host opens the
control endpoint. Next the descriptors are transferred and read by the host. The host then
loads the appropriate drivers, if any, so that the non-control endpoints described in the
control endpoint can be read and written to. Once the endpoints are opened, the device
connection is successfully established.
Endpoint Directionality
USB is a point to point type network, which means that a connection can only have two
nodes. This connection can have several different varieties. These include host to device,
host to hub, hub to hub, hub to device. In all of these cases, the directionality reference is
always towards the host. An input, therefore is information from the device to the host,
(i.e. from the hosts perspective, data is coming in). An output is data going out from the
host, to a hub or a device. In the case of the hub to hub connection, one hub is actually
considered the device. You will notice that on USB hubs, there is one USB connector that
is different from the rest. The unique connector is the one that is connected to the USB
host. If a series of hubs are chained together, the topology is still maintained (just think
about it real hard). An input is still information going towards the host computer. In mouse
example mentioned earlier, the endpoint is an IN direction endpoint. Serial communication
devices such as a USB to serial point converter would require an IN and an OUT direction
endpoint since communications must occur both ways. Control endpoints are special, and
are considered bi-directional.
So thats the gist of it. I think thats most of what you need to know before we start coding.
A Test Circuit
Before we start anything, well need a circuit to demonstrate our USB capabilities. Heres a
simple little gadget I made when I was starting out at my job.
Simple HID Joystick Schematic and BOM
When I first made the circuit, I forgot to put an oscillator, so I stuck one on with a bunch of
tape and some fancy rework. There are three analog inputs to the circuit as well as 6 contact
buttons. We will be only using the 6 buttons to create a simple joystick for this example.
Heres a photo of the finished circuit, along with my trusty ICD 3.

USB Power
A bit of discussion is needed for the power options in USB. The USB specification allows a
device to draw up to 100 mA during enumeration, and up to 500 mA after enumeration (if
the host deems that the required power is available). If you need more power, you will have
to provide it yourself through a separate power connection. A device that only uses the 5V
power from the USB connection is considered Bus Powered. If a device does not draw
any current from the USB connection, and only draws power from an external power
source, it is considered Self-Powered. These are the two most common options. You can
also create complex powered devices which takes priority depending on which power
source is available. The diagrams below are taken from the wiring requirements from
Microchips datasheets.

For this example, we can easily calculate how much power the circuit will require. The
oscillator and the microcontroller subcircuits are by far the modules that take the most
power. I would estimate them to be about 30 mA to 50 mA. The rest of the subcircuits draw
minimal power as pull-up resistors and potentiometers are not very power hungry. Since we
are below the 500 mA limit for the bus power device category, we can directly draw power
from the 5V USB connection. The connections and bypass capacitors are shown below.

The Vcap pin bypass can be supplemented with another 0.1uF capacitor if desired. Since
there are not that many digital switching circuits on this design, I opted not to add the
capacitor. The VBUS and VUSB pins must be separated as they refer to very different
votlages. The VBUS is the 5V connection directly coming directly from the USB
connection. This pin is used to detect that a USB connection has been made. A high
resistance resistor can be put in series on this pin to reduce susceptibility to surges and
voltage spikes. The VUSB pin is used to power the USB module on the PIC24. Since the
data signaling on the USB is at 3.3V, this pin is also connected to 3.3V. In some situations,
you might be powering your PIC at a lower voltage (ie. 2.5V). However, the VUSB pin
must always be at 3.3V due to the USB physical layer electrical requirements.
Exploring the USB stack
If you havent read the first section of the USB tutorial, please follow the instructions in
that section to install the Microchip Applications Library (MAL). Next we need to made a
copy of the USB stack. I usually do this because I want to make sure that my base code
does not get changed when there is an update to the Microchip Application Library. The
location of the USB stack is ./[MAL Directory]/Microchip/USB/.
I have a dedicated directory for all of my PIC projects, and a copy of the USB stack is
copied to this directory. Below is a snap show of my directory structure.

All of the compiled binaries for modules and drivers are stored under lib. All of the
source codes for modules and drivers that are not complied are stored under nlib. All
projects are stored under projects, and finally all of the Microchip drivers are stored
under Microchip. There are definitely other way to arrange your files, but I find this
system to work quite well.
Creating a Project
Microchip is slowly phasing out MPLAB, so we are going to do this next project with
MPLAB X. I have some Java programming background, so using a NetBeans IDE was
second nature to me. Overall, MPLAB X has been pretty good, although, its still quite a bit
buggier than the original MPLAB (as of August 2011).
We will now create a new project using the New Project wizard.

It will ask you a few questions, but you should end up with an empty project Standalone
Project. My circuit uses a PIC24FJ32GB002T-I/ML so make sure the appropriate
controller is picked. Now we add some files. Add the following sources from the ./[MAL
Copy]/Microchip/USB/ and its subdirectories.
usb_device.c
usb_function_hid.c

These are the only drivers needed for now. We now need to add the header files for these
USB drivers. A quick way to do this is to add all the header files in a directory by listing it
in the Include directory during compilation. We can right click on the MPLAB X project
and select Properties, then select the pic30-gcc option for Include directories. Add
the necessary directories so that the correct header files will be included. This will add all
header files in the directories without having to individually select them.

We are now ready to create a support package for our USB circuit.
Creating a Board Support Package
A Board Support Package (BSP) is a common term used to describe a bunch of headers and
sources that are used to define pins and timing information for a specific circuit. Embedded
programmers like to separate the reusable code from the board specific code. This way the
least amount of work has to be done every time a new board revision comes out. We will
now create a board support package for this particular circuit.
The file we will first create is a master include file. This file has all the headers listed
together, and more importantly, it lists the headers in the order in which they should be
included. I usually name this file dev.h. Below is the code listing.
(dev.h)
/************************************************************************
******
Engscope.com
Author JL
Created Jul 16, 2007
Modified Aug 27, 2011
Master Include for USB Simple Joystick
*************************************************************************
*****/

#ifndef DEV_H
#define DEV_H

//include proper headers
#ifdef __PIC24F__
#include "p24fxxxx.h"
#elif defined __PIC24H__
#include "p24hxxxx.h"
#else
#error No valid target device
#endif

//type definitions
#include "GenericTypeDefs.h"
#include "Compiler .h"

//board support
#include "bsp.h"

//USB drivers
#include "./USB/usb.h"
#include "./USB/usb_device.h"
#include "./USB/usb_function_hid.h"

//nlib driver
#include "nlib_sys.h"
#include "nlib_hid.h"
#include "nlib_drv_btn.h"

#endif
The main advantage of a master include file is that the headers are always linked in the
exact order specified in the dev.h file. The symbols that needs to get defined first, are
guaranteed to be defined first. In the above listing, I first include the device specific header,
then Microchips type definitions, then the file bsp.h. This last file has the board specific
symbols that I will explain shortly. Next comes the USB drivers, and lastly, drivers that I
have written myself.
We now need to define some board specific symbols, and we will do that in the file
bsp.h. Heres a sample listing of what to expect:
(bsp.h)
/************************************************************************
******
Engscope.com
Author JL
Created Jul 16, 2007
Modified Aug 27, 2011
Board Support Package for USB Simple Joystick
*************************************************************************
*****/

#ifndef bsp_h
#define bsp_h

/************************************************************************
******
Timing control bits
*************************************************************************
*****/
//oscillator frequency
#define BSP_PRIMARY_OSC_HZ 8000000UL

//mcu frequency
#define BSP_FCY_HZ (BSP_PRIMARY_OSC_HZ / 2)

//timer 2 tick frequency
#define BSP_TMR2_FREQUENCY 1000UL //1 ms ticks
#define BSP_TMR2_PERIOD (BSP_FCY_HZ / BSP_TMR2_FREQUENCY)

/************************************************************************
******
Pin definitions
*************************************************************************
*****/
//define buttons, policy
#define BUTTON1 PORTBbits.RB2
#define BUTTON2 PORTBbits.RB3
#define BUTTON3 PORTBbits.RB4
#define BUTTON4 PORTBbits.RB5
#define BUTTON5 PORTBbits.RB7
#define BUTTON6 PORTAbits.RA4

/************************************************************************
******
Custom Types for HID control
*************************************************************************
*****/
typedef union HID_CONTROLS_TYPEDEF
{
struct
{
BYTE B1:1; //buttons
BYTE B2:1;
BYTE B3:1;
BYTE B4:1;
BYTE B5:1;
BYTE B6:1;
BYTE Bpad:2; //filler
} buttons;
} HID_CONTROLS;

/************************************************************************
******
BSP Function Prototypes
*************************************************************************
*****/
//board specific functions
void BSP_init(void);
void BSP_ProcHid(HID_CONTROLS* joy);

#endif
Here Ive included some board level information concerning the oscillator timing that may
or may not be used. Each of the buttons are connected to a digital pin. These pin symbols
are redefined to BUTTONx so that they are easier to use in the actual context of code.
Next, I define a specific data type used to transfer information to and from the USB driver.
Since our circuit uses six buttons, we will use one bytes to represent the data. This byte
contains 6 bits for the buttons, plus 2 filler bits that wont do anything. Lastly, we need to
define some prototype functions that we will use in our bsp.c source file.
Next we need to create the source code for our board support package with the file bsp.c.
Below is the listing of the file for this project.
(bsp.c)
/************************************************************************
******
Engscope.com
Author JL
Created Jul 16, 2007
Modified Aug 27, 2011
Board Support Package for USB Simple Joystick
*************************************************************************
*****/
#include "dev.h"

/************************************************************************
******
define other board specific variables here
*************************************************************************
*****/
BtnObj btnJoy1; //buttons
BtnObj btnJoy2;
BtnObj btnJoy3;
BtnObj btnJoy4;
BtnObj btnJoy5;
BtnObj btnJoy6;

//create function pointers using these private functions
int btn1() {return BUTTON1;};
int btn2() {return BUTTON2;};
int btn3() {return BUTTON3;};
int btn4() {return BUTTON4;};
int btn5() {return BUTTON5;};
int btn6() {return BUTTON6;};

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

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

btn_Proc(btnJoy1);
btn_Proc(btnJoy2);
btn_Proc(btnJoy3);
btn_Proc(btnJoy4);
btn_Proc(btnJoy5);
btn_Proc(btnJoy6);

hid_Proc();
}

/************************************************************************
******
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;

//buttons needs to be pulled up
_CN6PUE = 1;
_CN7PUE = 1;
_CN1PUE = 1;
_CN27PUE = 1;
_CN23PUE = 1;
_CN0PUE = 1;

//set up buttons
btnJoy1 = btn_New();
btnJoy2 = btn_New();
btnJoy3 = btn_New();
btnJoy4 = btn_New();
btnJoy5 = btn_New();
btnJoy6 = btn_New();
btn_Init(btnJoy1, &btn1, 2000);
btn_Init(btnJoy2, &btn2, 2000);
btn_Init(btnJoy3, &btn3, 2000);
btn_Init(btnJoy4, &btn4, 2000);
btn_Init(btnJoy5, &btn5, 2000);
btn_Init(btnJoy6, &btn6, 2000);

//initialize HID USB Interface for joystick
hid_Init(HID_EP, &BSP_ProcHid);
}

//function processes the HID IOs
void BSP_ProcHid(HID_CONTROLS *joy){
joy->buttons.B1 = btnPressed(btnJoy1);
joy->buttons.B2 = btnPressed(btnJoy2);
joy->buttons.B3 = btnPressed(btnJoy3);
joy->buttons.B4 = btnPressed(btnJoy4);
joy->buttons.B5 = btnPressed(btnJoy5);
joy->buttons.B6 = btnPressed(btnJoy6);
}
This file uses many symbols, however, I no longer need to add the header directives
individually because I know that my dev.h file has the header listed in the order that I
need already. I can simply link all of the header files by including the dev.h file.
The file begins by defining some objects used to represent the buttons. These object use
dynamic memory and samples the button inputs during a timer interrupt. Next I define the
timer interrupt, which will be used to set up my periodic sampling of my buttons and to
update the value to be sent over the USB connection. Several functions are then defined.
BSP_Init is used to initialize some variables during the beginning of the program, and
BSP_ProcHid is used to update the USB data structure used to represent buttons.

We now need to define some USB hardware level stuff. The Microchip USB Framework
stores all of the hardware specific definitions for the USB Stack in a file called
HardwareProfile.h. Ive simplified their version of the file with my own, which takes out
all the code that are not used for the PIC24.
(HardwareProfile.h)
#ifndef HARDWARE_PROFILE_H
#define HARDWARE_PROFILE_H

/*******************************************************************/
/******** USB stack hardware selection options *********************/
/*******************************************************************/
//This section is the set of definitions required by the MCHPFSUSB
// framework. These definitions tell the firmware what mode it is
// running in, and where it can find the results to some information
// that the stack needs.
//These definitions are required by every application developed with
// this revision of the MCHPFSUSB framework. Please review each
// option carefully and determine which options are desired/required
// for your application.

//#define USE_SELF_POWER_SENSE_IO
#define tris_self_power TRISAbits.TRISA2 // Input
#define self_power 1

//#define USE_USB_BUS_SENSE_IO
#define tris_usb_bus_sense U1OTGSTATbits.SESVD //TRISBbits.TRISB5 //
Input
#define USB_BUS_SENSE U1OTGSTATbits.SESVD

//Uncomment this to make the output HEX of this project
// to be able to be bootloaded using the HID bootloader
#define PROGRAMMABLE_WITH_USB_HID_BOOTLOADER

//If the application is going to be used with the HID bootloader
// then this will provide a function for the application to
// enter the bootloader from the application (optional)
#if defined(PROGRAMMABLE_WITH_USB_HID_BOOTLOADER)
#define EnterBootloader() __asm__("goto 0x400")
#endif

/** I/O pin definitions ********************************************/
#define INPUT_PIN 1
#define OUTPUT_PIN 0

#endif //HARDWARE_PROFILE_H
Lastly, we need to copy over some project specific definitions for our USB device. These
files are written in a very specific way, and I dont know how to create them. However, I
can get a copy of them from the examples in the Microchip Application Library, then
modify them for my own use. I have taken the following files from ./[MAL
Directory]/USB/Device HID Joystick/Firmware and copied it into my projects folder:
main.c
usb_config.h
usb_descriptors.c
Before we go forward, the button drivers use dynamically allocated memory. We will need
to tell the linker to reserve some of the ram for heap memory. Right click on the project and
select Properties, then go to the pic30-ld options, and reserve a healthy amount of heap.
Ive selected 1000 bytes for this project.

The board support package is now complete, and we are ready to finish programming our
USB joystick in the next section.
14.4 USB HID Joystick

Now that the main components of the USB device has been set up, its time to worry about
the application specific implementation. There are three files that we need to modify before
we can compile and load the device.
main.c
The first of these is main.c. In the Microchip version of the file, there are a ton of
#pragma directives used to declare data and program space on the PIC18. On the C30
compiler for the PIC24, #pragma directives are not supported. You can safely delete
these lines. You can also delete all lines with the directive pertaining to #if
defined(__18CXX) since we are exclusively covering PIC24 usage. We are also defining
all board specific variables and types in our bsp.c and bsp.h files, so we can go ahead
and delete the joystick report type definitions and variable declaration in the main.c file
as well. We must keep the global USB function that are located towards the end of the file.
In the end your main.c file should look something like this:
/************************************************************************
******
Engscope.com
Authro: JL
Created May 27, 2010
Modified Mar 27, 2010
USB Framework
*************************************************************************
*****/

//headers for QP
#include "dev.h"

//configuration bits
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & FWDTEN_OFF)
//jtag off
//write protect off
//emulation off
//watchdog off

_CONFIG2(IESO_OFF & PLLDIV_DIV2 & FNOSC_PRIPLL & FCKSM_CSECMD \
& IOL1WAY_OFF & OSCIOFNC_OFF & POSCMOD_EC)
//secondary oscillator off
//set pll to 24Mhz
//use fast RC
//disable clock switching
//use osco
//unlimited writes to RP
//disable primary oscillator

_CONFIG3(WPDIS_WPDIS & SOSCSEL_IO)
//disable write protect

/** PRIVATE PROTOTYPES *********************************************/
static void InitializeSystem(void);

//main entry
int main(void)
{
//initialize the Board Support Package
BSP_init();

//initialize USB
InitializeSystem();

//attach device
#if defined(USB_INTERRUPT)
USBDeviceAttach();
#endif

//loop forever
while(1){
}
}

/********************************************************************
* Function: static void InitializeSystem(void)
*
//... rest of global USB functions
Device Descriptor
Now we need to describe our USB device to the USB host. This is done through the file
usb_descriptors.c. When you open the file, you will notice that it is structured exactly
like the USB hierarchy.
/* Device Descriptor */
ROM USB_DEVICE_DESCRIPTOR device_dsc=
{
0x12, // Size of this descriptor in bytes
USB_DESCRIPTOR_DEVICE, // DEVICE descriptor type
0x0200, // USB Spec Release Number in BCD format
0x00, // Class Code
0x00, // Subclass code
0x00, // Protocol code
USB_EP0_BUFF_SIZE, // Max packet size for EP0, see usb_config.h
MY_VID, // Vendor ID, see usb_config.h
MY_PID, // Product ID, see usb_config.h
0x0001, // Device release number in BCD format
0x01, // Manufacturer string index
0x02, // Product string index
0x00, // Device serial number string index
0x01 // Number of possible configurations
};

/* Configuration 1 Descriptor */
ROM BYTE configDescriptor1[]={
/* Configuration Descriptor */
0x09,//sizeof(USB_CFG_DSC), // Size of this descriptor in bytes
USB_DESCRIPTOR_CONFIGURATION, // CONFIGURATION
descriptor type
DESC_CONFIG_WORD(0x0029), // Total length of data for this cfg
1, // Number of interfaces in this cfg
1, // Index value of this configuration
0, // Configuration string index
_DEFAULT | _SELF, // Attributes, see usb_device.h
50, // Max power consumption (2X mA)

/************************ JOYSTICK INTERFACE
START************************/
/* Interface Descriptor */
0x09,//sizeof(USB_INTF_DSC), // Size of this descriptor in bytes
USB_DESCRIPTOR_INTERFACE, // INTERFACE descriptor type
0, // Interface Number
0, // Alternate Setting Number
2, // Number of endpoints in this intf
HID_INTF, // Class code
0, // Subclass code
0, // Protocol code
0, // Interface string index

/* HID Class-Specific Descriptor */
0x09,//sizeof(USB_HID_DSC)+3, // Size of this descriptor in bytes
RRoj hack
DSC_HID, // HID descriptor type
DESC_CONFIG_WORD(0x0111), // HID Spec Release Number
in BCD format (1.11)
0x00, // Country Code (0x00 for Not supported)
HID_NUM_OF_DSC, // Number of class descriptors, see usbcfg.h
DSC_RPT, // Report descriptor type
DESC_CONFIG_WORD(HID_RPT01_SIZE), //sizeof(hid_rpt01), // Size
of the report descriptor

/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_IN, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0xFF, //Interval

/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_OUT, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0xFF //Interval
};
The file first describes the device, then the configuration, the interface, and lastly the
endpoint. In addition, there is a special description for the HID class that tells the USB host
which report to use when sending data back and forth. Since the Microchip example is
already describing an HID device that only sends data to the USB host (recall that the
joystick only sends data one way, it does not receive any data from the host), there is not
much that we need to modify in the device descriptor.
Report Descriptor
In addition to describing the device, we also need to describe the report. This report
descriptor tells the USB host how to use the data that is being received and how to transmit
data in a way that the host can understand. Since the Microchip joystick is not the same as
the joystick we are building, we need to build the report descriptor from scratch. We will
use the HID descriptor tool, provided by the USB-IF to do this step. After starting a new
descriptor, I have loaded the following settings and clicked on Parse Descriptor to
properly format the indentations.

At this point, it really pays to have a brief read into USB HID Standard. The document
describes the requirements for defining the abilities of an HID device. Here I define the
device and states that it is used as a general desktop device and a joystick. Next I define 6
buttons which can have a value of 0 or 1, defined by the variable input descriptor (INPUT
(Data,Var,Abs). However, I need to use a single byte to represent 6 buttons. I need 2 filler
bits in order to fill up the byte, which is defined by the constant input descriptor (INPUT
(Cnst,Var,Abs)).
You can then use the save as header feature in the HID descriptor tool to get the C
version of the descriptor directly. Then copy and paste the code into the report descriptor
section of the usb_descriptor.c file. In the end your report descriptor should look like this:
ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x05, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x06, // USAGE_MAXIMUM (Button 6)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x02, // REPORT_SIZE (2)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0xc0
}
};
String Descriptors
Finally, USB supports custom strings embedded into the device. These are character strings
that are used when the operating system wants to refer to the device. In our example, I have
named the device Simple HID Joystick. This will cause the device to show up in the
Devices and Printers folder of Windows with the proper name.

String 0 must always indicate the language code. String 1 and greater can be any custom
string that you might want to use for the device. Under the Microchip USB framework, the
strings are defined as a ROM, and collectively pointed with a pointer:
//Language code string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[1];}sd000={
sizeof(sd000),USB_DESCRIPTOR_STRING,{0x0409
}};

//Manufacturer string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[8];}sd001={
sizeof(sd001),USB_DESCRIPTOR_STRING,
{'E','N','G','S','C','O','P','E'
}};

//Product string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[19];}sd002={
sizeof(sd002),USB_DESCRIPTOR_STRING,
{'S','i','m','p','l','e',' ','H','I','D','
','J','o','y','s','t','i','c','k'
}};

//Array of string descriptors
ROM BYTE *ROM USB_SD_Ptr[]=
{
(ROM BYTE *ROM)&sd000,
(ROM BYTE *ROM)&sd001,
(ROM BYTE *ROM)&sd002
};
You can then specify which string to use for the manufacturer and product name in the
device descriptor.

/* Device Descriptor */
ROM USB_DEVICE_DESCRIPTOR device_dsc=
{
0x12, // Size of this descriptor in bytes
USB_DESCRIPTOR_DEVICE, // DEVICE descriptor type
0x0200, // USB Spec Release Number in BCD format
0x00, // Class Code
0x00, // Subclass code
0x00, // Protocol code
USB_EP0_BUFF_SIZE, // Max packet size for EP0, see usb_config.h
MY_VID, // Vendor ID, see usb_config.h
MY_PID, // Product ID, see usb_config.h
0x0001, // Device release number in BCD format
0x01, // Manufacturer string index
0x02, // Product string index
0x00, // Device serial number string index
0x01 // Number of possible configurations
};
usb_config.c
The last file that needs to be modified is the usb_config.c file. This file contains your
VID and PID information, as well as ROM memory reservations.
If you need a refresh of the function of the VID and PID values, please read section 2 of the
USB tutorial. We will be borrowing Mircrochips VID, and randomly giving ourselves a
PID of 00100.
#define MY_VID 0x04D8
#define MY_PID 0x0100
Since we have changed the length of our report descriptor, we will need to change the size
of the ROM memory reservations. Here I have modified the size of my report descriptor by
changing the symbol HID_RPT01_SIZE.
/** ENDPOINTS ALLOCATION *******************************************/
/* 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 29
Finishing Touches
We also need to create a file that sends out data in the struct through the API defined by
Microchip. These files are called nlib_hid.c and nlib_hid.h. There is really nothing very
interesting to see in these files, but basically they take care of transmitting the tokens and
keeping track of whether a transfer is completed or not. The nlib_hid.h contains the
function prototypes, while nlib_hid.c is where the action takes place, shown below:
#include
#include "nlib_hid.h"

static USB_HANDLE devLastTransmission; //transmission status
static HID_CONTROLS hid_input; //stores current control values (button
on/off, axis etc)
static int endPoint; //this stores the EP number on initiation

//this is the function called during the process, updates Input Controls
static void (*processInput)(HID_CONTROLS *);

//this is the Initiation routine for custom user code
void hid_Init(int ep, void (*proc)(HID_CONTROLS *))
{
//get the end point number
endPoint = ep;

//initialize the variable holding the handle for the last
transmission
devLastTransmission = 0;

//link the function pointer
processInput = proc;

//hid_input should be zero by default (declaration zeros
everything)
}//end UserInit

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

//for interface 0
//If the last transmision is complete
if(!HIDTxHandleBusy(devLastTransmission))
{
//processInput(&hid_input); //process the input before
transmitting it
processInput(&hid_input);

//Send the packet over USB to the host.
devLastTransmission = HIDTxPacket(endPoint,
(BYTE*)&hid_input, sizeof(hid_input));
}
}//end ProcessIO
The main function called hid_Proc() takes the data structure that we defined earlier for our
buttons and transfers it through the USB bus. It keeps track of whether the USB connection
is ready for the next transfer. The hid_input variable, which was defined in our bsp.h is
declared in here as a static. We update the values of variable hid_input through the function
BSP_ProcHid() and transfers the variable with hid_Proc().
First Connection and Enumeration
I have taken the liberty of including the modified code below. The code should compile and
ready to be loaded into a PIC24 device.
Engscope Simple HID Joystick Project
Please read the disclaimer before downloading.
After compiling and loading the hex file, Windows will automatically load the drivers. If
you cannot get past this step, then either your firmware was not loaded properly or
something in the hardware is not working. Keep checking your circuit to find the culprit if
you are having problems with enumeration (look for bad voltages, bad oscillators, shorts,
opens etc.). The compiled code is verified and working on my systems.

You can then open the Set up USB game controllers utility to test the device.


Lastly, to see the actually data being transferred from the device to the USB host, we can
pop up the Simple HID Write tool and select the Simple HID Joystick.

The first byte indicates the report number, which is used if you have multiple reports on a
simple device (e.g. a mouse/keyboard combo can use two reports to separate the data and
functionality). Since we only have one report, the first byte is always 000. The second
byte is defined by our report descriptor. You will see that the first 6 bits will change when
the buttons are pressed, and the top 2 bits are always zero.

There you have it. We have just created a custom 6 button joystick.
14.5 USB Debugger Protocol Design

Now that you got your joystick working, you will probably be able to figure out how to
construct a mouse or a keyboard device on your own. Basically, if you want to create an
input only type of device, you can use the Simple Joystick example as a guideline. But
what happens when you need to talk back and forth from your computer through the USB
port?
To make a custom communications device, we need the device to act as both an input and
an output. Because most of the provisions in the USB HID standard are for input devices,
we cannot use a joystick, mouse or keyboard as a starting point. However, we can still use
the HID for both inputs and outputs because both IN and OUT type endpoints are supported
in the HID standard. Instead of declaring the device as defined device class (e.g. HID
Joystick class, or HID Keyboard class etc), we will need to declare it as a vendor defined
class. This way the operating system will still use the standard HID drivers during
enumeration but will not attempt to use the device as a mouse, keyboard or joystick. This
also means that we will need to write custom code on the host side to access the input and
outputs of the USB device.
Lets take a copy of the Simple Joystick code and start modifying the code to suite our
needs. The plan is to create a device that takes IN and OUT type interrupt transfers (the
Simple Joystick only allowed IN type transfers). On top of the USB protocol, I will also
have to specify a custom communications protocol for communicating between the host
and device.
A Simple Protocol Design
Before we get into any coding, lets design a communications protocol to get data in and
out of our USB device. I would like something simple and easy to understand, but have
enough features so that it can be used in many different applications. This will also
determine how many bytes I will need to specify in my Report Descriptor.
It would be nice to be able to send and receive at least 8 bytes of data in one transfer. This
number is picked arbitrarily. Please remember that the USB interrupt transfers allow up to
64 total bytes per transfer (for full speed), so in theory we could actually increase the
number of bytes transferred in one interrupt. With each of the 8 bytes of data, we need to
attach some additional information. Lets add a byte that determines the command, as well
as an address field. Since 1 bytes worth of address seems very limited as you can only
access 256 (2^8) addresses, well use two bytes for the address information. Finally, we
might want one more byte reserved for a command modifier. The command modifier is
only used during readpage and writepage commands, and indicates how many bytes we
would like to read/write during the page operation. I will explain how each field is used in
just a little bit. For now, just keep in mind that we will require:
1 command byte + 2 address bytes + 1 command modifier byte + 8 data bytes = 12 total
bytes
Lets take a look at a couple of examples of how a communications would occur. First we
need to define several commands and constants:
#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
Remember that the host ALWAYS initiates the communications in a USB connection. Our
device is exactly the same. We will also follow a strict format for all transfers. The first
byte is always the command byte. Byte 2 and 3 will always indicate the address, and byte 4
will be used for the command modifier. The 8 bytes of data come thereafter. As an
example, for the host to issue a read command at address 0x5AA5 to the USB device, the
12 bytes would look like this:
From host: 0xB0 0x5A 0xA5 0x00 (8 bytes of dummy data)
(12 bytes total)
Because we are merely reading data from the device, the 8 bytes of data is disregarded. The
device will respond with reply as:
From device: 0xB0 0x10 0x00 0x00 (1 byte reply data) (7
bytes of dummy data)
(12 bytes total)
The device must confirm that it has received the read command by replying with the read
command in the command byte position. Since the host already issued the address, there is
no need to reply back the address again. Instead we send a success reply, meaning that no
errors has occurred during the read. There is also no need to use the command modifier.
The first data byte is the data at address 0x5AA5. The 7 remaining data bytes are not used.
Now lets take a look at a writepage command at address 08080 with 8 bytes along with
the reply from the device:

From host: 0xA1 0x80 0x80 0x08 (8 bytes of write data)
From device: 0xA1 0x10 0x00 0x00 (8 bytes of dummy data)
In this case, we used the modifier byte to indicate how many bytes in the writepage that the
host would like to write to the device. The device responds with a 0xA1 command
(writepage), confirming that the command has been received. The data is written to address
08080 to 08087.
The reset command puts the communications protocol to a known state. Because of the
interrupt nature of the transfer, the reset command must be issued after each read/write
command. A reset command will look like this:
From host: 0x80 0x00 0x00 0x00 (8 bytes of dummy data)
From device: 0x80 0x00 0x00 0x00 (8 bytes of dummy data)
During a data exchange between the host and device, the device CANNOT know whether
the host has actually received the data. This is because the USB transfers might be ignored
during high CPU usage or might have been lost in the communications. To avoid this, the
device must keep on sending the read/write reply until a reset message has been received
from the host. After the reset message, all command and data is reset to 000. For example,
lets take a look at what might happen during a real set of transfers between a host and
device.
At the beginning the host sends the read command, and the device replies with the data
requested by the host:

From host: 0xB1 0x80 0x80 0x08 (8 bytes of dummy data)
From device: 0xB1 0x10 0x00 0x00 (8 bytes of reply data)
At this point, the device does not know whether the host has received the data. It must keep
on sending the same data until a reset command acknowledges the receipt of the read data.
The next set of transfers might look like this:
From device: 0xB1 0x10 0x00 0x00 (8 bytes of reply data)
From device: 0xB1 0x10 0x00 0x00 (8 bytes of reply data)
Finally, the host indicates that it has processed the data and replies with a reset:
From host: 0x80 0x10 0x00 0x00 (8 bytes of dummy data)
From device: 0x80 0x00 0x00 0x00 (8 bytes of dummy data)
From device: 0x00 0x00 0x00 0x00 (8 bytes of dummy data)
The device acknowledges the reset command with reset command reply. The transfers after
the reset are all 000. The host is now ready to send the next commands. The protocol is
rather wasteful in terms of bytes used, but it should work rather efficiently if we use mostly
writepage and readpage type commands. Since the transfers are interrupt based, wasted
bytes are not really an issue anyways. We get 12 bytes of transfer on every interrupt
regardless of bandwidth (up to 64 bytes if you decide to add more data bytes to this
protocol).
Implementation
Now that we have defined the protocol, lets write some code to implement our protocol.
First we need to define a struct type to represent our 12 bytes of data.
//packet typedef
typedef union _USB_CONTROLS_TYPEDEF
{
struct
{
BYTE cmd;
BYTE mod[3];
BYTE data[8];
} packet;
} USB_CONTROLS;
I have combined the address and command modifier byte into a single array, but the rest of
the stuff is pretty self explanatory. Next we construct a set of functions that read, write and
handle the data as described by the protocol.
USB_CONTROLS in_packet;
USB_CONTROLS out_packet;

//main processing function for usb
void UniProcessRx(USB_CONTROLS *in, USB_CONTROLS const *out)
{
switch(out->packet.cmd)
{
default:

//reset commands, address and options
case(CMD_RESET):
in->packet.cmd = 0;
in->packet.mod[0] = 0;
in->packet.mod[1] = 0;
in->packet.mod[2] = 0;
in->packet.data[0] = 0;
in->packet.data[1] = 0;
in->packet.data[2] = 0;
in->packet.data[3] = 0;
in->packet.data[4] = 0;
in->packet.data[5] = 0;
in->packet.data[6] = 0;
in->packet.data[7] = 0;
in->packet.data[8] = 0;
in->packet.data[9] = 0;
in->packet.data[10] = 0;
in->packet.data[11] = 0;
break;

//call write function pointer, then check for error
case(CMD_WR):
in->packet.cmd = CMD_WR;
cmd_wr(out);
in->packet.mod[0] = CMD_SUCCESS;
in->packet.mod[1] = 0;
in->packet.mod[2] = 0;
break;

//call write pagefunction pointer, then check for error
case(CMD_WRPAGE):
in->packet.cmd = CMD_WRPAGE;
cmd_wrPage(out);
in->packet.mod[0] = CMD_SUCCESS;
in->packet.mod[1] = 0;
in->packet.mod[2] = 0;
break;

//call write function pointer, then check for error
case(CMD_RD):
in->packet.cmd = CMD_RD;
cmd_rd(in, out);
in->packet.mod[0] = CMD_SUCCESS;
in->packet.mod[1] = 0;
in->packet.mod[2] = 0;
break;

//call write pagefunction pointer, then check for error
case(CMD_RDPAGE):
in->packet.cmd = CMD_RDPAGE;
cmd_rdPage(in, out);
in->packet.mod[0] = CMD_SUCCESS;
in->packet.mod[1] = 0;
in->packet.mod[2] = 0;
break;
}
}

//control register write functions
void cmd_wr (USB_CONTROLS const *cmd){
ctrlWrPtr(cmd->packet.mod[0]*256 + cmd->packet.mod[1], cmd-
>packet.data[0]);
}

void cmd_wrPage (USB_CONTROLS const *cmd){
unsigned char i;
for(i = 0; i < cmd->packet.mod[2]; i ++){
ctrlWrPtr(cmd->packet.mod[0]*256 + cmd->packet.mod[1] + i,
cmd->packet.data[i]);
}
}
//control register read functions
void cmd_rd (USB_CONTROLS *res, USB_CONTROLS const *cmd){
res->packet.data[0] = ctrlRdPtr(cmd->packet.mod[0]*256 + cmd-
>packet.mod[1]);
}
void cmd_rdPage (USB_CONTROLS *res, USB_CONTROLS const *cmd){
int i;
for(i = 0; i < cmd->packet.mod[2]; i ++)
res->packet.data[i] = ctrlRdPtr(cmd->packet.mod[0]*256 +
cmd->packet.mod[1]+i);
}
The functions take care of all the reading, writing as well as reset for our protocol. In the
next section, I will show you how to put everything together. I will modify the report
descriptor and build a program using our new protocol.
UART based I2C Controller

Introduction
I frequently have to fool around with new ICs. However, since a large number of modern
day ICs use the I2C bus, it would be inconvenient and wasteful to build a controller on
each and every circuit I prototype for component evaluation. Instead, I built a tool that
translates RS232 UART commands into I2C commands. Then, using a program in Java to
control my RS232 port from a computer, I can have full access to all the ICs in one simple
bus.
Hardware
This project requires several hardware components. They include the following:
-LP2985-3.3 Voltage regulator for providing 3.3V to all the digital circuits
-PIC24HJ32GP204 Microcontroller for translating the UART to I2C and vise-versa
-MAX208 RS232 Transceiver. RS232 voltage level might blow out of PIC!, so use this to
change the voltage levels
-FXO-HC735-10 10Mhz Oscillator for 50ppm accuracy (or inaccuracy)
-various connectors, resistors and capacitors
Circuit Notes
Below are the various circuit and sub-circuits for the UART based I2C Controller
Top Level Circuit Subcircuit connections to connectors
PIC Subcircuit Physical connections for the PIC24
MAX208 Subcircuit Physical connections for the MAX208
Regulator Subcircuit Physical connections for the LP2985-3.3
Oscillator Subcircuit Physical connections for the FXO-HC735-10 Oscillator
I used a USB connector to provide power. Since the total circuit probably wont take more
than 100mA, it is well withing the 500mA power capacity of a single USB connector from
a computer. That way I dont have to get a AC-DC power supply for the circuit.
The MAX208 (and all MAX200 series of transceivers) uses 5V input voltage. The PIC and
the oscillator uses 3.3V! Make sure you get it right or something might blow, or might not
work.
Since the EDID chips in monitors and video devices uses I2C, I have included a high
density DB15 connector (VGA). This is to facilitate EDID programming for various work
related tasks. You dont need to included if you only want to fool around with I2C chips.
Those tasks are done with a 1.25 mm 3 position header.
PIC program
Heres a .rar file with all the project and program files for this project.
Engscope PIC Controler
Please read the disclaimer before downloading.
You should find an h folder with all the headers, as well as the project folder. Just open it
with MPLABs 8.10 or higher.
Program Brief

The program is pretty easy to understand. Ill go over the main.c file, and the pic.c file.

main.c main loop

i2c_init(100); //Initiate I2C channel
UART1Init(15); //Initiate UART1 to 19200 at 10MHz

DelaymSec(1000);

//Main Program Loop, Loop forever
while(1)
{
//process data
PICCProcessEvents();
}
After initiating the ports, an infinite loop is created, and calls the function
PICCProcessEvents(). This function takes care of all the receiving and sending on the I2C
and the UART ports.
pic.c main function

//main processing event routine
void PICCProcessEvents(void)
{
//get first character
temp = UART1GetChar();
data = 0;
unsigned char addr;
unsigned char subaddr;
unsigned char value;
unsigned char valuehigh;
unsigned char valuelow;

//first char determines command
switch(temp)
{
//Ping Command
case 200:
UART1PutChar(1');
UART1PutChar(2');
UART1PutChar(3');
break;

//Poll Command
case 201:
addr = UART1GetChar();
data = I2Cpoll(addr);
UART1PutChar(data);
break;

//Write I2C
case 210:
addr = UART1GetChar();
subaddr = UART1GetChar();
value = UART1GetChar();
I2Cwrite(addr, subaddr, value);
break;

//Write I2C double byte
case 211:
addr = UART1GetChar();
subaddr = UART1GetChar();
valuelow = UART1GetChar();
valuehigh = UART1GetChar();
I2Cwritedouble(addr, subaddr, valuelow, valuehigh);
break;

//Read I2C
case 220:
addr = UART1GetChar();
subaddr = UART1GetChar();
data = I2Cread(addr, subaddr);
UART1PutChar(data);
break;

//Read I2C double byte
case 221:
addr = UART1GetChar();
subaddr = UART1GetChar();
tempdouble = I2Creaddouble(addr, subaddr);
UART1PutChar(tempdouble.x);
DelaymSec(2);
UART1PutChar(tempdouble.y);
break;
default:
break;
}
}
Here, several functions can be selected through a case statement. First the
UART1GetChar() function is called. This function waits for the computer to send a
command. If the command is 200 (0xC8), it is a ping command, and simply writes back to
the UART a 1,2,3 character. If the command is 201 (0xC9), then it poll the I2C address.
This is done by waiting for the next UART1GetChar() returned value, and using it as the
address for the I2C polling. In this manner, all I2C capabilities can be accessed through a
computer with a serial port.
Usage
Since theres been quite a few requests for example code, the project should quell a lot of
people. It utilizes both the I2C as well as the UART capabilities of the PIC. When properly
constructed, it looks something like this:

As you can see, the circuit connects to my various evaluation circuit with the ICs that I
want to fool around with. I etched the circuit myself and soldered everything on there. The
last part to the project is the computer side software. For this you can pretty much use
anything thatll take advantage of the serial port on a computer.
I like to use a program called Terminal (which has been discontinued I think, cant find the
URL anymore), which is very programmable and allows you to send all values through the
serial port, not just characters and numbers. However any terminal program will work.
RealTerm for example is an excellent free serial terminal program. However, in the end, I
ended up writing my own terminal program because I wanted more control over the I2C
ports (custom programmability, script execution so I can run 200 I2C commands in
sequence over and over again, etc).
Once you have successfully connected to the circuit, and everything is working, you can
send your first message to your controller. The first command you should send is the
PING command, value 200 (0xC8) to see if your circuit works. Once this happens, you
should receiver a 1, 2, 3 values on your terminal softwares receive window (or even just
watch it on the oscilloscope).
From then on, just send whatever you like. You can review the I2C and the UART
protocols in the tutorial. If for example I want to write to device 0xA0, at sub-address 030
the value 099, I would have to send the I2C sequence 0xA0 (write), 030, 099. To send
the sequence on my I2C port, I need to send the following UART sequence to my
microcontroller: command 210, address 0xA0, subaddress 030, value 099. In other
words, I have to send 4 bytes in order 0xD2, 0xA0, 030, 099 on the computers serial
port.
Reading is the similar, to read device 0xA0, sub-address 030, I need to send the I2C
sequence 0xA1 (read), 030, then wait for the read value. To send the sequence I need to
send the following UART sequence to my microcontroller: command 220, address 0xA0
(read bit automatically added in the pic24h_i2c.c file), sub-address 030 and wait for the
read value to come back. After sending 0xDC, 0xA0, 030 through the serial port, you
should have the value at sub-address 030 display on your terminal screen.
Thats basically how it works. There are some other functions in the program, but Im sure
you can figure those out on your own. Happy playing with the toy. Please leave a comment
if you have questions!
-J
Basic IO: Button Debounce

In this article we will take a look at how a button debouncer can effectively created using a
PIC. So what is button debouncing?
The Button Debounce Problem
Thinking of using contact switches on your next project? Well better be prepared! It might
not be as easy as you think. Contact switches have tiny little springs on them that pop them
up when you release the button. This is great for popping the button, but terrible as a digital
input. This is because the springs, when releases, bounces around and creates instability for
a short duration. Observe the following typical circuit. Keep in mind that you cannot feed
an open circuit to a microcontroller. A digital input to a microcontroller must always be a
guaranteed 1 or a 0, otherwise the behavior of the chip is unpredictable (hence, you need a
pull-up resistor).

When the switch is open, the pull-up resistor raises the voltage at Probe to Vcc. When the
switch is closed, the voltage at Probe is shorted to ground. Ive captured a scope trace of the
contact switch with a pull-up resister shown below.

Notice the region where the signal goes up and down. This duration of the instability is on
the order of several milliseconds, usually no more than 10ms. In human terms, this is not
such a big deal, but to a microcontroller processing at 20 MIPS, this can be disastrous. To a
microcontroller, it would read the input as multiple button presses instead of just one. This
means that for any circuit involving a contact switch, a debouncer should be used. A
debouncer is a device that filters out the messy outputs of a contact switch, and gives you
clean edge transition. Dirty inputs = bad, clean inputs = good. There are several ways to
achieve this. In geek speak, we want to build a sampling filter. Because different
applications have different requirements, Engscope is proud to present 3 different ways to
solve the button debounce problem.
External Filter Method When you have the money
Obviously this input problem has been around for a few decades. As advanced as we are in
technology there are simple laws of physics that you just cant get around. Every day items
from microwave and mouse buttons to keyboard keys all has to have mechanisms built in to
recognize a false edge from a real one. Imagine your computer mouse not having a
debouncer. Youll be doubling clicking on everything because nearly all your single clicks
will actually be read by the computer as several clicks within a very short amount of time.
If the keyboards didnt have a debouncing mechanism, this page would probably
lllloooooookkk liikkkeeee ttttthhiiisss..
Several companies make devices that solves this problem. Maxim has a line of debouncers
that I use very often. The 681x series are excellent ICs that get the job done. They are also
very easy to use with flexible input voltages and requiring maybe one external bypass
capacitor. Since they are specifically designed for debouncing switches, the delay
associated with the ICs are about 20-40ms. This means that the edge transition on the
output of the debouncer arrives at the microcontroller about 40ms later than when the
contact switch is pressed. This is usually acceptable when humans are doing the button
pushing. I dont know about you, but cant time my button pressing to the 10ms scale. A
50ms delay is usually not noticeable. If you dont have debouncers but have some voltage
supervisors around, they will work as well, although the delay associated with them are
about 200ms (a lot of voltage supervisors have 100ms or 200ms delays). This is usually
way too much, and the buttons will have a lag feel to it. If you have a 50ms voltage
supervisor lying around, they are much better for this purpose.
The advantages to using an external filter are many. For one, you are freeing up
computation time. If you know for sure that your input are clean, you do not need anything
on the software end when doing the sampling. Less computation requirements, your
microcontroller is free to do more stuff. This also means less code to write, making the
overall program easier to compose and revise. There are disadvantages as well. Extra ICs
cost a lot of money, and they take up real estate on PCBs. They also suck down power and
create heat (albeit very little). These are things to be concerned about especially in mobile
applications.
The following circuit shows how to hook up the MAX6818. This is the set up I use when I
dont have power/heat/PCB real estate restrictions. You can can then attached the input pin
to any of the allowed digital inputs on a PIC. You can find the PIC circuit here.

Theres no need for a pull-up resistor because it is included in the MAX6818. Sampling the
input before and after the debouncer IC shows the delay and the clean output. Note the
delay is about 50ms, as depicted in the datasheet.

Undersampling: sampling based filter When you are lazy

If you dont want complicated code, or are out of room on your PCB, it is still possible to
debounce your inputs. One way to do this would be to significantly undersample the
incoming signal. If you look at the output of a button, there is a period of uncertainty. As
we mentioned, this period of uncertainty is usually about 10 to 20 ms. Look what happens
if we sample more than once within the uncertainty period.

The resulting string of sampled data becomes 1, 1, 1/0, 1/0, 0, 0. This is a problem
because we have two uncertain samplings. If by coincidence the microcontroller reads the
uncertain data as 1, 1, 0, 1, 0, 0 then we have a problem, because the microcontroller
has detected two negative edge transitions from 1 to zero, and will assume that the button
has been pressed twice.
We can get around this problem by sampling with the exact period of the length of the
uncertainly. Consider the following sampling scenario:


The resulting string of sampled data is 1, 1, 1/0, 0, 0. Even though we sampled during
the uncertainty period, we only sampled once. We can get away from with this because the
microcontroller can either read the input as 1, 1, 1, 0, 0 or 1, 1, 0, 0, 0. Either way
we have only one negative edge transition.
Now suppose we really really undersample, then the following will occur:

Well, if we dont even sample in the debounced uncertain duration, we can be sure that our
inputs are correct and does not include double button presses due to debouncing. We can
write a simple program to detected input using timers. This sample program debounces the
inputs by undersampling. Heres a typical program that samples at period of 20ms.
Engscope Debounce Undersample Project
Please read the disclaimer before downloading.
Oversampling: software based filter When you are being a smart ass
Another way to debounce through software is by oversampling the input. Obviously, to
oversample means to sample very quickly (usually a lot higher than the Nyquist
frequency). In this case though, it just means that compared to the oscillation of the
debounce, the sampling frequency is very fast. So how does it work?
Consider the following graph. Lets say we sample the input very quickly and add the
results up. Then we set up the software to take a look at the last 100 samples. Suppose that
we define the software to compute a running sum of the last 100 samples in the following
manner: if the sample is a 0, subtract 1 (if the sum is already 0, dont subtract); if the
sample is a 1, add 1 (if the sum is already 100, dont add). Lets use this algorithm to
analyze the scope trace.

At the beginning of the trace, all the values are 0, so our running sum must be a zero.
During debounce period the running sum is between 0 and 100, so we are not sure if an
button has been pressed. However, after the debounce period, the running sum starts to add
up very quickly, and eventually increases to 100. By using if statements, we can easily put
together a program that can filter out the debounce using the running sum principle. You
can download a sample project with this running sum algorithm integrated.
PIC Button Debounce Oversample Project
Please read the disclaimer before downloading.
Please leave a comment if you got questions!
Finite State Machine Based LCD
Controller

One of the most used functions of the PIC is to control an LCD. LCD come inmany shapes
and sizes, but most of them all use the UART interface. This makes it a convenient and
compact way to communicate and control the LCD, since it only takes one wire (and
return) to send characters to the screen.
In this article, I will show you how to control a Matrix Orbital MOS series LCD screen. I
will be using a 162 character screen, and configuring the PIC24 to control the screen
using a finite state machine (FSM).

So what is an FSM? Its is an easy way to figure out all the possible configurations of a
machine and giving it instructions on what to do when that configuration is reached. These
configurations are called states, and since we are trying to figure out all the states (and
there must be a finite number of them), the term FSM is used to describe these machines. A
calculator for example is a FSM. There are only a finite number of possible outcomes that
can be configured, albeit a large number of states can exist when using a calculator. Finite
state machines are useful not only in hardware synthesis such as VHDL or VLSI, but also
in low level programing. This is because it allows you to create a complicated program
without having to deal with a labyrinth of if/else statements.
A Simple Example
Suppose we want to create a very simple circuit/program. We have an LED driver, and a
debounced button connected to a microcontroller. We want the microcontroller to keep
track of the state of the LED. If the LED is on, and the button is pressed, turn off the LED.
If the LED is off, and the button is pressed, then turn on the LED. We must also define
what happens when the circuit is booted up. We can arbitrarily select the LED to be turned
on. To represent this machine, we have 3 possible states: Start, LED on, LED off. A FSM
diagram of this program might look like the following:


How would we convert the diagram into code? Well the code might look something like
this (this is pseudo code, I dont know if itll compile).

#define startState 0
#define ledOnState 1
#define ledOffState 2
#define buttonPressed 1
#define buttonNotPressed 0

int state = startState;
int input = buttonNotPressed;

int main(void)
{
//init start state
state = startState;
while(1)
{
//get input first
input = getInput();
//
switch(state)
{
case startState:
state = ledOnState; break;
case ledOnState:
if(input == buttonPressed)
{
state = ledOffState;
ledOff();
}
break;
case ledOffState:
if(input == buttonPressed)
{
state = ledOnState;
ledOn();
}
break;
default: state = startState; //if unknown state, reboot
break;
}
}
}
Using the same concept, I have developed a driver board for the MOS LCD. I wont go into
the details of how it functions, but Ive included a project using the LCD drivers. Im sure
you can figure out how I constructed the FSM by looking at the code. It is quite an
ingenious way to control the LCD, if I may say so myself.
Button 1 clears the LCD screen, button 2 should make the following text appear:


The circuit configuration for this project is pretty standard. If you have been reading the
tutorials, you should be able to figure it out by looking at the code.
General Usage
For general usage, the driver makes it very simple to write to the LCD. You must first
initiate the LCD drivers by calling LCDInit(). To write a piece of text, first use LCDIdle()
to determine if the mircontroller is in the middle of writing something. Next use the
functions LCDText(int, string) and LCDText2(string, string) to write text. There are some
other functions in there for other effects. You can call LCDClear() to also clear the current
LCD text. Of course, if you dont like what I have written, you can always write your own
driver, or modify mine. Enjoy! If you got any questions, please feel free to leave a
comment.
Non-Blocking Code

What is non-blocking code and why is it so important? First lets look at the what.
Code is said to block when it takes a long time to return in order for other code to
execute. This is particularly true in embedded systems where there is usually only a single
thread of execution and plenty of while loops.
Suppose you are programming for a sequence of events. The pseudo code below
demonstrates the problem with blocking code.

//pseudo code for blocking
#include
void main() {
//initialize buttons, adc
initButtons();
initAdc();

while(1) {
//wait for button to be pressed
while(!buttonRisingEdge());
executeButtonPress();

//if button is pressed, sample adc
while(!adcIsBusy());
sampleAdc();
}
}
Now, Im sure many of you can see right away the problem with this code. There are two
things happening in this small segment. The program is in an infinite loop and it waits for a
button press. When a button press has occurred, the ADC is sampled. The while loop of the
ADC can block the input capture of the buttons, especially if sampleAdc() takes a long time
to return. If a button is pressed while sampleAdc() is executing, then a button press will be
missed.
So now that blocking code is explained, lets look at why it is important to write non-
blocking code. The best explanation of this subject matter that I have heard was from Dr.
Miro Samek during his presentation at ESC Boston 2009. He gave the example of a
vending machine. Imagine that we are trying to program this vending machine. In the
traditional sequential programming approach you might be tempted to write code that looks
something like this:
//sequential programming vending machine
#include
void main() {
//initialize components in the vending machine
initCoinDrop();
initButtons();
initSodaDispenser();
initCoinDispenser();

while(1) {
//wait for coin to be dropped
while(!coinDropped());

//wait for button to be pressed
//wait for user to make soda selection
while(!buttonSelected());

//dispense selected soda
sodaDispense();

//release change
coinDispense();
}
}
We might be able to model the typical vending machine as a series of events that must
occur in a certain sequence. First money must be deposited into the machine. This triggers
the vending machine into a state that accepts the users soda selection. The user then must
select his soda of choice. Next the machine dispenses the soda, and the change left over
from the sale.
The problem with this approach is that this sequence of events must occur at exactly the
same order. What if the user inserts one coin, pushes the button to select his soda, then puts
in the rest of his coins. Or the user inserts one coin, and decides he doesnt want to spend
his money and pushes the void sale button to get his money back. We cannot use while
loops to detect all these possible combination of sequences of events and still expect the
vending machine to be responsive and bug free. Somewhere along the line, a while loop
will block, and when that happens, the behavior of the machine will be unexpected. This
style of code might be okay for an alarm clock, but this is NOT okay for a medical device
(imagine a pace maker that blocks). So how do we get around this? The ultimate solution
of course is to use a simple kernel that allows the sharing of the MCU. Now you start to
understand why some people are willing to pay over 100 dollars for an consumer level
operation system, and upwards of tens of thousands in licensing costs for embedded OSs
(although there are free alternatives such as Linux and FreeRTOS). Sharing a single
processor in a system is not the least bit trivial. In addition, a proper system should
maintain flexibility and responsiveness all the while executing the required tasks. However,
Im not about to endorse, adopt or create a kernel for a hobby level project.

The simple solution to something like this is to break down your code to poll rather than
wait. For example, consider the button routine below:
//example button polling
//prototypes, macros
//this defines the actual pin we are polling.
//button is read as 1 if pressed, 0 if not pressed.
#define IOPin iopin

void BtnProcess();
void BtnInit();

//set to 1 if a button Press is detected
int btnPressEvent;

//set to 1 if a button Release is detected
int btnReleaseEvent;

//stores the current state of the button
int btnState;

void main() {
//initialize components
BtnInit();

while(1) {
BtnProcess();

if (btnPressEvent){
//do something
doSomething();
}
}
}

//function initializes registers
void BtnInit() {
int btnPressEvent = 0;
int btnReleaseEvent = 0;
int btnState = 0;
}

//function polls the IO pin
void BtnProcess() {
//reset events
btnPressEvent = 0;
btnReleaseEvent = 0;

//check the previous state, compare to current
if (btnState){
//tests true if a button release has occured
if(!IOPin){
btnReleaseEvent = 1;
return;
}
}else{
//tests true if a button press has occured
if(IOPin){
btnPressEvent = 1;
}
}
btnState = IOPin //store state for next process
}
In this example the button press routine is separated into two parts. The btnProcess()
function processes the input and detects events. The piece of code inside the perpetual
while loop polls for button events. Care still must be taken when writing the doSomething()
function. The task should be short and return quickly. The advantage of this style of
programming is that you can have multiple sense/detect code segments without blocking
conflicts (for example, another routine called adcProcess() and btnProcess() can both be
placed at the top of the while loop and detect separate events).
The next natural step is to take the same principle one step further. Since we only need to
poll the buttons every 5-10 ms or so, we can place the btnProcess() function in an interrupt.
This way, our btnProcess() routine will not execute very often, but often enough to still
detect changes in button events, freeing up processing power.
This type of interrupt driven code is usually more than enough for most hobby level
projects, although I would not use it for any professional projects. There are, however, still
vulnerabilities in our code. Suppose our doSomething() code takes 100 ms to complete
(ridiculously long function). During this time, it is possible to have a button pressed and
released, albeit youll need ninja like reflexes. If we set the interrupt to 10 ms, our
btnProcess() routine would have executed 10 times, during which there would have been a
btnPressEvent as well as a btnReleaseEvent. We would only be able to detect the release
event because the button press event would have been erased from subsequent btnProcess()
calls.

How do you get around this? As you can see, the problems associated with coding and
events are quickly mounting. The next logical step is to create an event queue as well as an
event processor. Each time an event occurs, it is placed in a FIFO queue. When processing
power is freed up, the events are processed. In this manner, all the events are accounted for,
even if we are in the middle of a long routine. This is the beginning of a Run to
Completion kernel (RTC Kernel).
To code this software model is another matter. It is beyond the scope of this article.
However if you wish to learn more about event-driven programming, I highly recommend
Dr. Sameks book Practical UML State Charts In C/C ++, ISBN 9780080569789. In the
book, a simple but full-featured kernel is presented, along with the source code (open
source, available at Quantum Leap).
Happy coding.
PCB Fab Tutorial


A few years ago, I started experimenting with homemade prototype PCBs as an alternative
to professionally fabricated PCBs from board manufacturing company. My company was
flexible enough to give me some resources and time to explore the subject matter. What I
discovered was that with a small initial investment, you can make reasonably high quality
two sided boards. In addition, all the equipment needed was easily accessible. Ive decided
to put my findings into this guide. Hopefully some of my fellow hobbyists will find the
information useful.
1. Intruduction
Why make your own?
2. PCB Basics
Got to understand what you are making, before making it.
3. Preparations
The general who wins the battle makes many calculations in his temple before the battle is
fought. The general who loses makes but few calculations beforehand. -Sun Tzu



01. Introduction

Ever since I started working, I have always been amazed at how fast technology moves.
Being in the technology business certainly exposes you to vulnerabilities of this trend. A
few of the projects of the past have been victims of this greatest and latest arms race. As
a direct result, these projects have failed miserably.
There are other problems with technology, not the least of which is that the state of the art
is always very expensive. I cannot speak for other fields and disciplines, but this is almost
always true of consumer products. The driving factor behind these products is silicon,
because without it, there would be no electronic revolution.
Within the discipline of electronics engineering, itself a sub-discipline of electrical
engineering, one of the major tasks is to connect the silicon. Typically, all electronic
products of today have one or more PCB (printed circuit board) connecting various ICs
(integrated circuits, or chips), resistors, capacitors and other passive components to make a
circuit that performs amazing feats. However, just as with any other field, the state of the
art of PCB can be quite expensive. NASAs 10+ layered PCBs used in satellites usually
costs tens of thousands of dollars for the board alone, and thats NOT including the parts!
Well we call them NASA boards in the industry, because only NASA can afford them; I
dont actually know what goes into a NASA PCB. All joking aside however, PCBs is a
major cost factor during manufacturing. These factors must be considered during the design
phase. For a small company or a hobbyist, the prototyping cost of a PCB is an even greater
financial detriment.

A video processing board I designed, with full PCB specs, $300
A few years ago, I started experimenting with homemade prototype PCBs as an alternative
to professionally fabricated PCBs from board manufacturing. My company was flexible
enough to give me some resources and time to explore the subject matter. What I
discovered was that with a small initial investment, you can make reasonably high quality
two sided boards. In addition, all the equipment needed was easily accessible. Ive decided
to put my findings into this guide. Hopefully some of my fellow hobbyists will find the
information useful.


A DVI to VGA converter circuit, made in the kitchen sink.
A word of advice, if youd rather just shell out a bit of money for your boards, its really
not that expensive for the lower end of the PCB spectrum. Advanced Circuits for example,
sells full featured 2 layer boards for only $33 each. For barebone boards, Ive purchased
them for as little as $11 dollars, with minimum quantities of 5 or more. Sunstone Circuits,
another online based board house sells 2 layer boards for about $28 dollars, with minimum
quantities of 2 (but there are limitations). Ill explain what the terms layers, full
featured and barebone means later in the guide if you are unfamiliar with PCBs, but just
keep in mind that if you are having trouble making the boards yourself, you can always buy
them pretty cheap.
However, the biggest motivation to make the boards at home is the turnaround time.
Usually the cheap economy services at the board houses are 5+ business days, plus
shipping. This means that from the time you submit your design to the time that you get
your board in the nice Fedex package, two weeks could have gone by. I can usually make a
two layered board in 2 hours or less. In the world rapid prototype iterations, it doesnt get
any better than that. This means that if I screwed up the design in the morning, I can cover
my ass by the afternoon. What more reason do you need?
So, without further ado, lets get on with the business of making PCBs. Before you can
make them however, you got to know what printed circuit boards are.
02. PCB Basics

Before we can make a PCB, lets take a look at its composition. If you deal with PCBs all
the time (as in, you do PCB layout all day long), you can probably skip this section.
However, if youre a bit shaky on PCB concepts, its best to at least skim through this
section.
PCBs stands for printed circuit boards. They are called printed because you print your
circuits out onto the copper. With the design printed, you then either mill or etch your prints
into the copper. The general process is actually quite complex, especially with quality
control considerations and efficiency measures implemented in large fabrication houses.
However, the process can be simplified into manageable steps such that home fabrication is
possible. We will also be skipping a ton of steps, just because some of the more
complicated features such as silkscreen and multilayer (more than two layers) is impossible
at home. But I digress, heres what you need to know about the PCB itself.

Circuits are cool, though most of you won't admit it in public.
The surface of the PCB has several features. You will probably notice right away, when
picking up any typical PCB, that the majority of the surface is covered with green stuff.
This is called the soldermask, and it is a dielectric (insulator). It actually has several
specific tasks. First, it is there to prevent corrosion, as the oxygen in our atmosphere is
quite toxic to the copper on the top and bottom layers. Next, it has the job of preventing
accidental shorts from occurring. The exposed copper is very vulnerable to paper clip drops
and loose screws. Best to cover it up with green stuff that wont conduct.

I even labeled everything!
The next thing you will notice are the tiny lines that run across the surface of the board
(albeit covered in green). They are the copper that reside on top of the PCB. This is how
electrical connections are created from one electronic element to another. The term used to
describe these lines is signal trace or just trace; they describe the trace that a copper
takes from one point to another. Next, there are the pads. These are exposed bits of metal
covered in tin (through electroplating). They are exposed so that the pins on your ICs and
your resistors can be soldered onto the board. The tin does not oxidize, but is still
conductive. This property protects the underlying copper, while still allowing an electrical
connection to occur to the component being soldered. A plus side is that the metal tin is a
major component in modern solder, such that the flow of the solder is facilitated by the
tinned pads. Lastly, the colored letters and markings seen on top of the soldermask are
called the silkscreens. They are aptly named since the markings are applied to the
soldermask through a silkscreen process. It is essentially a stencil made with a thin
membrane, onto which colored ink is applied. This layer allows the PCB designer to label
the components, and indicate switches and functionality.
However, there are things underneath the surface that cannot be seen with the naked eye,
but play a key role in the functionality of the board. Below is an example of a 4 layer PCB,
typically very cheap to manufacture.

Inside a PCB
On any given PCB, you can only see the top and bottom copper traces. Underneath
however, there may be many layers of copper creating connections between components.
The cost of a PCB is generally dictated by the number of layer. These layers increase the
number of possible connection options between components by allowing traces to intersect
one another without shorting out. For very dense circuits such as mobile devices, more
layers are need since the number of connection per area is high. On circuits with lower
densities, a lower number of layers is preferred since it reduces manufacturing costs.

The round circle like things that can be seen on the surface of PCBs are called vias.

Vias, a la PCB
These are drilled holes that create the connections between the layers. The holes are
actually drilled after the copper traces are created, and synthesized through copper
electroplating. With a combination of traces and vias, the PCB designer is allowed to create
circuits in three dimensions.
Finally, the layers between the copper (labeled core and prepreg in the above picture)
are FR4 (most of the time). The abbreviation stands for Flame Retardant 4, created out of
fiberglass and resin. These insulators create the structure of the board, and gives it rigidity.
The copper on each of the conductive layers are grown onto the FR4, then etched off in
acid to create the traces. Each stack, consisting of one layer of copper and one layer of FR4,
are then put together on a heated vacuum press, and allowed to meld together into a single
board structure.
The goal of our manufacturing process is much less ambitious. We will be constructing a
two layer board, with no soldermask, no pad tinning and no silkscreens. The process is
usually called barebone since it only contains the bare essentials of a functioning PCB.
As long as the signals pass through, it can be technically called a circuit board.


This one we can make at home
I am always amazed at these marvels of manufacturing technology. I think a lot of people
take for granted the plethora of electronic devices that are used in our daily lives, simply
because there are so many of them. However, the production of a PCB is by no means an
easy task. Next time you type on your keyboard or power up your favorite MP3 player,
please refrain from being impressed by the functionality of the gear. Take a moment and
marvel at the humble PCB that is surely lodged inside their plastic shells.
In the next section we will make preparations for the manufacturing process.
03. Preparations

There are a few basic precautions when working with wet chemistry. If you think back
really hard, long ago, in a galaxy far away chemistry class in high school, Im sure you
are reminded of the basics of lab safety. Always wear safety goggles, and wear protective
gloves. Also dont smell stuff from beakers whose contents you do not know. In fact, just
dont smell beakers, they usually dont smell very good.
Basic Process
The basic process goes something like this. We will be buying the boards with resist
already coated. For the technically uninitiated, a resist is a thin layer of chemical that is
deposited on a substrate (in this case copper) which will mask it from the subsequent steps.
We will then expose the copper using printed transparencies, and develop the exposure
using a commercially available developer solution. Then we mix our own solution and etch.
The result is a kitchen made PCB that should be good enough for most small to medium
sized hobby level projects. It doesnt cost too much either.
Materials
Anyways, you will need some basic equipment to make the PCBs. Heres a shopping list.

Hydrochloric acid 30% available at home improvements stores, sold as masonry
cleaners, and usually labled as muriatic acid. This is basically hydrochloric acid (HCl) at
around 31%. If you have access to HCl, you can mix it yourself. Remember if you are
mixing the HCl yourself, ALWAYS add the acid to the water, not the other way around. The
acid is required to lower the Ph so that the copper will oxidize.

Hydrogen Peroxide 3% we need an oxidizer that will provide the oxygen. H2O2
can be found at the pharmacy, and is sold as ointment to clean newly opened
wounds. You can also buy it off of McMaster in crates.

Sodium bicarbonate baking soda is available at any old grocery store. Good for
putting out fires and neutralizing acids (Ill let you guess which function well be
using it for).
MG Chemicals 600 series this is a commercially available copper board with the
resist already coated, sold by MG chemicals, 600 series. Available at Digikey. You
can pick and choose which size fits the prototype best. I usually get a few single and
double sided boards if I dont know the complexity of the project beforehand. 1.6
mm thickness is the standard size for most enclosures.

MG Chemicals positive developer 418 this is the developer that goes along with
the 600 series boards. As you will see later, the resist that is coated on the board
needs to be developed (like a photo), and this solution will do just that. Available at
Mouser.

UV lamp this is usually not that hard to find. You will need to expose the resist
somehow. Any source of stable and reliable UV is fine. The sun is NOT a stable and
reliable of UV (ever tried getting a tan in the winter?). Try to have a fixture for the
lamp so that it is raised about 5 8 inches from the table. MG Chemicals sells a
convenient little kit if you are short on time/creativity.

Thick piece of glass I cannot stress how important this material is. When we are
going to do the exposure, the transparency will be taped to the PCB. However, we
need to make sure that the transparency is as close to the PCB as possible so that the
shadows are crisp. Get a piece of glass and lay it on the mask, this will sharpen your
shadows and make the etching much easier. Available at McMaster, or just break a
window and grab a piece, preferably not your own (but dont blame me when the
police comes).
Two trays You will need a container to do the developing and etching. Glass do
not react to too many chemicals so they are a good candidate.
Chemical flask or graduated cylinder needed for measuring the solutions and
chemicals, available at McMaster.

Chemical squeeze bottles after mixing the solutions, a good way to dispense
them is through a squeeze bottle, for easy clean up and convenient use, available at
McMaster.
A good printer since PCBs stand for printed circuit boards, you need a printer.
This is where it gets tricky. I started using laser printers, but I discovered that when
using transparencies, the heat tends to shrink the transparencies just slightly. The
result was unreliable footprints and scaling. For this reason, I strongly recommend
inkjet printers. I use a Canon PIXMA 4500 series.

Transparencies inkjet transparencies are different from laser transparencies.
Make sure you get the right one for your printer.
Isopropyl alcohol or any kind of cleaning solution. Acetone would work too, so would
nail polish remover (which is diluted acetone).
Magic marker for correcting errors. I use black.
Preparing Solutions
Two of the three solutions used can be prepared ahead of the time. Unfortunately, the most
critical solution, the acid-oxide, cannot be prepared ahead of time because the oxygen tends
to escape and does not provide enough oxides to scrub off the copper. However the
developer and neutralizer can be prepared and stored for months. Follow the directions
provided by the manufacturer and load the solution into a squeeze bottle (usually something
like add 10 part water to 1 part developer). Next drop two table spoons of baking soda into
hot water. Mix until no visible baking soda is present and load into another squeeze bottle.
This is your neutralizer. Should you spill the acid on the table, in the sink or on your skin,
squeeze the acid with this solution as soon as possible. Dont worry, its just baking soda
(unless you are allergic to baking soda or something, then youre screwed).
Another thing, make sure you label your squeeze bottles. You dont want to be spraying the
acid when you should be neutralizing (something this author knows a thing or two about).
In the next section well take a look at making the mask.
04. Mask Fabrication

Photo-lithography has got to be one of the coolest inventions ever, and mask making is one
of the most important steps in the process. Photo-lithography is the reason why computer
chips are so cheap, and why circuit boards are a dime a dozen. In this section we will look
take a look at how to make an impeccable mask, and be on our way to making our own
printed circuit board.
The Process
In case you are new to the idea of making PCBs, the process is rather simple. Below is a
simplified version of the steps that we will be taking to get a finished PCB.

The simplified process

Since the traces on the copper is of a scale that is printable from an ink-jet printer, we will print a
pattern onto a transparency. Next, we transfer the pattern to a pretreated piece of copper
covered in photo reactive acid resistive chemical. This chemical is commonly called resist. The
mask is placed on top of the PCB and exposed to UV rays. Once the resist is exposed to UV rays, it
changes properties. The areas that are exposed to the UV rays can easily be washed off with a
special solvent. The portions that is not exposed to UV rays will not wash off. This step is call
strip. Once the stripping is completed, the circuit is ready for etching. It is dipped in acid until the
copper is etched off. Remember that some of the resist is still applied onto the copper. The acid
will only attack the areas where the copper is exposed and unprotected by the resist. After about
10 minutes, all the copper will be eaten away and you are left with a circuit. The remaining resist is
then removed and the circuit is ready for assembly.
Quality Print
Because the quality of the pattern transfer directly affect the etching of the board, the
making of the mask and the exposure onto the pretreated PCB is probably the trickiest step
in the whole process. A screw-up during this process will result in a broken trace or
unwanted shorts in the finished product, so pay attention! Every little detail counts.
First, wear some gloves when touching any of the materials. I prefer textured nitrile
because they give the fingers a bit of grip, and they dont stretch that much. Any oil that is
transferred to the mask and the copper will show up as unwanted artifacts on the final
circuit. You know those Intel commercials where the technicians are wearing bunny suits?
Its exactly like that. A clean work environment will give you better looking circuits and
less screw-ups. Itll also protect you from the various chemicals used during the fabrication
process.

Whatever ECAD program you use, all of them will have the option to create a Gerber file.
You can use a free Gerber viewer and then print your pattern, or these days, a lot of ECAD
programs will have the option of being able to conduct scaled prints. Use the HIGHEST
quality setting on your ink-jet and make sure the ink-jet transparency is on the correct side!
Print your mask in black.
In case you are wondering, you really do need an ink-jet. Ive experimented with laser
printers in the past, and the problem is that when the ink gets heated up, the pattern shrinks
by a bit, and unevenly in different direction. This causes the pattern to be transferred
wrong, and your fine pitch IC patterns will not match the copper. Since ink-jets do not heat
the ink, the pattern remains true. I use a Cannon Pixma 4500 series and it is one of the best
cheap printers for this kind of jobs. Also a word on transparencies. You need to get ink-jet
transparencies. Both laser and felt-tip pen transparencies will cause the ink to run. Ink-jet
transparencies have tiny little holes that will trap the ink, resulting in a dry print (rather than
black ink all over the place). These transparencies will have a printing side and a non-
printing side. Make sure you use the correct side. If you are careful with all of these details,
you should end up with a mask for your circuit that looks like this:

Finished product

As you can see, Ive taken the liberty of panelizing my circuit such that I get 16 circuits in one go.
This is common practice, and definitely worth the time if you are making more than one circuit.
Thats it for now, next we expose the mask.

You might also like