Introduction: Blink a LED With Assembly Language & a PIC

About: I'm a gardener, and have been one for the past twenty or so years. I also tend to dabble in a great many other things.
To state the blindingly obvious, there are many flavors of microcontroller in the world.  There are innumerable applications for them too.  This Instructable will cover the steps necessary to blink a LED using a PIC microcontroller and Microchip assembly language, showing you how to access and use some of the device's hardware peripherals

To do this I am going to show you how to blink a LED at approximately 1 Hz with a 50% duty cycle.

Step 1: Obtain Necessary Parts & Tools

What you will need

1. A PIC, preferably a 16F1936--but as long as you know your specific hardware, you could probably implement this on nearly any 8 bit PIC with an on-board 16 bit timer.  There are some slight programming differences between the 1936 and earlier uCs that you might be familiar with.  The 1936 is what I have at the moment, and it's pretty spiffy

2. Some way to program the PIC. I am going to be using a PICkit III to do ICSP (In-circuit serial programming).  Can be gotten from Microchip for a small sum of money.  There are many programming options for PICs.  You can even roll your own programmer.

3.  MPlab.  This is available from Microchip for the low low cost of Free.

4.  Miscellaneous electronic parts/equipment
- A 3-6V power supply
- Breadboard
- Jumper wires
- 1 uF Capacitor
- 10K resistor
- LED of choice (around 20 ma current draw), and appropriately sized resistor.
-A small tactile switch

Step 2: Build the Circuit

A few quick notes on the circuit presented here. 

-The header is meant to connect to the PICKIT III.  Pin 1 on the header corresponds to pin 1 on the PICKIT III. 

-The LED goes on when a logic 0 is presented to RB0.  Usually chips like this can sink more current than they can source.

-Vcc needs to be 3-6V

-SW1 can be a simple tactile push button.  The purpose of the switch is to give you a way to drive MCLR low, and reset your chip. 

-R1 is 10K or similar.  It pulls up the voltage on MCLR when C1 has charged up.

-You will probably need to view the original image to read the pin numbers

Step 3: About PICs

You probably know already that a computer executes code by loading the code from nonvolatile memory--such as a hard disk, and executing it in volatile memory which we call RAM.  Instructions + Data = Results

With PIC micro-controllers it is the same.  When a PIC starts up, the Arithmetic Logic Unit starts a special counter called a Program Counter at 0, and sequentially executes one instruction after the other, incrementing the Program Counter each time an instruction is executed.

The 8 bit mid-range pics have approximately 49 different instructions.  Depending on which PIC you use, these instructions may be of different width.  For the purposes of this tutorial I will be using a PIC 16F1936 which has instructions 14 bits wide.


Step 4: Understanding Data Memory and Paging

In the PIC 16F193x devices, there are three kinds of memory: data memory, program memory, and EEPROM memory.  This step will show you how to access  and use data memory.

To use program memory you must only know the hexadecimal location of that memory.  Being an 8-bit micro-controller, the PIC has to use a special trick called paging to expand it's addressable memory range past 256 bytes.

The 16F193x devices have their data memory divided into approximately 32 banks of 128 bytes each.  Each bank is accessed by moving a number to a register called the bank-select register (BSR).  To access the contents of a particular register, except for special un-banked registers which can be accessed from any bank, you've got to be in that register's bank.

Attached to this step you'll find part of the memory map for 16F1936 devices.  The unbanked registers have been highlighted/surrounded with a red rectangle.  A higher resolution image may be found on the data sheet.

Step 5: Creating Variables and Constants

When programming with a higher level language such as C++ or Java, where the operating system takes care of memory allocation, it is unimportant to know much about where in the RAM your program's variables are going to reside.

However in PIC assembly, where there is no operating system, it is essential that the compiler know the exact memory location of your variables.  To the assembler, a variable is a constant name for a specific location.

To create a variable you use the cblock directive.  What this directive does is define a block of memory, the bytes of which you can then assign names to. 

cblock h'20'
               APPLES
               ORANGES
               BANANAS
endc

This code would cause the compiler to interpret the words APPLES, ORANGES, and BANANAS as numbers that would correspond to the memory locations of bytes in bank 0. 
A word of warning however.  If your program asks for APPLES while in the wrong bank, the compiler won't realize that you're in the wrong bank and interesting things will happen.

Constants are slightly different.  To the compiler a constant is simply a name for a number.  In fact the only material difference between a variable and a constant is how they are stored in memory.

If I declare a constant like so...

OUTPUT_PIN equ d'4'

...my compiler will interpret every instance of the string OUTPUT_PIN as corresponding to the decimal number 4 when the instructions are written into the program memory.


Step 6: Instructions Plus Data Equals Results!

C = A + B;

The above statement is an assignment statement that should work in pretty much any C flavored language.  However in assembly, such a statement is slightly different.  In assembly the equivalent statement is

movf A, W     ;    Take the value located at A, put it in the working register
addwf B, W   ;   Add the value in the working register to the value at B, put the result in the
                       ;    working register
movwf C       ;    Copy the contents of the working register to location C.

The PIC performs useful tasks by moving the right values to the right registers in the right order.  The 8 bit PICs use approximately 49 instructions. These instructions and an explanation of each may be found  in the data book

Step 7: Configuring the Internal Oscillator

On page 69 of the PIC16F193x data sheet, you will find that this PIC has an internal oscillator. 

This oscillator has approximately 9 possible speeds ranging from 31 kHz to 16 Mhz.  No external crystal, oscillator, or resonator is necessary--although one can be used if you want to.  The device can operate with an external oscillator at speeds of up to 32 Mhz.

The value I've used for the value I load into the oscillator control register (OSCCON) selects a 4 Mhz clock speed from the internal oscillator.  Since the PIC has a 4 clock instruction cycle that means that all my instructions will take 1 uS.  

For timing purposes, this is quite useful.  I can use it to straightforwardly calculate the time my program will take to execute instead of being stuck with a single operating frequency.  It's even possible to change the oscillator frequency on the fly.

Step 8: Setting Up the Ports

The 28 pin version of the PIC16F193X has four ports, A, B, C, and E.  Port D only occurs on the 40 pin device.

Anyhow, to configure the pins on each port the PIC needs to know two things:

1)  Is this an analog pin?
2)  Is this an input pin?

There are two types of register I need to put values in to properly to answer these questions.  They are the analog select registers, and the tri-state registers.  A tri-state output may be a 1, a 0, or a high impedance.  The high impedance state is used when the pin in question is going to be an input.

Since none of my pins are analog and they're all output pins, I use clrf to zero out ANSELB and TRISB.  I'm going to use pin 0 on port B for my LED

Step 9: Setting Up Timer 1 and Determining Your Delay

The particular device I'm using has a Timer with a 16 bit period, and a pre-scale value that ranges from 1:1 to 1:8.  To make life easier, I have also opted to used the Instruction Clock as the source of the timer

You use Timer 1 by selecting your clock source, pre-scale, and preload value such that the overflow interrupt bit in PIR will trigger a certain amount of time after you have started your timer counting.

The time Timer 1 will take to trigger the Interrupt flag is given by the equation

T = Clock Source /  Prescaler Value * (65536 - Preload value)

In my case I wanted to achieve 0.5 seconds with a 1 Mhz clock.  That means I need to trigger after 500,000 clocks. 

500,000 is 2^5 * 5^6. 

I can't count that high with 16 bits.  So I need to use the pre-scaler set to 8 which is 2^3.
With my period counter now advancing every 8 uS, I only need 62,500 counts to achieve 0.5 seconds. 


Step 10: Using Hardware Interrupts

A hardware interrupt is a way of checking for special conditions that may be of interest to the programmer of the device.  Each condition is given a bit in the interrupt registers (PIRx).  An interrupt can wake our device from sleep, or even tell us when a Timer has reached an overflow state, or tell us when a button has been pressed on a keypad--rather than having us endlessly polling a Port. 

To use a hardware interrupt only two things need to be done

1)  The Interrupt in question must be enabled if it comes from a peripheral.
2)  The Interrupt must be serviced in a timely manner

Peripheral interrupts are enabled in the Peripheral Interrupt Enable registers.  To enable an interrupt, simply set the appropriate bit.  In the case of my program I have set the bit corresponding to a bit that goes high whenever the 16 bit period of Timer 1 overflows.

Polling this bit, will tell me if time is up.

Step 11: Putting It All Together -- With Link to Video!

The overall program is fairly simple.  I call all my initialization routines, oscillator first, and then enter a loop I have labeled Main.  Main will repeat indefinitely until the device wears out, I lose power, or the chip is reset with the button

The neat thing about using interrupts on the PIC is that your PIC can go do something else while Timer1 is running.  It would be ridiculous to waste 500000 instruction cycles on incrementing variables, and looping unless you really had to use Timer 1 for something else.

So now we have used peripherals, and hardware interrupts to blink a lowly LED and life is good.  I've attached the MPlab project files to this Instructable so you can see how it all fits together.  It should cover anything else.

An overly dramatic Youtube video showing the finished circuit blinking can be found here

As this is my first Instructable, please--go wild with the feedback and feel free to ask questions.  I'll do my best to answer them.