Table of Contents
This is blog-5 of Light Up your life challenge. In this blog post, I am going to cover the progress that I have made so far. In the previous blogs I have discussed LED parameters, PCB schematic creation in KiCad and then testing the final PCB that I received from NextPCB. So far everything worked fine as expected and that is good news (relief) for me.
In the last blog post, I have just started to create something interesting. I started to use wireless communication such as the ESP-NOW protocol to change the color and control the LEDs. That worked as expected but needed some more secure and finer control. I will continue to work on ESP-NOW but before that I have BLE functionality enabled on this device.
With ESP-NOW, I was using switches for analog input to the ESP32 chip and based on the analog values the ADC value will detect a button press. This is fine. But there are some issues that needs to be covered. They are following.
First, ADCs can suffer from noise. That means that there can be a wrong Analog value that can be detected by the chip. To reduce that there are some methods suggested by Espressif to put some 1nF capacitors at the input pin. So that the pin value does not accidentally change to 0.
But there are other wireless technologies such as Bluetooth that can be more effective in such cases. For example, BLE can operate using Android applications and follows a standard procedure to connect, disconnect, write values for characteristics, etc. This will reduce the need for separate remote devices to control the LED lights but will need complex Android BLE application programming knowledge.
One more protocol that I have seen in some smart home products is using IR LEDs to transmit data. This can be something like ESP RMT protocol that transmits IR pulses using remote. This will work only when transmitter and receiver devices are in line of sight for not more than 30 meters or similar. The advantage is that it is easy to control the lights using tiny remote and no need to manage BLE connections. Also like ESP-NOW there is not a chance of accidently sending a wrong value if keyboard is not working as expected.
Using Micropython BLE(Bluetooth) module
BLE protocol defines GATT service and GATT characteristics. These characteristic values can be read or write. There are other functions of these characteristics as well such as notify and indicate. But for read and write one can change the individual color value. i.e. between 0-255. There are many other possibilities such as fine control of strip and adding effects etc. But for now let's consider a simple application to change the color of the LEDs.
For this part, I am using the official Bluetooth module of Micropython. This module provides the required functionality for Bluetooth to work.
import time import bluetooth from bluetooth import BLE import asyncio import ubinascii import neopixel import machine from micropython import const import esp32
One need to register the event IDs for the callback functions. The following code snipet shows some of the common IDs such as connect, disconnect and characteristic read/write.
_IRQ_CENTRAL_CONNECT = const(1) _IRQ_CENTRAL_DISCONNECT = const(2) _IRQ_GATTS_WRITE = const(3) _IRQ_GATTS_READ_REQUEST = const(4) _IRQ_GATTC_READ_RESULT = const(15) _IRQ_GATTC_READ_DONE = const(16) _IRQ_GATTC_WRITE_DONE = const(17)
After this it is required to create a Bluetooth object and define connect, disconnect and IRQ callback functions. Remember not to forget some of these functions as they are mandatory to be defined for proper Bluetooth functionality.
class RGBMATRIX():
def __init__(self, name):
self.name = name
self.BLE = bluetooth.BLE()
self.BLE.active(True)
self.connection = set()
self.register_services()
self.BLE.irq(self.ble_irq)
self.advertise()
def register_services(self):
RGB_UUID = bluetooth.UUID('7bf20ec6-e66f-4a61-9aa4-8fa20097a0c4')
RED_UUID = (bluetooth.UUID('7bf20ec6-e66f-4a61-9aa4-8fa20097a0c5'), bluetooth.FLAG_READ | bluetooth.FLAG_WRITE,)
BLUE_UUID = (bluetooth.UUID('7bf20ec6-e66f-4a61-9aa4-8fa20097a0c6'), bluetooth.FLAG_READ | bluetooth.FLAG_WRITE,)
GREEN_UUID = (bluetooth.UUID('7bf20ec6-e66f-4a61-9aa4-8fa20097a0c7'), bluetooth.FLAG_READ | bluetooth.FLAG_WRITE,)
rgb_service = (RGB_UUID, (RED_UUID, BLUE_UUID, GREEN_UUID,),)
services = (rgb_service,)
( (self.red, self.green, self.blue,), ) = self.BLE.gatts_register_services(services)
def advertise(self):
name = bytes(self.name, 'UTF-8')
advdata = bytearray(b'\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name
self.BLE.gap_advertise(100, advdata)
#get MAC address
mac = self.BLE.config('mac')[1]
print("device MAC address is: "+ubinascii.hexlify(mac).decode())
def ble_irq(self, event, data):
if event == _IRQ_CENTRAL_CONNECT:
conn_handle, addr_type, addr = data
self.connection.add(conn_handle)
print('Central connected')
elif event == _IRQ_CENTRAL_DISCONNECT:
conn_handle, addr_type, addr = data
self.connection.remove(conn_handle)
self.advertise()
print('Central disconnected.')
elif event == _IRQ_GATTS_WRITE:
conn_handle, attrb_handle = data
#print('Gatts write')
self.val_red = self.BLE.gatts_read(self.red)
self.val_blue = self.BLE.gatts_read(self.blue)
self.val_green = self.BLE.gatts_read(self.green)
self.set_led_strip(self.val_red, self.val_blue, self.val_green)
elif event == _IRQ_GATTS_READ_REQUEST:
conn_handle, attrb_handle = data
#print('read request')
Next it is required to define some Neopixel objects on different pins for different types of LEDs.
ledcorner = machine.Pin(12)
ledfive = machine.Pin(45)
ledseven = machine.Pin(47)
neopixelcorner = neopixel.NeoPixel(ledcorner, 9)
neopixelfive = neopixel.NeoPixel(ledfive, 5)
neopixelseven = neopixel.NeoPixel(ledseven, 7)
def set_led_strip(self, red, blue, green):
for i in range(9):
neopixelcorner[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
for i in range(5):
neopixelfive[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
for i in range(7):
neopixelseven[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
neopixelcorner.write()
neopixelfive.write()
neopixelseven.write()
nvs.set_i32("red" , int.from_bytes(red))
nvs.set_i32("blue" , int.from_bytes(blue))
nvs.set_i32("green" , int.from_bytes(green))
Now we are good to go. Only the final thing is to define the main() function, create object and run it continuously.
def main():
BLE = RGBMATRIX("ESP32S3")
#BLE.set_color(255)
if __name__ == "__main__":
main()
One done just run the code. You will see that the device named ESP32S3 will appear on your BLE scanner lists. Now one can connect to the device and write the characteristics value for RED, GREEN and BLUE colors. The following video shows the colors in action with BLE application for Windows. One can use any BLE application such as nRF connect on mobile phones.
Using NVS to store color values
One most important thing to me is to store color values. I do not prefer that when there is power cut or user turns off the main switch and starts the LED light, there is either no color or there is specific prefixed color like white. I think that the LEDs should have the same color that was set previously. And for this reason, I am using NVS (non-volatile storage) to store color key-value pairs. In the begging the storage will be initialized once and values stored will be the color values for the LEDs.
This feature is useful in case where there are frequent power cuts and user do not need to set colors each time power is back or any situation similar to that.
nvs = esp32.NVS("RGBMAT")
try:
status = nvs.get_i32("Lighton")
except OSError:
print('Blob is not set, setting it now..')
nvs.set_i32("Lighton", 1)
nvs.set_i32("red" , 255)
nvs.set_i32("blue" , 0)
nvs.set_i32("green" , 0)
nvs.commit()
In the __init__ function one can insert the following code. This will set the values of each color to previously set values.
self.redl = nvs.get_i32("red")
self.bluel = nvs.get_i32("blue")
self.greenl = nvs.get_i32("green")
self.set_led_strip(self.redl.to_bytes(1), self.bluel.to_bytes(1), self.greenl.to_bytes(1))
The update the LED colors as there is BLE Write operation to the characteristic.
elif event == _IRQ_GATTS_WRITE:
conn_handle, attrb_handle = data
print('Gatts write')
self.val_red = self.BLE.gatts_read(self.red)
self.val_blue = self.BLE.gatts_read(self.blue)
self.val_green = self.BLE.gatts_read(self.green)
self.set_led_strip(self.val_red, self.val_blue, self.val_green)
In the set_led_strip() store the values
def set_led_strip(self, red, blue, green):
for i in range(9):
neopixelcorner[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
for i in range(5):
neopixelfive[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
for i in range(7):
neopixelseven[i] = (int.from_bytes(red), int.from_bytes(blue), int.from_bytes(green))
neopixelcorner.write()
neopixelfive.write()
neopixelseven.write()
nvs.set_i32("red" , int.from_bytes(red))
nvs.set_i32("blue" , int.from_bytes(blue))
nvs.set_i32("green" , int.from_bytes(green))
Adding Effects to the LEDs
Effects are ways to make LEDs blink more decorative and catchier. Hence, there are some standard effects such as Rainbow, chasing effect, fading effect, etc.
Out of these I was able to create Rainbow effect on these LEDs. Basically, one can take inspiration from Internet for different effects that LEDs can create. Here I have created a Rainbow effect using a Wheel() function that creates R, G, B values for the each color and updates these colors on the strip. Using some for() loop and doing the mathamatics of colors one can generate different effects that look eye catchy.
Following video shows the Rainbow effect on the LEDs.
(note: I still need to work on one of the SPI LEDs to create this effect but other SPI and normal LEDs work fine.)
import neopixel
import machine
import time
import esp32
from machine import PWM, Pin
class LIGHT_EFFECTS(object):
def __init__(self, interface = 2, miso = 38, mosi = 14, sck=10, num_led = 7, firstbit=0):
self.spi_num = interface
self.sck_pin = Pin(sck)
self.miso_pin = Pin(miso)
self.mosi_pin = Pin(mosi)
self.first_bit = firstbit
self.leds = num_led
self.spi = machine.SPI(self.spi_num, baudrate=8000000, polarity = 0, phase = 0, sck = self.sck_pin, mosi = self.mosi_pin, miso = self.miso_pin, firstbit=self.first_bit)
self.start_frame = bytes([0x00, 0x00, 0x00, 0x00])
def send_start(self):
self.spi.write(self.start_frame)
def wheel(self, pos):
if(pos) < 85:
return (pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return (255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return (0, pos * 3, 255 - pos * 3)
def rainbow_cycle(self, wait, numled, pin_num):
neopixel_id = neopixel.NeoPixel(machine.Pin(pin_num), numled)
for j in range(255):
for i in range(numled):
pixel_index = (i * 256 // numled) + j
neopixel_id[i] = self.wheel(pixel_index & 255)
neopixel_id.write()
time.sleep(wait)
def rainbow_cycle_spi(self, wait, numled, pin_num):
frame = []
self.spi = machine.SPI(self.spi_num, baudrate=8000000, polarity = 0, phase = 0, sck = self.sck_pin, mosi = self.mosi_pin, miso = self.miso_pin, firstbit=self.first_bit)
#b, g, r = self.get_pixel(0x00, 0x00, 0xff)
for j in range(255):
for i in range(self.leds):
pixel_index = (i * 256 // numled) + j
r, g, b = self.wheel(pixel_index & 255)
frame.append(0xFC)
frame.append(b)
frame.append(g)
frame.append(r)
self.spi.write(self.start_frame)
self.spi.write(bytearray(frame))
self.spi.write('b\x07')
time.sleep(wait)
frame = []
def wheel2(self, pos):
if(pos) < 1365:
return (pos * 3, 4096 - pos * 3 , 0)
elif pos < 2730:
pos -= 85
return (4096 - pos * 3 , 0, pos * 3)
else:
pos -= 2730
return (0, pos * 3, 4096 - pos * 3 )
def rainbow_cycle_spi2(self, wait, numled):
frame = []
self.spi = machine.SPI(self.spi_num, baudrate=8000000, polarity = 0, phase = 0, sck = 40, mosi = 13, miso = 38, firstbit=0)
for i in range(numled):
for k in range(48):
if((color >> i) & 0x1):
frame.append(0xFC)
else:
frame.append(0xC0)
send = bytearray(frame)
self.spi.write(send)
time.sleep(wait)
frame = []
eff = LIGHT_EFFECTS()
while True:
eff.rainbow_cycle(0.0000001, 9, 12)
eff.rainbow_cycle(0.00001, 7, 47)
eff.rainbow_cycle(0.000001, 5, 45)
eff.rainbow_cycle_spi(0.01, 5, 45)
#eff.rainbow_cycle_spi2(0.001, 3)