Pflanzenbewässerung mit ESP32 in MicroPython - Teil 2 - Es werde Licht - AZ-Delivery

This sequence is also available as PDF document available.

Plants need light to thrive. That's exactly what we're going to take care of today. Although the days are already much longer, when we are engaged in growing seedlings, but it does not hurt to have additional lighting with the right quality of light. After all, nurseries even have their plants illuminated at night with special lamps. Such a light source of the somewhat different kind we will make and program today. Of course there will be one or the other MicroPython programming trick again. So welcome to a new episode of

MicroPython on the ESP32 and ESP8266


Part 2 - Let there be light

The plants are like us, light brightens the mood and stimulates activity. That is why my seed box will receive a light sprinkler.

While the human eye is most sensitive in the green spectral range, plants prefer light in the red and blue spectral bands.

Figure 1: Plants love red and blue

Figure 1: Plants love red and blue

Well, if this is the case, it is precisely these portions that should be used for illumination. This can be done excellently by using neopixel LEDs. While conventional plant lamps filter out the green component, we don't generate it in the first place, saving a third of the energy. I selected two 8x8 panels, they should provide sufficient brightness. The control with an ESP32 is very simple. The parts have only one small disadvantage: even if no LED is on, a quiescent current of 120mA flows. That alone is about 10 times the value of what the ESP32 draws. To tame the sips, I put a relay in the supply line of the Neopixel panels. This brings us to the hardware list.


I chose an ESP32 as controller, because it has enough free selectable GPIO connectors. We need 10 of them for the full configuration.

The ESP32 models in the parts list are all usable. Only the ESP32-Lolin-board the GPIO25 pin must be used for the I2C connection SDA instead of GPIO21.


ESP32 Dev Kit C unsoldered or

ESP32 Dev Kit C V4 unsoldered or

ESP32 NodeMCU Module WLAN WiFi Development Board with CP2102 or

NodeMCU-ESP-32S kit or

ESP32 Lolin LOLIN32 WiFi Bluetooth Dev Kit


0.91 inch OLED I2C display 128 x 32 pixels


1-relay 5V KY-019 module high-level trigger

or 3's


U 64 LED matrix panel


Photo Resistor Photo Resistor


Resistor 47k


Resistor 220k


Resistor 100k


Resistor 10k


Resistor 1k


Transistor BC548 or similar


MB-102 breadboard plug-in board with 830 contacts


Jumper wire cable 3 x 40 pcs. each 20 cm M2M/ F2M / F2F possibly also

65pcs. jumper wire cable jumpers for breadboard


Power supply 5V / 3A


Logic Analyzer

The software

For flashing and programming the ESP32:

Thonny or


Used firmware for the ESP32:

v1.19.1 (2022-06-18) .bin

The MicroPython programs for the project: Hardware driver for the OLED display API for the OLED display The program for light control

MicroPython - Language - Modules and programs

For the installation of Thonny you find here a detailed manual (english version). In it there is also a description how the Micropython firmware (as of 18.06.2022) on the ESP chip. burned is burned.

MicroPython is an interpreter language. The main difference to the Arduino IDE, where you always and only flash whole programs, is that you only have to flash the MicroPython firmware once at the beginning to the ESP32, so that the controller understands MicroPython instructions. You can use Thonny, µPyCraft or to do this. For Thonny, I have described the procedure here here.

Once the firmware is flashed, you can casually talk to your controller one-on-one, test individual commands, and immediately see the response without having to compile and transfer an entire program first. In fact, that's what bothers me about the Arduino IDE. You simply save an enormous amount of time if you can do simple tests of the syntax and the hardware up to trying out and refining functions and whole program parts via the command line in advance before you knit a program out of it. For this purpose I also like to create small test programs from time to time. As a kind of macro they summarize recurring commands. From such program fragments sometimes whole applications are developed.


If you want the program to start autonomously when the controller is switched on, copy the program text into a newly created blank file. Save this file as in the workspace and upload it to the ESP chip. The program will start automatically at the next reset or power-on.

Test programs

Manually, programs are started from the current editor window in the Thonny IDE via the F5 key. This is quicker than clicking on the Start button, or via the menu Run. Only the modules used in the program must be in the flash of the ESP32.

In between times Arduino IDE again?

If you want to use the controller together with the Arduino IDE again later, simply flash the program in the usual way. However, the ESP32/ESP8266 will then have forgotten that it ever spoke MicroPython. Conversely, any Espressif chip that contains a compiled program from the Arduino IDE or the AT firmware or LUA or ... can easily be flashed with the MicroPython firmware. The process is always like here described.


Figure 2: Light and sound - circuit

Figure 2: Light and sound - circuit

To OLED display and AHT10 from the previous episode come a LDR (Light Dependent Resistor), five normal carbon resistors, a NPN transistor BC548 or similar, an active piezo buzzer, a relay and the two LED panels. I need the buzzer in connection with the water level sensors, which I will cover in the next post. How does this all work now?

Figure 3: entire setup

Figure 3: complete setup

Detect day brightness

If the daytime brightness is sufficient when the sun is shining, we obviously don't need any extra lighting. So the ESP32 has to know when to turn on the LEDs and when the natural lighting is enough. For this purpose there are resistors whose resistance value changes depending on the incidence of light: LDRs. Our LDR has a value of 2MΩ in complete darkness, 440Ω at the window in cloudy weather and 80Ω directly at the room lighting.

Because the ESP32 cannot measure the resistance directly, we have to convert the resistance into a voltage. This can be done with a so called voltage divider. To do this, two resistors are connected in series and of course the same current I flows through them. The resistance formula applies to both resistors. From this we can deduce that the voltages that drop across the resistors are in the same ratio as the resistance values themselves.

Figure 4: Series circuit as resistance-voltage converter

Figure 4: Series connection as resistance-voltage converter

With a fixed value for R1 and the LDR as R2 we get low values as U2 when it is bright outside and values up to U2 = 3.3V when it is dark.

But this already brings us to the next problem. The analog input GPIO34 can only handle 1.1V. So we need a second voltage divider to reduce the maximum 3.3V to a maximum of 1V. So that the second voltage divider does not (significantly) influence the actual measured value, the partial resistors together must be larger than the range in which the LDR operates. The LDR should deliver a voltage of approx. 1.5V at dusk. It will do this if its value is about 45kΩ and we take a resistor of 47kΩ as R1. For the following voltage divider I therefore choose values in the 10-fold range.

Figure 5: Reduction of the voltage for the analog input

Figure 5: Reducing the voltage for the analog input

That was the sensory section on light, let's move on to the actuators relay and LED panel.

The light switch

Because the ESP32 can supply a maximum of 12mA at its outputs, but the panels can draw up to 3.0A, we need either a fat transistor that can handle such currents as a switch, or an electromagnetically operated switch, a relay. A coil with an iron core forms an electromagnet that can move a switching contact. The switching contact S, or armature, is connected to another contact which is normally closed (NC). Opposite to this is a contact which is normally open (NO) to S in the idle state. If current flows through the coil, S is closed against NO and S is opened against NC.

Figure 6: Function of a relay

Figure 6: Function of a relay

Even the excitation current of the solenoid of about 30mA is still too high for a GPIO output. Therefore, a switching transistor lives on the relay module, which can be controlled with a few milliamperes and then allows the current to flow through the coil. The panel current can then flow through the S and NO contacts.

Figure 7: Relay in action

Figure 7: Relay in action

The panels

The LED panels are equipped with WS2812B neopixel LEDs, with the output of one LED connected to the input of the next. Each pixel contains a red, green and blue LED, as well as a controller. The latter receives the data via the DIN line, grabs the first three bytes, digests them, and outputs the subsequent sets of three amplified to DO. Such a packet of three data contains the color information for green, red and blue. Because 256 states can be encoded with one byte, each individual LED can reproduce 256 brightness levels of the respective color, and thus each neopixel unit can shine in 256 x 256 x 256 = 16.77 million hues.

In MicroPython, Neopixel modules are connected via a GPIO pin with the tools from the class NeoPixel which is already included in the kernel. After importing this class from the module neopixel a neopixel object is instantiated. As arguments you have to pass the GPIO pin and the number of LEDs to the constructor.

>>> from neopixel import NeoPixel
>>> np = NeoPixel(Pin(16, 4)
>>> np.buf

When instantiating a bytearray is created as a data buffer containing three times as many elements as there are LEDs on the panel, i.e. 12 in this example. I now write for the first LED the tuple (64,65,66) into this buffer.

>>> np[0]=(65,66,67)
>>> np.buf

The sequence red, green, blue in the tuple becomes the byte sequence for green, red, blue in the buffer. As the bytes are in the buffer, they are also sent to the cascaded LEDs of the panel using the method np.write() method.

The idle level on the neopixel line is LOW. The individual bits are sent as a pulse-pause combination. The period length should be 1.25 µs, it follows that the transmission frequency is 800kHz. The pulse length encodes the bit value. The pulse length and the period length have a tolerance of +/- 150ns.

Figure 8: Pulse diagram of the WS2812B LEDs

Figure 8: Pulse scheme of the WS2812B LEDs

The attributes of a Neopixel object can be listed with the Object Inspector. This can be opened via the View menu.

Figure 9: Call Object Inspector

Figure 9: Open Object Inspector

In the terminal, simply call the Neopixel object.

>>> np

Figure 10: Neopixel object in Object Inspector

Figure 10: Neopixel object in the Inspector object

Order specifies the order in which the color codes from the tuple (65,66,67) are sent: 66, 65, 67. bpp specifies that we use 3 bytes per pixel. In the tuple timing are the pulse-pause times. They differ from the datasheet, but are within the tolerance limits. Also MicroPython does not work exactly with these values, as I will show below. If the transmission does not work cleanly, you can adjust the values of the four-tuple yourself.

>>> np.timing
(400, 850, 800, 450)
>>> np.timing=(350,900,900,350)
>>> np.timing
(350, 900, 900, 350)

Just as the pixels are lined up on the panel, you can also cascade multiple panels by connecting the OUT output of one panel to the IN input of the next. I fixed my two panels to a 2mm glass plate with adhesive tape. On the top left I feed the voltage from the power supply, on the bottom right comes the line from GPIO34 of the ESP32.

NOTICE: During the development phase, it is essential to connect the GND terminal of the power supply to the GND potential of the superstructure in order to compensate for voltage differences between these parts.

Figure 11: Neopixel panels from behind

Figure 11: Neopixel panels from behind

One burst (pulse train) for the 128 pixels takes about 4ms. I calculated this with the Logic Analyzer and the free program Logic 2 recorded.

Figure 12: Whole burst

Figure 12: Whole burst

I'm going to zoom in on the beginning.

Figure 13: Actual pulse train recorded with Logic 2

Figure 13: Actual pulse sequence recorded with Logic 2

As you can see, the values deviate in part considerably from the specifications in the Object Inspector, but are still within the tolerance range of the data sheet.

The illumination program

Like most MicroPython programs, the illumination program starts as follows with the import business.

from machine import SoftI2C, Pin, ADC
from time import sleep, ticks_ms, sleep_ms
from oled import OLED
import sys
from neopixel import NeoPixel

The variable debug is used in case of errors or mystical program behavior to trigger text output at neuralgic points if they are set to True is set.


I need the I2C interface for the OLED display. The constructor gets the I2C object and the height of the display in pixels.


The ADC object will read the voltage at the LDR. We get voltages up to a maximum of 1 volt in total darkness and values around 0V in normal daylight.


The first neopixel panel is located at GPIO16. Together both panels have 128 pixels. The timing is set to the specifications of the WS2812B data sheet and the variables to and off define the color codes for light on and off. Because the quiescent current of the panels is quite high, I need a general light switch, the relay. GPIO15 switches on at 1 and off at 0.

np = NeoPixel(neoPin, neoCnt)
on =(63,0,50)

To be able to exit the program at a defined point, I use the flash key of the ESP32 as an abort key. With Ctrl + C the abort would be done at a random position. Then you never know in which state the circuit is.


A few functions are declared, first of all light(). The function optionally takes a 3-tuple with RGB values. By default the tuple to is submitted. If the red value is not 0, I activate the relay and fill the buffer. This could be done via a for loop, but fill() does it with a single statement, using the passed tuple. All the pixels then light up in the same color. After a short wait, I have the buffer sent to the panels. This is the burst from Figure 12.

But if the red value is 0, the relay and thus the supply line of the panels, is simply switched off.

def light(val=an):
   if r != 0:

getADC() takes an ADC object and optionally a value that specifies the number of individual conversions. By passing the ADC object, the function can be used flexibly for the other analog inputs as well. The sum variable sum is set to 0. The for loop works with the temporary variable _because the run index is not needed in the loop body. Each newly read value is added to the previous sum. The integer part of the arithmetic mean is returned as the result.

def getADC(adc,n=50):
   for _ in range(n):
       sum = sum +
   avg = int(sum/n)
   return avg  

The function illumination() needs the result of getADC() and therefore calls the function with the ADC object ldr as argument. The return value in h is output in the terminal of Thonny if debug to True is set. The value with which h is compared decides at what dim light from outside the lighting in the seed box is switched on. Higher values of h mean increasing darkness. It is best to determine the threshold value empirically. To switch on and off light() with the variables to and off called

def illumination():
   if debug: print("LDR: ",h)
   if h > 140:
   return h

After the output of the heading we enter the main loop. It does not have to do much, calling the function lighting(), output the value to the display, wait a second and check if the flash key is pressed. If so, clear the display and output an abort message. Then, above all, turn off the light and goodbye.

For the test it is sufficient to start with the values in to = (63,0,25). The current through the panels is then 900mA. In production mode, you then adjust the brightness according to your needs. If you drive full speed, the current will be around 3.0A.

In the next sequence we will take care of the water supply of the plant tray. Moisture sensors will monitor the levels in the tray and in the reservoir, and a pump will take care of refilling the tray. When there is no more water, the piezo beeper, which I control in an unusual way, will warn you. Let yourself be surprised.

See you then!

DisplaysEsp-32Projekte für anfängerSensorenSmart home

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery