>
section 6 of 1311 min read

6. The 8051 Microcontroller: The Workhorse

If the 8086 is the great-grandparent of the laptop CPU, the 8051 is the great-grandparent of the embedded controller. Designed by Intel in 1980, the 8051 is still in production from a dozen manufacturers (Atmel/Microchip AT89S51, Silicon Labs, NXP, Infineon, Dallas/Maxim DS89C, and many Chinese clones) 45 years later, and is found in millions of microwave ovens, washing machines, USB hubs, keyboards, RFID readers, simple toys, industrial controllers. The Atmel AT89S51 is the canonical 8051 in modern hobby/teaching labs because it speaks SPI for programming and works on a breadboard with a 5 V supply.

6.1 Microprocessor vs microcontroller, with the 8051 as exemplar

plaintext
   MICROPROCESSOR (8086)              MICROCONTROLLER (8051)
   ─────────────────────              ──────────────────────
   ┌──────┐                           ┌──────────────────┐
   │ CPU  │                           │  CPU             │
   └──────┘                           │  4 KB ROM        │
       │                              │  128 B RAM       │
   ┌─────────────┐                    │  4 × 8-bit ports │
   │ External    │                    │  2 × 16-bit timers│
   │  RAM/ROM    │                    │  1 × UART         │
   │  Decoders   │                    │  Interrupt ctrl   │
   │  Timers     │                    │  Oscillator       │
   │  PPI        │                    └──────────────────┘
   │  USART      │                            (40-pin DIP,
   │  IC         │                          one chip total)
   └─────────────┘

This is the chef-versus-food-truck analogy made concrete. A microprocessor is brilliant but bare; you build the kitchen around it. A microcontroller comes with the kitchen, ready to cook. The 8051 has CPU + 4 KB on-chip program memory + 128 bytes RAM + four 8-bit I/O ports + two 16-bit timers + a UART + an interrupt controller + an oscillator, all in a single 40-pin DIP. Apply 5 V, attach a crystal, and you have a complete embedded system.

6.2 8051 architecture

plaintext
                  ┌────────────────────────────────────────┐
                  │           8051 CPU core (8-bit)        │
                  │  ┌──────────┬───────────┬───────────┐  │
                  │  │ ALU 8-bit│ Accum (A) │ B reg     │  │
                  │  ├──────────┼───────────┼───────────┤  │
                  │  │ R0–R7    │ PSW       │ DPTR      │  │
                  │  │ (4 banks)│ flags     │ 16-bit    │  │
                  │  ├──────────┴───────────┴───────────┤  │
                  │  │ Internal RAM: 128 B + SFR area  │  │
                  │  │ + (8052 only) extra 128 B       │  │
                  │  ├──────────────────────────────────┤  │
                  │  │ Internal ROM: 4 KB (8051)       │  │
                  │  │ Timers T0, T1 (16-bit each)     │  │
                  │  │ UART (serial port)              │  │
                  │  │ Interrupt controller (5 src)    │  │
                  │  └──────────────────────────────────┘  │
                  │  Ports P0, P1, P2, P3 (8 bits each)    │
                  └────────────────────────────────────────┘
                              │  │  │  │
                            P0  P1 P2 P3 to outside world

The 8051 is Harvard architecture: it has separate program-memory and data-memory address spaces. Program memory holds your code (read-only at runtime, fetched via PSEN). Data memory holds your variables (read/write). This is unlike the 8086 (von Neumann — code and data share one space).

Why Harvard? Harvard lets the CPU fetch an instruction while simultaneously reading or writing data — two memory operations per cycle on different buses. It also gives natural protection against self-modifying code (you cannot accidentally overwrite your program). Most modern microcontrollers (8051, AVR, PIC, Cortex-M with Flash) are Harvard or modified-Harvard. CPUs run von Neumann at the architecture level but have separate L1 instruction/data caches inside, getting Harvard's benefit at the microarchitecture level.

6.3 8051 pin diagram (40-pin DIP)

plaintext
                  ┌─────────────┐
       P1.0 ──┤ 1│             │40├── VCC (+5 V)
       P1.1 ──┤ 2│             │39├── P0.0 / AD0
       P1.2 ──┤ 3│             │38├── P0.1 / AD1
       P1.3 ──┤ 4│             │37├── P0.2 / AD2
       P1.4 ──┤ 5│             │36├── P0.3 / AD3
       P1.5 ──┤ 6│             │35├── P0.4 / AD4
       P1.6 ──┤ 7│             │34├── P0.5 / AD5
       P1.7 ──┤ 8│             │33├── P0.6 / AD6
       RST  ──┤ 9│   8051      │32├── P0.7 / AD7
   P3.0/RXD ──┤10│             │31├── EA / VPP
   P3.1/TXD ──┤11│             │30├── ALE / PROG
   P3.2/INT0──┤12│             │29├── PSEN
   P3.3/INT1──┤13│             │28├── P2.7 / A15
   P3.4/T0  ──┤14│             │27├── P2.6 / A14
   P3.5/T1  ──┤15│             │26├── P2.5 / A13
   P3.6/WR  ──┤16│             │25├── P2.4 / A12
   P3.7/RD  ──┤17│             │24├── P2.3 / A11
       XTAL2──┤18│             │23├── P2.2 / A10
       XTAL1──┤19│             │22├── P2.1 / A9
       GND  ──┤20│             │21├── P2.0 / A8
                  └─────────────┘
  • P0 (pins 32–39): open-drain bidirectional; doubles as multiplexed AD0–AD7 for external memory.
  • P1 (pins 1–8): general-purpose I/O.
  • P2 (pins 21–28): doubles as upper address byte A8–A15 for external memory.
  • P3 (pins 10–17): alternate functions: RXD, TXD, two external interrupt inputs (INT0, INT1), two timer inputs (T0, T1), and external memory strobes WR and RD.
  • EA (pin 31): "External Access" — tie high to use internal program memory, low to fetch all code from external ROM.
  • PSEN (pin 29): Program Store Enable, strobe for external code fetches.
  • ALE (pin 30): address latch enable, like the 8086's, for the multiplexed P0 bus.
  • XTAL1, XTAL2 (pins 19, 18): crystal oscillator pins. Typical: 11.0592 MHz (gives clean 9600/4800/2400/1200 baud rates) or 12 MHz (1 µs per machine cycle, since the 8051 takes 12 clocks per machine cycle).
  • RST (pin 9): held high for two machine cycles to reset.

6.4 8051 internal memory map

The 8051 has a beautifully tight memory map.

plaintext
   Internal RAM (128 B), addresses 00–7F:
     00–1F: four banks of R0–R7 (32 bytes total). Bank selected by RS0/RS1 in PSW.
     20–2F: 16 bytes of bit-addressable memory (addresses 00.0–7F.7).
     30–7F: 80 bytes of general-purpose RAM.
 
   Special Function Registers (SFRs), addresses 80–FF:
     80: P0   (port 0)       A0: P2     B0: P3      D0: PSW
     81: SP   (stack ptr)    81: SP                 D8: ...
     82: DPL  (DPTR low)     A8: IE     B8: IP      E0: ACC
     83: DPH  (DPTR high)    87: PCON               F0: B
     88: TCON 89: TMOD       90: P1
     8A: TL0  8C: TH0
     8B: TL1  8D: TH1                  98: SCON  99: SBUF
   ... and so on.
 
   On the 8052 (a slight upgrade), addresses 80–FF in *indirect* mode access an
   extra 128 B of regular RAM, while *direct* mode still hits the SFRs.

Bit-addressable memory is a great feature: in the range 20H–2FH, individual bits have their own addresses 00H–7FH. So SETB 12H sets bit 12H, which is bit 2 of address 22H. Many SFRs are also bit-addressable (P0, P1, P2, P3, IE, IP, ACC, PSW, etc.) — bits within them have addresses too. This makes register-level bit manipulation a one-instruction affair. AVR and Cortex-M have similar but wordier mechanisms.

6.5 Registers and flags (PSW)

The Program Status Word (PSW, address D0H) holds the 8051's flags:

BitNameMeaning
7CYCarry.
6ACAuxiliary carry (BCD).
5F0User-defined general flag.
4RS1Register-bank select bit 1.
3RS0Register-bank select bit 0.
2OVOverflow.
1Reserved.
0PParity (even number of 1s in A).

The DPTR (Data Pointer) is a 16-bit pointer made of the SFRs DPH and DPL. Used for accessing 64 KB of external data memory and for ROM lookup tables (MOVC A, @A+DPTR).

6.6 8051 timers

Two 16-bit counters T0 and T1, each split as TH0/TL0 and TH1/TL1 SFRs. Configured by TMOD and controlled by TCON.

Modes:

  • Mode 0: 13-bit timer (legacy, rarely used).
  • Mode 1: 16-bit timer/counter. Counts up, fires interrupt on overflow.
  • Mode 2: 8-bit auto-reload. TL counts; on overflow it copies TH back into TL automatically. The most useful mode, especially for baud rate generation.
  • Mode 3: Special timer 0 split into two 8-bit timers (frees TF1 for other uses).

Timer mode 2 derivation for baud rate. The UART in mode 1 (8-bit async) takes the timer 1 overflow rate divided by 32. So baud = (T1 overflow rate) / 32. T1 overflow rate in mode 2 = oscillator / (12 × (256 − TH1)). For a crystal of 11.0592 MHz, oscillator/12 = 921,600 Hz. To get 9600 baud:

921,600/(256TH1)/32=9600921{,}600 / (256 - \text{TH1}) / 32 = 9600 256TH1=921,600/(32×9600)=3256 - \text{TH1} = 921{,}600 / (32 \times 9600) = 3 TH1=253=0xFD\text{TH1} = 253 = \text{0xFD}

So loading TH1 with 0xFD on an 11.0592 MHz crystal gives exactly 9600 baud — and that's why 11.0592 MHz crystals are standard in 8051 systems.

6.7 Serial port (UART)

The SCON (Serial Control) register and SBUF (Serial Buffer) implement the UART. SCON's mode bits select:

  • Mode 0: Synchronous shift register (up to f_osc/12 baud). Fixed clock on TXD, data on RXD.
  • Mode 1: 8-bit async, baud variable from T1.
  • Mode 2: 9-bit async, baud fixed at f_osc/64 or f_osc/32.
  • Mode 3: 9-bit async, baud variable from T1.

Receiving a byte sets the RI bit; transmitting a byte (write to SBUF) sets TI when done. Either fires the serial interrupt.

6.8 Interrupts

Five sources, fixed vectors:

SourceVectorPriority (default)
External INT0 (P3.2)0003H1 (highest)
Timer 0 overflow000BH2
External INT1 (P3.3)0013H3
Timer 1 overflow001BH4
Serial (RI or TI)0023H5 (lowest)

The IE register has individual enable bits plus a global EA bit. The IP register sets per-interrupt priority (high or low — two levels). On interrupt, the CPU pushes PC, jumps to the vector, runs the handler (usually a LJMP to the real handler since vectors are only 8 bytes apart), and RETI returns.

6.9 8051 assembly programming

The 8051 has roughly 110 mnemonics. Some flavor:

Blink P1.0 with delay.

asm
        ORG 0H
START:  CPL P1.0           ; toggle bit 0 of P1
        ACALL DELAY        ; wait
        SJMP START
 
DELAY:  MOV R7, #200       ; outer loop count
LOOP1:  MOV R6, #250       ; inner loop count
LOOP2:  DJNZ R6, LOOP2     ; decrement R6, jump if nonzero
        DJNZ R7, LOOP1     ; decrement R7, jump if nonzero
        RET
        END

DJNZ (Decrement and Jump if Non-Zero) is the workhorse loop instruction. Two nested DJNZs give about 100 ms at 12 MHz.

Echo serial input back at 9600 baud.

asm
        ORG 0H
        SJMP MAIN
 
MAIN:   MOV TMOD, #20H     ; T1 in mode 2 (auto-reload)
        MOV TH1, #0FDH     ; 9600 baud at 11.0592 MHz
        MOV SCON, #50H     ; serial mode 1, REN=1
        SETB TR1           ; start timer 1
LOOP:   JNB RI, $          ; wait for byte received
        MOV A, SBUF
        CLR RI
        MOV SBUF, A        ; transmit it
        JNB TI, $          ; wait for transmit done
        CLR TI
        SJMP LOOP
        END

Read switch on P1.0, light LED on P1.7.

asm
LOOP:   JB P1.0, ON        ; if P1.0 high, jump to ON
        CLR P1.7           ; else turn LED off
        SJMP LOOP
ON:     SETB P1.7          ; LED on
        SJMP LOOP

6.10 8051 addressing modes

ModeExampleEffective
ImmediateMOV A, #50A = 50
DirectMOV A, 30HA = (30H), an internal RAM byte
IndirectMOV A, @R0A = (R0), where R0 is the address
RegisterMOV A, R3A = R3
IndexedMOVC A, @A+DPTRA = code-mem[A+DPTR] (lookup table)
RelativeSJMP TARGETPC ± 8-bit signed displacement
AbsoluteAJMP TARGETwithin 2K page (11-bit absolute)
LongLJMP TARGETfull 16-bit absolute

6.11 Interfacing examples

LED on P1.0: SETB P1.0 lights it (drives high through internal pull-up; in the 8051 you typically sink current — the LED is between Vcc and P1.0 with a series resistor, and CLR P1.0 lights it). Standard.

Switch on P1.7: make P1.7 input by writing 1 to it (SETB P1.7); pull-up internal; switch to ground; JNB P1.7, … reads the press.

LCD (HD44780): 4 data lines + RS + EN. Write 0x38 (8-bit, 2-line, 5×7), 0x0E (display on), 0x06 (auto-increment cursor). Then send 8-bit ASCII characters with RS=1.

ADC0808: address lines select channel; START pulse begins conversion; EOC indicates ready; data on D0–D7.

Stepper motor: four phase coils driven by a Darlington pair (ULN2003) from P2.0–P2.3. Rotate by sequencing patterns 0x1, 0x2, 0x4, 0x8 (full step) or smoother half-step. Speed via delay between transitions.