APSC1299 Manual - Spring 2019
APSC1299 Manual - Spring 2019
Lab Manual
&
Lecture Notes
1
Table of Contents
Labs
INTRODUCTION ................................................................................................................................................3
REPORT FORMAT .............................................................................................................................................4
LAB 1 SERIAL COMMUNICATION ...............................................................................................................5
LAB 2 DEBUGGING TOOLS .......................................................................................................................... 13
LAB 3 DIGITAL I/O .......................................................................................................................................... 16
LAB 4 TOGGLES & MENUS ........................................................................................................................... 23
LAB 5 OSCILLATOR SPEED & PWM .......................................................................................................... 27
LAB 6 TIMERS .................................................................................................................................................. 33
LAB 7 ANALOG TO DIGITAL CONVERSION WITH A SENSOR........................................................... 37
LAB 8 TRANSDUCERS AND INTERFACING ............................................................................................. 45
LAB 9 INTRODUCTION TO THE LINE FOLLOWING ROBOT ............................................................. 47
LAB 10 FINAL PROJECT ................................................................................................................................ 51
MICROCONTROLLER (MCU) FUNDAMENTALS ........................................................... 53
SOFTWARE ............................................................................................................................... 55
WRITING TO THE PIC MCU USING THE PICKIT 2 OR 3 .............................................. 62
ASCII .......................................................................................................................................... 65
THE SIMULATOR .................................................................................................................... 68
THE OSCILLOSCOPE ............................................................................................................. 75
GRAPHING WITH MS EXCEL .............................................................................................. 83
NOTES I INTRODUCTION ............................................................................................................................. 87
NOTES II C PROGRAMMING ....................................................................................................................... 89
NOTES III NUMBERS ...................................................................................................................................... 96
NOTES IV REVIEW OF ELECTRIC CIRCUITS ....................................................................................... 109
NOTES V DIGITAL INPUT OUTPUT (DIO) .............................................................................................. 123
NOTES VI OSCILLATOR FREQUENCY & DELAYS .............................................................................. 136
NOTES VII PULSE-WIDTH MODULATION (PWM) ............................................................................... 143
NOTES VIII TIMER0 MODULE................................................................................................................... 147
NOTES IX ANALOG TO DIGITAL CONVERSION (ADC) ..................................................................... 154
NOTES X ROBOT ........................................................................................................................................... 163
2
Introduction
The Integrated Circuit (IC) has become pervasive in modern society. Once associated only with
Electronics Engineering, the modern IC has evolved to the point where it has become a standard
tool in all areas of engineering. Every professional engineer needs an understanding of the
capabilities of these devices. In this course you will learn the fundamentals of how a Programmable
Integrated Circuit/Microcontroller Unit (PIC/MCU) works. Working with a high level
programming language, C, you will make a MCU handle a wide variety of tasks including reacting
to external stimuli. The ultimate programming challenge will be to make a robot follow a
challenging and arbitrary course. Along the way you will learn basic electronics, interfacing with
sensors, group task management, professional engineer report writing style, and oral presentation
skills.
3
Report Format
There are no formal reports, such as you are used to from your physics and chemistry courses, for
these labs. Instead you will follow industry standards and keep a diary or journal of your daily
progress. A journal is a rough but thorough record of what you did in the lab – including things
that don’t seem to work out at all! It should indicate what you set out to do and why, what data
you measured, what calculations you made, and what conclusions you drew from the data at that
time. It should be as neat and legible as possible considering the circumstances. There are a few
rules to note:
Use a black hardcover lab notebook. Extra work, such as a graph, is to be glued or stapled
into the notebook.
Write in permanent ink.
Do not erase or whiteout anything. Use a single line through data or even paragraphs to
indicate corrections.
Do not remove pages from the notebook.
Initial and date each page.
Symbols must be defined.
Measurements require units and some indications of estimated uncertainty.
Record the time regularly as you work
While we are using a journal format, the notebook must be readable to you and your lab
instructor. That requires some structure. Each section should have a Goal outline and at the end
should have a Summary of your findings. In between you should
You will be allowed to bring and use your notebook during class tests. Your notebook will only
be as helpful as you make it.
4
Lab 1 Serial Communication
Equipment:
LCD display with SparkFun serial connector, wirestrippers, and wire.
Files:
HelloWorld.c, osc.c, configureUSART.c, osc.h, configureUSART.h
Pin label stuck on PIC MCU common node strip Regulator and
connector board
PICkit 3 Programmer
programmer
Connecting MCU
to VDD and VSS +5 V power rails (beside red line)
& 0 V ground rails (beside blue
line)
DC Adaptor
Figure 1-1: Recommended placement of PIC, battery wires, and PICkit 3 connector
5
Lab 1 Serial Communication
Volts and the rails marked with the ‘–’ sign are grounded (0 Volts). Each short column of five
holes is also connected to its own individual metal strip. See Figure I-2 in Lecture I of the Notes
section of the manual for a schematic.
The red board with the connector for the DC adaptor uses a regulator to reduce the adaptor voltage
to 5 V and to connect the power and ground rails. The PIC MCU needs to be connected to power
and ground on both sides. Refer to the pin out diagram in Appendix A to find the correct pin
numbers for VDD and VSS on each side of the MCU. You have also been given a small pin diagram
that can be stuck on the MCU to identify the pins. Stick it on the MCU making sure that it is the
correct orientation. Create small connecting wires for the connections. Use wire strippers to
remove the plastic covering from the ends of connector wires. The bare ends should be about a
centimetre in length. You may need to practice. Wires should be kept short and neat. Plug the bare
ends firmly into the holes. The wires should be flat to the breadboard. Wire pin VDD to power (+)
and pin VSS to ground (–) on both sides, Figure 1-1. Traditionally, red is used for power
connections and black for ground.
The breadboard has a connector or header for the black PICKit 2 or the red PICkit 3 programmer.
The connector should already be wired to the breadboard. If not, check the wiring diagram in
Appendix C: Writing to the PIC MCU using the PICkit 2 or 3. Note that the programmers should
normally be face up with the logo showing when connected properly. Your chip needs to be getting
power from a battery or other power supply for the device to write to the chip.
Possible Pitfall: The PICkit 2 and PICkit 3 devices are not interchangeable. The
wiring to the PIC is different.
Connect the DC adaptor and watch the display. Note that the SparkFun Splash screen is
automatically displayed before it goes blank. Also note that the LCD has two lines of sixteen
characters each.
6
Lab 1 Serial Communication
In serial communication, information is sent back and forth in series of ones and zeroes. The ones
are represented by a high voltage while the zeroes are at low voltage. To communicate, two
machines have to follow the same rules for the communication, such as whether data bits will be
sent in groups of eight or nine, and at what speed. In serial communication, the speed is called the
baud rate. The baud rate determines how long a high or low bit lasts; the higher the baud rate, the
shorter the time. Baud rates of 2400, 9600, 14 400, etc are typical. Modern USART connectors
can go higher, up to 115 200 and more.
The PIC18F4525 has a single USART module, or built-in circuit, for serial communication. Two
pins, 25 or TX, and 26 or RX, respectively, handle the transmission and reception of serial signals.
These pins are multiplexed with Input-Output (IO) ports RC6 and RC7. Serial Communication is
basically timed Digital Input and Output (DIO), i.e. sequences of ‘on’ and ‘off’ signals that each
last a specific amount of time. Hence pin 25, RC6, must be configured as a digital output to send
on/off signals, and pin 26, RC7, as a digital input to read incoming on/off signals. The library xc.h
contains functions for serial communication. The principle function for using serial
communication is called OpenUSART(). It is somewhat difficult to understand and to discuss right
now, so instead you have been provided with a simpler source file, configureUSART.c, and an
associated header file configureUSART.h. Be sure to include these files any time you plan on
using serial communication. The function configureUSART(), in the configureUSART source file,
ensures the proper configuration of the TX and RX pins and initializes the serial port for the most
common format of serial communication used by the LCD or by a PC running a terminal program
like PuTTY. This function is easy to use; it only needs the communication speed or baudrate and
the operating frequency of the MCU. The SparkFun LCD can operate at different speeds but the
default rate is 9600 bits/second or bps. We will use the familiar printf() function, in the stdio
library, to send text from the PIC MCU to the LCD. For printf() to work, it needs to know how
to pass text to the USART module. In the XC8 Compiler, this is done via a helper function putch()
that is also in the configureUSART files.
An LCD is a display controlled by its own IC. The IC controls which pixels on the display are dark
or not. The IC can communicate with other devices (it wouldn’t be much use otherwise) but it does
not use USART. At the time the LCD was developed, the electronics needed for USART were
both expensive and physically much larger. The makers of the LCD, Hitachi, opted for a much
simpler control method which requires many more wiring connections. Since USART–enabled
chips are now small and cheap, SparkFun developed the serLCD or serial LCD. An extra IC takes
USART signals and translates them into signals that the LCD understands. This requires only one
wire (besides power for the IC and LCD). The RX or receive pin of the SerLCD should be
connected to the TX transmit pin on the PIC18F4525. The power and ground pins can be connected
to the breadboard without any buffering or other worries.
7
Lab 1 Serial Communication
YourNameProjects
Common
osc.c, configureUSART.c, osc.h, configureUSART.h
Lab1
HelloWorld.c
Lab2
Lab3
Make sure that the case (upper and lower letters) of the names is exactly the same as listed
above. Rename the files if necessary.
Open the MPLAB program. Use the MPLAB icon on the desktop or find it in the Start|Programs
list. Follow the steps outlined in Appendix B to properly use Lab1 as your project directory and
name your project files. Your output files for this lab should end up in your Lab1 directory. Build
your project. With some luck, you will have had a successful build. If not, ask for assistance.
Your source files are HelloWorld.c, osc.c, and configureUSART.c. Open and inspect the source
files. Note the inclusion of the header files we have given you, osc.h and configureUSART.h,
which contain prototypes for the functions in osc.c and configureUSART.c. Inspect these files as
well. The file HelloWorld.c contains the only main() function in this project, as required. The
main() function has an endless while loop to keep the function running forever on this chip. There
are two types of header files used in the HelloWorld.c file. These are listed at the top of each source
file and are marked for inclusion by the #include directive. Header files in chevrons, <…>, indicate
use of the XC8 compiler’s own libraries of functions. The source files that these headers point to
are already compiled and you do not need to include them in your project source file list because
the compiler looks for them in the default XC8 folder. Header files in double quotes, “…”, are
header files for functions you will write or for functions and macros that we provide in this course.
Include these header files in your project by right clicking on Header Files in the project and
choosing add existing item. Do the same with Source Files. Your project files are in the Lab1
directory, while the functions we need to use are in the Common directory. In Appendix B, you are
given directions on how to make sure that Common is an Included directory. As long as this has
been done, the XC8 compile should be able to find all your files. If you get a message about one
of the header or source files not being found, this is probably the issue.
8
Lab 1 Serial Communication
Similarly, we will be using functions from the XC8 peripheral library. That library needs to be
linked to your project. Again, in Appendix B, there are instruction on how to do this. If you get
error messages that the compiler doesn’t understand a function, this is the probable cause.
Modify HelloWorld.c by commenting out the while(1) loop. Build the project and program the
MCU. Observe on the LCD the differences with the previous project.
Undo the previous changes before continuing, i.e. put the while(1) loop back in.
In the MPLAB IDE, create a new file. Cut and paste the lines
to the new file. Save the file as configuration_bits.h in your Common folder. Place an
#include directive to configuration_bits.h in HelloWorld.c. Add configuration_bits.h to your
project header files. Rebuild HelloWorld.c to check that everything was done correctly.
Include configuration_bits.h into the project once only at the top of the file containing main()!
9
Lab 1 Serial Communication
cycle; 1 TCY = 4TOSC = 4/fOSC. So for a PIC running at 32 MHz, TCY = 4/(32 MHz) = 1.25 × 10-7
seconds = 125 ns.
The XC8 Compiler has a set of library functions that will delay the MCU, i.e. cause it to do nothing,
for an exact number of instruction cycles TCY. The functions are void NOP(void) which gives a
1 instruction cycle delay, void _delay(unsigned long cycles) which gives a 1×cycles
instruction cycle delay, and _delay3(unsigned char unit) which gives a 3×unit instruction
cycle delay. Cycles in _delay() cannot be 0 or exceed 179200. If you exceed the maximum, the
compiler will give an error message. Note that the variable unit in _delay3() is of unsigned char
type, and can only be from 0 to 255. If you use a value of unit greater than 256, rollover occurs.
As well, zero will not give you a delay of 0 TCY (since any operations takes at least one instruction
cycle), instead it gives you 3 × 256 = 768 TCY.
There are also two functions that work in fractions of a second instead of TCY. The functions
__delay_us(float x) and __delay_ms(float x) actually count instruction cycles like
_delay(), but do a conversion to get the number of TCY to give the correct delay in microseconds
or milliseconds. These functions need to know the conversion factor, which is just the operating
frequency of the PIC MCU. Simply include the following definition to adjust for your operating
frequency.
Since the smallest time unit is 1 TCY, the smallest value of x in these functions must convert to a
minimum of 1 TCY. Similarly, the largest value of x cannot give a time interval greater than 179200
TCY. A value of x that is too large will generate an error at compilation
The parameter in quotes above is called the formatting string. The formatting string may contain
the basic formatting specifiers, such as %s for strings of characters, %c for single characters, %i for
integers, %u for unsigned integers, %x for integers displayed in hexadecimal, and %f for floating-
point numbers. These formatting specifiers indicate the format of the displayed variables. The […]
indicates that variables are optional; you could just print a string printf(“My message.”). Note
that printf(), like most C functions, does very little error checking. If the number of formatting
specifiers and variables don’t match, you will not receive an error message, and your program will
exhibit strange behaviour.
Computers like the PIC MCU only work with on’s and off’s. We know that we can treat these on’s
and off’s as the 1s and 0s of logic and binary numbers. No such simple relation applies to the
letters of the alphabet and other useful symbols. Instead, computers store symbols like ‘A’, ‘#’,
and ‘d’ as 8-bit (one byte) numbers. These numbers are then associated with the symbols of the
ASCII character set via a look-up table. The ASCII table is given in Appendix D. Functions like
10
Lab 1 Serial Communication
printf() tell displays to use the symbol at the corresponding table number. Since a char is byte-
sized, only 28 characters are available, some of which are nonprinting characters like TAB or
CARRIAGE RETURN (CR). Double quotes in C programming indicate a string of text symbols
that a computer should store as a string of bytes. Single quotes indicate a single character to be
stored as a single byte. For example, printf(“%c”,’A’) will print the capital letter A on the
display, but so will printf(“%c”,65), since decimal 65 is the number corresponding to the
symbol A in the ASCII table. An oddity that arises because of this is ASCII math. The statement
printf(“%c”,’#’+’A’) prints the character ‘d’. This happens because ‘#’ is represented by the
number 35, and ‘A’by the number 65. Since 35 + 65 = 100, the symbol ‘d’, ASCII character 100,
is printed.
Any number shown in a display is actually the string of characters for that number. For example
printf(“%i”,125) actually parses the number 125 into three symbols ‘1’, ‘2’, and ‘5’ that it asks
the display to show. Similarly, printf(“%x”,125) displays the symbols for 125 in base 16, i.e.
‘7d’.
Special Commands
The SerLCD IC interprets the ASCII character decimal 254, or hexadecimal 0xFE, as a command
character. Anytime you print this character to the SerLCD, the translator IC will interpret the next
one character, and only that one character, as a command. Some of the non-printing ASCII
characters 0 to 31 are also used as commands. The SparkFun datasheet uses notation like
<control>m, where m may be various symbols, to refer to these non-printing characters. Table
D-2 in Appendix D lets you translate this notation into the equivalent decimal or hexadecimal
value. Some common useful commands are listed in the table in Section 3.3 of the SparkFun
SerLCD datasheet. For example, the small snippet of code below, in Figure 1-3, shows how to
clear the display and move to the start of the display, turn the underline cursor on, move to the
fifth position on the bottom line of the display, and move to the 8th position of the top line. Note
that the character columns are numbered 0 to 15 on each line.
11
Lab 1 Serial Communication
The LCD doesn’t actually use all the standard ASCII characters. Watch the output and a non-
ASCII character eventually shows up. What is the character and what is its decimal value?
Modify your previous version of ascii.c such that the ASCII characters from 33 to 123 are each
printed on the bottom line at column 3, but include the decimal and hexadecimal values of the
character, you may use
where i is the loop variable. Again, there should be exactly a 0.20 second delay between
characters being displayed and a one second wait at the end of the for loop. Observe the output
on the LCD.
Make a note of the files and the lines of code needed to use the LCD or any other display. You
will need to use this code in later labs, in assignments, and on tests.
The commands given in Figure 1-3 and the SparkFun SerLCD datasheet are not obvious.
Create a source file and header file with functions that have easy to understand names. For
example, create a function LCD_ClearDisplay() that contains the single instruction
printf(“%c%c”,0xFE,0x01). Do not include set_osc_32MHz() in that function;
set_osc_32MHz() is used even if you don’t use the LCD. See the SparkFun SerLCD datasheet
for all available functions.
You will frequently need to use these and other functions in later labs, so make a library
consisting of serLCD.c and serLCD.h and place them in your Common directory. In future
labs, your lab instructor will not accept projects using the LCD that do not use your serLCD
library. Make sure your serLCD library functions work! In class, you will be asked to hand in
a copy of your library as an assignment.
12
Lab 2 Debugging Tools
Prelab:
Read the section of Appendix E on the simulator.
Files:
binaryformat.c, breakpoints.c, Debug.zip
Note:
We expect you to use your standard configuration_bits.h header and your serLCD library from
Lab 1 from now on.
Many C compilers have a format specifier %b to display integers of type char, int, and long as
binary numbers. For example, the code snippet shows how it works,
Output:
15 in binary format 0b00001111
Unfortunately, this is not true of the XC8 Compiler. We will sometimes need to display a number
in binary format, so we need to write our own functions. The function printCharAsBinary(unsigned
char number) in the provided file binaryformat.c shows how to do this.
Hint: What is the main difference between data type char and data type int? What will your
printIntAsBinary() function need to do differently than printCharAsBinary() did?
Create a binaryformat library (i.e. source and header files) and place them with your other
Common files.
13
Lab 2 Debugging Tools
The IDE has a debugging feature called the simulator that allows you to step through lines of
code and observe how variables change. In the simulator, you can also open a display
window to observe printing output. Read Appendix E on how to invoke (a) the simulator and (b)
the UART display window, then make the necessary changes by selecting File|Project
Properties|Simulator from the menu bar or by clicking on the Set Project Configuration icon.
Run a project from Lab 1 in the simulator and observe the output on the UART display.
Note that any special code for controlling the serLCD is inappropriate for the display window
and might produce garbage symbols. Furthermore, all the output tries to print out on the same
line. You can have the output print to the start of a new line by using newline and carriage return
formatting specifiers in your print statement, or by simply adding the line
printf(“\n\r”);
Debug the Main Project and use the watch window to view the bit operations of Section A.
Step slowly through each calculation and note the value of variables a, b, and c in binary in the
Watch window. (You can see the values of the variables in decimal, hex, and octal, but binary
operations make the most sense when the values are shown in binary.) When you stop at a
breakpoint, remember that line of code has not yet executed.
Recall the logic operation truth tables for & (AND), | (OR), and ^ (XOR – exclusive or). The
NOT operator, ~, flips bits so ~1 = 0 and ~0 = 1. Are the results for the value c consistent with
these operations?
& 0 1 | 0 1 ^ 0 1
0 0 0 0 0 1 0 0 1
1 0 1 1 1 1 1 1 0
Figure 2-1: Truth tables.
14
Lab 2 Debugging Tools
We can use the Stopwatch window to observe the number of instruction cycles (TCY) between
two successive breakpoints. Use the Stopwatch window to record the number of instruction
cycles for the addition of two numbers, first char, and then int. Which takes longer and why?
Use the Stopwatch window to time each delay function and the NOP() function. Does the
number of instruction cycles match the function call? Why or why not?
Most errors are caught by the IDE and a message is given in the output window, which often points
out on which line the error occurred. For that reason, make sure to turn on the display of line
numbers for each C file. Some of the messages are very cryptic and you need to refer to the manual
to figure out what the message means. Use the listed error and warning message explanations in
Appendix C of the MPLAB XC8 Compiler User’s Guide to debug the following projects.
There are a number of project folders in the file Debug.zip. Unzip the directory and place it on the
same level as your Common, Lab 1, and Lab 2 folders.
YourNameProjects
Common
Lab1
Lab2
Debug
Load each DebugN.X from inside the IDE using File|Open Project. Choose the appropriate
programmer, PICkit2 or PICkit 3. An explanation of what each program is supposed to do is
given at the start of the .c file of the same name. If there are function source and header files
in the folder, they must be added from the Common folder. Build and run the file and observe
the message produced in the output window. If there is an error or warning message, what do
you need to do to fix the projects? Do not significantly change the code.
Misbehaving Programs
Some projects compile without error messages but the program does not behave as desired. An
explanation of what each program is supposed to do is given at the start of the .c file of the same
name.
For each Debug project, use the simulator and watch how important variables change from
breakpoint to breakpoint. Check, using Stopwatch, that the timing is exactly as described at
the start of the source file. Find and fix each problem where the behavior does not match the
description. Do not significantly change the code. You are not supposed to rewrite the code.
Make only small changes to get the program to operate correctly. Note which changes are
needed and why.
15
Lab 3 DIO
Each short column of five holes, called the ‘internal strip’ or ‘rung’ or ‘tie down’, on the
breadboard is connected to its own individual metal strip. See Figure I-2 in Lecture I of the Notes
for a schematic. Figure 3-1 shows a typical circuit of a resistor and LED in series with the power
supply, and how to use the breadboard blocks to make the connection. In the lower part of Figure
3-1, conventional current flows from the +5V strip, through the connector wire, to the first
common block. The resistor makes a path from the first common block to the next. Finally, the
LED provides a path from the second common block to ground to complete the circuit.
+5 V 200 LED
(power
supply)
+5 V +5 V
connector
wire
16
Lab 3 DIO
Only one end of a resistor, or LED, or other circuit element, can be connected to a particular
internal strip. The other must be connected to a different internal strip or to the bus (power/ground)
rails. A circuit element with both ends connected to the same internal strip has been short circuited.
No current flows through it. The internal strips act as nodes or junctions that different circuit
elements can share.
Building simple circuits with series and parallel elements on a breadboard is a basic skill to be
learned. The ends of each circuit element (e.g. a resistor or LED) are connected to two different
strips or nodes. Connector wires can be used to create series and parallel arrangements. Connector
wires should only be as long as necessary. This keeps the circuit from being cluttered and hard to
trace. To gain some familiarity with building circuits and light emitting diodes (LEDs), we will
build the circuits given in Table 3-1 below.
The current through a resistor can be determined from Ohm’s Law I = V/R. Note that LEDs
do not obey Ohm’s Law, as they do not have a fixed resistance (as you will see from your
measurements.) To determine the current through the LED, first determine the current through
the resistors in the circuit and then use Conservation of Charge. Calculate the power P =
ILEDVLED used by the LED. Add columns for LED current and power to your table above.
Table 3-1: Resistor & LED combinations
One 200- resistor and LED in series
Two 200- resistors in series followed by an LED in series
Two 200- resistors in parallel followed by an LED in series
One 10-K resistor followed by an LED in series
One 200- resistor and LED in series but the LED is reverse biased. Note
voltmeters can give faulty readings across reverse-biased diodes since
they act like they have extremely large resistance.
17
Lab 3 DIO
We have stated that forward-biased LEDs can be treated as a 2 Volt drop. Is that always
reasonable? We have also said that the resistance of the LED circuit controls the brightness of
the LED. Is that a correct statement?
Possible Pitfall: Check the polarity of your LED (use the information provided in
the appendix). If it does not light, then you may have it reversed.
Grief Saver: Until you are experienced, remember to check the pattern of the
connecting bars inside the breadboard. See Figure I-2 in Lecture I of the Notes
section of the manual for a schematic.
Warning: Turn off the power to the board when making circuit changes. That way,
you avoid shocks and damaging the equipment.
Warning: If any element gets hot and or smoke rises, disconnect the power! You
probably shorted something.
Remember to make note of all your observations in your notebook; also include notes when things
do not work.
You will now use your PIC to control an LED, making it turn on and off. You do this in your
program by declaring a particular pin to be a digital output. That is, make the pin act as a simple
power supply, and turn the output pin on or off. Most pins on the P18F4525 can be used for DIO
– Digital Input and Output – operations. We will discuss Digital Input in another lab. The pins are
grouped in groups of eight pins known as ports. The ports are called A, B, C, and D. The first pin
of Port A is called RA0. The second pin of Port B is RB1. The last pin of Port D is RD7, and so
on. A glance at the pin-out diagram in Appendix A will show which pin belongs to which port. The
input or output status of each port is controlled by special function registers (SFR), which are a
byte of special memory of the PIC MCU called TRISA, TRISB, TRISC, and TRISD. C variables
with the same names, TRISA, TRISB, TRISC, and TRISD, are defined in xc.h to access these
ports. Each variable is an unsigned char, 8 bits long, and each bit controls a particular pin. The
input and output status of each pin of Port D can be set by the command
If we wish to set or clear individual bits by themselves, we can. A bit-field variable has been
defined in xc.h for each port and its eight pins for this purpose. The name of each bit-field is in
18
Lab 3 DIO
the form TRISXbits where X is A, B, C, or D. Each bit in the bitfield is named in the form RXn
where X is A, B, C, or D and n runs from zero to seven. The member operator (a period ".") is
used to make an expression that combines the bit-field variable name and the member name. For
example
When ports are configured as outputs – that is, when the pins can be set to high or low voltage – a
second set of SFRs – PORTA, PORTB, PORTC, and PORTD – are used to set (turn on) or clear
(turn off) the output signal, as shown in the next code fragment.
Again, a bitfield for each port and its eight pins has been defined in xc.h so that each pin may be
set individually. The names of the bitfields are PORTAbits, PORTBbits, PORTCbits, and
PORTDbits. Each bit in the bitfield is addressed by the expression PORTXbits.RXn where X is
A, B, C, or D and n runs from zero to seven. For example
19
Lab 3 DIO
Possible Pitfall: The location of the PIC pins on the circuit schematic does not
correlate to the physical location of the pins. Use the pin-out diagram provided
(or check the data sheet) for the physical location of the pins.
Grief Saver: leaving your wires longer than needed can lead to a rat’s nest
forming on your breadboard. Once you are sure that the circuit is working
properly, trim your wires relatively flush to the breadboard. This makes your
circuit easier to debug and it is less likely for a wire to be accidentally pulled out.
You have been provided with C source code (Blink_RD2.c) that will cause an LED connected
to RD2 to blink on and off. Make a project with this code and program the PIC MCU. Observe
the behaviour of the LED.
Open the simulator and open a Watch window. Add the TRISD and PORTD SFRs to the Watch
window. It is best to display them in binary. Add breakpoints to the source file so that you can
observe how the values of the SFRs change as code executes.
Now you will add user control to your circuit by means of a pushbutton switch connected to the
PIC. As long as the pushbutton is pressed, we want the PIC to light up an attached LED. The PIC
pin that the pushbutton is attached to must be configured as a digital input and the LED pin must
be configured as an output. We will use RD1 as the input from the switch and RD2 as the output.
Refer to the PIC pin-out schematic in Appendix A to determine the pin numbers (record this kind
of information in your notebook). The appropriate configuration code is
Digital hardware within the PIC will change the value of PORTDbits.RD2 in response to the input
voltage level. The value of PORTDbits.RD1 will thus automatically become 0 if the pin is pulled
low (VSS, ground or ‘zero’ volts) and automatically becomes 1 when the pin is pulled high (VDD,
5V). You need to add code to the while(1) loop to check the value of RD1 and respond in the
desired fashion:
Alternatively, you can streamline your code by using the variable value as a Boolean directly:
20
Lab 3 DIO
A small file Control_RD2.c is provided, but wiring the actual pushbutton to the PIC is more
complicated than the code. There may be 4 pins on the switch you use. When the switch is
depressed (closed) all 4 pins are electrically connected. When the switch is open, two pairs of pins
each remain connected. This is illustrated in Figure 3-4. Use a multimeter to investigate your
switch before attempting to build the circuit shown in Figure 3-5.
21
Lab 3 DIO
Wire your switch to the PIC according to the schematic shown in Figure 3-5 and connect your
LCD if it is not already connected. Use the DMM to check that the voltage at the input is indeed
zero when the button is not pressed and +5 V when it is pressed.
In Lab 2, we used the Simulator to investigate how internal variables changed from one part of the
program to the next. Since it was a simulation, the program was running virtually on your PC, not
on the MCU. So while the Simulator is great for debugging and tracing the behaviour of a program,
it cannot tell us how the program will respond to an external stimulus like a button press.
The PICkit 2 and 3 have a feature, In-Circuit-Debugging (ICD), that runs, halts and single-steps
the program while the PIC microcontroller is actually embedded in the application. When halted
at a breakpoint, the file registers can be examined and modified. For instance, this lets you see if
your button press actually has the intended consequence. You can install up to 3 breakpoints for
the PIC18F4525. The in-circuit debug implementation on PIC microcontrollers will halt execution
on the instruction after the breakpoint instruction. This means the breakpoint instruction will have
executed when the debugger halts. This is referred to as "breakpoint skidding". You may need to
add one or more NOP() functions (technically they are macros but they are written to look like
regular functions) after your desired breakpoints to stop near where you actually want to stop.
Add a breakpoint inside the while(1) loop. Again, you may need several NOP() functions. Add
a watch window that contains the PORTD SFR displayed in binary. It can be expanded to show
the individual pins, RD0 to RD7, by clicking on the + sign beside PORTD. Select Debug Main
Project and build and load the program. Observe the PORTD SFR as you step through the
project with and without your finger on the button.
Double click on the binary value of PORTD with your finger off the button. Change bit 2
(RD2) from 0 to 1 and observe the results.
Rewriting a string to the LCD every time the while(1) loop executes is inefficient. It wastes time,
and, if done with clear display commands, results in a faded display.
Modify your Control_RD2.c program to work with the LCD. When the switch is depressed,
the LED should light up and the LCD should display “Button #1 – ON”. When the switch is
not pressed, the LED should be off and the LCD should display “Button #1 – OFF”. If your
display is not the same darkness in both cases, explain why. To reiterate, the LCD display
should only update when necessary.
22
Lab 4 Toggles & Menus
Files:
buttons.h, buttons.c, softToggle.c, alterBlinkRate.c
The switch you have installed in the previous lab was a pushbutton switch. When depressed, the
contacts connect and the pins are all connected. Once released, the contacts open and no connection
is made. You are likely familiar with pushbutton mechanical toggle switches, where a single button
push turns the switch on (closes) and it latches on until you press the switch again and it releases
(opens). This is called a toggle switch because each action ‘toggles’ the state between open and
closed.
To make the software respond to a pushbutton switch toggle, i.e. a software-toggle or soft-toggle,
you must have a variable that will change from 0 to 1 when the switch is first pressed, and then
flip values for each new press of the button. A new button press is found by detecting a rising
voltage at the input pin. A function to do this, called monitor_switch1_for_edges(), has been
written for you and placed in buttons.c. Examine the logic in the incomplete code sample,
softToggle.c, below:
#include “buttons.h"
int main(void)
{
unsigned char has_switch1_changed = 0;
TRISDbits.RD1 = 1; // set RD1 as input (switch 1)
while(1)
{
has_switch1_changed = monitor_switch1_for_edges(PORTDbits.RD1);
// most of the time has_switch1_changed is 0
if ( has_switch1_changed == 1 ) // rising edge
{
//code here
}
if ( has_switch1_changed == 2 ) // falling edge;
{
//code here
}
}
}
23
Lab 4 Toggles & Menus
Note that you only have to insert code at the appropriate place to have a button or any other digital
input do useful actions.
Modify the above “action” function in the program so that a single press of the button will turn
on an LED while the next press of the button will turn the LED off. This is most easily
accomplished by checking if the LED pin is already on. If it is on, turn it off, else turn it off.
Also add in all the missing #include files to run at 32 MHz. Have the program update the LCD
as well. Make sure the LCD does not refresh unless it has to.
Examine buttons.h and buttons.c. Modify them so you can have a second switch (switch2).
You only need duplicate the existing functions and change the labels on them from 1 to 2.
Wire a second switch and second LED in the same manner as the first switch and LED. Your
schematic should look similar to Figure 4-1. You may choose to use different pins for your
convenience, but you should avoid Port A and B pins for now (see the very end of Notes V).
Each toggle separately controls its own LED.
VDD VDD
Button Button
switch switch PIC18F4525
B1 B2 200 25
TX LCD
RD0
200
200 RD3
RD1 200
RD0
RD2 LED
10 k 10 k
LED
VS VS VSS VSS
S S
24
Lab 4 Toggles & Menus
To blink an LED, you turn the output high for a given length of time using a delay function, then
turn it off for another length of time. The total time is the period of the blink. The ratio of the ‘on’
time to the period is called the duty cycle and is often given as a percent. The blink will repeat if
you are inside the while loop and make no other changes, as in the code snippet below.
while(1){
PORTXbits.RXn = 1; // pin on
// some delay here
PORTXbits.RXn = 0; // pin off
// some delay here
}
We want to be able to choose different blink rates by pushing a button. You will need to monitor
the button each time the while(1) loop repeats and increment a counter that controls the blink rate.
We can use a switch statement to move between the different blink rates, as shown in
alterBlinkRate.c. Note that if the total time to blink an LED is long, you don’t get to the
monitor_switch1_for_changes() function very often and the button will seem unresponsive.
#include "buttons.h"
int main(void)
{
unsigned char blinkrate = 0; // 4 rates, 0 to 3
unsigned char has_switch1_changed = 0;
TRISDbits.RD1 = 1; // set RD1 as input (switch 1)
while(1){
has_switch1_changed = monitor_switch1_for_edges(PORTDbits.RD1);
//happens every cycle of while loop
if (has_switch1_changed == 1)
{
//anything that should happen occasionally should be in here
blinkrate++; //increment counter
if (blinkrate > 3) blinkrate = 0; //cycle back to 0
}
25
Lab 4 Toggles & Menus
Modify alterBlinkRate.c to work properly and to have four rates of blinking; off, slow,
moderate, and full on. For slow blinking, use a period of 0.240 s and a duty cycle of 33%, and
for moderate, use a period of 0.080 s and a duty cycle of 66%. The LCD should display OFF,
SLOW, MODERATE, and ON. It should only be refreshed when necessary, i.e. on the button
press. Have the second toggle change the behaviour of the LEDs according to Table 4-1.
With two buttons you can also have the option of using the near-simultaneous pressing of both
buttons to perform a third action. One way to achieve this is the following:
Add logic to your program so that pressing both buttons at the same time will flip the polarity
of the output pin signals, i.e. so LED 2 will be off when LED 1 is on for the same amount of
time, and vice versa. Pressing both buttons again should return to synchronous blinking.
Have the LCD display the word Synchronous or Alternating on the bottom line, as appropriate.
The LCD should only change when necessary.
Note: We have now built a modestly complicated program in a series of small steps.
Small steps are much easier to write and to understand and to debug. Keep this in mind
when you work on later projects. Always break your development process down into a
series of small steps. You will become less frustrated trying to fix code and be more
efficient and productive. Lab time during the group project can be used more efficiently
by splitting large jobs up into smaller ones, with each partner taking their share.
26
Lab 5 Oscillator Speed & PWM
Equipment:
Oscilloscope, 2 sets of probes, two-way bi-colour LED, your flashing LED program from last
week’s lab, speaker
Prelab:
Read the appendix in the manual on the Oscilloscope.
Read Section 2.7 in the PIC18F4525 datasheet on oscillator switching and the Oscillator
Control Register (OSCCON). Read Section 2.6.4 in the PIC18F4525 datasheet on using PLL.
Also examine the Oscillator Tuning Register (OSCTUN).
Read section 15.4 on PWM in the PIC18F4525 datasheet.
Files:
Example code: osc.h, osc.c, pwm.c
0
T t
27
Lab 5 Oscillator Speed & PWM
Pin 14 only has the CLK0 functionality if it is configured that way in your program. You must
have included the following directive:
Once configured this way, you cannot program pin 14 to be a DIO pin or anything else.
Check your configuration_bits.h file to make sure that this line is present.
Set your oscilloscope to its default settings as outlined in Appendix F. The probes have small
alligator clips to facilitate connecting the oscilloscope to the breadboard. Adjust the time
control until you can see several complete square waves.
Use the cursors to measure the voltage, the frequency, and the period T of the pulse or square
wave.
Also measure the ‘on’ cycle of the square wave and compute the duty cycle. Duty cycles are
the ratio of the ‘on’ period to the complete period and are often expressed as a percentage.
Last lab, you created a program to blink LEDs at different rates. The signal you sent to the LED
was a square wave. You are going to use the oscilloscope to measure that square wave. Since
oscilloscopes work best with fast signals, change last week’s project so that the period for the
blinking is a thousand times faster. For example, if the blinking period was 0.240 s, change it to
0.240 ms.
Connect the pin for LED1 to CH1 of the oscilloscope and likewise have the pin for LED2 read
by CH2. Separate the two signals vertically so that you can see them clearly. Measure the
voltage, frequency, period, and duty cycle of each square wave output.
Grief-saver: If your duty cycles on SLOW and MODERATE no longer look like 33% and
66%, use the Stopwatch to determine what is altering your code’s timing at these speeds
Unlike the CPU in a desktop computer, the PIC MCU can work at various speeds. Low speeds
reduce the power used by the MCU. Often, the PIC MCU runs off batteries and is run at low speeds
to prolong battery life. Laptop computers often vary their speed for the same reason. The speed of
the chip is controlled by two registers, register 2.2 OSCCON, the Oscillator Control Register, and
register 2.1 OSCTUNE, the Oscillator Tuning Register.
28
Lab 5 Oscillator Speed & PWM
Registers are special reserved portions of the PIC MCU’s memory whose settings determine how
the particular feature will operate. Registers only have 8 setting bits, which can be either on (1) or
off (0), and thus are only 8 bits wide. All the bits of the OSCCON and OSCTUN registers can be
read or written (i.e. changed by you) but some registers have bits that are read-only. The notation
in the datasheet for each individual bit is registername<bitnumber> so OSCCON<6> refers to the
6th bit of the Oscillator Control Register and OSCCON<6:4> refers to bits 6, 5, and 4. For extra
convenience, the individual bits of a register have names. For instance, OSCCON<6> is called
IRCF2 in the datasheet under Register 2-2 on page 30. In the XC8 compiler, you set (turn on) the
value of a register bit by the code registername+bits.bitname = 1. For example, to set
OSCCON<6>, type OSCCONbits.IRCF2 = 1. If you want to clear (turn off) the register bit, you
type OSCCONbits.IRCF2 = 0. The header file xc.h tells the processor how to interpret your
command to set the proper area of memory to the proper state.
The header file osc.h contains a prototype for the function set_osc_32MHz() to set the processor
speed to 8 MHz by setting the appropriate bits of the OSCCON register. The function itself is in
the osc.c source file. The function also sets PLLEN, bit 6 of the OSCTUNE register, which further
increases the oscillator speed by a factor of four. As a result, the PIC MCU will now operate at 32
MHz. Note that the PLLEN only increases the frequency if the oscillator speed is 4 MHz or 8
MHz. At lower processor speeds, it has no effect.
Since osc.h is not a XC8 header file, you must tell the compiler to use it. In your code you need a
#include directive. Use quotes rather than brackets, i.e. “osc.h” rather than <osc.h>. Chevron-
brackets indicate that the header file is in the XC8 compiler directory. Also, in the project window,
you must add the file to header files.
Add functions to make the processor operate at 8 Mhz, 1 Mhz, and 500 kHz. They should be
named consistently with set_osc_32MHz().
Run the processor at these different speeds and use the oscilloscope to confirm that the chip
frequency changes.
Note: You must make sure that you are communicating with the LCD at the correct
speed for each clock frequency. The supplied configureUSART() function should
make this easy to do.
Note: You should run your programs at the highest speed of the processor for the
rest of the course.
An LED or a DC motor only has two states: on – if the applied voltage is great enough, and off –
if it is not. You could control the brightness or motor speed by putting a variable resistor in series
with the LED or Motor. When the resistance is low, the current is high and the LED is bright and
the motor spins fast. When the resistance is high, the current is low and the LED is dim and the
motor spins slowly. This is not a very efficient approach because energy is wasted in heating the
29
Lab 5 Oscillator Speed & PWM
resistor. An alternate, much more efficient, approach is to vary the LED brightness or the motor
speed using a square wave. Provided the period of the square wave is short enough, your eye
cannot register the LED going on and off. You see a brightness level controlled by how much of
the period the light is on. If the duty cycle is large, the LED is bright, and if the duty cycle is low,
the LED is dim. Similarly with a DC motor, the duty cycle lets you vary the speed of the motor.
During the duty cycle, the motor spins up to top speed, and after the duty cycle, slows down. For
short pulse periods, the average speed of the motor is controlled by the duty cycle. If the duty cycle
is large, the motor spins quickly and if the duty cycle is low, the motor rotates slowly. Controlling
a DC device in this manner is called Pulse Width Modulation (PWM), since an on-off square wave
is a pulse and you are modulating or controlling the width of the on portion of the pulse.
PWM is a common chore of embedded systems. As a result, many MCU PICs have built-in
modules for just that purpose. The PIC18F4525 has two PWM modules. One advantage of
dedicated PWM modules is that you do not need to continuously monitor PWM, so you can do
other processing chores at the same time.
PWM is controlled by the CCP1CON, CCP2CON, and T2CON control registers and the PR2,
CCPR1L, and CCPR2L data registers. The time base of the period and duty cycle is set by the
Timer2 Module. The properties of the Timer2 module are controlled by T2CON. The PWM signal
controlled by CCP1CON originates at the CCP1 pin, pin 17. PWM is discussed in Section 15.4 of
the datasheet. The steps needed to activate PWM are listed in Section 15.4.4.
Clearly setting all the required registers could be confusing and error prone. To assist in using
PWM, the XC8 Compiler has a set of three functions; ClosePWM1(), OpenPWM1(unsigned char
period), and SetDCPWM1(unsigned int dutycycle). OpenPWM1(unsigned char period)
takes period, a number from 0 to 255, and sets the actual period TPWM by the following formula:
If dutycycle is a number greater than 1023, you will get unintended results! Also, if TDC ≥ TPWM,
the signal is always on. ClosePWM1() turns off the PWM signal. The prescale value of Timer2
used in the above formulas can be set using the OpenTimer2(unsigned char config) function
from the timers library. The only defined names of consequence for PWM in this function are
T2_PS_1_1, T2_PS_1_4, and T2_PS_1_16 for the prescaler values of 1, 4, and 16. Only one
prescaler value can be used at a time.
The example program pwm.c shows the steps to get an 18% duty cycle and a PWM period of 0.4
ms on a chip with a processor speed of 32 MHz.
Note that the second PWM module uses CCP2 (Pin 16). Both PWM modules operate at the same
period, but the duty cycle for each can have independent values. Also, each on cycle will start at
the same instant even though they can last for different amounts of time.
30
Lab 5 Oscillator Speed & PWM
Compile and load pwm.c. Connect the two-way LED between CCP1 and RB0 (not forgetting
to use an appropriate buffer resistor). Make note of the colour you see and the relative
brightness of the LED.
Now switch the two-way LED to connect CCP1 and RB1 (again with an appropriate buffer
resistor). Again make note of the colour you see and the relative brightness of the LED
We can understand the behaviour of the two cases by examining Figure 5-2. On the left hand side,
the IO pin is low and conventional current will flow from CCP1 to the IO pin when CCP1 is high,
that is during the duty cycle. This will give you one colour of the bi-colour LED. On the right hand
side, the IO pin is high and conventional current will flow from the IO pin to CCP1 when CCP1
is low (i.e. not during the duty cycle). This will give you the other colour of the bi-colour LED.
high
V
IO Pin low Same
0 voltage,
Same Current
Current so no
voltage, flows high
flows high current
so no to low
to low flows
current
V flows
CCP1
0
Create a program to run an LED connected between CCP1 and RB0 progressing through duty
cycles of 0%, 33% of TPWM = 0.2 ms, 33% of TPWM = 0.3 ms, 67%, of TPWM = 0.2 ms, 67% of
TPWM = 0.3 ms, and 100% and back again by pressing a button (use your button code from Lab
4). It should start in the off position and cycle through the percentages with each tap. For now
either colour, red or green, is okay.
At the two different values of TPWM for the same duty cycle, 33% or 67%, do you see any
difference in the brightness of the LED?
Replace the LED with a speaker and a 200 resistor in parallel. At the two different values of
TPWM for the same duty cycle, 33% or 67%, do you hear any difference in the sound?
31
Lab 5 Oscillator Speed & PWM
If you have a bi-colour LED connected between CCPx and RXn (which can be any digital input
pin), you could control the colour of the LED with software by turning the output pin RXn on or
off. Of course, as can be seen from Figure 5-2, the brightness of the two colours would not be the
same as the on and off times are reversed. With a low IO pin, the on time or duty cycle of the LED
is the same as the duty cycle of the PWM signal, LED duty cycle = PWM duty cycle. But when the
IO pin is high, the on and off times switch and LED duty cycle = PWM period – PWM duty cycle.
If you wanted the bi-colour LED to be the same brightness for each colour or current direction, i.e.
have the same LED duty cycle of on and off times, you will need to change the duty cycle of the
PWM signal when you change the polarity of RXn.
Changing the direction of current through a DC motor changes the direction the motor rotates.
Connecting a DC motor between CCPx and RXn lets you have a software-controllable reverse
gear. Since most DC motors require larger currents than our MCU can provide, we will use the bi-
colour LED as a suitable and cheap stand in.
Modify your program so a second button switches the polarity of RB0 and hence the LED
colour with each press. Make sure each color goes through the same sequence (off, 33%, 67%,
full). This would be like having seven speeds on a motor: three forward speeds, three reverse
speeds, and stop.
PIC MCU
R
CCP1
2Way LED
RB0
32
Lab 6 Timers
Lab 6 Timers
Objectives:
(a) To use the built-in module Timer0 and associated library functions to measure time intervals
while allowing other operations to proceed.
(b) To use the timer module to produce a third colour from a bi-colour LED.
(c) To use the timer module to time an external square wave signal.
(d) To use the timer module as a counter of rising or falling edges of an external signal.
Equipment:
Photogate, photogate socket with three leads, two-way bi-colour LED
Prelab:
Read Chapter 11 in the PIC18F4525 datasheet on the Timer0 Module.
Files:
Example code: using_timers.c
The delays we have used so far are called blocking delays; your code cannot change anything until
the delay is over. Since timing is such a crucial part of embedded systems, it is only natural that
the PIC MCU has a better way of measuring time. The PIC18F4525 actually has four timing
modules but we will only look at Timer0. As you have read in Chapter 11 of the datasheet, the
features of the TMR0 module are determined by the Timer0 Control or T0CON register.
The timer module acts a lot like a 60-minute cooking timer. You set the timer for 15 minutes and
go away and do whatever you want rather than watching the stove. When the 15 minutes are up,
an alarm goes off reminding you to come back and check your cooking. Of course the Timer0
module counts instruction cycles rather than seconds or minutes. Another difference from the
cooking timer is that the timer0 module actually counts up rather than down. If the cooking timer
acted like the timer0 module, it would count from 15 up to 60, rather than down to zero, before the
alarm went off. Timer0 also doesn’t sound an alarm but sets a flag, which is a one-bit piece of
memory called the Timer0 Interrupt Flag, TMR0IF. This flag is set when the counter timer rolls
over. You need to clear the flag, i.e. shut off the alarm, before the timer module can be used as an
alarm again.
The T0CON register, like other registers, is a special reserved portion of the PIC MCU’s memory
whose settings determine how this particular module will operate. The TMR0 module can act as a
counter or as a timer but we are only interested in its timing mode.
33
Lab 6 Timers
The XC8 Compiler comes with library functions that simplify the use of the T0CON register. The
four functions for the timer0 module – CloseTimer0(), OpenTimer0(), WriteTimer0(), and
ReadTimer0() – are briefly discussed in Section 2.5 of the MPLAB XC8 Peripheral Libraries
datasheet. WriteTimer0(unsigned int timer) sets the timer counter to the value specified by
timer. Conversely, ReadTimer0(void) returns an unsigned integer that contains the current value
of timer counter. CloseTimer0(void) disables the timer0 module and clears TMR0IF, the timer
flag. The final function OpenTimer0(unsigned char config) configures the bits of the
TMR0CON register and is more complicated than the other three. The variable config is a bitmask
of defined names (quantities defined using the #define compiler directive) from timers.h,
TIMER_INT_OFF, TIMER_INT_ON, T0_16BIT, T0_8BIT, T0_SOURCE_INT, T0_SOURCE_EXT,
T0_EDGE_RISE, T0_EDGE_FALL, T0_PS_1_1, T0_PS_1_2, T0_PS_1_4, T0_PS_1_8,
T0_PS_1_16, T0_PS_1_32, T0_PS_1_64, T0_PS_1_128, T0_PS_1_256. The numeric values
of these names are such that you need only AND(&) the names together and the function will set
the T0CON register correctly for you. For example, if we wish to use the module as a timer using
an internal source, we use T0_SOURCE_INT. You can also use an external clock by applying a
square-wave signal of known period to pin6, T0CKI, and using the configuration entry setting
T0_SOURCE_EXT. Note that if the external square wave signal is not periodic, you have a counter
instead of a timer. The settings T0_EDGE_RISE and T0_EDGE_FALL determine if counting starts on
rising or falling edges of the square wave. For a 16-bit timer we will use T0_16BIT. The defined
names T0_PS_1_n, where n is 1, 2, 4, 8, 16, 32, 128, and 256, are prescalars that tell the timer
counter to increment when that number of clock cycles, nTCY, has passed. Your main decision will
be which prescalar to use. Thus, to get all these selections and to start incrementing the timer
counter every 32 TCY, for example, we use
Note that although this means you do not have to work on the T0CON register directly, you must
understand the register settings and the reasonably-clear defined names to get the proper settings.
For example, the names TIMER_INT_OFF and TIMER_INT_ON are mutually exclusive and can’t be
used together – something cannot be on and off at the same time. The names T0_EDGE_RISE and
T0_EDGE_FALL only have an effect if T0_SOURCE_INT is not present, that is when using the Timer0
in counter mode.
An example program, using_timers.c, demonstrates how to use the timer module as a simple alarm
clock delay in two different ways. It uses the Timer0 module to time the on-cycle and off-cycle of
the pulse used to light an LED connected between RB0 and RB1. During both parts of the pulse,
the program monitors a button on RD1 to switch the polarity (on-off state) of the RB0 and RB1
pins. The on-cycle uses the interrupt approach described above. The off-cycle takes a different
approach; it starts the timer at 0 and uses a while loop to poll or check the status of timer counter
with the ReadTimer0() function.
Carefully read the using_timers.c program and make sure you understand its operation.
Compile the program and load it on your PIC MCU and use the two-way LED, not a standard
LED. Recall that every time you use an LED, it needs a buffer resistor in series with it.
34
Lab 6 Timers
The using_timers.c program is not using the timer0 module efficiently. How would you make
it more efficient?
For each prescaler value in T0CON, determine the maximum interval you can time at 32 MHZ.
Put your results in a table.
With a bi-colour LED, you actually can produce a third colour. Red-on for a short time followed
by green-on for a short time will be perceived as yellow or orange depending on the ratio of the
length of each colour’s duration, as long as the durations are short. The PWM module of the
previous lab doesn’t allow you to create this mixed colour (why?) so we will use timers instead.
Note, nothing similar would be available for a DC motor. Indeed changing forward to reverse so
quickly would possibly damage the motor.
Modify using_timers.c and use a bi-colour LED to produce a period signal that lasts 0.100 ms
that is 35% red and 65% green. Pressing a button should switch the signal to 65% red and 35%
green. Pressing the button again should revert back and so on.
Besides timing an output signal, you can use a timer to measure an input signal. For example, the
photogate timer you use in the physics labs produces a square-wave (on/off) signal. When the
photogate is blocked, the signal is low; when unblocked, it is high. The signal would look like the
diagram below, Figure 6-1. Assume that the photogate signal is connected to pin X (you can pick
any convenient pin). A timer that determines the “blocked photogate” time is said to be in “gate
mode”. Photogates have three connections; +5V power (red), 0V ground (black), and a signal
output (yellow or blue). The signal wire would be connected to pin X, of course.
V
Photogate blocked
t
V
Figure 6-1: Timing a square wave signal
35
Lab 6 Timers
5. if the timer did not overflow, calculate and display the time in milliseconds
6. go back to monitor the input signal – step 1
If we don’t have a photogate available, we can use a button and measure how long it is pressed.
Note that the signal for a button is flipped compared to the photogate signal in Figure 6-1. If you
use a button, the logic of the program is the same as the six steps above but with falling edge
replaced by rising edge and vice versa.
Connect the photogate (or button if a photogate is not available) to the breadboard. Write a
complete main.c source file to act as a gate timer (or button-on timer) and display the result on
the LCD.
Time-saver: Remember that buttons.c, from Lab 4, has functions that allow you to run code
when either a rising or falling voltage edge is detected
Although the precision of your timing can be quite small, down to a few TCY, the accuracy of your
timer is limited by the accuracy of your PIC’s internal clock, which is only several percent. It is
possible to use an external oscillator, which can be far more accurate, but that is beyond the scope
of this lab.
Timers measure time by counting edges, rising or falling, of square waves with a well-known
period. The time measurement is simply the number of edges times the period. The square waves
can come from an internal or an external clock source. If the source is a square wave that is not
periodic, a timer acts as a counter to inform you of the number of edges that have occurred at the
pin associated with that timer. For Timer0 that pin, number 6, is designated T0CKI. To use Timer0
as a counter, simply include T0_SOURCE_EXT and either of T0_EDGE_FALL (to count falling edges)
or T0_EDGE_RISE (to count rising edges) when configuring OpenTimer0(). Each edge at pin 6
increments the Timer0 counter, and the function ReadTimer0() reports the current count. Pin 6
(T0CKI/RA4) should be configured as a digital input for Timer0 to correctly read signal edges.
Note that when you use Timer0 as a counter, the only prescaler that is usually appropriate is
T0_PS_1_1.
Counters are useful in a number of situations. Imagine, for instance, you wanted to know the
number of people entering a doorway through a turnstile. It is easy to design a circuit to generate
a square wave at each turn of the turnstile. In turn, the PIC MCU does the counting.
Connect a button to pin 6. Modify your program to count button presses at pin 6. Use the LCD
to show the current count.
36
Lab 7 ADC
In this lab you will configure the PIC to perform ADC and display it on an LCD. First you will
test your new ADC on some known values (a voltage divider). Later you will interface a reflective
sensor with the MCU and investigate its properties.
Equipment
Single RLS and holder, white plastic board and electrical tape, DMM, voltage divider board, LCD
Files:
sampleADC.c
Prelab
Read the RLS datasheet.
Read Appendix G on graphing in MS Excel.
Analog to Digital Conversion (ADC or A/D) is a major method of recording a smoothly varying
continuous signal. A MP3 file is an example of analog data, V
music, converted and stored as binary data. The fundamental
concepts involved in converting a continuous voltage signal
Vref
to a discrete binary value are straightforward. Consider a 2- 11
bit ADC for which the only possible values of the conversion ¾Vref
10
are 00, 01, 10, and 11. If the ADC reference voltage is VREF, ½Vref
then these four values correspond to four bins or ranges of 01 ¼Vref
voltages: 0 – ¼VREF, ¼VREF – ½VREF, ½VREF – ¾VREF, and 00 t
¾VREF – VREF. A voltage signal is sorted into just those four 0
bins. From a graph of a voltage signal varying with time, as t0 t1 t2 t3 t4
in Figure 7-1, the ADC values can be read directly. Thus at
t0, the voltage is between 0 and ¼VREF, and the digitized value Figure 7-1: 2-Bit Digitizing.
is 00. At t1, the voltage is between ¼VREF and ½VREF, so the
digitized value is 01. At t3, the voltage exceeds VREF, so the digitized value is 11, the maximum
37
Lab 7 ADC
value it can be. The quality of the digitizing depends on how many bins you can divide your voltage
range up into. The PIC18F4525 has 10-bit ADC, so it can have 210 = 1024 bins. The quality also
depends on the sampling rate, i.e. how often you make the conversion. It should happen more
quickly than the fastest changes in the voltage.
V
The ADC operation takes time to complete and has
two main steps. The instantaneous voltage must first small V
be captured, to provide a constant voltage that then
can be compared against known voltages. Internally,
a fast switch and a capacitor-resistor circuit with a big V
small time constant are used to capture the current
t
value. The time constant must be small enough that 0
the signal has not varied significantly during that
small t big t
capture time t 5, see Figure 7-2.
The known voltages that the sample voltage will be Figure 7-2: Effect of long capture times.
compared to are generated internally based on a high
and a low reference voltage, VREF+ and VREF–. Usually VREF+ = VDD (+5 V) and VREF– = VSS (0 V).
The known voltages go from VREF– to VREF+ in steps of size (VREF+ – VREF–)/2n where n is the
number of bits in the ADC operation. In our 2-bit example, the step size is (5 – 0)/4 = 1.25 V. For
10-bit ADC, it is 0.00488 V. In the 2-bit example, the comparison voltages are thus 0 V, 1.25 V,
2.50 V, 3.75 V, and 5 V.
Once a signal has been digitized, there is corresponding problem of interpreting the digital value
as a voltage. Since each digital value is a bin or range of voltages, it only makes sense to take the
average, middle-of-the-bin value as the reading. Since the actual reading is anywhere in the range,
38
Lab 7 ADC
the precision of the digital measurement is one-half the width of the range. Thus, four digital values
00, 01, 10, and 11 correspond to readings of 0.125VREF ± 0.125VREF, 0.375VREF ± 0.125VREF,
0.625VREF ± 0.125VREF, and 0.875VREF ± 0.125VREF. Since VREF is accurate to only to a few
percent, the resulting digital conversion has the same accuracy. For an n-bit conversion, the general
formula is
Since the PIC18F4525 is more adept at handling integers, rather than floating type numbers, it is
advisable to use VREF in millivolts in the formula above. We do not usually display the precision,
when giving the voltage, since it is a constant.
Since the ADC conversion feature of the MCU gives us a simple digital voltmeter, we are going
to want perform ADC on a circuit with known voltages to ensure everything is working properly.
For its simplicity, we are going to use a voltage divider circuit with either four or five equivalent
resistors (the five-resistor version is shown in Figure 7-4). The circuit is called a voltage divider,
because each of the points, 1 through 4, is a fraction of the potential at 5. Using Ohm’s Law, V=IR,
we can find that V1 = (200 / 1000 ) V5, V2 = (400 / 1000 ) V5, and so on.
V4 V3 V2 V1
V5 0
200 200 200 200 200
Connect the voltage divider to VDD and VSS on the breadboard as shown in Figure 7-5.
Investigate this circuit using a voltmeter, checking each of the potentials V0 through V5. That
is, clip the black voltmeter lead to the breadboard at VSS and use the red lead as a probe to
measure and record the circuit divider point potentials V1 to V5 with respect to ground.
Remember to record your observations in your notebook (as always). Note: your voltage
divider board may have fewer resistors!
39
Lab 7 ADC
+5V
V4
Voltage PIC18F4525
probe
V3 2
AN0
200
V2 4 VREF-
5 VREF+
V1
0V
Connect a long wire to your selected pin and buffer resistor to use as the ADC input probe (see
Figure 7-5, where AN0 is shown as the probe pin). The ADC pin is internally connected to ground,
so we only need the one lead to measure potentials (voltages) with respect to ground. Each ADC
pin you use will need its own wire and buffer resistor. When the wires are not in use connect them
to ground. Do not disconnect the serial communication pins that you used in the last lab; you will
be displaying the output of your program to the LCD.
The main function OpenADC(unsigned char config, unsigned char config2, unsigned
char portconfig) configures the registers (see Section 19 of PIC18F4525 datasheet) that control
ADC. The function takes three parameters (config , config2 , and portconfig) and, as usual,
the parameters are created by bitmasking groups of preset named constants. The settings for the
parameters are discussed in the Programming the PIC18F4525 for ADC section of Notes VIII.
It takes a little time for the ADC module to be fully operational, so it is a good idea to insert a little
time delay after the call to OpenADC().
The other functions are much simpler. Once the configuration is set by OpenADC(), the actual ADC
operation will not start until ConvertADC() is called. ConvertADC() takes no parameters. The
conversion of an analogue signal value to a digital value takes time (though it is blindingly fast to
40
Lab 7 ADC
people) and you have to wait for the process to complete. You can do other things while waiting
as long as you occasionally check to see if the conversion is complete. You check on the status of
the conversion by examining the status of the function via another parameterless function
BusyADC(). It returns a value of 1 if ADC is still going on and 0 if finished. You get the actual
results of the ADC operation by calling the ReadADC() function, which returns an integer that
holds the digital value. ReadADC() takes no parameters.
You can do another ADC operation by calling ConvertADC() again and polling BusyADC() until
this next conversion is done. You can repeat ADC operations as many times as you like.
You can also change pins and channels from one ADC operation to the next by using the
SetChanADC() function as long as you have already indicated that that channel will be used by
selecting the appropriate value of portconfig in OpenADC(). The SetChanADC(unsigned char
channel) function takes any one of the predefined channels, e.g. AD0_CH1, as an input.
When you are finished with your conversions and wish to free up the analog input pins you have
been using, call the CloseADC() function. It takes no parameters.
Figure 7-6 shows an example of the code to start ADC and continuously collect a value from AN0
followed by a value from AN1.
#include <xc.h>
int firstADCvalue, secondADCvalue;
Modify the sample code given in Figure 7-6, along with your LCD programs from previous
labs, to continuously sample the input voltage at the AN0 and AN1 pins. Display the results
with one signal on the top line of the LCD and one signal on the bottom. Display the results
41
Lab 7 ADC
both as the raw ADC value and as the voltage in millivolts. Measure and record the circuit
divider voltages V1 to V5 using each pin probe (i.e. twice!).
You can use an external upper reference voltage (that is, a value other than the voltage supplied to
VDD on the PIC) by replacing ADC_VREFPLUS_VDD with ADC_VREFPLUS_EXT and connecting pin 5
(VREF+) to whatever voltage you wish to use instead. Similarly, you can use an external lower
reference voltage (that is, a value other than VSS, which is normally connected to ground) by
replacing ADC_VREFMINUS_VSS with ADC_VREFMINUS_EXT and connecting pin 4 (VREF-) to
whatever voltage you wish to use instead. You can have either or both reference voltages as
external. The simplest source of external voltages that you can use are the voltages of the voltage
divider board.
There are several points to be careful about. The upper reference voltage must be greater than the
lower reference voltage. Also, the minimum allowable difference in these two voltages is about
three volts according to the PIC18F4525 Data Sheet.
Modify your previous program to work with two external reference voltages. You will need to
modify the portion of your code that converts the ADC value to voltage in millivolts. Use the
voltmeter readings values in your formula.
Use V4 (on the five-resistor voltage divider) as the upper reference and ground as the lower
reference. Measure and record the circuit divider voltages V1 to V5 using a pin probe.
Use VDD as the upper reference and (on the five-resistor) V1 as the lower reference. Measure
and record the circuit divider voltages V1 to V5 using a pin probe.
Line-following robots use a reflective infrared light sensor (RLS). It consists of both an infrared
(IR) LED and a photo-sensor with an IR filter. Review the RLS datasheet provided for specifics.
The robot RLS board contains five of these sensors. The photosensor voltage changes in response
to the amount of IR light reflected from a surface. The RLS is in a holder so that you can slide it
across a surface at constant height.
The behaviour of the RLS is quite simple. The bottom of the sensor has two pieces, as shown in
Figure 7-8, an IR LED (transparent) and an IR sensor (black). The sensor portion of the RLS is a
transistor. With no IR light shining on it, the sensor has a large resistance, on the order of 1 M.
With bright IR light shining on it, the light excites electrons and a current flows. If you connect an
ohmmeter across the transistor, the resistance will now appear to be only a few hundred ohms
42
Lab 7 ADC
because of the extra current. The right-hand side of the circuit shown in Figure 7-8 thus works like
a simple voltage divider consisting of the 10 k and IR sensor. When the sensor is not illuminated
VDD
IR LED
270 10K
QRD1114 AN0
RLS
IR Sensor
Vss
Figure 7-8: Reflective sensor
Figure 7-8: Connecting a RLS to the Breadboard. and electrical tape.
by IR light, the resistance of the sensor is so high that the voltage measured by the probe should
be near VDD in value. When the sensor is well illuminated, the resistance is low compared to the
10 k resistor and the voltage measured by the probe should be near VSS in value.
To check that the sensor portion is working correctly, attach an ohmmeter to the sensor side using
the sensor and VSS leads. The RLS should not be connected to the breadboard! There are several
LED flashlights that have had their visible light LEDs replaced with IR LEDs. Confirm that the
sensor is responding correctly.
Connect the wires from the holder to your breadboard with the appropriate buffer resistors, as
shown in Figure 7-8. The resistors may already be attached. You can check that the IR LED is
working if you have a camera cellphone. Most digital cameras will respond to IR. With the RLS
connected to power, check your cellphone camera screen to see if you see a white light. It should
go out if you cut the power to the breadboard.
Investigate the change in voltage as you move the RLS back and forth over white paper
containing a line of black electrician’s tape, as shown in Figure 7-8. Point the sensor towards
an outside window and towards the overhead lights. Remember to record your observations in
your notebook.
At the end of this lab, you will be given a grayscale chart. The number on each square is a
measure of the grayness of the square, where white is 0 and black is 255. Record the ADC
value for each square and plot the results in MS Excel. See Appendix G for details on graphing.
Fit a trend line to the data. Is the response linear?
What is the grayscale number of the two squares labelled A and B? Which are you more sure
of? Why (explain using your plot from above)?
Place the RLS and holder so that the RLS is about 1 cm from the tape. Align the RLS so that
the IR LED and photodetector are lengthwise parallel to the tape (see Figure 7-9). Carefully
slide the RLS perpendicularly across the tape. Measure position and ADC value as you go.
43
Lab 7 ADC
Again, carefully slide the RLS back across the tape. Note that the LED and photodetector have
changed places relative to the tape. Plot both sets of data on the same MS Excel chart. Is there
a threshold value of the readings that clearly indicates the sensor is over the edges of the tape?
Tape
RLS
44
Lab 8 Transducers
Prelab
Read Appendix G on finding fitting functions for data.
Review voltage divider circuits.
Equipment
DMM
Reference resistors of various sizes
Transducer (a particular transducer and datasheet will be assigned to each group)
Amplifier if necessary
Background
A transducer will in general produce a voltage output that changes with variations in the quantity
it will measure or have a resistance that changes with those variations. In the case of the voltage
output, the signal is often small and will need to be amplified to a suitable size. Once amplified,
the signal can then be measured by the ADC function of the PIC MCU. If, on the other hand, it is
a resistance that is changing, we need to use a voltage divider circuit. That is, the transducer
resistance must be in series with a known resistance with a known voltage over the two resistances,
see Figure 8-1. The voltage signal VT over the transducer resistance RT can then be related to the
external variable. We can use the ADC function to then determine VT. Note that the known resistor
Rref must be chosen carefully so that the measured voltage covers a wide enough range of values.
Collect a suitable number of data points, 6 to 10, for a set of reference values of the external
Rref VT
Vref
RT
parameter. For example, if you were using a thermocouple, you could use ice water and boiling
water for two data points. You might then have to use a glass thermometer to get readings at
intermediate temperatures. Be sure to get a wide range of data.
45
Lab 8 Transducers
Use MS Excel to plot the data. Determine the best fitting function – linear, quadratic, logarithmic,
or exponential – to the data. Determine the equation of the trend line. From the fit of the data,
estimate the accuracy of the fitting functions.
Write a program to use your fitting function with the transducer signal and to display the result on
the LCD display. One line should display the signal voltage, and one line the value of the external
condition.
Prepare a short presentation of your results on a sheet of poster paper. Include a short description
of how the transducer works, your graph and equation, and how you estimate the accuracy of your
sensor. Be prepared to have others test your sensor.
46
Lab 9 Robot
Equipment List:
Sumo-bot, stopwatch.
Files:
sumovore.c, sumovore.h, interrupts.c, interrupts.h, main.c, motor_control.c,
motor_control.h
Pre-lab:
(a) Visit websites http://www.vancouverroboticsclub.org/ and
http://www.robogames.net/rules/line-following.php. Learn more about the line follower robot
competition and rules.
(b) Read the program files listed above to get some idea of how to write the robot programs.
Choose your new group for next 4 weeks of the robot project. The standard group will have 3
students.
Part B: Using the PICkit 2 to program the robot and sensor control
There are three systems to the robot. First, there are the reflective infrared light sensors (RLS) used
to detect the line. The second system is the set of five indicator LEDs on the top of the robot. The
third system is the two electric motors that independently control the two wheels. The library files
sumovore.h and sumovore.c contain the basic functions to run these three systems. The files
main.c, motor_control.h, and motor_control.c show simple logic for following a line. The
files interrupts.c and interrupts.h contain advanced functions that we will not cover in this
course. They help prevent erratic behaviour when voltage drops or spikes. An overview of these
files is provided in Notes X Robot.
Create and build a project with the file main.c and the library files it uses.
Make sure at this stage that both power switches on your robot are in the “off” position (see
Figure 9-1) and connect your robot brain board to the PICkit 2 microcontroller programmer
using the given connector (see Figure 9-2). Turn on the power for the robot brain board (switch
47
Lab 9 Robot
#1) but keep the power supply switch for the motors (switch #2) “off”. You will see five small
LEDs mounted on the top of the brain board light up. Load your project onto the robot, turn off
the switch #1, and disconnect the PICkit 2 microcontroller programmer.
Connector
off off
Figure 9-1: Power switch locations for brain board (switch #1) and motors (switch #2).
Robot brain
Connector board
Programmer
Figure 9-2: PICkit 2 microcontroller programmer, connecting bracket, and brain board.
The program you compiled and downloaded will activate the robot’s sensor unit board but the
motors will not turn since the power to them is off. Turn on switch #1 and then place your robot
on the white paper with black tape attached. Move the robot over the tape and see how those 5
LEDs mounted on the brain board respond. Is there any position where sensor unit is over the black
tape yet none of the LEDs is on?
Consider the following line of the file sumovore.c, in the function initialization()
48
Lab 9 Robot
You do not need to use the default value of the threshold. For your convenience, the following line
is in main.c. It lets you choose any value.
Modify the threshold in main.c to see the changes in sensor response. What is a good value for
the threshold?
The function motor_control() uses the input from the sensors to travel. Forward motion, right
and left turns, and stopping are controlled by separate functions. These functions, in turn, all make
use of a single function set_motor_speed(the_motor, motor_speed, speed_modifier) to
control the speed and direction of each wheel. See Table 9-1 for the values of the function
parameters. The speed_modifier parameter is often zero but is included to allow you to
compensate for one wheel being slower than the other.
Test main.c on an oval track, a short straight line track, and then on various more complicated
tracks to see how it performs.
It is possible that your robot may malfunction at some point, so it would be good to have a test
program. Change motor_control() to run both wheels forward at fast speed for several
seconds, then medium for several seconds, and so on through the list of motor speeds –
including stop! – all the way to reverse fast. Run the program with the wheels up in the air so
that you may observe them. For added information about how the speed of one wheel compares
to the other, stick a piece of tape on each wheel. If the pieces of tape stay aligned the wheels
are turning at the same rate. Comment on any differences you see.
49
Lab 9 Robot
Modify motor_control() to always travel forward, ignoring sensor input, and time your
robot’s progress over a measured distance to determine its speed. Note if the robot’s actual
path deviates from straight.
Modify motor_control() to always travel backwards, ignoring sensor input, and time your
robot’s over a measured distance to determine its speed. Note if the robot’s actual path deviates
from straight.
A series of “robot dances” are defined in Figure 9-1. Modify the project three times to perform
all three dances.
Possible Literal Pitfall: When programming the robot, make sure that the
brainboard power is on but the motor power is off. A newly awakened robot may
make a dash for freedom and end up plunging off a table to its demise!
Spin around one wheel Spin at one place Spin in larger circle
50
Lab 10 Project
Equipment List
Sumo-bot, sample tracks
Table 10-1: Sample format for the task planning, assigning, and sharing.
Date: March 12, 2006 Morning
Tasks Descriptions Estimated Task leader Self-
time evaluation
1 Write algorithm and code for right 0:50 M. Coombes 100%
angle turns.
2 Write algorithm and code for 0:50 T. McMath
perpendicular crossings.
3 Misc. J. DeBenedictis
4 Etc. F. Callaghan
… … … …
We suggest that you start by writing programs to get your robot around the simpler tracks first.
Algorithms, i.e. path-following strategies for solving particular course challenges, must be
provided and explained. Explain what worked and what didn’t work.
51
Lab 10 Project
gitHUB
Code management will extremely important. Each version of your program should be numbered.
For example, Robot V2.23 .c might refer to the 23rd change of version 2 where version 2 might be
the code to handle right angle turns. Backup copies of each version should be kept. You will be
expected to submit all versions of your code. gitHUB is a service we will use to to keep everything
straignt.
52
Appendix A MCU Fundamentals
Most MCUs have a CPU, RAM, ROM, and some EEPROM (Electrically Erasable Programmable
Read Only Memory). Inexpensive controllers that are designed to be programmed once (for a
particular application) may omit the EEPROM, because there may be no need to ever change the
program once the project goes “into production”.
The MCU you will use, the PIC18F4525, has 256kb of EEPROM, which allows you to erase and
reprogram it about a million times, so you do not need to worry about “wearing out” the EEPROM
anyway. This EEPROM can hold your program and any data storage required in your application.
Technical details are available in the datasheet.
You will create the circuits you will use with the MCU on a “breadboard”. On a PC, using a
program called the MPLAB IDE, you will create and test the programs that will run on the MCU.
The programs will be written using the C language. When finished, you will transfer the program
to the MCU using a USB connection and a device called the PICkit 2 or PICkit 3 programmer.
More details can be found in Appendix B – Software.
The PCIF4525 is a 40 pin device. Some of the pins are used to connect a battery or power supply
to the MCU. The rest of the pins are for inputs and outputs. The pin-out diagram of the P18F4525
is shown in Figure A-1 below. Note the notch on the chip. This orients the chip and indicates the
top of the chip and which pin is number 1. The PIC18F4525 has many features built into the chip
including digital input and output (DIO), timing and square wave production, serial
communication, and analog-to-digital conversion (ADC) that you will learn to use. Depending on
53
Appendix A MCU Fundamentals
the features of the MCU being used, the pins have different functions and names (this is called
multiplexing).
The behaviour of a program on an MCU is different from other programs you have used. When a
MCU reached the end of a program, it resets and starts all over again – a useful feature in case of
power outages. You need to adapt your code to this behaviour by adding a while loop that runs
forever as in the sample program in Figure A-2 below.
#include <xc.h>
/* a header file with information specific to the chip */
int main(void)
{
/* put instructions that are to run only once here */
}
Figure A-2: Sample MCU program.
54
Appendix B Software
Software
The software we will use to write, compile, and debug C language code and to program the PIC
MCU are free, courtesy of MicrochipTM, the manufacturers of the PIC18F4525 chip that we are
using in this course. The software:
are available from www.microchip.com. Download them for use on your home PC if you wish.
You will find links on the APSC 1299 Moodle home page.
The MPLABX IDE and XC8 Compiler work together as one seamless program from the view of
the user. In the IDE, you build a project. A project consists of a C language code file that you write
and library files supplied by Microchip. A library is a group of functions grouped together for
easier reference and ease of linking. You can edit, debug, or step through your code line by line.
When your project is ready, you instruct the IDE to compile your program to a .HEX file. The Hex
file is your project in a form that the PIC18F4525 chip can understand. A USB device, the PICkit
2 or 3 programmer, is used to write those instructions into the memory of the PIC MCU.
55
Appendix B Software
Figure B-2: Installation Directory Problem Figure B-3: Finding the folder
56
Appendix B Software
Creating a Project
A project is a collection of files, some of which are yours and some of which are standard C library
files, that the compiler will turn into a program that will run on the MCU. Typically you will have
several source (.c and .h) files. Only one source file can have a main() function or an error will
occur when you try to compile your project. Since you will be working at home and in the lab, it
is wise to have a USB memory stick and to use it to store your projects or you can use cloud
storage. Be sure to back up your files at home since misplacing a memory stick is a common
occurrence.
Possible Pitfall: You can build your project in the Student Directory on the lab PC but
files in the Student Directory are deleted every night or sooner. Do not leave your
project there. Backup to a USB device.
We suggest having a folder called APSC1299 to contain your projects. Each project should have
its own subdirectory. During the course of the labs, you will be building up a library of useful
functions that can be added to any project. Place these functions, and any functions we give you,
in a subdirectory of the APSC1299 folder called Common. You will need to tell your project to
look in Common for any files it does not find in the project directory.
To start, click on the MPLABX IDE icon on the desktop of your PC. This will bring the main
screen of the program as shown in Figure B-4 below. You can create a new project by either
clicking on File in the Menu bar and choosing New Project or by clicking Create New Project
which is shown circled at the lower left of the diagram.
Menu bar
Icon
bar
57
Appendix B Software
When the New Project window appears, the first step of a seven-step process is to choose the type
of project we will create. It will always be a Microchip Embedded Standalone Project, as shown
in as shown in Figure B-5. In step 2, make sure that the program knows that you are using the
PIC18F4525 chip of the Advanced 8-bit MCUs (PIC18) family, as shown in Figure B-6.
Next, in step 3, see Figure B-7, we need to select the tool we will be using. We have two possible
choices here; we can use the Simulator or the PICkit3. If you choose the Simulator, as the name
suggests, you don’t need any hardware. Typically you write and do simple debugging in the
Simulator mode. When it appears debugged, you can then use the PICkit3 (see Figure B-7) to load
the program onto the chip and do further testing.
The next window skips to step 6, where we select the XC8 compiler (see Figure B-8). Then, in the
final step, step 7, you pick a name for the project and a location for it (see Figure B-9). The location
should be a folder with a name like APSC1299Projects where you will keep all your different
projects. You might want this work folder to be on a USB Memory Stick if you are working on
the same project at home and at school. Also choose the UTF-8 encoding; this is a universal
character set and should be more portable.
58
Appendix B Software
There are a few more things to do once your project is created. You need to change the project’s
properties. On the Menu Bar, click on File then click Project Properties. This will bring up the
Project Properties Window. On the left-hand side is the Categories frame. In that frame, click on
XC8 linker. This is shown as a box with a 1 in Figure B-11 below. Now, on the right-hand side,
59
Appendix B Software
you have an Option categories dropdown menu. Select Runtime from the dropdown menu, as
marked by the box with the 2 in Figure B-11 below. Scroll to the end and select Link in Peripheral
Library, which is marked by 3 in Figure B-11 below. Without this selection, the project will thow
an error message for all the function we will use from the XC8 peripheral library.
1 3
While still in the Project Properties window for XC8 linker, select Memory model from the Option
categories dropdown menu, which is in a box labelled 4 in Figure B-12 below. For both Size of
Double and Size of Float, noted by the box with the 5 below, select 32 bit instead of 24. This will
ensure any decimal number we use or calculate will have the greatest number of significant digits.
4
5
Finally, in the Categories frame on the left-hand side of the Project Properties window, click on
XC8 compiler. This selection is highlighted with a number 6 in Figure B-13 below. Then, in the
Option categories dropdown menu, select Preprocessing and messages as noted by a box with the
number 7 in Figure B-13 below. Then, scroll down to Include directories, shown by a box with
60
Appendix B Software
an 8 in Figure B-13 below. This is where we tell the project to find any files that are not in the
project directory. Browse and select the location of your Common folder.
6 8
61
Appendix C Writing to the PIC/MCU
1 1
2 3 = VSS (ground) 2 3 = VSS (ground)
3 3
4
4 = PGD 4
4 = PGD
5 5 = PGC 5 5 = PGC
6 6
6 = PGM 6 =Not used
Possible Pitfall: The PICkit 2 and PICkit 3 devices are not interchangeable. The
wiring to the PIC is different.
The MCU can be programmed (your hex file written to the MCU) directly from the MPLABX
IDE. First, connect the PICkit 2 or 3 to your MCU and to a USB slot on the PC. Second, ensure
the power supply is connected to your MCU by plugging your DC adapter into the red regulator
board on your breadboard and turning on the power switch on the regulator board (an LED should
come on). Next, tell the IDE that you are using the PICkit 2 or 3 by choosing Run|Set Project
Configuration|Customize as shown in Figure C-3.
62
Appendix C Writing to the PIC/MCU
This will bring up a window, see Figure C-4, where you may change from using the Simulator to
using the PICkit 2 or 3 and make many other changes necessary.
The icon bar of the IDE will change after you select the programmer. New programming icons
will appear next in the icon bar, Figure C-5.
63
Appendix C Writing to the PIC/MCU
When you click on the Run Main Project icon, the project builds and, if successful, connects to the
PICkit 2 or 3 programmer. A PICkit 2 or 3 tab will appear in the Output pane on the lower right
of the IDE window, see Figure C-6 for a successful write operation.
Hint: If the MCU is not wired correctly, or the power USB cable is disconnected,
or the PICkit 2 or 3 is not connected to the MCU, there will be a message that the
Target device was not found. Check your connections. If the PICkit 2 or 3 is not
connected to a USB port, a message will pop up alerting you of that fact. If these
steps do not work, ask for assistance.
64
Appendix D ASCII
ASCII
The char data type in the C language is just a number. However, every possible value of a char
number is associated with a text symbol or special function for printing, such as a carriage return,
as shown in Table D-1 below. The special non-printing characters are shown in bold in the table
below and discussed in more detail in the next section. When characters are printed, e.g. using the
printf() function, the number is replaced by the symbol. Which symbol replaces which number
depends on an arbitrary convention. The particular convention used in the table below is called
ASCII. Other conventions, and modifications of the ASCII standard convention, exist – especially
for different languages. As well, there is the Extended ASCII Character set for decimal values
from 127 to 255. These can be very different from device to device, such as the SmartFun LCD
and a PC.
65
Appendix D ASCII
Char Dec Oct Hex Bin Char Dec Oct Hex Bin
(rs) 30 36 0x1e 0b00011110 = 61 75 0x3d 0b00111101
> 62 76 0x3e 0b00111110 _ 95 137 0x5f 0b01011111
? 63 77 0x3f 0b00111111 ` 96 140 0x60 0b01100000
@ 64 100 0x40 0b01000000 a 97 141 0x61 0b01100001
A 65 101 0x41 0b01000001 b 98 142 0x62 0b01100010
B 66 102 0x42 0b01000010 c 99 143 0x63 0b01100011
C 67 103 0x43 0b01000011 d 100 144 0x64 0b01100100
D 68 104 0x44 0b01000100 e 101 145 0x65 0b01100101
E 69 105 0x45 0b01000101 f 102 146 0x66 0b01100110
F 70 106 0x46 0b01000110 g 103 147 0x67 0b01100111
G 71 107 0x47 0b01000111 h 104 150 0x68 0b01101000
H 72 110 0x48 0b01001000 i 105 151 0x69 0b01101001
I 73 111 0x49 0b01001001 j 106 152 0x6a 0b01101010
J 74 112 0x4a 0b01001010 k 107 153 0x6b 0b01101011
K 75 113 0x4b 0b01001011 l 108 154 0x6c 0b01101100
L 76 114 0x4c 0b01001100 m 109 155 0x6d 0b01101101
M 77 115 0x4d 0b01001101 n 110 156 0x6e 0b01101110
N 78 116 0x4e 0b01001110 o 111 157 0x6f 0b01101111
O 79 117 0x4f 0b01001111 p 112 160 0x70 0b01110000
P 80 120 0x50 0b01010000 q 113 161 0x71 0b01110001
Q 81 121 0x51 0b01010001 r 114 162 0x72 0b01110010
R 82 122 0x52 0b01010010 s 115 163 0x73 0b01110011
S 83 123 0x53 0b01010011 t 116 164 0x74 0b01110100
T 84 124 0x54 0b01010100 u 117 165 0x75 0b01110101
U 85 125 0x55 0b01010101 v 118 166 0x76 0b01110110
V 86 126 0x56 0b01010110 w 119 167 0x77 0b01110111
W 87 127 0x57 0b01010111 x 120 170 0x78 0b01111000
X 88 130 0x58 0b01011000 y 121 171 0x79 0b01111001
Y 89 131 0x59 0b01011001 z 122 172 0x7a 0b01111010
Z 90 132 0x5a 0b01011010 { 123 173 0x7b 0b01111011
[ 91 133 0x5b 0b01011011 | 124 174 0x7c 0b01111100
\ 92 134 0x5c 0b01011100 } 125 175 0x7d 0b01111101
] 93 135 0x5d 0b01011101 ~ 126 176 0x7e 0b01111110
^ 94 136 0x5e 0b01011110 (del) 127 177 0x7f 0b01111111
The SparkFun LCD reserves characters 124 and 254 to use as function identifiers or control
characters. The SparkFun interprets printf(“%c%c”, 124, 13); as a command (discussed
below), not as an order to display characters 124 and 13. That means you cannot get the symbol |
on your LCD.
Some devices, such as the SparkFun LCD, use the non-printing ASCII characters 0 to 31 as control
characters or commands. The control character, which is number 13 in the ASCII table, is often
denoted <control>M or CTRL-M or ^M in documents with similar notation for the other controls.
66
Appendix D ASCII
Different devices may use the same control character to do different things. It is important to look
up the device documentation to find the meaning of each control character. The SparkFun LCD
documentation indicates that you can select a baud rate of 9600 by first entering special command
character 124 followed by <control>M. Since <control>M means character 13, you would need
the line of code printf(“%c%c”, 124, 13); in your program to make this selection. A list of the
control characters and the common notation is given below.
67
Appendix E Simulator
The Simulator
When a build does not succeed, the output window in the lower right pane of the IDE will provide
a series of error messages and warnings that will help you track down the problem. The simplest
problems are just typos, such as misspelling a function name or forgetting a closing bracket or
semicolon. These messages will indicate the line number in the code where the problem occurred.
In either case, the error messages and warnings are all numbered and include a short, often cryptic,
explanation of the cause. More information on the error and warning messages is found in
Appendix D of the MPLAB XC8 Compiler User’s Guide and Chapter 7 – Troubleshooting of the
MPLAB XC8 C Compiler Getting Started manual. Although more detail is given there, it too may
be rather cryptic. Double clicking on an error message will bring you to the line of code where the
problem occurred. Note that the real problem might even be a line or two before that line. If the
line involves a function call, it might even be in the source file for that function.
Possible Pitfall: Sometimes the error in your code is a line or two above the line
number specified in the error message.
Possible Pitfall: Do not ignore warning messages even if the build succeeds.
Subtle logic problems indicated by the warning message can make your program
behave in unexpected and undesirable ways.
MPLABX has a number of features, besides messages, to help with debugging. We explore some
of them below.
Line Numbers
By default, the IDE automatically displays line numbers for each blank line or line of code. When
you print your file, however, line numbers are not automatically displayed. To display line number
when printing, select File|Print and then select Print Options in the Print Preview window. In the
Print Options window, check the Line Numbers box (see Figure E-1)
68
Appendix E Simulator
The Simulator
A program may build successfully but not do what you thought it should because of faulty logic
in the way it was constructed. Such faults are much harder to track down, but the MPLABX IDE
does provide a way to step through your code and watch how variables and registers change. To
invoke the simulator, choose Run|Set Project Configuration|Customize (see Figure C-3 in
Appendix C) and select Simulator as the Hardware Tool in the Project Properties window (see
Figure C-4).
Use Debug|Debug Main Project to start the debugging session. Now Debug, in the menu bar, will
have a number of choices for debugging the execution of your program, see Figure E-2. Similarly,
a new set of equivalent icons, see Figure E-3, will appear on the icon bar. These choices are most
effective when breakpoints and variable watches have been created, as discussed in the next two
sections. Use Debug Main Project from the Debug menu or the icon bar to start the debugging
session.
Finish Continue
Breakpoints
The simulator is often used by setting breakpoints in the code. When run, the simulator will execute
the code but stop at each breakpoint. If you also have a “watch window” open, you can see how
variables and function registers change from breakpoint to breakpoint in the execution of your
code.
You have multiple ways to set a breakpoint. The simplest approach to set a breakpoint in your
code is by left-clicking at the line number in the code pane where you wish to set the breakpoint.
The line number will change to a tiny pink square, indicating the breakpoint, and the line of code
will be highlighted in pink. Clicking twice removes the breakpoint. You can also right-click on the
line number, which brings up a small context menu, or use the shortcut Ctrl-F8, or right-click on
the line of code and pick Toggle Line Breakpoint from the pop-up menu. Sometimes breakpoints
69
Appendix E Simulator
cannot be set exactly where you want them to be. If that happens, insert a NOP(), which is a “do
nothing” or “no operation” function, and set the breakpoint on the line with the NOP().
After you have started the debugging session, the debugging icons will no longer be grayed out
(see Figure E-3 for the active icons). The key icons are the Finish icon, which ends the debugging
session, Continue, which moves you to the next breakpoint, and Reset, which restarts the project
from the beginning.
When the debugging session starts, the program will execute until it hits the first breakpoint. The
line of code will be highlighted in green. There will also be a small green arrow on top of the red
breakpoint square (see Figure E-4). Click on the Continue icon to go to the next breakpoint.
Watching Variables
As you step through your code, you can watch how your variables change from line to line in a
“watch window”. This is an excellent way to spot unexpected behaviour. You open a watch
window by highlighting your variable, or variables, and right-clicking. From the context menu,
select New Watch (see Figure E-5). This opens a small pop-up window asking you to confirm or
change your choice (see Figure E-6). In the Task output pane on the lower right of the IDE window,
there will be a new Variables tab (see Figure E-7). Your watched variables are listed in the tab.
Usually, you are most interested in the value of the variable, but the data type is also listed. The
values may initially be empty. If you did not initialize your variables, you may not see any value,
or you may see 0. It is unwise in C programming to expect that uninitialized variables are
automatically initialized to zero. You can also display the value in various number formats,
including binary and hexadecimal, by right-clicking on the top of the tab (again see Figure E-7).
As you step through the breakpoints, the values will change with the code. Changed values are
70
Appendix E Simulator
noted in red. If a breakpoint is placed on a line in main, and program execution stops on that line,
the local variables will be in scope and will be displayed.
Similarly, you can examine the behavior of any ‘register’, which is an internal MCU variable, used
in your project. The SFRs (special function registers) used in your project show up in a New Watch
pop-up window (see Figure E-6 again).
Stopwatch
Timing is an important part of MCU operations. In real applications, when things happen – such
as a signal turning on or off – is critical. In the simulator, you can examine how much time is
required for each line, or set of lines, of code to execute. You call up a “stopwatch window” by
selecting the menu item Window|Debugging|Stopwatch, as shown in Figure E-8. A stopwatch
window is shown in Figure E-9. At a breakpoint, you can zero or clear the stopwatch using the last
icon on the left-hand vertical bar (see Figure E-9). Proceeding to the next breakpoint will tell you
the number of clock cycles TCY, and the time between the breakpoints. You can also use the
trashcan icon to clear the tab.
71
Appendix E Simulator
The times will only be correct if the debugger knows what frequency your simulated MCU is
operating at. MCUs have multiple operating frequencies. The default frequency is 1 MHz. You
can check and/or set the frequency being simulated by selecting Run|Set Program
Configuration|Customize, which opens the Project Properties window. Highlight the Simulator in
the Categories box. Then, in the dropbox for Category options, select Oscillator Options (see
Figure E-10).
72
Appendix E Simulator
UART Display
When you run the simulator, the IDE steps through the code. If you have a printf() function in
the code, it will not print anything, because you do not have a physical display. A simulated display
is available, but it must be enabled. Select Run|Set Program Configuration|Customize, which
opens the Project Properties window. Highlight the Simulator in the Categories box. Then, in the
dropbox for Category options, select Uart 1 IO Options, and check the box to enable UART, and
make sure the output is to a window (see Figure E-11).
As soon as the first printf() command is executed, a UART 1 Output tab will open in the lower
right Output pane (see Figure E-12). Your printf() output should appear here. If it doesn’t, it
may be because the UART module has not been properly configured for serial communication.
You have been given a small file called ConfigureUSART to help you do this simply.
In-Circuit Debugging
The Simulator can be used to investigate how internal variables change from one part of the
program to the next. Since it is a simulation, the program is running virtually on your PC – not on
the MCU. So while the Simulator is great for debugging and tracing the behaviour of a program,
it cannot tell us how the program will actually respond to an external stimulus like a button press.
The PICkit 2 or 3 has a feature, In-Circuit-Debugging (ICD), that runs, halts, and single-steps
through the program while the PIC microcontroller is actually embedded in the application. When
halted at a breakpoint, the file registers can be examined and modified. For instance, this lets you
see if your button press actually has the intended consequence. You can install up to 3 breakpoints
for the PIC18F4525. The in-circuit debug implementation on PIC microcontrollers will halt
execution on the instruction after the breakpoint instruction. This means the breakpoint instruction
73
Appendix E Simulator
will have executed when the debugger halts. This is referred to as "breakpoint skidding". You may
need to add one or more NOP() functions after your desired breakpoints to stop near where you
actually want to stop.
To use ICD, the PICkit 2 or 3, not the Simulator, must be your hardware tool. Add breakpoints
and variable watches as needed. Then, simply select Debug|Debug Main Project, or click on the
Debug Main Project icon. A window will pop up to ask to change the configuration bits to enable
the MCLR pin (see Figure E-13). Say yes.
As you step through the program from breakpoint to breakpoint, you will able to see the current
values of variables and any SFRs. You will also be able to change these variables and any output
SFRs.
74
Appendix F The Oscilloscope
The Oscilloscope
The oscilloscope is a standard tool in laboratories and industrial settings wherever electronic
equipment needs to be tested. Its inventor, K.F. Braun, a German physicist, shared the 1909 Nobel
Prize with Marconi for their work in perfecting the radio. At its simplest, an oscilloscope is a
voltmeter with a screen showing how a voltage signal changes with time. Most oscilloscopes have
a bewildering number of dials, button, and switches. Fortunately, most can be ignored except for
advanced uses. The controls you need to learn are indicated in the picture of the Tektonix 1000
Series Digital Oscilloscope shown in Figure F-1.
Multipurpose knob Measure Cursor Autoset
Option buttons
Save/Print button
Math button
Reference signal
Pressing the various menu buttons on the front panel changes the menu displayed on the
oscilloscope screen’s right-hand edge (not shown in Figure A-1). The unlabelled “option” buttons
along the edge of the screen can then be used to step through the options in the menu. The
“multipurpose knob” allows you to go forward and backward through an active menu much
quicker.
Notice that the oscilloscope controls are grouped into sections. The control groups we will
concentrate on are the sections labelled Vertical, Horizontal, and Trigger in Figure F-1.
An important concept in the use of the oscilloscope is the Trigger Condition. An oscilloscope waits
until the voltage either rises above or falls below a certain voltage and then commences to draw
the signal, starting on the left-hand side of the screen. The trigger condition is set by the Level and
TRIG Menu controls on the digital scope in Figure F-1. The Sec/Div dial, in the Horizontal controls
group, controls how large a horizontal chunk, in seconds, of the signal the oscilloscope shows.
75
Appendix F The Oscilloscope
Figure F-2 illustrates how the time setting and the trigger condition control how a periodic
triangular voltage signal would be displayed on the screen. Figure F-2 shows a trigger condition
set to 2 Volts with a rising slope and a timing length T. Once the first trigger condition is met, the
oscilloscope draws a portion of the signal of time length T on the screen. The oscilloscope then
waits a small time delay T until the trigger condition is met again and then it redraws the screen,
and so on. If the timing length T is small enough, you don’t notice the redrawing of the screen and
your oscilloscope appears to have captured a picture of a segment of the voltage signal.
Third trigger
V First trigger Second trigger
condition condition condition
4
2
signal
0
-2
-4
T T T T
T T
What would the signal have looked like on the screen if the trigger condition had been 2 Volts
and a falling slope?
If your trigger voltage is above the signal maximum, or below the minimum, you might expect to
see nothing on the screen. However, what happens is that the oscilloscope displays first one trace
or snapshot of the first T seconds of the signal, then the next, and the next, and so on. Since these
traces are not identical, you observe a confusing jumble that appears to move as old traces
disappear while new ones are added.
An important and useful feature of the oscilloscope is that it can display two voltage signals at
once, allowing all sorts of interesting comparisons. The first signal is yellow on the display and is
called CH 1 (Channel 1) and the second CH 2 (Channel 2) is blue. The positioning controls (see
Figure F-1) allow the signals to be moved around and even laid on top of one another. Pressing
the buttons labelled [1] or [2], and using the choices presented by the Options buttons, allows you
to show CH 1 only, or CH 2 only, or both together.
There is a complication with measuring two signals at once. Equipment plugged into wall outlets
and handling large voltages and currents must be grounded. In the event of a malfunction, the
76
Appendix F The Oscilloscope
ground provides a low resistance path for a current to travel. As currents prefer the path of least
resistance, the high-resistance person using the equipment is safer. However, this means that both
channels must have a connection to ground. A typical arrangement might look like Figure F-3. The
black lead from each of CH 1 and CH 2 would need to be connected to ground on the power supply.
When the red lead is connected to the circuit, the channel reads the voltage between that point and
ground. As a result, in this configuration, CH 1 measures V1 + V2 and CH 2 records V2.
There is a neat trick that allows the oscilloscope to display V1 for the circuit in Figure F-3. If you
press the pink MATH button, the option buttons allow you to show a third signal in red, either Ch1
– Ch2, Ch1 + Ch2, or Ch1 Ch2. Choosing Ch1 – Ch2 combines the signals from CH1 and CH2,
so in this case the oscilloscope would display a third trace (in red) of (V1 + V2) + (–V2) = V1.
(Note you can press the Math button again to turn this trace off.). Make sure CH 1 and CH 2 both
have the same scale. The scale of the third trace may be set by the Multipurpose Knob.
Oscilloscope
CH 1 CH 2
R1 R2
V1 V2
Power Supply
Figure F-3: How to connect an oscilloscope to read two voltages at once.
The value of the Horizontal|Scale dial is displayed along the bottom edge of the oscilloscope
screen along with other useful information about the signal. The timing length T of any feature
displayed on the screen is determined from the number of horizontal squares times the setting of
the Horizontal|Scale dial. For example, if the screen is 8 squares wide and the Horizontal|Scale is
set to 0.1 ms, then total time displayed on the screen is (8 squares 0.1 ms) = 0.8 milliseconds.
However, rather than measuring squares off the screen, one can use cursors. Cursors and the
Cursor options menu are described a little later in this appendix. Also, many common
measurements of periodic signals are automatically calculated and can be displayed using the
Measure options to be described later.
77
Appendix F The Oscilloscope
The value of the Vertical|Scale dial is displayed along the bottom edge of the oscilloscope screen,
on the left-hand side. The range of voltage on the screen is likewise determined by the number of
vertical squares times the setting on the Vertical|Scale knob. For example, on a screen with 8
vertical squares and the CH 1 Vertical|Scale dial set to 1 Volt, the maximum range is (8 squares
1 Volt) = 8 Volts, i.e., the top of the screen is 8 Volts above the bottom of the screen.
There are two Vertical|Scale dials because you can set the scale for the signal on Channel 2
separately from Channel 1. Watch out for this when you make voltage measurements! (Both
signals do have the same time setting, however.)
Each of the channels has a small arrow displayed along the left-hand edge of the scope screen that
marks where the position of 0 Volts, or ground, is. For Channel 1, the arrow looks like this: “1“.
This marker is useful if you have used the vertical Position controls to move the signal around; it
allows you to see where zero volts is, and you can use the vertical Position knob to move the zero
line to a convenient location.
Because there are so many settings on an oscilloscope, it is always best to have your oscilloscope
set to a standard default arrangement. These default settings are outlined in Table F-1. The
oscilloscope we use has a Default Setup button on the second row of horizontal buttons (see Figure
F-1).
Note that the oscilloscope has a button in the upper portion of the control panel called Autoset.
Pressing this button will often display your voltage signal looking approximately right. Although
you should ensure you know how to adjust your vertical and horizontal Scale knobs to display
your signal correctly, Autoset can often make that task a bit faster.
When you are examining a single channel, say by pressing [1], the Options buttons let you switch
between AC (for alternating current), GND (for ground), and DC (for direct Current) Coupling.
Coupling refers to how the oscilloscope connects to the circuit. Setting it to GND (for ground)
connects the red lead on the channel to the ground (“grounds out the signal”) no matter where it is
connected in a circuit. Since the oscilloscope and power supply are connected to the same building
electrical system ground, the difference between the two ground connections is therefore zero
voltage and a horizontal line is displayed across the screen. This switch setting is useful is you
have used the vertical positioning controls (See Figure F-1) to move the signal around; it allows
you to find where zero volts is. You can then use the vertical positioning control to move the zero
line to a convenient location.
78
Appendix F The Oscilloscope
Note also that some signals average out to zero over a full period and are said to be pure AC
(alternating current), some signals have a constant or DC (Direct Current) voltage, and other
signals are a mixture of AC and DC. The reason we use the “Coupling: DC” settings for our
oscilloscope—even when we’re looking at an AC signal—is because the AC option causes the
oscilloscope to internally remove any DC portion from a mixed signal. Generally, we don’t want
to ‘hide’ the DC component of our signal like that. Also, the manner in which the DC portion of
the signal is removed introduces a capacitor to your circuit. At low frequencies in particular, this
capacitor can badly distort the shape of your signal.
Probe
Ordinary leads
Attenuation
information
– + +
BNC connector
–
You do have to be careful about the properties of your connection, i.e. your leads or probes, and
the settings on your oscilloscope. Probes have circuitry that can attenuate, or lower, voltages while
leads do not. Common rates of attenuation are 10X to 200X. Some probes can switch between one
value of attenuation and another. The attenuation values of a probe are marked on the end of the
probe that connects to the oscilloscope.
Your oscilloscope in its default setting is expecting a probe with an attenuation of 10X and
accordingly multiplies any voltage reading by 10. If the probe setting on the oscilloscope does not
79
Appendix F The Oscilloscope
match the actual attenuation of the probe, the voltage readings on the scope can be either too high
or too low. From the Measure menu, you can see and change the probe setting on your oscilloscope
(see Figure F-1).
When a BNC connector is used, remember that red is the high (+) terminal and black is the (–) or
low terminal. It is recommended that you use leads of the same colour as the terminals to avoid
confusion.
CURSOR MENU
Type: Time
Source: CH1
Once you have set these options, you should notice three things:
1) Two vertical lines have appeared on the oscilloscope screen.
2) On the menu displayed along the screen’s right-hand edge, there is now a section that has
1
values for two quantities called “t” and “ ∆𝑡 ”.
3) On the menu, you can now select either Cursor 1 or Cursor 2 by using the buttons.
80
Appendix F The Oscilloscope
Cursor 1 and Cursor 2 refer to the two vertical lines that appeared on your screen. You can select
these lines by using the multi-function buttons beside the screen, and then move them back and
forth across your screen by using the multi-function knob that is located in the upper left-hand
corner of the oscilloscope’s control panel (see Figure F-1).
1
When you move the cursors, the values for “t” and “ ∆𝑡 “ change to reflect the time difference
between the two cursor positions. You can, for example, place your cursors at the beginning and
1
end of a waveform and then read off the period (t) and frequency ( ∆𝑡 ) of your signal.
You can also change the Cursor menu to “Type: Amplitude” to measure the voltage of your signal,
and you can change the Source to CH2 to measure the voltage, period or frequency of a signal on
that channel.
1) Insert your USB drive into the slot for it on the front of your scope. You will have to wait a
few moments for the scope to recognize your USB drive. (Note that the scope will not be able
to work with USB drives that have too large a memory size.)
2) Press the “Save/Recall” menu button located at the top of the scope’s control panel.
3) Use the multi-function buttons beside the screen to set the following:
SAVE/RECALL MENU
Action: Save All
PRINT Button: Saves Images to File
4) If you wish the image to also include information from the cursor menu, press the Cursor
button again and make sure the screen is displaying everything you want it to.
81
Appendix F The Oscilloscope
5) To save the current screen image to your USB drive, press the SAVE button located in the
upper left-hand corner of the scope’s control panel, just underneath the unlabelled multi-
function knob.
It will take a few moments for the scope to finish saving the image to your USB drive. The image
will be saved to the upper levels of your drive. (There is a way to save to a particular folder on
your drive, but it’s a bit of a hassle to set that up!)
The default viewer, Windows Picture and Fax Viewer, will allow you to do this, but a nicer
program to use is IrfanView, which is located on the desktop of many of the lab computers.
If you open your image using IrfanView, choose the following options when you print it:
82
Appendix G Graphing
83
Appendix G Graphing
In Step 4 of the Chart Wizard, Figure G-4, you will be asked where to put the graph, either in the
sheet with the data, or on a separate chart page of the workbook. It is usually a good idea to have
it on its own sheet for editing purposes.
MS Excel calls the curve it fits to a set of points a ‘trendline’. To generate a trendline, highlight
the data points by clicking on one of them. Next, right-click to bring up a context window and
choose Add Trendline, as shown in Figure F-6a. The window that appears has two tabs. The first
tab, Figure G-6b, lets you choose the sort of curve that Excel will fit to the data. You have lots of
choice: Linear, Logarithmic, Polynomial (note a quadratic is a polynomial of order 2), Power, and
Exponential. We are not interested in Moving Averages. In the second tab, shown in Figure G-6c,
you can display the equation of the line and the R-squared value. The R-squared value is measure
of how well the data fits the trend line, with 1 being a perfect fit. Try the different types of trendline
until you get the best fit.
84
Appendix G Graphing
Figure G-7 shows the finished plot with the data, a trendline, and an equation.
85
Lecture Notes
86
Lectures - I Introduction
Notes I Introduction
The microcontroller (MCU) is simply a computer designed to work with other machines rather
than people. Because of this, it doesn’t need a monitor, keyboard, mouse, or any other device that
requires human interaction.
Most MCUs have a CPU, RAM, ROM, and some EEPROM (Electrically Erasable Programmable
Read Only Memory). Inexpensive controllers that are designed to be programmed once (for a
particular application) may omit the EEPROM, because there may be no need to ever change the
program once the MCU goes “into production”.
The MCU you will use, the PIC18F4525, has 48kb of flash memory which allows you to erase and
reprogram it at least one hundred thousand times, so you do not need to worry about “wearing out”
the chip. This flash memory can hold your program and any data storage required in your
application. Technical details are available in the datasheet.
You will create the circuits you will use with the MCU on a “breadboard”. On a PC, using a
program called the MPLAB IDE, you will create and test the programs that will run on the MCU.
The programs will be written using the C language. When finished, you will transfer the program
to the MCU using a USB connection and a device called the PICkit 2 or PICkit 3 programmer.
The PIC18F4525 is a 40 pin device. Some of the pins are used to connect a battery or power supply
to the MCU. The rest of the pins are for inputs and outputs. The pin-out diagram of the P18F4525
is shown in Figure I-1 below. Note the notch on the chip. This orients the chip and indicates the
top of the chip and which pin is number 1. The PIC18F4525 has many features built into the chip,
including digital input and output (DIO), timing and square wave production, serial
87
Lectures - I Introduction
communication, and analog-to-digital conversion (ADC). Depending on the feature of the MCU
being used, the pins have different functions and names (this is called multiplexing).
BreadBoard
A breadboard is a convenient way to assemble circuits where the parts are small, such as
microchips, diodes, and resistors. Pinholes in the front of the breadboard let you securely connect
circuit elements to it. The sets of pinholes are electrically connected to each other by conducting
bars in two different patterns, short horizontal bars and long lengthwise bars, under the plastic
cover. See Figure I-2. This setup allows parallel and series arrangements of elements to be put
together quickly. When using wire connectors, make sure to keep them short and neat!
We will start this course with a review of C programming and the particulars of programming an
MCU (microcontroller) unit. We will put a lot of emphasis on understanding how numbers
(integers) are stored in memory.
We will also review basic electric circuits and Kirchhoff’s Rules. We will also cover the simplified
notation used in electronics for circuit diagrams.
Separately, we will learn how to use special modules on the MCU, including timing, DIO, ADC,
and PWM.
The main project for this course, the line following robot, is a simple machine whose brain is the
PIC18F4525 chip. The robot, or more correctly the program you write for the robot, must correctly
interpret sensor input for the surface the robot is on and correctly adjust the wheel motion to follow
a black line. You will be applying your programming skills and your knowledge of the modules
of the MCU to complete the task. In class, we will spend some time discussing algorithm design,
testing, and debugging. We will spend a short amount of time going over proper formal report and
public presentation styles.
88
Lectures - II C Programming
Notes II C Programming
You all have taken an introductory course in C++. C++ is a grandchild of C and, at the introductory
level, C and C++ look very much the same. The version of C you will be using, XC8, is specifically
designed for use with the Microchip PIC18 chips.
You need to know basic decision handling using if…else and switch() statements. This further
means that you need to understand comparisons such as greater than >, equivalent to ==, not !, etc.
You need to be familiar with using some of the standard library functions in C. Some examples
would be printf() in stdio and ln() in math.
XC8 and C
Because the MCU has limited memory, the XC8 is in many ways a stripped down version of
standard C. Not all the functions and libraries in C are available with the XC8 compiler. Even
when those functions do exist, they may not have all the same capabilities as standard C. For
instance, the function printf(), which will print out text and numbers to an output device, needs a
small helper function putch(), which understands how the PIC operates. This helper function is
given to you in the configureUSART.c and configureUSART.h library. Conversely, there are a
number of libraries and functions in XC8, and not in C, which pertain exclusively to working with
microcontroller units.
The smallest program you could write would consist of a single file containing a single function.
That single function must be called main(). Bigger programs and projects could have multiple
source files and many functions, but there can only be one main() function. Program execution
89
Lectures - II C Programming
starts from inside the main() function. A simple program to add two numbers and display the
result would look like the following, Figure II-1, if we were writing a program for a PC:
Output on display:
Sum of two numbers 15 and 22 is 37
On a PC, the standard output is the display, and the standard input is the keyboard, and this PC
program would output “Sum of two numbers 15 and 22 is 37” in a window on the PC. But
your MCU doesn’t have a keyboard or display, so programming the MCU with this program will
appear to do nothing. We need a display, so we will attach a small two-line, 32 character LCD to
the MCU to be our standard output. Even though now we have a display, this program will not
produce an output. We need to include instructions to tell the MCU which pins are used in the
connection and how to send data, in both format and speed, to the LCD. So even if you are a
programmer with a good knowledge of C, you have a lot more to learn if you want to program
MCUs. You need to know how to connect electronic components together and you need to know
how to control the different features of the MCU.
The first step to programming the MCU is to include a library called xc.h , see Figure II-2, that
contains all the MCU-specific information and definitions and functions we will be using. You
will include this library in every program you write. You will need to know the functions in this
library if you wish to use ADC, PWM, DIO, etc. The second step is to know which functions to
call to access the feature you want. To get you started and to simplify communication with the
LCD, we are providing you with a small library we wrote called configureUSART.h. It contains
the function configureUSART() to let you set up communication with the LCD display using
stdio.h and printf(). As mentioned earlier, the MCU outputs data differently than a program
compiled to run on a PC would. As a result, to keep the printf() function in XC8 looking and
behaving as it does in most C compilers meant for PC, a small helper program putch() needs to
be included. That function is in this library configureUSART.h as well.
90
Lectures - II C Programming
You can and will write your own functions. The compiler insists that you prototype the function
before it is used or defined. A PC example with several functions is shown below in Figure II-3.
91
Lectures - II C Programming
if (value >= 0 )
printf(“Result is %i”, value);
else
printf(“Result is negative”);
}
Output on display:
Result is 28
Figure II-3: A program with user-defined functions.
In C, prototypes must always be defined at the top of the file after directives (e.g. #include and
#define statements). A function can be of datatype void, char, int, or float, meaning that it
returns nothing, an integer number of size char, a integer number of size int, or a decimal number
of size float. The functions may take no variables (void), or multiple variables of different
datatypes. Note that although a prototype of the form
is legitimate C, it is not useful. It is much better programming style to include the variable with a
meaningful name
For easier editing and reusability, the source code can be broken up into separate files. Typically,
a family of functions is put into the same file. Another file, called a header, will contain the
prototypes of those functions. Other files can call those functions as long as the header is included
using the #include directive. The previous program could be split as follows:
92
Lectures - II C Programming
Well-named functions are a great way to write programs that you or anyone else can understand.
For example, see Figure II-7.
#include <xc.h>
#include <stdio.h>
int main(void)
{
float voltage;
PrintToLCD(“Hello World”);
TurnOnLEDonPin21();
voltage = ReadADCVoltageOnPin20();
printf(“Voltage = %f”, voltage);
return 0;
}
//functions to be defined
93
Lectures - II C Programming
Libraries
A collection of related functions in a C file and the associated header file is usually called a library.
In group work, large projects are split into smaller tasks with different members of the group
assigned to code different libraries. Usually, although we won’t be doing it, the C file is compiled
to an object file (extension .o) which is not a text file and therefore cannot be read. The header file
is simply a text file, so it is important that comments and instructions to users of the library be
included here. Some brief points:
Headers must end with a blank line. Leave it out and you can get error messages that point
to the wrong place in the program, usually a line with no problem.
Headers mostly contain function prototypes but may also have useful #define statements.
The source C file and the header file must have the same name.
Both the source C file and the header must be added to your project.
The source C file must #include its own header file before any of the code for the
functions. If the functions call other functions from outside that C file, you must also
#include the appropriate headers for those libraries. For example, if your function in your
library calls printf(), you must have #include <stdio.h> in your source C file.
94
Lectures - II C Programming
int main(void)
{
/* put instructions that are to run only once here */
95
Lectures - III Numbers
1 11
178786485
6211456
184997941
There are similarities between how numbers are added in the above example and how programs
and processors handle the same operation. Like you, the program needs space to write the given
numbers, the answer, and intermediate steps like carrying the one. It doesn’t use scratch paper, of
course, but solid-state memory instead. Unlike you, it doesn’t use decimal, base 10, numbers but
rather binary, base 2, numbers – the zeroes and ones of digital logic.
All RAM “data” is accessed and saved in the PIC MCU in 8 bit bytes. One-bit access and saving
are also possible. To access multiple bytes, the PIC must do sequential read or writes. A C (or
C++) compiler will set aside the correct number of storage locations needed for a particular
variable type when a variable is defined. Common variable keywords are char, int, and long,
which indicate variables that are stored in one, two, and four bytes respectively. Note that although
char is short for character, it is in fact a number type, as shown in the code snippet below.
char a = 10;
char b = 5;
char c;
c = a + b;
A char variable has one byte or eight bits, and each bit can either be a zero or a one. The possible
values of that variable are 00000000 to 11111111. By convention, we break the set of eight bits
into two sets of four bits called nybbles for easier reading, e.g. 0000 0000 and 1111 1111. With
eight bits, there are exactly 28 = 256 possible numbers. By the same logic, an int can hold exactly
216 = 65,536 numbers while a long can hold 232 = 4,294,967,296 numbers. There is a further
categorization of these integer types into unsigned and signed. An unsigned variable type
contains only positive integers and zero, while the signed variable type can hold negative numbers
as well. When you leave out unsigned or signed in the variable declaration, the default is signed.
So in the code snippet above, the variables a, b, and c could hold negative numbers if we wished.
96
Lectures - III Numbers
With unsigned variables, there is a simple relationship between the memory space and the number
it represents or holds. For example, in a char, 0000 0000 = 0, 0000 0001 = 1, 0000 0010 = 2, and
so on up to 11111111 = 255. The relationship between memory and signed numbers is more
complicated and will be discussed later.
Overflow
One point to keep in mind is that the memory space for a variable is fixed. Consider the code
snippet below,
In an ideal world, we would have the result that c = 300. But 300 is bigger than 255. In binary, 300
= 1 0010 1100 but this is nine bits long. There is no room for the 9th bit to be stored in the allotted
memory space, so it disappears and we are left with c = 0010 1100 = 44. Memory space is like a
loop or ring going from 0000 0000 to 1111 1111 (in the case of a char variable). When you add 1
to 1111 1111, you are back to 0000 0000. This happens on mechanical counters, like your car
odometer, as well. This rollover or overflow is a common source of programming bugs. You need
to make sure that there is sufficient memory space for the calculations your program will do.
We don’t need to convert 300 to binary to see what number a computer will treat it as. Simply note
that 300 = 1 × 256 + 44. This is equivalent to subtracting 256 (28) as often as necessary until the
remainder is less than 256. What number would unsigned char a = 1200 be?
Q. Consider the following unsigned int variables. What numbers are they equivalent to?
97
Lectures - III Numbers
10000000
10000001
11111110
11111111
00000000
00000001
00000010
01111110
01111111
… …
For a char variable, signed values run from –128 to 127. For an int, signed values run
from -32,768 to 32,767, and for a long, from –2,147,483,648 to 2,147,483,647.
Rollover will also happen with signed variables. For example, signed char a = 128 is equivalent
to –128 and signed char b = -129 is equivalent to 127. In general, if you wish to know the
actual value of unsigned char number, either subtract or add 256 (28) until the remainder is in the
range –128 to 127. For instance, signed char c = 200 is equivalent to –56 since 200 –256 = -56,
and signed char d = -250 is equivalent to 6 since –250 + 256 = 6.
Q. Consider the following signed int variables. What numbers are they equivalent to?
In your code, you can write any integer number in any of these formats, like the code snippet
below, where a = b = c. Each number is stored exactly the same in memory.
The printf() function will display a number of size char or int, one or two bytes, in decimal,
octal, or hexadecimal formats using the formatting specifiers %u for unsigned integer, %i or %d for
signed decimal, %o for octal, and %x for hexadecimal such as
98
Lectures - III Numbers
Note that the hexadecimal and octal output may look like decimal output. You may wish to do the
following to make the base clear.
Output
Various formats: a = 0x7d b = -5 c = 125 c = o175.
Unfortunately, XC8 (unlike some C Compilers) does not support a binary %b format specifier.
Please note that the printf() function allows a great deal of flexibility in what you can print, but
the downside is that there is limited error checking and if you do not match the formatting to
variable type, the results may not make sense, as demonstrated in the code snippet below.
Output
Unsigned a = 65355 - Signed a = -1
Unsigned b = 255 - Signed b = 255
For numbers of size long, 4 bytes, use the formatting specifiers %lu for unsigned decimal, %ld for
signed decimal, and %lx for hexadecimal, rather than %u, %d, or %x. As mentioned, %u, and %x work
with two bytes, not the four of a long number. The printf() function does not check to see if the
variable agrees in size with the formatting specifier, with interesting results. As shown in the code
snippet below, the printf(“%u”, longnumber) only prints the lower two bytes. This is equivalent
to the remainder after dividing the number by 216 = 65536. You can use %u twice, as in
printf(“lower: %u upper: %u”, longnumber), but then you get the remainder and number of
times that 65536 goes into the value. You can use the hex specifiers twice to get the right answer
(although in the wrong order), but you need to keep leading zeroes in the lower byte, 4 for hex (see
the code example for details).
Output
k (base 65536) = 2562
k = 2562 + 11*65536
k = 0a02 b
k = 723458 = b0a02
99
Lectures - III Numbers
Q. What would printf(“%x%x %b%b”, k, k) display? What are the decimal equivalent
values of these numbers as displayed?
If you use a literal number (e.g. 50000) rather than a variable with the printf() function, you will
also run into problems because C assumes that any literal number without a decimal in it is an
unsigned int. The cast operator is one way of explicitly making the correct amount of memory
space available, although it is typically used with variables. The simplest method to indicate that
the literal is unsigned is by adding a u at the end of the number. Similarly, adding a terminal l or
ul indicates that the number is signed long or unsigned long.
Output
195
-15536
50000
-15536
50000
You have your choice of printing a decimal number in ordinary or scientific notation using %f or
%e.
100
Lectures - III Numbers
printf("%f\n",12300.5250000);
printf("%12.5e\n",12300.5250000); // Sci notation to 5 decimal places.
Output
123000.525
1.23001e+4
int a = 8;
int b = 52;
int c = 100;
int d;
d = a/b*c;
printf("%i",d);
Output
0
We are usually surprised at the result because we know that 8/52 × 100 = 15.38, which when
rounded to an integer should give 15. However, the first operation, 8 52, is between integers and
must go in a space for an integer. The value 0.1538 is thus rounded to zero before being multiplied
by 100, yielding 0. You can make room for the calculation by using a cast operation, but you need
to use care in doing so, as shown below
int a = 8;
int b = 52;
int c = 100;
int d;
Output
0
15
The first cast operation of a/b to float occurs after the division and is thus too late. The variable
a must be promoted to float first. A float divided by an int is of size float. A float multiplied
by an int is also a float. The final step is the rounding to an int to get the value of d.
Q. What will the following code produce on output? How would you change the code to
produce more sensible results?
unsigned char a = 70;
101
Lectures - III Numbers
d = a*b – c;
printf(“d = %i\n”,d);
You can assign the value to a char variable either numerically or symbolically, as shown in the
code snippet below. The formatting specifier %c is used in the printf() function to direct the
processor to use the ASCII symbol for that number. Note that $ is number 36 in the ASCII table,
and H is 72.
char a, b, c;
int d = 65*256 + 66.
a = 36;
b = ‘$’;
c = a + b;
printf("a = %c b = %c c = %c\n",a,b,c);
Output
a = $ b = $ c = H
d = B
Note that %c will convert an integer to a byte by ignoring the upper half. The letter B is number 66
in ASCII.
Bitwise Operators
C has a set of operators that derive from Boolean logic. They are and, or, exclusive or, and not (&,
|, ^, and ~). Note that the number 1 is synonymous with true and 0 with false. The truth tables for
the first three operators are
& 0 1 | 0 1 ^ 0 1
0 0 0 0 0 1 0 0 1
1 0 1 1 1 1 1 1 0
102
Lectures - III Numbers
c = a & b;
printf("a = %u & b = %u => c = %u\n\r",a,b,c);
c = a | b;
printf("a = %u | b = %u => c = %u\n\r",a,b,c);
c = a ^ b;
printf("a = %u ^ b = %u => c = %u\n\r",a,b,c);
a = ~a;
b = -b;
printf(“not a = %u and not b = %u\n\r”, a,b);
Output
a = 3 & b = 5 => c = 1
a = 3 | b = 5 => c = 7
a = 3 ^ b = 5 => c = 6
not a = 252 and not b = 251
Two other operators that work at the bit level are the left shift << and right shift >> operators. The
usage is clearest on integers in binary format. If c = 0b00101 then c = c << 2 yields c = 0b0010100.
Everything moved left two places, and the new positions are filled with zeroes. Similarly, if c =
0b00101, then c = c >> 2 moves everything right two places. This yields c = 0b00001. Shift
operators are equivalent to c = c / 2n (right shift n places) and c = c * 2n (left shift n places). The
shift operations are much faster than the equivalent expressions for division or multiplication.
c = a << 2;
printf("a = %i, c = %i\n",a,c);
c = b >> 2;
printf("b = %i, c = %i",b,c);
Output
a = -32000, c = 8384
b = 32000, c = -3072
103
Lectures - III Numbers
Bit Masks
Many of the functions, like DIO and ADC, of the MCU are controlled by a single bit of special
bytes of computer memory called Special Functions Registers (SFRs). The bit often turns a
particular aspect on (1) or off (0). Since the smallest quantity C can handle is the byte, bit masks
are a way to read the current setting of a particular bit or to set a particular bit.
If we have unsigned char variable named sfr, and two more variables mask_bit5 = 0b0010
0000 and mask_bit6 = 0b0100 0000, then we can set bit 5 (make it have value 1) of sfr with
the code
or more compactly
sfr |= mask_bit5.
If we wish to clear bit 5 (make it have value 0) of sfr, we use the code
or more compactly
Many of the functions in the XC8 libraries are written to take advantage of bit masks, although in
a different fashion, by using a literal (i.e. a number rather than a variable) bit mask. For example,
the 8 bits of the config parameter of OpenUSART(unsigned char config, unsigned int
spbrg) function for serial communication, on page 1202-1204 of the
MPLAB_XC8_Peripheral_Libraries.pdf, controls a variety of options. An extract of the PDF for
the function is shown in Figure III-2 on the next page.
104
Lectures - III Numbers
Interrupt on Receipt:
* USART_RX_INT_ON
* USART_RX_INT_OFF
* USART_RX_INT_MASK
usart Mode:
* USART_ASYNCH_MODE
* USART_SYNCH_MODE
* USART_MODE_MASK
Transmission Width:
* USART_EIGHT_BIT
* USART_NINE_BIT
* USART_BIT_MASK
Reception mode:
* USART_SINGLE_RX
* USART_CONT_RX
* USART_CONT_RX_MASK
Baud rate:
* USART_BRGH_HIGH
* USART_BRGH_LOW
* USART_BRGH_MASK
Synchronous mode:
Fosc / (4 * (spbrg + 1))
105
Lectures - III Numbers
To make the programming more intuitive, a set of macro identifiers that are literal numbers are
defined in the xc.h file
Examining the identifiers above, we see that bit 7 set the transmit interrupt on or off. Similarly,
every other bit is controlled by a pair of identifiers. Rather than remember which bit does what,
the programmer ANDs the appropriate choices together. The bit patterns that are all 1s will have
no effect when ANDed with the others but should be included for clarity.
Q. Consider the literal bit mask in the code example at the bottom of Figure III-2. Using the
macro definitions, what is the 8 bit result?
You can now access the fields of the variable (studentname and studentnumber) using the
member of operator, which is a period “.”.
strcpy(APSCStudents[1].studentname, “Smith”);
APSCStudents[1].studentnumber = 10123456;
One can also create a structure that is a named groups of bits (such a structure called a bitfield), or
create a structure that is just named individual bits (this is also a structure called a bitfield).
106
Lectures - III Numbers
Microchip has done so for the Special Functions Registers (SFRs), and these variables are already
defined in p18f4525.h to make working with the SFRs much simpler.
Unions
Unions allow you to save space by stuffing more than one variable in the same memory. Of course,
it must make sense to do. Since we often deal with a set of bits, unions can let us deal with them
as a set or individually. Consider the following structure and union
struct setLED
{
unsigned Zero:1;
unsigned One:1;
unsigned Two:1;
unsigned Three:1;
unsigned Four:1;
unsigned Five:1;
unsigned Six:1;
unsigned Seven:1;
};
union sensor_union
{
unsigned char Byte;
struct setLEDs bits;
};
The structure could be used with a set of eight indicator LEDs. The zeroth bit, controlling the first
LED, is labelled Zero and the seventh bit is labelled Seven. The union lets us work with the bits
individually or as a group of size char.
A statement
creates the space for the information and initializes the values to zero. We can set the values we
want each LED to have using
indicatorLEDs.bits.Zero = 1;
indicatorLEDs.bits.One = 0;
indicatorLEDs.bits.Two = 0;
indicatorLEDs.bits.Three = 1;
indicatorLEDs.bits.Four = 1;
indicatorLEDs.bits.Five = 0;
indicatorLEDs.bits.Six = 1;
indicatorLEDs.bits.Seven = 1;
Then
printf(“%b”, indicatorLEDs.Byte);
107
Lectures - III Numbers
will output 0b11011001. We could reverse this, set all the bits at once using
indicatorLEDs.Byte = 0b00000001;
and now
printf(“%” indicatorLEDs.bits.Zero);
will output 1.
108
Lectures - IV Electric Circuits
Current
Electrons are negatively charged particles. The movement of electrons constitutes the flow of
electric current. The movement of a negative charge in one direction can be thought of as the
movement of a positive charge in the opposite direction. The direction of the positive charge is the
direction of current flow. You can think of an electric charge as water and of an electric current as
water flowing in a river or a pipe. The water flows in a certain direction – from the higher ground
to the lower ground. Electric current flows from high potential (voltage) to low. The measure of
current is ampere (A). An instrument called ammeter is used to measure the current that passes
through it.
+5 V
+
–
Resistors
V
109
Lectures - IV Electric Circuits
where V is the voltage drop across the resistor, I is the current through the resistor, and R is the
resistance of the resistor (see Figure IV-2). Resistors dissipate energy as heat (this is known as
Joule Heating) according to the formula
Resistors are rated by how much heat they can dissipate without melting, e.g. ¼W.
Colour Code
Resistors come in a wide variety of standard resistances. The value of the resistance is give by the
colour bands on the resistor. One band isolated from the other three gives the tolerance or accuracy
of the resistance value. The other three bands encode the value of the resistance. The colour code
is given in the diagram Figure IV-3 below.
Colour Value Multiplier
Black 0 100
Brown 1 101
Red 2 102
Value (×10) Orange 3 103
Value (×1) Yellow 4 104
Green 5 105
Multiplier Blue 6 106
Violet 7 107
Tolerance Gray 8 108
White 9 109
As an example, a resistor with red, green, and blue bands in sequence separated from a gold band
would have a resistance of R = (2 × 10 + 5 × 1) × 106 ± 5% = 25 × 106 ± 5%.
Q. What is the resistance of a resistor with yellow, violet, brown and silver bands?
Series/Parallel Connections
Resistors can be connected by conducting wires (or paths) in series, or in parallel, as shown in
Figure IV-4 (a) and (b). In Figure IV-4(b), we use the symbols designated for battery and resistors.
110
Lectures - IV Electric Circuits
I1 R1 I1
+ +
–
R2 –
Rseries
(a)
I2 I2
+
–
R1 R2 +
Rparallel
–
(b)
Figure IV-4: (a) Series and (b) parallel connections.
The equivalent resistors will draw the same current I from the battery. Their value can be calculated
as:
Rseries = R1 + R2 [IV-3 ]
1
R parallel R1 || R2 . [ IV-4 ]
1 1
R1 R2
Note that series resistors always carry the same current as one another.
Shorting
One consequence of Eqn [IV-4] is that
the equivalent resistance of two R1 a R1 a
resistors in parallel is always less than
either of the two resistors. Since
parallel resistors always have the
same voltage drop, V1 = V2. Ohm’s R2
Law, applied to this result, means I1R1
= I2R2. So the smaller resistor carries
the bigger current. These results reach
their extreme conclusion when a b b
resistor is shorted. This means a wire
of negligible resistance is connected Figure IV-5: A short circuit.
across a resistor, as shown in Figure
IV-5 below. Having no resistance, all the current flows through the wire and no current flows
through R2. The equivalent resistance of R2 and the wire is zero, so they can be replaced by the
wire itself as shown in Figure IV-5. Shorting resistors unintentionally can cause big problems. If
R1 is a small resistor, the current produced by the battery could be dangerously high, the power
111
Lectures - IV Electric Circuits
rating of the resistor could be exceeded, and the Joule heating could melt the resistor or even start
a fire.
BreadBoard
A breadboard is a convenient way to assemble
circuits where the parts are small, such as
microchips, diodes, and resistors. Pinholes in
the front of the breadboard let you securely
connect circuit elements to it. The sets of
pinholes are connected by conducting bars in
two different patterns, short horizontal bars and
long lengthwise bars, under the plastic cover,
see Figure IV-6. This setup allows parallel and
series arrangements of elements to be put
together quickly. When using wire connectors,
make sure to keep them short and neat!
Pins Connector bars
Figure IV-6: Breadboard connectors.
Electronics Style Circuit Diagrams
In electronics, batteries and other power supplies are seldom shown. A small circle indicates where
one end of the power supply goes. The other end is assumed to be connect to ground (symbol
). There may be more than one ground symbol in a diagram. This indicates that a wire
connecting the two points has not been drawn. The diagram below, Figure IV-7, illustrates how a
standard circuit could be drawn in this style. In general, we will use the electronics style from now
on.
+12
R1 R2
R1
R2
12 V R3
R3
112
Lectures - IV Electric Circuits
Kirchhoff’s Rules
More complicated circuits are usually analyzed using Kirchhoff’s Rules. Consider the circuit in
Figure IV-8 below. Points b and e, where three or more wires meet, are known as nodes or
junctions. A branch is a path from one node to another. In Figure IV-8 there are three branches,
15
a b c
12 V 20 I3
10 V
I1 I2
f e d
5
be, bcde, and bafe. These branches are said to be parallel, since the start and end nodes are the
same. Each branch carries a single current. Elements in the same branch are said to be in series, so
the 5 and 15 resistors in branch bcde are in series. At a node, Kirchhoff’s Current Rule is
obeyed,
I in I out , [ IV-5 ]
the sum of the currents entering a node equals the sum of the currents leaving a node. For the
nodes, we have
I1 + I2 + I3 = 0 (point b)
and
0 = I1 + I2 + I3 (point e).
Kirchhoff’s Current Rule is simply conservation of charge – electrons are neither being created
not destroyed in the circuit.
A loop is a closed path from a point back to itself. Where you start is arbitrary. Several loops in
the circuit in Figure IV-8 are fabef and dcbed. Around a loop, Kirchhoff’s Loop Rule:
V
loop , i
i 0, [ IV-6 ]
113
Lectures - IV Electric Circuits
is obeyed where Vi is the voltage drop across each circuit element in the loop. The orientation of
the voltmeter must be kept the same as you go around the elements of the loop. For loop fabef we
get
Note that battery voltages are positive when we travel from the negative to the positive side and
vice versa. The voltage drop across a resistor is negative if we travel around the resistor in the
direction of the current through it, and positive if the current is in the opposite direction.
Kirchhoff’s Loop Rule is simply conservation of energy since voltage is energy per charge.
Solution of the equations is straightforward, if tedious, as long as you have as many independent
equations as you have unknown currents.
Applying Kirchhoff’s Laws to circuits written in electronics style is somewhat simpler. As before,
at the nodes, apply circuits current laws. Note that even if a branch has no resistors in it, you still
assign a current, as between points e and f in Figure IV-9.
a b 15 c
+12 V +10 V
I1 I2
20 I3
I4
e d
I5 5
f
Figure IV-9: A complicated circuit in electronics style.
I1 + I2 + I3 = 0 (point b)
and
0 = I3 + I4 + I5 (point e).
114
Lectures - IV Electric Circuits
In electronic style diagrams, you don’t appear to have loops, but you do know the voltage drop
between each battery point and each ground point.
Here, we can go from a battery to ground four different ways and we get the equations:
and
Voltage Divider
Shown in Figure IV-10 is a simple circuit called voltage divider.
V0
R1 R2
The current flowing through both series resistors is given by I = /(R1 + R2). This current, following
Ohm’s law, will cause a voltage drop across the resistor R2. This voltage drop with respect to
ground is given as:
R2
V0 I R2 [ IV-7 ]
R1 R2 1
R1
R2
By selecting a proper ratio of R1/R2, we can obtain any voltage V0 between zero and .
Figure IV-10 is an electronics style drawing. Here we use the symbol “ground” to indicate common
connection (or common node). Any voltage indicated without additional reference information is
taken with respect to the ground.
115
Lectures - IV Electric Circuits
In circuits we can talk about the voltage or potential drop across one or more elements of a circuit.
This is what is measured by a voltmeter connected in parallel with the circuit elements. We can
also talk about the voltage or potential of a point. The voltage at a point is simply the voltage of
the point with respect to ground, that is, what a voltmeter would read if connected to that point and
to ground. See Figure IV-11.
Voltage or potential
drop across a resistor
Potential at a point or
voltage wrt ground
Potentiometer V0
A voltage divider can be conveniently built using a variable Sliding
resistor called a potentiometer (or pot for short). A electrode
R1 R2
potentiometer has the sliding electrode (a wiper), the
position of which can be manually adjusted to a desired
location resulting in a variable resistance. A voltage divider
using a potentiometer is shown symbolically in Figure R
IV-12. Figure IV-12: Potentiometer
116
Lectures - IV Electric Circuits
A diode is an electrical “valve” that conducts current only in one direction – from Anode to
Cathode. A special diode, called a Light Emitting Diode (LED), emits light whenever there is
current flowing through it. An LED is often used as an indicator of the presence (LED is “on”) or
absence (LED is “off”) of a voltage.
1 k I 5 mA
+5 V
117
Lectures - IV Electric Circuits
A small voltage Vforward, of between 0.5 and 2 V, must first be applied to a diode before a current
can pass through it. Diodes are non-ohmic, meaning that they do not obey Ohm’s Law. To apply
Kirchhoff’s Rules to a circuit containing a diode, we use a simple model. If the diode is forward
biased, assume a constant Vforward for the diode. This is a reasonable approximation if the
resistances in the branch are not huge. If the diode is reverse-biased, the voltage drop over the
diode is unknown but we can assume that the current in the branch with the diode will be zero.
The actual current is not zero, just very, very small – on the order of tens of A.
+12 V 3 k 2 k +12 V 3 k 2 k
(i) (ii)
Solution
In both cases (i) and (ii), applying Kirchhoff’s Rules gives the same equation
For (i), we assume that the LED is forward biased so that VLED = 2 V. The resistors are in the same
branch, so they must carry the same current. Resistors do obey Ohm’s Law, so we have
12 V = I(3000 ) + 2 V + I(2000 ) .
So I = 2 mA and VR1 = 6 V and VR2 = 4 V. The LED was forward biased since I is positive.
For (ii) the LED is reverse biased so that VLED is unknown. However the current in the branch is
zero, so by Ohm’s Law the voltage drops over the resistors are zero,
12 V = 0 + VLED + 0 .
For (ii), suppose we were not sure that the LED was reverse-biased. How do we check? Well we
assume it is forward biased with a 2 volts drop from right to left and that a current I also flows
right to left. The equation is then
12 V = –I(3000 ) – 2 V – I(2000 ) .
118
Lectures - IV Electric Circuits
Solving for I, we find I = –14/5000 A. Since the current is negative, left to right, and it cannot flow
that way through a diode, the current is actually zero and the LED is reverse biased.
20
50
Solution
LEDs light up if they are forward biased. We don’t know if the LEDs are forward biased, so we
will assume that they are and that the currents flow in the correct direction for forward bias.
+8 V
100 I1
+5 V
20 A
50
I3 I2
B
I1 = I2 + I3
8 V = (100 )I1 + (50 )I3 + VLEDB
5 V = –VLEDA – (20 )I2 + (50 )I3 + VLEDB
Solving the system of equations using VLEDA= VLEDB = 2V, and not showing the details, we get I1
= 17/800 A, I2 = –9/160 A, and I3 = 62/800 A. Since the direction of I2 is impossible, the first LED
A is actually reverse biased, and VLEDA is unknown and must be found. As well, we can assume I2
= 0. Our equations become:
I1 = 0 + I3
8 V = (100 )I1 + (50 )I3 + (2 V)
5 V = –VLEDA – (20 )(0) + (50 )I3 + (2 V)
119
Lectures - IV Electric Circuits
Capacitors
A capacitor, in its simplest form, is just a pair of parallel metal plates. When equal and opposite
charges +q and –q are placed on the plates, there is a voltage difference that depends on the
capacitance of the particular configuration
V = q/C. [ IV-8 ]
As you put charge on a capacitor, it gets harder and harder and takes longer and longer to add more
charge. The diagram below shows a capacitor-charging circuit and a plot of the voltage across the
capacitor plates as a function of time.
I +q –q
C
Schematic symbol
The equation for a capacitor being charged from zero is given by the formula
t
VC (t ) V 1 e RC . [ IV-9 ]
120
Lectures - IV Electric Circuits
The quantity RC is called the time constant of the circuit. When is small, the capacitor charges
quickly; when it is large, the capacitor charges slowly. After 5, the capacitor is over 99% charged.
A convenient way of thinking of a capacitor is that it acts as a switch. An uncharged capacitor is
VC
VC
R C
V
VC
When disconnected from a battery, a capacitor can hold its charge for minutes or even hours before
it leaks away. Providing a path between the plates discharges the capacitor much more quickly.
The discharging circuit and the VC-t graph are shown in Figure IV-18 below.
VC
VC
R C
V1
VC V2
t1 t2 t
t
VC (t ) VC e RC
. [ IV-10 ]
When the time constant is small, the capacitor discharges quickly, and when it is large, the
capacitor discharges slowly. After 5, the capacitor is over 99% discharged. The discharge of a
capacitor can be used for timing if you can measure voltage much faster than . Consider the two
voltages V2 and V1, shown on the graph in Figure IV-18. Using Eqn [IV -10], the time difference
between the voltages is
V
t 2 t1 RC ln 1 . [ IV-11 ]
V2
121
Lectures - IV Electric Circuits
Since we are dealing with DC circuits, we may want to know initial currents and voltage drops in
a circuit so that the PIC is safely buffered, as well as the steady state or t → case values. We can
readily apply Kirchhoff’s Rules to the resistor-capacitor circuits for these two limiting cases. For
the t = 0 case, note that the voltage drop across the capacitor is known. It will be zero, as given in
equation IV-12. This is equivalent to treating the capacitor as a short. For the t = case, the voltage
drop across the capacitor is unknown but constant. However, a fully charged capacitor stops
current from flowing in the branch where it resides. The capacitor acts like an open, so the voltage
drop across any resistor in the same branch will be zero.
Capacitors also come in handy when you want to measure a varying voltage. An ordinary DC
voltmeter reading the signal would fluctuate. A “sample and hold” circuit can be built using a very
fast switch, a series resistor, and capacitor whose time constant = RC is much smaller than the
time variation of the voltage signal. The switch is closed long enough for the capacitor to charge
nearly completely, then opened. Since a voltmeter has a huge resistance, connecting it across the
charged capacitor creates a circuit with a large time constant, thus allowing for a more leisurely
measurement.
Transducers
Transducers are electronic devices whose physical properties, usually voltage output or resistance,
change with variations in its surroundings such as temperature, pressure, ambient light, and the
like. For example, you may have learned that the resistance of a resistor depends on its temperature.
However this effect is weak when the temperature changes are only a few degrees and it would be
impractical to use a resistor as a thermometer. A thermocouple, on the other hand, is very sensitive
to temperature and is often used as a high temperature thermometer. A thermocouple utilizes the
Seebeck Effect in which two dissimilar metals in contact with one another develop a potential
difference. This potential difference is strongly dependent on temperature. In general, the
relationship between the external effect one wishes to measure and the physics property of the
transducer is nonlinear or, at best, only approximately linear over some range. As well, if the
voltage output is small, the result may have to be amplified.
122
Lectures - V DIO
Digital means having discrete, as opposed to continuous or analog, values. Here there are two
discrete states, 0 or 1, which can have a number of meanings: OFF or ON, FALSE or TRUE, LOW
or HIGH voltage. For a pin, digital output means that the pin acts as a power supply that can be
set to a high voltage (+5 V) or to a low voltage (0 V or ground). Both these values are approximate,
but no other values are possible.
Digital input means that the pin acts as a voltmeter that can detect whether the pin is connected to
a high voltage (+5 V) or to a low voltage (0 V or ground). If you connect the pin to 4 V, it will
probably read that voltage as high. If you connect the pin to 1 V, it will probably read that voltage
as low. If you connect the pin to 2.5 V, it probably won’t be able to decide if the input is high or
low and it may randomly oscillate between reading high and low. Note the “probably”. The
datasheet specifies which ranges of voltages will be read as high and which as low.
The pins on the MCU that have DIO functionality are grouped into sets of eight, called ports. On
the PIC18F4525, we have 4 ports: PORTA, PORTB, PORTC, and PORTD. The PORTA pins are
labelled RA0 to RA7 on the pin-out diagram, Figure V-1 below. For example, RA0 is pin 2.
Similarly, the other port pins are labelled RBn, RCn, and RDn, where n is 0 to 7.
We configure the pins for DIO using variables defined in a header file specific to each PIC. We
can configure a port as a group or as individual pins. Figure V-2 shows the code for an
entire port, while Figure V-3 shows the code for working with an individual pin.
123
Lectures - V DIO
RD7 RD0
outputs inputs
output input
High Low High impedance
+5 V 0V (meaning large resistance)
Note: Voltmeters need a large
resistance to work properly
TRISA, TRISB, etc., are the names of special 8-bit wide areas of RAM on the MCU that hold the
DIO configuration values. TRISA, etc., are called Special Function Registers (SFR). There are
SFRs for functions other than DIO as well. All the SFRs have structures defined in the header file
xc.h so that each bit can be addressed individually. For example, TRISAbits.RA3 is the 4th bit
(remember we start from 0, so 3 is the 4th bit) of that 8-bit area. The structures have the same name
as the SFRs.
In output mode, we can set whether the pin is high or low using another SFR. Each port has its
own SFR. The SFRs are PORTA to PORTD. Again, the SFR PORTx refers to a specific eight bits
of RAM memory of the MCU.
Again we can set all the pins of a port to any combination of high or low all at once, or we can do
any individual pin. See Figure V-4 and Figure V-5 for example code.
124
Lectures - V DIO
Let’s use this last bit of code to write a program that would actually set these pins when loaded
onto the PIC.
int main(void)
{
TRISAbits.RA3 = 0; // set RA3 as output
TRISAbits.RA1 = 0; // set RA1 as output
Q. The diagram below represents your MCU programmed with the previous code. The vertical
lines represent the power and ground lines of the breadboard. Four voltmeters are connected to
the breadboard and pins on the MCU. Note that when a red (dashed) lead (V on the DMM) is
connected to a higher voltage point and a black (solid) lead (COM on the DMM) is connected
to a lower voltage point, the voltmeter will register a positive value. Reverse the connections
and the value will be negative. What is the reading on each voltmeter?
125
Lectures - V DIO
VDD
+5 V
V1
MCU
V2
RA1
RA3 V3
V4
VSS
0V
V1 = __________________ V2 = _________________
V3 = __________________ V4 = _________________
Q. How would you change the code example to change the sign of the voltage readings?
WARNING! The pins and MCU have a maximum current they can provide (source) or that they
can accept (sink). Exceeding these limits can alter the operation of the MCU or burn it out.
According to Section 26.0 of the PIC18F4525 datasheet, the maximum current sourced or sunk
by any pin is 25 mA. The total current that can be sourced or sunk by all the pins operating at
the same time is 200 mA.
126
Lectures - V DIO
The pin can supply 5 V and a maximum current of 25 mA. From this, we can determine the smallest
equivalent resistance that any circuit attached to a pin can have, R = 5 V / 25 mA = 200 . Any
resistor of 200 or bigger should be safe to use with the MCU.
Since we cannot see currents, the circuit in Figure V-6 is not very impressive. To see something
happen, we usually add LEDs to the circuit, as shown below in Figure V-7. LEDs are convenient
to use because they require less than 5 volts to operate. LEDs have a very small resistance of
around 10 , so we must use them in series with a resistor to keep the pin currents within
acceptable limits. This also protects the LEDs themselves. LEDs will burn out if the current is too
great. Connecting an LED across 5 V without an added resistor is likely to burn it out. LEDs are
diodes, and diodes only carry current in one direction. Therefore, how you connect the LED to a
pin is important. Here are two LED circuits that will light up an LED using the previous code.
MCU
R
RA1
RA3
R
An entire port of pins, for example PORTD, can be monitored as long as the pins have already
been configured as inputs (digital voltmeters) using the TRISD SFR. The PORTD SFR
continuously monitors the pins and holds their current state. As an example, in Figure V-8 below,
certain pins are high, some low, and one left unconnected.
127
Lectures - V DIO
VDD
RD0
RD1
RD2
RD3
MCU
RD4
RD5
RD6
RD7
VSS
The code we would use to examine all these pins at once would be
int main(void)
{
unsigned char portvalue;
If this program was loaded into the MCU and run with the above circuit setup, the SFR PORTD
and portvalue would hold
ReadPORTD.c is a useless program, as we have not created a way for the MCU to tell us the status
of the input pins.
128
Lectures - V DIO
A better program follows. It monitors two input pins, and depending on their status, lights up two
LEDs attached to two output pins (see circuit schematic Figure V-9 after code).
// LEDinputsensor.c configure RA3 and RA2 to light LEDS based on RA1 and RA0
#include <xc.h>
int main(void)
{
TRISAbits.RA3 = 0; // set RA3 as output
TRISAbits.RA2 = 0; // set RA2 as output
VDD
MCU
R
RA3
RD1
RA2
RD0 R
VSS
In the circuit in Figure V-9 above, the LED at RA3 would be lit and the LED at RA2 would be
off. The nice thing about this circuit is that you can change the connections at RD1 and RD0 as
you wish.
Q. How would you change LEDinputsensor.c so that if RD1 is high, RA3 would be set low
(LED off) and the same for RD0 and RA2?
129
Lectures - V DIO
Q. If you do not change LEDinputsensor.c above, how could you change the LED circuits at
RA3 and RA2 so that if RD1 is high then RA3 is set low (LED off) and the same for RD0 and
RA2?
VDD
MCU
We need to spend a little time understanding how the button works. When the button is up, RD0
is connected to ground through the two resistors, so the voltage at the pin is 0.
The button is just a conducting piece of metal, so when it is pressed down, point A is at the same
potential as VDD, +5 V. What will be the voltage at RD0, point B, in this case? We can figure this
out if we remember that the input pin has a large resistance, on the order of millions of Ohms. We
can model the button circuit and the MCU as shown in Figure V-11 below.
VDD MCU
200 Rinput
A B
10 k
Point A is at 5 Volts when the button is pressed, and there are two parallel paths to ground. Because
both paths are high resistance, very little current is flowing. That’s good because it would just be
wasting power. The branch to the input pin is a series circuit of the 200- resistor and Rinput. If we
recall the voltage divider circuit, the voltage at point B is easy to find
130
Lectures - V DIO
The voltage drop across the 200 ohm resistor would be negligible. The only purpose of the 200
ohm resistor in this circuit is to limit current if the wrong program is downloaded into the PIC and
the I/O pin is configured as an output.
Now consider the program below to work with the circuit in Figure V-10.
int main(void)
{
TRISAbits.RA2 = 0; // set RA2 as output
input signal t
t
output signal
131
Lectures - V DIO
With buttonLED.c, if the button is pressed on and off, the LED flashes on and off in unison.
Note that the button and resistor circuit is a logic switch. Modern digital logic devices require a
discrete voltage level to work reliably. In the case of the PIC, the input will be taken as a high if
the input voltage is above 2 volts DC, and the input will be taken as a low if the input is below 0.8
volts DC. If the voltage is between 0.8 and 2.0 volts DC, we can’t predict what the input will be.
This circuit set the input pin voltage high or low but with very little current flow. This is in contrast
to a wall light switch in a house circuit, which allows a large current to flow through a lightbulb
filament, a resistor, to make it glow (see Figure V-13 below).
Switch
Q. Why don’t we want to use a mechanical switch in series with the MCU as shown in the diagram
below?
MCU
R
RA2
RD0 R
VDD Switch (large R)
132
Lectures - V DIO
To create a toggle in software, you must look for the times that the input signal goes from low to
high (known as a rising edge). When that occurs, you note if it is a rising edge or falling edge.
Source code to do this is in buttons.c:
#include "buttons.h"
return has_switch1_changed;
}
Note the elements of this program. There is the monitor_switch1_for_edges() function, which
sits inside the while(1) loop waiting to detect a rising or falling voltage signal or edge. It does
nothing until the edge is detected. A key part of the function is the global variable
last_switch1_edge. It holds the last reading from the input pin. That value is compared to the
current value. If the previous value is 0 and the current value is 1, we have found a rising edge. If
the values had been 1 and 0, we found a falling edge. If you need a second button, simply duplicate
the code, replacing the number 1 by 2 in the function labels.
int main(void)
{
133
Lectures - V DIO
// end program
Insert your code at the indicated spots to make the program do what you wish it to do.
Q. How would you modify the code to change on a fall (see Figure V-15)?
input signal t
t
output signal
DIO pins 33 to 37, RB0 to RB4, are only available if the following configuration directive is
included in your programs.
134
Lectures - V DIO
If PBADEN = ON, the pins are only analog and suitable for ADC, discussed later.
Somewhat similarly, pins RA0, RA1, RA2, RA3, and RA5 (but not RA4) are configured as analog
inputs suitable for ADC, discussed later. They will work fine as digital outputs to light up an LED
but will not work with buttons. Their PORTAbits.RAn (n = 0, 1, 2, 3, and 5) value will always
read zero whether the button is up or down. If you wish to use these pins for button input, you will
need to include two lines of code to override the default setting, as shown in the sample code below
#include <xc.h>
void main(void) {
while(1)
{
PORTDbits.RD1 = PORTAbits.RA5;
}
return;
}
One last point: you cannot use RA6 pin 14 for digital input or output because it is outputting the
PIC frequency (fosc/4) due to the following configuration directive
135
Lectures - VI Oscillator Frequency & Delays
0
TOSC t
Every operation – storing a number in memory, adding two numbers, or setting a port pin – requires
time. The minimum amount of time required on this chip, because of the limits of the computer
architecture, is 4TOSC. This minimum time is called an instruction cycle TCY = 4TOSC.
0
TCY t
The MPLABX has a method of letting us find out how many instruction cycles, and how much
real time, a C statement would take to execute by simulating the running of the program. If you
set “breakpoints” in the code, you can stop execution at those points. With the stopwatch feature
of MPLABX selected, you can measure the time elapsed between two breakpoints.
Figure VI-3 show the breakpoints (the red squares) and stopwatch window for a sample program.
The green arrow indicates the current line that the simulator is at, i.e. line 24. The stopwatch
window indicates that 6TCY, or 6 s at 1 MHz, were required to process the calculations between
this line and the previous breakpoint.
The program has comments showing how many instruction cycles are needed for each line.
136
Lectures - VI Oscillator Frequency & Delays
Note that i = 2 + 3 (line 16) takes less time than i = j + k (line 17) because the CPU has to fetch
the variables j and k from memory. Also, the same operation with 16-bit integers (line 21), rather
than 8-bit char (line 16), takes more computing time since there is more memory to juggle.
See that the use of i++ (line 19) is optimized and thus takes less time than i = i + 1 (line 18).
A breakpoint can only be inserted on a line of code and not at a blank line. If there is no code
available where you want the breakpoint, you can insert a NOP() function and place the breakpoint
on the NOP(). Think of NOP() as a do-nothing function. It takes 1 instruction cycle to use a NOP().
137
Lectures - VI Oscillator Frequency & Delays
This pin is multiplexed with other features. For instance, it is also RA6. These features are mutually
exclusive – at a given time, you can have one but not the other.
To tell the chip to output a timing signal on pin 14, i.e. to configure the pin, you need the following
directive.
#pragma config OSC = INTIO7 // puts osc/4 on RA6 pin 14 to check freq
Without this statement you would not have a signal on pin 14 and you could use the pin for DIO
(with the right code).
#include <xc.h>
#include “..\Common\osc.h”
void set_osc_32MHz(void)
{
int i;
Q. Modify the code to tell the MCU to operate at 2 Mhz. Repeat for 16 MHz.
138
Lectures - VI Oscillator Frequency & Delays
Delays
The XC8 compiler has a library of delay functions that wait a precise number of instruction cycles
TCY. The functions in the library, as listed in the header, are:
Cycles in _delay() cannot be 0 or exceed 179200. If you exceed the maximum, the compiler will
give an error message. Note that unit in _delay3() is of unsigned char type, and can only be
from 0 to 255. If you use a value of unit greater than 256, rollover occurs. As well, zero will not
give you a delay of 0 TCY (since any operations takes at least one instruction cycle), instead it give
you 3 × 256 = 768 TCY.
139
Lectures - VI Oscillator Frequency & Delays
The functions __delay_us() and __delay_ms() actually count instruction cycles, like _delay(),
but do a conversion to get the number of TCY to give the correct delay in microseconds or
milliseconds. These functions need to know the conversion factor, which is just the operating
frequency of the PIC MCU. Simply include the following definition, adjusting for your operating
frequency.
Since the smallest time unit is 1 TCY, the smallest value of x in these functions must convert to a
minimum of 1 TCY. Similarly, the largest value of x cannot give a time interval greater than 179200
TCY. A value of x that is too large will generate an error at compilation.
Turn pin on
Wait a while
Turn pin off
Wait a while
Repeat
int main(void)
{
Set_OSC_32MHz();
TRISAbits.RA3 = 0; // set RA3 as output
while(1) // anything in here repeats forever
{
PORTAbits.RA3 = 1; // set RA3 high
__delay_ms(10); // wait 10 ms
PORTAbits.RA3 = 0; // clear RA3 (low)
__delay_ms(30); (30); // wait 30 ms
}
}
We could measure this signal using an oscilloscope connected to RA3 (pin 5) and ground, or we
could also use the stopwatch feature to measure the on/off times in the simulator.
140
Lectures - VI Oscillator Frequency & Delays
We define the duty cycle of a square wave as the ratio of the ‘on’ time to the period of the square
wave times 100% (duty cycle = Ton/T 100 %).
Ton
V
0
T t
Q. How would the output square wave differ if you connect the oscilloscope between RA3 and
VDD? What would the duty cycle be?
Q. On a 32 MHz MCU, what delays would you need for a 0.250 ms period and 32% duty cycle?
Timing Diagrams
When the signal at one or more pins changes with time, a way to understand the circuit behaviour
is to draw the Voltage versus Time graph of the signals. For example, consider an LED and series
buffer resistor connected between pins RA3 and RA5 on the MCU. When RA3 is high and RA5
is low, the LED will shine. The code controlling the pins is
int main(void)
{
Set_OSC_32MHz();
TRISAbits.RA3 = 0; // set RA3 as output
TRISAbits.RA5 = 0; // set RA5 as output
PORTAbits.RA3 = 0; // initialize to low
PORTAbits.RA5 = 0; // initialize to low
while(1) // anything in here repeats forever
{
PORTAbits.RA3 = 1; // set RA3 high
_delay(10000); // wait 10,000 TCY
PORTAbits.RA5 = 1; // set RA5 high
_delay(10000); // wait 10,000 TCY
PORTAbits.RA3 = 0; // clear RA3 (low)
_delay(20000); // wait 20,000 TCY
PORTAbits.RA5 = 0; // clear RA5 (low)
_delay(20000); // wait 20,000 TCY
}
}
141
Lectures - VI Oscillator Frequency & Delays
Determine the period and duty cycle of the LED using a timing diagram.
Initial delay
Unknown
5V
RA3
0
5V
RA5
0
t (KTCY)
On
LED
Off 0 20 40 60 80 100 120 140
Reading Figure VI-6, the period is clearly 60 KTCY and the duty cycle is 10/60.
142
Lectures - VII PWM
Ton
V
0
T t
with a long period drives the LED, you see the LED blink on and off. At smaller periods, for
frequencies more than about 20 Hz, the blinking is too fast for your eyes to separate and the light
level appears constant, although dimmer than its full brightness, since it is partly on and partly off.
Thus, a small duty cycle means a dim light and a large duty cycle means a bright light. PWM is
thus the equivalent of a brightness control for an LED. Similarly for a motor, PWM is a way of
controlling motor speed. Motors have a certain amount of inertia. When the motor is turned on, it
takes some time to reach full speed, and when turned off, it takes some times to slow to a stop. For
a short period signal, a motor will never reach full speed during the on cycle and never completely
slow during the off cycle. For both the case of the LED and the motor, you would choose an
appropriately small period and change only the duty cycle. Speakers are the reverse. Our ears are
quite sensitive to frequency. When we change the period, the frequency is different, and we hear
a different tone. The duty cycle is usually kept constant at 50%, since the duty cycle hardly affects
what we hear.
DC motors are more complicated than LEDs, in that they can operate both forward and in reverse,
normally draw a lot of current, and can drive a large current backward into the signal source when
it switches on and off (back emf). Consequently, to protect the MCU, a separate chip called a driver
is usually put between the MCU and a motor. The driver can supply much bigger currents than the
MCU and is designed to withstand much greater reverse currents.
PWM is an efficient method to control brightness and speed, because energy is not wasted during
the offcycle, and so it is widely used. For comparison’s sake, consider an alternate control circuit
consisting of a variable resistor in series with the LED or motor. When the resistance is low, the
current is high and the LED is bright and the motor spins fast. When the resistance is high, the
current is low and the LED is dim and the motor spins slowly. This is not a very efficient approach
because energy is wasted in heating the resistor.
143
Lectures - VII PWM
PWM is controlled by the CCP1CON, CCP2CON, and T2CON control registers and the PR2,
CCPR1L, and CCPR2L data registers. The time base of the period and duty cycle is set by the
Timer2 Module. The properties of the Timer2 module are controlled by T2CON. The PWM signal
controlled by CCP1CON originates at the CCP1 pin, pin 17. Similarly, the PWM signal controlled
by CCP2CON originates at the CCP2 pin, pin 16. PWM is discussed in Section 15.4 of the
datasheet. The steps needed to activate PWM are listed in Section 15.4.4.
The XC8 Compiler simplifies the use of setting of the PWM SFRs through the use of four
functions. PWM also requires the use of the OpenTimer2() function from the timers library to
help set the period.
The x in the function names stand for 1 and 2, where 1 is for the signal from CCP1 (pin 17) and 2
is for CCP2 (pin 16). The OpenTimer2() function should be called before the OpenPWMx()
function, as it helps determine the period. In the OpenTimer2() function, the only argument we
need is which prescaler to use, T2_PS_1_1, T2_PS_1_4, or T2_PS_1_16, that is, every 1, 4, or 16
instruction cycles. Note that when PWM is using Timer2, you cannot also use Timer2 for timing.
The argument in the OpenPWMx() function, period, determines the period according to the formula
where prescaler is 1, 4, or 16. Note that period can only have values from 0 to 255. The on
time of the signal is chosen by the SetDCPWMx() function using the equation
The dutycycle argument can be any number from 0 to 210 (1024). If dutycycle is zero, the output
signal is always off. Note if Tdutycycle / TPWM is greater than 1, the duty cycle is 100% – always on.
Consider the case where the period is 24 and the prescaler is 4. The PWM period is thus 100
TCY. The only useable values of dutycycle would be between 0 and 400, giving dutycycles of 0
to 100 TCY. Anything above 400 is still full on. The on portion of the signal could last between 0
TCY and 100 TCY. The dutycycle, as a percentage of the period, is given by
dutycycle/4/(period+1)*100%.
The output can be shut off by using the ClosePWMx() function.
Q. At 32 MHz, what is the longest period a PWM output can have? What is the shortest? For
the shortest period, what duty cycles are possible?
144
Lectures - VII PWM
int main(void)
{
OpenTimer2(TIMER_INT_OFF & T2_PS_1_4 & T2_POST_1_1); // never changed
145
Lectures - VII PWM
The PWM1 and PWM2 square wave signals are restricted to having the same period but the duty
cycle can be different. Another restriction on the two signals is that the start of their on cycles will
coincide because of the shared Timer2 time base. So if, for example, the PWM1 is started and
there is a delay before the PWM2 signal is started, the PWM2 signal starts at the next rise of the
on cycle of the PWM1 signal, as show in the diagram below.
5V
PWM1
0
5V
PWM2
0
t
PWM2 called
PWM2 starts
Initial delay
Initial delay
here
here
Figure VII-2: The rising edge of the two PWM signals always coincide.
The default pin for CCP2 is pin 16 (RC1) but if you examine the pinout diagram you will see that
pin 36 (RB3) can also be CCP2. To switch from pin 16 to pin 36, use the configuration #pragma
directive
146
Lectures - VIII Timer0
Using either for-loops or delay functions means that nothing else can be done while we are waiting
for the count to be finished. Such delays are called “blocking” delays since we cannot do anything
else during their execution. If we wanted to check a button, no luck. During blocking delays, the
programs are largely useless and not interactive. We can only do one thing at a time. Fortunately,
the PIC18F4525 designers and their predecessors in chip design were a clever bunch. These chips
have separate modules that can run simultaneously. This chip has several (4) different timer
modules. The simplest function of a timer module is like an eggtimer. You set it for a certain
amount of time, you start the timer, and you wander away and do something else. You can either
check the timer periodically or you can listen for an alarm (if you have the fancier model).
A timer module works by checking the internal oscillator signals. It can tell time by counting either
rising edges or falling edges of the clock square wave signal. When you tell the module to start at
a rising edge, it waits for that first edge and then increments a counter for each successive edge. If
the setup is done appropriately, when the desired number of edges is reached, a flag is set, see
Figure 0-1. A flag is just a bit in an SFR. At any time, you can check this flag bit.
A counter, which is also an SFR, keeps a running tally of the number of edges counted. The counter
can be byte-sized, 0 - 255, or two-byte sized, 0 - 65535. You can start (write) the counter at any
value in its range. You can also read the counter at any time. However even 65535 edges, i.e.
65535 TCY, is not a long time on a PIC run at 32 MHz. For that reason, you can have the counter
increment only after multiple edges are counted, such as every 2 successive edges, every 4 edges,
and even to every 256 edges. This multiple is called the prescale value. Each timer module is
slightly different in terms of which multiples or prescalars are allowed. Check the
MPLAB_XC8_Peripheral_Libraries.pdf for details.
TCY
V
t
0
flag sets
Figure VIII-1: Timers start at the next edge (falling or rising as requested).
The counter, being limited in memory space, will roll over at 256 (1 byte counter) or 66536 (2 byte
counter). It is this rolling over that sets the special flag bit to 1. Note: once the flag is set it, remains
set (1) until it is cleared (0) manually. Some timer modules also have a postscaler value. If a
postscaler was 2, the 1 byte counter would not roll over until 256 2 = 512.
The XC8 Compiler has a library of functions for using the available timer modules
147
Lectures - VIII Timer0
The N in the functions stands for 0, 1, 2, or 3, the four timer modules of the PIC18F4525. Different
chips can have different numbers of timer modules. The flags for the four timers are respectively
TMR0IF, TMR1IF, TMR2IF, and TMR3IF. The following discussion will be restricted to Timer0, since
the other timers function similarly. The only significant difference is that not every timer has as
many prescaler values (discussed below) as Timer0, and some have postscaler values.
Of the four functions in the XC8 compiler library, only the OpenTimer0() function requires much
thought. The XC8 Compiler uses a bit mask and special defined names to set the proper value of
the input variable config. Figure 0-2 shows the available choices. You should always use
TIMER_INT_OFF. For timing, we will use the internal clock and thus use T0_SOURCE_INT.
Timer Width:
* T0_8BIT
* T0_16BIT
Clock Source:
* T0_SOURCE_EXT
* T0_SOURCE_INT
Prescale Value:
* T0_PS_1_1
* T0_PS_1_2
* T0_PS_1_4
* T0_PS_1_8
* T0_PS_1_16
* T0_PS_1_32
* T0_PS_1_64
* T0_PS_1_128
* T0_PS_1_256
148
Lectures - VIII Timer0
T0_PS_1_256 would work. With this prescaler, you would be timing every 256th edge of the clock.
This is very coarse. Your desired time interval divided by this prescale value is 35,000 / 256 =
136.719. Counting is done in whole numbers, so you would either count 256*136 = 34816 TCY or
256*137 = 35072 TCY. If being off by only 72 TCY is acceptable, you could make this choice.
Otherwise, you would need the 16 bit counter. In that case, all the prescaler values would work.
Indeed, using T0_PS_1_1 would allow you to time the desired interval to within one TCY.
Q. Which counter and prescaler would you use for a 505,116 TCY interval? What is the minimum
uncertainty you would have in this measurement?
There are two slightly different ways of using a timer. For the example we used of 35000 TCY, we
need the timer to count 35,000 edges. The counter can count at most 65536 edges, so we start the
timer at 65336 – 35000 = 30336. That is, we use the function WriteTimer0(30336) to start
counting the edges. When the counter reaches 65536, after 35000 TCY, the flag is set. TMR0IF now
equals 1 (provided it was cleared or set to zero before we started). We monitor the flag using an if
statement to compare its value to 1, and stop what we are doing when the result is true. We should
reset, or clear, the flag for possible later use. The code snippet below shows how to write this.
int main(void)
{
unsigned int I;
unsigned char winner;
set_osc_32MHz();
configureUSART(9600);
// wait for splash screen to finish
for(i=0; i++; i = 80) _delay(100000ul);
149
Lectures - VIII Timer0
winner = 1;
} // wait until button is pressed
}
// message – “Loser!”
if ( winner == 0 )
{
// message – “Loser!”
}
}
}
// end program
The other approach requires no calculation. Start the timer at zero, WriteTimer0(0). But now
monitor the counter itself using the ReadTimer0() function in an if statement such as
We don’t even need to bother resetting the flag, as we never use it. Again, a code snippet is below.
int main(void)
{
unsigned char winner;
set_osc_32MHz();
configureUSART(9600);
// wait for splash screen to finish
for(i=0; i++; i = 80) _delay(100000ul);
150
Lectures - VIII Timer0
// message – “Loser!”
if ( winner == 0 )
{
// message – “Loser!”
}
}
}
// end program
#include <xc.h>
#include “..\Common\osc.h”
#include “..\Common\configureUSART.h”
#include “..\Common\serLCD.h”
#pragma config WDT = OFF
#pragma config OSC = INTIO7 // puts osc/4 on pin 14 to check freq
#pragma config MCLRE = OFF
#pragma config LVP = OFF
#pragma config PBADEN = OFF
int main(void)
{
unsigned int time = 0, flag = 0;
set_osc_32MHz();
configureUSART(9600);
// wait for splash screen to finish
for(i=0; i++; i = 80) _delay(100000ul);
WriteTimer0(0); // reset
TMR0IF = 0; // reset
printf( Hello World ); //time single or mutliple lines of code
time = ReadTimer0();
flag = TMR0IF;
printf(«t = %u Flag = %u », time, flag);
while(1);
} // end program
You can also time external events, such as button presses. Suppose, for instance, you wish to time
how long it takes you to press a button ten times. We could modify the button counting program
we have used before.
#include <xc.h>
#include “..\Common\osc.h”
151
Lectures - VIII Timer0
#include “..\Common\configureUSART.h”
#include “..\Common\serLCD.h”
#include “..\Common\buttons.h”
#pragma config WDT = OFF
#pragma config OSC = INTIO7 // puts osc/4 on pin 14 to check freq
#pragma config MCLRE = OFF
#pragma config LVP = OFF
#pragma config PBADEN = OFF
int main(void)
{
unsigned int counter = 0, time = 0, flag = 0;
unsigned char has_switch1_changed = 0;
set_osc_32MHz();
configureUSART(9600);
// wait for splash screen to finish
for(i=0; i++; i = 80) _delay(100000ul);
while(1)
{
has_switch1_changed = monitor_switch1_for_edges(PORTAbits.RA1);
} //end while
} // end program
Note that in both examples, we print out the timer flag status as well. Overflow in timers can cause
problems if we aren’t careful.
152
Lectures - VIII Timer0
Timers as Counters
Timers measure time by counting edges, rising or falling, of square waves with a well-known
period. The time measurement is simply the number of edges multiplied by the period. The square
waves can come from an internal or an external clock source. If the source is a square wave that is
not periodic, a timer acts as a counter to inform you of the number of edges at have occurred at the
pin associated with that timer. For Timer0, that pin, number 6, is designated T0CKI. To use Timer0
as a counter, simply include T0_SOURCE_EXT and either of T0_EDGE_FALL (to count falling edges)
or T0_EDGE_RISE (to count rising edges) when configuring OpenTimer0(). Each edge at pin 6
increments the Timer0 counter, and the function ReadTimer0() reports the current count. Pin 6
(T0CKI/RA4) should be configured as a digital input for Timer0 to correctly read signal edges.
Note that when you use Timer0 as a counter, the only prescaler that is usually appropriate is
T0_PS_1_1.
Counters are useful in a number of situations. Imagine, for instance, you wanted to know the
number of people entering a doorway through a turnstile. It is easy to design a circuit to generate
a square wave at each turn of the turnstile. In turn, the PIC MCU does the counting.
In the example below, we compare and contrast how to count edges using our button code versus
using Timer0 in external counting mode.
Button code lets you use many different pins for your button, while you can only use pin 6, T0CKI,
with Timer0. The button code can miss an edge, a button press, if it is busy doing something else
in the while loop. Timer0 won’t miss an edge, since it is a separate module dedicated to the one
task.
153
Lectures - IX ADC
The DIO pins work with just two voltage values, low and high, which are typically 0 V and +5 V.
As we saw, we need only one bit of memory to store the voltage value as either 0 (low) or 1 (high).
Analog to Digital Conversion (ADC or A/D) is a method for measuring an arbitrary voltage signal
and storing the value as a binary number. In this chapter, we will explore the simplest method for
ADC called the Method of Successive Approximation, which is qualitatively similar to what
happens with the PIC. There are other methods, but we need not concern ourselves with these. In
the Method of Successive Approximation, two circuit elements are involved. The first is called a
comparator and is shown schematically in Figure VIII-1. As the name suggests, it compares two
voltages and if V1 is greater than or equal to V2 it outputs a 1 (true). If not, it outputs 0 (false).
The second element is a voltage divider with 2n equal resistors connected between reference
V1
?
V2
voltages, VREF+ and VREF–. Typically, VREF+ will be set to VDD, which is +5 V, and VREF– will be
set to VSS, which is 0 V. However, other reference values may be used. The 2n resistors provide 2n
voltage values with which to compare any input voltage. ADC is categorized by the number of
comparisons available. The PIC18F4525 has a 10-bit ADC module, so it makes 210 = 1024
comparisons. As a result, any voltage measurement is stored as a 10-bit binary number. As an aid
to understanding the ADC process, a 2-bit example is shown below in Figure VIII-2.
?
VREF+ = VDD = +5 V
R
V3 = 3/4(VREF+ – VREF–) = 3.75 V
R
V2 = 2/4(VREF+ – VREF–) = 2.50 V
S
Vinput R
V1 = 1/4(VREF+ – VREF–) = 1.25 V
R
VREF– = VSS = 0 V
154
Lectures - IX ADC
Move S
to V2
Move S Move S
to V1 to V3
00 01 10 11
Digital values
Q. If the input voltage was actually Vinput = 5.25 V for the 2-bit example, what would the
binary equivalent value be?
Q. If the input voltage was actually Vinput = –1.60 V for the 2-bit example, what would the
binary equivalent value be?
Once a signal has been digitized, there is corresponding problem of interpreting the digital value
as a voltage. Since each digital value is a bin or range of voltages, it only makes sense to take the
midpoint of the range or bin as the reading. For the example, the midpoints are 1/8(VREF+ – VREF–)
= 5/8 V, 3/8(VREF+ – VREF–) = 15/8 V, 5/8(VREF+ – VREF–) = 25/8 V, and 7/8(VREF+ – VREF–) = 35/8 V.
Since the actual reading is anywhere in the range, the precision of the digital measurement is one-
155
Lectures - IX ADC
half the width of the range, here being 1/8(VREF+ – VREF–) = 5/8 V. Thus, four digital values 00, 01,
1,0, and 11 correspond to readings of (0.125VREF ± 0.125VREF), (0.375VREF ± 0.125VREF),
(0.625VREF ± 0.125VREF), and (0.875VREF ± 0.125VREF). For an n-bit conversion, the general
formula is
1 V V 1 VREF VREF
V ADCValue * REF n REF VREF .
2 2 2 2n
The accuracy of the resulting digital conversion depends on the accuracy of (VREF+ – VREF–), which
may only be accurate to a few percent.
Since the PIC18F4525 is more adept at handling integers than floating type numbers, it is advisable
to use VREF in millivolts in the formula above.
R To ADC
Vin(t) Module
VC
C
Q. According to the datasheet on p. 227, C = 25 pF and R (actually the series sum of the R, the
input resistance, and the switch resistance) is about 4 k. What is ? How long does it take to
fully charge the capacitor (i.e. to more than 99%)?
156
Lectures - IX ADC
A second timing factor is that all the ADC comparisons take time to complete. If TADC is the time
for a single measurement and conversion, then the variation in the input voltage signal must be
much slower than this. Figure VIII-6 shows how the number of ADC bits and size of TADC affect
how well the process captures the input signal. Clearly, the more bits involved and the smaller the
TADC, the better the digitized shape captures the input signal. Note as well that no matter how large
N is, the resulting digitization will be poor if the input signal is outside the range set by VREF+ and
VREF–.
V V
VREF+ VREF+
VREF– VREF–
0 t 0 t
V V
0 VREF+ VREF+
0 t
VREF– VREF–
0 t 0 t
The XC8 Compiler simplifies analog to digital conversion using the following functions:
OpenADC(), SetChanADC(), ConvertADC(), BusyADC(), ReadADC(), and CloseADC(). Note that
there are several versions of OpenADC() listed. Use the version for “All Other Processors”.
The main function OpenADC(unsigned char config, unsigned char config2, unsigned
char portconfig) configures the registers (see Section 19 of PIC18F4525 datasheet) that control
ADC. The function takes three parameters (config , config2 , and portconfig) and, as usual,
the parameters are created by bitmasking groups of preset, named constants. In the first parameter
config, the simplest choice is in A/D result justification, which has just two options,
157
Lectures - IX ADC
ADC_RIGHT_JUST or ADC_LEFT_JUST. Recall that we are dealing with 10-bit ADC, so the result of
an ADC is a binary number of length 10. The result will be presented as an integer, which in binary
has 16 digits. The constants control how the 10 digits are placed in the 16 spaces. For now, and
unless otherwise asked, right-justify the result.
The A/D clock source option also must be chosen. Conversion time for a 10-bit measurement is
given as 11 TAD (1 TAD per bit, plus a 1 TAD overhead) on the datasheet on page 229. You can
select how big you want TAD to be: 2 TOSC, 4 TOSC, 8 TOSC, 16 TOSC, 32 TOSC, or 64 TOSC. In general
you want the smallest time possible so that you can deal with rapidly changing, i.e. high frequency,
input signals if necessary. However TOSC changes with the PIC operating frequency, so TOSC can
be quite small. There is a minimum allowable value for TAD of 0.7 s. In Figure IX-7 below, a
variety of processor frequencies and consequent values of TOSC are shown. Below that, the value
of n and TAD for each A/D clock source value is given. The optimal choice of the parameter for
each processor frequency is shown by the shaded cells. These are the smallest values of TAD greater
than 0.7 s. As you can see, running our MCU at 32 MHz requires choosing ADC_FOSC_32. Note
that a further 2TAD is required between conversion cycles.
The last option, A/D acquisition time select, in the config parameter is the amount of time
the sample-and-hold switch is kept closed to allow the capacitor to charge completely. From the
previous question, we see this is about 0.5 s. The choices are expressed in multiples of TAD: 0, 2,
4, 6, 8, 12, 16, and 20. Operating at 32 MHz and using ADC_FOSC_32, TAD = 1 s, as calculated in
Figure IX-7. Thus, ADC_2_TAD is an acceptable choice.
Q. What is the maximum frequency of a sine wave signal that we could capture with reasonable
coverage, say 10 points per period?
The second parameter config2 has two constants that we must select. Choose the channel from
the list of constants, based on which pin will be used, e.g. ADC_CHO for pin AN0. The A/D voltage
158
Lectures - IX ADC
The final parameter, portconfig, determines which of the 13 possible pins on the PIC18F4825
will be configured as analog inputs. If portconfig is 0, 1, or 2, all 13 pins will be analog inputs.
If portconfig is 3, AN12 is not an analog input. If portconfig is 4, both AN12 and AN11 are
not analog inputs, and so on. Figure IX-8 summarizes which pins will be analog inputs for which
value of portconfig. It takes a little time for the ADC module to be fully operational, so it is a
good idea to insert a little time delay after the call to OpenADC().
portconfig
AN12
AN11
AN10
AN9
AN8
AN7
AN6
AN5
AN4
AN3
AN2
AN1
AN0
0 A A A A A A A A A A A A A
1 A A A A A A A A A A A A A
2 A A A A A A A A A A A A A
3 D A A A A A A A A A A A A
4 D D A A A A A A A A A A A
5 D D D A A A A A A A A A A
6 D D D D A A A A A A A A A
7 D D D D D A A A A A A A A
8 D D D D D D A A A A A A A
9 D D D D D D D A A A A A A
10 D D D D D D D D A A A A A
11 D D D D D D D D D A A A A
12 D D D D D D D D D D A A A
13 D D D D D D D D D D D A A
14 D D D D D D D D D D D D A
15 D D D D D D D D D D D D D
A – Analogue Input D – Digital IO
Figure IX-8: Analogue pin selection and portconfig.
The other functions are much simpler. Once the configuration is set by OpenADC(), the actual ADC
operation will not start until ConvertADC() is called. ConvertADC() takes no parameters. The
conversion of an analog signal value to a digital value takes time, as discussed above (though it is
blindingly fast to people) and you have to wait for the process to complete. You can do other things
while waiting as long as you occasionally check to see if the conversion is complete. You check
on the progress of the conversion by examining the status of another parameterless function,
159
Lectures - IX ADC
BusyADC(). It returns a value of 1 if ADC is still going on and 0 if finished. You get the actual
results of the ADC operation by calling the ReadADC() function, which returns an integer that
holds the digital value. ReadADC() takes no parameters.
You can do another ADC operation by calling ConvertADC() again and polling BusyADC() until
this next conversion is done. You can repeat ADC operations as many times as you like.
You can also change pins and channels from one ADC operation to the next by using the
SetChanADC() function. The SetChanADC(unsigned char channel) function takes any one of
the predefined channels, e.g. AD0_CH1, as an input.
When you are finished with your conversions and wish to free up the analog input pins you have
been using, call the CloseADC() function. It takes no parameters.
Figure VIII-9 shows an example of the code to start ADC and collect a value from AN0 followed
by a value from AN1.
#include <xc.h>
#include “..\Common\osc.h”
// Configure pins AN0 and AN1 only for ADC operation using
// VDD and VSS as the references. The digital value is right
// justified. The other values are default settings
OpenADC( ADC_FOSC_32 & ADC_2_TAD & ADC_RIGHT_JUST,
ADC_CH0 & ADC_VREFPLUS_VDD & ADC_VREFMINUS_VSS, 13);
_delay(50); // Delay for 50TCY to stabilize
while(1)
{
SetChanADC(ADC_CH0); // Read from pin AN0
ConvertADC(); // Start ADC operation
while( BusyADC() ); // Wait for completion
firstADCvalue = ReadADC(); // Read result
160
Lectures - IX ADC
161
Lectures - IX ADC
then amplified. The resistance of the phototransistor is effectively very small, so the voltage at the
ADC pin will be small.
A plot of the measured voltage versus the physical property, such as light intensity, is often a
nonlinear curve. Manufacturers may provide a formula, P = f(V), so you can measure the physical
parameter P. More often, there is only a general curve, and it is necessary to measure one or two
known values of the parameter to calibrate the curve. For example, if you have a thermocouple or
a thermistor for measuring temperature, then ice water at 0° and boiling water at 100° make
excellent references.
Q. What happens if we swap the location of the phototransistor and LED with the 10 K and 270
resistors? Will the RLS still work? What will the ADC values be over white paper and over
black electrical tape?
162
Lectures - X Robot
Notes X Robot
In your robot project, you will incorporate the major concepts you have already encountered –
PWM, ADC, and DIO – to program a robot to follow a black line around a track. A schematic of
the robot as seen from above is shown in Figure IX-1. The robot senses its environment though an
array of 5 reflective infrared light sensors (RLS). Each sensor is connected to an analog input pin.
The ADC module can then be used to get a reading from each sensor. If a sensor is over a black
line, the reading will be quite different than when it is over white space. In general, you set a
threshold value for this reading and assume values higher than the threshold indicate that sensor is
currently above a black line. There are also 5 LEDs on the top of the robot chassis. Most programs
for the robot use these LEDs as indicators. The typical use is to have an LED light up when a
corresponding RLS finds a black line. This can be a useful programming and diagnostic tool. For
example, moving the robot over an intersection of black lines and watching the LEDs will help
you figure out the sensor pattern that indicates an approaching intersection. You could also, for
instance, have the LEDs flash on and off when it enters particular portion of the programming
code. The robot has two independent motors to drive its wheels. A PWM signal dictates how fast
each wheel rotates, and a DIO signal controls whether each wheel turns forward or reverse. In your
project, you need to write code that uses the sensor data to control the speed and direction of wheels
to keep the robot on track. The pin connections for the robot are shown in Figure IX-2 on the next
page.
Back
LED4 LED2
LED5 LED3 LED1
Right Left
Motor Motor
PIC18F4525
RLS sensors
left center right
center center
left right
Front
163
Lectures - X Robot
Inputs Outputs
Right Motor
RX TX
PC COM port
if necessary
Four library files are available to make programming the robot easier. The first two are
interrupts.c and interrupts.h. These files are involved in robot functions that we will not be
covering in this course. These functions help keep your robot code functioning smoothly even if
the batteries are running down or there is a voltage spike. The other two library files, sumovore.c
and sumovore.h, contain the basic functions and macros that you need to run the robot. These
functions and macros are based on chip operations that we have covered, such as DIO and ADC.
Other functions watch for voltage problems that cause unexpected exits from code, and provide
error messages about these problems via the LEDs. You will need to understand most of this code.
The function initialization() configures the pins, readies the USART module for serial port
communication at 19200 bits per seconds, configures the ADC module to use pins AN0 to AN5,
and configures the PWM module, including setting the length of the PWM period, and setting the
PWM duty cycle to zero. In effect, the only code you will need to write will be to tell the robot
what to do when.
164
Lectures - X Robot
The header file sumovore.h also contains macro aliases that simplify turning the indicator LEDs
on and off. The aliases are very useful for making your code easier to read, by swapping an arcane
term for something easier to understand. For example, in Figure IX-2 above, an LED – called
LED1 – is connected between pin RD0 and +5 Volts. When the output pin is low, current will flow
through the diode in the correct direction, and the diode will light up. Provided that you configured
the pin as an output, which is done for you in the function initialization(), you would light
the LED by using the command
However, the header file defines a function-like macro, setLED1(a), where a can be ON or OFF.
A function-like macro is more complicated than the macro aliases we have used, but still easy to
use. Note we also have macros aliases of ON for the value 1, and OFF as an alias for the value 0.
that lets you set or clear all the indicator LEDs at once
The logic of the macro keeps the three bits that we don’t use from being changed.
The sumovore library also tries to make using the reflective infrared light sensors (RLS) easier.
The ADC module is configured for you in the function initialization(). Using the ADC
module is simplified to a single function, unsigned int adc(unsigned char channel), that
returns the ADC value from 0 to 1023. The channel names ADC_CH0 to ADC_CH5 have been
assigned macro aliases corresponding to the RLS sensor locations
165
Lectures - X Robot
So, to get the ADC reading from the centre left RLS, we use the code
reading = adc(RLS_CntLeftCH1);
The ADC reading returns a number from 0 to 1023 that rates the blackness of the path under the
sensor. This value is then compared to a threshold value to determine if the sensor is over the black
line or not. The default threshold is 512, and you may need to change this, depending on your
robot’s sensors’ sensitivity. To read the sensors all at once, simply use the provided function
check_sensors().
The information from the ADC readings is a set of five elements consisting of 0s and 1s that you
will use to guide the robot’s motion. That information needs to be stored in a variable so you can
do comparisons. We would like the flexibility to work with either the individual elements or all
the elements as a set. To this end, a structure and a union have been created for you to create a
flexible variable called SeeLine.
struct sensors
{
unsigned Left:1;
unsigned CntLeft:1;
unsigned Center:1;
unsigned CntRight:1;
unsigned Right:1;
unsigned :3;
};
union sensor_union
{
unsigned char B;
struct sensors b;
};
The union is configured so SeeLine has two members, B and b. Member B is of size char and thus
can hold all the information at once. However, only the five least significant bits hold sensor data.
The three most significant bits are not used. The other member of SeeLine, b, is of type sensors,
where sensors is a structure containing five member elements that will hold the individual ADC
comparisons to the threshold. The five member elements of the structure are named Left,
CntLeft, Center, CntRight, and Right and will only have values of 0 or 1. The member elements
of the structure b, itself a member of SeeLine, can be accessed by the member of operator, “.”,
e.g. SeeLine.b.Left.
166
Lectures - X Robot
The function sensor_check() generates the values that are held by variable SeeLine by
comparing the ADC reading to the threshold, as shown in the code below.
Note that since we are dealing with a union, the above code has also set the value of SeeLine.B.
threshold = THRESHOLD_DEFAULT;
where THRESHOLD_DEFAULT is defined to be 512 in sumovore.h. You can change this value if
necessary.
Once the RLS are polled, i.e. the data is stored in variable SeeLine, that data is usually used to set
the indicator LEDs on top of the robot. The function-like macros setLEDn(), defined in
sumovore.h, contains code to do this individually
setLED1(SeeLine.b.Left);
setLED2(SeeLine.b.CntLeft);
setLED3(SeeLine.b.Center);
setLED4(SeeLine.b.CntRight);
setLED5(SeeLine.b.Right);
set_all_LEDs(SeeLine.B);
For example, if only the left sensor was over a line, SeeLine.B would have the value 0b00001
and set_all_LEDs(SeeLine.B) would turn on LED1. Note if you look at the indicator LEDs
using the orientation shown in Figure IX-1, you can read the binary value of SeeLine.B from the
on/off settings of the LEDs. In the figure, SeeLine.B = 0b00010. Be careful! If you accidently
flip the robot around, as if you turned Figure IX-1 upside down, you reverse the positions of the
LEDs and the value would appear to be 0b01000 instead.
We will use PWM to control the two electric motors that each control one wheel. Again, the
function Initialization() configures the PWM module and the pins used in PWM.
Furthermore, the motor control pins also have macro aliases based on their function. If pins RC1
and RC2 are set, the PWM signal is sent to the motors, so these pins turn the right and left motors
on and off. The aliases from the sumovore.h are
167
Lectures - X Robot
Pins RC0 and RC4 control the spin direction of the motors, on for forward and off for reverse,
e.g.
Pins RE0 and RE1 are used for dynamic braking. If you merely cut voltage to an electric motor,
the motor and thus the wheel will continue turning due to inertia. Dynamic braking occurs when
the motor leads are shorted together. This can be achieved by grounding both sides of the motor.
The braking is much closer to instantaneous.
#define RmotorGoFwdCmp PORTEbits.RE0
#define LmotorGoFwdCmp PORTEbits.RE1
These four macros are used in several functions, set_motor_speed() and motors_brake_all(),
contained in sumovore.c, that give you full control over the robot’s motion. You should not need
to access these macros directly. The function motors_brake_all(), as the name suggests, stops
the robot very quickly.
The motor speed and direction is set for each wheel individually by the function
set_motor_speed(enum motor_selection the_motor, enum motor_speed_setting
motor_speed, int speed_modifier). The enum keyword in the first two parameters of the
function is a way of assigning meaningful names or aliases to the members of a numerical list. The
first enumerated list, which determines which motor to activate and which wheel to turn,
const static int motor_speeds[] = { -800, -600, -400, 0, 400, 600, 800};
sets the value of rev_fast to –800 rather than the default 0, and so on. The rev_ prefix in the list
names is short for reverse. The numerical values actually refer to the PWM duty cycle. The PWM
period is 0.400 ms. For this period, the duty cycle for the wheels – as determined by the dutycycle
length, which can be 0 to 800 – are stop (0 or 0%), slow (400 or 50%), medium (600 or 75%), and
fast (800 or 100%). You should not need more speeds, however the variable speed_modifier lets
you increase the duty cycle length if it is a positive number, or decrease the duty cycle if it is a
negative number.
168
Lectures - X Robot
Most of your battery energy is going into turning the wheels. Under certain circumstances, this
load can drop the voltage across the MCU. This can lead to erratic behavior or program
termination. Extra code, using functions we have not covered in this course, are present to try to
prevent erratic behaviour.
main.c &motor_control.c
The sample files main.c and motor_control.c are provided to show you the bare bones of
making the robot follow a simple curved black line using the functions described above. After
calling the initialization function, it uses an infinite while loop to read the sensors, adjust the
indicators LEDS based on the sensor readings, and then hands off control of the motors to a
function called motor_control(), defined in the library motor_control.c and
motor_control.h. The other functions present are to prevent erratic behaviour due to voltage
spikes. The motor_control() function handles five cases best shown through a set of diagrams,
Figure IX-3 , that indicate what the sensors are reading.
The goal of the program is to keep the centre sensor centered on the black linek as shown in (c) in
Figure IX-3. When such a sensor reading occurs, the program has both wheels move forward at
fast speed. When the Left sensor is activated, as in (a), the program has the left wheel reverse at
fast speed while the right wheel continues forward at fast speed. This makes the robot twist sharply
to the right. If instead the CntLeft sensor is set as in (b) below, less of a correction is needed. The
left wheel is stopped – not reversed – while the right wheel continues forward as before. The robot
rotates right, but not as much as in the previous case. The case for (e) is the mirror image of (a),
so the motion of the wheels is just reversed. The same is true for case (d) with case (b). The code
is shown below
void motor_control(void)
{
// very simple motor control
if ( SeeLine.b.Center ) straight_fwd();
else if (SeeLine.b.Left) spin_left();
else if (SeeLine.b.CntLeft) turn_left();
else if (SeeLine.b.CntRight) turn_right();
else if (SeeLine.b.Right) spin_right();
There is a sixth case, in which the sensors are not reading any line, and dynamic braking is called.
It is important to note that the ADC module completes measurements very rapidly and the motion
of the wheels only lasts as long as it takes to run through the if else statements and rerun the
ADC to get a new sensor status. It is quite possible for the robot to be making hundreds of course
corrections each second.
169
Lectures - X Robot
The program is a good starting point but is ill-suited to any more complicated cases. You will need
a much larger and more sophisticated motor control decision tree to get your robot to follow
complicated tracks smoothly and without mistakes.
170