Blink an LED, or Disco Fever

As you may have guessed, this tutorial will show you how to make your PIC18F452 make an LED blink, programming with assembler.

First off, you should have a kind of standard header put together from the last tutorial. If not, go to the Basic Assembler tutorial.

Wiring

There's a bit more wiring to do, but it's easy. Take a a 220 Ohm resistor and connect one end to a port pin on the pic. I used pin D1 since it's conveniently in the corner. Connect the other end of the resistor to an empty row of the breadboard.

Now take an LED and connect it to the resistor and to ground, with the ground pin (usually the longer one) in ground. That's it.

Programming

So you have the setup from the last tutorial. Modify the descriptive header for this program. Note this setup is for a 4MHz external crystal clock, if yours is different, change the configuration to suit.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ledblink.asm: Blinks an LED on port D1
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-04-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    title "Hello World"
    List p=18f452,f=inhx32
    #include <p18f452.inc>

    __CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
    __CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
    __CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
    __CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
    __CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
    __CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
    __CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
    __CONFIG    _CONFIG7H, _EBTRB_OFF_7H

    org 0x0000

If we just turn the LED on and off as fast as possible we won't see anything, so we're going to need to turn it on, wait, then turn it off, and wait, then repeat. In order to wait we need to have a delay loop. The delay loop will just decrement a number until the number is zero, then move on. The size of the decremented number will determine how long the delay is. Since the processor does 4 million operations per second at 4MHz, we need a big number. The largest number one byte can hold is 255. Our delay loop will be built with labels, goto statements to loop back, and conditionals that will skip the goto when we've done enough looping.

First we need to define the variables that we'll decrement. We'll use two:

COUNT1 equ 0x01
COUNT2 equ 0x02

We'll get to the actual loop in a bit. Next we need to set up our output port. On the PIC there is an 8 bit register for each port. Each bit corresponds to a pin on the port. Setting a pin high sets the port to input, setting a bit low sets the pin to output. These registers are called TRISA, TRISB etc. We're only using one pin, but we might as well set them all to output. There is some discussion about what one should do with unused pins, but I haven't had a problem so far, so we'll do it this way.

clrf TRISD ; set all bits in TRISD to zero.

Most PIC programs have a main loop so that they continue running, instead of running once and finishing. To do this make a label named "Main" and a goto statement to that label, like this:

Main
    goto Main

The main body of your code will go there. It can break out to go into subroutines, but then it returns to this main loop.

To turn on the LED you'll set the pin it's on high, then to turn it off you'll set the pin low. The code to do that should go within the main loop. To set one pin you set a bit using bsf in a register corresponding to the port the pin is part of.

Main
    bsf PORTD, RD1 ; set bit RD1 in the PORTD register
    bcf PORTD, RD1 ; clear bit RD1 in the PORTD register
    goto Main

I hope you guessed that this presents the problem of turning the LED on and off too fast for the human eye to see, so we'll write the delay loop.

    setf COUNT1 ; set all the bits in COUNT1, eg. set it equal to 255
    setf COUNT2
Loop1 ; this is a label since it's in the first column
    decfsz COUNT1, F ; decrement COUNT1, store result in COUNT1, skip next line if COUNT1 is zero
    goto Loop1 ; loop - gets skipped if COUNT1 == 0
    setf COUNT1 ; reinit COUNT1
    decfsz COUNT2, F
    goto Loop1
    return

As you may have noticed, it's a nested loop. Essentially it's looping over COUNT2, each iteration it loops over COUNT1.

We could paste this right after the bsf and the bcf to have the delay, but that would be messy and you need to learn how to do a subroutine. So add put the delay code at the end of the file and put a label on the line before it starts called "Delay". We'll call the subroutine with the 'call' instruction after the bsf and the bcf. End the file with the end instruction, so the whole file looks like this:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ledblink.asm: Blinks an LED on port D1
;; 
;; By Ian Smith-Heisters ian{remove this}\at 0x09{remove this}.com
;;   12-04-2004: Started
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    title "Hello World"
    List p=18f452,f=inhx32
    #include <p18f452.inc>
    
    __CONFIG    _CONFIG1H, _OSCS_ON_1H & _XT_OSC_1H ; External Clock on OSC1 & OSC2
    __CONFIG    _CONFIG2L, _BOR_ON_2L & _BORV_20_2L & _PWRT_OFF_2L ; Brown out reset on at 2.0V, no power-up timer
    __CONFIG    _CONFIG2H, _WDT_OFF_2H & _WDTPS_128_2H ; watchdog off, postscaler count to 128
    __CONFIG    _CONFIG3H, _CCP2MX_ON_3H ; CCP2 pin Mux enabled. What is this?
    __CONFIG    _CONFIG4L, _STVR_ON_4L & _LVP_ON_4L & _DEBUG_OFF_4L ; Stack under/overflow reset on, LVP on, debug off
    __CONFIG    _CONFIG5L, _CP0_OFF_5L & _CP1_OFF_5L & _CP2_OFF_5L & _CP3_OFF_5L ; all protection off
    __CONFIG    _CONFIG5H, _CPB_OFF_5H & _CPD_OFF_5H
    __CONFIG    _CONFIG6L, _WRT0_OFF_6L & _WRT1_OFF_6L & _WRT2_OFF_6L & _WRT3_OFF_6L
    __CONFIG    _CONFIG6H, _WRTC_OFF_6H & _WRTB_OFF_6H & _WRTD_OFF_6H
    __CONFIG    _CONFIG7L, _EBTR0_OFF_7L & _EBTR1_OFF_7L & _EBTR2_OFF_7L & _EBTR3_OFF_7L
    __CONFIG    _CONFIG7H, _EBTRB_OFF_7H

    org 0x0000

COUNT1 equ 0x01
COUNT2 equ 0x02

    clrf TRISD ; set all bits in TRISD to zero.

Main
    bsf PORTD, RD1 ; set bit RD1 in the PORTD register
    call Delay
    bcf PORTD, RD1 ; clear bit RD1 in the PORTD register
    call Delay
    goto Main

Delay
    setf COUNT1 ; set all the bits in COUNT1, eg. set it equal to 255
    setf COUNT2

Loop1 ; this is a label since it's in the first column
    decfsz COUNT1, F ; decrement COUNT1, store result in COUNT1, skip next line if COUNT1 is zero<
    goto Loop1 ; loop - gets skipped if COUNT1 == 0
    setf COUNT1 ; reinit COUNT1
    decfsz COUNT2, F
    goto Loop1
    return

    end

Burn this into your chip and it should blink the LED on and off forever.