Program description

What does it do?

The Chaser program demonstrates the use of conditional while statements, variable passing, the bit shift operator, and a hardware-based time delay using the TMR0 register. All of these are combined in a program that chases a lit LED back and forth across Port B.

New concepts

Conditional checks

PIC microcontrollers have a limited instruction set due to their RISC design. As a result, it is recommended to use one of the following hard conditional checks in programs:

  • == (equal to)
  • != (not equal to)

The softer conditional checks, below, require more processing and impact the size and speed of the compiled code:

  • < (less than)_
  • > (greater than)
  • <= (less than or equal)
  • >= (greater than or equal)

Bit shifting

Microcontrollers often contain a variety of bit manipulation instructions. The following C language bit shift operations directly translate into machine code to efficiently shift bits within a RAM register:

  • >> n (shift bits right n positions)
  • << n (shift bits left n positions)

Chaser programming activity

The previous output and count programs included __delay_ms(), a simple-to-use, compiler-generated time delay function. The timing-related change in this program is the use of TMR0, the microcontroller's built-in hardware timer. Using hardware timers can free the processor core to run code while the timer runs, rather than needing software code to run the timer. This effectively shortens the total program size while increasing processing capability.

The PIC16F886 microcontroller contains three hardware timers, TMR0, TMR1 and TMR2. TMR0 can be considered a legacy device, descended from the original — and only — hardware timer in the early PIC microcontrollers. Due to its legacy, TMR0 has a few quirks, as described below.

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 count 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 to the timer 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.

Create the program

All of the files, except for Chaser.c, are the same as in the count project. Create new PIC16F886config.c, CHRP3.h and CHRP3.c files in the Chaser project by copying each of these files from the Count project. Then, create the Chaser.c file using the code, below:


/*==============================================================================
	Project: Chaser
	Version: 3.1				Date: November 24, 2014
	Target: CHRP3				Processor: PIC16F886

	A back-and-forth light chaser demonstrating port output, bit sensing using
	symbolic constants, function variable passing, and hardware timers.
==============================================================================*/

#include    "xc.h"              // XC compiler general include file

#include    "stdint.h"          // Include integer definitions
#include    "stdbool.h"         // Include Boolean (true/false) definitions

#include    "CHRP3.h"           // Include user-created constants and functions

/*==============================================================================
	TMR0 hardware timer delay function. TMR0 is a hardware-based timer/counter
	configured through OPTION_REG in CHRP3.c. Pass it a starting count (1-255),
	and wait for it to roll-over to zero.
==============================================================================*/

void delayTMR0(unsigned char startCount)
{
	TMR0 = startCount;			// Preset TMR0 with starting count

	while (TMR0 != 0);			// Wait until TMR0 = 0
}

/*==============================================================================
	Main program code
==============================================================================*/

int main(void)
{
    // Initialize I/O and peripherals
    initPorts();

    // Set starting conditions
    LED10 = 1;							// Turn LED 10 on

    while(1)
    {
		while (LED3 == 0)				// Repeat until the MSB LED becomes lit
		{
			PORTB = (PORTB << 1);		// '<<' shifts port contents left 1 bit
			delayTMR0(61);				// Call hardware-based 50ms time delay
		}
		while (LED10 == 0)				// Repeat until the LSB LED becomes lit
		{
			PORTB = (PORTB >> 1);		// '>>' shifts register right 1 bit
			delayTMR0(61);
		}
    }
}
		

The delayTMR0 function

The function declaration that starts the delayTMR0 function indicates that it expects a startCount parameter. The startCount variable has been defined in the function declaration as an unsigned char — an 8-bit positive integer value. Defining the startCount variable in the function declaration creates a local variable accessible only within the delayTMR0 function.

The void return-type signifies that this is a one-way function — it accepts a parameter, but does not return any data to the calling code.

The TMR0 = startCount; statement loads TMR0, the timer register, with the data passed to the function. On every (pre-scaled) clock count, the TMR0 register increments by one. Since TMR0 is an 8-bit register, it will reset to zero after its maximum count of 255 has been reached.

Conditional loops

The while (TMR0 != 0); statement is a conditional loop that executes while the condition is true. As long as the TMR0 value is not zero, the code within the while statement, and normally within curly braces, executes.

But wait! There are no curly braces. In this case, the while condition is terminated using a semicolon, and will be evaluated repeatedly, until it becomes true. This structure essentially 'wait while timer is not done' function. When the timer reaches zero and finishes, the function is complete and execution returns to the main code.

The main loops


int main(void)
{
    // Initialize I/O and peripherals
    initPorts();

    // Set starting conditions
    LED10 = 1;							// Turn LED10 on

    while(1)
    {
		while (LED3 == 0)				// Repeat until the MSB LED becomes lit
		{
			PORTB = (PORTB << 1);		// '<<' shifts port contents left 1 bit
			delayTMR0(61);				// Call hardware-based 50ms time delay
		}
		while (LED10 == 0)				// Repeat until the LSB LED becomes lit
		{
			PORTB = (PORTB >> 1);		// '>>' shifts register right 1 bit
			delayTMR0(61);
		}
    }
}
		

The main code starts completing the now-familiar initPorts port initialization and then pre-sets Port B to 00000001 using the LED10 = 1; bit operation.

Bit operations are somewhat different than the byte operations we have used before. For example, both of these statements produce similar, but potentially very different, results:


PORTB = 0b00000001;			// Sets only the RB0 bit (all other bits are cleared)
LED10 = 1;					// Only sets the RB0 bit (all other bits are left as they were)
		

PORTB = 0b00000001; is a byte operation that sets LED10 (RB0) and clears all of the other Port B outputs. LED10 = 1; is a bit operation that turns on the RB0 bit (since LED10 is defined in CHRP3.h as an alternate name for bit RB0). Only RB0 is affected by the bit operation, and bits RB1-7 remain in the state they were in (which happened to be zeroes, because they were cleared in CHRP3.c).

Just as in the earlier programs, while(1) forms an infinite loop that contains the functional program code. The while (LED3 == 0) statement uses a double equal sign '==' to form a conditional check. One equal sign is considered an assignment operation, such as in the line PORTB = 0b00000001;, above. Two equal signs are used in conditional checks for equality.

The code within while (LED3 == 0) executes while the condition within the brackets is true. LED3 is defined in the CHRP3.h file as a symbolic name for RB7, the most-significant (left-most) bit of Port B. Since the least-significant (right-most) bit of Port B was set to a one in the LED10 = 1; statement, LED3 is currently be equal to zero, and the code within the braces of the while statement executes.

PORTB = (PORTB << 1); shifts all of the bits in the Port B register one bit position to the left, resulting in a Port B value of 00000010. This operation has the effect of moving the lit LED from LED10 to LED9 on the actual CHRP board.

The delayTMR0(61); function call passes the value 61 to the delayTMR0 function. Starting TMR0 at 61 and counting up to 255 results in a delay of approximately 50 ms. After the delay finishes, the LED3 == 0 conditional check is evaluated again.

The first loop repeats eight times, each time shifting the lit LED one position to the (register) left, until LED3 is lit. Once this happens, the first conditional loop is false and code execution 'falls through' to the second conditional loop. The second conditional while loop similarly executes eight times, each time shifting the Port B contents one position to the right, until LED10 is lit. Then, the entire process repeats from the first conditional loop, continuously sweeping the lit LED back and forth.

Test your knowledge

  1. What do you think will happen if the number passed to the delayTMR0 function from main is increased from 61? Decreased? Try it out to confirm your hypothesis.
  2. What TMR0 value results in the longest delay?
  3. Examine the delayTMR0 function above the main code. Notice that the while condition is terminated using a semi-colon. Curly braces are not necessary below the while condition since there is no code to execute &emdash; while just waits while the condition is true. Try to remove the semi-colon and then add curly braces containing a blank line between them below the while statement. Compile the program. Does it still work? Remove the braces entirely. Does it still work now?
  4. Which file lets MPLAB identify symbolic names (such as LED3 and LED10) as I/O port bits? What are the bit numbers or register names of LED3 and LED10?
  5. What are the advantages of using symbolic names over specific port bit or register names?
  6. Change the first conditional while loop in the main function to be: while (LED3 != 1);. Change the second conditional while loop to match. Is this new code equivalent to the original Chaser program in operation?

Apply your skills

  1. Shorten the chaser so that the LED sweeps from LED10 to LED6 and back.
  2. Instead of displaying one eight-bit chaser, can you think of a way to display two 4-bit chasers on the LEDs? That is, one lit LED will chase back and forth between LED3 and LED6, while a second LED chases back and forth between LED7 and LED10. Try it!