Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (9.59 MB, 687 trang )
The PIC18Fxx2: System Startup and Parallel Port IO
229
FIGURE 8.10 RCON register definition, bit values under
different reset conditions.3
Figure 8.11 shows a C program useful for experimenting with the different
PIC18 reset conditions. The code assumes the existence of a serial port connection
for displaying the results of the printf() statements and reading serial port input,
which is discussed in Chapter 9.
On main() entry, the program initializes the serial port, and then determines if
a power-on reset or watchdog timer reset has occurred. If the test POR == 0 is true,
power-on reset has occurred and the variable reset_cnt is initialized to zero. Observe that POR = 1 is included, as this bit is unaffected by other reset types, so it must
be set in order to detect other reset types. In addition, the persistent qualifier is
used for the declaration of reset_cnt, excluding it from being initialized by the
start-up code executed before main() entry. This means that reset_cnt is only initialized in the if{} body of the POR test, allowing this variable to keep a count of
the number of non-POR resets experienced. If the test TO == 0 is true, the watchdog
timer has expired, causing a reset. The watchdog timer is then disabled by clearing
3
Figure 8.10 adapted with permission of the copyright owner, Microchip Technology, Incorporated. All rights reserved. No
further reprints or reproductions may be made without Microchip Inc.’s prior written consent.
230
Microprocessors
void pcrlf (void) // print a newline to terminal
{
putch(0x0a); putch(0x0d);
}
persistent qualifier
prevents variable from being
initialized by start-up code prior to main
persistent char reset_cnt;
main(void){
int i;
char c;
Initializes serial interface, discussed in Chapter 9.
serial_init(95,1); // 19200 in HSPLL mode, crystal = 7.3728 MHz
pcrlf();
Detects power-on reset
if (POR == 0){
printf("Power-on reset has occurred."); pcrlf();
POR = 1; // setting to bit to 1 means that will
// remain a '1' for other reset types
reset_cnt = 0;
reset_cnt only initialized here
}
if (TO == 0) {
Detects watchdog timer expiration
SWDTEN = 0; // disable watchdog timer
printf("Watchdog timer reset has occurred."); pcrlf();
}
i = reset_cnt;
printf("Reset cnt is: %d",i);
pcrlf();
reset_cnt++;
reset_cnt increases in value for each non-POR reset
while(1) {
printf("'1' to enable watchdog timer"); pcrlf();
printf("'2' for sleep mode"); pcrlf();
printf("'3 ' for both watchdog timer and sleep mode"); pcrlf();
printf("Anything else does nothing, enter keypress: ");
c = getch();
putch(c);
pcrlf();
if (c == '1') SWDTEN = 1; // enable watchdog timer
Inline assembly used to insert SLEEP
else if (c == '2') asm("sleep");
else if (c == '3') {
instruction
SWDTEN = 1;
// enable watchdog timer
asm("sleep");
}
}
}
ON THE CD
FIGURE 8.11 Program (reset.c) for experimenting with reset types.
the SWDTEN bit, which is bit 0 of the WDTCON register. After these two reset
checks, the reset_cnt variable is incremented, and an infinite while(1){} loop is entered. A choice menu is printed within the loop, giving the user the option of enabling the watchdog timer, entering sleep mode, or enabling the watchdog timer,
and then entering sleep mode. Choice #1 enables the watchdog timer via the statement SWDTEN = 1, which sets the SWDTEN bit. If nothing else is done after this
choice is made, the WDT expires after approximately two seconds assuming the
WDT postscaler is set to 128:1. This causes a reset, and the message “Watchdog
timer reset has occurred” is printed by the code that detects this condition. Choice
#2 enters sleep mode by executing the SLEEP instruction, which is inserted in the C
code by the statement asm(“SLEEP”). This is known as inline assembly, and allows assembly language instructions to be specified directly within C code. After this
choice is made, the only method of waking the processor is to press the MCLR#
reset button, or to cycle power (assuming the schematic of Figure 8.4). Choice #3
first enables the watchdog timer, and then enters sleep mode. Two seconds later,
the watchdog timer expires, waking the processor from sleep mode, and causing execution to resume at the instruction following the SLEEP instruction. Since this is at
The PIC18Fxx2: System Startup and Parallel Port IO
231
the end of the loop, a jump is made back to the beginning of the loop, and the
choice menu is reprinted. If no choices are made, the watchdog timer expires again
after another two seconds, causing a device reset. Extending this code to check for
other reset conditions, or generating other types of reset is left for the review problems at the end of the chapter. Figure 8.12 shows terminal output from testing the
code of Figure 8.11.
Power-on reset, so reset_cnt is 0
Pressed reset button to generate
MCLR# reset, so
reset_cnt increments
Typed “1”, WDT enabled.
Menu reprinted,
but no input typed,
WDT reset occurs, WDT disabled.
reset_cnt increments.
Typed “2”, so sleeps.
Must press reset to wakeup,
incrementing reset_cnt
Typed “3”, so WDT enabled
and sleep.
WDT expires, waking PIC.
Loop and re-display menu.
No input typed, WDT reset occurs,
WDT disabled.
reset_cnt increments.
Cycle Power
Power-on reset, so reset_cnt is 0
FIGURE 8.12 Testing the reset.c program.
8.9 PARALLEL PORT OPERATION
Parallel port IO refers to groups of pins whose values can be read or written as a
group via special function registers. On the PIC18F242, three parallel ports are
available: PORTA, PORTB, and PORTC. Two additional ports, PORTD and
232
Microprocessors
PORTE, are available on larger pin count versions of the PIC18Fxx2. Only PORTA,
PORTB, and PORTC are discussed in this book, as the PIC18F242 is the target device for the interfacing examples. Each parallel port has three special function registers associated with it: PORTx/TRISx/LATx, where x is A, B, or C. The TRISx
register is used to configure each bidirectional port bit as either an input or output.
A “1” in a TRISx register bit configures the associated PORTx register bit to be an
input, while a “0” configures the associated PORTx register bit to be an output. The
LATx register is the data latch used to drive the port pins when it is configured as
an output. Reading PORTx returns the values of the external pins, while reading
LATx reads the data latch value. Writing to either LATx or PORTx writes to the
data latch of the associated port. Please note that reading LATx may not return the
same value as reading PORTx. If the port is configured as an input, reading LATx
returns the last value written to LATx or PORTx, while reading PORTx returns the
value of the external pin. If the port is configured as an output, reading LATx will
normally return the same value as reading PORTx because the data latch is driving
the external pin. However, if there is another external driver that is clashing with
the port driver, or if the port driver itself is a special case like an open drain output
(explained later in this section), LATx and PORTx may return different values
when read. A write to a port bit configured as an input changes the value of the output data latch (LATx), but does not change the value seen on the external pin whose
value is set by whatever is driving that pin.
PORTB
Figure 8.13 shows the internal logic of port pins RB[2:0]. Each port has slightly different features, and the PIC18Fxx2 datasheet should be consulted for a complete
description of each port’s capabilities. This diagram clearly shows the differences
between the PORTB, LATB, and TRISB special function registers. The TRISA register controls port direction, PORTB represents the state of the external pins, and
LATB holds the data used to drive the port pins.
Figure 8.13 shows that the TRISB[y] bit is connected to the enable input of the
tristate buffer that is on the output data latch that drives the PORTB pin. If
TRISB[y] is “0” (the port bit is an output), the tristate buffer is enabled, and simply passes its input value to its output. If TRISB[y] is “1” (the port bit is an input),
the tristate buffer is disabled, and its output becomes high impedance, whose state
is commonly designated as “Z”. Figure 8.14 shows that one can think of the tristate
buffer enable as controlling a switch on the output of the buffer; the switch is closed
when the enable is asserted, allowing the buffer to drive its output. The switch is
open when the enable is negated, causing the output to float (also known as high
impedance). Note that a port bit cannot be both an input and an output simultaneously; it is either one or the other based on the setting of the TRISB[y] bit.
The PIC18Fxx2: System Startup and Parallel Port IO
233
The lower part of Figure 8.14 shows data flow on a bidirectional data link using one
wire; data is either flowing from CPU_a to CPU_b or vice versa, but never both directions at the same time over one wire if voltage mode signaling is used.
Vdd
RBPU(1)
P Weak Pullup
RD LATB
Data Bus
WR LATB
or PORTB
Data Latch
D
Q
IO Pin
CK
TRIS Latch
D
Q
WR TRISB
CK
TTL Input
Buffer
RD TRISB
Q
RD PORTB
D
EN
RD PORTB
Schmitt Trigger Input
RBn/INTn
(1) To enable weak pull-ups, set the appropriate TRIS bit(s) and clear the
RPBU bit (INTCON2[7])
Figure redrawn by author from PIC18Fxx2 datasheet (DS39564B), Microchip Technology Inc.
FIGURE 8.13
Port RB2:RB0 pin internals.4
In Figure 8.13, if a port is configured as an input and the RBPU bit (INTCON2[7]) is “0”, the weak pullup is enabled; observe that this enables the weak
pullup for all PORTB pins configured as inputs. The weak pullup is implemented
as a high-resistance P-transistor; when enabled, the gate of this transistor is “0”,
turning it on. The weak pullup is useful for eliminating the need for an external
pullup resistor on an input switch (see Figure 8.15). The term weak is used because
the resistance is high enough that an external driver can overpower the pullup resistor and pull the input to near ground, producing a “0” input. PORTB is the only
port with internal weak pullups. Note that a pushbutton switch configured as a
low-true input switch must have some form of pullup resistor, either internal or external, or the input floats when the pushbutton is not pressed, allowing the input to
be read as either “1” or “0”.
The PORTB pin logic of Figure 8.13 shows both a TTL buffer and a Schmitt
trigger buffer used for driving internal signals from the external pin. Figure 8.16
shows the Vin/Vout characteristics of these two input buffer types. Observe that for
a Schmitt trigger, a low-to-high transition must become close to Vdd before the
4
Figure 8.13 adapted with permission of the copyright owner, Microchip Technology, Incorporated. All rights reserved. No
further reprints or reproductions may be made without Microchip Inc.’s prior written consent.
234
Microprocessors
Tristate buffer with low-true enable
Y=A
A
A
Y=A
EN=0
EN=0
Y = high-z
A
A
Y = high-z
EN=1
EN=1
Tristate buffer with high-true enable
A
Y=A
A
Y=A
EN=1
EN=1
Y = high-z
A
A
Y = high-z
EN=0
EN=0
data direction
high-z
RBn
CPU_a
1 or 0
RBn
TRISn=1
CPU_b
TRISn=0
data direction
1 or 0
RBn
CPU_a
high-z
RBn
CPU_b
TRISn=1
TRISn=0
FIGURE 8.14 Tristate buffer operation and
bidirectional IO.
Vdd
RBPU = 0
0
P
TRISBn = 1
Vdd
No External
Pullup
Needed
Must always have
some form of pullup,
or input floats when
switch is not pressed.
Weak Pullup
Enabled
IO Pin
FIGURE 8.15 Weak pullup operation.
buffer trips; conversely, a high-to-low transition must be close to ground before the
buffer trips. This hysteresis in the buffer action provides extra immunity against
noise on the input signal, and is also used for transforming slowly changing inputs
into signals with fast rise and fall times. Schmitt triggers consume more power than
The PIC18Fxx2: System Startup and Parallel Port IO
235
TTL inputs, and thus are only used on inputs that either provide internal interrupts
(such as RB0, RB1, RB2, MCLR#) or drive the clock signal of an internal subsystem.
TTL Input (Vdd = 5 V)
Vout
Schmitt Trigger (Vdd = 5 V)
Vout
Voh
Voh
approx
1.0 V
approx
4.0 V
Vin
VIL = 0.8 V
(max)
Vin
VIH = 2.0 V
(min)
Vout
Vin
Vin
Vout
FIGURE 8.16 TTL buffer versus Schmitt trigger buffer PORTA.
PORTA pins are shared with the analog-to-digital converter subsystem, and the
default configuration is for these pins to be analog inputs, causing a “0” to always
be read for those inputs when reading special function register PORTA. To configure all PORTA pins for digital operation, the statement ADCON1 = 0x06 is required;
other values written to the ADCON1 register provide different combinations of
analog/digital inputs (see the PIC18Fxx2 datasheet and Chapter 12 for more details). Pins RA0:RA3 and RA5 provide bidirectional IO, CMOS logic levels for output, and use a TTL buffer for input. Pin RA4 is different; it is an open-drain output
configuration, which can either drive its output low or leave it floating (see Figure
8.17). The term open-drain is used because the drain terminal of the N pulldown
transistor is open; there is no P pullup transistor. The right-hand side of Figure 8.17
shows a common use of open-drain outputs, which is to implement wired logic. The
three RA4 outputs of the CPUs are wired directly together, and pulled up to Vdd
through an external resistor. This is a low-true wired-or configuration because the
BUSY line is asserted low, turning on the LED, whenever CPU_a or CPU_b or
CPU_c asserts its RA4 output low. If all RA4 outputs are floating, the BUSY line is
high, and the LED is turned off. You cannot connect normal CMOS outputs directly together like this because there will be a clash when one output drives high,
and another output drives low, resulting in an uncertain voltage level on the wire.
A common mistake is to use RA4 as a normal output that must provide both high
and low voltage levels. This is difficult to debug, as a “1” written to RA4 causes the
output to float, which can be read by the receiving logic (if it is a CMOS input) as
236
Microprocessors
either a “1” or “0”. Thus, the system will fail intermittently, which is one of the most
difficult types of hardware problems to debug.
Vdd
No P pullup
(open-drain)
RD LATA
Busy
LED
Q
Data Bus
D
WR LATA
or PORTA
Q
N
CPU_a
IO Pin
RA4
CK
Data Latch
D
WR TRISA
CPU_b
Q
RA4
Schmitt Trigger
Input
CK
TRIS Latch
Busy
line
CPU_c
RA4
RD TRISA
Q
D
EN
RD PORTA
TMR0 Clock Input
Wired OR (low-true) :
if CPU_a OR
CPU_b OR
CPU_c asserts RA4 low,
then BUSY is asserted
(LED on).
Figure redrawn by author from PIC18Fxx2 datasheet (DS39564B), Microchip Technology Inc.
FIGURE 8.17 Pin RA4, open-drain output.5
PORTC
PORTC pins are bidirectional, with CMOS output drivers. In the PIC18F242, the
PORTC output pins are shared with other subsystem functions that are used in this
book’s interfacing examples. As such, the examples in this book use PORTB and
PORTA pins for any parallel IO needs. Details on the PORTC parallel port logic, as
well as PORTD and PORTE can be found in the PIC18Fxx2 datasheet.
Sample Question: Write C code that configures pins RB0, RB2, RB4 as outputs, and the
rest of PORTB as inputs.
Answer: Recall that if a TRISx bit is a “1”, the port pin functions as an input;
a “0” configures the port as an output. This configuration can be accomplished in two ways; either by a single assignment to the TRISB register or by
individual TRISB bit assignments. The statement TRISB = 0xEA (0b11101010)
works, as this clears bits TRISB4, TRISB2, TRISB0 to “0” and sets the rest as “1”.
If the TRISB configuration occurs after power-on reset (POR), then the statements:
TRISB4 = 0; TRISB2 = 0; TRISB0 = 0;
5
Figure 8.17 adapted with permission of the copyright owner, Microchip Technology, Incorporated. All rights reserved. No
further reprints or reproductions may be made without Microchip Inc.’s prior written consent.
The PIC18Fxx2: System Startup and Parallel Port IO
237
also works, as all bits in the TRISB register are reset to “1” after power-on reset
(see Appendix A for the settings of all special function register bits after POR).
Obviously one statement is more efficient than multiple statements, but if you
are new to microcontrollers it is suggested that you use the coding style that
you understand the best.
8.10 LED/SWITCH IO AND STATE MACHINE PROGRAMMING
A common input device is a momentary pushbutton switch. Figure 8.18 shows a
pushbutton switch connected to RB6. When the pushbutton is released (not
pressed) the RB6 input reads as “1”; when the pushbutton is pressed the RB6 input
reads as “0”.
Vdd
10 kΩ
RB6
Input
Switch
PIC
RB4
470 Ω
Count number of
switch presses.
ON THE CD
main(){
int i;
TRISB = 0xEF;
RB4 = 0;
i = 0;
while (1) {
if (!RB6) {
//switch pressed
//increment i
i++;
}
}
}
a. Incorrect, variable i is
incremented as long as
the switch is pushed, which
could be a long time!
main(){
int i;
TRISB = 0xEF;
RB4 = 0;
i = 0;
while (1) {
// wait for press
while (RB6); //loop (1)
DelayMs(30); //debounce
// wait for release
while (!RB6); //loop (2)
DelayMs(30); // debounce
i++;
}
b. Correct, loop 1 executed while
switch is not pressed. Once pressed,
becomes trapped in loop 2 until
switch is released, at which point
variable i is incremented.
FIGURE 8.18 LED/switch IO example #1.
Assume we would like to count the number of pushbutton presses and releases;
each press and release counts as one switch activation. A common mistake is shown
in code segment (a), which increments a variable i when RB6 returns “0”. The
problem with this code is that the variable i is not only incremented when the
pushbutton is pressed, but is also incremented for as long as the pushbutton is held
down. Human reaction times on pushbutton switches are measured in tens of milliseconds, so i is incremented many times for each pushbutton activation!
Code segment (b) shows a correct solution to this problem. When the
while(1){} loop is entered, the code becomes trapped in the loop while(RB6){},
238
Microprocessors
which loops waiting for the pushbutton to be pressed. Once the pushbutton is
pressed, the code is then trapped in the loop while(!RB6){}, waiting for the pushbutton to be released. Upon release, the variable i is incremented and the code becomes trapped in the loop while(RB6){} again. Thus, i is incremented only once for
each press and release of the pushbutton. The DelayMs(30) function calls are included after each change in the input switch status to ensure that all switch bounce
has settled before continuing. Mechanical switch bounce can produce multiple
pulses when a pushbutton is activated. The required delay is a function of the mechanics of the switch bounce, which can only be seen by using an oscilloscope to
capture the switch waveform or from a manufacturer data sheet. The value of 30
ms used here should be adequate for common momentary switches. This is a simple method for debouncing a switch with the drawback that the CPU cycles spent
in the software delay loop are wasted. Alternate methods for switch debouncing are
presented in Chapter 10.
Another example of LED/switch IO is given in Figure 8.19, in which the goal is
to toggle the LED each time the pushbutton is pressed and released. Code segment
(a) is incorrect, as the if{!RB6} statement only turns on the LED as long as the
pushbutton is held down. Code segment (b) is correct, as the two statements
while(RB6){}, while(!RB6){} wait for a press and release before turning on the LED;
then the next two statements while(RB6){}, while(!RB6){} wait for a subsequent
press and release before turning off the LED. The bit assignments LB4 = 1, LB4 = 0
can be used instead of RB4 = 1, RB4 = 0, as this also writes to the port data latch register. For the PICC-18 compiler, data latch bits can also be referenced using the
LAT prefix (e.g., LATB4).
Sample Question: Assume the same LED/switch configuration of Figure 8.19. Write a
while(1){} loop that blinks the LED twice for each switch press and release. Assume
the port is already configured.
Answer: One solution is shown in Listing 8.3.
LISTING 8.3
Solution A.
while(1){
while(RB6); DelayMs(30);
while(!RB6); DelayMs(30);
//blink twice
RB4 = 1; DelayMs(200);
RB4 = 0; DelayMs(200);
RB4 = 1; DelayMs(200);
RB4 = 0; DelayMs(200);
}
//wait for press
// wait for release
//turn
//turn
//turn
//turn
on, delay for blink
off, delay for blink
on, delay for blink
off, delay for blink