The necklace lamp is nice for the Christmas season, but on the one hand not interactive and not exactly in terms of style and as out of place for the rest of the year as the new gingerbread in August or the Easter bunnies shortly after the New Year. Therefore, today and in the following articles, we want to think about how we can create a lighting object from the Engellampe, the lighting effects of which can be controlled directly on the device.
Last summer, suggested by a visit to a friend of a friend, I LED lamp for cozy summer evenings Built and programmed. I finally have the part with one App from Android cell phone controlled. Innovative was the use of a neopixel ring for me, which could change the light color, contrary to the original found.
When visiting a restaurant, the variant of such a table lamp literally got my fingers a few weeks ago. The thing was to be controlled by touching, but in a somewhat mystical way. Reason enough to further research with the use of an ESP32. And - it was successful, of course using Micropython. Here is the result in a further episode from the series
Micropython on the ESP32 and ESP8266
today
A table lamp with touch control
With the cell phone app you naturally have many options to control a table lamp. But the Probs already start with the decision - WLAN via router or via the controller's own access point. This must be taken into account in the program or are already available at the start.
It would be nice if the light output could be controlled interactively directly on the device. We will now build and program a device. The first choice controller was an ESP32 because it has touch pad inputs. Further considerations for the use of an ESP8266 or the Raspberry Pi Pico then produced further solutions to the touch problem. We will deal with these in the next episodes. Today we stay with the ESP32. I will show how you can permanently store data in the NVS area and how to achieve that processes are carried out in parallel. At least the first feature only offers the ESP32.
The hardware
An ESP32, an RGB LED, a neopixel ring and, for the beginning, three board pieces, represent the hardware for this project. In addition, we naturally need the corresponding mechanical basis so that the pure electronics become a device that can be used we later.
For the Controllerboard ESP32 Dev Kit C V4 used, two Breadboards are needed to develop the circuit, which are put together on the side via a current rail. This is the only way to get enough free contact points for the jumpling cables.
Figure 1: Setup with ESP32
As a touchpad, I used the remaining pieces of a circuit board and a crest. Small aluminum children will serve in the production system.
I use the RGB LED to display the operating status. The colors of the LEDs on the ring can be set up individually and down. The RGB LED shows the color. There will be a total of five conditions: red, green, blue, white and in.
The pre -resistances are dimensioned in such a way that together white light comes out. The blue and especially the green LED are much brighter than the red. Therefore, the big differences in the resistance values stir. If you want a lighter display, just take smaller ohmic values.
The circuit picture in Figure 2 shows how everything belongs together.
Figure 2: circuit with ESP32 and touchpads
As an energy supply, I chose a Li-ion cell of type 18650, plus a battery owner with a loading part and 5V output. I soldered the supply lines for my circuit on the connection spins of the USB A socket. In this way I get along without USB plugs and can still use the mechanical switch on the board. It will point in the housing towards the ground, as well as the loading socket at the upper end of the board in Figure 2.
Because there will be too little space in the lamp for the Breadboards, I have designed a circuit board on which the few individual parts can be accommodated. You can do the layout as Download the PDF file.
Figure 3: sensor lamp - layout
I used an RGB LED module in the experimental setup. For the lamp itself, I found one of the LEDs from the range more optimally because of the easier assembly in the lid. The LED is simply glued into the frame with two -component adhesives.
Figure 4: RGB LED with a common cathode
The lamp parts
Housing frame and lid are made of wood. The frame consists of 15mm maple boards 45mm x 150mm. As a lid, I took a 5mm thick plate to which a wooden cylinder was glued to the center of the brass tube in the middle.
Figure 5: Lamp parts
Figure 6: Frame strips before gluing
Figure 7: Installation in the housing frame and lamp holder in the lid
Figure 8: Sensor keys and signal LED
The lamp head sits on a 230mm long brass tube with 6mm Ø.
Figure 9: LED ring in the lamp head
The software
For flashing and the programming of the ESP32:
Thonny or
Signal tracking:
Used firmware for an ESP32:
The micropython programs for the project:
timeout.py Non-blocking software timer
touch.py TouchPad module
touchlampy.py Operating program
Micropython - Language - Modules and Programs
To install Thonny you will find one here detailed instructions (English version). There is also a description of how that Micropython firmware (As of 18.06.2022) on the ESP chip burned becomes.
Micropython is an interpreter language. The main difference to the Arduino IDE, where you always flash entire programs, is that you only have to flash the Micropython firmware once on the ESP32 so that the controller understands micropython instructions. You can use Thonny, µpycraft or ESPTOOL.PY. For Thonny I have the process here described.
As soon as the firmware has flashed, you can easily talk to your controller in a dialogue, test individual commands and see the answer immediately without having to compile and transmit an entire program beforehand. That is exactly what bothers me on the Arduino IDE. You simply save an enormous time if you can check simple tests of the syntax and hardware to trying out and refining functions and entire program parts via the command line before knitting a program from it. For this purpose, I always like to create small test programs. As a kind of macro, they summarize recurring commands. Whole applications then develop from such program fragments.
Autostart
If the program is to start autonomously by switching on the controller, copy the program text into a newly created blank tile. Save this file from Main.py in WorkSpace and upload it to the ESP chip. The program starts automatically the next time the reset or switching on.
Test programs
Programs from the current editor window in the Thonny-IDE are started manually via the F5 button. This can be done faster than the mouse click 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, Arduino id again?
Should you later use the controller together with the Arduino IDE, just flash the program in the usual way. However, the ESP32/ESP8266 then forgot that it has ever spoken Micropython. Conversely, any espressif chip that contains a compiled program from the Arduino IDE or AT-Firmware or Lua or ... can be easily provided with the micropython firmware. The process is always like here described.
The neopixel ring
Type WS2812 neopixel LEDs contain three individual LEDs that release red, green or blue light. They are addressed by a controller who receives its instructions via a kind of bus system that is clocked with 800kHz.
At the I2C bus or the SPI bus, the signals from the controller, for example an ESP32, reach all slaves on the bus in the same way, everyone sees everything. It is different with the WS2812 modules. Each component has a data input and data output. Several building blocks can be cascaded by connecting the data input of each additional component to the data output of the predecessor. The first component in the chain receives a pulse chain of three bytes for R, G and B from the ESP32, which he feeds himself, i.e. removed from the entire pulse. All subsequent impulses are waved through and handed over at the data output. Every component in the chain gets its share in the same way and passes on the rest. This makes it possible for each component to be controlled individually in the chain. The intensity of each color can be varied in 256 steps, depending on the value of the received bytes for the individual color. We give the color code in the form of one list with three bytes for each WS2812.
Let's start with an attempt. We use the structure of Figure 2. First the Class Pin and Neopixel imported. I create a GPIO pin object as an outcome and thus instance a neopixel object with eight components.
The neopixel instance neo Contains a bytearar buf And the method write(), with which the content of the bytearar is transferred to the neopixel ring.
XXXXXXXXXX
>>> From machine import Pin
>>> From neopixel import Neopixel
>>> NP=Pin(13,Pin.OUT)
>>> neo=Neopixel(NP,8)
>>> neo[0]=(0xe0,0x07,0x3c)
>>> neo[1]=(0xf0,0xf0,0xf0)
>>> neo.write()
>>> neo.buf
bytearar(B '\ x07 \ XE0 <\ xf0 \ xf0 \ xf0 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00')
With neoI address the first three elements of the array and hand over the values for red, green and blue, 0xe0, 0x07 and 0x3c. Internally, this makes the private function setitem(). The values are entered in the buffer in a changed order. As we will see at the curve, which I recorded with the help of the Logic Analyzer. The values are sent as they are in the buffer. One 0 corresponds to a narrow impulse of approx. 500NS width followed by a break of approx. 750ns. The 1 is sent by a pulse of 750ns and a break of 500ns. It is striking that the first two bytes are sent in exchanged order. This corresponds to the information from the data sheet and can also be verified as follows.
XXXXXXXXXX
>>> neo.ORDER
(1, 0, 2, 3)
Figure 10: Pulse sequence for RGB = 0xe0, 0x07, 0x3c, 0xF0, 0xF0, 0xF0
At the output of the first WS2812B, the three bytes 0xe0, 0x07 and 0x3c are missing, which has eaten this building block. Instead, the Code 0xF0, 0xF0, 0xF0 comes out time -delayed, the code for the second component. The input for channel 1 of the Logic Analyzer was connected to this measurement at the entrance of the first LED, channel 2 at the output of it.
Now we know how the kangaroo on the WS2812B is running and can turn to the program for the lamp.
The program
We want to control the neo-LEDs with three sensor pads. The color selection is carried out with the touchpad of GPIO33. There are five channels: on/off, red, green, blue and light/dark. The intensity is increased to GPIO32 and reduced to GPIO27. The operation of these three activities in the background runs in parallel in three functions. This is made possible by the module asyncio.
At first I tried to do the module for that _thread to use. This worked well for two processes, but refused to serve for the third party. I couldn't discover what was the cause of this. So we work with asyncio.
We start with the import business. The module touch Support us with the methods of touch pads. Pin we need for the LED outputs. Over Neopixel we manage the WS2812B of the LED ring. The Alias NP Allow us a shorter spelling. The methods serve for small naps sleep and Sleep_ms From the module time. Uasyncio Is the version adapted to Micropython asyncio From C-Python, we also miss this module an alias. That the class NVS is only available for the ESP32 ESP32 to see from where we import the class. NVS is acronym for non -volatile storage. This allows us to permanently store data in the system. We use this to take off our favorite color combination, which is then automatically loaded again at the cold start. (Download: timeout.py touch.py touchlampy.py)
XXXXXXXXXX
# touchlampy.py
From touch import TP
From machine import Pin
From neopixel import Neopixel AS NP
From time import sleep, Sleep_ms
import Uasyncio AS asyncio
From ESP32 import NVS
XXXXXXXXXX
NVS=NVS("Config")
The NVS area is partitioned in name spaces (name spaces) in which Signed 32-bit number And so -called Binary Blobs, so bytes objects can be stored. The storage points are addressed as a key by a string. Our partition bears the name config.
Here we set the touchpad objects. The first argument is the GPIO pin number. GPIO33 therefore stands for Touch8, GPIO32 for Touch9 and GPIO27 for Touch7. The second argument is a limit from which the touchpad is classified as touched. When touched, the read value and the method drops touched() delivers a 1, otherwise a 0.
XXXXXXXXXX
TP1=TP(33, 350)
TP2=TP(32, 350)
TP3=TP(27, 350)
To set up the limit value, we ask the corresponding input of the TP object directly without touch.
XXXXXXXXXX
>>> TP1.TPIN.read()
486
>>> TP1.touched()
0
Then with touch.
XXXXXXXXXX
>>> TP1.TPIN.read()
49
>>> TP1.touched()
1
We connect the signal line of the LED ring to GPIO13. The ring has 8 LEDs, just as well you can use a ring with 12 LEDs. Of course, it also sucks the battery empty. With the 8-series ring, the current consumption is between 40mA and 250 mA. When using a 2200 mAh cell, a load is enough for about eight hours upwards, depending on the brightness set.
XXXXXXXXXX
neopin=Pin(13,Pin.OUT)
neo=NP(neopin,8)
channel=0 # sw = 0, rt = 1, gn = 2, bl = 3, ws = 4
color=[0,0,0]
I have already listed the meaning of the 5 channels above. The color in color As a list of the code "All LEDs out" = [0.0.0].
This is followed by the definitions of the routines for saving and reading the color codes in the NVS area. The color names serve as the keys, the values come from the list elements. Important: Only with the command commit() are transferred to the memory.
XXXXXXXXXX
def storecolor():
NVS.Set_i32("Red",color[0])
NVS.Set_i32("Green",color[1])
NVS.Set_i32("Blue",color[2])
NVS.commit()
print("Fardcode secured",color)
XXXXXXXXXX
def loadcolor():
RT=NVS.get_i32("Red")
gn=NVS.get_i32("Green")
BL=NVS.get_i32("Blue")
print("Fardcode invited:",[RT,gn,BL])
return [RT,gn,BL]
Reading is easier. We return the color values in the form of a list. If we omitted the square brackets, we would get a return object Tupel. We cannot need that, because we want to change the element values in the program, and that is not possible with a tupel.
At the first start is the namespace config still empty. Reading access therefore throws an exception that we have with Try Except intercept. Instead of reading the color code, we write the code defined above in the memory. At the next start, the keys red, green and blue exist.
XXXXXXXXXX
try:
color=loadcolor()
except:
storecolor()
We continue with the definition of other variables and objects. realize Is a fleeting intermediate memory for the current color code if we switch the LEDs dark during operation. noted Then we take with us True.
Then we define the outputs for the RGB LED. We put the objects in the tupel LEDs together. Because the object references are not changed, a tupel can be safely used. Also for shapes a tupel can be taken. The elements are in turn tuels and determine how cables to be switched to the signal LED. In channel stand the names for the four channels, which we for the output in Replica need.
XXXXXXXXXX
realize=color
noted=False
Ledr=Pin(2,Pin.OUT,value=0)
LEDB=Pin(15,Pin.OUT,value=0)
LedG=Pin(4,Pin.OUT,value=0)
LEDs=(Ledr,LedG,LEDB)
# Made of red green blue white
shapes=((0,0,0),(1,0,0),(0,1,0),(0,0,1),(1,1,1),)
channel=("Red","Green","Blue","White")
The function lum() Fill the neopixel buffer with the color code and send the buffer content to the ring via the GPIO13 output.
XXXXXXXXXX
def lum():
for I in range(8):
neo[I]=color
neo.write()
Now comes the module asyncio into play. We thus define three functions that take on the touch control. With TP1 If we switch through the channels, save the current color in Nvs.config And switch the ring dark. We decodes what should happen in each case with the period with which the pad is touched.
A function that is supposed to represent a process in the asyncio system must be async def be initiated. The process ends as soon as the function is left. We don't want that, so the process runs as an endless loop. The objects channel and color Experience potential changes that we have to use elsewhere, so we declare them when starting the function as global.
The normal Sleep_ms() out of time we replace with the asyncio-Variant asyncio.sleep_ms(). With the line
XXXXXXXXXX
await asyncio.Sleep_ms(10)
We signal the system that the process can be interrupted at this point in order to operate other processes.
XXXXXXXXXX
async def switch channel():
global channel, color
while 1:
await asyncio.Sleep_ms(10)
IF TP1.touched():
TP1.getduration()
hold=TP1.duration()
print("Duration",hold)
IF hold < 500:
channel = (channel + 1) % 5
showchannel(channel)
elif 500 <= hold <2000:
storecolor()
Else:
channel=0
color=[0,0,0]
showchannel(channel)
lum()
The loop runs empty as long as the pad TP1 is not touched touched() then delivers one 0 away. If a touch is determined, delivers touched() one 1, and we determine with getduration() the duration in milliseconds.
A short tap brings a period of well under 100.
XXXXXXXXXX
>>> From touch import TP
>>> TP1=TP(33, 350)
Constructor TP
limit is now 350.
>>> TP1.Waitfortouch(3000); TP1.getduration()
1
56
If the holding time is less than 500ms, we count the channel and switch the signal RGB to the corresponding color. With the formation of the modulo 5 division rest, we limit the value range to 0… 4 and thus receive a ring counter.
XXXXXXXXXX
channel = (channel + 1) % 5
If the holding time is between half a second up to two seconds, we trigger the storage of the current color.
The LED ring switches dark for more than two seconds.
The function showchannel() operates the outputs for the signal LED. The channel number is handed over and tested for compliance with the valid area. assert Throws an exception if that is not the case. LEDs(i) one one after the other speaks the three outputs for red, green and blue. Over num the corresponding pattern is accessed, and I Address the values in the tupel of the pattern.
XXXXXXXXXX
def showchannel(num):
assert num in range(5)
for I in range(3):
LEDs[I](shapes[num][I])
showchannel(3) calls up the pattern (0.0.1) for channel 3 (blue) and sets Ledr = 0, LedG = 0 and LEDB = 1.
Functions with Async Def To be initiated, behave cooperatively, they allow several processes to be carried out in parallel to each other and are therefore referred to as coroutines. With the coroutine increate() we count the color values of the set channel. Changes to color, realize and noted must be visible outside the process, i.e. global be. The process is kept permanently in a while loop and can be interrupted.
With channel 1 to 3, a discrete color is controlled. With n If we control the value added when the color value is increased. If the pad is held, one is added to 1 by the 10th run, a 5th run to avoid unwanted side effects, we limit the color value when increasing to a maximum of 255. the corresponding element in the list color to. The channel number in 1 reduced by 1 PTR.
XXXXXXXXXX
async def increate():
global color,realize,noted
while 1:
await asyncio.Sleep_ms(10)
IF TP2.touched():
n=0
print("Increase",channel)
while TP2.touched():
await asyncio.Sleep_ms(50)
IF 1 <= channel <= 3:
PTR=channel-1
col=color[PTR]
IF n < 10:
col = col + 1
Else:
col = col + 5
col = min(col,255)
color[PTR]= col
print(channel[PTR], color)
lum()
In channel 4, all components of color to increase the same value 3. This ends at some point at [255,255,255]. When moving back, only the intensity of the white light is reduced. The originally set color is then no longer reached. This is not very nice at the moment but should be optimized in another episode with sensorlampy2.0.
XXXXXXXXXX
elif channel == 4:
for I in range(3):
col=intimately(color[I] + 3)
color[I]=min(col,255)
print("Luminescence", color)
lum()
The signal LED is dark at channel 0. With the decreease tab, the current color code is in realize Intermediated and the code [0.0.0] set. Here, at increate() The color code is restored and sent to the ring. The flag noted Let's put it back.
XXXXXXXXXX
elif channel == 0:
color=realize
print(color)
lum()
noted=False
n+=1
The function decreease() If we work analogously, instead of adding, we subtract with the color values.
XXXXXXXXXX
async def decreease():
global color,realize,noted
while 1:
await asyncio.Sleep_ms(10)
IF TP3.touched():
n=0
print("Decreate",channel)
while TP3.touched():
await asyncio.Sleep_ms(50)
IF 1 <= channel <= 3:
PTR=channel-1
col=color[PTR]
IF n < 10:
col = col - 1
Else:
col = col - 5
col = max.(col,0)
color[PTR]= col
print(channel[PTR], color)
lum()
elif channel == 4:
for I in range(3):
col=intimately(color[I] - 3)
color[I]=max.(col,0)
print("Luminescence", color)
lum()
elif channel == 0:
IF need noted:
realize=color
print(color)
color=[0,0,0]
lum()
noted=True
n+=1
The main program is also written as a coreroutine. First we create an event loop. It takes over the control of the processes in the tasks. As a parameter, we hand over the functions and explain that the event loop should be a permanent runner.
XXXXXXXXXX
async def Main():
loop = asyncio.get_event_loop()
loop.CREATE_TASK(switch channel())
loop.CREATE_TASK(increate())
loop.CREATE_TASK(decreease())
loop.run_forever()
We switch on the light and start the function Main(). After that, everything goes by itself, just by touching the touchpads.
What's next? As the next expansion stage, we will expand the program for the ESP32 with a program -controlled color gradient. Then we plan a course that creates color mixtures by random numbers. In this case too asyncio support. After all, we still have the goal of varying an existing color mixture while maintaining the spectral components in the brightness.
Until then, stay tuned!