Have you ever wanted a certain color for a NeoPixel project and had trouble figuring out which RGB or HSV values to enter? You can use web colors to try and get specific colors but you never know how it will look in real life until you load up your code and see it in action. This project allows you to use potentiometers to adjust the red, green and blue values individually in real time to fine tune NeoPixel colors and to see exactly how RGB and HSV values from the code work in practice.
Introduction
Circuit
The Feather M0 Express board acts as the brains of the operation, since it has the perfect amount of pins to control the LCD, analog potentiometers and NeoPixels. The three potentiometers that are controlling the RGB values are connected to analog pins 0, 1 and 2. The NeoPixels' data pin is pin 13 and then LCD uses six of the digital pins (pins 5, 6, 9, 10, 11 and 12).
The main portion of the circuit is soldered to a perma proto board. This makes laying out the components very simple and allows for Ground and 5V to be easily accessible using the rails. The LCD screen is not soldered directly to the board. Instead a row of female headers are soldered in so that the LCD can be removed easily if needed and to lift it to the proper height for the top of the enclosure. The LCD needs an potentiometer to control the screen brightness, which is located to its left.
The individual 5mm LEDs are only receiving power and ground from the Feather M0 Express, so that they turn on whenever the board receives power. They're being used as labels to help identify which value the potentiometers are controlling.
RGB CircuitPython Code Using the NeoPixel Library
import math import time import adafruit_character_lcd import board import digitalio import neopixel from analogio import AnalogIn lcd_rs = digitalio.DigitalInOut(board.D5) lcd_en = digitalio.DigitalInOut(board.D6) lcd_d7 = digitalio.DigitalInOut(board.D12) lcd_d6 = digitalio.DigitalInOut(board.D11) lcd_d5 = digitalio.DigitalInOut(board.D10) lcd_d4 = digitalio.DigitalInOut(board.D9) lcd_columns = 16 lcd_rows = 2 lcd = adafruit_character_lcd.Character_LCD( lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns, lcd_rows ) potR = AnalogIn(board.A0) # pot pin for R val potG = AnalogIn(board.A1) # pot pin for G val potB = AnalogIn(board.A2) # pot pin for B val pixpin = board.D13 # neopixel pin numpix = 8 strip = neopixel.NeoPixel(pixpin, numpix, brightness=0.1) def val(pin): # divides voltage (65535) to get a value between 0 and 255 return pin.value / 257 while True: strip.fill((int(val(potR)), int(val(potG)), int(val(potB)))) # int converts float val to an integer lcd.set_cursor(3, 0) # text at the top of the screen lcd.message('R + G + B =') lcd.set_cursor(2, 1) # str converts float val to a string, trunc removes decimal values lcd.message(str(math.trunc(val(potR)))) lcd.set_cursor(6, 1) lcd.message(str(math.trunc(val(potG)))) lcd.set_cursor(10, 1) lcd.message(str(math.trunc(val(potB)))) time.sleep(0.5) # refreshes screen to send updated values from pots lcd.clear()
The goal of the code is to have the three potentiometers each control the red, green and blue values of the NeoPixels. The NeoPixels should update in real time according to the values read from the potentiometers and then those same values should appear on the LCD's screen.
The analog values produced by the potentiometers are converted to digital values by dividing the analog value by 257, giving a minimum value of 0 and a maximum value of 255. This value exists as a float
. For the values to be able to be used as a value for the NeoPixels they need to be converted to integers with int
.
For these same values to be used with the LCD screen, they need to be converted to strings, which is done with str
. The only issue that remains is that the values will appear with their decimal points, making them incredibly long and unable to fit on the screen. The decimal is removed by using the truncate function (math.trunc
) from the Python math library. Finally, lcd_clear()
has to be called at the end of the loop so that the LCD refreshes to reflect the changing values from the potentiometers.
HSV CircuitPython Code Using the FancyLED Library
You don't have to remain confined to RGB values though. The FancyLED library in CircuitPython allows you to use HSV, or hue, saturation and value, to affect the NeoPixel strip's color.
The code is very similar to the NeoPixel library RGB code that we just looked at. The goals are still the same: have the pots control the three values that affect the NeoPixel strip's color (in this case HSV) and have these values display on the LCD in real time.
import time import adafruit_character_lcd import adafruit_fancyled.adafruit_fancyled as fancy import board import digitalio import neopixel from analogio import AnalogIn lcd_rs = digitalio.DigitalInOut(board.D5) lcd_en = digitalio.DigitalInOut(board.D6) lcd_d7 = digitalio.DigitalInOut(board.D12) lcd_d6 = digitalio.DigitalInOut(board.D11) lcd_d5 = digitalio.DigitalInOut(board.D10) lcd_d4 = digitalio.DigitalInOut(board.D9) lcd_columns = 16 lcd_rows = 2 lcd = adafruit_character_lcd.Character_LCD( lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6, lcd_d7, lcd_columns, lcd_rows ) potH = AnalogIn(board.A0) # pot for hue potS = AnalogIn(board.A1) # pot for saturation potV = AnalogIn(board.A2) # pot for value pixpin = board.D13 # NeoPixel pin numpix = 8 strip = neopixel.NeoPixel(pixpin, numpix, brightness=1.0) def val(pin): # divides voltage (65535) to get a value between 0 and 1 return pin.value / 65535 def round_pot_h(): # rounds decimal value to 2 decimal places return round(val(potH), 2) def round_pot_s(): return round(val(potS), 2) def round_pot_v(): return round(val(potV), 2) while True: # calls for HSV values color = fancy.CHSV(val(potH), val(potS), val(potV)) # converts float HSV values to integer RGB values packed = color.pack() # writes converted int values to NeoPixels strip.fill(packed) lcd.set_cursor(3, 0) # text at the top of the screen lcd.message('H + S + V =') lcd.set_cursor(1, 1) # sends the rounded value and converts it to a string lcd.message(str(round_pot_h())) lcd.set_cursor(6, 1) lcd.message(str(round_pot_s())) lcd.set_cursor(11, 1) lcd.message(str(round_pot_v())) time.sleep(0.5) # refreshes screen to display most recent pot values lcd.clear()
The first change is the division of the analog voltage value by itself (pin.value / 65535
) so that the minimum value read is 0 and the maximum is 1, since that is the range of HSV values.
Next, are the functions round_pot_h
, round_pot_s
and round_pot_v
. These functions return a rounded number for the HSV values. Since HSV uses a decimal range between 0 and 1, the numbers can be very long in length, which won't fit on the LCD screen. Here, the numbers are being rounded to 2 decimal places so that all three HSV values can fit comfortably on the screen. However, you can easily change the number of decimal places to suit your needs.
In the loop, the HSV values are assigned to color
, which is then converted from float HSV values to integer RGB values using pack()
from the FancyLED library. These integer values are then written to the NeoPixel strip using strip.fill(packed)
from the NeoPixel library.
For the LCD portion, the only big change from the NeoPixel RGB value code is that functions are being used and called to round the HSV values. This was done to avoid memory allocation issues.
Also, you'll notice that the values are not being converted to integers for the LCD. This is because although we need integers to write the colors to the NeoPixel strip, we want to see the float HSV values on the screen. Just like the RGB value code, the rounded HSV values are sent to the LCD as strings with str
.
Some examples of HSV values being dialed in. |
3D Printing
The case was designed in Fusion360 to have all of the components available on the top of the box with a hole in the back that allows the Feather M0 Express's USB port to be accessible. The bottom of the case has standoffs designed to screw the Feather and perma proto board in place with M2.5 screws. The case is also a snap fit case, making opening and closing it very simple.
Planning the case's layout and measurements |
The case was printed with 20% infill and .2mm layer height. No supports are needed. Silver PLA filament was used to act as a neutral, yet still bright, color with the lights.
The single 5mm LEDs used are very bright and need to be diffused so that you can still look comfortably at the NeoPixel strip. There is a great diffuser model on Thingiverse designed by 3dprintsolutions that fits perfectly over a 5mm LED. But since the LEDs are in LED holders, they aren't fully exposed and as a result the provided model is too long. You can shorten the model though in your slicer by performing a cut. A cut at Z height 6.7 provided the best fit, sitting flush with the case. You may find that you need to print this a bit smaller, between 93%-95% since the lip found on the bottom of LEDs is not being used to catch onto the diffuser's housing.
This cut model was then printed with 20% infill and .2mm layer height with white PLA. Supports were used as suggested by 3dprintsolution, the designer.
Assembly
Now it's time to solder everything together and fit everything snugly into the housing. Begin by placing all of the components onto the perma proto board and then running wires to the Feather M0 Express and other components. As mentioned in the Circuit portion of the guide, female headers were used for the LCD to plug into so that it can be removed easily and to lift it up high enough for the top of the case.
Circuit layout. Poster tack can be helpful when placing through-hole parts since it keeps everything in place and is easily removed after soldering. |
For the individual LEDs be sure to use heat shrink to avoid any shorts. You can use color coded heat shrink to help keep the clear LEDs straight.
After you've finished soldering and have tested everything, it's time to put everything into the housing. First, use M2.5 screws to secure the Feather M0 Express board and perma proto board to the bottom of the case. Also slot the 5mm LED holders into the top of the case. Next, feed the potentiometers and individual LEDs into their holes. Secure the potentiometers with their included washer. Then, press the NeoPixel strip into its slot. Depending on your printer's tolerances you may need to use a small piece of electrical tape to help keep it in place; otherwise the housing should hold the pixels securely. After that, you're ready to close up the case, which should snap shut. With the headers the LCD's screen should be flush with the top of the case and fit properly in its cutout without much adjustment.
Laying out parts after soldering |
The knobs for the potentiometers help in labeling which color is being controlled. However, you may have noticed that there are no knobs with green markings. We can fix this with a simple Sharpie Paint marker. First, tape off the knob so that only the white strip is seen and then go to town with your Sharpie. You can use paint with a brush if you prefer, but the Sharpie is quick drying and less messy.
Taping and painting process for the green knob. |
Once everything is inside the case you're ready to dial in all of the colors of the rainbow!
This guide was first published on Mar 02, 2018. It was last updated on Mar 02, 2018.