Embedded C track

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

GPIO Output — Blinking an LED

Port output and active-low wiring

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

The "Hello, World" of embedded systems is blinking an LED. Sending an output to a single pin to turn a light on and off seems simple, but it contains the core of GPIO output: what value to write to the port to turn on the LED (wiring method), how long to wait for it to be visible (delay), and how to create patterns with multiple LEDs (bit masking).

In this chapter, we repeatedly write to the port SFR learned in chapter 6 within an infinite loop to create a running light with 8 LEDs connected to P1. Many 8051 boards attach LEDs as active low (ON when the pin is 0), so having a sense of "making only the bits you want ON to 0" is important. Pin waveforms are observed using the μVision Logic Analyzer.

Learning Objectives

  • Control LEDs with GPIO output and explain the differences in active Low/High wiring.
  • Change port values inside while(1) to make the LEDs blink.
  • Understand the behavior and limitations (inaccuracy) of software delay loops.
  • Create running light patterns using bit masks ~(1u << pos).
  • Observe port pin toggling with μVision Logic Analyzer.

Target Environment

  • Compiler: Keil C51.
  • Device: AT89C52 (μVision simulator).
  • Verification: μVision simulator (Logic Analyzer / Port 1 window). Pattern logic is PC gcc(pc_test.c).

Key Concepts

1) LED Wiring — Active Low vs Active High

LEDs only turn on when current flows. The "turning value" varies depending on how the pins and LEDs are connected.

Method Wiring LED ON Conditions
Active High Pin → Resistor → LED → GND Pin = 1
Active Low VCC → LED → Resistor → Pin Pin = 0

The 8051 can carry more current when the port outputs 0 (sink), so boards often use active low. This example also assumes active low: Only the bit you want to turn on is 0.

2) Turning on only one LED — ~(1u << pos)

To turn on only one LED pos in the active row, only that bit is 0, and the rest are written to the port as the single values.

pos = 0 → ~(1<<0) = ~0x01 = 0xFE = 11111110   (Only LED0 ON)
pos = 3 → ~(1<<3) = ~0x08 = 0xF7 = 11110111   (Only LED3 ON)

Increase the pos to 0→7, and repeating this value will cause the light to flow one bar at a time.

3) Software Delay

The MCU is so fast that without delay, the LEDs don't blink but appear to be all lit blurry. The simplest delay is the empty loop.

static void delay(unsigned int count)
{
    unsigned int i;
    for (i = 0; i < count; i++) { }
}

This method is simple but not accurate (timing varies depending on clock and optimization). The exact time is set by 9 hardware timers. For now, observable levels in the simulator are sufficient.

note: The compiler can optimize empty loops. If precision delay is needed in actual code, use a timer; if you must keep an empty loop, set the loop variable to volatile.

4) Multiple LED Patterns — Mask Combinations

You can turn on multiple LEDs at the same time by OR-ing multiple bits together (if it's active low, invert that bundle).

unsigned int mask = (1u << pos) | (1u << (pos + 1));  /*Adjacent 2*/
P1 = (unsigned char)~mask;                            /*Two LEDs ON*/

5) Port Width and Type Casting

~(1u << pos) is the result of unsigned int (16-bit on the 8051), so when putting it into an 8-bit port, cast it to (unsigned char) to make the intention clear. The upper byte will be discarded anyway, but casting reduces warnings and shows the intention of "using only 8 bits."

Creating a μVision Project

  1. Project → New µVision Project… → name it c51_gpio_out.
  2. Device: Atmel → AT89C52.
  3. Include STARTUP.A51 (Yes).
  4. After adding main.c, copy and paste the code below.
  5. Options for Target → Debug → Use Simulator.
  6. Rebuild(F7) → Start Debug(Ctrl+F5).
  7. Viewing pin waveforms: instead of Debug → Debug Settings, open View → Analysis Windows → Logic Analyzer during debugging and add P1.0~P1.7 in Bit mode from Setup. Or directly check the bit states using Peripherals → I/O-Ports → Port 1.

Examples

Example 1 — main.c: Running Light

Turn on only one LED and move its position from 0 → 7 in an infinite loop.

#include <reg52.h>

/*
 *Lesson 7 Example 1 — Running Light.
 *Target: AT89C52, Compiler: Keil C51.
 *
 *Assume 8 LEDs are connected to P1. Many 8051 boards
 *use 'active low' for LEDs (LED ON when pin is 0). So to turn on only one LED,
 *write a value to the port where only that bit is 0 and the rest are 1: ~(1 << pos).
 */

#define LED_PORT  P1

/*Software delay (not precise). Keep it short for simulator observation.
 *Exact time delay is covered in Lesson 9 (Timer).*/
static void delay(unsigned int count)
{
    unsigned int i;
    for (i = 0; i < count; i++) {
        /*Just passing time*/
    }
}

void main(void)
{
    unsigned char pos = 0;

    while (1) {
        /*Active low: only the pos-th bit is 0 → only that LED is ON*/
        LED_PORT = (unsigned char)~(1u << pos);
        delay(20000);

        pos++;
        if (pos >= 8) {
            pos = 0;          /*When it reaches the end, return to the starting position*/
        }
    }
}

Example 2 — pc_test.c: Pattern Sequence Verification

Check the port values (0xFE → 0xFD → … → 0x7F) that the running light generates on the PC.

#include <stdio.h>

/*
 *Lesson 07 Example 2 — The sequence of port values for flowing lights is checked on the PC.
 *Since it is active low, 'pos LED ON' = only that bit 0 = ~(1 << pos).
 */

static void print_bin8(unsigned char v)
{
    int i;
    for (i = 7; i >= 0; i--) {
        putchar(((v >> i) & 1u) ? '1' : '0');
    }
}

int main(void)
{
    unsigned char pos;

    for (pos = 0; pos < 8; pos++) {
        unsigned char port = (unsigned char)~(1u << pos);
        printf("pos %u: ", (unsigned)pos);
        print_bin8(port);
        printf(" (0x%02X)\n", port);
    }

    return 0;
}

Checking with Simulator

  • Add P1.0~P1.7 to the Logic Analyzer and run (F5), and you will see a staircase waveform where each pin sequentially drops Low (= LED ON).
  • Or in the Port 1 window, you can check that the checked bit values rotate in the order 0xFE → 0xFD → 0xFB …
  • Expected behavior: The LED flows one by one from 0 to 7, and when it reaches the end, it repeats from 0.

Verification on a PC

gcc pc_test.c -o pc_test && ./pc_test

Expected output (stdout):

pos 0: 11111110 (0xFE)
pos 1: 11111101 (0xFD)
pos 2: 11111011 (0xFB)
pos 3: 11110111 (0xF7)
pos 4: 11101111 (0xEF)
pos 5: 11011111 (0xDF)
pos 6: 10111111 (0xBF)
pos 7: 01111111 (0x7F)

Common Mistakes

Q. I wrote a 1 to the port, but the LED doesn't turn on.

A. If the board is active low, 1 is OFF. You need to write 0 to turn it on. If the wiring is active high, it’s the opposite. If you don’t know which it is, try both or check the circuit diagram. This example assumes active low.

Q. The LED isn’t blinking and just stays on.

A. There is no delay or the delay is too short for the human eye to notice. Increase the delay() value. In the simulator, execution speed may look different, so it’s accurate to check the toggle itself using the Logic Analyzer’s time axis.

Q. The empty delay loop disappears due to optimization.

A. The compiler can remove a "do-nothing loop." Declare the loop variable as volatile (e.g., volatile unsigned int i;), or if precise delay is needed, use a timer (Lesson 9).

Q. The LED stops when pos exceeds 8.

A. When pos becomes 8, 1<<8 goes out of the port range. Wrap it so that it only goes from 0~7 (as in the example) using if (pos >= 8) pos = 0;.

Summary

  • The value to turn on the LED varies depending on wiring — 0 is ON for active low.
  • To turn on a single LED: use ~(1u << pos) for active low.
  • Use while(1) + delay to create Blinking/Pattern. Software delay is inaccurate.
  • Control multiple LEDs by ORing the masks together (invert if necessary).
  • Observe pin waveforms with the Logic Analyzer and bit values with the Port window.

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_out/main.cC
#include <reg52.h>

/*
 *Lesson 7 Example 1 — Running Light.
 *Target: AT89C52, Compiler: Keil C51.
 *
 *Assume 8 LEDs are connected to P1. Many 8051 boards
 *use 'active low' for LEDs (LED ON when pin is 0). So to turn on only one LED,
 *write a value to the port where only that bit is 0 and the rest are 1: ~(1 << pos).
 */

#define LED_PORT  P1

/*Software delay (not precise). Keep it short for simulator observation.
 *Exact time delay is covered in Lesson 9 (Timer).*/
static void delay(unsigned int count)
{
    unsigned int i;
    for (i = 0; i < count; i++) {
        /*Just passing time*/
    }
}

void main(void)
{
    unsigned char pos = 0;

    while (1) {
        /*Active low: only the pos-th bit is 0 → only that LED is ON*/
        LED_PORT = (unsigned char)~(1u << pos);
        delay(20000);

        pos++;
        if (pos >= 8) {
            pos = 0;          /*When it reaches the end, return to the starting position*/
        }
    }
}
src/c51_gpio_out/pc_test.cC
#include <stdio.h>

/*
 *Lesson 07 Example 2 — The sequence of port values for flowing lights is checked on the PC.
 *Since it is active low, 'pos LED ON' = only that bit 0 = ~(1 << pos).
 */

static void print_bin8(unsigned char v)
{
    int i;
    for (i = 7; i >= 0; i--) {
        putchar(((v >> i) & 1u) ? '1' : '0');
    }
}

int main(void)
{
    unsigned char pos;

    for (pos = 0; pos < 8; pos++) {
        unsigned char port = (unsigned char)~(1u << pos);
        printf("pos %u: ", (unsigned)pos);
        print_bin8(port);
        printf(" (0x%02X)\n", port);
    }

    return 0;
}

Assignment

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


Problem 1 — Alternating Blinking of Two LEDs

  • File name: homework_01/main.c
  • Key concepts: Port bit writing, while(1), delay
  • Platform: C51 (AT89C52)

Requirements

  • Keep P1.0 (LED0) and P1.1 (LED1) always in opposite states and blink alternately (active low).
  • Swap roles after each cycle delay (LED0 ON ↔ LED1 ON).

Expected Operation (Simulator)

  • P1.0 and P1.1 on Port 1 alternate between 0 (ON)/1 (OFF).
  • When LED0 is on, LED1 is off, and in the next cycle, the opposite.

Hint

  • Declare sbit LED0 = P1^0; sbit LED1 = P1^1; globally.
  • If you set one to state and the other to !state, the opposite state is always guaranteed.
  • Include delay() so it can be observed.

Problem 2 — Two-step Flowing Light (PC)

  • File name: homework_02/main.c
  • Key concepts: Multi-bit mask, active low inversion
  • Platform: Common (PC gcc)

Requirements

  • Turn on two adjacent LEDs simultaneously and output the port value with pos moved from 0 → 6 (active low).
  • Port value = ~((1<<pos) | (1<<(pos+1))).
  • The output format pos N: 0xNN must match the "expected output" character by character.

Expected output

pos 0: 0xFC
pos 1: 0xF9
pos 2: 0xF3
pos 3: 0xE7
pos 4: 0xCF
pos 5: 0x9F
pos 6: 0x3F

Hint

  • Mask combines two bits with OR. Since it is active low, the inversion (~) is used for the port.
  • pos ranges only from 0 to 6 (7 would go beyond the port with 1<<8).

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) — Two LEDs alternate flashing
 *
 *Key Points:
 *- When one side is toggled so that P1.0 and P1.1 are always in opposite states, the other side
 *Align in the opposite way (assuming active low).
 *- Toggle → delay → repeat inside while(1).
 *
 *Common mistake:
 *- When the delay is skipped, it is too fast in the simulator to observe.
 *- Toggling two LEDs separately results in them being in the same state at the same time.
 *
 *Expected behavior (simulator): P1.0 and P1.1 on Port 1 turn ON/OFF alternately.
 *When LED0 is ON, LED1 is OFF, and the next cycle is the opposite.
 */

sbit LED0 = P1 ^ 0;
sbit LED1 = P1 ^ 1;

static void delay(unsigned int count)
{
    unsigned int i;
    for (i = 0; i < count; i++) {
    }
}

void main(void)
{
    bit state = 0;     /*If 0, LED0 ON; if 1, LED1 ON*/

    while (1) {
        /*Active Low: 0 = ON*/
        LED0 = state;          /* state=0 → LED0 ON */
        LED1 = (bit)!state;    /*Always opposed*/
        delay(30000);

        state = (bit)!state;   /*Role rotation in the next cycle*/
    }
}
homework_02/main.cC
#include <stdio.h>

/*
 *Correct Answer 2 (Pure Logic, PC) — A pattern where a two-block light flows
 *
 *Key Points:
 *- Turn on two adjacent LEDs at the same time: mask (1<<pos) | (1<<(pos+1)).
 *- Since it's active low, the port value is the inverse of that mask: ~mask.
 *
 *Common mistake:
 *- Cases where forgetting the inversion (~) causes LEDs that should turn on and off to be reversed.
 *- Cases where pos exceeds 6, pointing outside the port (1<<8) (only 0~6 allowed).
 */
int main(void)
{
    unsigned char pos;

    for (pos = 0; pos < 7; pos++) {
        unsigned int mask = (1u << pos) | (1u << (pos + 1));
        unsigned char port = (unsigned char)~mask;
        printf("pos %u: 0x%02X\n", (unsigned)pos, port);
    }

    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