Learning the theory aspects of a microcontroller is only a small part of the experience. The real fun begins when you put that knowledge into action. When I say ‘action,’ I mean designing a circuit and writing applications using the microcontroller. This is applicable even to basic microcontrollers such as the 8051 Microcontroller. As a tradition, one of the first programs we write for any microcontroller is to Blink an LED. So, in this guide, I would like to share everything you need to know about Interfacing an LED with the 8051 Microcontroller. In the process, I will show how to write a GPIO Driver for an 8051 Microcontroller.
If you followed my earlier 8051 Microcontroller Guides, you might know that I will be working with AT89S52 Microcontroller from Atmel (now Microchip). Even if you have some other variant of the 8051 Microcontroller, the process remains more or less the same.
Before learning how to Blink an LED using 8051 Microcontroller, familiarize yourself with the 8051 Microcontroller Special Function Registers (SFRs).
Why Blinky Though?
You might be wondering, what is the point of the basic and simple application such as blinking an LED using an 8051 Microcontroller (or any microcontroller for that matter).
Yes, the Blinky application seems silly at this point. But the main purpose of Blinking an LED is, well, not to Blink an LED but to check if all the essential hardware and software are working properly or not.
If we successfully blink an LED, then we can safely assume that the power supply, clock circuit, toolchain, and software configuration are working as expected. After this, we can move on to more complex applications without worrying about the essential components.
Components Needed
- 8051 Microcontroller: Any 8051 microcontroller to control the LED. I will be using AT89S52 for demonstration.
- LED: A light-emitting diode that will blink. I used a 5mm green LED.
- Resistor (220Ω or 330Ω): To limit the current flowing through the LED.
Breadboard and Jumper Wires: For connecting all the components.
Circuit Design for Interfacing an LED with the 8051 Microcontroller
With that aside, let us now proceed to build the circuit for Interfacing an LED with 8051 Microcontroller. There are a couple of ways to connect the LED to the I/O Pin of the Microcontroller. The following image shows both these ways:

In the first method, the Microcontroller Pin acts as the Source of the Power Supply to the LED. We have to connect the Anode of the LED to the I/O Pin of the 8051 Microcontroller and Cathode to ground (GND). The I/O Pin provides the necessary current to drive the LED.
In the second method, the Microcontroller Pin absorbs current from an external source. Here, we have to connect the Anode of the LED to positive supply (+5V) and Cathode to the I/O pin of the Microcontroller. The I/O Pin of the Microcontroller will ‘sink’ the current through the LED to light it up.
Microcontroller I/O pins have limitations on how much current they can source or sink. Exceeding these limits could damage the microcontroller.
Since we are just driving a LED (with a current of about 10 – 20 mA), we need not worry too much here. However, if your circuit design requires more current than the I/O pin can safely supply or absorb, external components like transistors or MOSFETs come in handy. They can easily switch larger currents while the microcontroller controls the base or gate of the transistor.
Why Do We Need the Resistor When Powering LEDs?
If you notice the previous image, I used a resistor in the circuit. What is that for? First of all, the resistor is an important part of any LED circuit. It is known as the current limiting resistor. As the name suggests, it limits the current passing through the LED.
Every LED has two important specifications in the form of Forward Voltage and Forward Current. Forward voltage is the voltage drop across the LED to make it forward biased. This is the voltage we need to apply on the LED to turn it on and make it emit light.
The forward voltage depends on the type of LED and its color (wavelength). For instance:
- Red LEDs: 1.8 – 2.2V
- Green LEDs: 2.0 – 3.0V
- Blue and White LEDs: 3.0 – 3.5V
Coming to the forward current, it is the amount of current that flows through the LED when it is operating at its forward voltage.
Common values of forward current range from 10 mA to 30 mA for standard LEDs, but high-power LEDs can use much higher currents, sometimes exceeding 350 mA or more. If the current through the LED exceeds this forward current limit, it can permanently damage the LED.
The simplest way to limit the current through an LED is to place a resistor in series with the LED. Using Ohm’s Law, we can easily calculate the value of the resistor.
If Vs is the supply voltage, Vf is the forward voltage of the LED, and If is the forward current of the LED, then the value of the Series Resistor (Current Limiting Resistor) is:
R = (Vs – Vf) / If
Circuit Diagram of Interfacing LED with 8051 Microcontroller
With that information on essential LED driving techniques, let us design the actual circuit to interface an LED with 8051 Microcontroller. As you know from the previous guide, the 8051 Microcontroller has four I/O Ports, each with 8 Pins. We can use any of these 32 I/O Pins to connect to the LED circuit as long as you are not interfacing any external Program or Data Memory.
The AT89S52 Microcontroller I will be working with comes with a pretty decent 8K Bytes of internal Program Memory (Flash) and 256 Bytes of internal Data Memory (RAM). This is more than sufficient for most of the applications we will be working on.
I already talked about the 8051 Microcontroller Minimal Circuit in the 8051 Microcontroller Pin Diagram and Pin Description guide. This is the bare minimum circuit that you need to build for the microcontroller to operate. It includes the following parts:
- A Reset Circuit
- Clock Circuit
- Pulling EA pin HIGH
- Pull-up resistors for P0 (Port 0) pins
These must be a part of all your 8051 Microcontroller circuits no matter what the final project looks like.
Circuit Description
- For this project, I will connect a simple green LED to Pin 0 of Port 1 (P1.0). I will use the Microcontroller I/O Pin to sink the current from the LED.
- So, the Anode of the LED goes to +5V, cathode goes to one end of the resistor (220Ω), and the other end of the resistor to the P1.0 Pin of 8051 Microcontroller.
The following image shows the complete circuit for interfacing an LED with 8051 Microcontroller.

- I will use P1.0 (Port 1, Pin 0) of the microcontroller as the output pin to control the LED.
- The LED will turn ON when P1.0 is set to 0 (LOW), and OFF when it is set to 1 (HIGH).
My development board already has all the essential components that I mentioned before. Additionally, it also has on-board programming hardware. So, I need not worry about that as well.
Simple 8051 LED Blink Program
We will write a simple C program that blinks the LED connected to P1.0. The LED will turn ON for 1 second and OFF for 1 second, repeatedly.
Code Structure
- Initialization: Set the necessary pin as output.
- LED Control: Toggle the state of the LED.
Delay Function: Implement a delay to control the ON/OFF time.
If you just want to Blink an LED using 8051 Microcontroller and nothing else, then you can use the following code as reference and customize it as per your needs.
I put comments in the code so that you can easily understand it. An important thing to note here is that a HIGH on Port Pin will make the LED off and a LOW on Port Pin will make the LED on. This is because I am using the Microcontroller Pin as a sink. It is essentially an active LOW control.
/*Include the header file for the 8051 microcontroller/ #include <REG52.H> /*Define the LED pin (connected to P1.0)*/ sbit LED_PIN = P1^0; /*LED is connected to Port 1, bit 0*/ /*Function to create a small delay (in milliseconds)*/ void delay_ms(unsigned int ms); void main() { /*Set Port 1 as output (by default, Port 1 pins are output) P1 = 0x00; /*All bits of P1 are set to low initially (LED ON)*/ /*Main program loop to blink the LED*/ while(1) { LED_PIN = 1; /*Turn OFF the LED (logic HIGH, Active LOW LED)*/ delay_ms(1000); /*Wait for 1 second (1000 milliseconds)*/ LED_PIN = 0; /*Turn ON the LED (logic LOW)*/ delay_ms(1000); /*Wait for 1 second (1000 milliseconds)*/ } } /*Delay function to create a time delay in milliseconds*/ void delay_ms(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) { for(j = 0; j < 1275; j++) { /*Empty loop to create delay*/ /*This inner loop generates an approximate 1ms delay*/ } } }
Explanation of the Code
Let me explain the code step-by-step:
1. Including the 8051 Header
#include <REG52.H>
This header file provides all the necessary definitions for the 8051 microcontroller, including the addresses of the I/O ports and various registers.
2. Defining the LED Pin
sbit LED_PIN = P1^0; /*LED connected to Port 1, Pin 0*/
sbit is used to declare a bit-addressable pin. Here, I defined a variable LED_PIN of type sbit as P1.0 (Port 1, Pin 0), which is the pin controlling the LED. We will toggle this pin to turn the LED ON and OFF.
3. Main Program
void main() { /*Set Port 1 as output (by default, Port 1 pins are output) P1 = 0x00; /*All bits of P1 are set to low initially (LED ON)*/ /*Main program loop to blink the LED*/ while(1) { LED_PIN = 1; /*Turn OFF the LED (logic HIGH, Active LOW LED)*/ delay_ms(1000); /*Wait for 1 second (1000 milliseconds)*/ LED_PIN = 0; /*Turn ON the LED (logic LOW)*/ delay_ms(1000); /*Wait for 1 second (1000 milliseconds)*/ } }
Initialization: The line P1 = 0x00; initializes all pins of Port 1 as Outputs. This will also turn the LED ON by default (since I am using the Microcontroller Pin to sink the current).
LED Control: Inside the infinite while(1) loop:
- LED OFF: Set LED_PIN = 1 to turn the LED OFF (LED is active-low, so it turns OFF when the Pin is HIGH).
- Delay: Call delay_ms(1000) to keep the LED OFF for 1 second.
- LED ON: Set LED_PIN = 0 to turn the LED ON (Pin goes LOW).
- Delay: Call delay_ms(1000) to keep the LED ON for 1 second.
- This loop will repeat, causing the LED to blink ON for 1 second and OFF for 1 second.
4. Delay Function
void delay_ms(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) { for(j = 0; j < 1275; j++) { /*Empty loop to create delay*/ /*This inner loop generates an approximate 1ms delay*/ } } }
We have to hold the state of the pin for a certain time to perceive that Blinking action. This is where the delay_ms() function creates a time delay in milliseconds.
The outer loop runs for ms times (number of milliseconds to delay). The inner loop runs 1275 times to create a delay approximately equal to 1 millisecond.
This delay function helps create the ON and OFF time for the LED (1 second each).
Compiling and Running the Code
- Create a Project: Open Keil uVision, create a new project, and use your 8051 Microcontroller as a ‘target device.’ In my case, I set it to AT89S52.
- Write the Code: Create a new ‘main.c’ file and add the above code.
- Configure the Target Options: Set the target options such as clock speed, internal memory in the ‘target options’ settings. Also, enable generation of the HEX file.
- Compile the Code: Build the project and make sure there are no errors.
- Program the Microcontroller: Use a compatible programmer (e.g., USBasp, or another programmer) to upload the compiled HEX file into the AT89S52 microcontroller. I used ProgISP software to upload the binary file.
- Connect the Circuit: Connect the LED and the current-limiting resistor to P1.0, as shown in the circuit diagram.
- Test the Circuit: Once the code is loaded onto the microcontroller, the LED will start blinking. It will turn ON for 1 second and OFF for 1 second, repeatedly.
In the ‘How to Program 8051 Microcontroller in Keil µVision?’ guide, I discussed how to use Keil µVision IDE, how to configure it to generate the HEX file, and even how to upload the HEX file to 8051 Microcontroller (AT89S52) using an USBASP Programmer and ProgISP software. Do check that guide for more information.
Code for Blinking 8 LEDs Connected to an I/O Port
What if you want to blink several LEDs connected to an I/O Port? We can do that with a simple modification to the code. Instead of using ‘sbit’ to define a single I/O Pin, we can directly use the Port Register (P0, P1, P2, or P3) and turn the LEDs on or off.
/* Header file for 8052 Microcontroller */ #include <reg52.h> /* Delay function Prototype */ void delay_ms (unsigned int ms); Void main() { while(1) { P1 = 0xFF; /* Turn OFF all the LEDs connected to P1 */ delay_ms(500); /* wait for approx. 500ms */ P1 = 0x00; /* Turn ON all the LEDs connected to P1 */ delay_ms(500); /* wait for approx. 500ms */ } } void delay_ms (unsigned int ms) { unsigned int i,j; for(i=0; i<ms; i++) { for(j=0; j<1275; j++); } }
GPIO Driver for 8051 Microcontroller
This style of coding is okay as long as you work with the microcontroller only one time (or maybe a handful of times). However, if you regularly work with the same microcontroller on several applications, then it becomes extremely difficult to maintain and debug the code. This is where Drivers (or Library Files if you are familiar with Arduino Libraries) come into play.
In this section, I will present you with a simple GPIO Driver for 8051 Microcontroller. As I am working with AT89S52 Microcontroller, I named the driver files as ‘at89s52_gpio.h’ for the header file and ‘at89s52_gpio.c’ for the main driver file.
Later, when I write drivers for other peripherals and devices, I will follow the same naming scheme.
GPIO Driver Header File
The following is the header file for the GPIO Driver (at89s52_gpio.h). I put a lot of comments in the header file for you to understand.
#ifndef AT89S52_GPIO_H #define AT89S52_GPIO_H /*Include the 8052 header file*/ #include <reg52.h> /** * Typedef Structure to represent a GPIO Pin, including Port and Pin number */ typedef struct { unsigned char port; /*Port number (0-3 for P0, P1, P2, P3)*/ unsigned char pin; /*Pin number (0-7 for each Port)*/ } GPIO_Pin; /** * Typedef enum to represent GPIO Ports. */ typedef enum { GPIO_PORT_0 = 0, /*P0*/ GPIO_PORT_1 = 1, /*P1*/ GPIO_PORT_2 = 2, /*P2*/ GPIO_PORT_3 = 3 /*P3*/ } GPIO_Port; /** * GPIO Pin Mode/Direction (INPUT or OUTPUT) enumeration */ typedef enum { GPIO_PIN_INPUT = 0U, GPIO_PIN_OUTPUT } GPIO_PinMode; /** * GPIO Bit SET and Bit RESET enumeration */ typedef enum { GPIO_PIN_RESET = 0U, GPIO_PIN_SET } GPIO_PinState; /** * GPIO Function Prototypes. */ void GPIO_SetDirection(GPIO_Pin gpio, GPIO_PinMode mode); void GPIO_Write(GPIO_Pin gpio, GPIO_PinState value); GPIO_PinState GPIO_Read(GPIO_Pin gpio); void GPIO_Toggle(GPIO_Pin gpio); void GPIO_WritePort(GPIO_Port port, GPIO_PinState value); unsigned char GPIO_ReadPort(GPIO_Port port); #endif
Main GPIO Driver File
As you saw in the header file, I created six essential functions in the GPIO Driver. You can perform almost all the I/O related operations using these six functions.
The following is the main GPIO Driver file for the 8051 Microcontroller AT89S52 (at89s52_gpio.c). Once again, I put a lot of effort into commenting on each and every section of the driver. Go through both the files and understand how the GPIO Driver works.
#include "at89s52_gpio.h" /** * @brief Function to set a pin as an output or input * @param gpio is a structure that contains Port (0..3) to select P0, P1, P2, or P3 * and Pin (0..7). * @param mode specifies the Mode/Direction of the selected Port Pin. * This parameter can be one of the GPIO_PinMode enum values: * @arg GPIO_PIN_INPUT: to set the Port Pin as Input (High Impedance) * @arg GPIO_PIN_OUTPUT: to set the Port Pin as Input * @retval None */ void GPIO_SetDirection(GPIO_Pin gpio, GPIO_PinMode mode) { /*Check for valid Port and Pin*/ if (gpio.port > 3 || gpio.pin > 7) return; /*Invalid Port or Pin*/ /*Handle direction for each Port*/ if (mode == GPIO_PIN_OUTPUT) /*Output*/ { switch (gpio.port) { case 0: /*P0*/ P0 |= (1 << gpio.pin); /*Set the Pin as Output*/ break; case 1: /*P1*/ P1 |= (1 << gpio.pin); /*Set the Pin as Output*/ break; case 2: /*P2*/ P2 |= (1 << gpio.pin); /*Set the Pin as Output*/ break; case 3: /*P3*/ P3 |= (1 << gpio.pin); /*Set the Pin as Output*/ break; default: return; /*Invalid*/ } } else /*Input*/ { switch (gpio.port) { case 0: /*P0*/ P0 &= ~(1 << gpio.pin); /*Set the Pin as Input (High Impedance)*/ break; case 1: /*P1*/ P1 &= ~(1 << gpio.pin); /*Set the Pin as Input (High Impedance)*/ break; case 2: /*P2*/ P2 &= ~(1 << gpio.pin); /*Set the Pin as Input (High Impedance)*/ break; case 3: /*P3*/ P3 &= ~(1 << gpio.pin); /*Set the Pin as Input (High Impedance)*/ break; default: return; /*Invalid*/ } } } /** * @brief Set or clear the selected Port Bit. * @param gpio is a structure that contains Port (0..3) to select P0, P1, P2, or P3 * and Pin (0..7). * @param value specifies the value to be written to the selected bit. * This parameter can be one of the GPIO_PinState enum values: * @arg GPIO_PIN_RESET: to write 0 (clear) the port pin * @arg GPIO_PIN_SET: to write 1 (set) the port pin * @retval None */ void GPIO_Write(GPIO_Pin gpio, GPIO_PinState value) { /*Check for valid Port and Pin*/ if (gpio.port > 3 || gpio.pin > 7) return; /*Invalid Port or Pin*/ if (value == GPIO_PIN_SET) { /*Instead of Switch, you can also use if..else conditions.*/ if (gpio.port == 0) /*P0*/ P0 |= (1 << gpio.pin); /*Set Pin to High*/ else if (gpio.port == 1) /*P1*/ P1 |= (1 << gpio.pin); /*Set Pin to High*/ else if (gpio.port == 2) /*P2*/ P2 |= (1 << gpio.pin); /*Set Pin to High*/ else if (gpio.port == 3) /*P3*/ P3 |= (1 << gpio.pin); /*Set Pin to High*/ } else { if (gpio.port == 0) /*P0*/ P0 &= ~(1 << gpio.pin); /*Set Pin to Low*/ else if (gpio.port == 1) /*P1*/ P1 &= ~(1 << gpio.pin); /*Set Pin to Low*/ else if (gpio.port == 2) /*P2*/ P2 &= ~(1 << gpio.pin); /*Set Pin to Low*/ else if (gpio.port == 3) /*P3*/ P3 &= ~(1 << gpio.pin); /*Set Pin to Low*/ } } /** * @brief Read the specified Input Port Bit. * @param gpio is a structure that contains Port (0..3) to select P0, P1, P2, or P3 * and Pin (0..7). * @retval The input Port Pin value. */ GPIO_PinState GPIO_Read(GPIO_Pin gpio) { /*Check for valid port and pin*/ if (gpio.port > 3 || gpio.pin > 7) return 0; /*Invalid port or pin*/ switch (gpio.port) { case 0: /*P0*/ return (P0 >> gpio.pin) & 0x01; /*Return Status of the selected I/O Pin*/ case 1: /*P1*/ return (P1 >> gpio.pin) & 0x01; /*Return Status of the selected I/O Pin*/ case 2: /*P2*/ return (P2 >> gpio.pin) & 0x01; /*Return Status of the selected I/O Pin*/ case 3: /*P3*/ return (P3 >> gpio.pin) & 0x01; /*Return Status of the selected I/O Pin*/ default: /*Invalid Port*/ return 0; /*(Should not reach here)*/ } } /** * @brief Toggle the specified GPIO pin. * @param gpio is a structure that contains Port (0..3) to select P0, P1, P2, or P3 * and Pin (0..7). * @retval None */ void GPIO_Toggle(GPIO_Pin gpio) { /* Check for valid Port and Pin*/ if (gpio.port > 3 || gpio.pin > 7) return; /*Invalid Port or Pin*/ switch(gpio.port) { case 0: /*P0*/ P0 ^= (1 << gpio.pin); /*Toggle Pin State (XOR Operation)*/ case 1: /*P*/ P1 ^= (1 << gpio.pin); /*Toggle Pin State (XOR Operation)*/ case 2: /*P2*/ P2 ^= (1 << gpio.pin); /*Toggle Pin State (XOR Operation)*/ case 3: /*P3*/ P3 ^= (1 << gpio.pin); /*Toggle Pin State (XOR Operation)*/ default: break; } } /** * @brief Function to write to the entire Port. * @param port specifies the Port to be written to (P0, P1, P2, or P3). * This parameter can be one of the GPIO_Port enum values: * @arg GPIO_PORT_0: to select P0 * @arg GPIO_PORT_1: to select P1 * @arg GPIO_PORT_2: to select P2 * @arg GPIO_PORT_3: to select P3 * @param value is the actual data to be written to the selected Port. * @retval None */ void GPIO_WritePort(GPIO_Port port, unsigned char value) { /* Check for valid Port*/ if (port > 3) return; /*Invalid Port*/ switch (port) { case GPIO_PORT_0: /*P0*/ P0 = value; /*Write value to Port*/ break; case GPIO_PORT_1: /*P0*/ P1 = value; /*Write value to Port*/ break; case GPIO_PORT_2: /*P0*/ P2 = value; /*Write value to Port*/ break; case GPIO_PORT_3: /*P0*/ P3 = value; /*Write value to Port*/ break; default: /*Invalid Port*/ break; } } /** * @brief Function to read from the entire port. * @param port specifies the Port to be written to (P0, P1, P2, or P3). * This parameter can be one of the GPIO_Port enum values: * @arg GPIO_PORT_0: to select P0 * @arg GPIO_PORT_1: to select P1 * @arg GPIO_PORT_2: to select P2 * @arg GPIO_PORT_3: to select P3 * @retval Value present in the respective Port Register */ unsigned char GPIO_ReadPort(GPIO_Port port) { /* Check for valid Port*/ if (port > 3) return 0; /*Invalid Port*/ switch (port) { case GPIO_PORT_0: /*P0*/ return P0; /*Return Status of the selected Port*/ case GPIO_PORT_1: /*P1*/ return P1; /*Return Status of the selected Port*/ case GPIO_PORT_2:/*P2*/ return P2; /*Return Status of the selected Port*/ case GPIO_PORT_3:/*P3*/ return P3; /*Return Status of the selected Port*/ default: /*Invalid Port*/ return 0; } }
The driver-exposed APIs (or functions) hide most of the low-level operations from the user.
If you want to use these driver files in your project, you need to add both the header (at89s52_gpio.h) and the main driver (at89s52_gpio.c) files in your Keil µVision Project and include the header file in your ‘main.c’ file.
8051 Microcontroller GPIO Driver Demo Example
The following code is a small example that demonstrates the 8051 Microcontroller GPIO Driver. As usual, I put very-detailed comments to make it easy to understand.
#include "at89s52_gpio.h" /*Define the GPIO Pin (P1.0)*/ GPIO_Pin LED_PIN = {1, 0}; /*P1.0*/ /* Delay Function Prototype*/ void delay_ms (unsigned int ms); void main() { /*Configure LED_PIN as Output*/ GPIO_SetDirection(LED_PIN, GPIO_PIN_OUTPUT); while (1) { /*Use the dedicated 'Toggle' API to Toggle State on Pin*/ GPIO_Toggle(LED_PIN); /*Set the period of Blink using delay*/ delay_ms(1000); } } void delay_ms(unsigned int ms) { unsigned int i, j; for(i = 0; i < ms; i++) { for(j = 0; j < 1275; j++) { /*Empty loop to create delay*/ /*This inner loop generates an approximate 1ms delay*/ } } }
I will be using this GPIO Driver in a lot of upcoming 8051 Microcontroller Guide. So, if you want to continue developing such drivers for other peripherals and devices, I suggest you understand this driver very clearly.
Conclusion
This was a simple guide on interfacing LED with 8051 Microcontroller. In this guide, we saw how to design the circuit for connecting an LED to 8051 Microcontroller and how to write a basic, straightforward application to blink the LED.
Additionally, I wrote the 8051 Microcontroller GPIO Driver (library) files that helps in hiding the low-level complexities from the user. If you have any trouble following this guide (especially the driver file), feel free to message me through the comments section. I will reply as soon as possible and even change the structure of the guide to make it easier to understand.