If in Part 7 you output a signal through the pin, this time you read a signal through the pin. This involves receiving digital signals from buttons, switches, and sensors so the program can respond. The 8051 has a particular pitfall. Its ports are quasi-bidirectional, meaning you must write a 1 to the pin first before reading it as input to correctly receive external signals.
In this section, we read a button connected to P3.2 to control an LED on P1.0. Buttons are usually pull-up + active low (0 when pressed). Additionally, we verify on a PC edge detection logic that toggles each time the button is pressed, going beyond simply "lighting while pressed." Chattering (contact bounce) issues are treated in detail in Part 21.
Learning Objectives
- Explain why you need to write 1 before reading from a quasi-bidirectional port on the 8051.
- Read the input pin with
sbitand perform conditional branching. - Understand the signal of a low button (normally 1, 0 when pressed) with Pull-up/Active.
- Mirror the button state on an LED.
- Implement logic that detects only the "moment of press" using edge detection (1→0).
Target Environment
- Compiler: Keil C51.
- Device: AT89C52 (μVision simulator).
- Verification: μVision simulator (manipulate and observe pins in Port 1/Port 3 windows). Edge logic is tested on PC
gcc(pc_test.c).
Key Concepts
1) Quasi-bidirectional port — write 1 before reading
The 8051's port pins share an output latch and an input buffer. If you write 0 to the latch, the pin is strongly fixed to Low, and even if High is applied externally, it will not be read. Therefore, you must first write 1 to the pin you want to use as input to "release" the pin so that the external signal is reflected.
BTN = 1; /*Prepare for input: Set the pin to 1 (weak pull-up)*/
if (BTN == 0) { } /*Now it can be read when 0 is provided externally*/
Immediately after reset, the port is 0xFF (all 1s), so it is basically in a state that allows input. However, if you switch a pin that was being used as output to input, you must write 1 again. It's safer to make this a habit.
2) Reading Pins
Use sbit to name input pins and compare values.
sbit BTN = P3 ^ 2;
if (BTN == 0) { /*Pressure*/ }
You can also read the entire port and mask: if ((P3 & 0x04) == 0) (checking P3.2).
3) Pull-up + Active Low Button
The most common button circuit connects one side to GND and the other to the pin, and uses a pull-up resistor to keep it normally High.
| State | Pin Value |
|---|---|
| Not pressed | 1 (Pull-up) |
| Pressed | 0 (Connected to GND) |
So the determination of "pressed" is BTN == 0.
4) Button Mirroring vs Edge Detection
- Mirroring (Level): LED ON while pressed. Simply follows the current state.
- Edge Detection: Responds only at the moment pressed (1→0) (e.g., toggle). Compare the previous state with the current state.
if (prev_btn == 1 && btn == 0) { /*Falling edge = the moment it is just pressed*/
led = !led; /*Toggle*/
}
prev_btn = btn;
Edge detection is essential when "only once per press" action is needed (mirroring continues to respond while holding).
5) Chattering (Bounce) — A Preview
In reality, the contact of a button bounces (chatter) for a few milliseconds the moment it is pressed, switching back and forth between 0↔1 several times. This can cause multiple edge detections and multiple toggles. The solution (debouncing) is to either recheck after a short delay or recognize the input only when it is stable for a certain period, which is discussed in detail along with state machines in Chapter 21.
Creating a μVision Project
Project → New µVision Project…→ name itc51_gpio_in.- Device: Atmel → AT89C52. Include STARTUP.A51 (Yes).
- Add
main.cand then copy and paste the code below. Options for Target → Debug → Use Simulator.- Rebuild(F7) → Start Debug(Ctrl+F5).
- Open
Peripherals → I/O-Ports → Port 3(for input manipulation) andPort 1(for LED observation). In the simulator, toggle the P3.2 checkbox in the Port 3 window directly to simulate "button press".
Examples
Example 1 — main.c: Control LED with Button
Turn on the LED while the button is pressed (mirroring).
#include <reg52.h>
/*
*Lecture 08 Example 1 — Read button input and control LED.
*Target: AT89C52, Compiler: Keil C51.
*
*The button is connected to P3.2 as active low (0 when pressed, normally 1 with pull-up).
*The LED is connected to P1.0 as active low (ON when 0).
*
*Since the ports of the 8051 are 'quasi-bidirectional,' before reading as input, you must first write 1 to that pin
*so that the external signal can be read correctly.
*/
sbit BTN = P3 ^ 2; /*Input: button*/
sbit LED = P1 ^ 0; /*Output: LED*/
void main(void)
{
BTN = 1; /*Prepare input: write 1 before reading*/
while (1) {
if (BTN == 0) { /*Button pressed (0)*/
LED = 0; /* LED ON */
} else { /*Button released (1)*/
LED = 1; /* LED OFF */
}
}
}
Example 2 — pc_test.c: Toggle on Press (Edge Detection)
Provide a button state sequence and verify the logic that toggles the LED each time the button is pressed (1→0).
#include <stdio.h>
/*
*Lesson 08 Example 2 — Edge detection logic that toggles the LED every time the button is 'pressed' (PC verification).
*Button state 1 = released, 0 = pressed. Toggle only when comparing to the previous state and detecting 1→0 (falling edge).
*On an actual chip, BTN is read periodically to apply this logic.
*/
static unsigned char update_led(unsigned char led,
unsigned char prev_btn,
unsigned char btn)
{
if (prev_btn == 1 && btn == 0) { /*Falling edge = the moment it is pressed*/
led = (unsigned char)!led;
}
return led;
}
int main(void)
{
unsigned char seq[] = { 1, 1, 0, 0, 1, 0, 1, 0, 0, 1 }; /*Time-sequential input*/
int n = (int)(sizeof(seq) / sizeof(seq[0]));
unsigned char led = 0;
unsigned char prev = 1;
int t;
for (t = 0; t < n; t++) {
led = update_led(led, prev, seq[t]);
printf("t=%d btn=%u led=%u\n", t, (unsigned)seq[t], (unsigned)led);
prev = seq[t];
}
return 0;
}
Checking with Simulator
- During debug execution, click the P3.2 bit in the
Port 3window to change 0/1 and simulate pressing the "button." - Expected Behavior: When P3.2 is set to 0, P1.0 of
Port 1goes to 0 (LED ON), and when set back to 1, P1.0 goes to 1 (LED OFF).
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
t=0 btn=1 led=0
t=1 btn=1 led=0
t=2 btn=0 led=1
t=3 btn=0 led=1
t=4 btn=1 led=1
t=5 btn=0 led=0
t=6 btn=1 led=0
t=7 btn=0 led=1
t=8 btn=0 led=1
t=9 btn=1 led=1
Common Mistakes
Q. Even when I press the button, the input is always read as 0 (or always the same value).
A. This is a pitfall of the 8051 quasi-bidirectional port. If you have previously written 0 to that pin as an output, input will be blocked. Before reading, release the pin with BTN = 1;.
Q. I pressed it once, but the LED toggled multiple times.
A. ① Edge detection is used, not mirroring, and if there is bouncing, 1→0 may be detected multiple times → Use debouncing (chapter 21). ② If the loop is too fast, you might mistake multiple cycles "while holding the press" as toggles → switch to edge detection.
Q. if (P3 & 0x04 == 0) behaves strangely.
A. This is a priority pitfall. == comes before &, so 0x04 == 0 (false=0) is calculated first. Use parentheses: if ((P3 & 0x04) == 0). Or it is clearer with sbit and if (BTN == 0).
Q. What if there is no pull-up?
A. If an input pin is not connected anywhere, the value will float and read randomly. You must set a default level using an external Pull-up/Pulldown resistor or, if possible with the MCU, an internal pull-up. 8051 ports have a weak internal pull-up, keeping them at 1 normally.
Summary
- For the 8051 quasi-bidirectional port, write 1 to the pin before reading as input.
- Pull-up + active-low button: normally 1, 0 when pressed. The judgment is
BTN == 0. - Mirroring follows the current state, edge detection captures only the "moment of pressing."
- Edge detection compares the previous state with the current state (
prev==1 && cur==0). - Contact bounce (chattering) is resolved with debouncing (chapter 21).
Try it yourself
Solve the assignment below. The reference solution is shown below.