Program description

What does it do?

The chaser program sweeps a lit LED back and forth using two called subroutines.

New instructions

During this activity, you will learn about these microcontroller instructions:

call 'call a subroutine' - save the next program instruction address on the Stack and continue running the program from a label. Call is ike goto, but call is used with return.
return 'return from subroutine' - returns to the address saved on the Stack, continuing the program from the instruction after a call.
bcf 'bit clear file register' - clears a single bit in a file register (RAM).
bsf 'bit set file register' - sets a single bit in a file register.
rlf 'rotate left file register' - shift every bit in a file register (RAM location) one bit to the left.
rrf 'rotate right file register' - shift every bit in a file register one bit to the right.

Chaser programming activity

Chaser uses decision structures to choose between two subroutines, sweeping an illuminated LED back and forth in the process. It also illustrates subroutine re-use, by using call and return instructions instead of goto.

What you should know before starting

Microcontroller related information

The PIC16F886 microcontroller includes an 8-level hardware Stack which is attached directly to its processing unit (see the simplified PIC16F886 block diagram). This Stack is a LIFO (last-in, first-out) buffer that stores up to eight program addresses.

Certain instructions automatically write or read program addresses to or from the Stack, providing the microcontroller with the ability to store and remember the address of instructions that will be executed following a subroutine or other event (such as an interrupt). Unlike more advanced microprocessors, only the PIC's hardware can control the stack—no program instructions can be used to access the stack.

A call instruction, for example, will store the address of the next physical instruction (ie. the one directly after the call in memory, not the one being called) on to the top of the Stack before jumping to the called subroutine. A return instruction at the end of the called program code will read the top address from the Stack, and reset the program counter with it, effectively allowing the original sequence of instructions to continue from the call.

The advantage of a call instruction over a goto is that call allows a program subroutine to be called from multiple places in the program code, and will always return to the specific subroutine that initiated the call.

Program requirements

To use this program you will need:

An assembled CHRP 3 board, an optional power supply, a programmer and/or programming cable, and a computer with the MPLAB IDE or MPLAB X software as described in the Output activity.

Create the program

The entire CHASER.ASM program is shown below. Create a Chaser project in MPLAB, copy this code into it, and build the program.


;CHASER			v3.1	January 14, 2013
;===============================================================================
;Description:	Back and forth light chaser demonstrating bit manipulation and
;				calls to a common time delay subroutine.

;Configure MPLAB and the microcontroller.

	include	"p16f886.inc"		;Include processor definitions

	__config _CONFIG1, _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_ON & _PWRTE_ON & _WDT_OFF & _INTOSCIO
	__config _CONFIG2, _WRT_OFF & _BOR40V

;Start the program at the reset vector.

				org	00h				;Reset vector - start of program memory

				clrf	PORTA		;Clear all port outputs before configuring
				clrf	PORTB		;port TRIS registers. Clearing RA4 turns on
				clrf	PORTC		;the Run LED when TRISA is initialized.

				goto	initPorts	;Jump to initialize routine

				org	05h				;Continue program after the interrupt vector

initPorts       ;Configures PORTA and PORTB for digital I/O.

				banksel	ANSEL		;Switch register banks
				clrf	ANSEL		;Set all PORTA pins to digital
				clrf	ANSELH		;Set all PORTB pins to digital
				movlw	01010111b	;Enable Port B pull-ups, TMR0 internal
				movwf	OPTION_REG	;clock, and 256 prescaler
				banksel	TRISA		;Switch register banks
				movlw	00101111b	;Set piezo and LED pins as outputs and
				movwf	TRISA		;all other PORTA pins as inputs
				clrf	TRISB		;Set all PORTB pins as outputs for LEDs
				banksel	PORTA		;Return to register bank 0

main			bsf		PORTB,0		;Turn on one LED only

chaseLeft		call	timeDelay	;Delay so humans can see the light
				bcf		STATUS,C	;Clear Carry before shifting PORTB
				rlf		PORTB,F		;Shift PORTB contents one position left
				btfss	PORTB,7		;Has the 1 moved to the left-most position?
				goto	chaseLeft	;If not, keep moving left
									;Otherwise, switch direction

chaseRight		call	timeDelay	;Delay so humans can see the light
				bcf		STATUS,C	;Clear Carry before shifting PORTB
				rrf		PORTB,F		;Shift PORTB contents one position right
				BTFSS	PORTB,0		;Has the 1 moved to the right-most spot?
				goto	chaseRight	;If not, keep moving right
				goto	chaseLeft	;Otherwise, switch direction

timeDelay		movlw	61			;Preload TMR0 for ~50ms time period
				movwf	TMR0

checkTimer		movf	TMR0,W		;Check if the TMR0 value is zero by
				btfss	STATUS,Z	;testing the Z bit
				goto	checkTimer	;Repeat check until TMR0 = 0
				return				;Return to the calling routine when done

				end
		

Download the program into the CHRP and verify its operation.

How the program works

The start of the program and port initialization follow the same pattern as in the Count program.

Following the port initialization, the bsf PORTB,0 instruction beside the main label lights up one PORTB LED by setting the bit in position 0 of the PORTB register. Whereas the combination of movlw and movwf instructions used in Output and the initPorts subroutine let us change eight bits at a time, bsf changes only the one, specified bit.

		
main			bsf		PORTB,0		;Turn on one LED only

chaseLeft		call	timeDelay	;Delay so humans can see the light
				bcf		STATUS,C	;Clear Carry before shifting PORTB
				rlf		PORTB,F		;Shift PORTB contents one position left
				btfss	PORTB,7		;Has the 1 moved to the left-most position?
				goto	chaseLeft	;If not, keep moving left
									;Otherwise, switch direction
		

The first line of chaseLeft calls the timeDelay subroutine. Like goto, a call instruction changes the flow of the program. Unlike goto, call stores its return address on the stack so that a later return instruction (in the called subroutine) can resume program execution from the line directly after the call. This important difference between call and goto allows both the chaseLeft and chaseRight subroutines to call the same timeDelay subroutine and in each case return to the appropriate calling routine.

The timeDelay subroutine is essentially the same as the delay code in the Count program, with the addition of a return instruction at its end. After TMR0 reaches zero, return causes the microcontroller to return to the instruction directly below call timeDelay.

After returning from the time delay, the bcf STATUS,C instruction clears the C (Carry) bit in the Status register. Carry needs to be cleared before the upcoming rotate instruction that will be used to move the LED pattern. During a rotate operation, the contents of a file register are rotated (shifted by one bit) through C and back into the opposite side of the register. Clearing C before the rotate ensures that it won't accidentally introduce a second lit LED into our display pattern.

Next, rlf PORTB,F shifts the contents of Port B (the light pattern in this program) one position to the left. The left-most bit (bit 7) in the register rotates into Carry, and Carry rotates back into bit 0 of the register—which, again, is the reason we had to clear Carry before rotating.

Then the decision-making btfss PORTB,7 instruction checks to see if the one we loaded into the right side of the PORTB register has arrived in the left-most bit position (the bit 7 position) by checking if bit 7 is set. If bit 7 is clear, the program executes the goto chaseLeft instruction and repeats the time delay before rotating Port B again. After seven rotates, when bit 7 is finally set, program execution will skip over the goto chaseLeft instruction and continue into the chaseRight subroutine.

chaseRight performs the exact same function as chaseLeft, albeit in the opposite direction, and both subroutines are able to use the same timeDelay code through their respective call instructions.

Test your knowledge

  1. The Stack is attached to, and controlled by, the microcontroller. Can your program perform nested calls—a call to a subroutine calls another subroutine—and how many levels deep?
  2. What do you think will happen if there are more calls than the stack has places for? (Try this in the MPLAB simulator)
  3. What do you think will happen if a return instruction is encountered before a call? (Try this in the MPLAB simulator)

Apply your skills

  1. Rotate instructions perform the software equivalent of shift registers and state machines. Modify the Chaser program to continuously cycle a pattern through PORTB. How many states does this pattern have? Can you think of a simple way to expand the number of states in the pattern?