What does it do?
The count program counts in binary and displays the count on the LEDs. One of the PICmicro's hardware timers (TMR0) is used to add a delay to make each counter state visible.
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.)|
Count programming activity
Did you try to make a flashing light pattern in the Output activity? If you did, you would have noticed that the patterns made by Output changed so fast that you were not able to see the individual pattern steps. The Count program solves that problem by displaying a binary count using a time delay, so that each count is visible on the LEDs for a longer period of time. The Count program also introduces you to a common decision-making software structure and to one of the PIC's built-in hardware timers.
What you should know before starting
Microcontroller related information
PIC microcontrollers contain one or more hardware timers. Timers are hardware circuits in the microcontroller that count or time events on their own, freeing up the software resources that would have been needed to keep track of time.
This program uses TMR0 (timer 0) for its time delay. TMR0 is an 8-bit timer, so it can have up to 256 states. TMR0 also incorporates a programmable 8-bit prescaler, meaning that it can count up to the equivalent of 65,536 (256 * 256) states.
The prescaler divides the input by a binary factor before generating a TMR0 count. Using the maximum prescaler of 256, for example, TMR0 will increment by one only after the first 256 events have clocked into the prescaler. In other words, TMR0 will remain at its initial count for the first 255 new events, and increment its count on the 256th event.
By having a separate prescaler, TMR0 makes a trade-off between the total number of counts and 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 every second event. A prescaler of four counts to 1024 by counting every fourth event, and so on, up to the maximum count of 65,536, by counting every 256th event.
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 COUNT.ASM program is shown below. Create a new project in MPLAB, copy this code into it, and assemble the program.
;COUNT v3.1 January 14, 2013 ;=============================================================================== ;Description: Count delay program. Continuously counts in binary on the PORTB ; LEDs. Uses a hardware timer-based delay to make each count ; visible for long enough to see. Demonstrates bit testing. ;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 setTimer 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 incf PORTB ;Increase the count on the LEDs by one goto setTimer ;Reset TMR0 for the next delay end
Download the program into the CHRP and verify its operation.
How the program works
Clearing I/O ports
Like Output, this program starts at program memory location 0, the Reset Vector. Unlike Output what follows is not a goto, but three instructions to clear the Port A, B and C registers. Clearing the registers now actually has no effect on the I/O pins now (because the I/O pins default to inputs on power-up), but it ensures that when the output pins become active, they will be at a known (zero) state.
;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.
The goto initPorts instruction follows clearing the ports. It causes the microcontroller to jump over the Interrupt Vector (memory location 4) exactly as it did in the Output program. Also, just as in Output, initPorts is located at program memory address 5, one location past the Interrupt vector. If you could look into the program memory, this representation shows where the instructions would be located:
|00||clrf PORTA (Reset Vector)|
|04||(empty - Interrupt Vector)|
Using the View > Program Memory menu option in MPLAB after assembling the program will show the contents of the microcontroller's memory.
Initialization and TMR0 setup
The comments for the fourth and fifth lines of the initPorts subroutine will probably be a bit more clear to you now (you did read the 'What you should know before starting' section, above, didn't you?). One of the functions of OPTION_REG is controlling TMR0, the Timer 0 module. Some of the OPTION_REG bits being controlled enable timer TMR0, select its input (either internal—from the clock—or external from an I/O pin), and select TMR0's prescaler (1-256). Refer to Microchip's PIC16F886 data sheet for detailed information about the function of each OPTION_REG bit.
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
Otherwise, the initPorts subroutine is the same as in the Output program, except that it doesn't clear PORTA here, since it was already cleared before initPorts ran.
setTimer movlw 61 ;Preload TMR0 for ~50ms time period movwf TMR0
The setTimer subroutine starts the program by pre-loading the TMR0 timer register with the count of 61. During operation, TMR0 will count from 61 up to 256, incrementing by one for every 256 microcontroller clock cycles (as set by OPTION_REG prescaler), before automatically wrapping the count in the TMR0 register back around to zero.
Since TMR0 is a hardware timer, its count changes automatically. Our program sets the starting time, and all that we need to do to use the timer is to read the time from the timer TMR0 register.
Deciding how long to wait
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 incf PORTB ;Increase the count on the LEDs by one goto setTimer ;Reset TMR0 for the next delay
The number zero turns out to be very important in most microcontrollers. For this reason, most microcontrollers have built-in instructions to check for zero. This program will wait until the timer rolls over (from 11111111 to 00000000) by checking for zero.
The checkTimer subroutine is a loop that 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 mathematic 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, the Z bit responds like this:
If a number or answer is 0, Z=1 (set), and
If 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 checkTimer executes next, repeating the loop. Or,
If Z=1 (set), goto checkTimer is skipped. incf PORTB,F executes next.
Let's imagine the Count 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. Executing the movf instruction takes one clock cycle, so TMR0 won't have increased yet. The btfss instruction won't skip, and goto will cause the movf instruction to execute again. At this point, only four clock cycles have passed so this same loop continues. After 256 clock cycles have passed, TRM0 finally increments again to 62. The cycle repeats for another 256 clock cycles, and TRM0 increments to 63. And so on...
Assuming that PIC microcontroller instructions take one or two clock cycles to execute, we can infer it will be quite a while until TMR0 reaches zero. It works out to roughly 50,000 clock cycles, or about 50ms in a 4MHz PIC.
When TMR0 eventually does reach zero, the Z bit in the Status register will be set, causing the goto checkTimer to be skipped. Instead, the incf PORTB,F instruction is executed, adding 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 (or 20 times per second) the count changes, which is slow enough for us humans to see it.
After the increment instruction, the program retarts from setTimer by reloading TMR0 with 61, checking for zero over and over again for another 50ms, and finally, when TMR0 reaches zero, incrementing PORTB to the next count. When you watch it run, you'll see continuous counting from 0 to 11111111 on the LEDs.
The end directive is a necessary part of the program even though the microcontroller won't ever run it.
Test your knowledge
- Use the stopwatch in 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 the breakpoint. Then clear the stopwatch and re-run the program. How many clock cycles does it take between successive PORTB increments?
- Will this program ever stop? Why or why not.
- Set the timer starting value in the setTimer 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?
- What will happen if the setTimer routine sets TMR0 to 0 instead of 61?
Apply your skills
- Modify the Count program to display an alternately flashing pattern. The easiest way to do this is to move a pattern into PORTB before the loop started (before the setTimer subroutine). Then, replace the incf PORTB,F with comf PORTB,F, which performs a logical NOT operation and flips the pattern.
- Modify the Count program to display a pattern made up of three or more distinct LED patterns. (This is quite a bit trickier than 1, above. Hint: you'll need to duplicate some code.)