Program description

What does it do?

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

The second INPUTA program demonstrates how to sense digital input from the analogue devices connected to PORTA, and shows how to use logical mask operations to activate selected outputs.

New instructions

During this activity, you will learn about these microcontroller instructions:

andwf 'logical AND W with file register' - performs a logical AND operation between each of the bits in W and the specified file register.
btfsc 'bit test file register, skip if clear' - checks if the specified bit of a file register is clear (0). If the bit is clear (0), the line immediately following btfss is skipped. If set (1), the line immediately following btfss is executed. (opposite of btfss)
iorwf 'inclusive-OR W with file register' - performs a logical OR operation between each of the bits in W and the specified file register

New directives

This program introduces the following new directive:

equ 'equate' - makes a label equivalent to a number or expression

Input programming activity

The previous activities showed you how to make the microcontroller activate outputs. This activity demonstrates how to receive inputs from externally connected devices. The input programs also extend the concept of abstraction—the ability of a program to refer to I/O circuits using assigned variable names rather than their specific hardware addresses (eg. PORTB instead of 06h).

What you should know before starting

CHRP related information

Examining the CHRP 3 schematic, you can see that the microcontroller's Port B pins are connected to both the octal buffer that drives the LEDs (output devices), as well as to six pushbutton switches (input devices). You might wonder if it's possible for the microcontroller to use output and input devices on the same I/O port. Through careful hardware design and the use of the previously described TRIS registers, it's not just possible, but also relatively easy.

Let's examine the output part hardware 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, and then activates the appropriate LEDs—in effect isolating the LEDs and their current draw from the other PORTB circuitry.

Next, let's look at the input circuits. The PORTB pushbuttons are connected directly to the PORTB I/O pins, albeit 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. If the 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.

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

PORTA of the microcontroller also includes a mix of input and output circuits, but not on the same, shared pin as occurs with 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

How input circuits 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 switch (S1) and pull-up resistor (R1) form a series circuit, also known as a voltage 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 won't conduct any appreciable current themselves. This essentially 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).

Phototransistor input circuit image

In the light-sensing circuit, above, the switch has been replaced by a phototransistor, but otherwise the circuit acts in a similar manner to the switch input. 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, causing a high input voltage at the microcontroller's port pin. If the input voltage is high enough, the microcontroller will sense it as a logic 1. In bright conditions, the phototransistor's resistance drops, reducing the voltage at the mid-point of the voltage divider. Again, if the divider voltage 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 won't be enough to change the state of the input.

The potentiometer circuit behaves in an almost identical way to the phototransistor circuit, with the difference that it responds to rotational position instead of light. 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. If the voltage is below a specific threshold, the microcontroller senses a logic 0. Above a threshold, and the microcontrolle senses a logic 1.

The voltage divider used as a voltage sensor behaves the same way, but uses a set of fixed resistors so that its output only varies as the input 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 better suited to analogue measurement than digital.

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 above PORTA 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 to interfere with the port signals.

On the schematic, you will notice that there are 2.2kΩresistors connected in series with the PORTB 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 PORTB 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.

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 Input program

The entire INPUT.ASM program is shown below. INPUT reads the state of two of the CHRP pushbuttons and outputs a unique pattern for each button on the LEDs. Start a new project in MPLAB, copy the INPUT.ASM code into the project, and build the program.


;INPUT			v3.1	January 14, 2013
;===============================================================================
;Description:	Demonstrates switch input on PORTB of the CHRP 3.0.

;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
S3		equ		1		;PORTB position of pushbutton S3

;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	00000011b	;Set S2 and S3 as inputs and all other
				movwf	TRISB		;PORTB pins as outputs
				banksel	PORTA		;Return to register bank 0

checkS2			btfsc	PORTB,S2	;Check if S2 is pressed
				goto	checkS3		;If S2 is not pressed, check S3
				movlw	10000000b	;If S2 is pressed, copy this pattern to
				movwf	PORTB		;the LEDs

checkS3			btfsc	PORTB,S3	;Check if S3 is pressed
				goto	checkS2		;If S3 is not pressed, check S2
				movlw	01100000b	;If pressed, copy this pattern to
				movwf	PORTB		;the LEDs
				goto	checkS2		;Check pushbuttons again

				end
		

Download the program into the CHRP and verify its operation.

How the program works

		
S2		equ		0		;PORTB position of pushbutton S2
S3		equ		1		;PORTB position of pushbutton S3
		

The Input program begins by using the equate directive to assign bit addresses to the S2 and S3 labels. Equate statements enable us to refer to the bits by name, like this 'btfsc PORTB,S2', instead of 'btfsc PORTB,0', helping to improve the readability of our assembly code programs (especially if logical and useful label names are used). In addition to naming bits, equate directives can also assign a name, or label, to constants, register addresses, and expressions. At build time, the assembler simply substitutes the equivalent value or expression for the equated label.

Using equ directives provides some important benefits to assembly code programmers by abstracting the hardware. If the circuit design changes (perhaps in a future revision of the circuit board), the program code can be updated by simply changing the label reference once at the top of the program, rather than at every location in which the reference appears in the program. In addition, the label names have more meaning than bit numbers because they relate to the circuit board—you can look at the board and see where S2 is, but you have to refer to the schematic to find out that it's really PORTB,0. And, as humans, we tend to prefer names over numbers. This web page is hosted at www.siriusmicro.com, and also at an equivalent numeric IP address such as 67.220.228.5, but I'll bet you didn't type in the IP address to get to here in your browser!

		
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	00000011b	;Set S2 and S3 as inputs and all other
				movwf	TRISB		;PORTB pins as outputs
				banksel	PORTA		;Return to register bank 0
		

Following the equates, the initialize code in the input program is almost exactly like that in the previous output programs. In addition to controlling the timer, the OPTION_REG register also enables or disables the internal pull-up resistors. The pull-ups are enabled as a group for all Port B pins, but only affect those pins set to be inputs in TRISB. In the Input program, PORTB bits 0 and 1 have been set as inputs by setting their corresponding TRISB bits.

		
checkS2			btfsc	PORTB,S2	;Check if S2 is pressed
				goto	checkS3		;If S2 is not pressed, check S3
				movlw	10000000b	;If S2 is pressed, copy this pattern to
				movwf	PORTB		;the LEDs
		

The checkS2 and checkS3 subroutines both use btfsc instructions to check the button states. If no buttons are pressed, the associated port pins will be high, causing the code to go to the other check subroutine. If one of the buttons is pressed, the corresponding port pin will be low, and the bit test instruction will cause the processor to skip over the goto instruction and set the LEDs with a unique pattern for that button.

Create the INPUTA program

A variation of the input program, INPUTA.ASM, is shown below. INPUTA uses Port A for input, and requires the phototransistors to be installed in the CHRP board. When each phototransistor senses light, four LEDs corresponding to that phototransistor are lit. Start a new project in MPLAB, copy the INPUTA.ASM code into the project, and build the program.

		
;INPUTA			v3.1	January 14, 2013
;===============================================================================
;Description:	Demonstrates phototransistor input on PORTA of the CHRP 3.0.

;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.

Q1		equ		0		;PORTA bit position of right phototransistor (Q1)
Q2		equ		1		;PORTA bit position of left phototransistor (Q2)
LED12	equ		7		;PORTA bit position of phototransistor LED (LED12)
S2		equ		0		;PORTB bit position of pushbutton S2
S3		equ		1		;PORTB bit position of pushbutton S3

;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 LEDs
				banksel	PORTA		;Return to register bank 0

main			bsf		PORTA,LED12	;Turn on LED12 for the phototransistors

checkQ1			btfsc	PORTA,Q1	;Check if Q1 sees light
				goto	q1Off		;If not, turn off Q1 pattern
				movlw	00001111b	;If Q1 sees light, turn on only
				iorwf	PORTB,f		;the 4 least significant LEDs
				goto	checkQ2		;Check if Q2 sees light

q1Off			movlw	11110000b	;Turn off the 4 least significant LEDs
				andwf	PORTB,f

checkQ2			btfsc	PORTA,Q2	;Check if Q2 sees light
				goto	q2Off		;If not, turn off Q2 pattern
				movlw	11110000b	;If Q2 sees light, turn on only
				iorwf	PORTB,f		;the 4 most significant LEDs
				goto	checkQ1		;Check Q1 again

q2Off			movlw	00001111b	;Turn off the 4 most significant LEDs
				andwf	PORTB,f
				goto	checkQ1		;Check the phototransitors again

				end
		

Download the program into the CHRP and verify its operation.

How the program works


Q1		equ		0		;PORTA bit position of right phototransistor (Q1)
Q2		equ		1		;PORTA bit position of left phototransistor (Q2)
LED12	equ		7		;PORTA bit position of phototransistor LED (LED12)
		

As in the Input program, equate statements are used to assign bit addresses to phototransistors Q1 and Q2, and LED12, the phototransistor light source. Notice that the actual phototransistor bit positions are the same as they were for pushbuttons S2 and S3 in the Input program, but in the InputA program these bits refer to PORT A bit addresses rather than PORT B bit addresses. MPLAB doesn't inherently know which port you are referring to from the equate—it merely substitutes the equated bit address into the btfsc PORTA instructions appearing later.

The initPorts subroutine configures PORT A I/O exactly as in the previous Output, Count and Chaser programs. The main subroutine turns on LED12, providing a source or IR (infrared) illumination for the phototransistors. The input-checking code is different, however:


checkQ1			btfsc	PORTA,Q1	;Check if Q1 sees light
				goto	q1Off		;If not, turn off Q1 pattern
				movlw	00001111b	;If Q1 sees light, turn on only
				iorwf	PORTB,f		;the 4 least significant LEDs
				goto	checkQ2		;Check if Q2 sees light

q1Off			movlw	11110000b	;Turn off the 4 least significant LEDs
				andwf	PORTB,f
		

Similar to the Input program, the checkQ1 and checkQ2 subroutines each check the state of their associated phototransistors to determine which LEDs should be illuminated. The similarity ends at the iorwf instruction. Unlike Input, in which pressing one button lights specific LEDs until the other button is pressed, the InputA program will show the state of both inputs simultaneously, by using logical operations to control the PORTB bits.

Logical operations

In InputA, half of the LEDs will illuminate when one of the phototransistors sees light. To avoid overwriting the state of the other half of the LEDs (as would happen using a movwf instruction), we use logical gate instructions to modify only the four bits corresponding half of the PORTB LEDs. We do this by using an AND operation to clear all bits and turn off the LEDs, and an OR operation to turn on the LEDs.

To see how this works, take a look at the following logic gate truth tables. Imagine that the A input is under our control (through the program), and the B input is the value already in the PORTB RAM register. Divide each truth table into a top half (in which A is 0), and a bottom half (in which A is 1).

AND Gate
A B X
0 0 0
0 1 0
1 0 0
1 1 1
OR Gate
A B X
0 0 0
0 1 1
1 0 1
1 1 1

If you compare the AND gate's X output while A is 0, and again while A is 1, you should see the pattern that the AND gate provides. When A=0, X=0, but when A=1, X=B. In and AND operation, we can use the A input to either clear the register (A=0), or to leave the register value unchanged (A=1).

Similarly, for the OR truth table, we can use the A input to either leave the register unchanged (A=0), or set the register (A=1).

Now, let's look at the actual gate operation that takes place in the program. Assume that PORT B is still clear, as it would be after power-up. The checkQ1 subroutine has determined that four LEDs should go on. We can use an OR operation to accomplish this:


  OR constant (A)     00001111
  PORT B (B)          00000000
                      ========
  New Result  (X)     00001111
  

Any bits in the register ORed with a 0 will remain unchanged. Those bits ORed with a 1 will be set.


   AND constant (A)   11110000
   PORT B (B)         11111111
                      ========
   Result (X)         11110000
  

Any bits in the register ANDed with a 1 will remain unchanged. Those bits ANDed with a 0 will be cleared.

By using logical operations, such as AND and OR, we can change as many (or as few) bits in a register as we need to, without modifying any of the other bits that need to remain unchanged.

Test your knowledge

  1. Explain the purpose of the equate statements in these programs, as well as two advantages of using equates instead of statically-coded references.
  2. Explain the difference between the btfsc and btfss instructions.
  3. How many clock cycles does it take to execute a goto instruction? Why is this different from other instructions, like movlw, or bsf?
  4. When a PORT B switch is pressed, will the microcontroller input be 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. Assemble the Input program. What do you think will happen when both buttons are pressed? Verify this with the simulator or on your CHRP circuit.
  2. Modify the Input program to be a push-on, push-off light switch. Use S2 to turn one or more lights on, and S3 to turn the same lights off.
  3. Make a copy of the program in which the three switches S2, S3, and S4 each illuminate a different light pattern.
  4. Modify the InputA program to use the CHRP board's potentiometer as its input, and measure the input threshold voltages—the potential at which the input pin switches from 0 to 1, and back from 1 to 0 using a multimeter.