Program description

What does it do?

The time delay loops program demonstrates accurate software time delay loops.

New instruction

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.

New directives

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.

Program requirements

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:

XOR Gate
A B X
0 0 0
0 1 1
1 0 1
1 1 0

 

Assume that we can control the A inputs, and the port data is represented by the B inputs. The following patterns should become apparent:

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.

Small counts

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.

Larger counts

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

  1. 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?
  2. 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?
  3. What would you expect the total delay to be if a second nop instruction was added to the innerLoop?

Apply your skills

  1. 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.
  2. 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.