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
Project → New µVision Project…→ name itc51_gpio_out.- Device: Atmel → AT89C52.
- Include STARTUP.A51 (Yes).
- After adding
main.c, copy and paste the code below. Options for Target → Debug → Use Simulator.- Rebuild(F7) → Start Debug(Ctrl+F5).
- Viewing pin waveforms: instead of
Debug → Debug Settings, openView → Analysis Windows → Logic Analyzerduring debugging and addP1.0~P1.7in Bit mode fromSetup. Or directly check the bit states usingPeripherals → 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.