What does it do?
The Loops program demonstrates typical microcontroller loop structures using a decrement (count down) instruction. Most microcontrollers have the ability to sense a zero result, so counting down from a starting number toward zero is a common operation.
During this activity, you will learn about this microcontroller instruction:
|decfsz||'decrement file register and skip if zero' - subtracts one from the contents of a file register and skips the next instruction if the result is zero. (incfsz is the complementary increment instruction, and also skips when the result is zero.)|
Loops programming activity
The Loops program demonstrates the most common finite loop structure used in PIC assembly code, and also demonstrates a method of waiting for and acting on user action.
What you should know before starting
Finite loops are used to repeat sections of program code a specific number of times. Whereas we humans would likely count events by counting up (except for rocket launches, of course), microcontroller loops typically count down to zero.
In the decision activity, you learned that the STATUS register can be used to test for a zero result. The decfsz (decrement file register and skip if zero) instruction is unique in that this one instruction combines the functions of a math instruction (by decrementing, or subtracting one) and a bit test instruction (by testing the STATUS register Zero flag, and skipping the next instruction on a zero result).
Sensing individual user actions
When you consider most of the previous programs, you will notice that they are primarily structured as infinite loops. For example, the input program repeatedly senses and updates the state of the pushbuttons. If we wanted to use a program similar to input to count button presses, the microcontroller would quickly accummulate thousands (or millions) of counts because it can execute the entire code loop in microseconds.
The loops program is structured as a finite loop as well, but the loop execution is broken up by decisions based on the state of the input pushbutton, limiting the number of times the code is allowed to loop. Each path through the loop is counted, and the count is repeated and displayed on the LEDs.
As you use this program, you may notice that occasionally a switch press triggers more than one count on the LEDs. The most common cause of multiple counts is switch bounce — the mechanical connection, disconnection, and re-connection of the switch contacts. You can't sense switch bounce because it happens so fast, but the microcontroller is fast enough to register each switch closure as a separate event.
It's not used in the loops program, but one method of eliminating the effect of switch bounce utilizes a software time delay to ignore any inputs for a small amount of time after the switch changes state. A delay of 20-50ms is usually sufficient for most switch contacts to settle.
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 LOOPS.ASM program is shown below. Create a Loops project in MPLAB, copy this code into it, and build the program.
;Loops v3.1 January 18, 2013 ;=============================================================================== ;Description: This program demonstrates a finite loop. It lights an LED each ; time a switch is pressed. After 6 presses all LEDs turn off. ;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 ;Set hardware equates. S2 equ 0 ;PORTB position of pushbutton S2 ;Set RAM register equates. counter equ 20h ;RAM storage register for the first number ;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 movlw 00000001b ;Set S2 as an input and all other movwf TRISB ;PORTB pins as outputs banksel PORTA ;Return to register bank 0 resetCount movlw 6 ;Preload Counter with number of movwf counter ;loop cycles clrf PORTB ;Turn off all LEDs waitForKey btfsc PORTB,S2 ;Check pushbutton S2 for press goto waitForKey ;Keep waiting until S2 is pressed bsf STATUS,C ;Set carry and turn on an LED for rrf PORTB,F ;each key press waitForRel btfss PORTB,S2 ;Check pushbutton S2 for release goto waitForRel ;Keep waiting until button released decfsz Counter,F ;Subtract 1 from Counter register goto waitForKey ;Repeat until counter becomes zero goto resetCount ;Reset the count after 6 presses end
Download this program into your CHRP board and verify its operation. Pressing S2 should increment the count 6 times, then repeat.
How the program works
The initPorts section of the program is similar to that in the input program and sets up S2 as an input on PORTB while the other PORTB pins are set up as outputs.
resetCount movlw 6 ;Preload Counter with number of movwf counter ;loop cycles clrf PORTB ;Turn off all LEDs
The resetCount subroutine sets the counter variable to 6 and turns off all the PORTB LEDs.
waitForKey btfsc PORTB,S2 ;Check pushbutton S2 for press goto waitForKey ;Keep waiting until S2 is pressed bsf STATUS,C ;Set carry and turn on an LED for rrf PORTB,F ;each key press
Next, waitForKey waits until pushbutton S2 is pressed using a bit test instruction that will skip the goto when the pushbutton input goes low. Once this happens, the Carry bit is set before being rotated into the PORTB register, similar to the way in which the Chaser program worked.
waitForRel btfss PORTB,S2 ;Check pushbutton S2 for release goto waitForRel ;Keep waiting until button released decfsz Counter,F ;Subtract 1 from Counter register goto waitForKey ;Repeat until counter becomes zero goto resetCount ;Reset the count after 6 presses
Finally, the waitForRel subroutine waits until S2 is released and goes high before continuing.
Once S2 is released, the Counter register is decremented. If the result is not zero, the microprocessor re-enters the waitForKey subroutine and gets ready for the next key press.
If all of the counts have been registered and the result of the decfsz Counter,F instruction is zero, the code continues by skipping the first goto, and returning to the resetCount subroutine, so that PORTB can be cleared and the entire process can repeat.
Test your knowledge
- How many counts occur as the Counter register decrements down from 6?
- If the waitForKey subroutine used an rlf PORTB,F instruction instead of rrf, would all counts appear on the LEDs? Why or why not?
Apply your skills
- Create a program that counts S2 button presses and lights the LED on RB5 after 10 presses. Use button S3 to reset the count and turn off the RB5 LED. All other LEDs (except RB0 and RB1, since they are monitoring the switch inputs) should remain off.
- All other code execution stops while this loops program alternates between waiting for button presses and releases. While it serves as an example to demonstrate user input and loop counting, this program would obviously not be able to accomplish any processing independent of button activity. Think about how to structure code to accomplish a task while waiting for button input, and design a flow-chart of your code idea.