Program description

What does it do?

The count program counts in binary, displaying the count on the LEDs.

New instructions

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

btfss 'bit test file register, skip if set' - checks if the specified bit of a file register is set (1). If the bit is clear (0), the line immediately following btfss is executed. If set (1), the line immediately following btfss is skipped. (btfsc is the complement to btfss, and is used to check if a bit is clear.)
movf 'move file register' - move a number from a file register (RAM) to itself, or to W, the Working register
incf 'increment file register' - increment (add one) to the contents of a file register (RAM location). (decf is the complement to incf, and subtracts one from the contents of a file register.)

Jumper positions

The CHRP jumpers need to be set to the following positions in order for this program to function.

J7 - serial receive - Ser. position
J8 - serial transmit - Ser. position
J9 - analogue input - n/a
J11 - power select - Cont. position
J13 - voltage divider - n/a

Count programming activity

Did you try to make a flashing light pattern in the Output activity? The pattern you made would have changed so fast that you would not have been able to see it. This program uses a time delay to display a binary count—one that you can see—on the LEDs. It also introduces you to a common decision-making structure and one of the PIC's hardware timers.

What you should know before starting

Microcontroller related information

PIC microcontrollers contain one or more hardware timers. Timers are hardware circuits inside the microcontroller that count or time events so that your program software can do other things.

This program uses TMR0 (timer 0) to make its time delay. TMR0 is an 8-bit timer (256 states) with an optional 8-bit prescaler, meaning that it can actually count up to an equivalent of 65,536 (256 * 256) states. The prescaler divides the input by a binary factor before generating a TMR0 count. With a maximum prescaler of 256, for example, TMR0 will increment by one after the first 256 events have gone through the prescaler. TMR0 will remain at that count for the next 255 events—and increment again on the 256th event.

TMR0 trades the number of counts for resolution. With a prescaler of 1 (no prescaler), each event gets counted, but only up to a maximum of 256 events. Using a prescaler of two allows a count of 512 by counting only every other event. A prescaler of four counts to 1024 by counting every fourth event, and so on, up to the maximum count of 65,536.

Program requirements

To use this program you will need:

An assembled CHRP board, microcontroller, and power supply, a programming cable, and a Windows PC with the MPLAB IDE software and downloader software as described in the Output activity.

Create the program

The entire COUNT.ASM program is shown below. Create a new project in MPLAB, copy this code into it, and assemble the program.


;COUNT.ASM 	v1.2	Last modified on July 28, 2007
;===============================================================================
;Description:	Count-up program. Display binary count on LEDs.

;Start of MPLAB and processor configuration.

	list	 p=16F876A		;Define processor type
	include	"p16f876a.inc"		;Include processor definitions

	__CONFIG  _CP_OFF & _DEBUG_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _LP_OSC

;End of MPLAB and processor configuration.

	org	00h			;Start of program memory

		clrf	PORTA		;Turn off all port outputs
		clrf	PORTB
		clrf	PORTC
		goto	Init_Ports	;Jump to initialize subroutine

	org	05h

Init_Ports	;Set Ports B and C to support CHRP digital circuitry.

		banksel	TRISB		;Switch to TRISB register bank
		movlw	01010111b	;Enable Port B pull-ups, internal TMR0
		movwf	OPTION_REG	;clock with prescaler of 256
		clrf	TRISB		;Set all LED (Port B) pins as outputs
		movlw	10110000b	;Set up serial input and output pins,
		movwf	TRISC		;and set motor pins as outputs
		banksel	PORTB		;Return to PORTB register bank

Set_Timer	movlw	61		;Preload TMR0 for 50ms time period
		movwf	TMR0

Check_Timer	movf	TMR0,W		;Check TMR0 value
		btfss	STATUS,Z
		goto	Check_Timer	;Repeat until TMR0 = 0
		incf	PORTB,F		;Increase the count on the LEDs
		goto	Set_Timer	;Reset TMR0 for the next count

	org	1F00h			;Start of bootloader code area
	res	256			;Reserve memory for bootloader

	end
		

Download the program into the CHRP and verify its operation.

How the program works

Like Output, this program starts at program memory location 0, the Reset Vector. Unlike Output what follows is not a goto, but instructions to clear the Port A, B and C registers. Clearing the registers now actually has no effect (because the I/O pins default to input on power-up), but it does ensure that when the output pins become active, they will be at a known (zero) state.

		
	org	00h			;Start of program memory

		clrf	PORTA		;Turn off all port outputs
		clrf	PORTB
		clrf	PORTC
		goto	Init_Ports	;Jump to initialize subroutine
		

The goto Init_Ports instruction follows clearing the ports. It causes the microcontroller to jump over the Interrupt Vector (memory location 4) exactly as in the Output program. Also, just as in Output, Init_Ports is located at program memory address 5, one location past the Interrupt vector. If you could look into the program memory, this is where you would find the instructions:

Address Contents
00 clrf PORTA
01 clrf PORTB
02 clrf PORTC
03 goto 05
04 (empty - Reset Vector)
05 Init_Ports
. .

Initialization and TMR0 setup

The second and third lines of the Init_Ports subroutine will probably be a bit more clear to you now (you did read the 'What you should know before starting', above, didn't you?). One of the functions of OPTION_REG is controlling TMR0, the Timer 0 module. Some of the OPTION_REG bits enable TMR0 and select its input, and other bits set TMR0's prescaler. Refer to Microchip's PIC16F87xA data sheet for detailed information about the function of each OPTION_REG bit.


Init_Ports	;Set Ports B and C to support CHRP digital circuitry.

		banksel	TRISB		;Switch to TRISB register bank
		movlw	01010111b	;Enable Port B pull-ups, internal TMR0
		movwf	OPTION_REG	;clock with prescaler of 256
		clrf	TRISB		;Set all LED (Port B) pins as outputs
		movlw	10110000b	;Set up serial input and output pins,
		movwf	TRISC		;and set motor pins as outputs
		banksel	PORTB		;Return to PORTB register bank
		

The remainder of the Init_Ports subroutine is the same as in the Output program. It prepares PORTB for output to the LEDs, and sets up PORTC for motor output and serial I/O.

		
Set_Timer	movlw	61		;Preload TMR0 for 50ms time period
		movwf	TMR0
		

The Set_Timer subroutine starts the program by pre-loading the TMR0 register with the count of 61. TMR0 will count from 61 up to 256, incrementing by one for every 256 microcontroller clock cycles, before automatically wrapping the count in the TMR0 register back around to zero.

Deciding how long to wait


Check_Timer	movf	TMR0,W		;Check TMR0 value
		btfss	STATUS,Z
		goto	Check_Timer	;Repeat until TMR0 = 0
		incf	PORTB,F		;Increase the count on the LEDs
		goto	Set_Timer	;Reset TMR0 for the next count
		

The Check_Timer subroutine waits for the TMR0 count to overflow to zero. movf TMR0,W copies the current TMR0 value into W, and btfss STATUS,Z checks if its value is zero.

How does the microcontroller know if something is zero? The STATUS register contains three arithmetic flag bits that indicate mathetatic results: the Z (zero) bit in the STATUS register becomes a one whenever a number is zero, and the C (carry) and DC (digit-carry) bits indicate mathematical carry or borrow operations (these will be described in more detail in the Math programming activity). So,

when a number or answer is 0, Z=1 (set), and

when a number or answer is not 0, Z=0 (clear).

Moving the TMR0 value into W activates a check for zero. If the micrcontroller detects that the value in TMR0 is zero, it sets the Z bit in the STATUS register. The btfss STATUS,Z (bit test file register, skip if set) instruction reads the Z bit and decides what to do next:

If Z=0 (clear), goto Check_Timer executes next, repeating the loop. Or,

If Z=1 (set), goto Check_Timer is skipped. incf PORTB,F executes next.

Play the computer

Imagine the program running. We know that TMR0 contains 61 on startup. We also know that it will take 256 microcontroller clock cycles (the prescaler value) before TMR0 goes up by one count to 62. Assuming that PIC microcontroller instructions take one or two clock cycles to execute, we can infer it will be a while until TMR0 reaches zero—roughly 50,000 clock cycles in this case, or about 50ms in a 4MHz PIC.

When TMR0 eventually does reach zero, the Z bit in the Status register will be set, goto Check_Timer is skipped, and incf PORTB,F adds one to the value in the PORTB register (the ',F' at the end of the instruction makes the result stay in PORTB—the other option, ',W' would move the result into W). Since the PORTB RAM register is also connected to the eight CHRP LEDs, we see the register's value as it counts. Every 50ms (20 times per second) the count changes, which is slow enough for us to see it.

After the increment instruction, the program starts again from Set_Timer by reloading TMR0 with 61, checking for zero over and over again for another 50ms, and finally incrementing PORTB to the next count.

Test your knowledge

  1. Use the simulator to verify the time delay between counts. Set a breakpoint at the incf PORTB,F line of the program and run the program up to there. Then clear the stopwatch and re-run the program. How many clock cycles does it take between successive PORTB increments?
  2. Will this program ever stop? Why or why not.
  3. Set the timer starting value in the Set_Timer subroutine to 1 instead of 61. Re-build the program and determine how much time it takes to increment PORTB now. Compare this result to the first question. Why are they different?
  4. What will happen if the Set_Timer routine sets TMR0 to 0 instead of 61?

Apply your skills

  1. Modify the Count program to display a flashing pattern. The easiest way to do this is to move a pattern into PORTB before the loop started by the Set_Timer subroutine. Then, replace the incf PORTB,F with comf PORTB,F, which performs a logical NOT operation and flips the pattern.
  2. Modify the Count program to display a pattern made up of three or more distinct LED values. (This is quite a bit trickier than 1, above. Hint: you'll need to duplicate something.)