Introduction

 

Another piece involving LEDs. This time I chose some small SOT23 amber ones.

 

The Work

 

Here it is on a small table in my workroom.

 

 

The following video shows it working.

 

 

At the start I was going to have it as another canvas on the wall but, as it gradually developed, it went through

a phase where I was going to have the grid of LEDs sitting in a preservative jar, but I didn't entirely like that

either so it finally ended up as this free-standing kind of 'ship' idea. That's art for you!

 

The rolling flicker, that you see partway through the video, isn't apparent in real life. The matrix is scanned and

is interacting with the electronic shutter of the camera's video sensor. Unfortunately, the video also makes the

LEDs look very much brighter than they appear to the eye.

 

The Technical Stuff

 

Here's the circuit diagram.

 

 

 

The circuit is a bit messy (too many components) and could be greatly simplified. When I started this challenge

I decided I'd try and recycle old parts I've got lying around, rather than just keep buying new stuff, but it does

mean that some of this design is getting a bit convoluted. The shift registers keep appearing because I have

several tubes of them (they were originally excess parts for products manufactured about 25 years ago that

were kept as spares and which I later rescued from going into an electrical-waste skip. I looked them up on

TI's website and, amazingly, they still make them; probably because they had an automotive use).

 

Rather than try and drive 36 LEDs using GPIO lines, the LEDs are arranged in a 6x6 matrix, with 6 columns

and 6 rows. The drivers to those rows and columns are, in turn, controlled by shift register outputs, thus

reducing the GPIO count to four. I'm going to have a 1:6 multiplex on the columns and present the data on

the rows. When I experimented with a single LED I found I needed about 10mA to get the illumination I wanted

for a reasonable 'dot' on the paper. That means the row drive will need to work at something like 60mA

[in order to average 10mA] and the column drive will need to cope with up to 360mA [if all 6 LEDs are on].

Because I didn't have a good option handy for driving the 360mA if I put it on the high side, and the shift

register isn't quite rated for doing it if I did it on the low side, I decided to put BS170 MOSFETs on the low

side for the 360mA and 2N3906 transistors on the high side for the row drive since I have plenty of both.

 

For the level-conversion up from the Pi's 3.3V GPIO pins to the shift register 5V CMOS inputs, this time

around I experimented with using BSS138 MOSFETs in a common-gate configuration (similar to a

cascode-type arrangement) where the MOSFET shields the Pi from the 5V. The BSS138 parts are in a

surface-mount SOT23 package but they're very easy to work with and there's no problem soldering them

with an ordinary soldering iron. [Although it worked adequately for the piece, the output waveforms aren't

ideal. They stall momentarily around the 3V point.]

 

Here's the interface board as I've built it on a small prototyping board with the wiring done with wire-wrap

wire soldered to the pads.

 

 

 

For the display part, I super-glued the LEDs down to a piece of fairly heavy cartridge paper. They actually

face down towards the paper, so the pixel is formed by the light that gets reflected back up through the

body and what spills out the sides. Visually, it was much more comfortable to look at than having the very

bright dot of light from the die directly visible.

 

Here they are, glued to the paper, before the wiring was done.

 

 

The paper was then glued to part of a chop-stick, which acts as the mast, and strands of wire from a stranded

cable were used to wire the matrix to the drive parts on the circuit board.

 

The Code

 

Once I had the hardware built and working, I needed to come up with some code. Again I've used Python. That

probably isn't a particularly good decision, given that it needs reasonably accurate timing, but I'm really pushed

for time here and so this is the point where 'good design' flies out the window and 'whatever works' is now the

order of the day. In practice it worked surprisingly well, with the only noticeable blemishes coming when the Pi

starts doing network stuff (whenever the network connector LED flashes, the display stutters).

 

Here's the code. I won't go through it in detail - hopefully it's fairly understandable. Getting the right LEDs to light

up, when I wanted them to, took a few iterations of the code but it's very easy to debug something when you can

literally see the results of what you're doing displayed in front of you.

 

# Trickle-Down (slice3.py)
import time
import RPi.GPIO as GPIO
import random
# set GPIO direction for pins we are using to output
GPIO.setmode(GPIO.BCM)
GPIO.setup(18,GPIO.OUT) #clock
GPIO.setup(4,GPIO.OUT) #latch
GPIO.setup(15,GPIO.OUT) #data
GPIO.setup(14,GPIO.OUT) #enable
# set them to appropriate start levels
GPIO.output(18,False)
GPIO.output(4,False)
GPIO.output(15,False)
GPIO.output(14,True)
# set up the frame store
displayStore = [[True,False,False,False,False,False,True,True],
[False,False,False,False,False,False,True,True],
[False,False,False,False,False,False,True,True],
[False,False,False,False,False,False,True,True],
[False,False,False,False,False,False,True,True],
[False,False,False,False,False,False,True,True],
[True,True,True,True,True,True,True,True,True],
[True,True,True,True,True,True,True,True,True]]
# start off with something sensible and in range in variables
fillFrame = True
currentRow = 0
currentCol = 0
nextRow = 0
nextCol = 0
displayCol = 5
frameCount = 0
floodCount = 0
testCount = 0
def outputToShiftRegisters(displayStore,displayCol):
  #two more dummy bits
  GPIO.output(18,True) # shift clock high
  GPIO.output(18,False) # shift clock low
  GPIO.output(18,True) # shift clock high
  GPIO.output(18,False) # shift clock low
  #then the column data
  for x in range(0,6): # do for the 6 bits of col data
    if x == displayCol: #set up data
      GPIO.output(15,True)
    else:
      GPIO.output(15,False)
    GPIO.output(18,True) # shift clock high
    GPIO.output(18,False) # shift clock low
  #two dummy bits
  GPIO.output(18,True) # shift clock high
  GPIO.output(18,False) # shift clock low
  GPIO.output(18,True) # shift clock high
  GPIO.output(18,False) # shift clock low
  #then the row data
  for x in range(0,6): # do for the 6 bits of row data
    if displayStore[5-x][displayCol] == False:
      GPIO.output(15,True)
    else:
      GPIO.output(15,False)
    GPIO.output(18,True) # shift clock high
    GPIO.output(18,False) # shift clock low
  #disable output drive, latch outputs, then re-enable
  GPIO.output(14,True) #enable low (s/r outputs off)
  GPIO.output(4,True) # register clock high
  GPIO.output(4,False) # register clock low
  GPIO.output(14,False) #enable high (s/r outputs back on)
  return
# main loop of the program
while testCount < 100000:
  #wait for 2ms (gives 83Hz refresh on LEDs)
  time.sleep(0.002)
  #update column count
  displayCol = displayCol + 1
  if displayCol > 5:
    displayCol = 0
    #ready to draw next frame?
    if frameCount > 39:
      frameCount = 0
      if fillFrame == True: # True, so state is trickling down
        displayStore[currentRow][currentCol] = False
        if (displayStore[currentRow + 1][currentCol] == False) and (displayStore[currentRow][currentCol + 1] == False):
          if (random.randint(0,100) > 50):
            currentRow = currentRow + 1
          else:
            currentCol = currentCol + 1
        else:
          if (displayStore[currentRow + 1][currentCol] == False) and (displayStore[currentRow][currentCol + 1] == True):
            currentRow = currentRow + 1
          else:
            if (displayStore[currentRow + 1][currentCol] == True) and (displayStore[currentRow][currentCol + 1] == False):
              currentCol = currentCol + 1
            else:
              if (displayStore[currentRow][currentCol + 1] == True) and (displayStore[currentRow + 1][currentCol] == True):
                displayStore[currentRow][currentCol] = True
                if (currentRow == 0) and (currentCol == 0):
                  fillFrame = False
                  floodCount = 6
                else:
                  currentCol = 0
                  currentRow = 0
        displayStore[currentRow][currentCol] = True
      else: # False, so state is flooding up
        floodCount = floodCount - 1
        if floodCount >= 0:
          for row in range(0,6): # do for 6 rows
            for col in range(0,6): # and 6 columns
              if ((col > floodCount) or (row > floodCount)):
                displayStore[row][col] = False
              else:
                displayStore[row][col] = True
        else:
          displayStore[0][0] = True
          fillFrame = True
          currentCol = 0
          currentRow = 0
    else:
      frameCount = frameCount + 1
  outputToShiftRegisters(displayStore,displayCol)
  testCount = testCount + 1
GPIO.cleanup()

 

 

Links to the previous slices:

9 Pieces of Pi: Slice 1: The Long Way Round

9 Pieces of Pi: Slice 2: Manifesto for Art Electronic

 

If you are interested in my other blogging here on Element14, this link takes you to an index page where many of them are listed: jc2048 Blog Index

 

I'm following along with this Design Challenge as an independent participant, not as one of the challengers. I'm not entering the competition for the prizes.