Seven segment displays have been around for more than a century (https://en.wikipedia.org/wiki/Seven-segment_display) and form the familiar shape of the numbers in digital watches, instrument panels and many other numerical displays. They’ve been replaced in many cases by screens, but from an efficiency standpoint it’s hard to argue with the brevity of encoding the state of a numerical display in only 7 bits of information (each segment on or off)
Most seven-segment displays are monochrome, so this edge-lit version adds visual interest by using the full color capabilities of cheap RGB LED strips to illuminate each digit and even each segment (or even half-segment) of the display in a different color. Embedding the clear acrylic segments in a larger acrylic frame allows you to see completely through the entire display, which another unique aspect of this project.
Disclaimer:
This build combines small dozens of small laser-cut acrylic pieces which fit together with very tight tolerances. It uses skinny (4mm wide) LED strips which must be soldered, bent, and then slotted in between those acrylic pieces. When assembling the parts you must be willing to force pieces into place, even though it feels like you are stressing the brittle acrylic. You must also be willing to remove and re-seat said pieces and LED strips when it turns out they *can’t* actually be forced into place. At some point during the assembly there is a strong likelihood that you will have to remove everything and re-solder your LED strip when you realize that forcing everything into place broke one of the wires away from your LED strip or created a short circuit.
With all of that said, this is a rewarding project if you are patient and willing to rework the alignment until everything slots into place.
This write up describes the build of the 6-digit display. It’s a bit easier to build the 4-digit display, and the steps are basically the same for both, so it’s probably best to start with the 4 segment display unless you feel very confident.
Components:
The quantities required depend on whether you’re building the 4-digit or 6-digit display.
1/8″ thick sheet wood for laser cutting
1/16″ clear acrylic for laser cutting
1/4″ clear acrylic for laser cutting
Skinny (4mm wide) SK6812 3535 RGB LED strip with 60 LEDs/m (like this one)
The zip file below contains six vector files for laser cutting, three apiece for the 4-digit or 6-digit build. The laser cut shapes are sized to exactly fit the LEDs and capacitors in a 60 LED/meter 3535 sized strip, so do not resize them before laser cutting them.
The tolerances for this project are very tight, and the segments have notches that are exactly spaced and *just* large enough to hold the LEDs and capacitors on the acrylic strip (see image below):
Because LED strips vary between manufacturers, you will want to check the spacing and size of the LEDs and capacitors on your LED strip to confirm that they align with the notches shown in the SVG file. You can test the design before doing the laser cutting by printing out a scale image of the segments and seeing if the notches align with your LED strip. If yours do not fit exactly, the build will not work. If you are good with CAD, you should be able to modify the spacing of the notches in the SVG file to match your strip, however.
Step 1: Cut and Build the Enclosure
Cut the enclosure pieces from 1/8″ wood of your choice. I forgot to photograph this step, but first take the outer and inner lid pieces (outlines shown below), and bond them together with wood glue so that the holes all line up with each other. The larger piece is the outer part of the lid and the smaller piece will sit inside the enclosure.
Note that the placement of the holes is not totally symmetric and so the orientation of the lid pieces matters. Make sure *ALL* the holes line up when you align the pieces. Clamp the pieces together or use M3 screws and nuts through the holes to fasten them together while they dry.
Assemble the body of the enclosure by laying out the pieces as shown below, applying glue to the edges, and sliding the matching slots and tabs together. Clamp or tape the pieces together while the wood glue dries.
import board
import neopixel
import time
import random
import busio
import adafruit_fancyled.adafruit_fancyled as fancy
uart = busio.UART(board.TX, board.RX, baudrate = 57600, timeout=50)
#DEFINE THE NUMBER OF DIGITS IN THE DISPLAY HERE:
NDIGITS = 6
NDOTS = NDIGITS//2 - 1
NPIX_PER_STRIP = 28
NPIX_PER_DIGIT = NPIX_PER_STRIP//2
#ADJUST THIS VALUE TO CHANGE THE BRIGHTNESS
BRIGHTVAL=.6
DIGIT_LEFT = 1
DIGIT_RIGHT = 2
DIGIT_BOTH = DIGIT_RIGHT | DIGIT_LEFT
dot_strips = [neopixel.NeoPixel(board.D13, NDOTS, auto_write=False)]
if NDIGITS == 6:
dot_strips = dot_strips + [neopixel.NeoPixel(board.D12, NDOTS, auto_write=False)]
num_strips = [neopixel.NeoPixel(board.D9, NPIX_PER_STRIP, auto_write=False),
neopixel.NeoPixel(board.D10, NPIX_PER_STRIP, auto_write=False)]
if NDIGITS == 6:
num_strips = num_strips + [neopixel.NeoPixel(board.D11, NPIX_PER_STRIP, auto_write=False)]
all_strips = dot_strips + num_strips
#Bits (written as decimal) corresponding to the digits 0-9 for both left and right digit display
digitSegments = [[126,66,55,103,75,109,125,70,127,79],[63,12,91,94,108,118,119,28,127,124]];
#Defines the segments in each digit which form a circle
circleLeds = [[2,3,4,5,6,7,8,9,10,11,12,13],[20,21,22,23,24,25,14,15,16,17,18,19]]
def showAll():
for i in range(len(all_strips)):
all_strips[i].show()
def clearAll():
for i in range(len(all_strips)):
all_strips[i].fill((0,0,0))
showAll()
#lights up the correct LEDs in the segment
def segment(strip, index, seg, col):
led_offset = index*NPIX_PER_DIGIT #will be 0 or 14
strip[seg*2+led_offset] = col
strip[seg*2+led_offset+1] = col
def fillDots(col):
dots1.fill(col)
dots2.fill(col)
#DRAWS A CIRCLE PATTERN ON ALL DIGITS
firstIndex = 0
def circle(position, digit = DIGIT_BOTH, reps=48,trailLength=4):
strip = num_strips[position]
global firstIndex
global gHue
hueIncrement = .02
nLeds = len(circleLeds[0])
for rep in range(reps):
gHue = gHue + hueIncrement
bright = 1.0
for i in range(0,trailLength,1):
col = fancy.gamma_adjust(fancy.CHSV(gHue+i*hueIncrement, 1.0, bright)).pack()
if (digit & DIGIT_LEFT):
strip[circleLeds[0][(firstIndex+i)%nLeds]] = col
if (digit & DIGIT_RIGHT):
strip[circleLeds[1][(firstIndex+i)%nLeds]] =col
bright = bright*.75
if (digit & DIGIT_LEFT):
strip[circleLeds[0][(firstIndex+trailLength)%nLeds]] = (0,0,0)
if (digit & DIGIT_RIGHT):
strip[circleLeds[1][(firstIndex+trailLength)%nLeds]] = (0,0,0)
strip.show()
firstIndex = firstIndex - 1
if firstIndex < 0:
firstIndex = len(circleLeds[0]) - 1
def clearDigit(position, doShow=True):
strip = num_strips[position//2]
offset = position % 2
first = offset * NPIX_PER_DIGIT
last = first + NPIX_PER_DIGIT
strip[first:last]=[(0,0,0)]*NPIX_PER_DIGIT
if doShow:
strip.show()
#Translates drawing digits to lighting up the correct segments
def drawStripDigit(strip, offset, segValues, col):
mask = 1
for i in range(7):
if (segValues & mask):
segment(strip, offset, i, col)
else:
segment(strip, offset, i, (0,0,0))
mask <<= 1
def drawDigit(position, value, col,doShow=False):
offset = position % 2
strip = num_strips[position//2]
segValues = digitSegments[offset][value]
drawStripDigit(strip, offset, segValues, col)
if doShow:
strip.show()
#Draws a 2-digit number on the specified "set" of digits in the display
def drawNumber(setIndex, value, col, doShow=False, drawLeadingZero=True):
strip = num_strips[setIndex]
if (drawLeadingZero or value >= 10):
lValues = digitSegments[0][value // 10]
drawStripDigit(strip, 0, lValues, col)
else:
clearDigit(setIndex*2)
rValues = digitSegments[1][value % 10]
drawStripDigit(strip, 1, rValues, col)
if doShow:
strip.show()
gHue = 0
hueInc = 0.003
lastUpdateTime = 0
timeHr = 0
timeMin = 0
timeSec = 0
baseSec = 0
baseMin = 0
baseHr = 0
def updateTime():
global timeSec, timeMin, timeHr
global baseSec, baseMin, baseHr
elapsedSec = int(time.monotonic() - lastUpdateTime)
totSec = elapsedSec + baseSec
timeSec = totSec % 60
timeMin = ((totSec // 60) + baseMin) % 60
timeHr = ((totSec // 3600) + baseHr) % 24
def getUartTimeData():
global lastUpdateTime, baseHr, baseMin, baseSec
data = uart.read(32)
result = False
if data is not None:
#convert bytearray to string
try:
data_string = ''.join([chr(b) for b in data])
#print(data_string, end="")
hr = int(data_string[0:2])
min = int(data_string[3:5])
sec = int(data_string[6:8])
lastUpdateTime = time.monotonic()
baseHr = hr
baseMin = min
baseSec = sec
result = True
except:
print(data_string)
return result
def onStart():
while not getUartTimeData():
for pos in range(NDIGITS/2):
circle(pos, DIGIT_BOTH, 1)
time.sleep(.05)
onStart()
while True:
if lastUpdateTime:
updateTime()
gHue = timeSec/60.0
col = fancy.gamma_adjust(fancy.CHSV(gHue, 1.0, BRIGHTVAL)).pack()
drawNumber(0, timeHr, col, drawLeadingZero=False)
drawNumber(1, timeMin, col)
if NDIGITS == 6:
drawNumber(2, timeSec, col)
index = timeSec % 2
if NDIGITS == 6:
dot_strips[index].fill(col)
dot_strips[1-index].fill((0,0,0))
elif NDIGITS == 4:
dot_strips[0][index] = col
dot_strips[0][1-index] = (0,0,0)
showAll()
getUartTimeData()
I hope to clean up and update this code at some point later – but I’m putting it up now, warts and all, in case anyone would like to use it in the meantime.
Step 12: Whew!
If you’ve made it this far, good for you! I hope you enjoy this project. It was a lot of work, but quite rewarding to make.
how can i get in contact with builder of this project, the contactform is not working.
Have 2 questions.
1. Is the second *.ino for the Itsy? and hoe to define the : #DEFINE THE NUMBER OF DIGITS IN THE DISPLAY HERE:
and : #ADJUST THIS VALUE TO CHANGE THE BRIGHTNESS
2. Or must i combine the 2 programs togeher.
Wow! I started a 7-segment led project last year but this blows it away.
how can i get in contact with builder of this project, the contactform is not working.
Have 2 questions.
1. Is the second *.ino for the Itsy? and hoe to define the : #DEFINE THE NUMBER OF DIGITS IN THE DISPLAY HERE:
and : #ADJUST THIS VALUE TO CHANGE THE BRIGHTNESS
2. Or must i combine the 2 programs togeher.