What does it do?
The two input programs demonstrate how to sense digital inputs from either the switches or phototransistors on the CHRP board.
The second INPUTA program demonstrates how to activate selected outputs within a register by using logical operations to mask the register bits.
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|
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 introduce 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.
What you should know before starting
CHRP related information
If you examine the CHRP 2 schematic, you can see that the microcontroller's Port B pins are connected to both the octal buffer that drives the LEDs, as well as to eight pushbutton switches. You might wonder how the microcontroller can use one I/O port for both output and input circuits at the same time. It's relatively easy, as it turns out, through a combination of CHRP hardware design and the use of the previously described TRIS registers.
First, let's examine the output part of the hardware. On the schematic, notice that the LEDs are not connected directly to PORTB. Instead, the LEDs are driven through a 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 from the other PORTB circuitry.
Now, 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 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.
Port A of the microcontroller also includes some input circuits. Both phototransistors (Q1 and Q2) as well the potentiometer inputs can be sensed by the PIC. These circuits are input only in the CHRP 2 board—Port A is not shared with any output devices. The potentiometer and phototransistor Q1 share one input pin—use J9 to select which one of the two connects to bit 0 of Port A.
Microcontroller related information
Now, let's re-examine the function of the TRIS registers. The TRISA and TRISB registers control the direction of data transfer on each pin of Port A and Port B, 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
The CHRP circuit board includes a number of input circuits, such as pushbutton switches, phototransistors (light sensors), and a potentiometer (position sensor). All of these inputs operate in a similar, though slightly different way. Examine the switch input schematic diagram, below:
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).
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, some 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.
Internal pull-up resistors
The input circuits, above, all include a pull-up resistor to enable the operation of the input device. If you refer to the CHRP 2 schematic, you'll see pull-up resistors connected to all of the PORT A input circuits, but not the PORT B switches. The pull-ups on PORT B are omitted because the PIC microcontroller used in the CHRP circuit 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.
Referring back to the CHRP schematic for a moment, you will notice that there are 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 is outputting a high signal, and its pushbutton was pressed, shorting the output signal to ground. To prevent this, the series current-limiting resistors provide enough of a load that short circuits are prevented.
To use this program you will need:
An assembled CHRP board, microcontroller, and power supply, a programming cable, and a Windows PC with the MPLAB IDE software and downloader 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.ASM v2.0 Last modified on December 1, 2008 ;=============================================================================== ;Description: Input test program. Displays Port B switch state on Port B LEDs. ;Start of MPLAB and processor configuration. 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 ;End of MPLAB and processor configuration. ;Start of hardware equates S2 equ 0 ;Port B position of switch S2 S3 equ 1 ;Port B position of switch S3 ;End of hardware equates org 00h ;Start of program memory clrf PORTA ;Turn off all port outputs clrf PORTB clrf PORTC goto initPorts ;Jump to initialize subroutine org 05h initPorts ;Set Ports B and C to support CHRP digital circuitry. banksel ANSELH ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSELH ;Set all PORTB pins to digital movlw 00000011b ;Set S2 and S3 as inputs and movwf TRISB ;all other PORTB pins as outputs banksel TRISC ;Switch register banks movlw 10110000b ;Setup serial input and output pins, movwf TRISC ;and set motor outputs banksel PORTB ;Return to PORTB register bank checkS2 btfsc PORTB,S2 ;Check if S2 is pressed goto checkS3 ;If S2 is not pressed, check S3 movlw 10000000b ;If 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 org 1F00h ;Start of bootloader code area res 256 ;Reserve memory for bootloader end
Download the program into the CHRP and verify its operation.
How the program works
S2 equ 0 ;Port B position of switch S2 S3 equ 1 ;Port B position of switch S3
This program begins by using the equate directive to assign bit addresses to the S2 and S3 labels. The equate statements allow us to refer to the bits by name, like this 'btfsc PORTB,S2', instead of 'btfsc PORTB,0'. Equate directives can really improve the readability of 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, also not inconsequentially, humans prefer names over numbers. This web page is hosted at www.siriusmicro.com, and also at an equivalent numeric IP address such as 18.104.22.168, but I'll bet you didn't type in the IP address to get to here in your browser!
initPorts ;Set Ports B and C to support CHRP digital circuitry. banksel ANSELH ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSELH ;Set all PORTB pins to digital movlw 00000011b ;Set S2 and S3 as inputs and movwf TRISB ;all other PORTB pins as outputs ...
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.
checkS2 btfsc PORTB,S2 ;Check if S2 is pressed goto checkS3 ;If S2 is not pressed, check S3 movlw 10000000b ;If 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 the appropriate button is pressed the port pin will be low, and the bit test instruction will cause the processor to skip over the goto 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.ASM v2.0 Last modified on December 1, 2008 ;=============================================================================== ;Description: Displays Port A phototransistor input state on Port B LEDs. ;Start of MPLAB and processor configuration. 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 ;End of MPLAB and processor configuration. ;Start of hardware equates Q1 equ 0 ;Port A bit position of phototransistor Q1 Q2 equ 1 ;Port A bit position of phototransistor Q2 LED10 equ 7 ;Port A bit position of LED10 ;End of hardware equates org 00h ;Start of program memory clrf PORTA ;Turn off all port outputs clrf PORTB clrf PORTC goto initPorts ;Jump to initialize subroutine org 05h initPorts ;Set Ports B and C to support CHRP digital circuitry. banksel ANSEL ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSEL ;Set all PORTA pins to digital I/O clrf ANSELH ;Set all PORTB pins to digital I/O and banksel TRISA ;Switch register banks movlw 01101111b ;Setup LED and beeper outputs, and movwf TRISA ;make all other PORTA pins inputs clrf TRISB ;Make all PORTB pins outputs movlw 10110000b ;Setup serial input and output pins, movwf TRISC ;and set motor outputs banksel PORTB ;Return to PORTB register bank bsf PORTA,LED10 ;Turn LED10 on checkQ1 btfsc PORTA,Q1 ;Check if Q1 sees light goto q1Off ;If not, turn off Q1 pattern movlw 00001111b ;If Q1 sees light, copy this pattern iorwf PORTB,f ;to the LEDs goto checkQ2 ;Check of Q2 sees light q1Off movlw 11110000b ;Turn off LEDs displaying Q1 state 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, copy this pattern iorwf PORTB,f ;to the LEDs goto checkQ1 ;Check Q1 again q2Off movlw 00001111b ;Turn off LEDs displaying Q2 state andwf PORTB,f goto checkQ1 ;Do it again org 1F00h ;Start of bootloader code area res 256 ;Reserve memory for bootloader end
Download the program into the CHRP and verify its operation.
How the program works
Q1 equ 0 ;Port A position of phototransistor Q1 Q2 equ 1 ;Port A position of phototransistor Q2
As in the Input program, equate statements are used to assign bit addresses to the Q1 and Q2 phototransistors. 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 later btfsc PORTA instructions.
initPorts ;Set Ports B and C to support CHRP digital circuitry. banksel ANSEL ;Switch register banks movlw 01010111b ;Enable Port B pull-ups, TMR0 internal movwf OPTION_REG ;clock, and 256 prescaler clrf ANSEL ;Set all PORTA pins to digital I/O clrf ANSELH ;Set all PORTB pins to digital I/O and banksel TRISA ;Switch register banks movlw 01101111b ;Setup LED and beeper outputs, and movwf TRISA ;make all other PORTA pins inputs
The initPorts subroutine now has to configure PORT A I/O as well. (Refer to the CHRP 2 schematic to determine the correct number and positions of input, output or multi-purpose pins.) The ANSEL and TRISA registers control the PORT A pins.
checkQ1 btfsc PORTA,Q1 ;Check if Q1 sees light goto q1Off ;If not, turn off Q1 pattern movlw 00001111b ;If Q1 sees light, copy this pattern iorwf PORTB,f ;to the LEDs goto checkQ2 ;Check of Q2 sees light q1Off movlw 11110000b ;Turn off LEDs displaying Q1 state andwf PORTB,f
In a manner 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, however. Unlike Input, in which pressing one button lights the LEDs until the other button is pressed, we now want the LEDs to go on only when the phototransistor is actually seeing light. Logical operations can be used to make this work.
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, we can use logical gate instructions to modify only the four bits corresponding to one set of 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).
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).
Likewise, 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
- Explain the purpose of the equate statements in these programs, as well as two advantages of using equates instead of statically-coded references.
- Explain the difference between the btfsc and btfss instructions.
- How many clock cycles does it take to execute a goto instruction? Why is this different from other instructions, like movlw, or bsf?
- When a PORT B switch is pressed, will the microcontroller input be low, or high? Why?
- When a light sensor connected to PORT A sees light, will its I/O pin be low, or high? Why?
Apply your skills
- 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.
- 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.
- Make a copy of the program in which the three switches S2, S3, and S4 each illuminate a different light pattern.
- 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.