Professional Documents
Culture Documents
au
This series of lessons introduces the enhanced mid-range PIC architecture, using assembly language.
It starts by simply making an LED, connected to one of the output pins of a PIC, light up.
The apparently straightforward task of lighting an LED – never mind flashing it or anything else1 – relies
on:
Having a functioning circuit in a workable prototyping environment
Being able to use a development environment; to go from text to assembled PIC code
Being able to correctly use a PIC programmer to load the code into the PIC chip
Correctly configuring the PIC
Writing code that will set the correct pin to output a high or low (depending on the circuit)
If you can get an LED to light up, then you know that you have a development, programming and
prototyping environment that works, and enough understanding of the PIC architecture and instructions to
get started. It’s a firm base to build on.
In summary, this lesson covers:
Introduction to the enhanced mid-range PIC architecture, using the PIC12F1501
Simple control of digital output pins
Using MPLAB X to create assembly language projects
Using a PICkit 3 programmer with MPLAB X
Getting Started
For some background on PICs in general and details of the recommended development environment, see
lesson 0. Briefly, these tutorials assume that you are using a Microchip PICkit 3 programmer and either
the Gooligum Baseline and Mid-range PIC Training and Development Board or Microchip’s Low Pin
Count (LPC) Demo Board, with Microchip’s MPLAB X integrated development environment. But it is of
course possible to adapt these instructions to different programmers and/or development boards.
The four LEDs on the LPC demo board don’t work (directly) with 8-pin PICs, such as the 12F1501. So to
complete this lesson, using an LPC demo board, you need to either add an additional LED and resistor to
the prototyping area on your board, or use some solid core hook-up wire to patch one of the LEDs to the
appropriate PIC pin, as described later.
1
We’ll get to the traditional first exercise in microcontroller programming of flashing an LED in lesson 2…
This is one reason the Gooligum training board was developed to accompany these tutorials – if you have
the Gooligum board, you can simply plug in your 8-pin 12F PIC, and go.
We’re going to start with the simplest enhanced mid-range PIC – the PIC12F1501.
Of course, “simplest” is a relative term. The enhanced mid-range architecture is certainly more complex
than the earlier baseline PIC architecture, introduced in the Baseline PIC Assembler tutorial series. Those
lessons were able to start with a very simple PIC indeed (the 10F200), which made it possible to introduce
only a few basic topics at first, without needing to say “ignore this for now; we’ll explain later”. More
advanced topics were introduced by moving up to more advanced baseline and then eventually mid-range
PICs through the Mid-range PIC Assembler tutorial series – building on what came before.
This tutorial series doesn’t refer back to those earlier lessons – it’s a fresh start. Unfortunately that does
make it harder to ignore some of the complexities of the enhanced mid-range architecture, although we’ll
keep it as simple as possible to begin with. If you do want to start learning with simpler PICs, you should
consider working through the baseline and mid-range tutorial series.
But to repeat – the earlier lessons are not a prerequisite for these enhanced mid-range lessons.
In summary, for this lesson you should ideally have:
A PC running Windows 7 or 8, with a spare USB port
Microchip’s MPLAB X IDE software
A Microchip PICkit 3 PIC programmer
The Gooligum mid-range training board
A PIC12F1501-I/P microcontroller (supplied with the Gooligum training board)
We’ll look at the various features mentioned in the table, such as timers, analog inputs, and EEPROM
memory, in later lessons. But even without knowing what these things are, you can see that the 12F1501
has fewer features than other enhanced mid-range PICs, such as the 12F1822 or 12F1840, while being
roughly comparable to the 12F675 and 12F683 mid-range devices, and significantly more capable than the
baseline 12F508.
The 12F family are all 8-pin devices, with six pins available for I/O (input and output).
They share a common pin-out, as shown below.
VDD is the positive power supply.
VSS is the “negative” supply, or ground. All
VDD 1 8 VSS of the input and output levels are measured
relative to VSS. In most circuits, there is only
PIC12F1501
a single ground reference, considered to be at
RA5 2 7 RA0
0 V (zero volts), and VSS will be connected to
ground.
RA4 3 6 RA1 The power supply voltage on the PIC12F1501
can range from 2.3 V to 5.5 V2.
RA3/ MCLR 4 5 RA2 This wide range means that the PIC’s power
supply can be very simple. Depending on the
circuit, you may need no more than a pair of
1.5 V batteries.
Normally you’d place a capacitor, typically 100 nF and ceramic, between VDD and VSS, close to the chip,
to smooth transient changes to the power supply voltage caused by changing loads (e.g. motors, or
something as simple as an LED turning on) or noise in the circuit.
The remaining pins, RA0 to RA5, are the I/O pins. They can be used directly as simple digital inputs or
outputs, or they can be configured as analog inputs for peripherals such as comparators and analog-to
digital converters or as outputs for peripherals such as the pulse-width modulation (PWM) module.
PIC12F1501 Internals
8-bit PICs use a so-called Harvard architecture, where program and data memory is entirely separate.
In the 12F1501, program memory extends from 0000h to
03FFh (hexadecimal). Each of these 1024 addresses can hold Standard bank layout
a separate 14-bit word – usually a program instruction. Offset Register Type
User code starts by executing the instruction at 0000h, and 00h
then proceeds sequentially from there – unless of course your Core
program includes loops, branches or subroutines, which any Registers
real program will! 0Bh
0Ch
Special Function
The data memory, also known as the register file, in enhanced Registers
mid-range PICs is banked; it is divided into 32 banks, with 1Fh
128 addresses in each bank. Each address may be empty, or it 20h
may hold an 8-bit register used to control the PIC or a byte of
General Purpose
general purpose memory where your program can store data.
RAM
6Fh
Each bank (other than bank 31, as we’ll see later) is laid out
70h
the same way, as illustrated on the right. Common
RAM
7Fh
2
A low-power variant, the PIC12LF1501, is also available, where V DD can range from 1.8 V to 3.6 V, with at least
2.5 V needed for clock rates above 16 MHz in both variants.
‘movlw’ is our first PIC assembler instruction. It loads the W register with an 8-bit value (between 0 and
255), which may represent a number, character, or something else.
Microchip calls a value like this, which is embedded in an instruction, a literal. They also refer to a load
or store operation as a ‘move’ (even though nothing is moved; the source never changes).
So, ‘movlw’ means “move literal to W”.
If you wanted to write that value into (for example) the BSR register, you would use:
movwf BSR
The ‘movwf’ instruction copies (Microchip would say “moves”) the contents of the W register into the
specified register – “move W to register file”.
The WREG register provides access to W, for those occasional situations where it would be useful to be
able to explicitly operate on W.
We’ll see some examples of using WREG in later lessons, but most of the time you’ll use instructions
such as “movlw” or “movwf” to access W directly; it’s rare to have to access it via the WREG register.
In the enhanced mid-range architecture, only seven bits are allocated to register addressing.
For example, the opcode for movwf is 0000001, which is seven bits long, and the remaining seven bits
specify which register is to be acted on (the register that the contents of W will be copied into).
Seven bits is enough to allow up to 128 registers, numbered from 0 to 127 (or 00h to 7Fh in hexadecimal),
to be addressed. This 7-bit register addressing limitation is why each register bank consists of only 128
addresses. So to allow for more than a total of 128 registers, or data memory addresses, the data memory
has to be divided into multiple banks – 32 of them in the enhanced mid-range architecture.
This scheme provides for 32 × 128 = 4096 data memory addresses.
To specify which of these 4096 possible addresses (000h – FFFh) an instruction will access, we need two
things: the address offset (00h – 7Fh) within the bank, and which bank (0 – 31 or 00h – 1Fh) to access.
As we’ve seen, the offset is specified as a 7-bit field within the instruction opcode. But how to specify the
bank to access, if it’s not part of the instruction? That’s where the bank select register, BSR, comes in.
As you might guess, BSR holds a 5-bit number (0 – 31) specifying the currently selected bank.
When an instruction accesses a register, the 7-bit offset is taken from the instruction opcode, while the
bank number is taken from BSR. Or putting it another way, BSR provides the most significant five bits
of the 12-bit data memory address, with the instruction opcode providing the lower 7 bits of the address.
When you’re accessing a core register, banking isn’t a problem. The core registers appear in every bank,
always at the same offset, so it doesn’t matter which bank is selected when you go to access them.
But as we’ll see in the next section, the special function registers are different in each bank, making it
necessary to select the correct bank before attempting to access any of them.
For example, the TRISA register, which we’ll introduce later in this lesson, is at data memory address
08Ch, which is another way of saying that it’s at address offset 0Ch in bank 1.
Suppose we want to write the binary value ‘111101’ into TRISA (you’ll see why, in the example later).
To access TRISA, we must first select bank 1, by writing the value ‘1’ into BSR.
We can do this with:
movlw 1 ; select bank 1:
movwf BSR ; write '1' to BSR
Alternatively (and better), we could use an enhanced mid-range PIC instruction, ‘movlb’, which writes a
literal value directly into the bank select register – “move literal to BSR”
For example:
movlb 1 ; select bank 1
By the way, it should be obvious why BSR has to be available, at the same location, in every bank. If
BSR only appeared in one bank, you’d have to select that bank before you could update BSR – but to
select a bank, you need to update BSR… Having BSR available in every bank avoids this “Catch 22”
situation.
With 20 × 31 = 620 addresses available for SFRs, the enhanced mid-range architecture has enough
potential register locations to support a wide range of advanced peripherals, such as USB, which require a
large number of registers to configure, control and access them.
But since the 12F1501 is a relatively basic device, compared with other enhanced mid-range PICs, it
doesn’t need hundreds of special function registers. Many of its 32 banks do not contain any SFRs.
Others contain only a few.
Nevertheless, the 12F1501 does have dozens of special function registers, scattered across several banks.
It doesn’t make sense to list them all here – you should refer to the PIC12F1501 data sheet for that – but
the table on the next page shows the content of the first four banks, where most of the commonly-used
SFRs, including those references in this lesson, are located.
3
Note however that their contents are lost whenever the device loses power.
This means that enhanced mid-range PICs can have up to 80 × 31 = 2480 bytes of general purpose RAM.
The 12F1501, however, has only 48 bytes of general purpose RAM, all in bank 0, as shown in the diagram
on the previous page.
So, although the enhanced mid-range architecture allows for up to 2480 bytes of general purpose RAM,
most devices will have less than that amount.
Common RAM
The last 16 addresses of each bank are used for “common RAM”.
Every enhanced mid-range PIC device has exactly 16 bytes of common RAM – no more, and no less.
It is equivalent to general purpose RAM, except it is mapped into the same location in every bank.
This makes it very useful for the storage of commonly-accessed variables, allowing you to reduce your
code size by avoiding the need for bank selection instructions when accessing those variables. But with
only 16 bytes available, you should try to use it sparingly.
So overall the 12F1501 has only 48 bytes of general purpose RAM, all in bank 0, along with 16 bytes of
common RAM (mapped into every bank) giving it a total of 64 bytes of data memory.
Finally, bank 31 contains registers related to interrupts and the hardware stack – topics that, once again,
we’ll look at in future lessons.
If a pin is configured as an output, and assuming that no peripherals have been configured to take control
of that pin, setting the corresponding PORTA bit to ‘1’ outputs a high voltage4 on the pin; clearing it to
‘0’ outputs a low voltage5.
If a pin is not configured as an analog input, reading the PORTA register provides a digital representation
of the voltage present on each pin. If the voltage on a pin is high6, the corresponding bit reads as ‘1’; if the
input voltage is low7, the corresponding bit reads as ‘0’.
4
a ‘high’ output will be within 0.7 V of the supply voltage (V DD), for small pin currents (< 3.5 mA with VDD = 5 V)
5
a ‘low’ output is less than 0.6 V, for small pin currents (< 8 mA with V DD = 5 V)
6
the threshold level depends on the power supply, but a ‘high’ input is any voltage above 2.0 V, given a 5 V supply
7
a ‘low’ input is anything below 0.8 V, given a 5 V supply – see the data sheet for details of each of these levels
To configure a pin as an input, set the corresponding bit in the TRISA register to ‘1’. To make it an
output, clear the corresponding TRISA bit to ‘0’.
Why is it called ‘TRIS’? Each pin (except RA3) can be configured as one of three states: high-impedance
input, output high, or output low. In the input state, the PIC’s output drivers are effectively disconnected
from the pin. Another name for an output than can be disconnected is ‘tri-state’ – hence, TRIS.
Note that bit 3 of TRISA is greyed-out. Clearing this bit will have no effect, as RA3 is always an input.
The default state for each pin is ‘input’; TRIS is set to all ‘1’s when the PIC is powered on or reset.
The ANSELA register is used to select analog or digital input mode for certain pins:
If a bit in the ANSELA register is set to ‘1’, the digital input buffer of the corresponding pin is disabled,
and the corresponding bit in PORTA will read as ‘0’.
We’ll examine analog inputs in a later lesson, but for now it’s important to note that any pins with an
associated analog input function (RA0, RA1, RA2 and RA4) default to analog mode, and that the
corresponding ANSELA bit must be cleared to ‘0’ before any of these pins can be used as digital inputs.
Assuming that a pin has not been configured as an analog input, PORTA reflects the actual voltage
present on that pin, even if the pin is configured as an output.
Note: the port registers represent the actual voltages present on each digital I/O pin, including
pins configured as digital outputs
If you attempt to output a ‘high’ on an output pin by writing a ‘1’ to the corresponding port bit, but the
external circuit holds that pin low, that pin will read as ‘0’ – not what you might have expected.
This behaviour can lead to what are known as read-modify-write problems, where instructions which are
intended to modify only specific pins actually read the entire port, including pins which may not reflect
the value that had been output to them, and then write the new value (with some bits possibly incorrect)
back to the port.
To avoid the potential for read-modify-write problems, the enhanced mid-range architecture makes
available an “output data latch” register, associated with each port:
Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
LATA LATA5 LATA4 LATA2 LATA1 LATA0
Writing to LATA has the same effect as writing to PORTA: if a pin in configured as an output, setting the
corresponding LATA bit to ‘1’ outputs a ‘high’ on that pin; clearing it to ‘0’ outputs a ‘low’.
However – reading LATA returns the value that was last written to LATA. It does not read the voltages on
the pins (whether input or output) themselves.
This means that you can avoid read-modify-write problems by following these rules:
if you are writing an entire byte to a port, you can write to either PORTA or LATA
if you are modifying individual port pins, you should operate on LATA
if you are reading digital input pins, you must read PORTA
To keep it simpler, you won’t run into any problems if you always access LATA to write to or modify
output pins, and PORTA to read digital input pins.
When configured as an output, each I/O pin on the 12F1501 can source or sink (i.e. current into or out of
the pin) up to 25 mA – enough to directly drive an LED.
PICs are tough devices, and you may get away with exceeding these limits – but if you ignore the absolute
maximum ratings specified in the data sheet, you’re on your own. Maybe your circuit will work, maybe
not. Or maybe it will work for a short time, before failing. It’s better to follow the data sheet…
Example Circuit
We now have enough background information to design a circuit to light an LED.
We’ll need a regulated power supply, let’s assume 5 V, connected to VDD and VSS. And remember that
we should add a bypass capacitor, preferably a 100 nF (or larger) ceramic, across it. We’ll also need an
LED of course, and a resistor to limit the current.
Although the PIC12F1501 can supply up to 25 mA from a single pin, 10 mA is more than enough to
adequately light most LEDs. With a 5 V supply and assuming a red or green LED with a forward voltage
of around 2 V, the voltage drop across the resistor will be around 3 V.
Applying Ohm’s law, R = V / I = 3 V ÷ 10 mA = 300 Ω. Since precision isn’t needed here (we only need
“about” 10 mA), it’s ok to choose the next highest “standard” E12 resistor value, which is 330 Ω. It
means that the LED will draw less than 10 mA, but that’s a good thing, because, if we’re going to use a
PICkit 3 to power the circuit, we need to limit overall current consumption to 30 mA, because that is the
maximum current the PICkit 3 can supply.
Finally, we need to connect the LED to one of the PIC’s pins.
We can’t choose RA3, because it’s input-only.
If you’re using the Gooligum training board, you could
choose any of the other pins, but if you use the Microchip
LPC Demo Board to implement the circuit, it’s not a good
idea to use RA0, because it’s connected to a trimpot on the
LPC demo board, which would divert current from the LED.
So, we’ll use RA1, giving the circuit shown on the right.
8
Note that, although the PIC12F1501 comes in an 8-pin package, it will not work in the 8-pin ‘10F’ socket. You
must install it in the ‘12F’ section of the 14-pin socket.
Development Environment
You’ll need Microchip’s MPLAB Integrated Development Environment (MPLAB IDE), which you can
download from www.microchip.com.
As discussed in lesson 0, MPLAB comes in two varieties: the older, Windows-only MPLAB 8, and the
newer multi-platform MPLAB X. Although MPLAB 8 remains a stable and usable environment, it has
been effectively retired by Microchip and will not continue to be updated.
Therefore, this tutorial series assumes that you will be using MPLAB X.
Installation
You should download the MPLAB X IDE installer for your platform (Windows, Linux or Mac) from the
MPLAB X download page at www.microchip.com, and then run it.
There are no installation options (other than being able to choose the installation directory). It’s an “all or
nothing” installer, including the MPASM assembler and support for all of Microchip’s PIC MCUs and
development tools.
When the installation completes, you are prompted to download one (or more) of Microchip’s “XC” series
of C compilers:
Even if you want to use C in addition to assembly language, there is no problem with downloading and
installing the compilers separately, later. So you can leave this box unchecked for now, and then click
‘Finish’ to finish the installation.
In the first step, you need to specify the project category. Choose ‘Standalone Project’:
The next step is to select the tool you will use to program your PIC.
First, you should plug in the programmer (e.g. PICkit 3) you intend to use. If it is properly connected to
your PC, with a functioning device driver9, it will appear in the list of hardware tools, and you should
select it, as shown:
After selecting the hardware tool, you select the compiler (or, in our case, assembler) you wish to use:
9
There is no need to install a special device driver for the PICkit 3; it works “out of the box”.
Finally, you need to specify your project’s location, and give it a name:
For example, in the environment used to develop these tutorials, all the files related to this lesson,
including schematics and documentation, are placed in a folder named ‘1 - Light an LED’, which is the
“Project Location” given above.
By default, MPLAB X then creates a separate folder for the PIC source code and other files related to this
project, in a subfolder that has the same name as the project, with a ‘.X’ on the end. If you wish, you can
remove the ‘.X’ extension from the project folder, before you click on ‘Finish’.
If you select “Use project location as the project folder”, this behaviour changes – the project files are then
placed in the “Project Location” folder, instead of being in a separate folder. This isn’t recommended,
which is why that box is left unchecked above. But if you prefer not to have a separate folder for the
MPLAB files, you can select this option.
Note the warning about project name and folder path length. To avoid possible problems, it’s best to use
shorter names and paths, when using Windows, although in this case it’s actually ok.
Since this is the only project we’re working on, it doesn’t make much difference whether you select “Set
as main project”; this is something that is more useful when you are working with multiple projects.
After you click “Finish”, your workspace should look something like this:
The panel in the top left allows you to see and access the various files that comprise your project – we’ll
add a source code file (which will appear under “Source Files” in the next step.
The panel in the bottom left shows your project’s configuration and status, such as which device you’re
using (12F1501), the selected programmer (shown here as “Debug Tool”) and the amount of PIC program
(“Flash”) and data (“RAM”) memory our program is using – 0% for now, because we haven’t created a
program yet!
The largest panel, in the upper right, is where you edit your source code.
Below it is a panel where you’ll see the status of processes such as assembling your program and
programming the PIC.
Note that MPLAB X has many, many features that we won’t be exploring in these tutorials. These lessons
are about PIC programming, not using MPLAB X. So it’s worth taking some time to explore the training
resources available from the “Learn & Discover” tab shown above.
There are a couple of ways to create a new source file and add it to the project.
You could select the “File → New File…” menu item, press Ctrl+N, or click on the “New File” button in
the toolbar:
The “8b” refers to 8-bit PICs. Since we’re starting with a simple example, the “simple 8-bit asm file” is
the best choice for now. But when you begin working on more advanced programs, you may find that the
“pic_8b_general.asm” option is a better choice.
After you click ‘Next’, you have the option of naming your file:
The “Folder” field allows you to place your file in a different directory, not necessarily within the project
folder. You wouldn’t normally do that, so it’s ok to leave it blank, as shown here.
The second way to create a new assembler source file is to right-click ‘Source Files’ in the project tree,
and select “New → pic_8b_simple.asm…”:
Either way, your source file should now appear under ‘Source Files’ in the project tree, and you should be
able to see the source code in the editor window, as shown:
If you don’t see the source code, you may need to click on the tab at the top of the editor pane, or double-
click the source file name in the project tree.
Now you can finally start working on your code!
Program Code
As we’ll see in lesson 3, a large program can consist of a number of source files, each containing various
modules or definitions, or, in a simple example such as this one, you may have only a single source file.
Regardless of whether a source file stands on its own or is part of a larger program, it is usual to begin it
with a block of comments, providing essential information about the source file such as what it’s called,
the last modification date and current version (and sometimes a history of previous versions, what has
changed in this version, and who changed it), who wrote it, and a general description of what the program
or module does.
It can also be useful to include a “Files required” section. This is helpful in larger projects, where your
code may rely on other files or modules; you can list any dependencies here.
It is also a good idea to include information on what processor this code is written for; useful if you move
it to a different PIC later. You should also document what each pin is used for. It’s common, when
working on a project, to change the pin assignments – often to simplify the circuit layout. Clearly
documenting the pin assignments helps to avoid making mistakes when they are changed!
As mentioned earlier, MPASM comments begin with a ‘;’. They can start anywhere on a line. Anything
after a ‘;’ is ignored by the assembler.
The following comment block illustrates the sort of information you should include at the start of each
source file:
;************************************************************************
; *
; Filename: EA_L1_1-Turn_on_LED.asm *
; Date: 15/9/13 *
; File Version: 0.1 *
; *
; Author: David Meiklejohn *
; Company: Gooligum Electronics *
; *
;************************************************************************
; *
; Architecture: Enhanced Mid-range PIC *
; Processor: 12F1501 *
; *
;************************************************************************
; *
; Files required: none *
; *
;************************************************************************
; *
; Description: Lesson 1, example 1 *
; *
; Turns on LED. LED remains on until power is removed. *
; *
;************************************************************************
; *
; Pin assignments: *
; RA1 = indicator LED *
; *
;************************************************************************
Note that the file version is ‘0.1’. I don’t call anything ‘version 1.0’ until it works; when I first start
development I use ‘0.1’. You can use whatever scheme makes sense to you, as long as you’re consistent.
You can type these comments into the start of the source code in the editor pane – or, better, copy and
paste them from the source file provided with this lesson.
This is reminding us that our program should start with processor configuration code.
The 12F1501 has a number of options that are selected by setting various bits in a pair of “configuration
words”, sometimes known as “fuses”, which sit outside the normal address space.
The __CONFIG assembler directive is used to specify these configuration bits.
We could look up the configuration bits in the data sheet and type in the appropriate __CONFIG directives
ourselves – and in fact, when you’re creating a new program, based on one that you’ve worked on before
(as you’ll often do), it’s quite normal to directly edit the __CONFIG directives.
But as this comment suggests, MPLAB X includes a generator which can create these directives for us.
To use it, select the “Window → PIC Memory Views → Configuration Bits” menu item.
You will see the processor configuration options in the “Configuration Bits” window under the editor
pane:
As you can see, there are quite a few options, but you only need to change three (shown in red, above):
FOSC = INTOSC
WDTE = OFF
LVP = OFF
When you have made these selections, click on the ‘Generate Source Code to Output’ button.
The generated source code will appear in the “Config Bits Source” tab in the “Output” window:
You can then copy and paste (using the usual select and right-click method) this into your source code in
the editor window, replacing the “INSERT CONFIG CODE HERE” comment.
The ‘#include’ directive causes an include file (‘p12f1501.inc’10, located in the ‘mpasmx’ folder within
the MPASM install directory11) to be read by the assembler. This file sets up aliases for all the features of
10
If you are using Linux, filenames are case sensitive so you should change this to ‘ #include "p12f1501.inc"’
11
If you are using Windows, this will typically be ‘C:\Program Files\Microchip\MPLABX\mpasmx’.
the processor, so that we can refer to registers, bits, etc. by name (e.g. ‘PORTA’, ‘RA1’) instead of numbers.
Lesson 6 explains how this is done; for now we’ll simply use these pre-defined names, or labels.
If the filename specified in the ‘#include’ directive contains spaces, it must be enclosed in quotes (as
shown) or in angle brackets (<>).
We’ll examine these in greater detail in later lessons, but briefly the options being set here are:
_FOSC_INTOSC
This selects the internal RC oscillator as the clock source.
Every processor needs a clock – a regular source of cycles, used to trigger processor operations
such as fetching the next program instruction.
Most modern PICs, including the 12F1501, include an internal ‘RC’ oscillator, which can be used
as the simplest possible clock source, since it’s all on the chip! It’s built from passive components
– resistors and capacitors – hence the name RC.
The internal RC oscillator on the 12F1501 runs at approximately 16 MHz and by default this is
divided down to 500 kHz. Program instructions are processed at one quarter this speed: 125 kHz,
or 8 µs per instruction.
Alternatively, the 12F1501 can use a (possibly more accurate) external clock signal, via the
CLKIN pin. This shares its physical pin with RA5, so if you’re using an external clock, you can’t
use the RA5 pin for I/O.
To turn on an LED, we don’t need accurate timing, so we’ll use the internal RC oscillator.
_CLKOUTEN_OFF
Regardless of whether the internal RC oscillator or an external clock signal is used as the
processor clock (FOSC) source, the instruction clock (FOSC/4) can optionally be output on the
CLKOUT pin, to allow other devices to be synchronised with the PIC’s operation. CLKOUT
shares its pin with RA4, so if you’re using the clock out facility, you can’t use RA4 for I/O.
We don’t need to use CLKOUT, so we will leave this feature disabled.
_MCLRE_ON
Enables external reset, or “master clear” ( MCLR ) on pin 4.
If external reset is enabled, pulling this pin low will reset the processor. Or, if external reset is
disabled, the pin can be used as an input: RA3. That’s why, on the circuit diagram, pin 4 is
labelled “RA3/ MCLR ”; it can be either an input pin or an external reset, depending on the setting
of this configuration bit.
The Gooligum training board includes a pushbutton which will pull pin 4 low when pressed,
resetting the PIC if external reset is enabled. The PICkit 3 is also able to pull the reset line low,
allowing MPLAB to control MCLR (if enabled) – useful for starting and stopping your program.
So unless you need to use every pin for I/O, it’s a good idea to enable external reset by including
‘_MCLRE_ON’ in the __CONFIG directive.
_CP_OFF
Turns off code protection.
When your code is in production and you’re selling PIC-based products, you may not want
competitors stealing your code. If you specify _CP_ON instead, your code will be protected,
meaning that if someone tries to use a PIC programmer to read it, all they will see are zeros.
Since we’re not designing anything for sale, we’ll make our lives easier by leaving code
protection turned off.
_WDTE_OFF
Disables the watchdog timer.
This is a way of automatically restarting a crashed program; if the program is running properly, it
continually resets the watchdog timer. If the timer is allowed to expire, the program isn’t doing
what it should, so the chip is reset and the crashed program restarted – see lesson 8.
The watchdog timer is very useful in production systems, but a nuisance when prototyping, so
we’ll leave it disabled.
_BOREN_ON
Enables brown-out resets.
The PIC’s operation can become unreliable if the supply voltage drops too low, which can happen
during a brown-out, when the supply voltage sags, but does not fall quickly to zero. The 12F1501
has brown-out detect circuitry, which will reset the PIC in a brown-out situation, if _BOREN_ON is
selected.
Although your power supply is not likely to suffer from brown-outs, it doesn’t hurt to leave this
option enabled – just in case.
_BORV_LO
Selects the low brown-out reset voltage option
This option selects the voltage level at which the brown-out reset (if enabled) will be tripped.
_LPBOR_OFF
Disables low-power brown-out resets.
The 12F1501 also has a lower-power brown-out reset facility; we can leave it disabled.
_PWRTE_OFF
Disables the power-up timer.
When a power supply is first turned on, it can take a while for the supply voltage to stabilise,
during which time the PIC’s operation may be unreliable. If the power-up timer is enabled, the
PIC is held in reset (it does not begin running the user program) for some time, nominally 64 ms,
after the supply voltage reaches a minimum level.
However, since we have enabled the brown-out reset facility, which will prevent the device from
starting until the supply voltage is high enough, we don’t need to also enable the power-up timer.
_WRT_OFF
Disables flash memory write protection.
Many newer PIC devices, including the 12F1501, are capable of writing to their flash (program)
memory. This is useful in a number of situations, including boot loaders, which allow program
firmware to be updated easily in the field, or to store data long term (flash memory being non-
volatile).
Of course, you don’t want your program to be overwritten by mistake! To prevent that from
happening, you may wish to write-protect all or some of the flash memory.
Nevertheless, it’s safe in this example to leave flash write protection disabled.
_STVREN_ON
Enables stack overflow/underflow resets.
As we’ll see in lesson 3, the stack is a special set of registers used when calling subroutines.
Although we won’t be using the stack in this example, it doesn’t hurt to leave this feature enabled.
_LVP_OFF
Disables low-voltage programming.
Normally, to program the device, a high voltage (around 12 V) must be applied to the VPP pin.
Low-voltage programming mode avoids the need for this high voltage, but we don’t need it
because the PICkit 3 can operate in the traditional high-voltage programming mode.
The comments in the generated configuration code aren’t very useful; unless you remember what all of
these symbols mean, it’s hard to know how the device is being configured.
So we’ll add comments and rearrange the configuration code to make it more readable:
;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code protect
; no write protection, stack resets on, low brownout voltage,
; no low-power brownout reset, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF
__CONFIG _CONFIG2, _WRT_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_OFF
‘goto’ is an unconditional branch instruction12. It tells the PIC to go to a specified program address.
An interrupt is a means of interrupting the main program flow in response to an event, such as a change
on an input pin, can be responded to immediately.
As we’ll see in lesson 7, when the interrupt is triggered, program execution jumps to an interrupt service
routine (ISR), which, in the enhanced mid-range PIC architecture, is always located at address 0004h.
This means that the ISR, or at least its entry point, must be located at address 0004h. But our main
program code starts at address 0000h. Unless the main code is only four bytes long, it’s going to run into
the address where we have to place the ISR – unless the main program is located somewhere else in
memory, where it won’t conflict with the ISR, in which case, when the PIC starts running, the first thing
to do is to jump to where the main program is. Hence the ‘GOTO START’ instruction.
We won’t be using interrupts in this example, so we can remove this comment about interrupts, and also
remove the ‘GOTO START’, because, with no interrupts, there is no ISR to jump around.
The next piece of the template code represents the start of the main program code:
MAIN_PROG CODE ; let linker place main program
START
Using the CODE directive like this, without specifying a section address (such as 0x0000), allows the linker
to place this section wherever it best fits into program memory.
That’s fine, but since we’re not using interrupts and therefore don’t have an ISR sitting at 0x0004, our
main program code might as well continue on directly from the reset vector at 0x0000 – there is no need to
create a separate code segment to hold the main program.
So we can remove this CODE directive.
It doesn’t hurt to keep the ‘START’ label though – it helps to show where the program starts, and, being no
more than a label, doesn’t use any memory.
END
Programs running on small microcontrollers, such as enhanced mid-range PICs, have nowhere to go if
they “finish” – it’s not like a PC with an operating system that the program can return control to. If a PIC
program is allowed to run past its “end”, it will attempt to execute whichever “instructions” happen to be
12
PIC assembler instructions are not case-sensitive – ‘goto’ and ‘GOTO’ are both acceptable, although lowercase
has traditionally be used for instructions, so we’ll use lowercase in these tutorials. Labels, such as ‘START’ may or
may not be (depending on the assembler configuration) case sensitive, so it’s best be consistent then using them – if
you use the label ‘START’, don’t ever refer to it, or any other label, as ‘start’ or ‘Start’.
in the (uninitialised, non-programmed) remainder of the program memory. Whatever the “program” is
doing at that point, it’s not under your control and not behaviour that you want.
So, programs running on small microcontrollers, where there is no operating system, are almost always
designed to never finish running. Instead, the usual structure is to have some initialisation code which
runs when the program starts, often some interrupt services routines which will be run when interrupts are
triggered, and a main loop, which repeats the same processes “forever” – that is, until the power is cut off
or the device is reset.
This fragment of code isn’t really a “main loop”. Instead we have a one-line “endless” or “infinite” loop,
an instruction that does nothing other than continually looping back onto itself, to stop the program from
running past this point into uninitialised program memory.
Such a loop could be written as:
here goto here
‘END’ is an assembler directive, marking the end of the program source. The assembler will ignore any
text after the ‘END’ directive – so it really should go right at the end of the program!
Of course, we need to replace these example instructions with our own. This is where we place the code
to turn on the LED!
To configure only RA1 as an output, we have to clear bit 1 of the TRISA register, leaving all the other bits
in TRISA set. This is done by:
movlw b'111101' ; configure RA1 (only) as an output
banksel TRISA
movwf TRISA
Note again that to specify a binary number in MPASM, the syntax b‘binary digits’ is used, as shown.
To make RA1 output a ‘high’, we have to set bit 1 of PORTA to ‘1’, which we could do with:
movlw b'000010' ; set RA1 high
banksel PORTA
movwf PORTA
Although there is no risk of running into read-modify-write problems when updating the port register in a
single write operation like this, to avoid potential problems in other situations it is better to get into the
habit of only ever writing to LATA to modify output pins:
movlw b'000010' ; set RA1 high
banksel LATA
movwf LATA
Finally, as explained earlier, if we leave it there, when the program gets to the end of this code, it will
attempt to execute whatever happens to be in the remainder of the program memory. We need to get the
PIC to just sit doing nothing, indefinitely, with the LED still turned on, until it is powered off – which
means finishing with an endless loop, as above:
goto $ ; loop forever
And of course an ‘END’ directive has to go at the end of the source code.
Once again, this little program has a structure common to most PIC programs: an initialisation section,
where the I/O pins and other facilities are configured and initialised, followed by a “main loop”, which
repeats forever. Although we’ll add to it in future lessons, we’ll always keep this basic structure of
initialisation code followed by a main loop.
Complete program
Putting together all the above, and adding a few more comments, here’s the complete assembler source for
turning on an LED, for the PIC12F1501:
;************************************************************************
; Description: Lesson 1, example 1 *
; *
; Turns on LED. LED remains on until power is removed. *
; *
;************************************************************************
; *
; Pin assignments: *
; RA1 = indicator LED *
; *
;************************************************************************
#include "p12f1501.inc"
;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code protect
; no write protection, stack resets on, low brownout voltage,
; no low-power brownout reset, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF
__CONFIG _CONFIG2, _WRT_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_OFF
;***** Initialisation
start
; configure port
movlw b'111101' ; configure RA1 (only) as an output
banksel TRISA
movwf TRISA
; loop forever
goto $
END
13
Although there is only one source file in this simple example, larger projects often consist of multiple files, as
mentioned earlier; we’ll see an example in lesson 3.
To set the project you want to work on (and build) as the main project, you should right-click it and select
“Set as Main Project”. If you happen to have more than one project in your project window, you can by
removing any project you are not actively working on (to reduce the chance of confusion) from the
Projects window, by right-clicking it and selecting “Close”.
To build the project, right-click it in the Projects window and select “Build”, or select the “Run → Build
Main Project” menu item, or simply click on the “Build Main Project” button (looks like a hammer) in the
toolbar:
This will assemble any source files which have changed since the project was last built, and link them.
An alternative is “Clean and Build”, which removes any assembled (object) files and then re-assembles all
files, regardless of whether they have been changed. This action is available by right-clicking the project
in the Projects window, or under the “Run” menu, or by clicking on the “Clean and Build Main Project”
button (looks like a hammer with a brush) in the toolbar.
When you build the project, you’ll see messages in the Output window, showing your source files being
assembled and linked. Toward the end, you should see:
BUILD SUCCESSFUL (total time: 2s)
(of course, your total time will probably be different…)
If, instead, you see an error message, you’ll need to check your code and your project configuration.
You are, however, likely to see some warning messages, similar to:
Message[302] C:\...\EA_L1-TURN_ON_LED.ASM 55 : Register in operand not in bank
0. Ensure that bank bits are correct.
Message[303] C:\...\EA_L1-TURN_ON_LED.ASM 67 : Program word too large.
Truncated to core size. (FFE4)
The [302] messages are generated whenever your code references a register which is not in bank 0, to
remind you that you should be taking care to set the bank selection bits correctly. Since we have been
using the banksel directive to ensure that the correct bank is selected, it can be annoying to see these
messages – particularly in a larger program, where there will be many more of them. And worse, having a
large number of unnecessary messages can make it easy to miss more important messages and warnings.
Luckily, messages and warnings can be disabled, using the ‘errorlevel’ directive:
errorlevel -302 ; no warnings about registers not in bank 0
The [303] messages are generated because the processor configuration constants are defined in the
‘p12f1501.inc’ include file as 16-bit values, while the 12F1501’s configuration words are only 14 bits
wide. 16 bit into 14 don’t go, so the assembler has to truncate the config values to 14 bits by dropping the
top two bits.
We don’t need to see these warnings either, so it’s ok to disable them also:
errorlevel -303 ; no warnings about program word too large
These directives should be placed toward the beginning of your program, before the __CONFIG directives,
as follows:
#include "p12f1501.inc"
;***** CONFIGURATION
; ext reset, internal oscillator (no clock out), no watchdog,
; brownout resets on, no power-up timer, no code protect
; no write protection, stack resets on, low brownout voltage,
; no low-power brownout reset, high-voltage programming
__CONFIG _CONFIG1, _MCLRE_ON & _FOSC_INTOSC & _CLKOUTEN_OFF & _WDTE_OFF &
_BOREN_ON & _PWRTE_OFF & _CP_OFF
__CONFIG _CONFIG2, _WRT_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_OFF
If you now build the project again, you should no longer see any [302] or [303] messages.
14
Or, in general, that the PIC you wish to program is connected to whichever programmer or debugger you are using,
whether it’s in a demo/development/training board, a production board, or a standalone programmer.
If you have been following this lesson, you will have specified the programmer when you created your
project (in step 4 of the wizard).
The project dashboard, in the panel in the bottom right of the workspace, shows the currently-selected
programmer under “Debug Tool”.
If you want to change this tool selection, you can right-click your project in the Projects window and
select “Properties”, or simply click on the “Project Properties” button on the left side of the project
dashboard, as shown:
This will open the project properties window, where you can verify or change your hardware tool
(programmer) selection:
After closing the project properties window, you can now program the PIC.
You can do this by right-clicking your project in the Projects window, and select “Make and Program
Device”. This will repeat the project build, which we did earlier, but because nothing has changed (we
have not edited the code), the “make” command will decide that there is nothing to do, and the assembler
will not run.
Instead, in the “Build, Load” tab in the Output pane you should see output like:
BUILD SUCCESSFUL (total time: 1s)
Loading code from C:/Work/Gooligum/Tutorials/Series 2/Web/Enhanced/1 - Light an LED/EA_L1-
Turn_on_LED.X/dist/default/production/EA_L1-Turn_on_LED.X.production.hex...
Loading symbols from C:/Work/Gooligum/Tutorials/Series 2/Web/Enhanced/1 - Light an LED/EA_L1-
Turn_on_LED.X/dist/default/production/EA_L1-Turn_on_LED.X.production.cof...
Loading completed
Connecting to programmer...
Programming target...
(the total time is smaller than before, because no assembly had to be done).
A “PICkit 3” tab will also appear in the Output pane, where you can see what the PICkit 3 is doing.
Your PICkit 3 may need to have new firmware downloaded into it, to allow it to program enhanced mid-
range devices, in which case you will see messages like:
Downloading Firmware...
Downloading RS…
Downloading AP...
AP download complete
Programming download...
Firmware Suite Version.....01.29.33
Firmware type..............Enhanced Midrange
Since we are using a 5 V device, you can click ‘OK’. And feel free to click “Do not show this message
again”, to avoid seeing this caution every time you program your PIC.
You may now see an error message in the PICkit 3 output tab, stating:
Target device was not found. You must connect to a target device to use PICkit 3.
This happens if the PIC is unpowered, so we need to tell the PICkit 3 to supply power.
Open the project properties window (as on the previous page), select ‘PICkit 3’ in the categories tree, and
choose ‘Power’ option in the drop-down option categories list:
Select “Power target circuit from PICkit3”, as shown. You can leave the voltage set to 5.0 V, and then
click ‘OK’.
If you now perform “Make and Program Device” again, the programming should be successful and you
should see, in the build output tab, messages ending in:
Programming completed
Note that this action combines making (or building) the project, with programming the PIC.
In fact, there is no straightforward way with MPLAB X to simply program the PIC without building your
project as well.
This makes sense, because you will almost always want to program your PIC with the latest code. If you
make a change in the editor, you want to program that change into the PIC. With MPLAB X, you can be
sure that whatever code you see in your editor window is what will be programmed into the PIC.
But most times, you’ll want to go a step further, and run your program, after uploading it into the PIC, to
see if it works. For that reason, MPLAB X makes it very easy to build your code, program it into your
PIC, and then run it, all in a single operation.
There are a few ways to do this:
Right-click your project in the Projects window, and select “Run”, or
Select the “Run → Run Main Project” menu item, or
Press ‘F6’, or
Click on the “Make and Program Device” button in the toolbar:
Whichever of these you choose, you should see output messages ending in:
Running target...
The LED on RA1 should now light.
Being able to build, program and run in a single step, by simply pressing ‘F6’ or clicking on the “Make
and Program Device” button is very useful, but what if you don’t want to automatically run your code,
immediately after programming?
If you want to avoid running your code, click on the “Hold in Reset” toolbar button ( ) before
programming. You can now program your PIC as above.
Your code won’t run until you click the reset toolbar button again, which now looks like and is now
tagged as “Release from Reset”.
Summary
The sections above, on building your project and programming the PIC, have made using MPLAB X seem
much more complicated than it really is.
Certainly, there are a lot of options and ways of doing things, but in practice it’s very simple.
Most of the time, you will be working with a single project, and only one hardware tool, such as a
programmer or debugger, which you will have selected when you first ran the New Project wizard.
In that case (and most times, it will be), just press ‘F6’ or click on to build, program and run your
code – all in a single, easy step.
That’s all there is to it. Use the New Project wizard to create your project, add a template file to base your
code on, use the editor to edit your code, and then press ‘F6’.
Conclusion
For such a simple task as lighting an LED, this has been a very long lesson!
In summary, we:
Provided an overview of the enhanced mid-range PIC architecture
Introduced the PIC12F1501
Showed how to configure and use the PIC’s output pins
Implemented an example circuit using two development boards:
o Gooligum training and development board
o Microchip Low Pin Count Demo Board
Looked at Microchip’s assembly template code and saw:
o some PIC assembler directives
o some PIC configuration options
o our first couple of PIC instructions
Modified it to create our (very simple!) PIC program
Introduced the MPLAB X integrated development environment
Showed how to use MPLAB X to:
o Create a new project, based on a template
o Modify that template code
o Build the program
o Program the PIC, using a PICkit 3
o Run the program