Javier Bonilla

Researcher at CIEMAT - PSA
PhD. in Computer Science / Solar Thermal Energy Researcher
Javier Bonilla

Helpful? spread the word!

This is a basic tutorial about how to use a breadboard with a ESP8266 NodeMCU board to dim a LED applying Pulse-Width Modulation (PWM).


If you need further clarification about some concepts in this tutorial, have a look at the following posts. They explain concepts and terminology from scratch. The first one is an introduction to ESP8266 NodeMCU boards whereas the second one shows how to program with Arduino Integrated Development Environment (IDE).


  • Using a Breadboard
  • ESP8266 NodeMCU pinout
  • Analog and Digital Inputs / Outputs
  • Wiring a LED
  • Writing and Testing the Code

Using a Breadboard

A breadboard, also known as protoboard, is a solderless device for prototyping electronic circuits. We only have to insert the electronic components’ terminals in the holes and connecting them through wires. They are available in different sizes and shape factors, but they all work in the same way.

Have a look at the picture below. The black lines determine how the pins are connected together inside the breadboard. Pins in the horizontal lines are wired together. The red line (+ sign) is intended to be used as a power bus, whereas the blue line (- sign) is generally used as a ground bus. Vertical pins are also wired together up to the division. This division facilitates mounting integrated circuits and other components in the breadboard.


ESP8266 NodeMCU pinout

Let’s consider a NodeMCU V2 board, the NodeMCU V3 for practical purposes is identical. In this tutorial, we are interested in power pins (3.3 V), ground pins (GND) and GPIO pins. Commonly, NodeMCU boards have only one analog pin (A0) and several digital pins D0 – D9 (GPIO XX).

NodeMCU V2 pinout
NodeMCU V2 pinout

If we want to interact with a digital pin we have to remember the GPIO number (0..16), whereas for the analog pin the alias is used (A0). Digital pins can be used as inputs or outputs, however the analog pin can only be used as an input. By default, pins are set as inputs, but it is always a good practice to specify how they will be used in our program.


Analog and Digital Inputs / Outputs

A digital pin can be used as a digital input or output, where we can read or write HIGH (3.3V) or LOW (0V) values. As we will see, a digital pin can also be used as an analog output by means of Pulse-Width Modulation (PWM).

On the other hand, an analog pin can be used as an analog input, but not as an output. This is different than in Arduino boards, where analog pins can also be used as digital inputs and outputs. Also, we must take into account that the maximum allowed voltage for the analog pin is 1 V.

Next are shown the Arduino functions that we can use to read and write values. Remember that we have to properly set pins as inputs or outputs in advanced in order to read or write values.

  • digitalRead(pin) – It returns LOW (0 V) or HIGH (3.3 V).
  • digitalWrite(pin,value) – It writes LOW or HIGH.
  • analogRead(pin) – It returns a value between 0 (0 V) and 1023 (1 V), because the resolution of the analog to digital converter is 10 bits, 2^10 = 1024.
  • analogWrite(pin,value) – It writes a value between 0 (0 V) and 1023 (3.3 V), because the resolution is also 10 bits. The analogWrite resolution is 8 bits in most Arduino boards, 2^8 = 256.
   digitalRead(pin)LOW / HIGHD
   digitalWrite(pin,value)LOW / HIGHD
   analogRead(pin)0 – 1023A
   analogWrite(pin,value)0 – 1023D

Now, the question is: how can we set an analog value in a digital pin? since a digital pin can only be set to digital values (LOW / HIGH). The answer is Pulse-Width Modulation (PWM).

Digital signals are only read in particular points in time. The time interval between two “readings” is called cycle. This cycle have a period (for instance, 2 ms). We can emulate analog signals by setting the digital pin HIGH only a percentage of the cycle period. This percentage can be set from 0% (0) to 100% (1023) in approximately 0.1% intervals (100/1024).

A LED with PWM turns on and off on each cycle, where the on period in the cycle is determined by a specified percentage, remember from 0% (0) to 100% (1023). The on / off transition is performed so fast that our eyes cannot see the LED blinking and our brain interprets this as a weighted average brightness between the HIGH and LOW values, were the weights are the percentages for each state. For a LED, the LOW state is no brightness at all, and the HIGH the maximum brightness. This maximum brightness is defined by the maximum current which is determined by the circuit resistance.

Have a look at the Arduino PWM tutorial for detailed information. The analogWrite function automatically perform PWM in digital pins.

Wiring a LED

Light Emitting Diodes (LEDs) are polarized, so it matters in which direction you wire them up. The positive (+) and larger lead is the anode and the negative (-) and shorter lead is the cathode. The cathode can also be identified because the casing lip is flat in that side.

Light Emitting Diodes (LEDs)
Light Emitting Diodes (LEDs)

When working with LEDs, we commonly have to use a resistor in order to limit the current to a safe value, otherwise when can burn the LED out. The maximum permitted current is given by the LED manufacturer, if we don’t have this information available, we can assume a maximum 20 mA as a safe value.

LEDs are diodes and the current through them grows exponentially with voltage. Also, small changes in voltage can produce huge changes in current. Commonly, LEDs have a voltage drop from 1.8 V to 3.3 V. To be on the safe side, let’s consider 1.8 V.

Since NodeMCU boards provide 3.3 V, we need to increase the voltage drop, 3.3 V – 1.8 V = 1.5 V. Then, we can apply Ohm’s law to calculate the minimum resistance to increase the voltage drop and dissipate the extra power, R=V / I, R = 1.5 V / 20 mA = 90 Ω. The following formula summarizes the calculation.

R_{LED} = \dfrac{V_{source} - V_{LED}}{I_{LED}} = \dfrac{3.3 \mathrm{V} - 1.8 \mathrm{V}}{20 \mathrm{mA}} = 90

Since resistors are commonly available at standard values (100 Ω, 220 Ω, 330 Ω, etc), we should choose a resistance higher than 90 Ω to not damage the LED, let’s say 100 Ω. However, there are other factors that we should take into account, such as the tolerance and the temperature coefficient. Have a look at this great resistor color code tutorial for learning more about how to identify those parameters.

So, to be on the safe side, let’s consider a 220-Ω resistor. From a practical point of view you can use 220-Ω or 330-Ω resistors, or even higher values. The only difference is that increasing the resistance decreases the current and therefore the maximum LED brightness (up to a point where no light is emitted at all).

Resistors are not polarized, so we don’t have to worry about the connection direction. Also, it doesn’t matter if the resistor is connected to the to the LED anode or cathode, since the voltage drop is the same across the circuit.

This diagram shows how the LED is connected to the NodeMCU board in our example.

LED connected to a NodeMCU V2 board
LED connected to a NodeMCU V2 board

Writing and Testing the Code

This program simply sets the LED brightness (analogWrite) from 0 (0%) to 1023 (100%) forward and backward using PWM. When a particular level is set, a delay is introduced (delay). We also turn off the built-in LED in the setup function. The LED_BUILTIN constant defines its GPIO pin number (16). Notice that the built-in LED is off at HIGH level.

const unsigned int LED_PIN    = 5; // GPIO pin number
const unsigned int DELAY_TIME = 5; // Milliseconds
const unsigned int MIN_LEVEL  = 0;
const unsigned int MAX_LEVEL  = 1023;

void setup() {
  Serial.begin(115200); // Data rate in bits per second
  pinMode(LED_PIN,OUTPUT); // Set LED pin as output
  digitalWrite(LED_BUILTIN,HIGH); // Built-in LED off

void loop() {
  unsigned int i;

  for(i=MIN_LEVEL;i<MAX_LEVEL;i++) // [0,1022]

  for(i=MAX_LEVEL;i>MIN_LEVEL;i--) // [1023,1]

void analogWriteDelay(unsigned int pin, 
                      unsigned int value, 
                      unsigned int waitTime) {
    analogWrite(pin,value); // Analog write in LED pin
    delay(waitTime);        // Delay in milliseconds  

Finally, we can see our LED in action! If you want to keep testing, set LED_PIN to LED_BUILTIN and connect the LED anode to D0 (GPIO 16). You will see two LEDs in action (the built-in LED and your own LED) but in opposite states since the built-in LED is on at LOW state whereas external LEDs are on at HIGH states.

Dimmable LED with Pulse-Width Modulation (PWM) – ESP8266 NodeMCU
Give us your opinion!

Leave a Reply

Notify of