Implementing an LED Breathing Light Using the 51 Microcontroller

Jun 11, 2026 Leave a message

[Introduction]


Microcontroller technology is an indispensable mainstream technology in modern industrial automation, electronics, electrical engineering, and the Internet of Things (IoT). As our lives become increasingly smart, microcontroller technology has permeated virtually every aspect of our daily lives, such as in smart rice cookers, smart speakers, and more.


With this in mind, the "Relearning the 51 Microcontroller" series of articles aims to help beginners get started with microcontroller technology. We'll start with the simplest task-turning on a single LED-and gradually progress to implementing modules such as button controls, the LCD1602 display, DS18B20 and DS1302 temperature sensors, and communication between two microcontrollers. We'll also cover hardware communication protocols like UART, I²C, and SPI. By combining these concepts with C programming techniques, we'll use real-world engineering projects to illustrate programming approaches, enabling you to flexibly apply C pointers and structures to achieve modular programming.


Now, let's get back to the main topic: using a 51 microcontroller to control an LED and create a breathing light effect.

 

[How Breathing Lights Work]


Let's first take a look at how a breathing light effect works.
A breathing light gradually brightens and then gradually dims, repeating this cycle in a manner that resembles breathing. However, since a microcontroller's pins can only output either a 1 (on) or a 0 (off), how can a gradual transition effect be achieved?
This is due to the persistence of vision in our eyes. When we look at something, the image formed by our eyes persists for 0.04 seconds (this figure was found online).
If we calculate based on 0.04 seconds, that equals 40 ms. Therefore, when the LED is on and off for 20 ms each, it appears to the human eye as if it is constantly lit.

图片

Is the effect of an LED flashing on for 20 ms and off for 20 ms the same as having it stay on all the time?

 

Haha, it's definitely different. When the light alternates between on and off every 20 milliseconds, the effect we see is dimmer than when it stays on continuously. If we assume that the brightness of a continuously lit light is 100%, then the brightness of a light that alternates between on and off every 20 milliseconds is 50%. Based on this, we can adjust the LED's brightness.

图片

 

At this point, we can adjust the brightness of the LED (by setting the duration of the high level within the 40ms cycle). This is the principle behind the well-known PWM (Pulse Width Modulation) method of brightness control, and setting the duration of the high level is equivalent to adjusting the duty cycle (i.e., the duration of the high level divided by the total cycle: 20/40 = 50%).


Here, the most important factor is this duty cycle. For example, if the period is 20 ms, with the LED alternating between 10 ms on and 10 ms off, the perceived brightness is still 50% (i.e., the duty cycle is 10/20 = 50%).

Next, let's see how this is implemented in the program.

 

[Program Implementation]


Turning on an LED
First, let's start by turning on an LED, and then we'll gradually implement a breathing light effect. The hardware we'll be using is as follows:

Development Board ZeroOne Microcontroller Training Development Board
Microcontroller Model STC89C52
LED Interface Pin P4^4
图片

 

From the schematic, we can see that the LED is connected to pin P4^4 of the microcontroller. When the microcontroller outputs a 1, the LED turns on; when it outputs a 0, the LED turns off. Therefore, the program to turn on an LED is quite simple, as shown below:

info-299-230

 

 

The program to turn on an LED is quite simple; I'm sure everyone knows how to do it.
 

Adjusting LED Brightness


Next, we'll implement a function that allows us to adjust the brightness (i.e., adjust the duty cycle), as follows:

info-400-538


 

Define a static variable `duty_cycle` to store the duty cycle. When `flag` is 1, the duty cycle gradually increases to 255, then set `flag` to 0, and `duty_cycle` gradually decreases from 255 to 0. Repeat this cycle.
Haha, at this point, you might think the breathing light is already working, but it's not. If you don't believe me, try the code above yourself.

So where exactly is the problem?


The problem lies in our direct call to the brightness-setting function `set_led_luminance()`. This function takes 40 ms to complete one cycle, meaning the duty cycle cannot be changed during those 40 ms; otherwise, the brightness adjustment won't work. Let's take another look at the `breath_led` function. After each call to `set_led_luminance()` to set the duty cycle, it immediately changes the `duty_cycle` value without waiting for 40 ms.


At this point, we need to add a software timer to update the `duty_cycle` value after 40 ms has elapsed. The modified program is as follows:

info-495-656

 

 

Note: The timer duration just needs to be greater than 40 ms (meaning the value of `s_breathCounter` must be greater than 255), but it's best to set it to a multiple of the cycle. For example, if our cycle is 255 (i.e., 256 values from 0 to 255), we can set it to twice that value: 256 * 2 - 1 = 511 (i.e., 512 values from 0 to 511).
And there you have it-our breathing light is complete! Isn't that simple? (* ̄︶ ̄)

 

Next up is today's bonus section.

Although we've achieved the breathing light effect, the code isn't quite concise or elegant enough-it uses a bunch of if and else statements. Let's see if we can simplify it further.

First, let's simplify this section of the set_led_luminance() function, as shown in the figure below.
 

图片

 

Before we simplify this, let's cover a quick tip about C: the bitwise AND operation.

info-600-117

 

From this, we know that whether it is 1 or 0, performing a bitwise AND operation with 0 results in 0.

Whether it is 1 or 0, performing a bitwise AND operation with 1 results in the original value.

For convenience, we use hexadecimal notation (prefixed with "0x"); for example, 0xff corresponds to 255 in decimal. Therefore,

 

When a number less than or equal to 0xff is ANDed with 0xff, the result is the number itself, as shown below

info-534-63

 

 

What happens if you perform a bitwise AND operation between a number greater than 0xff and 0xff?

info-551-63

The result is the remainder when this number is divided by (0xff + 1) (i.e., the result is still between 0 and 0xff).
With this bitwise AND operation, the code above can be simplified to

info-550-64

 

 

This way, the value of s_Counter will always be within the range of 0x00 to 0xff.
Similarly, the software timer in the breath_led function above can also be simplified as follows:

info-389-353

In line 3, 0x1ff is 511 in decimal. The condition is true when the value of s_breathCounter is (0x1ff+1), or 512, because 512 & 0x1ff = 0. The exclamation mark before it indicates a bitwise NOT operation (to be precise, the condition is met when the value of `s_breathCounter` is a multiple of 512; this eliminates the need to reset `s_breathCounter`. I hope this explanation is clear-please think it through). If the condition is met, the duty cycle begins to increase or decrease.


But isn't the duty cycle range 0 to 255? Why does line 5 also become 0x1ff (511)? Don't worry-look at line 8. We subtract 0xff again, so the duty cycle range remains 0 to 255.


Lines 7–10 mean: When `duty_cycle > (0xff)`, i.e., 256–511, subtracting 0xff is equivalent to increasing from 1 to 255, so the brightness gradually increases.


When duty_cycle <= 0xff, the duty_cycle increases from 0 to 255, while the set brightness is 255 - duty_cycle. This effectively decreases the brightness from 255 to 0, causing the light to gradually dim. This achieves the breathing light effect.


Haha, did you think our simplification was over?

 

No, no, no

Actually, lines 7 through 10 could be simplified even further. This is where the absolute value function comes in handy.

What? Why use the absolute value function?

 

Look at line 10: 0xff - duty_cycle is equivalent to duty_cycle - 0xff and then taking the absolute value. Okay, here is the simplified code:

set_led_luminance(ABS(duty_cycle - (0xff)));

 

 

The macro function for taking the absolute value is as follows

#define ABS(N) ((N) < 0 ? -(N) : (N))

 

Finally, I've included the entire simplified code below

 

info-562-684

What do you think? Isn't it simple? (* ̄︶ ̄)

Send Inquiry

whatsapp

Phone

E-mail

Inquiry