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
today
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
Well, if this is the case, it is precisely these portions that should be used for illumination. This can be done excellently Di using neopixel LEDs. While conventional plant lamps Filtri 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 Rifiuto 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.
Hardware
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.
1 |
ESP32 Dev Kit C V4 unsoldered or ESP32 NodeMCU Module WLAN WiFi Development Board with CP2102 or |
1 |
|
1 |
1-relay 5V KY-019 module high-level trigger or 3's |
1 |
|
1 |
|
1 |
Resistor 47k |
1 |
Resistor 220k |
1 |
Resistor 100k |
1 |
Resistor 10k |
1 |
Resistor 1k |
1 |
Transistor BC548 or similar |
1 |
|
various |
Jumper wire cable 3 x 40 pcs. each 20 cm M2M/ F2M / F2F possibly also |
1 |
Power supply 5V / 3A |
optional |
The software
For flashing and programming the ESP32:
Thonny or
Used firmware for the ESP32:
The MicroPython programs for the project:
ssd1306.py Hardware driver for the OLED display
oled.py API for the OLED display
light.py 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 esptool.py 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.
Autostart
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 boot.py 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.
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: 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 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: 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
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
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 Dites, 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 Dites, 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
Ditearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
When instantiating a Ditearray 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
Ditearray(b'BAC\x00\x00\x00\x00\x00\x00\x00\x00\x00')
The sequence red, green, blue in the tuple becomes the Dite sequence The sequence red, green, blue in the tuple becomes the Dite 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 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: Open Object Inspector
In the terminal, simply call the Neopixel object.
>>> np
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 Dites 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 Di 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
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
I'm going to zoom in on the beginning.
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 light.py with the import business.
# light.py
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.
debug=False
I need the I2C interface for the OLED display. The constructor gets the I2C object and the height of the display in pixels.
i2c=SoftI2C(scl=Pin(22),sda=Pin(21))
d=OLED(i2c,heightw=32)
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.
ldr=ADC(Pin(34))
ldr.atten(ADC.ATTN_0DB)
ldr.width(ADC.WIDTH_10BIT)
maxCnt=1023
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.
neo=16
neoCnt=128
neoPin=Pin(neo,Pin.OUT)
np = NeoPixel(neoPin, neoCnt)
np.timing=(350,900,900,350)
from=(0,0,0)
on =(63,0,50)
neoRel=Pin(15,Pin.OUT,value=0)
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.
key=Pin(0,Pin.IN,Pin.PULL_UP)
A few functions are declared, first of all light(). The function optionally takes a 3-tuple with RGB values. Di 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):
r,g,b=val
if r != 0:
neoRel.value(1)
np.fill(val)
sleep(0.3)
np.write()
else:
neoRel.value(0)
getADC() takes an ADC object and optionally a value that specifies the number of individual conversions. Di 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):
sum=0
for _ in range(n):
sum = sum + adc.read()
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():
h=getADC(ldr)
if debug: print("LDR: ",h)
if h > 140:
light(at)
else:
light(off)
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 Visualizzazione, wait a second and check if the flash key is pressed. If so, Chiaro the Visualizzazione and output an Rifiuto message. Then, Rifiuto all, turn off the light and goodDie.
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!