Program description

What does it do?

The input program demonstrates how to sense digital inputs from the switches on PORTB of the CHRP 3 board.

In addition, the input program demonstrates the use of if-else conditional statements.

If-else conditions

The structure of an if-else condition typically looks like:

if (condition)
	statement;
else if (condition)
	statement;
else if (condition)
	statement;
	...
else
	statement;
			

Conditions are evaluated from top to bottom, until a match is found. If no conditions match, the last, optional else statement provides a default path of execution before the condition is exited.

Input programming activity

The previous programming projects demonstrated various ways of controlling microcontroller outputs. This activity demonstrates the process of sensing inputs from externally connected devices. The input program also provides an example of an if-condition, one of the most common conditional checks in microcontroller programs. When you finish this activity, you should be able to make a simple line-following robot using your CHRP board.

What you should know before starting

CHRP related information

Near the centre-right of the CHRP 3 schematic, the microcontroller's Port B pins branch out to an octal buffer used to drive the LEDs (output devices), six pushbutton switches (input devices), the L293D motor driver IC (an output device), and an optional LCD. You might wonder how it's possible for the microcontroller to mix input and output devices on the same I/O port. Through careful hardware design and the use of the previously described TRIS registers, it's relatively easy to switch between I/O devices.

Let's examine the output hardware devices first. You'll notice on the schematic that the LEDs are not connected directly to PORTB. Instead, the LEDs are driven through an octal (8-wide) buffer IC. The buffer presents a high-impedance load to the microcontroller I/O pins. In other words, the buffer just senses the state of the PORTB wires at its inputs, and it activates the appropriate LEDs on the output side without drawing any current from the PORTB circuitry. Likewise, the motor driver senses the state of the lower nybble (4 bits) of Port B, and internally switches higher current to the motors. The LCD similarly has its own microcontroller watching the state of the Port B pins.

Next, let's look at the input circuits. The Port B pushbuttons connect to the Port B I/O pins through a series resistor. When the port pin is set up as an input and the pushbutton is pressed, very little current flows between ground and the I/O pin, so the series resistor has a negligible effect. The reason for the resistor is to protect each port pin's output transistor by limiting the current flow when a pushbutton is pressed.

If a pushbutton is pressed while the port pin is outputting a high signal, the series resistor limits the current flow to ground, preventing a short circuit on the output pin. If a pushbutton is pressed while the port pin is outputting a low signal, very little current flows anyway, since the switch is connected to ground, with is essentially the same potential as the I/O pin.

Since the hardware is set up to effectively prevent the input and output devices from affecting each other, the programmer can choose which type of I/O device to use — input or output — and set the TRISB register accordingly.

PORTA of the microcontroller also includes a mix of input and output circuits, but on separate pins — not on one pin shared with many devices as seen on PORTB. PORTA includes two phototransistor inputs (Q1 and Q2), a potentiometer, a temperature sensor, and a voltage divider input, as well as LED and beeper outputs.

Microcontroller related information

Let's re-examine the function of the TRIS registers. The TRISA, TRISB and TRISC registers control the direction of data transfer on each pin of PORTA, PORTB, and PORTC, respectively. The easy way to remember the TRIS register settings is to match the numbers 0 and 1 to the letters O and I:

0 is like O, as in Output
1 is like I, as in Input

So, writing 00110000 to a TRIS register would have the effect of making port bits 0-3 outputs, bits 4 and 5 inputs, and bits 6 and 7 outputs.

Input circuits

How digital input devices work

All input circuits, such as pushbutton switches, phototransistors (light sensors), potentiometers (position sensor), and voltage sensors operate in a similar way. Each is based on a series voltage divider, illustrated by the switch circuit, below:

Switch input circuit image

The pull-up resistor (R1) and switch (S1) form a series circuit, also known as a voltage or potential divider. The microcontroller input is connected to the output at the mid-point of the voltage divider, and senses the electrical potential across the switch. Since any microcontroller pins configured as inputs have a high impedance, they will just sense the externally applied voltage but will not conduct any appreciable current themselves. This means that the effect of the series resistor (R2) between the voltage divider and the microcontroller is negligible and can be safely ignored — R2 is installed to limit the current due to ESD (electro-static discharge), protecting the microcontroller from static charges conducted into the switch circuit by a user's fingers.

Ignoring R2, the operation of the switch input circuit can be explained by the interaction of the switch and pull-up resistor, R1. You can think about how this works either by Ohm's law analysis, or by thinking about it as a voltage divider. Using Ohm's Law analysis, we know that when the switch is open, no current flows through the pull-up resistor. Since no current flows, the voltage loss across the resistor is zero, and the microcontroller input pin senses the full power supply voltage connected to the resistor — 5V, or a logic 1. You could say the resistor pulled-up the switch circuit voltage to 5V.

When the switch is pressed, current flows through both the switch and resistor. Since the switch has effectively zero resistance, there is a zero volt loss across the switch. Ohm's Law shows that the current flow through the resistor causes all of the applied voltage to be dissipated across the resistor, and so the microcontroller ends up sensing the ground potential, 0V, or a logic 0.

Thinking about the circuit as a voltage divider, we know that the two elements in a series circuit divide the applied voltage in the ratio of their resistances. The voltage at the mid-point of the switch circuit will be proportional to the resistance of the switch, which has either infinite resistance (switch open), or zero resistance (switch closed). When the switch is open, its infinite resistance is infinitely higher than that of the pull-up resistor, causing the entire applied voltage to be measured across the switch, and producing a logic 1 (5V). When the switch is closed, its (ideally) zero resistance is infinitely smaller than that of the pull-up resistor, causing no voltage drop, and producing a logic 0 (0V).

How analogue input devices work

In addition to pushbuttons, the CHRP board also includes analogue input devices. Ideally, the potential produced by these analogue devices would be sensed using an A-D (analogue-to-digital) converter, and we'll do that in the next programming activity. For now, we can use the microcontoller's input circuits to obtain a digital value from these analogue circuits. When doing this, you'll find that the input potentials are asymmetrical (the range of potentials that represent zero is less than the range that represents one) and incorporate hysteresis (a zone through which the input value has to go before it will change).

Phototransistor input circuit image

The light-sensing circuit, above, is similar to a digital input circuit except that the switch has been replaced by a phototransistor. Phototransistors change their resistance in response to light — high resistance in dim light, and low resistance in bright light. In dark conditions, the phototransistor exhibits higher resistance than the pull-up resistor, creating a high input potential at the microcontroller's port pin. If it's dark enough, and the input potential across the phototransistor is high enough (see the Light Intensity graph, above), the microcontroller will sense the input as a logic 1.

Similarly, in bright conditions the phototransistor's resistance drops, reducing the potential at the mid-point of the voltage divider. Again, if the divider potential is low enough, the microcontroller will sense it as a logic 0. Because of the analogue characteristics of the phototransistor (as opposed to the binary action of a switch), and the voltage threshold levels of the microcontroller's input circuits, small changes in brightness may not be enough to change the state of the input.

The potentiometer circuit behaves in an almost identical way to the phototransistor circuit. Instead of light, the potentiometer responds to rotational position. The wiper attached to the shaft of the potentiometer divides its internal resistor into two parts. Turning the potentiometer's wiper changes the ratio of the two resistor halves. The voltage at the wiper will be proportional to the ratio of the resistors. When turned through its full rotation, the potentiometers voltage varies from zero to the full power supply potential.

The voltage divider, used as a voltage sensor in the CHRP circuit, behaves the same way but uses a set of fixed resistors so that its output only varies as the input potential varies. The voltage divider has been pre-set to produce a maximum 5V output at an input voltage of 25.5V, corresponding to 0.1V per bit of accuracy. Because of these precise steps, the voltage sensor is ideally suited to analogue measurement.

Temperature sensor T1 is a bit different in that it is an active circuit that converts temperature into a proportional output potential. As with the voltage sensor, the temperature sensor is designed for analogue measurement rather than digital.

Internal pull-up resistors

The Port A input circuits, except the temperature sensor, all include a pull-up resistor (a resistor connected to the power supply potential) to enable their operation. Referring back to the CHRP 3 schematic, you'll notice there are no pull-up resistors connected to the Port B switches. Instead, this PIC microcontroller includes internal, software-controlled pull-up resistors for all of the Port B input/output pins. These pull-ups are enabled in the initPorts subroutine through the OPTION_REG register, and affect only the Port B pins set to be inputs. Using software-controlled pull-up resistors provides additional hardware flexibility by allowing Port B to be used as both an input and output port, without having permanently attached pull-up resistors interfere with the port signals.

On the schematic, you will notice that there are 2.2kΩ resistors connected in series with the Port B switches. These are not pull-up resistors, but are instead series current-limiting resistors necessary because the Port B switches are connected to ground. To understand why, imagine what would happen if one of the Port B wires was outputting a high signal and its pushbutton was pressed. This would cause the signal to be shorted to ground. To prevent this, the series current-limiting resistors provide enough of a load that short circuits are prevented.

Create the Input project

The Input project requires the new CHRP3.c and Input.c files, below, as well as the two CHRP3.h and PIC16F886config.c files from the count project.

Create the header and microcontroller configuration files by copying them from the count project. Next, create the CHRP3.c file:


/*==============================================================================
	CHRP 3.1 (PIC16F886) hardware initialization and user functions.
==============================================================================*/

#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

void initPorts(void)
{
	// TODO - Initialize oscillator, ports, and add (optional) user functions.

	// Initialize user ports and peripherals.

	ANSEL = 0b00000000;			// Make all PORTA pins digital I/O
	ANSELH = 0b00000000;		// Make all PORTB pins digital I/O
	OPTION_REG = 0b01010111;	// PORTB pull-ups on, TMR0 internal div 256

	// Set port directions for I/O pins: 0 = Output, 1 = Input

	TRISA = 0b00101111;			// Set runLED, IR LEDs as outputs in PORTA
	TRISB = 0b00000011;			// Set S2 and S3 as input, other PORTB LEDs as outputs
	TRISC = 0b10110000;			// Set receive and transmit lines for IR
								// demodulator U5 and LED11, servo outputs

	// Set starting I/O conditions.

	PORTA = 0;					// Turn off all PORTA outputs, turn on run LED
	PORTB = 0;					// Turn off all PORTB LEDs
	PORTC = 0;					// Turn off all PORTC outputs

	// Enable interrupts, if needed.
}
		

The only change here is in the TRISB setup — the two pins corresponding to the S2 and S3 pushbutton switches are set up as inputs. All of the other Port B pins are set as outputs.

Create the Input.c program


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

	This program demonstrates input and output devices sharing an I/O PORT.
	Pushbutton inputs are normally high (due to the pullup resistor) and will be
	represented by lit PORTB LEDs. When pressed, pushbutton inputs go low.
	Try to modify the program to use PORTA devices as inputs.
==============================================================================*/

#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

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

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

    while(1)
    {
		if (S2 == 0)					// Pressed buttons are logic 0 (0V level)
			PORTB = 0b11100000;			// Light up this pattern when S2 pressed
		else if (S3 == 0)
			PORTB = 0b00011100;
		//else							// What do you think happens if you
		//	PORTB = 0;					// uncomment these two lines? Try it!
    }
}
		

Compile the program and download it into your CHRP to verify its operation.

How the program works

The concept of the if-else conditional check is fairly self-evident. if conditions are evaluated in sequence, performing the statement immediately below the if condition if the conditional check is true, or skipping to the next else if if the prior conditional check is false, until a true condition is found. The code below the first true condition is executed.

Note that the single code statements within (below) the if and else if operators are indented. If more than one line of code is to be executed following a true conditional check, the code must be enclosed in curly braces.

If no true conditions are found and a final else condition is present, it executes by default. The final else condition is optional, and if no else condition is present the if statement code terminates without any code operations being executed.

More ways to use symbolic constants

Reading the pushbutton states using symbolic names (eg. S2) rather than their I/O pin names is another example of the benefits of abstracting physical I/O pin names and using symbolic names instead. Symbolic names can be used to refer to individual I/O pins, as well as hardware and software constants.

For example, you could describe a motor pin as:

#define LEFTMOTOR RB1

Or, you could use a constant to describe motor direction:

#define FORWARD 0b00000110

Using symbolic constants provides a number of advantages, including the ability to use logical, English-language names for I/O devices instead of port/pin names.

Test your knowledge

  1. When the program is first started, the LEDs corresponding to the S2 and S3 switches are on. Explain why this is so.
  2. Watch what happens when S2 and S3 are pressed and released in the code, above. Un-comment the last two lines that make up the else condition. How does the operation of the program change with else enabled?
  3. Press and hold S2, and then press S3. What light pattern is shown, the pattern produced by S2 or S3? Next, press and hold S3, then press S2. What light pattern is shown this time? Did you expect this?
  4. When a PORT B switch is pressed, will the microcontroller input attached to the switch go low, or high? Why?
  5. When a light sensor connected to PORT A sees light, will its I/O pin be low, or high? Why?

Apply your skills

  1. Modify the program so that pressing and holding both S2 and S3 at the same time illuminates all the LEDs that each switch would light individually.
  2. Create a program in which the three switches S2, S3, and S4 each illuminate a different light pattern. (Hint: You will need to make another Port B pin into an input, leaving only five outputs available.)
  3. Create a program to use the CHRP board's potentiometer as its input, and a single Port B LED as its output. Use symbolic names for both the input and the output. (Hint: Check the user.h file for the symbolic name of the potentiometer input.) Measure the input threshold potentials — the potential at which the input pin switches from 0 to 1, and back from 1 to 0 — using a multimeter connected to the wiper terminal of the potentiometer as you rotate the potentiometer top.
  4. If you are making a line-following robot using your CHRP and haven't already done so, install the downward-facing LED and phototransistors now. Write a sensor test program that turns on the floor LED and displays the state of the phototransistors on two of the Port B LEDs.