What does it do?
The time delay loops program demonstrates accurate software time delay loops.
During this activity, you will learn about this microcontroller instruction:
|xorwf||'exclusive-OR W to file register' - performs an XOR (exclusive-OR) operation between each of the bits in W and the selected file register.|
This activity introduces these directives:
|cblock||'constant block' - defines a block of constants starting at the specified address. Each named label following a clbock directive is assigned a successive address in memory.|
|endc||'end constant block' - ends the block of constants defined by cblock.|
Time delay loops programming activity
This activity demonstrates software delay loops. The earlier Count activity demonstrated the use of a microcontroller hardware timer (TMR0) to create time delays. The advantages of hardware timers include the ability to easily create very long time delays as well as the ability to run other program code while the timer is running.
In this activity, we'll examine a delay that uses software loops to provide the time delay. One advantage of this method is the ability to precisely tailor the accuracy and time length of the delay. The major downside of software time delays is that the micrcocontroller core is occupied running the delay code and can't easily multitask or run other code during the delay.
What you should know before starting
Microcontroller related information
The execution speed of assembly code (and any code) is directly proportional to the microcontroller's clock frequency. As a result, tuning a software loop to produce a specific amount of time delay at one frequency will naturally result in a different time delay if the hardware clock frequency is changed.
The example code in this activity is designed to run in a 4MHz PIC16F886 microcontroller. If your application is expected to run at a different clock speed, you will have to modify the code to produce your desired delay. Fortunately, modifying the total delay time relatively simple to do, and you'll have a good understanding of how the delay loop works after this activity.
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 DELAYS.ASM program is shown below. Create a Delays project in MPLAB, copy this code into it, and build the program.
;Delays v3.1 January 18, 2013 ;=============================================================================== ;Description: This program demonstrates a time delay using nested loops. ;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 ;Define RAM registers. cblock 20h counter1 ;Inner loop counter counter2 ;Outer loop counter endc ;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 the LEDs banksel PORTA ;Return to register bank 0 main movlw 01010101b ;Display a pattern on the LEDs movwf PORTB flashLEDs movlw 11111111b ;Load W with pattern used to toggle LEDs and xorwf PORTB,f ;XOR PortB with pattern movlw 250 ;Preload outer loop counter with number of movwf counter2 ;times to run inner loop outerLoop clrf counter1 ;Reset inner loop counter innerLoop nop ;Pad loop with one extra clock cycle decfsz counter1,f ;Decrement inner loop counter until goto innerLoop ;inner loop counter = 0 decfsz counter2,f ;Decrement outer loop when inner loop is done goto outerLoop ;Repeat until outer loop counter = 0 goto flashLEDs ;Keep toggling LEDs end
Download this program to your CHRP board and observe its operation.
How the program works
The top section of the program introduces a couple of new directives.
;Set hardware equates. S2 equ 0 ;PORTB position of pushbutton S2 ;Define RAM registers. cblock 20h counter1 ;Inner loop counter counter2 ;Outer loop counter endc
As described in earlier activities, equ directives can be used to assign bit addresses to a label (as shown in the S2 equate, above), and also to assign memory address locations to labels (as demonstrated in previous activities). Instead of equate statements, this activity demonstrates the use of the cblock directive to start a block of defined memory address labels.
A memory address block started with cblock requires the starting address — 20h in this case, representin the address of the first free RAM register. Next, each memory address label follows. Address 20h will be assigned to counter1, and address 21h will be assigned to counter2, in sequence, until an endc directive is encountered. The endc directive is required to end the memory block.
Following the RAM register definitions, the initPorts subroutine is unchanged from most of the previous programs that use PORTB for output, so we'll skip over this section and examine the main subroutine next.
main movlw 01010101b ;Display a pattern on the LEDs movwf PORTB flashLEDs movlw 11111111b ;Load W with pattern used to toggle LEDs and xorwf PORTB,f ;XOR PortB with pattern
The main subroutine sets up a pattern of lights on PORTB. To flash the pattern, flashLEDs reverses the 0's and 1's using an XOR operation. Let's examine how that operation works by reviewing an XOR gate truth table:
Assume that we can control the A inputs, and the port data is represented by the B inputs. The following patterns should become apparent:
- When the A input is 0, the X output is equal to the B input (ie. the data is unchanged).
- When the A input is 1, the X output is equal to not-B, the inverted B input data.
The movlw 11111111b instruction loads W with a string of ones that will each be XORed with their corresponding PORTB bits. As shown in the truth table, this process inverts all of the PORTB bits to flash the LEDs.
Let's analyse the software delay starting with the inner delay loop code:
innerLoop nop ;Pad loop with one extra clock cycle decfsz counter1,f ;Decrement inner loop counter until goto innerLoop ;inner loop counter = 0
The nop instruction only serves to pad the loop by one extra clock cycle. The decfsz instruction decrements the loop counter by 1, and the goto instruction ensures the loop runs for the number of cycles pre-loaded into counter1.
Assuming counter1 is cleared to start (which it is, in the previous instruction), the first decfsz operation will decrement the count from 0 to 255. The zero-check built into decfsz only checks the result of the subtraction, so starting with a zero count won't cause goto to be skipped just yet.
Adding clock cycle counts to the code will help us to determine what happens next and how many clock cycles the inner loop takes to run:
nop (1 clock, every cycle) decfsz counter1,f (1 clock, 2 clocks during the skip - last cycle) goto innerLoop (2 clocks, skipped during the last cycle) ------------------------------------------------- (4 * (counter1 - 1)) + 3 = 1023 clocks
Each pass through the loop, except the last one, takes four clock cycles: one each for the nop and decfsz instructions, and two for goto. With counter1 starting at 0 (equivalent to 256, as the first decrement results in counter1 becoming 255), the 4-cycles of the loop are run 255 times, resulting in a total delay of 1020 clock cycles. The very last cycle, only the nop and decfsz instructions run, but since decfsz skips on the zero result it takes two clock cycles to run this last time, adding three more clock cycles. This brings the total delay 1023 clock cycles, or 1.023 ms at a 4 MHz oscillator frequency.
outerLoop clrf counter1 ;Reset inner loop counter innerLoop nop ;Pad loop with one extra clock cycle decfsz counter1,f ;Decrement inner loop counter until goto innerLoop ;inner loop counter = 0 decfsz counter2,f ;Decrement outer loop when inner loop is done goto outerLoop ;Repeat until outer loop counter = 0
The innerLoop code is a part of outerLoop, which begins by clearing counter1. While it's not necessary to clear counter1 since the count is zero at the end of each innerLoop cycle, this would be the ideal place to set counter1 to a smaller number (remember, zero represents a full count of 256 cycles) if a shorter innerLoop delay is desired.
After innerLoop runs, taking 1023 clock cycles, counter2 is decremented before the innerLoop runs again. In this case, the innerLoop delay of 1.023 ms is repeated 250 times (the number counter2 is pre-set to), resulting in a total delay of slightly more than 0.25 seconds between LED flashes.
Choose your interval
Using this nested loop structure for other amounts of delay is relatively straightforward. The innerLoop function is used to create a specific delay interval, and the outerLoop repeats the delay a specific number of times.
The longest possible duration using the program shown here would be 256 cycles of 256 innerLoop cycles, just slightly longer than the delay in this activity. To lengthen the delay even more, another loop could be added outside of outerLoop, repeating the entire delay multiple times. To shorten the delay, both the outer and inner loops can be shortened.
Decreasing counter2 reduces the number of times the innerLoop interval is run, in steps of approximately 1 ms. To shorten the total delay below 1 ms, counter1 can be reduced to shrink the innerLoop delay.
Test your knowledge
- The nop instruction in the innerLoop pads the delay loop to 4 cycles. What is the maximum number of loops that can be counted using an 8-bit counter? Can you think of an advantage to using a 4-cycle loop in an 8-bit architecture?
- If the nop instruction were left out of the innerLoop, what would you expect the innerLoop interval to be? What would you expect the total delay to be?
- What would you expect the total delay to be if a second nop instruction was added to the innerLoop?
Apply your skills
- Make a delay of exactly 250 ms. Adjust the value loaded into counter1 to make the inner delay loop shorter, so that the total time delay between flashes is as close to 250 ms as possible.
- Make a variable time delay. Create a callable time delay subroutine that delays for the number of milliseconds passed to it as a variable in W. See the Analogue programming activity for an example of how parameters can be passed using W. Where will the value to be passed come from? One idea could be to use the potentiometer input (from Analogue) to set the time delay, and therefore the flash rate, in this program.