PIC MCU's - making an LED blink

 

> Home
> PIC Microchip Tutorials
> PIC Getting Started Kit
> Robotics
> Contact
> About

 

 

Making an LED blink is kind of the hardware version of "Hello World." It's about as simple as you can get - it requires no more than 2 components besides the MCU - an LED, and if you want your LED to last very long, a resistor. And of course, batteries... and some wire... and a little breadboard to connect it all up.

This is a fairly long and involved tutorial - not for the faint of heart! SO, turn on some appropriate music, grab a can of Pepsi, and 'ere we go!

1. I'm using the 16F630 for this tutorial. If you're using a different PIC, and you're completely new to PICs, you might have trouble! Conceptually, this will still be good information for you, and most of the actual assembly won't change for different PIC's, but certain configuration values may be different, and you may have to use a different port (i.e. many PIC's don't have a PORTC).

Instead of building this program up chunk by chunk, I'm just going to throw the whole thing at you, and explain it line by line. Here it is:

;Anthony Rogers
;Oct 5th, 05
;BlnkDem - flash an LED

	list      p=16F630           ; list directive to define processor
	#include <p16F630.inc>        ; processor specific variable definitions

	__CONFIG   _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OF
F & _PWRTE_ON & _INTRC_OSC_NOCLKOUT 

VARIABLES	 UDATA_SHR
delayA	RES	1
delayB	RES	1	


;**********************************************************************
RESET_VECTOR	CODE	0x000		; processor reset vector
		goto    main              ; go to beginning of program

PROGRAM	CODE
main

	call    0x3FF             ; retrieve factory calibration value
	bsf     STATUS,RP0        ; set file register bank to 1 
	movwf   OSCCAL            ; update register with factory cal value 
	movlw	0x00
	movwf	TRISC		
	bcf     STATUS,RP0        ; set file register bank to 0
		
loop

	clrf	PORTC	
	call	delay
	movlw	0xFF
	movwf	PORTC
	call	delay
	
	goto	loop
	
;-------------------------------------------------------------------
;Delay Routine
	
delay
	movlw	0xFF
	movwf	delayA	
loopA
	movlw	0xFF
	movwf	delayB	
loopB
	decfsz	delayB, f
	goto	loopB
	decfsz	delayA, f
	goto	loopA
	
	return
;-------------------------------------------------------------------
		END                       ; directive 'end of program'
			  

 

;Anthony Rogers
;Oct 5th, 05
;BlnkDem - flash an LED

Semicolons (obviously) denote comments....

list p=16F630

This line tells the assembler what PIC you are compiling for. Honestly, I don't know why this is necessary, as you ALSO have to tell which PIC you're using under menu item Configure | Select Device... . But, it's in the Microchip provided templates, so I guess I'd better use it. : )

#include <p16F630.inc>

This file essentially contains a bunch of EQU directives, associating special register locations with names, as they appear in the PIC data sheets. For example, without this file, if you wanted to access PORTC on this PIC, you'd have to use it's actual address: 0x0007. There are at least 3 reasons you don't want to have to do this. 1) There are MANY special registers, and
obviously no one is going to want to be memorizing or constantly checking register location charts! 2) Using the names, your code is more portable. A different PIC might have PORTC mapped to a different address. If you ever use your code on a different processor, you wouldn't have to go through and update all the lines that use the PORTC address if you utilized the include file - it would automatically update the address for you. 3) It makes your code more readable.

It's very beneficial to actually open the include file and take a look. It will be under C:\Program Files\Microchip\MPASM Suite\P16F630.INC (or similiar, per your installation particulars). There are a few other goodies in there besides EQU's (i.e. Configuration Bits... read on!)

__CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT

This scary looking line sets certain "fuses" in the PIC pertaining to configurations that do not change during program execution. These "fuses" are just bits in a special register - CONFIG - that cannot be accessed accept during programming. In other words, normal program execution cannot access this register. See the data sheet for what each bit means.

VARIABLES UDATA_SHR

This defines the start of a section in the program used soley for defining variables. There is nothing particular about the "VARIABLES" term - it could be any valid label. In fact, if don't even need a label, and the assembler will automatically label the section ".UDATA_SHR". But, a good label improves program readability. UDATA_SHR stands for "Shared Uninitialized Data." You can specifiy a memory start location for the section, but might as well let the assembler decide where to put it! One less detail to worry about.

delayA RES 1
delayB RES 1

Here I've defined the only two variables we'll need, to be used in the delay loop. "delayA" is the variable name, RES stands for "reserve", and "1" is how many bytes to reserve.

RESET_VECTOR CODE 0x000

This, like the UDATA_SHR section, defines the start of a section in the program. The CODE directive tells the assembler that this section will contain actual assembly, as opposed to say - variable definitions. Also different from the UDATA section, for this particular code section we must define the start address. The reason is - the start of this section must be at the memory location the processor defaults to on startup.

goto main

Ahhh! Finally, some actual assembly! This line simply causes program execution to jump straight into the main program routine. Because the previous line (RESET_VECTOR CODE 0x00) forces this section of code to be placed at memory location 0x00, and because the program counter starts at 0x00, this will be the first line executed when you power up your PIC.

PROGRAM CODE

Yet another section of code - this time specifying the main body of the program. You'll notice there's no address specified for this section. Not explicity giving it an address is basically giving the assembler permission to put it wherever it fits.

main

This is simply a label. It's purpose is soley to provide a location to jump to. During the assembly process, it will be converted to a physical address, and any instructions that "goto" or "call" it will simply set the program counter to that physical address. When you power up the PIC, the program counter will jump from "goto main" to this location.

call 0x3FF ; retrieve factory calibration value
bsf STATUS,RP0 ; set file register bank to 1
movwf OSCCAL ; update register with factory cal value

These lines are particular to this PIC, and any others that have a factory oscillator calibration value. If you look back at our configuration bits, you'll see that the _INTRC_OSC_NOCLKOUT is set. This means we're going to be utilizing the built-in 4MHz clock - thus we need to use the calibration, as instructed in the original 16F630 template. Register Banks are covered in another tutorial.

movlw 0x00
movwf TRISC

The first line here moves the literal value 0x00 ("0xXX" specifies a hex number) into the working register, and the second line moves that value into the speical register TRISC. The TRIS registers control the input/output status of the corresponding ports

(i.e. TRISA controls PORTA). We'll be using PORTC, thus "TRISC." Putting "0" into the TRIS registers specifies that bit will be used for output. "1" specifies input. An easy way to remember which specifies which? 1nput and 0utput. : )

Why does it take two commands to simply put a literal value into a register? There is no operation in the PIC instruction set to put a literal directly into an arbitrary register. You must use the working register as the "middle-man."

bcf STATUS,RP0

Another line for the Banking Tutorial.

loop

Another label, like main. These labels, by the way, can be named just about anything. You could use "asdf" if you wanted. Of course, if you want to be able to read your code after you haven't looked at it for a week, I'd recommend using well thought out labels. : ) We will use THIS label to continuously loop through our program - to turn the LED on and off.

clrf PORTC

Now for some action! clrf is the mnemonic (instruction) for "clear file" - a "file" being a program accessable "register." We're simply turning all the bits of PORTC to 0. This forces the PORTC pins, which we've configured as outputs, to go low - turning the LED's off.

call delay

This will cause the program counter to jump down to a delay routine. At 4MHz clock speed, if we didn't have the delay, the LED would be blinking pretty darn fast! Far too fast to see even a flicker, of course. For now, think of the delay routine as a black-box that simply pauses the execution for half a second or so. I'll cover the actual routine in another tutorial.

movlw 0xFF
movwf PORTC

Here we are again moving a literal into a file register, using the working register as the middle man. Again - we set all the bits of PORTC to output earlier in the program, so when we put binary 11111111 (hex FF) into it, every output goes high (there are actually only 6 I/O's on PORTC on this PIC - the 2 most significant (left most) bits are ignored). At this point, our LED will light up.

call delay

We want our LED to stay on long enough to see! Thus another pause.

goto loop

Jump back up to label "loop"... and clear PORTC again, turning the output's off.

That's it for the software! The delay routine is another substantial chunck to go over, and I'm sure you need a break at this point (I sure do!), so I'll cover in the next tutorial.

For now, just copy it, assume it works, and let's get to the hardware!

How's this for simple? Heck - I won't even post a schematic! After you've uploaded your compiled program to your PIC, simply connect +3 volts to Vdd (pin 1), the LED (with a current limiting resistor - I'm using 680 ohms) to any of the PORTC pins (I used RC0 - pin 10), and 0 volts to Vss (pin 14) - and enjoy your blinker!

 


Note: I didn't have a dual AA battery holder, so I used a quad with a quick tin-foil modification - but just to
clarify, there are actually 2 AA batteries on my circuit (3 volts).

That's it for this one - please email me if anything is not clear, if you find any mistakes, or it simply won't work.

Good luck!

 

top

Copyright © 2005 Anthony Rogers