The Matrix

Here’s a final post for 2014!

I’ve been busy with our “bungy” recently – we’re having a lot of decorating etc. done so have been moving stuff around and playing at being a proper bloke rather than just mucking about with my toys this Xmas.

A while ago, I picked up some Adafruit NeoMatrix displays. Five of them to be exact, and at about £25+ a pop that’s not an insignificant investment to have lying around doing nothing.

So, it was time to do something with them. As documented elsewhere, I managed to kill one of them by a slight accident with voltage 😳 Time to move on… As they stand, an 8×8 display is great, and colourful, and er, whatever. But not really much use. My (obvious) idea was to 2×2 the 8×8’s so I get a 16×16 matrix. Would that be “good enough” for animation?

Adafruit do a fine library for controlling the NeoMatrix so I decided to use that – do check out the stuff at josh.com though. I may well end up trying to merge the two since memory becomes a big problem with these puppies. Since memory would clearly be an issue and I didn’t have enough arsed to plumb in an SD card reader I dug out a cheap ENC28j60 ethernet module that I’ve, sort of, played with in the past. After a bit of research, I opted for the UIPEthernet library for Arduino and got to work.

First, the hardware…

The NeoMatrices came in 2 by 2...

The NeoMatrices came in 2 by 2…

That’s 4 NeoMatrix boards stuck together (with bits of wood and hot glue 😳 ). Common ground points are soldered for extra strength. The 2 x 2 is arranged with the first matrix at the top left, second at top right, then bottom left and finally bottom right – all from the front of course.

Since I got the boards, Adafruit now advise a 1000μF capacitor across the power input for the pixels and a 300-500Ω resistor in line with the signal (I used 470Ω).

There’s a mini-USB for powering the LEDs but, after a cock-up with a 2A wall wart, I had to switch to a beefier power supply – 256 pixels x 3 colours @ 20mA each is 5.12A. Shame I didn’t do that simple sum before running full on, in yer face, white with a 2A wall wart that now smells of burnt plastic 😳

After playing around with an Arduino Uno for a little while, it became obvious that 2K of SRAM just wasn’t going to cut it. At all.

At some point, I bought an Arduino Mega2560 so I switched to that – it has 8K of SRAM so much more headroom to store the pixel data and buffer things received over ethernet. Here’s the Arduino code:

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>

#define PIN 6

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, 2, 2, PIN,
  NEO_TILE_TOP   + NEO_TILE_LEFT   + NEO_TILE_ROWS   + NEO_TILE_PROGRESSIVE +
  NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE,
  NEO_GRB + NEO_KHZ800);

// Ethernet server
#include <UIPEthernet.h>
EthernetServer server = EthernetServer(1234);

void setup() {
  //Serial.begin(115200);

  uint8_t mac[6] = {0xa0,0xa1,0x02,0x03,0x04,0x05};
  IPAddress myIP(192,168,0,99);
  Ethernet.begin(mac,myIP);
  server.begin();

  matrix.begin();
  matrix.setBrightness(25);
  matrix.fillScreen(matrix.Color(0, 128, 0));
  matrix.show();
}

void loop() {
  int size;

  if (EthernetClient client = server.available())
    {
      while((size = client.available()) > 0)
        {
          uint8_t* msg = (uint8_t*)malloc(size);
          size = client.read(msg,size);
          //Serial.println(size);
          if ( size == 768 ) {
            int p = 0;
            for (int j=0; j<16; j++) {
              for (int i=0; i<16; i++) {
                byte r = msg[p+0];
                byte g = msg[p+1];
                byte b = msg[p+2];
                p += 3;
                matrix.drawPixel(i, j, matrix.Color(r, g, b));
              }
            };
          } else if ( size == 3 ) {
            byte r = msg[0];
            byte g = msg[1];
            byte b = msg[2];
            matrix.fillScreen(matrix.Color(r, g, b));
          } else if ( size == 1 ) {
            matrix.setBrightness(msg[0]);
          }
          matrix.show();
          free(msg);
        }
      client.println("OK");
      client.stop();
    }
}

It’s fairly simple (well, more like “brain-dead” to be honest). A one byte packet sets the brightness level, 3 bytes represents an RGB colour that all the pixels get set to and 768 bytes is interpreted as 16×16 RGB image data. Anything else is ignored. That’s a problem because TCP/IP can fragment packets to improve performance as it sees fit so not all of my 768 byte images arrive in one piece – yes, that means frames can get randomly dropped. Not good.

I use Python to sort out the images and pull out multiple frames if it’s an animation. Here’s the main program:

from __future__ import print_function

import sys
import time

import frames
import matrix

HOST = '192.168.0.99'
PORT = 1234

images = frames.Frames(sys.argv[1])

m = matrix.Matrix(HOST, PORT)
m.setBrightness(20)
try:
    if images.length > 1:
        while True:
            for img in images.images:
                m.send(img.tobytes())
                time.sleep(0.2)
    else:
        m.send(images.images[0].tobytes())
        while True:
            x = raw_input("")

except:
    m.off()

sys.exit(0)

There are a couple of modules to go with that, firstly “frames.py” to sort out the images:

from __future__ import print_function

from PIL import Image

# Default image size
_WIDTH = 16
_HEIGHT = 16


class Frames(object):

    '''
    Turn an image file into a list of 16x16 RGB frames that can
    be sent to an RGB LED display.
    '''

    def __init__(self, fileName, width=_WIDTH, height=_HEIGHT):
        self.width = width
        self.height = height
        img = Image.open(fileName)
        frame = self.__forceSize(img)

        results = [frame]

        # Look for additional frames
        nframes = 0
        while True:
            nframes += 1
            try:
                img.seek(nframes)
            except EOFError:
                break
            results.append(self.__forceSize(img))

        self.images = results
        self.length = len(results)

    def __forceSize(self, img):
        img = img.convert('RGB')
        img.thumbnail((self.width, self.height), Image.ANTIALIAS)
        # check size
        size = img.size
        if size[0] != self.width or size[1] != self.height:
            tmp = Image.new(img.mode, (self.width, self.height))
            tmp.paste(img, (0, 0))
            img = tmp
        return img

And “matrix.py” to handle talking to the display:

from __future__ import print_function

import sys
import socket

class Matrix(object):

    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    def send(self, stuff):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except socket.error, msg:
            sys.stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(1)

        try:
            sock.connect((self.ip, self.port))
        except socket.error, msg:
            sys.stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(2)

        # Send the data
        sock.send(stuff)

        # Wait for reply
        data = sock.recv(16)
        string = ""
        while len(data):
            string = string + data
            data = sock.recv(16)

        assert string.strip() == "OK"

        sock.close()

    def off(self):
        self.setBrightness(0)

    def setBrightness(self, brightness):
        data = bytearray()
        data.append(brightness)
        self.send(data)

    def rgb(self, r, g, b):
        data = bytearray()
        data.append(r)
        data.append(g)
        data.append(b)
        self.send(data)

The Python Imaging Library is used to do the heavy lifting and the socket stuff is just based on the standard example. I’m leaving this here for now (insufficient 2014 arsed reserves remaining 😆 ) but I’ll get back to it and improve it for next Xmas. I need to handle packet fragmentation and probably add some kind of command/protocol so I can also do scrolling text (as supported by the Adafruit library). It would be nice to handle bigger displays too so maybe using an ATmega1284 (yes, I have one) with 16K RAM would help – as would merging the library by Josh (no pixel storage) with the Adafruit library so I only have to store the data received over ethernet.

All in all, it’s fairly amazing that you can put something together like this so easily. What was that about standing on the shoulders of giants? Finally, here’s the obligatory crap video…

Happy New Year!

 

Update: Code now on GitHub (so I can find it again) as megaMatrix and PyMatrix

This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*