Image Animation on a 20X20 LED Matrix
A Contol System designed and made by Jens Laland for Hege Tapio

The matrix is going to be part of an art installation running extremly small film sequenses in 'black and white' using a 20 by 20 pixels image with each LED evenly distributed over an area of 2 by 2 meters (i.e. distanced 10 cm apart in all dirctions). To tie each light spot together despite the physical distance between them, they are mounted with a 15 mm airgap behind a 12 mm thick satinated sheet of plexiglas. Test results show that a visual effect occurs much like a beautiful illuminated halo within the satinated plexiglas around each LED being turned on.

Foto: Jonas Haarr Friestad  
Hege Tapio working on one of her LED Panel in my studio
Something about how it all started...

I reckon a lot of things get started when somebody use their phone to ask somebody else for help. This particular project started when Hege Tapio, an artist with IOLAB in Stavanger, called me on my cellular sometime during the fall of 2003. She had this idea about running simple animations on a light panel made up by numerous Super Bright LEDs (light emitting diodes) configured within a two-dimentional matrix. She wanted me to show her how she could make such a panel herself, but she definitely needed me to design and build the rest of the stuff that had to go with it.

So, what we had to come up with turned out to be more than twentyfive circuits boards and some adequate software to run transformed video animations on a double sided 2X2 meter panel consisting of 800 LEDs total. The final result was to become a permanent artwork installation within a 3X3 meter sized window within the north wall of the Byterminalen building in Stavanger, Norway.

At first I thought everything was going to be dead easy, and cheap. That is, an opportunity to get some money out of it. But it didn't take me long to prove otherwise. If you start something be sure you know what your client expect to get. Don't forget to pinpoint all your deliverables. Do not improvise as you go along, like I did. Ok, you may think it's an interesting project, but most of your other priorities will be suffering from it. That is. you will not be able to make much money on the side, because the project ought to be your main income for the entire duration of the job. Well, it did not quite work out like that for me (it normally never does). However, while prosperity and profit slowly faded away, I assumed and failed, studied and experimented, and slowly gained a lot of new teknowledge.

However, I would like to thank some of my friends who helped me cut corners and solve problems: Øyvind Idland, who wrote a utility program to convert her bitmap images extracted from video snippets into the Turbo Pascal array format; Kjell Sele, who personally volunteered to fabricate the 5 Volt, 30 Amps power distribution bars needed to illuminate all the LEDs (drawing 16 Amps when fully loaded); and finally Rolf Rolfsen, who assisted me in solving an annoying problem with glitches during the first few test runs of the panel.

matrix2oei.jpg  
The Matrix Control System
How to Individually Control 400 Light Emitting Diodes within a two-dimentional matrix

I decided to assemble two cards of the K-8000 kit from Velleman, and hook those up between the printer port of a 486 PC and my own homebrewed hardware. I knew the K-8000 cards from a former project, my own Sound Panel (an 8X8 matrix with 64 loudspeakers), back in 1999. So I quoted the job under the assumption that the control of a light panel would be similar to the idea of painting with sound, but again I was proven wrong (both technically and economically). While it was a relatively simple task to individually control an array of loudspeakers (where only one is expected to produce sound at any given time); it became a whole lot different to control 2X400 individual LEDs. How could I have overlooked the simple fact that animations require everything from zero to all "pixles" to be active within a particular image frame?

Such a blunder called for some serious thinking, and I finally realized that the best thing to do would be to write and store the current status of each horizontal line into a memory array made up of dual 2-bit bistable transparent latches; i.e. using 5 IC chips of the type 74HCT75 to remember each of the 20 horizontal lines as a 20 bit wide data word. This way I did not have to run a multiplexing switch really fast and just hope that the LEDs retain their brightness long enough to retain the image until the multiplexer came round to the same line again. You see, I could not allow the possibility that such a latency type setup (as used in a TV set) should produce any bad amounts of flicker (although, it would have saved us a large amount of hardware if we did it like this).

I hope the use of memory cells would produce interesting animations at any speed (frames per second), giving us the possibility of producing some very peculiar forms of animation, somewhat distinctly different than running a movie (at 24 fps). This, because you avoid the choppy effect we know so well from the very first, old silent movies - instead, now you may only register a line being changed a bit here and there (depending on what is being animated, of course).

matrix3oei.jpg  
The First Successful Animation Test Run

I first made a prototype card to handle one line, and when that was tested I designed a proper printed circuit board using appropriate software and finally had 20 cards made

And as each line housed 20 diodes as... (?) write each of the 20 Lines in the matrix into D-Latches, using one latch for each LED in every line. To accomplish this, I use all 16 Digital IO channels on my master K-8000 card + 4 Digital IO channels on my slave K-8000 card.

Each of the 20 lines in the matrix requires therefore 20 D-Latches (i.e. 5 ea. 74HCT75N). 20 open-collector drivers for each and every one of the LEDs in the line, using BC558B transistors.

For a 20X20 Matrix (400 LED's), this adds up to a total of 100 ea. 74HCT75N ICs [providing me with the required amount of 400 latches] 400 ea. BC558B transistors [open-collector LED drivers]

Since I write only one line at the time, I needed 5 additional Digital IO channels on my slave K-8000 card for addressing the lines (i.e. enabling the relevant D-latches). This way, each line remembers it's setting until next time that particular line is updated. 2 ea. 74HCT154E plus one inverter (74HCT04N) makes up the addressing circuitry.

In principle, each 20 by 20 character or matrix image is initially drawn using a 20 by 20 Monochrome Bitmap Editor that I made myself. Another homemade program (code generator) then converts the bitmap into a Turbo Pascal two-dimentional data array. The same software also generate the rest of the code needed to control the two K-8000's and it's LED matrix.

biosensor01.jpg
The BioSensor [viewed from the street]
References
The Software

The following software has been developed by Øyvind Idland in conjunction with this project. It is a utility program that converts bitmap images extracted from video snippets and converts them into the Turbo Pascal array format.

bmpconv.jpg
The 32-bit command line utility, BMPCONV.EXE

Make sure that the location of the program bmpconv.exe is declared within the path-statement of your computer, or that it resides in the same folder as the bitmap files you're processing.

To change to a different folder, write the following command:

C:\ANY_FOLDER\ANY_SUBFOLDER\>cd\your_dir\bmp\eye

C:\YOUR_DIR\BMP\EYE>_

So, when we've changed to the new folder, this is what we do next:

C:\YOUR_DIR\BMP\EYE>dir *.bmp

 Volume in drive C has no label
 Volume Serial Number is 0F36-13D5
 Directory of C:YOUR_DIR\BMP\EYE

EYE001    BMP               144 26.04.04  19:43
EYE002    BMP               144 26.04.04  19:47
EYE003    BMP               144 26.04.04  19:58
         3 file(s)                432 bytes
                          182 358 016 bytes free

C:\YOUR_DIR\BMP\EYE>_

Note: All bitmap files you plan to use and include in the animated sequence must be monochrome 1-bit type, and have a size of exactly 20 X 20 pixles (ie. 144 bytes).

Let us try the BMPCONV.EXE with the above bitmap files and route the result to a Turbo Pascal 7 unit named EYE.PAS:

C:\YOUR_DIR\BMP\EYE>bmpconv eye*.bmp > c:\your_dir\pas\eye.pas

This is what the output file EYE.PAS would look like:

unit eye;

interface

uses matrix;

procedure Play_eye;

implementation

uses crt;

CONST

eye001: Bmp20X20 =
{00}  ( ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{01}  ($F4, $FF, $70), { OOIOIIII IIIIIIII IIIO }
{02}  ($F0, $FF, $F0), { OOOOIIII IIIIIIII IIII }
{03}  ($FC, $FF, $F0), { OOIIIIII IIIIIIII IIII }
{04}  ($FC, $FF, $F0), { OOIIIIII IIIIIIII IIII }
{05}  ($F0, $FF, $F0), { OOOOIIII IIIIIIII IIII }
{06}  ($00, $FF, $F0), { OOOOOOOO IIIIIIII IIII }
{07}  ($00, $FC, $F0), { OOOOOOOO OOIIIIII IIII }
{08}  ($00, $E0, $F0), { OOOOOOOO OOOOOIII IIII }
{09}  ($00, $00, $F0), { OOOOOOOO OOOOOOOO IIII }
{10}  ($00, $00, $E0), { OOOOOOOO OOOOOOOO OIII }
{11}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{12}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{13}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{14}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{15}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{16}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{17}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{18}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{19}  ($00, $00, $00) ); { OOOOOOOO OOOOOOOO OOOO }

eye002: Bmp20X20 =
{00}  ( ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{01}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{02}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{03}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{04}  ($00, $06, $00), { OOOOOOOO OIIOOOOO OOOO }
{05}  ($00, $0F, $00), { OOOOOOOO IIIIOOOO OOOO }
{06}  ($C0, $2F, $80), { OOOOOOII IIIIOIOO OOOI }
{07}  ($C0, $27, $00), { OOOOOOII IIIOOIOO OOOO }
{08}  ($60, $42, $00), { OOOOOIIO OIOOOOIO OOOO }
{09}  ($20, $C6, $00), { OOOOOIOO OIIOOOII OOOO }
{10}  ($01, $0E, $00), { IOOOOOOO OIIIOOOO OOOO }
{11}  ($21, $FE, $00), { IOOOOIOO OIIIIIII OOOO }
{12}  ($33, $FE, $80), { IIOOIIOO OIIIIIII OOOI }
{13}  ($23, $FE, $80), { IIOOOIOO OIIIIIII OOOI }
{14}  ($6F, $DE, $C0), { IIIIOIIO OIIIIOII OOII }
{15}  ($6F, $FE, $E0), { IIIIOIIO OIIIIIII OIII }
{16}  ($FF, $7F, $F0), { IIIIIIII IIIIIIIO IIII }
{17}  ($FF, $3F, $F0), { IIIIIIII IIIIIIOO IIII }
{18}  ($FB, $FF, $F0), { IIOIIIII IIIIIIII IIII }
{19}  ($EA, $FF, $F0) ); { OIOIOIII IIIIIIII IIII }

eye003: Bmp20X20 =
{00}  ( ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{01}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{02}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{03}  ($00, $00, $00), { OOOOOOOO OOOOOOOO OOOO }
{04}  ($00, $06, $00), { OOOOOOOO OIIOOOOO OOOO }
{05}  ($00, $0F, $00), { OOOOOOOO IIIIOOOO OOOO }
{06}  ($C0, $2F, $80), { OOOOOOII IIIIOIOO OOOI }
{07}  ($C0, $27, $00), { OOOOOOII IIIOOIOO OOOO }
{08}  ($60, $42, $00), { OOOOOIIO OIOOOOIO OOOO }
{09}  ($20, $C6, $00), { OOOOOIOO OIIOOOII OOOO }
{10}  ($01, $0E, $00), { IOOOOOOO OIIIOOOO OOOO }
{11}  ($21, $FE, $00), { IOOOOIOO OIIIIIII OOOO }
{12}  ($33, $FE, $80), { IIOOIIOO OIIIIIII OOOI }
{13}  ($23, $FE, $80), { IIOOOIOO OIIIIIII OOOI }
{14}  ($6F, $DE, $C0), { IIIIOIIO OIIIIOII OOII }
{15}  ($6F, $FE, $E0), { IIIIOIIO OIIIIIII OIII }
{16}  ($FF, $7F, $F0), { IIIIIIII IIIIIIIO IIII }
{17}  ($FF, $3F, $F0), { IIIIIIII IIIIIIOO IIII }
{18}  ($FB, $FF, $F0), { IIOIIIII IIIIIIII IIII }
{19}  ($EA, $FF, $F0) ); { OIOIOIII IIIIIIII IIII }


procedure Play_eye;
begin
 DoFrame(eye001);
 DoFrame(eye002);
 DoFrame(eye003);
end;

end.

As you can see, the file EYE.PAS uses the MATRIX.PAS file (a Turbo Pascal Unit compiled to MATRIX.TPU). This file looks like this:

unit matrix;
interface
TYPE
  Bmp20X20 = array[$0C..$1F,0..2] of byte;

procedure DoFrame(Frame : Bmp20X20);

implementation
uses I2C;

procedure DoFrame(Frame : Bmp20X20);
var
  Line : byte;
begin
  For Line := $0C to $1F do begin { 12..31 }
    IOoutput(0,Frame[Line,0]);
    IOoutput(1,Frame[Line,1]);
    IOoutput(2,Frame[Line,2]);
    { write address to 74HCT154's }
    IOoutput(3,Line);
    { write data to latches, i.e. 
      enable both chips (74HCT154)}
    IOoutput(3,Line OR $20);
    { unable both chips (74HCT154) }
    IOoutput(3,Line);
  end;
end;

end.

The main program, called BIOSNSOR.PAS (now using the following bitmap sequence files EYE.PAS, HAND.PAS, LILJE.PAS, SAU.PAS, VANN.PAS, SIGN.PAS, and FROE.PAS plus the units I2C.PAS and MATRIX.PAS) looks like this:

PROGRAM BioSensor;
USES i2c, matrix, eye, hand, lilje, sau, vann, sign, froe;
CONST
  NUMBER_OF_LOOPS = 6;
  {--------------------------------------------------------------------
    NUMBER_OF_LOOPS denotes the number of sequences available to play
    when Door is OPEN. This constant is used to pass to the Random
    Function which then returns an integer (word) greater than or equal
    to ZERO and less than the value of NUMBER_OF_LOOPS.
  -------------------------------------------------------------------- }
  PrinterPort = 1;    {1=Lpt1, 2=Lpt2}
var I : integer;

procedure ShutDown;
  begin
    ClearIOchip(0);
    ClearIOchip(1);
    ClearIOchip(2);
    ClearIOchip(3);
    I2CbusNotBusy;      { I/O Card }
    Halt;
  end;

PROCEDURE Config_IO_Cards;
begin
  SelectI2CprinterPort(PrinterPort);
  I2CbusDelay := 0;
  ConfigIOchipAsOutput(0);
  ConfigIOchipAsOutput(1);
  ConfigIOchipAsOutput(2);
  ConfigIOchipAsOutput(3);
  ConfigIOchannelAsInput(32);  { reading status of door open/close }
  ClearIOchip(0);
  ClearIOchip(1);
  ClearIOchip(2);
  ClearIOchip(3);
  UpdateCard(0);
  UpdateCard(1);
end;

Procedure PlayDefault;
var
  Enough : boolean;
  ActiveLoop : integer;
begin
 Enough := FALSE;
 repeat
   ReadIOchannel(32);
   case IO[32] of
     FALSE : begin
       ActiveLoop := Random(NUMBER_OF_LOOPS);
       case ActiveLoop of
         { Please note that ZERO TO 5 equals ONE TO NUMBER_OF_LOOPS }
         0 : Play_eye;
         1 : Play_hand;
         2 : Play_lilje;
         3 : Play_sau;
         4 : Play_vann;
         5 : Play_sign;
       end; { ActiveLoop }
     end; { IO[32] = FALSE }
     else Play_froe; { default animation }
   end; { CASE IO[32] OF...}
 until Enough; { Endless Loop, ie. Enough is always FALSE }
end;

BEGIN
  Config_IO_Cards;
  PlayDefault;
  Shutdown;
end.

The computer which controls the ledmatrix is booted from a 3.25" floppy disk set up with the MS-DOS Version 6.2 as operative system. To do this, write the following following the DOS prompt on your Auxilliary DOS Lab-Machine:

C:\YOUR_DIR\BMP\EYE>format a: /u /s

Press <ENTER> and follow the instructions on the screen. When this is done, and you are back at the DOS Prompt, write the following to make the batch-file AUTOEXEC.BAT:

C:\YOUR_DIR\BMP\EYE>copy con: autoexec.bat
biosnsor.exe
^Z
        1 file(s) copied

C:\YOUR_DIR\BMP\EYE>_

The ^Z is written simply by simultaneously pressing the keys <Ctrl> and <Z> on the keyboard!

Now, the floppy disk is loaded with a start-up batch file (AUTOEXEC.BAT) which contains the following single command:

biosnsor.exe

Now you change from the current folder to the folder where your program (BIOSNSOR.EXE) resides, and you do this by writing the following command at the DOS Prompt:

C:\YOUR_DIR\BMP\EYE>cd\your_dir\bin

Finally, you copy the executable file (BIOSNSOR.EXE) itself onto the floppy disk which should still be in the Floppy Drive Station A:

C:\YOUR_DIR\BIN>copy biosnsor.exe a:

If you now check what's on the floppy, you should read something like this:

C:\YOUR_DIR\BIN>dir a:

 Volume in drive A has no label
 Volume Serial Number is 0F36-13D5
 Directory of A:\

AUTOEXEC  BAT                14 26.04.04  19:39
COMMAND   COM            54 645 26.04.04  19:00
BIOSNSOR  EXE           102 371 26.04.04  19:47
         3 file(s)            159 028 bytes
                            1 350 921 bytes free

C:\YOUR_DIR\BIN>_
Download Files

Copyright © 2003  Jens Laland