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 board, microcontroller, and power supply, a programmer and/or 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 v2.0 Last modified on December 1, 2008 ;=============================================================================== ;Description: Counts in binary on the PORTB LEDs. ;Start of MPLAB and processor configuration. 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 org 00h ;Start of program memory clrf PORTA ;Turn off all port outputs clrf PORTB clrf PORTC goto initPorts ;Jump to initialize subroutine org 05h ;End of MPLAB and processor configuration. initPorts ;Set Ports B and C to support CHRP digital circuitry. banksel ANSELH ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSELH ;Set all PORTB pins to digital clrf TRISB ;Set all PORTB pins as outputs for LEDs banksel TRISC ;Switch register banks movlw 10110000b ;Setup serial input and output pins, movwf TRISC ;and set motor outputs banksel PORTB ;Return to PORTB register bank setTimer movlw 61 ;Preload TMR0 for 50ms time period movwf TMR0 checkTimer movf TMR0,W ;Check TMR0 value btfss STATUS,Z goto checkTimer ;Repeat until TMR0 = 0 incf PORTB,F ;Increase the count on the LEDs goto setTimer ;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
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 instructions to clear the Port A, B and C registers. Clearing the registers now actually has no effect on the I/O pins (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.
org 00h ;Start of program memory clrf PORTA ;Turn off all port outputs clrf PORTB clrf PORTC goto initPorts ;Jump to initialize subroutine
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 is a representation of where you would find the instructions:
|00||clrf PORTA (Reset Vector)|
|04||(empty - Interrupt Vector)|
Initialization and TMR0 setup
The comments for the third and fourth 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 ;Set Ports B and C to support CHRP digital circuitry. banksel ANSELH ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSELH ;Set all PORTB pins to digital clrf TRISB ;Set all PORTB pins as outputs for LEDs banksel TRISC ;Switch register banks movlw 10110000b ;Setup serial input and output pins, movwf TRISC ;and set motor outputs banksel PORTB ;Return to PORTB register bank
The remainder of the initPorts subroutine is the same as in the Output program. It prepares PORTB for output to the LEDs, and sets up PORTC for motor output control and serial I/O.
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. All that we need to do to use the timer is to be able to read the time from the timer register.
Deciding how long to wait
checkTimer movf TMR0,W ;Check TMR0 value btfss STATUS,Z goto checkTimer ;Repeat until TMR0 = 0 incf PORTB,F ;Increase the count on the LEDs goto setTimer ;Reset TMR0 for the next count
The easiest value to read in most microcontrollers turns out to be zero. Here, the checkTimer 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 checkTimer executes next, repeating the loop. Or,
If Z=1 (set), goto checkTimer is skipped. incf PORTB,F executes next.
Play the computer
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. After another 256 clock cycles, TRM0 increments again to 63. And so on...
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. 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 to see it.
After the increment instruction, the program starts again 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.
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 a flashing pattern. The easiest way to do this is to move a pattern into PORTB before the loop started by 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 values. (This is quite a bit trickier than 1, above. Hint: you'll need to duplicate some code.)