0% found this document useful (0 votes)
4 views69 pages

Programming PIC 24Vwith MPLAB X

The document provides a comprehensive guide on programming a PIC24 microcontroller using MPLAB X, covering project creation, IDE navigation, and specific programming techniques. It highlights differences between PIC24 programming and C++, including variable limitations and the use of interrupts for efficient code execution. Additionally, it emphasizes best practices such as using header files, commenting code, and creating templates for common functions to enhance code readability and maintainability.

Uploaded by

boucheneb
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views69 pages

Programming PIC 24Vwith MPLAB X

The document provides a comprehensive guide on programming a PIC24 microcontroller using MPLAB X, covering project creation, IDE navigation, and specific programming techniques. It highlights differences between PIC24 programming and C++, including variable limitations and the use of interrupts for efficient code execution. Additionally, it emphasizes best practices such as using header files, commenting code, and creating templates for common functions to enhance code readability and maintainability.

Uploaded by

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

Programming

a PIC24 in
MPLAB X
I

Table of Contents
Making a Project and Navigating the IDE 1
How Programming a PIC24 Differs from Writing Code in C++ 4
Basics of a PIC24 Program 6
General Good Practices 8
How to Setup Basic PIC24 Connections 10
Configuring and Use the Debugging Tool 12
Configuring Pins as Digital I/O Pins without Change Interrupts 14
Code Example 16
Configuring Pins as Digital I/O Pins with Change Interrupts 17
Code Example 20
About the Analog to Digital Converter 22
Configuring the Analog to Digital Converter without Threshold Detect 24
Code Example 28
Configuring the Analog to Digital Converter with Threshold Detect 30
Code Example 38
Configuring a Timer and Timer Interrupt 41
Code Example 44
Configuring a PWM Signal and Counter Interrupt without Fault Inputs 46
Code Example 48
Configuring a PWM Signal and Counter Interrupt with Fault Inputs 50
Code Example 55
Configuring Input Capture and Input Capture Interrupts 57
Code Example 62
Code Example – HC-SR04 Ultrasonic Range Finder 64
II
1

Making a Project and Navigating the IDE

Initial Screen Creating a Project


Upon startup you should see the startup Upon clicking the new project button,
screen as shown above that has shortcuts to we are presented with the following popup:
websites and functions of the IDE. From this
start page we will either make or continue a
previous project with the buttons in the top left
corner. The manila folder with a plus on it
creates a new project and the blue folder with a
manila folder coming out opens a project.

From this point we select a standalone project


and hit next at the bottom of the screen. The
next window we see looks like the following:
2

In this menu you choose where your project


Here we need to define what device we are will be saved and what the name of the project
programming to. In the family drop down will be. Do not use special characters or spaces
select 16-bit MCUs (PIC24) and in the device in the name to avoid compiler issues. When
dropdown select PIC24F16KA301. If you have done hit finish and your project should open.
a PicKit3 plugged in you can select that under
the tool drop down or you can just skip that Navigating the IDE
section and hit next. The next menu looks like When done the front screen will have
the following: some new side panels. The top left panel is the
project view and shows you the files that make
up your project. These will include source and
header files. It looks like the following:

The bottom panel is a summary of project


resources and settings. It looks like the
This is VERY IMPORTANT. If you do not see following:
an XC16 compiler option then you must go and
download one from online. This compiler is in
charge of converting your code into
instructions for the PIC24. A google search
should provide you the options needed to get
one. Select the most recent XC16 compiler and
hit next. The final screen looks like the
following:
3
The top tool bar contains the commands to
compile, build, program, and debug your code
with your PIC24. For more details on these
features refer to the sections on the basics of a
PIC24 program and the set up for the
debugging tool.
4

How Programming a PIC24 Differs from


Writing Code in C++
Variable Limitations No Delay or Wait Function
If you have had previous experience The PIC24 has no in-built wait function.
programming in C++ you are probably Often times in coding in C++ or coding an
accustomed to the wide varieties of variables Arduino we are used to being able to tell the
and STL containers that are available on PC device to wait before doing something else.
devices. While most of these capabilities carry This is still possible on the PIC24 but requires
over to the PIC24 most of them are not the programmer to make use of a timer and
inherent and must be included as header files. timer interrupt as well as a global variable
However, it is important to note that the PIC24 which will get set to a specific value when to
does not have much memory. This means that timer is done to let the code know to continue.
as a general rule of thumb dynamic memory See page 41 for more information on how to
allocation should be avoided as much as use timers and timer interrupts.
possible. Another consideration is that global
variables should only be used if they are used
Interrupts
frequently. A good rule of thumb for global Putting a while loop in your main loop
variable use is that if it will be used in more is a common way to loop through repeated
than one function repeatedly then it is ok to actions and respond to inputs in normal code.
have. While in most cases these will hardly be This leads to problems when programming a
problems for robots and small projects, any microcontroller however because we are
attempt to be fancy should keep in mind these dealing with peripheral devices and sensors
limitations. which interact with the world around them. A
while loop would keep the code stuck until its
Registers conditions are met and this could lead to us not
Unlike most code in C++, programming responding to potential hazards for our device
a PIC24 requires you to access and write or more important tasks that arise. While loops
specific bytes of data to storage areas called also do not have a sense of time and this can
registers. These registers and what each byte of lead to problems when dealing with timers,
data controls are all explained in the datasheet. PWM signals, or time sensitive inputs. The use
When programming a register, you will have of interrupts allows the PIC24 to respond in the
to use special calls to the specific register you moment to different inputs and cleans up the
want to change and then specific bits you want main loop significantly. Interrupts are code
to write. Some registers have shortcuts to that stops the main loop temporarily and runs
specific bits as well. Further on in this code in response to a specific event. This could
document we will give example code that will be a timer reaching a certain time, a PWM
show you how to perform common tasks on signal counting a certain amount, a digital
the PIC24 with their associated registers. input going high, etc. These interrupts have to
5
be enabled but make coding much more fluid
and real time than just writing code. In a
practical application this allows you to respond
to sensors on your system quickly and
correctly. It also helps to prevent code from
interrupting each other as interrupts enter a
queue with priorities.

Voltages vs. Numbers


When using the PIC24 most of your
sensors will not output a simple digital high or
low. Often times you deal with voltage values
that must be converted to digital numbers. We
discuss this further on pages 24 and 31 but the
complication of programming a PIC24 is that
your inputs are no clean numbers like 2V or
1.25V but instead are converted into values like
900 and 2400.
6

Basics of a PIC24 Program


Making a Main File
After loading your project your side
panel should look like this:

It already has the xc.h header included so that


we can program the registers on our PIC24.

Choosing an Oscillator
Frequency
An important thing about programming
a PIC24 is the frequency of your oscillator. The
Currently we have no files within our project. frequency of your oscillator divided by two is
To remedy this, we need to make a main.c file. called your cycle frequency. Each iteration of
If we right click on the Source Files folder we the cycle frequency runs one command of
get the following options: code. Each line of code can translate to
different amounts of commands but often it is
about six commands per line. When
programming timers or PWM signals this is
much more important since you are limited in
the PIC24’s memory to how many cycles you
can count and thus how long a timer or PWM
period can be. The options you have are as
follows:
FRC – This is the internal 8 MHz
oscillator
FRCDIV – This refers to the internal 8
MHz oscillator with a postscaler
divider. A postscaler divider means the
frequency of the oscillator will be
reduced.
Selecting the mainXC16.c option we can
LPRC – This is the 31.25 kHz Low-
rename the file to just main.c and hit finish.
Power internal oscillator
From there we should have a file open that
LPFRC – This is the 500 kHz Low Power
looks like the following image:
internal oscillator with a postscaler
divider.
7
To set this up in the code simply place the Writing in the Main Loop
following line before your main loop:
The main loop will loop repeatedly after
#pragma config FNOSC = /**/;
it finishes all commands. While at times this
In place of the /**/ simply place one of the 4
may be advantageous often we want it to run
options from above. If you chose any of the
once and loop only certain parts. To prevent
clock options with a postscaler divider you will
this from happening we just insert the
need an additional line of code. While the clock
following code into our main loop at the end
can only be defined once out of the main loop.
before the return
This line defines the divider and can be
while(1);
changed whenever in the code. This is useful if
you need to change clock signals to This is an infinite loop that has our main loop
temporarily be able to make a PWM signal idle while we may have things going on with
work or get a timer to wait for a certain period interrupts or other code. Now any code we
of time. For the divider use the following line: insert before this command will run once and
then the PIC24 will stay in that infinite loop
_RCDIV = 0bXXX;
until it times out after a few minutes on
Where the XXX is you must choose one of the
inaction or until we reset it.
following values when you are using the
FRCDIV 8MHz oscillator:
111 = 31.25 kHz (divide-by-256)
110 = 125 kHz (divide-by-64)
101 = 250 kHz (divide-by-32)
100 = 500 kHz (divide-by-16)
011 = 1 MHz (divide-by-8)
010 = 2 MHz (divide-by-4)
001 = 4 MHz (divide-by-2) (default)
000 = 8 MHz (divide-by-1)
When you are using the LPFRC 500 kHz
oscillator
111 = 1.95 kHz (divide-by-256)
110 = 7.81 kHz (divide-by-64)
101 = 15.62 kHz (divide-by-32)
100 = 31.25 kHz (divide-by-16)
011 = 62.5 kHz (divide-by-8)
010 = 125 kHz (divide-by-4)
001 = 250 kHz (divide-by-2) (default)
000 = 500 kHz (divide-by-1)
After setting the oscillator for your PIC24 your
code is now ready to be written.
8

General Good Practices


Global Variables Header and have them write their functions in that
header file. Make sure to include all your files
To keep your main.c file as clean and in your main.c file.
neat as possible it is a good idea to make a
dedicated header file for your global variables. Comment Everything
To do so, right click in your project window on PIC24 code is hard to read. Making
the Header Files folder, select new, and then many comments to save your life and your
select C Header File. Name your file something project in the inevitable future. Having more
obvious for another code reader and select comments is always better than having less.
finish. Now include the file in your main.c file
with the following line of code: Using Custom Definitions to
#include “your_file_name.h” Improve Code Legibility and
In this file you can define all your global Avoid Magic Numbers
variables and initialize them. Include this file
where you use these variables. PIC24 code is often nonintuitive to read.
We are setting registers and variables to many
Interrupts Header different values and this can be hard to come
It is highly recommended you make a back to and review when we need to fix code.
dedicated header file for the code needed to To avoid this becoming an issue try using the
handle the interrupts when they occur. Make a following line of code to help out:
header file like before but name it interrupts.h #define /*register*/ /*new name*/
or ISRs.h. Include it in your main.c file and This assigns the register an alias of your choice.
write out all the code to handle interrupts in As an example, if we had a photodiode on pin
this file. Use switch statements in your 3 we would have to call ADC1BUF1 to read its
handlers so you can make cases for different value. With a custom definition, anywhere we
functions or sections of code that use this would type ADC1BUF1 to read the data of a
interrupt and need to behave differently. photodiode on pin 3, we could replace with
something like frontPhotodiode which is a lot
Using Header Files for Other easier to understand and diagnose. Make sure
Code to place these definitions in your global
variables header file for ease of reading and to
Often times you may write long
keep your main.c file clean and clear. In
functions or work on code as a group. In these
addition to improving your code’s readability,
times using header files to hold code that will
custom definitions should be used to avoid
be used in main.c file but would make it much
magic numbers. Magic numbers are repeatedly
harder to read is best. A good rule of thumb for
used numbers that have a meaning to the
functions is that if it will take 10 or more lines,
programmer but would be hard or impossible
place it in a separate header file. If you are
to understand for another reader. As an
having each member of your group write their
example, let’s say you have a PWM signal
own code make a header file for each person
9
whose frequency you change between 3 values you need to change or redo parts of your code.
in your code. Instead of just writing these Just make sure you call those functions in your
numbers every time you could use a custom main loop so that the settings are actually
definition like the following: made in your PIC24.
#define fiftyHz 4999
So that anywhere you would place 4999 you
can use fiftyHz instead. The compiler will
automatically make the substitutions for you
before downloading to the PIC24. This makes
your code much easier to read and can reduce
the number of variables you use if you are
smart about your definitions.

Clear All Pins at the Start of


your Code
The PIC24 requires all unused I/O pins
to be set to digital outputs set to low when not
in use. To ensure your code behaves how you
expect it to insert the following lines of code at
the beginning of your main loop.
ANSA = 0;
ANSB = 0;
TRISA = 0;
TRISB = 0;
LATA = 0;
LATB = 0;
This will ensure every pin is a digital output
set to low until you reassign them later on in
your code.

Create Code Templates


It is extremely helpful if you take the
time to make code templates or template
functions that you can use later. A good way to
do this is to make a header file of functions that
you can copy and use in different projects.
Examples of functions that would be useful are
a function to setup the analog to digital
converter, a function to create a timer, a
function to setup a PWM signal, etc. If you add
plenty of comments to these functions it can
save you a tremendous amount of time when
10

How to Setup Basic PIC24 Connections


MCLR Pin Be careful not to draw too much current or you
risk burning out your PIC24.
The MCLR pin is the master clear pin
for the PIC24. It must be held at a constant Programming Pins
high voltage for it to be inactive and the device Pins 9 and 10 are your programming
to run. This pin is pin 1 on the PIC24 and the pins. It is VERY IMPORTANT that you remove
required circuit is as shown below: any sensors or other connections before
connecting your PICKit 3 to prevent
interference. After programming the PIC24
you can then use these pins freely. The
programming pins can be changed if needed
but before doing so, make sure to review the
datasheet to see what pins have what
capabilities. These programming pin options
are as follows:
PGx3 = Pins 9 and 10 (default)
PGx2 = Pins 3 and 2
PGx1 = Pins 4 and 5
The options are listed in the order that the pins
will be connected to the PICKit 3 programmer.
The first pin listed is the PGD pin and the
second pin is the PGC pin. On the PIC24 PCB
available at the PSC pins 9 and 10 are
preconnected to the PICKit 3 connection on
board. If you intend on changing the pins you
VDD needs to be 3.3V, R1 is a 10 kΩ resistor, should not use the PICKit 3 connection on that
R2 is a resistor between 100 Ω and 470 Ω, and PCB and instead use individual wires going to
C1 is a .1μF capacitor. the proper connections from the PICKit 3. To
change the programming pins, use the
VSS and VDD Pins following optional line of code in your main.c
Pin 19 is the VSS pin and pin 20 is the file outside of the main loop:
VDD pin. You will need to place a .1μF #pragma config ICS = XXXX
capacitor between the two pins. This helps to
Where XXXX is replaced by one of the options
reduce any fluctuations in the power supply
above.
and ensure the PIC24 outputs a consistent
power supply. The max current output on the
VDD pin is 250 mA. This is shared with all the
I/O ports which can only output 25mA each.
11
Wiring the PICKit 3 case. Use the voltage regulator circuit to get the
external power down to 3.3V instead.
To wire the PICKit 3 follow this diagram
and the paragraph that follows:

MCLR connects to pin 1, VDD pin 20, VSS to


pin 19, PGD to pin 9, and PGC to pin 10. If you
changed the programming pins make sure you
refer to the previous paragraph to ensure that
your PGD and PGC pins are properly
connected.

Powering the PIC24 from the


PICKit 3
If you do not have a voltage regulator
circuit or any other power source hooked up to
your PIC24 you can use the PICKit 3 to provide
the power you need to run your PIC24. To do
this, got to file>>project properties and in the
menu that pops up select the PICKit 3 on the
side bar. Then at the top drop-down menu
select Power and set the voltage level to 3.25V.
Then check the box that says power target
circuit from the PICKit 3. If you have a voltage
regulator circuit attached you should turn this
option off since the PICKit 3 will not be able to
supply enough power for your circuit in that
12

How to Configure and Use the


Debugging Tool
Programming Pins will talk about these further on. The Call Stack
tab shows the current functions that have been
Refer to the section on these pins on called at this point in the code. The Variables
page 10 to ensure your programming pins are tab contains information on both watches you
properly selected. Make sure even if you don’t set and local variables.
change pins to include the following line of
code outside your main loop: Setting and Using
#pragma config ICS = PGx3 Breakpoints
So that the default pins 9 and 10 are designated Breakpoints are generally used when
as the programming and debugging pins. After you want to verify that a value is met at a
including that line of code and wiring the certain pint in the code, or to verify that a
PICKit 3 to its proper connections you are now register is set properly when you arrive at a
ready to use the debugging tool. part of the code. To set a breakpoint. Find a
Programming the PIC24 for line of code of code you would like to stop at.
Now go to that line and click the number of
Debugging that line. You should see the following:
At the top of the MPLAB X window you
should see the following icon:
This is a visual indicator of a breakpoint. In the
bottom left hand of your screen in the
dashboard you should see the following as
well:

This icon programs your PIC24 and opens the This tells you how many breakpoints you have
Debugging tool. Upon programming your available for use with your debugging tool.
PIC24 you should see the following window The PICKit 3 supports three breakpoints.
open up at the bottom of your screen: However, it is important to note that using all
three will result in the inability to reset the
PIC24 programmatically as well as other
features. If you run your debugged code and
reach your breakpoint you should see the two
The output tab is your standard project
things occur. First the screen should show a
warnings. The Breakpoints tab refers to points
green line like this:
that you would like the code to pause at. We
13

This is indicative of where the code is stopped


at. The other thing you should see is this at the
top:

This shows you the code is paused and you


have some options of how to proceed. The
green arrow means continue. The blue cycle
means reset. And the rest stop means end
debugging. The other arrows are used to in
order: step over the current step, step into the In this window select global symbols for global
code, step out of the code, run to cursor, set pc variables or SFR’s for registers. Search with the
at cursor, and focus cursor at pc. These tools bar at the top or scroll till you find what you
help you to navigate code more slowly. While need and hit ok. Now you should see you
paused you can view variables and the call watch in the variables tab. To delete it right
stack and make sure everything is running the click on it and select delete. To change what
way it should information you see, right click on the headers
and select the information you’re interested in
Setting and Using Variable and a column will appear. Now whenever you
Watches pause the code these will be updated.
Whenever you pause the code with the
pause button at the top of the screen or you hit
a breakpoint. The code will update the variable
tab with a snapshot of any local variables at
that point in the code and a snapshot of any
watches you have set. Watches can be set to
view global variables or registers in the PIC24.
To set a watch go to the variables tab and click
on the diamond with a green plus in the
corner. You should get a window that looks
like the following:
14

Configuring Pins as Digital I/O Pins


without Change Interrupts
PORTS register. Replace the Y with a 0 for output and
1 for input.
The PIC24 consists of two ports. Ports
are groups of pins that are tied to a register for LATX Registers
data. These ports on the PIC24 are ports A and The LATX Registers control the output of the
B. Referring to the pin diagram on Page II of pins. Use the following command to set a
this document you can see which pins belong digital output to high for each pin you want to
to what port and what pin number they are by set:
looking for an RX## symbol on the diagram. _LATX## = 1;
The X represents what port the pin belongs to
Where X is the pins port and ## is the bit in
and the ## refers to what bit in the registers
that port.
below the pin corresponds to.

ANSX Registers Reading from a Digital Pin


To read from a pin we need to read
The ANSX registers are in charge of
from the PORTX register. As an example, if we
telling the PIC24 if a pin is an analog or digital
wanted to read a digital input from pin 6
pin. Only 12 pins on the PIC24 are analog
which is RB2 and set a variable equal to its
enabled pins and require this line of code.
value I would use the following line of code:
These are pins 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, and
18. To set them as digital pins you must use the int var = PORTBbits.RB2;
following line of code for each pin you want to If I were calling a pin from port A I would
use: have to make sure to change PORTBbits to
_ANSX## = 0; PORTAbits and then change the ending to the
corresponding pins.
Where X refers to the port that the pin belongs
to and ## refers to the corresponding bit in Max Voltage Input on any
that port.
Digital Pin
TRISX Registers The max voltage on any digital pin is
The TRISX registers are in charge of 3.6V. Exceeding this voltage can lead to
defining whether a pin a digital input or potential failure of your PIC24. Verify inputs
output pin. To set each pin you must use the with a multimeter or voltage regulator before
following line of code for each pin you want to inputting them to the PIC24.
use:
_TRISX## = Y;
Where X refers to the port of the pin and the
## refers to the corresponding bit in that
Max Current Out on any
Digital Pin
15
The current output limit for the PIC24 is
25mA. Exceeding this current draw can lead to
potential failure of your PIC24.
16

Code Example - Configuring Digital I/O


Pins without Change Interrupts
/*
* File: main.c
* Author: Spencer Mosley
* Created on May 31, 2022
* Description: In this code I set pin 2 as a digital output and pin 16 as a digital input
* This document is provided as a reference material
*/
#include "xc.h"
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
/*pin 2 is RA0*/
/*pin 16 is RB13*/
_RCDIV = 0b100; // sets a 16 post scaler
/*Reset All Registers We will be Using*/
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Pins 2 and 3 as Digital Outputs*/
_ANSA0 = 0; //sets pin 2 as a digital pin
_TRISA0 = 0; //sets pin 2 as digital output
_LATA0 = 1; //sets pin 2 output to high
/*Set Pin 16 as a Digital Input*/
_ANSB13 = 0; //sets pin 16 as a digital pin
_TRISB13 = 1; //sets pin 16 as a digital input
/*Keep the Code Running as is*/
while(1);
return 0;
}
17

Configuring Pins as Digital I/O Pins with


Change Interrupts
PORTS that register. Replace the Y with a 0 for output
and 1 for input.
The PIC24 consists of two ports. Ports
are groups of pins that are tied to a register for LATX Registers
data. These ports on the PIC24 are ports A and The LATX Registers control the output
B. Referring to the pin diagram on Page II of of the pins. Use the following command to set
this document you can see which pins belong a digital output to high for each pin you want
to what port and what pin number they are by to set:
looking for an RX## symbol on the diagram. _LATX## = 1;
The X represents what port the pin belongs to
Where X is the pins port and ## is the bit in
and the ## refers to what bit in that register the
that port.
pin corresponds to.

ANSX Registers Reading from a Digital Pin


To read from a pin we need to read
The ANSX registers are in charge of
from the PORTX register. As an example, if we
telling the PIC24 if a pin is an analog or digital
wanted to read a digital input from pin 6
pin. Only 12 pins on the PIC24 are analog
which is RB2 and set a variable equal to its
enabled pins and require this line of code.
value I would use the following line of code:
These are pins 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, and
18. To set them as digital pins you must use the int var = PORTBbits.RB2;
following line of code for each pin you want to If I were calling a pin from port A I would
use: have to make sure to change PORTBbits to
_ANSX## = 0; PORTAbits and then change the ending to the
corresponding pins.
Where X refers to the port that the pin belongs
to and ## refers to the corresponding bit in Max Voltage Input on any
that port.
Digital Pin
TRISX Registers The max voltage on any digital pin is
The TRISX registers are in charge of 3.6V. Exceeding this voltage can lead to
defining whether a pin a digital input or potential failure of your PIC24. Verify inputs
output pin. To set each pin you must use the with a multimeter or voltage regulator before
following line of code for each pin you want to inputting them to the PIC24.
use:
_TRISX## = Y;
Where X refers to the port the pin belongs to
and the ## refers to the corresponding bit in
Max Current Out on any
Digital Pin
18
The current output limit for the PIC24 is an internal pull-up or pull-down resistor to
25mA. Exceeding this current draw can lead to eliminate the need for an external resistor. A
potential failure of your PIC24. This is also the pull-up resistor keeps the voltage of the pin
max current out for a pin which has a pull up high until the pin is connected to ground and a
resistor enabled for its change interrupt pull-down resistor keeps the pin low until it is
connected to power. If your circuit calls for one
Change Interrupts of these you could remove it and enable the
The PIC24 has the capability of resistor with one of these lines of code:
detecting when a digital input pin changes _CNXPDE = 1;
state. This is great for responding to a button or
being pressed, a switch being thrown, or a _CNXPUE = 1
digital sensor going high. Each of the digital
Where X is the CNX number of the pin you are
I/O pins has a built-in pull up and pull-down configuring. It is important to note that only
resistor that can be enabled to eliminate the one of these may be enabled at a time so make
need for an external resistor. In your interrupt sure you do not enable both to avoid errors or
handler you will need to write code to handle hurting the PIC24. Use these lines of code for
changes from low to high AND from high to
each pin you are configuring.
low. You can do this by checking what the
value of the pin is, high or low, to respond Configuring the Change
accordingly. It is VERY IMPORTANT to note Interrupt
that the change interrupt is shared between all
pins so you will need to check each pin in your After defining which pins will use the
handler to find which pin had changed change interrupt we need to configure how the
change interrupt should work. Use the
Enabling the Change following lines of code to do this:
Interrupt on Pins _CNIP = XX;
_CNIF = 0;
Each I/O pin has its port number RBXX
_CNIE = 1;
but it also has its own change interrupt
number CNXX that corresponds to it. Look at Where XX represents the priority of the
the diagram on page II of this document to find interrupt and needs a value between 1 and 7. 1
the number for the pin you are configuring. being least important 7 being most important.
After finding that number use the following This only matters if interrupts will potentially
line of code for each pin you want to enable the occur at the same time and one has to be done
change interrupt on that pin: before the other. I always set them as 4. The
second line clears the interrupt flag. When we
_CNYYIE =1;
handle the interrupt, we must always clear the
Where YY is replaced with the number of the flag so that the code can continue running
pin you are configuring.
normally after the interrupt handler or we will
Optional: Pull-Up and Pull- be infinitely stuck in the handler. The last line
enables the interrupt. You can enable and
Down Resistors disable the interrupt throughout your code
Each of the pins that has a change which can be useful if there are times where
interrupt capability also has the ability to use
19
you are not concerned with how the pins
change.

Handling the Change


Interrupt
When the interrupt is called we need to
make a handler that clears the interrupt flag
and reacts to the interrupt. This can be done in
the main.c file or if you are following the good
practices guidelines in your interrupts header
file. The handler function is written as follows:
void __attribute__((interrupt, no_auto_psv))
_CNInterrupt(void){
_CNIF = 0;
/*your code here*/
}
The function name must be as given above for
the code to work properly. The first line clears
the interrupt flag and must always be
included. The rest is up to you. You should
check the values of pins here and respond
according to your design.
20

Code Example - Configuring Digital I/O


Pins with Change Interrupts
/*
* File: main.c
* Author: Spencer Mosley
* Created on May 31, 2022
* Description: In this code I set pins 2 and 4 as digital inputs and pins 3 and 5 as a
* digital inputs with change interrupts. I am only concerned in pins 3 and 5 go high*/
* This document is provided as a reference material
*/
#include "xc.h"
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
void __attribute__((interrupt, no_auto_psv)) _CNInterrupt(void){
_CNIF = 0; //clears interrupt flag
if(PORTAbits.RA1 == 1){
/*react to pin 3 going high here*/
}
if(PORTBbits.RB1 == 1){
/*react to pin 5 going high here*/
}
}
main(void) {
/*pin 2 is RA0*/
/*pin 3 is RA1 and CN3*/
/*pin 4 is RB0*/
/*pin 5 is RB1 and CN5*/
_RCDIV = 0b100; // sets a 16 post scaler
/*Reset All Registers We will be Using*/
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Pins 2 and 4 as Digital Outputs*/
_ANSA0 = 0; //sets pin 2 as a digital pin
_ANSB0 = 0; //sets pin 4 as a digital pin
_TRISA0 = 0; //sets pin 2 as digital output
21
_TRISB0 = 0; //sets pin 4 as digital output
_LATA0 = 1; //sets pin 2 output to high
_LATB0 = 1; //sets pin 4 output to high
/*Set Pins 3 and 5 as Digital Inputs*/
_ANSA1 = 0; //sets pin 3 as a digital pin
_ANSB1 = 0; //sets pin 5 as a digital pin
_TRISA1 = 1; //sets pin 3 as a digital input
_TRISB1 = 1; //sets pin 5 as a digital input
/*Enable Change Interrupt on Pins 3 and 5*/
_CN3IE = 1; //enables change interrupt for pin 3
_CN5IE = 1; //enables change interrupt for pin 5
/*Enable Internal Pull-Down Resistors*/
_CN3PDE = 1;
_CN5PDE = 1;
/*Configure the Change Interrupt*/
_CNIP = 4; //sets change interrupt priority to 4;
_CNIF = 0; //clears the change interrupt flag
_CNIE = 1; //enables the change interrupt
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
22

About the Analog to Digital Converter


with Threshold Detect
Registers maximum is 3.3V and its minimum is 1.7V. If
you use the smallest voltage reference, the
There is a total of 11 registers that are smallest voltage measurement you could have
used to configure the analog to digital would be the following:
converter. 2 of these are not used on the PIC24
10-bits:
and 2 of them are only used if you intend to
1.7
measure capacitance on the analog to digital = .0017V
210 − 1
converter. The other 7 are used to configure the
12-bits:
analog to digital converter, the pins to check,
1.7
and the threshold detection feature. = .00042V
212 −1
Max Voltage Input on the Pins
However, if you do this, anything above 1.7V
The maximum voltage you can input on any will be read as 1.7V. The analog to digital
analog pin is 3.6V. Exceeding this can converter also does not output the voltage as a
potentially cause PIC24 failure so take care to decimal value. Instead it outputs it as a
check all signals you intend to read with a multiple of the values given above. In the 10-
multimeter or oscilloscope before inputting bit operating mode your voltage will be
them into the PIC24. represented as a value between 0 and 1023. In
Resolution the 12-bit operating mode you value will be
represented as a value between 0 and 4095. To
When using the analog to digital calculate what the value you should expect to
converter it is important to remember how receive, given you know what voltage should
precise your measurements can be. The PIC24 be inputted, use the following equation:
has two operating modes for the analog to Your Voltage
digital converter. These are the 10-bit and 12- × (1023 or 4095)
3.3V
bit operating modes. Assuming you will be
The value you multiply by depends on your bit
using the PIC24 operating 3.3V as the voltage
operation.
reference these are the smallest measurements
you can get in each operating mode Analog to Digital Converter
10-bits: Clock Cycle
3.3
= .0032V The analog to digital converter has its
210 − 1
own internal counter that is responsible for
12-bits:
creating a new frequency for the analog to
3.3
= .00081V digital converter. This frequency must have a
212 −1 minimum period of 600nS however for the
You can also use a reference voltage with pin 2
sampling time a minimum of 750nSis required.
being the positive input and pin 3 being the
The largest period that the analog to digital
negative input. The reference voltage
23
converter can have is 64 times the oscillator
period. Alternatively, you can use one of the 5
timers on the PIC24 to act as the timing for the
analog to digital converter. This will have to be
done if you plan on using the threshold detect
feature.

Analog to Digital Converter


Interrupt Rate
The analog to digital converter throws
an interrupt after a set number of samples have
been taken. You will need to make sure that
this interrupt occurs after you have sampled all
of your pins. If it does not you will not see
what happens on certain pins because the scan
will reset after each interrupt.

Threshold Detect
The analog to digital converter has a
built-in feature for detecting when a pin rises
above a certain value, falls below a certain
value, is within a certain window of values, or
is outside a certain window of values. This
feature can throw an interrupt if desired so
that you can address these changes or can
make it so that you don’t need to store the
value the sensor is at or run any comparison
logic on your pins and rather just look at what
pins tripped.
24

Configuring the Analog to Digital


Converter without Threshold Detect
Clear All Converter Registers positive voltage choose from the following
options and use the following line of code:
To start you need to clear the registers 11 = 4 * Internal VBG
so you can have proper settings for your 10 = 2 * Internal VBG
analog to digital converter. To do this use the
01 = External VREF+
following lines of code to clear the relevant
00 = AVDD
registers:
_PVCFG = 0bXX
AD1CON1 = 0;
Where the XX is exchanged for the option you
AD1CON2 = 0;
choose above. The VBG can be ignored since it
AD1CON3 = 0;
falls outside the scope of the projects you will
AD1CON5 = 0;
do. To set the ground reference choose from
AD1CSSL = 0;
the following options and use the following
AD1CSSH = 0;
line of code:
Setting the Pins for Analog 1 = External VREF-
Use 0 = AVSS
_NVCFG = X;
We need to tell the pins we are going to
use that they are analog inputs. To do this we Where X is exchanged for the option you chose
use the following lines of code for each pin we above.
want to use: Set the Analog to Digital
_ANSYXX = 1;
_TRISYXX = 1;
Conversion Timing
Where Y is the letter corresponding to the port When not using the threshold detect
of the pin we use. The XX refers to the bit in you can have the analog to digital converter
that port. Look at page II of this document for take care of its own timing. Simply use the
the pin out diagram and find the RYXX value following line of code:
of the pin you want to use for that information. _SSRC = 0b0111;
This means the converter automatically
Setting Voltage and Ground
converts after finishing sampling.
References
Choose Bit Mode
The analog to digital converter needs to
know with regards to what voltage and You need to decide if you would like to
ground it should base its conversion on. Refer use the 10-bit or 12-bit resolution mode. Refer
to the Resolution section in About the Analog to the Resolution section in About the Analog
to Digital Converter with Threshold Detect to to Digital Converter with Threshold Detect to
understand what your options are. To set the
25
understand what your options are. Use the will encounter negative voltages in your
following line of code: readings and since your PIC24 does not care
_MODE12 = X; for the difference between and integer and a
float it is recommended to use the unsigned
Where X is 0 for 10-bit mode or 1 for 12-bit
integer results for ease of programming. Some
mode.
math will be required on you part to
Choose Output Format understand what you are looking for or what
The PIC24 has 4 output formatting the output means but it will be easier to deal
options you can choose from. They are as with in the future rather than fractions. To set
follows: this use the following line of code:

11 = Fractional result, signed, left- _FORM = 0bXX:


justified Where XX is replaced by the value
10 = Absolute fractional result, corresponding to the option you decide to use.
unsigned, left-justified Remember that if you choose a fractional form
01 = Decimal result, signed, right- any attempt to store in a variable must use a
justified float variable.
00 = Absolute decimal result, unsigned, Auto Sampling
right-justified
Auto sampling means after the
If you use the signed fractional result you will
converter is done converting the last value it
get a value between -0.500 and .500. The
read, it will begin reading another value. If you
inverse of your reference voltage is equal to -
are sampling from multiple pins you should
.500 and your reference voltage is .500. To find
use this option. If you are only reading from
what voltage you are at you would need to
one analog pin you should still set this. If you
multiply that number by your reference
want to sample on demand read the data sheet
voltage. An unsigned fractional result will be
for more information on how that is done. Use
represented as a value between 0 and 1. This is
the following line of code to set it:
in absolute form so a negative voltage reading
would still be read as simply its value. To find _ASAM = X;
what voltage you are at take the value and Where X = 1 for enabled and 0 for disabled;
multiply that by your reference voltage. A
signed decimal result would give you a value
Setting Output Location
between -2048 and 2047 for 12-bit mode or -512 There are options for where to output
and 511 in 10-bit mode. To find what voltage your converted data but it is HIGHLY
you are at take the value and divide it by 2048 recommended you set the following option to
for 12-bit mode and 512 for 10-bit mode. Then enabled. The reason for this is that it prevents
multiply that value by your reference voltage. data from being overwritten and simplifies
An unsigned decimal result would be a value accessing your data. If you desire to do
between 0 and 4095 for 12-bit mode and 0 and otherwise this is at your own discretion and
1023 for 10-bit mode. Take the value and will require some through reading of the data
divide it by 4095 in 12-bit mode or 1023 in 10- sheet starting at page 210. Use the following
bit mode and multiply the result by your line of code:
reference voltage. It is highly unlikely that you _BUFREGEN = 1;
26
Change the value to 0 to disable this at your Where XX represents the ANXX number of the
own risk. The following only applies to if this pin. Look at the pinout on page II of this
option is enabled. To read the value you must document next to the pin you intend to use for
access it with this value: its ANXX number.
ADC1BUFXX Defining the Interrupt Rate
Where XX represents the ANXX value that
The analog to digital converter throws
corresponds to the pin you are reading from.
an interrupt after it has converted a certain
For example, if you are reading from pin 2 it
number of samples. With the auto scan enabled
corresponds to AN0, see page II of this
it is VERY IMPORTANT that you properly
document for a diagram of the pinout, and I
change this setting. Even without the interrupt
would access its converted value with
enabled this must be set. Take the number of
ADC1BUF0. I could set a variable equal to that
pins you are scanning, subtract one, and
value or you can compare that value in a
convert that number to binary. That is the
comparison statement.
input for the interrupt rate you will want to
Auto Scan Inputs use. Set the interrupt rate with the following
line of code:
In most cases you should have the
PIC24 iterate through the analog pins. If you _SMPI = 0bXXXXX
do not iterate through the pins you will be in Where XXXXX is the number you calculated.
charge of telling the PIC24 which pin it needs You need to add the leading zeros so that it is
to look at for each sample iteration. Look at the five digits long. For example, if I was
datasheet for more information on how to do converting eight pins, I would need to use
that. To turn on the auto scanning use the binary seven which is 111. I would then add
following line of code: the two leading zeros to get 00111 which is
_CSCNA = 1; what I would use as my setting.
Change the 1 to 0 to disable auto scanning. If Defining the Analog to
you are auto scanning it is VERY IMPORTANT
that you follow the next step to tell the PIC24
Digital Converter Clock
what pins should be included in the auto scan. Cycle
Defining Which Pins to Auto You must always set the conversion
clock setting. To do this you must choose from
Scan the following options and use the following
The PIC24 does not look at the ANSX line of code:
registers to determine which pins it should 11111111-01000000 = Reserved
scan when auto scanning. Instead it looks at 00111111 = 64·TCY = TAD
the ADC1CSSL register. Having reset the •
register, it will not scan any pin currently. To 00000001 = 2·TCY = TAD
tell it which pins to scan you must use the 00000000 = TCY = TAD
following line of code for each pin you want to
_ADCS = 0bXXXXXXXX;
scan:
Where TCY is your chosen Oscillator period, or
_CSSXX = 1;
one divided by your Oscillator Frequency, and
27
TAD is your converter period. This period that the code can continue running normally
must be longer than 600nS for converting. after the interrupt handler or we will be
Replace XXXXXXXX with your chosen option. infinitely stuck in the handler. The last line
enables the interrupt. You can enable and
Defining the Auto Sample disable the interrupt throughout your code
Time which can be useful if there are times where
you are not concerned with responding to the
After setting the converter clock you
data acquisition or thresholds.
need to set how long the converter should
sample the pin value for. This must be longer Handling the Analog to
than 750nS. To set it choose one of the
following options and use the following line of
Digital Converter Interrupt
code: If we have enabled the interrupt we
11111 = 31 TAD must handle that interrupt. To do so we must
• create a handler in our code. This can be done
• in the main.c file or if you are following the
• good practices guidelines in your interrupts
00001 = 1 TAD header file. The handler function is written as
00000 = 0 TAD follows:
_SAMC = 0bXXXXX; void __attribute__ ((interrupt, no_auto_psv))
_ADC1Interrupt(void){
Where XXXXX is replaced by the option you
chose and TAD is the converter clock cycle. _AD1IF = 0;
/*your code here*/
Configuring the Analog to }
Digital Converter Interrupt The handler must be written with that name in
This step is optional but the interrupt order for the PIC24 to properly function. The
rate must still be set even if this is not used. interrupt flag line must also always be at the
After sampling all your pins, you can throw an top of the interrupt to clear the flag.
interrupt to pause your main loop and run Enabling the Analog to
specific code in response to that data. To do
this we need the following three lines of code: Digital Converter
_AD1IP = X; The last thing to do is enable the analog
_AD1IF = 0; to digital converter. It can be turned on and off
_AD1IE = 1; as needed and is all done with the following
Where X represents the priority of the interrupt line of code:
and needs a value between 1 and 7. 1 being _ADON = X;
least important 7 being most important. This Where X is 1 for on and 0 for off.
only matters if interrupts will potentially occur
at the same time and one has to be done before
the other. I always set them as 4. The _ADC1IF
command refers to the interrupt flag. When we
handle the interrupt, we must clear the flag so
28

Code Example – Configuring the Analog


to Digital Converter without Threshold
Detect
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 2, 2022
* Description: In this code I set pins 17 and 18 as analog inputs. I also configure the A/D
* converter without threshold detect and an interrupt for when the values are available.
* This document is provided as a reference material
*/
#include "xc.h"
void __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void){
_AD1IF = 0; //clears interrupt flag
/*code would go here based on what I was doing*/
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
/*pin 17 is RB14 and AN10*/
/*pin 18 is RB15 and AN9*/
_RCDIV = 0b100; // sets a 16 post scaler
/*Reset All Registers We will be Using*/
AD1CON1 = 0;
AD1CON2 = 0;
AD1CON3 = 0;
AD1CON5 = 0;
AD1CSSL = 0;
AD1CSSH = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Pins we want to use as Analog Pins and Inputs*/
_ANSB14 = 1; //sets pin 17 as an analog pin
_ANSB15 = 1; //sets pin 18 as an analog pin
29
_TRISB14 = 1; //sets pin 17 as an input
_TRISB15 = 1; //sets pin 18 as an input
/*Set Positive and Negative Voltage References*/
_PVCFG = 0b00; //selects voltage reference
_NVCFG = 0; //selects ground reference
/*Set Conversion Timing*/
_SSRC = 0b0111; //sets the converter as managing its own time
/*Set Bit Mode*/
_MODE12 = 1; //sets converter to the 12-bit operating mode
/*Set Output Format*/
_FORM = 0b00; //sets converter to absolute decimal format
/*Set Auto Sampling*/
_ASAM = 1; //enables auto sampling
/*Set Output Location*/
_BUFREGEN = 1; //sets output to buffers corresponding to pins
/*Set Auto Scanning of Inputs*/
_CSCNA = 1; //auto scanning enabled
/*Define which Pins to Scan*/
_CSS10 = 1; //enables scanning of pin 17
_CSS9 = 1; //enables scanning of pin 18
/*Choose Interrupt Rate*/
//(2 – 1) in binary is 00001
_SMPI = 00001; // interrupts every other sample
/*Define the Analog to Digital Converter Clock Cycle*/
_ADCS = 0b00000000; //sets clock to be the same as the system clock
/*Define the Auto Sample Time*?
_SAMC = 0b00001; //sets the sample time to be one converter clock cycle
/*Configure the Analog to Digital Converter Interrupt*/
_AD1IP = 4; //sets interrupt priority to 4
_AD1IF = 0; //clears interrupt flag
_AD1IE = 1; //enables interrupt
/*Turn on Analog to Digital Converter*/
_ADON = 1; //turns analog to digital converter on
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
30

Configuring the Analog to Digital


Converter with Threshold Detect
Setting the Pins for Analog positive voltage choose from the following
options and use the following line of code:
Use 11 = 4 * Internal VBG
We need to tell the pins we are going to 10 = 2 * Internal VBG
use that they are analog inputs. To do this we 01 = External VREF+
use the following lines of code for each pin we 00 = AVDD
want to use: _PVCFG = 0bXX
_ANSYXX = 1; Where the XX is exchanged for the option you
_TRISYXX = 1; choose above. The VBG can be ignored since it
Where Y is the letter corresponding to the port falls outside the scope of the projects you will
of the pin we use. The XX refers to the bit in do. To set the ground reference choose from
that port. Look at page II of this document for the following options and use the following
the pin out diagram and find the RYXX value line of code:
of the pin you want to use for that information. 1 = External VREF-
0 = AVSS
Clear All Converter Registers
_NVCFG = X;
To start you need to clear the registers
Where X is exchanged for the option you chose
so you can have proper settings for your
above.
analog to digital converter. To do this use the
following lines of code to clear the relevant Choose Bit Mode
registers:
You need to decide if you would like to
AD1CON1 = 0; use the 10-bit or 12-bit resolution mode. Refer
AD1CON2 = 0; to the Resolution section in About the Analog
AD1CON3 = 0; to Digital Converter with Threshold Detect to
AD1CON5 = 0; understand what your options are. Use the
AD1CSSL = 0; following line of code:
AD1CSSH = 0;
_MODE12 = X;
Setting Voltage and Ground Where X is 0 for 10-bit mode or 1 for 12-bit
References mode.

The analog to digital converter needs to Choose Output Format


know with regards to what voltage and The PIC24 has 4 output formatting
ground it should base its conversion on. Refer options you can choose from. They are as
to the Resolution section in About the Analog follows:
to Digital Converter with Threshold Detect to
11 = Fractional result, signed, left-
understand what your options are. To set the
justified
31
10 = Absolute fractional result, Where XX is replaced by the value
unsigned, left-justified corresponding to the option you decide to use.
01 = Decimal result, signed, right- Remember that if you choose a fractional form
justified any attempt to store in a variable must use a
00 = Absolute decimal result, unsigned, float variable.
right-justified
Auto Sampling
If you use the signed fractional result you will
get a value between -0.500 and .500. The Auto sampling means after the
inverse of your reference voltage is equal to - converter is done converting the last value it
.500 and your reference voltage is .500. To find read, it will begin reading another value. If you
what voltage you are at you would need to are sampling from multiple pins you should
multiply that number by your reference use this option. If you are only reading from
voltage. An unsigned fractional result will be one analog pin you should still set this. If you
represented as a value between 0 and 1. This is want to sample on demand read the data sheet
in absolute form so a negative voltage reading for more information on how that is done. Use
would still be read as simply its value. To find the following line of code to set it:
what voltage you are at take the value and _ASAM = X;
multiply that by your reference voltage. A Where X = 1 for enabled and 0 for disabled;
signed decimal result would give you a value
between -2048 and 2047 for 12-bit mode or -512 Auto Scan Inputs
and 511 in 10-bit mode. To find what voltage In most cases you should have the
you are at take the value and divide it by 2048 PIC24 iterate through the analog pins. If you
for 12-bit mode and 512 for 10-bit mode. Then do not iterate through the pins you will be in
multiply that value by your reference voltage. charge of telling the PIC24 which pin it needs
An unsigned decimal result would be a value to look at for each sample iteration. Look at the
between 0 and 4095 for 12-bit mode and 0 and datasheet for more information on how to do
1023 for 10-bit mode. Take the value and that. To turn on the auto scanning use the
divide it by 4095 in 12-bit mode or 1023 in 10- following line of code:
bit mode and multiply the result by your _CSCNA = 1;
reference voltage. It is highly unlikely that you
Change the 1 to 0 to disable auto scanning. If
will encounter negative voltages in your
you are auto scanning it is VERY IMPORTANT
readings and since your PIC24 does not care
that you follow the next step to tell the PIC24
for the difference between and integer and a
what pins should be included in the auto scan.
float it is recommended to use the unsigned
integer results for ease of programming. Some Defining Which Pins to Auto
math will be required on you part to
understand what you are looking for or what
Scan
the output means but it will be easier to deal The PIC24 does not look at the ANSX
with in the future rather than fractions. To set registers to determine which pins it should
this use the following line of code: scan when auto scanning. Instead it looks at
_FORM = 0bXX: the ADC1CSSL register. Having reset the
register, it will not scan any pin currently. To
tell it which pins to scan you must use the
32
following line of code for each pin you want to _ADCS = 0bXXXXXXXX;
scan: Where TCY is your chosen Oscillator period, or
_CSSXX = 1; one divided by your Oscillator Frequency, and
Where XX represents the ANXX number of the TAD is your converter period. This period
pin. Look at the pinout on page II of this must be longer than 600nS for converting.
document next to the pin you intend to use for Replace XXXXXXXX with your chosen option.
its ANXX number. Defining the Auto Sample
Defining the Interrupt Rate Time
The analog to digital converter throws Since we are using threshold detect we
an interrupt after it has converted a certain cannot auto sample so this setting can be
number of samples. With the auto scan enabled ignored.
it is VERY IMPORTANT that you properly
change this setting. Even without the interrupt Set the Analog to Digital
enabled this must be set. Take the number of Conversion Timing
pins you are scanning, subtract one, and
convert that number to binary. That is the Since we are using threshold detect we
input for the interrupt rate you will want to will need to use a timer to control when the
use. Set the interrupt rate with the following sampling should end and when the conversion
line of code: should begin. To do this select a timer from
one of the following options:
_SMPI = 0bXXXXX
0101 = Timer 1 event ends sampling and
Where XXXXX is the number you calculated.
starts conversion
You need to add the leading zeros so that it is
0011 = Timer5 event ends sampling and
five digits long. For example, if I was
starts conversion
converting eight pins, I would need to use
0010 = Timer3 event ends sampling and
binary seven which is 111. I would then add
starts conversion
the two leading zeros to get 00111 which is
what I would use as my setting. All the timers on the PIC24 are 16 bits but
timers 2 and 3 are linked as well as 4 and 5.
Defining the Analog to These links make it so you can use timers 2 and
Digital Converter Clock 4 as 32-bit timers. However, if you intend on
using timers 3 or 5 for this timing you must
Cycle turn off this link so that you can access timers 3
You must always set the conversion or 5. For this to work properly you need to do
clock setting. To do this you must choose from some math on what your timer period will be.
the following options and use the following The timer period must encompass our
line of code: conversion time which is TAD and our
sampling time as well as the time needed to
11111111-01000000 = Reserved
00111111 = 64·TCY = TAD start conversion. The data sheet states that
there is a delay of 3 TAD periods after

conversion ends for sampling to begin.
00000001 = 2·TCY = TAD
Therefore, our minimum timer period must be
00000000 = TCY = TAD
33
4 TAD periods with an additional 750nS for at all until it has scanned all the pins and one
sampling. To setup the timer, look at our of those pins met our threshold. The interrupt
instructions on page 41 to properly initialize after a valid compare changes the interrupt to
and set the timer. Some trial and error will be only occur after a pin has met our threshold.
needed here to find a timer period that works The interrupt after a threshold detect sequence
well with your setup. Use the following line of completes means will behave like normal,
code to then enable this setting: interrupting after a certain amount of
_SSRC = 0bXXXX; conversions but it will also interrupt
immediately if it detects that our threshold is
Where XXXX is replaced by the option that you
met. This is the feature you will use 99% of the
chose from the list above
time. The last feature disables the interrupt for
Enabling Threshold Detect the threshold detect and it behaves like a
normal analog to digital converter with the
To use the threshold detect feature we
added feature of the PIC24 keeping track of
need to enable it with the following line of
what pins met a predefined threshold. After
code:
choose your desired operation use the
_ASEN = 1;
following line of code to properly enable this
Defining Threshold Detect setting:

Interrupt Operation _ASINT = 0bXX;


Where XX is replaced by the code for the
The threshold detect feature shares the
operation you would like to use.
same interrupt with the normal analog to
digital converter interrupt. This setting will Setting Output Location
modify how the interrupt occurs and requires a
There are options for where to output
more involved version of handling. In our
your converted data but it is HIGHLY
handling the interrupt section later on we
recommended you set the following option to
discuss how to recognize in the interrupt
enabled. The reason for this is that it prevents
handler if a threshold detect feature occurred
data from being overwritten and simplifies
or if it was a standard conversion interrupt.
accessing your data. When using threshold
The options for the threshold detect are as
detect this feature must almost always be
follows:
enabled to prevent your thresholds from being
11 = Interrupt after a Threshold Detect overwritten accidentally. Use the following
sequence completed and a valid line of code:
compare has occurred
_BUFREGEN = 1;
10 = Interrupt after a valid compare has
Change the value to 0 to disable this at your
occurred
own risk. If you desire to do otherwise this is
01 = Interrupt after a Threshold Detect
at your own discretion and will require some
sequence completed
through reading of the data sheet starting at
00 = No interrupt
page 210. The following only applies to if this
The interrupt after a threshold detect sequence
option is enabled. To read the value you must
completes and a valid compare completes
access it with this call:
changes the interrupt to where it will not occur
ADC1BUFXX
34
Where XX represents the ANXX value that Where the first pin is the pin you would
corresponds to the pin you are reading from. measure on and the second pin would be the
For example, if you are reading from pin 2 it one that is disabled. Any of the other options
corresponds to AN0, see page II of this writes their threshold only to the buffer of the
document for a diagram of the pinout, and I pin it uses. You can only have one type of
would access its converted value with threshold for all your pins defined at one time.
ADC1BUF0. I could set a variable equal to that With some creative programming loops and
value or you can compare that value in a control it is possible to use different threshold
comparison statement. types for different pins but this requires some
more advanced thought than this guide will
Defining Our Threshold Type mention. After you have selected an option
To use threshold detect we need to from the list above use the following lines of
define what kind of threshold we would like to code:
use. The options are as follows: _CM1 = X;
11 = Outside Window mode (valid _CM0 = Y;
match occurs if the conversion result is Where X is the first digit of the code you
outside of the window defined by the selected and Y is the second digit.
corresponding buffer pair)
10 = Inside Window mode (valid match Setting Our Thresholds
occurs if the conversion result is inside We can only set the thresholds when the
the window defined by the analog to digital converter is off. If we want to
corresponding buffer pair) change them we must turn of the converter
01 = Greater Than mode (valid match then turn it back on. To set a threshold for a
occurs if the result is greater than the pin we need to write the threshold to the
value in the corresponding buffer output register of that pin. If we are using any
register) of the windowed thresholds we write the
00 = Less Than mode (valid match lower limit to the threshold of the pin we are
occurs if the result is less than the value reading from and we write the upper limit to a
in the corresponding buffer register) corresponding register of another pin. A list of
If you choose either of the window modes you the paired pins are as follows:
are limited to 5 pins which correspond to pins ADC1BUF0(pin 2) + ADC1BUF9(pin 18)
AN0, AN1, AN2, AN3, and AN4. These are ADC1BUF1(pin 3) + ADC1BUF10(pin 17)
pins 2, 3, 4, 5, and 6. This is because they use ADC1BUF2(pin 4) + ADC1BUF11(pin 16)
the buffers of other pins to write their upper ADC1BUF3(pin 5) + ADC1BUF12(pin 15)
window limit. The pins and buffer pairs are as ADC1BUF4(pin 6) + ADC1BUF13(pin 7)
shown below: Where the first pin is the one that is read and
ADC1BUF0(pin 2) + ADC1BUF9(pin 18) the other pin is the one that does not function
ADC1BUF1(pin 3) + ADC1BUF10(pin 17) during this operation. Make sure you do not
ADC1BUF2(pin 4) + ADC1BUF11(pin 16) scan the pins that are used to set the upper
ADC1BUF3(pin 5) + ADC1BUF12(pin 15) limit since they have no corresponding upper
ADC1BUF4(pin 6) + ADC1BUF13(pin 7) pin. For any other threshold mode any pin can
be used by setting its threshold to its register.
35
To set the threshold use the following line of more problematic because you won’t know
code: which output buffer was written to and
ADC1BUFXX = YYYY; therefore which threshold may have been
overwritten. If you use one of these options
Where XX is the corresponding analog register
you should disable the converter and timer in
number given by the ANXX of the pin we want
your interrupt handler, process the value, reset
to use and YYYY is the value of the threshold.
the threshold, and reenable your converter and
To calculate what the threshold should be use
timer when you’re done. It is recommended
the following equation:
you use the first option for simplicity sake
Vthreshold
× Max Value since there is a dedicated register that will tell
Vreference
you which pin met its threshold so you don’t
Where the max value is 4095 for 12-bit have to worry about its value. After choosing a
operation and 1023 for 10-bit operation. setting use the following lines of code:
Determining What to Do with _WM1 = X;
_WM0 = Y;
the Converted Voltage
Where X is the first digit of your option and Y
When we use the threshold detect is the second digit of your option.
feature we need to decide what to do with the
converted voltage value. The options we have Configuring the Analog to
are as follows: Digital Converter Interrupt
10 = Auto-compare only (conversion
This step is optional but the interrupt
results not saved, but interrupts are
rate must still be set even if this is not used.
generated when a valid match as
After sampling all your pins, you can throw an
defined by CM and ASINT bits occurs)
interrupt to pause your main loop and run
01 = Convert and save (conversion
specific code in response to that data. To do
results saved to locations as determined
this we need the following three lines of code:
by register bits when a match as defined
by CM bits occurs) _AD1IP = X;
00 = Legacy operation (conversion data _AD1IF = 0;
saved to location determined by buffer _AD1IE = 1;
register bits) Where X represents the priority of the interrupt
The first option does not save the voltage value and needs a value between 1 and 7. 1 being
and is only concerned if our threshold is met. least important 7 being most important. This
The second option saves our data according to only matters if interrupts will potentially occur
our previous output settings, only when the at the same time and one has to be done before
threshold value is met. The last option saves the other. I always set them as 4. The _ADC1IF
the data according to our previous output command refers to the interrupt flag. When we
settings after every conversion. If you used the handle the interrupt, we must clear the flag so
recommended output setting the last two that the code can continue running normally
options are problematic however because our after the interrupt handler or we will be
threshold value gets overwritten. If you didn’t infinitely stuck in the handler. The last line
use the recommended output setting it’s even enables the interrupt. You can enable and
disable the interrupt throughout your code
36
which can be useful if there are times where In the code to handle your interrupt you
you are not concerned with responding to the need to identify which pins may have met their
data acquisition. threshold. If a pin meets a threshold the
AD1CHITL register bit corresponding to the
Handling the Analog to pin is thrown high. This must be cleared by the
Digital Converter Interrupt user so it makes the most sense in your
interrupt handler to check which pins are high,
THIS IS VERY IMPORTANT WITH
clear them, act on the information, repeat. To
THRESHOLD DETECT. If we have enabled the
check and write to the corresponding bit use
interrupt we must handle that interrupt. With
the following call:
threshold detect it is HIGHLY recommended
that you have the interrupt enabled. Also, _CHHXX
when using threshold detect this operation Where XX is replaced by the corresponding XX
changes based on the settings we changed in value for the ANXX value of the pin we are
the Defining Threshold Detect Interrupt using. If set that bit equal to zero then we have
section. If we used either of the first two cleared it. If we check that bit and see it is
options an interrupt will only occur after a equal to one then we know that the pin met
value that meets a threshold is detected. The our threshold requirements. It is important to
third option will have interrupts when after it note however that for the Outside window
scans all the pins or when a threshold is met. It mode operation this bit is written as one is the
is important to note with the third option data was written to the ADC1BUFXX register
interrupts can be either a threshold was met or or if a match occurred. If you have turned of
it scanned all the pins. In the next section we data saving it will only occur when a match
address how to distinguish which pins met has happened. See the code example below to
their thresholds. We must create a handler for understand what I would do in the handler.
the interrupt in our code. This can be done in
the main.c file or if you are following the good
Enabling the Analog to
practices guidelines in your interrupts header Digital Converter
file. The handler function is written as follows: The last thing to do is enable the analog
void __attribute__ ((interrupt, no_auto_psv)) to digital converter with the following line of
_ADC1Interrupt(void){ code:
_AD1IF = 0; _ADON = X;
/*your code here*/ Where X is 1 for on and 0 for off. You can turn
} it on and off freely so you are not always
The handler must be written with that name in converting if not needed.
order for the PIC24 to properly function. The
interrupt flag line must also always be at the
top of the interrupt to clear the flag.

Identifying Which Pins Have


Met their Thresholds
37

Code Example – Configuring the Analog


to Digital Converter with Threshold
Detect
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 7, 2022, 1:32 PM
* Description: In this code I set pins 17 and 18 as analog inputs and configure the A/D
* converter with threshold detect to know when they become greater than 1.61V. I also
* configure an interrupt for when the data is available or when a pin is above the
* threshold. In my interrupt handler I process data and check which pins exceed 1.61V.
* This document is provided as a reference material
*/
#include "xc.h"
void __attribute__((interrupt, no_auto_psv)) _ADC1Interrupt(void){
_AD1IF = 0; //clears interrupt flag
if(_CHH10 == 1){ //if pin 17 was greater than 1.61V
_CHH10 = 0; //clears threshold detected flag
/*code would go here for pin 17*/
}
else{
/*what to do if pin 17 is not above 1.61V*/
}
/*DO NOT use else if statements. If the first pin meets the condition all the else if statements
are ignored. Instead each pin should be an if statement of its own to ensure we are checking
each pin*/
if(_CHH9 == 1){ //if pin 18 was greater than 1.61V
_CHH9 = 0; //clears threshold detected flag
/*code would go here for pin 18*/
}
else{
/*what to do if pin 18 is not above 1.61V*/
}
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
38
/*pin 17 is RB14 and AN10*/
/*pin 18 is RB15 and AN9*/
_RCDIV = 0b100; // sets a 16 post scaler
/*Reset All Registers We will be Using*/
AD1CON1 = 0;
AD1CON2 = 0;
AD1CON3 = 0;
AD1CON5 = 0;
AD1CSSL = 0;
AD1CSSH = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Pins we want to use as Analog Pins and Inputs*/
_ANSB14 = 1; //sets pin 17 as an analog pin
_ANSB15 = 1; //sets pin 18 as an analog pin
_TRISB14 = 1; //sets pin 17 as an input
_TRISB15 = 1; //sets pin 18 as an input
/*Set Positive and Negative Voltage References*/
_PVCFG = 0b00; //selects voltage reference
_NVCFG = 0; //selects ground reference
/*Set Bit Mode*/
_MODE12 = 1; //sets converter to the 12-bit operating mode
/*Set Output Format*/
_FORM = 0b00; //sets converter to absolute decimal format
/*Set Auto Sampling*/
_ASAM = 1; //enables auto sampling
/*Set Auto Scanning of Inputs*/
_CSCNA = 1; //auto scanning enabled
/*Define which Pins to Scan*/
_CSS10 = 1; //enables scanning of pin 17
_CSS9 = 1; //enables scanning of pin 18
/*Choose Interrupt Rate*/
//(2 – 1) in binary is 00001
_SMPI = 00001; // interrupts every other sample
/*Define the Analog to Digital Converter Clock Cycle*/
_ADCS = 0b00000000; //sets clock to be the same as the system clock
/*Set Conversion Timing*/
_SSRC = 0b0101; //sets timer one as managing the timing for the converter
/*Configure Timer 1*/
39
T1CON = 0; //clears timer 1 configuration register
T1CONbits.TCKPS = 0b00; //sets a prescaler value of 1
T1CONbits.TCS = 0; //sets the clock to the internal oscillator
PR1 = 100; //sets the timer period
T1CONbits.TON = 1; //turn on timer 1
/*Define the Auto Sample Time*?
_SAMC = 0b00001; //set to one clock cycle but not needed with threshold detect
/*Enable Threshold Detect*/
_ASEN = 1; //enables threshold detect feature
/*Define Threshold Detect Interrupt Operation*/
_ASINT = 0b01; //sets interrupt to occur when input scanning completes
/*Set Output Location*/
_BUFREGEN = 1; //sets output to buffers corresponding to pins
/*Define Threshold Type*/
_CM1 = 0; //sets the threshold type to greater than
_CM0 = 1;
/*Setting the Thresholds*/
ADC1BUF10 = 2000; //sets the voltage threshold to 1.61V
ADC1BUF9 = 2000; //sets the voltage threshold to 1.61V
/*What to do with the Converted Value*/
_WM1 = 1; //sets the PIC24 to discard converted values
_WM0 = 0;
/*Configure the Analog to Digital Converter Interrupt*/
_AD1IP = 4; //sets interrupt priority to 4
_AD1IF = 0; //clears interrupt flag
_AD1IE = 1; //enables interrupt
/*Turn on Analog to Digital Converter*/
_ADON = 1; //turns analog to digital converter on
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
40

Configuring a Timer and Timer Interrupt


About the Timers on the 32-bit timers, timers 2 and 4 are the timers
whose registers correspond to those controls.
PIC24
Optional: Unlink Timers
The PIC24 has five 16-bit timers on
board, four of which are linked to make two If you are unlinking timer 3 from timer 2
32-bit timers. You can unlink one or both of the or timer 5 from timer 4 to have more 16-bit
32-bit timers if you so desire. The bits timers you need to use the following line of
determine how many cycles you can count and code:
thus how long your timer may be without a TXCONbits.T32 = 0;
prescaler. A 16-bit timer can count 65,535 Where X is 2 or 4 depending on which timer
cycles and a 32-bit timer can count 4.29 X 10^9 you are resetting. The first line clears the
cycles. To convert this into time use the parent timer settings and the second unlinks
following equation: the child timers. After doing this you can
1 configure both timers normally.
× Prescaler × Cycles = Time
𝑓oscillator
Set the Timer Clock Source
Where 𝑓oscillator is the frequency of your
oscillator after the postscaler and the cycles is While there are different options for the
how many cycles you intend to count. clock source, it is always recommended to use
Alternatively, you can solve the equation with the internal oscillator as the clock. If you desire
a time in mind and find how many cycles you to choose otherwise refer to the manual for
need. If you set a prescaler the timer will only instructions on how and what to do for this. To
count every so many cycles effectively set the clock sue the following line of code:
increasing the time you can count. A prescaler TXCONbits.TCS = 0;
of two means every other cycle it counts, of Where X is the number of the timer you are
four every fourth cycle, etc. Bear this in mind configuring.
for your designs because timers are used to
operate different features such as the threshold Set the Timer Prescaler
detect on the analog to digital converter and We need to decide which prescaler to
input capture. Timers are also useful for use for our timer to match the time we want to
ensuring something happens every so often count. The options are as follows:
even if other code may be running.
11 = 1:256
Clear the Timer Register 10 = 1:64
01 = 1:8
To start clear the Register of the timer
00 = 1:1
you intend to use. TO do this sue the following
Where the value after the colon is the value of
line of code:
your prescaler. IMPORTANT: The prescaler
TXCON = 0;
value is reset whenever we write to the TMRX
Where X is replaced with the value of the timer register, we turn off the timer, or the device
you wish to use, being 1-5. If you are using the
41
resets. If you plan on turn off and turning on be infinitely stuck in the handler. The last line
the timer or prematurely resetting the timer enables the interrupt. You can enable and
due to some event make sure you set the disable the interrupt throughout your code
prescaler again. To set it use the following line which can be useful if there are times where
of code: you are not concerned with responding to the
TYCONbits.TCKPS = XX; timer. Alternatively, you could turn the timer
off.
Where Y is the number of the timer you’re
configuring and XX is the code for the Handling the Timer Interrupt
prescaler you would like to use.
When the interrupt is called we need to
Set the Timer Period make a handler that clears the interrupt flag
and reacts to the interrupt. This can be done in
In order for the timer to function
the main.c file or if you are following the good
correctly we need to tell it how long to count
practices guidelines in your interrupts header
for. After calculating how many cycles you will
file. The handler function is written as follows:
need use the following line of code to set the
period of the timer: void __attribute__ ((interrupt, no_auto_psv))
_T$Interrupt(void){
PRX = Y;
_T$IF = 0;
Where X is the number of the timer you are
/*your code here*/
configuring and Y is the number of cycles you
intend to count. }
Where the $ in the handler name is replaced
Configuring the Timer with the number of the timer you are
Interrupt configuring. The handler must be written with
that name in order for the PIC24 to properly
To know when the timer has finished
function. The interrupt flag line must also
counting we need to configure an interrupt
always be at the top of the interrupt to clear the
that will be called when that happens. To do so
flag.
we use the following lines of code:
_TXIP = Y; Turning on the Timer
_TXIF = 0;
The last step is to turn on the timer with
_TXIE = 1;
the following line of code:
Where X is the number of the timer which you
TXCONbits.TON = 1;
are configuring. Y represents the priority of the
Where X is the number of the timer you are
interrupt and needs a value between 1 and 7. 1
configuring
being least important 7 being most important.
This only matters if interrupts will potentially Optional: Gated Timer
occur at the same time and one has to be done
before the other. I always set them as 4. The
Operation Mode
_TXIF command clears the interrupt flag. The timers have an additional
When we handle the interrupt, we must clear functionality that may or may not be useful to
the flag so that the code can continue running you. This function known as gated
normally after the interrupt handler or we will accumulation makes use of preset pins which
42
must be set as digital inputs. The pins and the
timers they are associated with are as follows:
T1CK (pin 13) = Timer 1
T2CK (pin 18) = Timer 2
T3CK (pin 18) = Timer 3
T4CK (pin 6) = Timer 4
T5CK (pin 6) = Timer 5
Gated accumulation means that when the
corresponding pin goes high the timer will
start counting and throw an interrupt when the
signal goes low or when the timer counts its
full period, whichever comes first. This is great
for if you are receiving a signal that you need
to know if it stays on longer or shorter than a
certain time period. An example would be if
you wanted to know if a limit switch had been
pressed for more than half a second and do
something if it was. You would enable this and
then in your while loop or other control
structure check the TMRX value to see how
long it has been. It is important to note that the
time that it was on does not get saved
anywhere when the interrupt is thrown and is
lost. Use input capture to track how long
something has been on and use the time for
calculations or inputs. To enable this feature,
use this line of code:
TXCONbits.TGATE = 1;
Where X is replaced with the number of the
timer you are configuring.
43

Code Example – Configuring Timer 2


with a Timer Interrupt
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 7, 2022
* Description: In this code I configure timer 2 to count for half a second and then throw
* an interrupt. I also configure a handler for that interrupt that would run code every
* half second.
* This document is provided as a reference material
*/
#include "xc.h"
void __attribute__((interrupt, no_auto_psv)) _T2Interrupt(void){
_T2IF = 0; //clears interrupt flag
/*your code here*/
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
_RCDIV = 0b100; //sets a 16 post scaler
/*Reset All Registers We will be Using*/
T2CON = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Unlink Timer 2 from Timer 3*/
T2CONbits.T32 = 0; //unlinks timer 2 from timer 3 so it is a 16-bit timer
/*Set the Timer 2 Clock Source*/
T2CONbits.TCS = 0; //sets timer 2 clock to the internal oscillator
/*Set Timer 2 Prescaler Value*/
/*I want to be able to count for half a second so my math would be ((.5/65535)*250000) =
Prescaler. This equals 1.9 and I can only go up so I set a prescaler value of 8 in order to be able
to count half a second*/
T2CONbits.TCKPS = 0b01;
/*Set the Timer Period*/
/*With my prescaler selected I need to solve now for the cycles I need to count in order to get
44
half a second. My math for this would be as follows: ((.5/8)*250000) which gives me 15625*/
PR2 = 15625 //sets the timer period to half a second
/*Configure the Timer 2 Interrupt*/
_T2IP = 4; //sets the timer 2 interrupt priority to 4
_T2IF = 0; //clears the timer 2 interrupt flag
_T2IE = 1; //enables the timer 2 interrupt flag
/*Turn on Timer 2*/
T2CONbits.TON = 1;
/*Let the Interrupt Control the Rest of the Code*/
while(1);
return 0;
}
45

Configuring a PWM Signal and Counter


Interrupt without Fault Inputs
Output Compare Registers 111 = System clock
110 = Reserved
The PIC24 has 3 built in PWM signals 101 = Reserved
that are available for use. Each of these has 100 = Timer1
their own set of control registers that are called 011 = Timer5
the output compare registers. The PWM pins 010 = Timer4
and their associated registers are as listed: 001 = Timer3
OC1CON1 000 = Timer2
OC1CON2: Pin 14 If you use any of the timer options you need to
OC2CON1 have that timer configured and on before you
OC2CON2: Pin 4 enable the output compare module. Unless the
OC3CON1 period you want to use is more than a 16-bit
OC3CON2: Pin 5 timer could count, you should use the system
Depending on which pin you plan on using clock. If you want to use another option refer
refer to this list to know which register to call to the manual for more instructions. Another
and configure. option to look at in the manual if you have a
very long period is cascading OC1 and OC2 to
Clear Relevant Registers create a 32-bit PWM counter. This also can
After clearing all the ANSX, TRISX, and increase the period but will not be covered
LATX registers to make sure the pins are reset, further in this guide but is covered in the
you should also clear the output compare manual. To set the clock use the following line
registers associated with the pin you plan on of code:
using. To do this use these lines of code for OCXCON1bits.OCTSEL = 0bYYY;
each pin you plan on using:
Where X is the number corresponding to the
OCXCON1 = 0; pin you are using and YYY is the value of the
OCXCON2 = 0; clock you intend on using.
Where the X is replaced with the associated
value for the pin you are using.
Select the Output Compare
Select the Output Compare Operating Mode
When using the output compare
Clock module there are 8 operating modes that can
Each output compare pin has its own be used. 6 of them deal with generating a pulse
dedicated timer in the output compare module or a delayed output. For more information
so that it can time its period and duty cycle. refer to the manual. The two we are concerned
We need define which source the internal timer with are the following options:
uses and the options are as follows:
46
111 = Center-Aligned PWM on OCx applications we should have the module sync
110 = Edge-Aligned PWM on OCx with its own timer to ensure the duty cycle and
Where center-aligned PWM means that the frequency are properly set. To look at other
high portion of the PWM is set in the middle of options look at the manual for more
the period rather at the beginning. The edge- information. To set the sync source to the
aligned PWM mode has the signal start high output compare module itself use this line of
and go low after the duty cycle is reached, then code:
stays low until the period is over. If you intend OCXCON2bits.SYNSEL = 0b11111;
on using the optional fault pin capabilities Where X is the corresponding number for the
mentioned later on you must use the center- pin you are configuring.
aligned PWM option. Besides that, either or is
fine for a PWM signal. Most times defaulting to Set the PWM Period
an edge-aligned PWM is easier for people to To set the period of the PWM signal we
understand and diagnose with an oscilloscope. need to calculate what value the output
To set the operating mode use the following compare module needs to count to. Use the
line of code: following equation to find that value:
OCXCON1bits.OCM = 0bYYY; 1
Where X is the corresponding number for the 𝑓PWM
− 1 = Period
1
pin you are configuring and YYY is the code
𝑓oscillator
for the option you chose.
Where 𝑓PWM is your desired frequency and
Set the Output Compare 𝑓oscillator is your set clock frequency. After you
have that value use the following line of code
Module to Sync Mode
to set the period:
The output compare module can be OCXRS = Period;
triggered by or synced by a module. Trigger
Where X is the corresponding number for the
mode is used for the other operating modes of
pin you are configuring.
the output compare module. For PWM
applications it should be set to be synced by a Set the PWM Duty Cycle
source to keep the module timed properly. To
To set the PWM duty Cycle value use
set it to sync mode use this line of code:
the following equation:
OCXCON2bits.OCTRIG = 0;
% Duty Cycle
Where X is the corresponding number for the Period × = Duty Cycle
100
pin you are configuring. After finding your duty cycle value use this
Set the Output Compare line of code to set your duty cycle:
OCXR = Duty Cycle;
Module Sync Source
Where X is the corresponding number for the
The output compare module timer pin you are configuring.
counts according to the clock that we defined
earlier but the module must know when the Configuring a Counter
end of a period occurs. There are many options Interrupt
that can be used to sync with but for PWM
47
A counter interrupt occurs after every that name in order for the PIC24 to properly
pulse from a PWM signal. It can be used to function. The interrupt flag line must also
count the pulses for things like counting steps always be at the top of the interrupt to clear the
on a stepper motor, counting time, or reacting flag.
to sensors while a PWM signal is being driven.
To configure it use the following lines of code:
Optional: Using the Counter
_OCXIP = Y; Interrupt without a PWM
_OCXIF = 0; Output
_OCXIE = 1;
The output compare module can be
Where X represents the module which you are
used in PWM mode like a timer interrupt to
configuring. Y is the priority of the interrupt
run code at consistent intervals and leave the
and needs a value between 1 and 7. 1 being
pin open for other uses. This is done by using
least important 7 being most important. This
the following line of code:
only matters if interrupts will potentially occur
at the same time and one has to be done before OCXCON2bits.OCTRIS = 1;
the other. I always set them as 4. The _OCXIF Where X is the number of the module which
command clears the interrupt flag. When we you are configuring. This sets the output of the
handle the interrupt, we must clear the flag so compare module to be tri-stated, meaning a
that the code can continue running normally high impedance state that effectively removes
after the interrupt handler or we will be it from the other circuits on the PIC24. This
infinitely stuck in the handler. The last line means we can still use the pin as an analog
enables the interrupt. You can enable and input or digital I/O pin while using the output
disable the interrupt throughout your code compare module.
which can be useful if there are times where
Optional: Inverting the PWM
you are not concerned with counting the PWM
cycles. Output
Handling a Counter Interrupt The output compare module can be
inverted where the duty cycle setting is treated
When the interrupt is called we need to as the low time percentage of the period. To set
make a handler that clears the interrupt flag this use this line of code:
and reacts to the interrupt. This can be done in
OCXCON2bits.OCINV = 1;
the main.c file or if you are following the good
practices guidelines in your interrupts header Where X is the number of the module which
file. The handler function is written as follows: you are configuring.

void __attribute__ ((interrupt, no_auto_psv))


_OC$Interrupt(void){
_OC$IF = 0;
/*your code here*/
}
Where the $ in the handler name is replaced
with the number of the module you are
configuring. The handler must be written with
48

Code Example – Configuring a PWM


Signal and Counter Interrupt without
Fault Inputs
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 8, 2022
* Description: In this code I configure the PWM signal on pin 14 to run at 50Hz and
* have a duty cycle of 50%. I also configure a counter interrupt that will increment a
* global variable for each pulse of the PWM.
* This document is provided as a reference material
*/
#include "xc.h"
int count = 0; //a global variable I want to increment in my counter interrupt
void __attribute__((interrupt, no_auto_psv)) _OC1Interrupt(void){
_OC1IF = 0; //clears interrupt flag
count++; //increments my global variable
/*your code here*/
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
_RCDIV = 0b100; //sets a 16 post scaler
/*Reset All Registers We will be Using*/
OC1CON1 = 0;
OC1CON2 = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Output Compare Module Clock*/
OC1CON1bits.OCTSEL = 0b111; //tells the OC1 module to use the system clock
/*Set the Output Compare Operating Mode*/
OC1CON1bits.OCM = 0b110; //sets the OC1 module to Edge-Aligned PWM mode
/*Set the Output Compare Module to Sync Mode*/
OC1CON2bits.OCTRIG = 0; //sets the OC1 module to sync mode and not trigger mode
49
/*Set the Output Compare Module Sync Source*/
OC1CON2bits.SYNSEL = 0b11111; //sets the OC1 module to sync to its own counter
/*Set the PWM Period*/
/*To get 50Hz on the PWM my math would be ((1/50)/(1/250000)) – 1. This gives me 4999
which is in the range of the PWM 16-bit counter*/
OC1RS = 4999; //sets OC1 period to 50Hz
/*Set PWM Duty Cycle*/
/*For a 50% duty cycle I divide the value I got for the period by two which is just under
2500*/
OC1R = 2500; //sets a 50% duty cycle
/*Configuring the Counter Interrupt*/
_OC1IP = 4; //sets the OC1 counter interrupt priority to 4
_OC1IF = 0; //clears the OC1 counter interrupt flag
_OC1IE = 1; //enables the OC1 counter interrupt
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
50

Configuring a PWM Signal and Counter


Interrupt with Fault Inputs
Output Compare Registers 111 = System clock
110 = Reserved
The PIC24 has 3 built in PWM signals 101 = Reserved
that are available for use. Each of these has 100 = Timer1
their own set of control registers that are called 011 = Timer5
the output compare registers. The PWM pins 010 = Timer4
and their associated registers are as listed: 001 = Timer3
OC1CON1 000 = Timer2
OC1CON2: Pin 14 If you use any of the timer options you need to
OC2CON1 have that timer configured and on before you
OC2CON2: Pin 4 enable the output compare module. Unless the
OC3CON1 period you want to use is more than a 16-bit
OC3CON2: Pin 5 timer could count, you should use the system
Depending on which pin you plan on using clock. If you want to use another option refer
refer to this list to know which register to call to the manual for more instructions. Another
and configure. option to look at in the manual if you have a
very long period is cascading OC1 and OC2 to
Clear Relevant Registers create a 32-bit PWM counter. This also can
After clearing all the ANSX, TRISX, and increase the period but will not be covered
LATX registers to make sure the pins are reset, further in this guide but is covered in the
you should also clear the output compare manual. To set the clock use the following line
registers associated with the pin you plan on of code:
using. To do this use these lines of code for OCXCON1bits.OCTSEL = 0bYYY;
each pin you plan on using:
Where X is the number corresponding to the
OCXCON1 = 0; pin you are using and YYY is the value of the
OCXCON2 = 0; clock you intend on using.
Where the X is replaced with the associated
value for the pin you are using.
Select the Output Compare
Select the Output Compare Operating Mode
When using the output compare
Clock module there are 8 operating modes that can
Each output compare pin has its own be used. 6 of them deal with generating a pulse
dedicated timer in the output compare module or a delayed output. For more information
so that it can time its period and duty cycle. refer to the manual. The two we are concerned
We need define which source the internal timer with are the following options:
uses and the options are as follows:
51
111 = Center-Aligned PWM on OCx applications we should have the module sync
110 = Edge-Aligned PWM on OCx with its own timer to ensure the duty cycle and
Where center-aligned PWM means that the frequency are properly set. To look at other
high portion of the PWM is set in the middle of options look at the manual for more
the period rather at the beginning. The edge- information. To set the sync source to the
aligned PWM mode has the signal start high output compare module itself use this line of
and go low after the duty cycle is reached, then code:
stays low until the period is over. If you intend OCXCON2bits.SYNSEL = 0b00000;
on using the optional fault pin capabilities Where X is the corresponding number for the
mentioned later on you must use the center- pin you are configuring.
aligned PWM option. Besides that, either or is
fine for a PWM signal. Most times defaulting to Set the PWM Period
an edge-aligned PWM is easier for people to To set the period of the PWM signal we
understand and diagnose with an oscilloscope. need to calculate what value the output
To set the operating mode use the following compare module needs to count to. Use the
line of code: following equation to find that value:
OCXCON1bits.OCM = 0bYYY; 1
Where X is the corresponding number for the 𝑓PWM
− 1 = Period
1
pin you are configuring and YYY is the code
𝑓oscillator
for the option you chose.
Where 𝑓PWM represents your desired frequency
Set the Output Compare and 𝑓oscillator represents your set clock
frequency. After you have that value use the
Module to Sync Mode
following line of code to set the period:
The output compare module can be OCXRS = Period;
triggered by or synced by a module. Trigger
Where X is the corresponding number for the
mode is used for the other operating modes of
pin you are configuring.
the output compare module. For PWM
applications it should be set to be synced by a Set the PWM Duty Cycle
source to keep the module timed properly. To
To set the PWM duty Cycle value use
set it to sync mode use this line of code:
the following equation:
OCXCON2bits.OCTRIG = 0;
% Duty Cycle
Where X is the corresponding number for the Period × = Duty Cycle
100
pin you are configuring. After finding your duty cycle value use this
Set the Output Compare line of code to set your duty cycle:
OCXR = Duty Cycle;
Module Sync Source
Where X is the corresponding number for the
The output compare module timer pin you are configuring.
counts according to the clock that we defined
earlier but the module must know when the
end of a period occurs. There are many options
that can be used to sync with but for PWM Using Fault Inputs
52
The PIC24 has 3 fault options that can be After setting which modules will use
used with the output compare modules. what faults you need to define how these faults
Depending on the fault configuration you will impact the modules and how to respond
choose you could have it so if the fault pin goes to the fault. The following options deal with
low the PWM signal pin stops outputting a the fault operating mode:
signal and instead is either forced to be low or 1 = Fault mode is maintained until the
high, until the fault pin goes high or until you Fault source is removed and the
programmatically reset the fault signal. The corresponding OCFLTx bit is cleared in
fault options are as follows: software
Comparator Module (Comparator 1 for 0 = Fault mode is maintained until the
OC1 and OC2 and Comparator 2 for Fault source is removed and a new
OC3) PWM period starts
OCFA = Pin 17; Where the first option states that after a fault is
OCFB = Pin 16; removed the user must clear the fault bit to
The first option uses one of the 3 on board resume the PWM signal and the second states
comparator modules for its operation. You that after the fault is removed the PWM
must enable this fault on the module you want resumes by itself. After selecting the operating
it to affect after you have configured and mode you would like to enable, use this line of
enabled the associated comparator. Refer to the code to set it:
manual for more information on configuring OCXCON2bits.FLTMD = Y;
the comparator module and using it. The Where X is the corresponding number for the
second and third options are two active low pin you are configuring. And Y is the option
pins that are shared for all output compare you chose.
modules.
Determining Fault Input
Enabling Fault Inputs
Effect
To use fault inputs, use the line of code
below that corresponds to the fault you want The next option determines how the
to use: PWM pin reacts to a fault and the options are
as follows:
OCXCON1bits.ENFLT2 = 1;
OCXCON1bits.ENFLT1 = 1; 1 = Pin is forced to an output on a Fault
OCXCON1bits.ENFLT0 = 1; condition
0 = Pin I/O condition is unaffected by a
Where X is the corresponding number for the
Fault
output compare module, you are configuring.
The first line enables the comparator fault for In the first option you decide which output
the module of your choice, the second the should be forced on the PWM pin. The second
OCFB fault, and the third the OCFA fault. option leaves it wherever the PWM signal was
at be it high or low. To set this option use this
line of code:
Determining Fault Input OCXCON2bits.FLTTRIEN = Y;
Mode Where X is the corresponding number for the
pin you are configuring. And Y is the option
53
you chose. If you chose the first option you _OCXIP = Y;
also must change the following setting which _OCXIF = 0;
determines what to output on a fault condition: _OCXIE = 1;
1 = PWM output is driven high on a Where X represents the module which you are
Fault configuring. Y is the priority of the interrupt
0 = PWM output is driven low on a and needs a value between 1 and 7. 1 being
Fault least important 7 being most important. This
After making a choice set it with this line of only matters if interrupts will potentially occur
code: at the same time and one has to be done before
the other. I always set them as 4. The _OCXIF
OCXCON2bits.FLTOUT = Y;
command clears the interrupt flag. When we
Where X is the corresponding number for the
handle the interrupt, we must clear the flag so
pin you are configuring. And Y is the option
that the code can continue running normally
you chose.
after the interrupt handler or we will be
Resetting Fault Input Status infinitely stuck in the handler. The last line
enables the interrupt. You can enable and
The last thing you may need to do is
disable the interrupt throughout your code
programmatically reset the fault status bit if
which can be useful if there are times where
you set the fault input mode, FLTMD, to 1. If
you are not concerned with counting the PWM
you did you need a line of code from the list
cycles.
below somewhere in your code when you
want the PWM to start again after the fault Handling a Counter Interrupt
condition is resolved:
When the interrupt is called we need to
OCXCON1bits.OCFLT2 = 0; make a handler that clears the interrupt flag
OCXCON1bits.OCFLT1 = 0; and reacts to the interrupt. This can be done in
OCXCON1bits.OCFLT0 = 0; the main.c file or if you are following the good
Where X is the corresponding number for the practices guidelines in your interrupts header
pin you are configuring. The first line is for if a file. The handler function is written as follows:
comparator fault was tripped, the second if void __attribute__ ((interrupt, no_auto_psv))
OCFB fault was tripped, and the third for if the _OC$Interrupt(void){
OCFA fault was tripped.
_OC$IF = 0;
Configuring a Counter /*your code here*/

Interrupt }
Where the $ in the handler name is replaced
A counter interrupt occurs after every
with the number of the module you are
pulse from a PWM signal. It can be used to
configuring. The handler must be written with
count the pulses for things like counting steps
that name in order for the PIC24 to properly
on a stepper motor, counting time, or reacting
function. The interrupt flag line must also
to sensors while a PWM signal is being driven.
always be at the top of the interrupt to clear the
To configure it use the following lines of code:
flag.
54
Optional: Using the Counter
Interrupt without a PWM
Output
The output compare module can be
used in PWM mode like a timer interrupt to
run code at consistent intervals and leave the
pin open for other uses. This is done by using
the following line of code:
OCXCON2bits.OCTRIS = 1;
Where X is the number of the module which
you are configuring. This sets the output of the
compare module to be tri-stated, meaning a
high impedance state that effectively removes
it from the other circuits on the PIC24. This
means we can still use the pin as an analog
input or digital I/O pin while using the output
compare module.

Optional: Inverting the PWM


Output
The output compare module can be
inverted where the duty cycle setting is treated
as the low time percentage of the period. To set
this use this line of code:
OCXCON2bits.OCINV = 1;
Where X is the number of the module which
you are configuring.
55

Code Example – Configuring a PWM


Signal and Counter Interrupt with Fault
Inputs
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 8, 2022
* Description: In this code I configure the PWM signal on pin 14 to run at 50Hz and
* have a duty cycle of 50%. I then configure a counter interrupt that will increment a
* global variable for each pulse of the PWM. Finally, I configure a fault interrupt on
* OCFB (pin 16) that will drive the PWM pin to a low output if it goes low.
* This document is provided as a reference material
*/
#include "xc.h"
int count = 0; //a global variable I want to increment in my counter interrupt
void __attribute__((interrupt, no_auto_psv)) _OC1Interrupt(void){
_OC1IF = 0; //clears interrupt flag
count++; //increments my global variable
/*your code here*/
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
_RCDIV = 0b100; //sets a 16 post scaler
/*Reset All Registers We will be Using*/
OC1CON1 = 0;
OC1CON2 = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Set Output Compare Module Clock*/
OC1CON1bits.OCTSEL = 0b111; //tells the OC1 module to use the system clock
/*Set the Output Compare Operating Mode*/
OC1CON1bits.OCM = 0b110; //sets the OC1 module to Edge-Aligned PWM mode
/*Set the Output Compare Module to Sync Mode*/
56
OC1CON2bits.OCTRIG = 0; //sets the OC1 module to sync mode and not trigger mode
/*Set the Output Compare Module Sync Source*/
OC1CON2bits.SYNSEL = 0b11111; //sets the OC1 module to sync to its own counter
/*Set the PWM Period*/
/*To get 50Hz on the PWM my math would be ((1/50)/(1/250000)) – 1. This gives me 4999
which is in the range of the PWM 16-bit counter*/
OC1RS = 4999; //sets OC1 period to 50Hz
/*Set PWM Duty Cycle*/
/*For a 50% duty cycle I divide the value I got for the period by two which is just under
2500*/
OC1R = 2500; //sets a 50% duty cycle
/*Enable OCFB Fault Input*/
OC1CON1bits.ENFLT1 = 1; //enables the OCFB (pin 16) fault input
/*Set Fault Input Mode*/
OC1CON2bits.FLTMD = 0; //PWM starts automatically after the fault goes high
/*Set Fault Input Effect*/
OC1CON2bits.FLTTRIEN = 1; //forces pin 14 to an output when faulted
/*Set PWM Output after Fault Input*/
OC1CON2bits.FLTOUT = 0; //forces pin 14 to be low when faulted
/*Configuring the Counter Interrupt*/
_OC1IP = 4; //sets the OC1 counter interrupt priority to 4
_OC1IF = 0; //clears the OC1 counter interrupt flag
_OC1IE = 1; //enables the OC1 counter interrupt
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
57

Configuring Input Capture and Input


Capture Interrupts
About Input Capture Select Input Capture Clock
The PIC24 has 3 input compare modules The input capture modules need a clock
onboard that can measure how long a pin has to count off of for their timers. It is important
been on or off. Each of these modules has its to note that the input capture module timers
own in built 16-bit timer. The first two can also have no prescaling capabilities so if you
be linked to make a 32-bit timer if needed. This anticipate a longer period than a 16-bit timer
however can most often be avoided with with your clock frequency would read you
careful planning on the part of the user. If you should plan on using one of the timers that is
intend on using a 32-bit timer see the manual properly setup with a prescaler value to divide
for more details on how this is done. Use input your clock frequency. The options for the clock
capture when measuring pulses from devices selection are as follows:
like the HC-SR04 ultrasonic range finder 111 = System clock (FOSC/2)
whose output is a pulse whose width tells you 110 = Reserved
how far away it sensed an obstacle. See the 101 = Reserved
Code Example below on page 62 for more 100 = Timer1
information on how to do that. The pins that 011 = Timer5
these modules are connected to are as follows: 010 = Timer4
IC1 = pin 14 001 = Timer2
IC2 = pin 13 000 = Timer3
IC3 = pin 15 Use the following line of code after having
Clear Input Capture Control made your selection:
ICXCON1bits.ICXTSEL = 0bYYY;
Registers
Where X is replaced with the value of module
Each of the input capture modules on you are configuring and YYY is replaced with
the PIC24 has two registers that control the the option you chose.
settings for its use. You should clear these
registers when you start your code with these Select Input Capture Mode
lines of code: The input capture module needs to
ICXCON1 = 0; know when its needs to write its timer value to
ICXCON2 = 0; its FIFO buffer. The mode options and a
Where X is replaced with the number of the definition of each are as follows:
module you are configuring. 101 = Prescaler Capture mode: Capture
on every 16th rising edge – This means
that you will have started the ICX timer
earlier and when it has counted 16
58
rising edges whatever the value of that Select Input Capture Timing
timer is gets saved to the ICX FIFO
buffer. Method
100 = Prescaler Capture mode: Capture The input capture modules have three
on every 4th rising edge – This means options on how they can tell when to start their
that you will have started the ICX timer timer and when to reset it. These are the
earlier and when it has counted 4 rising triggered, synchronous, and normal operation
edges whatever the value of that timer is modes. The triggered mode means that the
gets saved to the ICX FIFO buffer. software or a hardware event starts the timer
011 = Simple Capture mode: Capture on and the user must end it programmatically.
every rising edge – This means that you These events could be as follows: another input
will have started the ICX timer earlier capture module saving its data, a comparator
and whenever a rising edge occurs the returning true, an analog to digital converter
value of that timer gets saved to the ICX finishing its conversions, an output compare
FIFO buffer. module finishing one of its periods, or a timer
010 = Simple Capture mode: Capture on reaching its period. The synchronous operating
every falling edge – This means that you mode means that the timer resets at the same
will have started the ICX timer earlier time that another module begins on the PIC24.
and whenever a falling edge occurs the The options for this mode are an output
value of that timer gets saved to the ICX compare module, an input capture module, or
FIFO buffer. a timer. The normal operating mode means
001 = Edge Detect Capture mode: when you turn the module on it will count up
Capture on every edge (rising and to its limit and reset only after it reaches that
falling); ICI<1:0> bits do not control value. To summarize these options, look at this
interrupt generation for this mode – list:
This means that you will have started
Normal Operation Mode – The timer
the ICX timer earlier and whenever a
operates as a standard timer that rolls
rising edge or a falling edge occurs the
over when it reaches its max value.
value of that timer gets saved to the ICX
Synchronous Operation Mode – The
FIFO buffer. Also, the interrupt will go
timer synchronizes itself with another
off after every change in the pins value
timer so that both roll over
and this cannot be changed.
simultaneously
000 = Input capture module is turned off
Software Triggered Operation Mode –
If you are trying to read a pulse coming in you The timer starts when the user sets the
should use the Edge Detect Capture mode TRIGSTAT bit and ends when the user
because it makes it easy to start and end the clears the TRIGSTAT bit
timer as the signal comes in. The other options Hardware/Software Triggered
are more useful for trying to count incoming Operation Mode – The timer starts
pulses and time the interval between them. when a hardware event occurs or when
Later we will discuss timing options and how the user sets the TRIGSTAT bit. It ends
they relate to the options above. when the user clears the TRIGSTAT bit.
59
If you are using the Edge Detect Capture Where X is replaced with the value of the input
mode, I suggest using the Software Triggered capture module you’re configuring. YYYYY is
Operation Mode since it will make your life replaced with one of the following options
easier to measure the time between the two which determines what the timer should sync
edges. Once you have chosen an option from to:
above hop down to section that follows which 10111 = Input Capture 4
explains how to configure that mode. 10110 = Input Capture 3
Normal Operation Mode 10101 = Input Capture 2
10100 = Input Capture 1
In normal operation mode the timer is 01111 = Timer5
always running and when an edge is detected 01110 = Timer4
that matches your input capture mode the time 01101 = Timer3
is written to the FIFO buffer. This mode can 01100 = Timer2
work well for timing the length of a pulse or 01011 = Timer1
the time between pulse but it requires more 01010 = Input Capture 5
thought in the interrupt handler to properly 00101 = Output Compare 5
find the correct time. To use this mode, add the 00100 = Output Compare 4
following lines of code: 00011 = Output Compare 3
ICXCON2.ICTRIG = 0; 00010 = Output Compare 2
ICXCON2.SYNCSEL = 0b00000; 00001 = Output Compare 1
Where X is replaced with the value of the input Software Triggered
capture module you’re configuring.
Operation Mode
Synchronous Operation
In software triggered operation mode,
Mode the timer is held in reset till the TRIGSTAT bit
In synchronous operation mode the is set high by the programmer. Then, until the
falling edge of a sync input signal resets the TRIGSTAT bit is set low, the timer will count
timer. You can use this to ensure that the timer up and roll over at its max period. This is very
of the input capture module counts in sync useful because we can use the edge detect
with another timer or module. This can be input capture mode to set the TRIGSTAT bit at
useful for if you want to only have the max the start of a pulse and turn it off at the end of
period of the input capture module be smaller a pulse. This is very helpful for timing the
than normal and match another 16-bit timer length of a pulse. For timing the amount of
without prescaler. Similar to the normal time between pulses we could set the
operation though it may be difficult to time a TRIGSTAT bit when we get the first rising
pulse width or time how long before another edge and clear it when we get the second rising
signal occurs since the timer is not reset upon edge. In both cases this will ensure our timer
getting a signal. To set this mode use the only counts when we want to measure and not
following lines of code: while idle. To set this mode use the following
ICXCON2.ICTRIG = 0; lines of code:
ICXCON2.SYNCSEL = 0bYYYYY; ICXCON2.ICTRIG = 1;
ICXCON2.SYNCSEL = 0b00000;
60
Where X is replaced with the value of the input Interrupt Rates
capture module you’re configuring.
The input capture module can create
Hardware/Software Triggered interrupts when it saves data to its buffer. If
Operation Mode you want it to save multiple measurements
before setting an interrupt use this line of code:
In hardware/software triggered
ICXCON1bits.ICI = 0bYY;
operation mode an external module event can
start the timer and have it run until the Where X is replaced with the number of the
programmer clears it. It also can function like input capture module you are configuring and
the normal software triggered operation mode. YY is replaced with one of these options:
This can be useful if you anticipate an input 11 = Interrupt on every fourth capture
signal at the end of a timer or other module event
that you want to measure. To set this mode use 10 = Interrupt on every third capture
the following lines of code: event
ICXCON2.ICTRIG = 1; 01 = Interrupt on every second capture
ICXCON2.SYNCSEL = 0bYYYYY; event
00 = Interrupt on every capture event
Where X is replaced with the value of the input
capture module you’re configuring. YYYYY is It is important to note however that if you are
replaced with one of the following options using edge detect mode these bits will not
which determines what the timer should be affect the interrupt. In that mode it interrupts
triggered by: on every edge it finds.
11100 = CTMU Getting Data from the FIFO
11011 = A/D
11010 = Comparator 3
Buffer
11001 = Comparator 2 Each input capture module has a FIFO
11000 = Comparator 1 buffer with space for four measurements. Here
10111 = Input Capture 4 is a diagram to visualize it:
10110 = Input Capture 3 MOST RECENT DATA- FIRST READ
10101 = Input Capture 2
X
10100 = Input Capture 1
X
01111 = Timer5
01110 = Timer4 FURTHEST DATA – LAST READ
01101 = Timer3 This buffer can present complications when
01100 = Timer2 reading data because fi your module is
01011 = Timer1 running at really fast speeds your data can get
01010 = Input Capture 5 buried or lost. One thing you can do before
00101 = Output Compare 5 making an important measurement is clear the
00100 = Output Compare 4 buffer. To do this you have to read all the data
00011 = Output Compare 3 in it. Writing a value to the buffer only adds
00010 = Output Compare 2 data to it. To tell when you the buffer is clear
00001 = Output Compare 1 there is a bit that the hardware sets high when
61
the buffer is not empty. To reset the buffer, use When the interrupt is called we need to
the following code: make a handler that clears the interrupt flag
while(ICXCON1bits.ICBNE == 1){ and reacts to the interrupt. This can be done in
the main.c file or if you are following the good
int temp = ICXBUF;
practices guidelines in your interrupts header
}
file. The handler function is written as follows:
Where X is replaced with the value of the input
void __attribute__ ((interrupt, no_auto_psv))
capture module you are reading from. Each
_IC$Interrupt(void){
read will clear one value from the buffer so the
_IC$IF = 0;
while loop ensures enough reads are made to
/*your code here*/
clear it all. With careful programming you can
avoid getting improper data and only getting }
relevant values. Where the $ in the handler name is replaced
with the number of the module you are
Configuring an Input Capture configuring. The handler must be written with
Interrupt that name in order for the PIC24 to properly
function. The interrupt flag line must also
An input capture interrupt occurs after a
always be at the top of the interrupt to clear the
certain number of signals have been captured.
flag.
To configure it use the following lines of code:
_ICXIP = Y;
_ICXIF = 0;
_ICXIE = 1;
Where X represents the module which you are
configuring. Y is the priority of the interrupt
and needs a value between 1 and 7. 1 being
least important 7 being most important. This
only matters if interrupts will potentially occur
at the same time and one has to be done before
the other. I always set them as 4. The _ICXIF
command clears the interrupt flag. When we
handle the interrupt, we must clear the flag so
that the code can continue running normally
after the interrupt handler or we will be
infinitely stuck in the handler. The last line
enables the interrupt. You can enable and
disable the interrupt throughout your code
which can be useful if there are times where
you are not concerned with capturing the input
signals timing.

Handling an Input Capture


Interrupt
62

Code Example – Configuring Input


Capture with an Input Capture Interrupt
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 13, 2022
* Description: In this code I configure the input capture module on pin 13 to tell me the
* total period of a PWM signal being inputted on that pin. I use only rising edge
* detection and software triggered operation in order to get the proper data. I then
* configure the interrupt to occur every edge it detects and handle the interrupt in my
* code.
* This document is provided as a reference material
* Since I am trying to calculate the period of a PWM signal being inputted I
* should know the limits of my system. To do this I can look at the maximum
* resolution of the input capture module and my oscillator. I know that I cannot
* measure anything less than 6 oscillator cycles due to limitations on the
* interrupts so the fastest PWM frequency I could get would be FOSC/6 which in
* this case would be a max frequency of 41.67 kHz.
*/
#include "xc.h"
float frequency = 0.0; //global variable that stores the value of the incoming frequency
void __attribute__((interrupt, no_auto_psv)) _IC2Interrupt(void){
_IC2IF = 0; //clears interrupt flag
if(IC2CON2bits.TRIGSTAT == 0){ //At the start of a new PWM period
IC2CON2bits.TRIGSTAT = 1; //starts IC2 timer
while(IC2CON1bits.ICBNE == 1){ //clears IC2 buffer
int temp = IC2BUF;
}
}
else{
IC2CON2bits.TRIGSTAT = 0; //stops timer at start of next PWM period
frequency = (250000/IC2BUF) //gives me the input PWM frequency
}
}
#pragma config ICS = PGx3
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
_RCDIV = 0b100; //sets a 16 post scaler
/*
63
/*Reset All Registers We will be Using*/
IC2CON1 = 0;
IC2CON2 = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Configure Input Capture 2 Module Clock*/
IC2CON1bits.IC2TSEL = 0b111; //sets IC2 module clock to the system clock
/*Configure Input Capture 2 Module Capture Mode*/
IC2CON1bits.ICM = 0b011; //sets the IC2 module mode to capture rising edges
/*Configure Input Capture 2 Module in Software Triggered Operation Mode*/
IC2CON2bits.ICTRIG = 1; //sets the IC2 module to trigger mode
IC2CON2bits.SYNCSEL = 0b00000; //sets the IC2 module as software triggered
/*Set Input Capture 2 Module Interrupt Rate*/
IC2CON1bits.ICI = 0b00; //sets interrupt to occur on every capture event
/*Configure the Input Capture 2 Module Interrupt*/
_IC2IP = 4; //sets the IC2 interrupt priority to 4
_IC2IF = 0; //clears the IC2 interrupt flag
_IC2IE = 1; //enables the IC2 interrupt
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}
64

Code Example – Configuring Input


Capture with a HC-SR04 Ultrasonic
Range Finder
/*
* File: main.c
* Author: Spencer Mosley
* Created on June 13, 2022
* Description: In this code I configure the input capture module on pin 13 to tell me the
* total length of the echo pulse from the HC-SR04 on that pin. I use the edge detect
* mode to find both the rising and falling edges. I then use the interrupt handler to stop
* and end the timer and calculate the distance it saw. Finally, I configure timer 2 so that
* it will send the 10uS output pulse on pin 12 for the HC-SR04 trigger input and wait .5
* sec between each trigger pulse
* This document is provided as a reference material
* Since I am trying to calculate the length of a pulse being inputted I should
* know the limits of my system. To do this I can look at the maximum length of
* the echo pulse output. From the datasheet I know that it can measure 4 meters
* and it says that uS/58 = cm or uS/148 = in. Therefore, 400 * 58 is the max pulse
* width in uS I can measure. This ends up being 23200uS. At a clock frequency of
* 8MHz we can measure increments of .25uS which means our 16-bit timer
* would need to count 92800 for the full distance possible. Since this is out of
* range we select a postscaler of 2 which gives us a max count of 46400 and a
* resolution of .5uS. That means our distances will have a very small error in
* them but it is a maximum of .5/58 = .0086cm which is negligible in our
* application of not touching a wall.
*/
#include "xc.h"
float distance = 0.0; //global variable that stores the value of the measured distance
void __attribute__((interrupt, no_auto_psv)) _IC2Interrupt(void){
_IC2IF = 0; //clears interrupt flag
if(IC2CON2bits.TRIGSTAT == 0){ //At the start of a new pulse
IC2CON2bits.TRIGSTAT = 1; //starts IC2 timer
while(IC2CON1bits.ICBNE == 1){ //clears IC2 buffer
int temp = IC2BUF;
}
}
else{
65
IC2CON2bits.TRIGSTAT = 0; //stops timer at end of pulse period
distance = IC2BUF/2000000; //converts timer value to seconds
distance*= 1000000; //converts to uS
distance/= 58; //converts to cm
IC2TMR = 0; resets timer for next pulse;
}
}
void __attribute__((interrupt, no_auto_psv)) _T2Interrupt(void){
_T2IF = 0; //clears interrupt flag
if(PR2 == 20){ //if sending the 10uS pulse
if(_LATB8 == 0){ //if pin 12 is off
_LATB8 = 1; //turns pin 12 on
}
else{ //if pulse is sent
T2CONbits.TON = 0; //turn timer 2 off
_LATB8 = 0; //turn off pin 12
T2CONbits.TCKPS = 0b10; //sets a timer prescaler value of 64
PR2 = 15625; //sets a period of half a second
TRM2 = 0; //resets timer 2 value
T2CONbits.TON = 1; //turns timer 2 back on
}
}
else if(PR2 == 15625){ //if we waited half a second
T2CONbits.TON = 0; //turn off timer 2
T2CONbits.TCKPS = 0b00; //sets a timer prescaler value of 1
PR2 = 20; //sets timer 2 period to 10uS
TMR2 = 0; //resets timer 2 value
T2CONbits.TON = 1; //turns timer 2 back on;
}
}
#pragma config ICS = PGx3 //configures programming and debugging pins
#pragma config FNOSC = FRCDIV //Sets clock to 8 MHZ oscillator with post scaler
int main(void) {
_RCDIV = 0b001; //sets a 2 post scaler
/*Pin 12 is RB8*/
/*Pin 13 is RB9*/
/*Reset All Registers We will be Using*/
IC2CON1 = 0;
IC2CON2 = 0;
T2CON = 0;
LATA = 0;
LATB = 0;
TRISA = 0;
66
TRISB = 0;
ANSA = 0;
ANSB = 0;
/*Configure Pin 12 as a Digital Output*/
_TRISB8 = 0; //sets pin 12 as a digital output
_LATB8 = 0; //sets pin 12 output to low
/*Configure Pin 13 as a Digital Input*/
_TRISB9 = 1; //sets pin 13 as a digital input
/*Configure Input Capture 2 Module Clock*/
IC2CON1bits.IC2TSEL = 0b111; //sets IC2 module clock to the system clock
/*Configure Input Capture 2 Module Capture Mode*/
IC2CON1bits.ICM = 0b001; //sets the IC2 module mode to capture every edge
/*Configure Input Capture 2 Module in Software Triggered Operation Mode*/
IC2CON2bits.ICTRIG = 1; //sets the IC2 module to trigger mode
IC2CON2bits.SYNCSEL = 0b00000; //sets the IC2 module as software triggered
/*Set Input Capture 2 Module Interrupt Rate*/
IC2CON1bits.ICI = 0b00; //sets interrupt to occur on every capture event
/*Configure the Input Capture 2 Module Interrupt*/
_IC2IP = 4; //sets the IC2 interrupt priority to 4
_IC2IF = 0; //clears the IC2 interrupt flag
_IC2IE = 1; //enables the IC2 interrupt
/*Configure Timer 2*/
T2CONbits.TCS = 0; //sets timer 2 clock source to system clock
T2CONbits.TCKPS = 0b00 //sets a prescaler value of 1
T2CONbits.T32 = 0; //unlinks timers 2 and 3
PR2 = 20 //sets the period to 10uS
TMR2 = 0; //resets the timer 2 value
/*Configure Timer 2 Interrupt*/
_T2IP = 4; //sets the timer 2 interrupt priority to 4
_T2IF = 0; //clears the timer 2 interrupt flag
_T2IE = 1; //enables the timer 2 interrupt
/*Turn on Timer 2*/
T2CONbits.TON = 1; //turns on timer 2
/*Let the Interrupts Control the Rest of the Code*/
while(1);
return 0;
}

You might also like