Embedded C track

Lesson 08 · Part 2: 8051 (Keil C51 / AT89C52)

GPIO Input — Buttons & Switches

Reading quasi-bidirectional ports

Part 2: 8051 (Keil C51 / AT89C52)7 min readFree lesson

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 sbit and 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

  1. Project → New µVision Project… → name it c51_gpio_in.
  2. Device: Atmel → AT89C52. Include STARTUP.A51 (Yes).
  3. Add main.c and then copy and paste the code below.
  4. Options for Target → Debug → Use Simulator.
  5. Rebuild(F7) → Start Debug(Ctrl+F5).
  6. Open Peripherals → I/O-Ports → Port 3 (for input manipulation) and Port 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 3 window to change 0/1 and simulate pressing the "button."
  • Expected Behavior: When P3.2 is set to 0, P1.0 of Port 1 goes 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.

Example files

The runnable files used in this lesson. Copy them into your own project and experiment.

src/c51_gpio_in/main.cC
#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 */
        }
    }
}
src/c51_gpio_in/pc_test.cC
#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;
}

Assignment

Question 1 is for C51 (AT89C52, μVision simulator), and Question 2 is for pure logic (PC gcc verification).


Problem 1 — Controlling 8 LEDs with a Button

  • File name: homework_01/main.c
  • Key concepts: Prepare input pin (write 1), branching, write to entire port
  • Platform: C51 (AT89C52)

Requirements

  • Read the button (active low) connected to P3.3.
  • If the button is pressed (0), turn all 8 LEDs on P1 ON (active low → P1 = 0x00), if released (1), turn all off (P1 = 0xFF).

Expected Operation (Simulator)

  • In Port 3, if P3.3 is 0, Port 1 is 0x00 (all LEDs ON), if 1, 0xFF (all OFF).

Hint

  • Prepare input with BTN = 1; before reading.
  • Declare sbit BTN = P3 ^ 3; globally.

Problem 2 — Counting Button Presses (PC)

  • File name: homework_02/main.c
  • Key concepts: Edge detection (1→0), counting
  • Platform: Common (PC gcc)

Requirements

  • From the button state sequence {1, 1, 0, 1, 0, 0, 1, 0} (chronological order, 1=ttem/0=pressed), count the number of presses (number of falling edges 1→0).
  • Output one line of presses = N. The format must match the "expected output" exactly, character by character.

Expected output

presses = 3

Hint

  • Count only the moments when prev==1 && cur==0 occurs, not the duration btn==0 is pressed (even if held long, count as once).
  • Initialize the previous state prev to 1 and update each step.

Check your answers

After solving it yourself, compare it with the reference solution below. The solution files include comments on key points and common mistakes.

Show the reference solution

Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.

homework_01/main.cC
#include <reg52.h>

/*
 *Answer 1 (C51, AT89C52) — Turn on all 8 LEDs at once with a button
 *
 *Key Points:
 *- Input pin (P3.3) is set to 1 before reading to prepare for input.
 *- If the button is pressed (0), set all of P1 to 0x00 (all ON); if released (1), set to 0xFF (all OFF).
 *
 *Common mistake:
 *- If 1 is not written to the input pin, it is always read as 0 (quasi-bidirectional port trap).
 *- Confusing active low may cause the operation to be reversed.
 *
 *Expected behavior (simulator): When P3.3 is set to 0 (button pressed), all 8 LEDs on P1
 *turn ON; when set to 1, all turn OFF.
 */

sbit BTN = P3 ^ 3;

void main(void)
{
    BTN = 1;                 /*Input preparation*/

    while (1) {
        if (BTN == 0) {      /*Pressure*/
            P1 = 0x00;       /*Active low: All ON*/
        } else {
            P1 = 0xFF;       /*All OFF*/
        }
    }
}
homework_02/main.cC
#include <stdio.h>

/*
 *Answer 2 (Pure logic, PC) — Counting button presses
 *
 *Key Points:
 *- A 'press' is the falling edge where the state changes from 1→0. Only that moment is counted.
 *- Even if you keep holding it down (0,0,0...), it should be counted as 1, so it is determined by the edge.
 *
 *Common mistake:
 *- Counting the times when btn==0 as they are. In that case, holding it down for a long time will count multiple times.
 *Only count the exact moment when it is 'previously 1, currently 0'.
 */
int main(void)
{
    unsigned char seq[] = { 1, 1, 0, 1, 0, 0, 1, 0 };  /*Time-sequential input*/
    int n = (int)(sizeof(seq) / sizeof(seq[0]));
    unsigned char prev = 1;
    int presses = 0;
    int t;

    for (t = 0; t < n; t++) {
        if (prev == 1 && seq[t] == 0) {   /*Falling edge*/
            presses++;
        }
        prev = seq[t];
    }

    printf("presses = %d\n", presses);   /* 3 */

    return 0;
}

Learn this lesson with a mentor

Work through the Embedded C track on your own, or join a guided cohort at our Pitampura campus and online.

Call NowEnroll Now