Embedded C track

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

8051 Architecture, SFRs & reg52.h

SFRs and `sbit`/`sfr`

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

So far (Parts 1~5), we have reinforced C syntax and the concepts of bits and pointers without using a board. From this part onward, we will get on an actual chip. The first protagonist is the classic in embedded education, the 8051 (here, AT89C52). The 8051 has a simple structure and few registers, making it the best chip to learn "how to manipulate hardware with C."

The key of this part is the SFR (Special Function Register). All of 8051's ports, timers, serial communication, and interrupts are controlled via SFRs. The <reg52.h> provided by Keil C51 pre-declares these SFRs with names, allowing us to handle hardware as if assigning values to variables like P1 = 0x0F;. This part will teach two ways to write values to port SFRs (entire port / individual bit sbit), leading to the LED blinking in Part 7.

Simulator reference: The 8051 examples in this track are verified using the μVision built-in simulator. An actual board is not necessary, and the port status can be checked in the Peripherals → I/O-Ports window.

Learning Objectives

  • Outline the basic structure of the 8051 (CPU, ROM/RAM, 4 ports, timer, serial, interrupt).
  • Explain what SFRs are and where they are located in memory (0x80~0xFF).
  • Understand how <reg52.h> exposes SFRs with names.
  • Use the distinction between writing to the entire port SFR (P1 = ...) and writing to individual bits (sbit).
  • Understand the usage of C51-specific keywords sfr, sbit, bit.

Target Environment

  • Compiler: Keil C51 (for 8051 series).
  • Device: AT89C52 (8051 compatible, μVision simulator supported).
  • Verification: μVision simulator (Peripherals → I/O-Ports → Port 1). Pure logic checking on PC gcc (pc_test.c).

Key Concepts

1) Overview of 8051

Resource Details
CPU 8-bit, accumulator (ACC) centered
Program Memory Code ROM/Flash (AT89C52: 8KB)
Data Memory Internal RAM 256 bytes (lower 128 + upper 128), SFR area
Input/Output 4 8-bit ports P0~P3 (total 32 pins)
Timer Timer0/1 (AT89C52 adds Timer2)
Serial 1 channel UART
Interrupt External 2, timer, serial, etc.

int is 16-bit in 8051 (see Part 2). So values that are sufficient with 8 bits use unsigned char (or uint8_t) to save RAM and code size.

2) SFR — Special Function Registers

SFRs are registers that control peripheral devices and are arranged in the internal RAM address range 0x80~0xFF. Ports (P0~P3), timer control (TCON/TMOD), serial (SCON/SBUF), interrupts (IE/IP), etc., are all SFRs.

SFR Address Function
P0 0x80 Port 0
P1 0x90 Port 1
P2 0xA0 Port 2
P3 0xB0 Port 3
TMOD 0x89 Timer Mode
TCON 0x88 Timer Control
SCON 0x98 Serial Control
IE 0xA8 Interrupt Enable

SFRs with addresses that are multiples of 8, like 0x80, 0x88, 0x90 … (0x_0/0x_8), can be bit-addressable. That is, individual bits can be accessed using sbit. This includes P0~P3, TCON, SCON, IE, and IP.

3) <reg52.h> — SFR by Name

<reg52.h> is a built-in header in Keil C51 that pre-declares the above SFRs. Internally, it looks roughly like this.

sfr  P1   = 0x90;   /*The name P1 refers to the SFR at address 0x90*/
sfr  TMOD = 0x89;
sbit P1_0 = 0x90;   /*(Example) Bit 0 of P1*/

So with one line of #include <reg52.h>, we can directly use names like P1, TMOD, IE. No external library is required.

4) Two Ways to Write to a Port

(1) Write the Entire Port — 8 bits at once.

P1 = 0x0F;   /* P1.0~P1.3 = 1, P1.4~P1.7 = 0 */

(2) Write Individual Bits — Give names to bits with sbit and write only one.

sbit LED0 = P1 ^ 0;   /*P1.0 is named LED0 (declared at file scope)*/
LED0 = 1;             /*Only P1.0 is set to 1, the remaining bits remain the same*/

^ in P1 ^ 0 is C51-specific notation meaning 'bit 0 of P1', not XOR. sbit can only be used in bit-addressable SFRs.

5) C51-Specific Keywords — sfr, sbit, bit

Keyword Meaning Example
sfr Declare an 8-bit SFR sfr P1 = 0x90;
sbit A bit of an SFR sbit LED = P1 ^ 0;
bit Bit variable (internal bit memory) bit flag;

These are Keil C51 extensions not present in standard C. Therefore, this main.c will not compile on a PC's gcc (PC verification is done separately with pc_test.c).

Creating a μVision Project

  1. Specify Project → New µVision Project… → folder and name (e.g., c51_sfr).
  2. In the Device selection window, choose Atmel → AT89C52 (if not available, install the 8051 device pack via the Pack Installer).
  3. When asked 'Copy STARTUP.A51 to project?', select Yes — includes default startup code.
  4. Add main.c to Source Group 1 (Right-click → Add New/Existing Item), then copy and paste the code below.
  5. In the Project → Options for Target → Debug tab, select Use Simulator.
  6. Rebuild(F7) → Start/Stop Debug Session(Ctrl+F5).
  7. Open the Peripherals → I/O-Ports → Port 1 window to observe the bit status.

Examples

Example 1 — main.c: Two Ways to Access the SFR

Shows both writing to the entire port and writing to sbit bits.

#include <reg52.h>

/*
 *Lesson 6 Example 1 — Two ways to access SFR (Special Function Register).
 *Target: AT89C52 (8051 compatible), Compiler: Keil C51.
 *
 *(1) Write to the entire port at once:  P1 = 0x0F;
 *(2) Write to individual bits with assigned names: declare with sbit, then LED0 = 0;
 *
 *Inside reg52.h, P1 is pre-declared as 'sfr P1 = 0x90;',
 *so we can directly access the port SFR at address 0x90 using the name P1.
 */

/*Assign meaningful names to individual bits of the P1 port (P1 is a bit-addressable SFR).*/
sbit LED0 = P1 ^ 0;    /* P1.0 */
sbit LED7 = P1 ^ 7;    /* P1.7 */

void main(void)
{
    /*(1) Write to the entire port: P1.0~P1.3 = 1, others = 0*/
    P1 = 0x0F;

    /*(2) Write to individual bits: leave the remaining bits as they are and change only one bit*/
    LED0 = 0;          /* P1.0 = 0 → 0x0E */
    LED7 = 1;          /* P1.7 = 1 → 0x8E */

    /*The embedded main does not terminate. It waits while maintaining the value of P1.*/
    while (1) {
        /*Confirm P1 = 0x8E in the simulator Port 1 window.*/
    }
}

Example 2 — pc_test.c: Verify Final Port Value on PC

Simulate the SFR as a regular variable and verify the result of the above operation (0x8E) on the PC.

#include <stdio.h>

/*
 *Lesson 6 Example 2 — Verify the final port value produced by the lecture example (main.c) on the PC.
 *On an actual chip, P1 is an SFR (address 0x90), but here it is simulated with a general variable.
 *Only check the result when "P1 = 0x0F and then P1.0 is set to 0, P1.7 is set to 1".
 */
int main(void)
{
    unsigned char P1 = 0x0F;          /*Full port write*/

    P1 = (unsigned char)(P1 & ~(1u << 0));   /* LED0 = 0 → P1.0 clear */
    P1 = (unsigned char)(P1 | (1u << 7));    /* LED7 = 1 → P1.7 set   */

    printf("P1 = 0x%02X\n", P1);      /* 0x8E */

    return 0;
}

Checking with Simulator

  • Start a debug session with Ctrl+F5, then run with F5 (or step line by line with F11).
  • Observe P1 bits in the Peripherals → I/O-Ports → Port 1 window.
  • Expected behavior: P1 = 0x0F (0000 1111) → LED0 = 0 (0000 1110) → LED7 = 1 (1000 1110). Final P1 = 0x8E. In the Port 1 window, the pattern with bit 7 and bits 1~3 on (1000 1110) is visible.

Verification on a PC

Pure logic verification is only possible on the PC (device main.c is for C51 only).

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

Expected output (stdout):

P1 = 0x8E

Common Mistakes

Q. When compiling main.c with gcc, it says reg52.h does not exist.

A. <reg52.h>, sfr, and sbit are exclusive to Keil C51. Device main.c can only be built in μVision. On a PC, just verify the logic using pc_test.c (standard C).

Q. I get an error when I use sbit LED = P1 ^ 0; inside a function.

A. The declaration of sfr/sbit must be file scope (global). It cannot be declared inside a function. Declare it above main.

Q. I used P1 = 0x0F but input is not read.

A. This section only deals with writing output. To read ports as input on the 8051, you must first write 1 to the corresponding pin to set it to input mode (quasi-bidirectional port). Input reading will be covered in Chapter 8.

Q. I used int on a large counter and it behaves strangely.

A. On the 8051, int is 16-bit. For an 8-bit value, use the port Value/Counter-is unsigned char. If a large number is needed, explicitly use unsigned int (16-bit) or unsigned long (32-bit).

Summary

  • The 8051 is a simple 8-bit MCU and controls peripherals using SFRs (0x80~0xFF).
  • <reg52.h> provides SFRs such as P1, TMOD, IE by name.
  • Ports are handled with full write (P1 = 0x0F) and bit write (sbit).
  • sfr/sbit/bit are keywords exclusive to C51, so they do not work with PC gcc (logic is verified with pc_test.c).
  • For values small enough to fit in 8 bits, use unsigned char to save resources.

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

/*
 *Lesson 6 Example 1 — Two ways to access SFR (Special Function Register).
 *Target: AT89C52 (8051 compatible), Compiler: Keil C51.
 *
 *(1) Write to the entire port at once:  P1 = 0x0F;
 *(2) Write to individual bits with assigned names: declare with sbit, then LED0 = 0;
 *
 *Inside reg52.h, P1 is pre-declared as 'sfr P1 = 0x90;',
 *so we can directly access the port SFR at address 0x90 using the name P1.
 */

/*Assign meaningful names to individual bits of the P1 port (P1 is a bit-addressable SFR).*/
sbit LED0 = P1 ^ 0;    /* P1.0 */
sbit LED7 = P1 ^ 7;    /* P1.7 */

void main(void)
{
    /*(1) Write to the entire port: P1.0~P1.3 = 1, others = 0*/
    P1 = 0x0F;

    /*(2) Write to individual bits: leave the remaining bits as they are and change only one bit*/
    LED0 = 0;          /* P1.0 = 0 → 0x0E */
    LED7 = 1;          /* P1.7 = 1 → 0x8E */

    /*The embedded main does not terminate. It waits while maintaining the value of P1.*/
    while (1) {
        /*Confirm P1 = 0x8E in the simulator Port 1 window.*/
    }
}
src/c51_sfr/pc_test.cC
#include <stdio.h>

/*
 *Lesson 6 Example 2 — Verify the final port value produced by the lecture example (main.c) on the PC.
 *On an actual chip, P1 is an SFR (address 0x90), but here it is simulated with a general variable.
 *Only check the result when "P1 = 0x0F and then P1.0 is set to 0, P1.7 is set to 1".
 */
int main(void)
{
    unsigned char P1 = 0x0F;          /*Full port write*/

    P1 = (unsigned char)(P1 & ~(1u << 0));   /* LED0 = 0 → P1.0 clear */
    P1 = (unsigned char)(P1 | (1u << 7));    /* LED7 = 1 → P1.7 set   */

    printf("P1 = 0x%02X\n", P1);      /* 0x8E */

    return 0;
}

Assignment

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


Question 1 — Full Port Write + sbit Bit Adjustment

  • File name: homework_01/main.c
  • Core concept: Full SFR write, individual bits of sbit
  • Platform: C51 (AT89C52)

Requirements

  • After writing 0xAA to P2, adjust P2.0 to 1 and P2.7 to 0 with sbit.
  • Then maintain the value with while(1).

Expected Operation (Simulator)

  • Final P2 = 0x2B (0010 1011) on Peripherals → I/O-Ports → Port 2.
  • Procedure: 0xAA (1010 1010) → set P2.0 (1010 1011=0xAB) → clear P2.7 (0010 1011=0x2B).

Hint

  • Declare sbit P2_0 = P2 ^ 0; and sbit P2_7 = P2 ^ 7; in file scope (global).
  • Individual bit write preserves the other bits.

Question 2 — Step-by-Step Port Value Check (PC)

  • File name: homework_02/main.c
  • Core concept: Accumulated result of bit manipulation
  • Platform: Common (PC gcc)

Requirements

  • Start unsigned char p from 0x00, apply the following in order, and output each step value with 0x%02X.
  1. Set upper nibble (p |= 0xF0)
  2. bit4 clear
  3. bit0 toggle
  • The output format must match the "expected output" character by character.

Expected output

step1 (set high nibble): 0xF0
step2 (clear bit4)     : 0xE0
step3 (toggle bit0)    : 0xE1

Hint

  • Each step accumulates on the previous result.
  • If you clear bit 4 (0x10) in 0xF0, it becomes 0xE0, and if you toggle bit 0, it becomes 0xE1.

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) — Write entire port + individual bits with sbit
 *
 *Key Points:
 *- Write the entire port at once with P2 = 0xAA, then correct each bit individually with sbit.
 *- Writing individual bits does not affect the remaining bits (read-modify-write of SFR).
 *
 *Common mistake:
 *- Case of declaring sbit inside a function. sbit/sfr should be placed at file scope (global).
 *- Without verifying the result from bit0 set and bit7 clear on 0xAA (1010 1010) as 0x2B
 *and expecting an incorrect value.
 *
 *Expected behavior (simulator): Displayed as P2 = 0x2B (0010 1011) in the Port 2 window.
 */

sbit P2_0 = P2 ^ 0;
sbit P2_7 = P2 ^ 7;

void main(void)
{
    P2 = 0xAA;        /* 1010 1010 */

    P2_0 = 1;         /* bit0 set   → 1010 1011 = 0xAB */
    P2_7 = 0;         /* bit7 clear → 0010 1011 = 0x2B */

    while (1) {
        /*P2 remains 0x2B*/
    }
}
homework_02/main.cC
#include <stdio.h>

/*
 *Correct answer 2 (pure logic, PC) — Verify the port value changes step by step
 *
 *Key Points:
 *- Simulate the port SFR as a regular variable and check the results of bit manipulations step by step.
 *- Look at the cumulative results of setting the upper nibble (|= 0xF0) and clearing/toggling specific bits.
 *
 *Common mistake:
 *- Forgetting that each step is cumulative and starting again from 0x00 every time.
 */
int main(void)
{
    unsigned char p = 0x00;

    p = (unsigned char)(p | 0xF0);          /*Setting the upper nibble*/
    printf("step1 (set high nibble): 0x%02X\n", p);   /* 0xF0 */

    p = (unsigned char)(p & ~(1u << 4));    /* bit4 clear     */
    printf("step2 (clear bit4)     : 0x%02X\n", p);   /* 0xE0 */

    p = (unsigned char)(p ^ (1u << 0));     /* bit0 toggle    */
    printf("step3 (toggle bit0)    : 0x%02X\n", p);   /* 0xE1 */

    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