Meggy pixel video display. 4-in-4, day one.

Spent most of the day at Tymm’s house, pretending to participate in 4-in-4, but mostly drinking coffee and watching videos. I did eventually get around to tinkering with my new Meggy Jr RGB from Evil Mad Scientist Laboratories. The Meggy is a pixel game platform built around a vivid 8×8 LED matrix running an Arduino compatible ATMega168. EMSL has also released a simple to use library for managing the display, buttons and speaker. It’s really a great kit.

I decided to ignore all the nice easy stuff and get right to speaking to the Meggy’s frame buffer, a 192 byte long array containing the values for each RGB pixel. I wrote up a Processing sketch on a MacBook Pro to capture a video image, scale it to 8×8, format the image in a Meggy framebuffer style array and send the whole thing via serial. The most challenging part was figuring out the framebuffer format, which although documented by EMSL was difficult for me to work out initially.

Also, the colors are not correct on the first try, and the RGB elements respond with different brightnesses than each other. The source code from the Meggy library provided some insight on suitable adjustment levels, which the Processing script is doing before sending along the pixel values. Fun times…enjoy the video (may help to squint at the pixels)! Code below the video.

Arduino code:

/*
  MeggyJr_VideoDisplay
 2009 Robert Carlsen // robertcarlsen.net

 Accepts serial data of a video stream from a desktop counterpart
 Expects a header byte of 128, then 192 bytes of pixel data (<= 127)
*/

#include <MeggyJr.h>
MeggyJr Meg;

// stores how many bytes have been received for the current frame:
byte index = 0;

void setup()
{
  Meg = MeggyJr();    // Required.
  Serial.begin(57600);
}  

void loop()
{
  // check if data has been sent from the computer:
  if (Serial.available()) {
    // read in the current byte:
    byte val = Serial.read();

    // look for the header:
    if(val == 128){
      // the header byte was found, the beginning of a new frame in next:
      index = 0;
      return;
    }

    // write the current info directly to the MeggyFrame buffer
    // this may cause tearing or other artifacts...please clean up if you like :)
    // the frame data has been packed in the expected order by the sender program
    // also, only the low four bits are used for pixel color. "& 0x0F"  masks them.
    // i'm thinking of packing other data, such as vertical blanking
    // pixel index or "timecode" in the high bits.
    Meg.MeggyFrame[index] = val & 0x0F;
    index++;
  }

  // don't really need a delay here
  // delay(7);
}

Processing sketch:

/*
  MeggyVideoSend
  2009 Robert Carlsen // robertcarlsen.net

  Captures the video camera image, downsamples it to 8x8
  and packs the pixel data in a format compatible with the Meggy Jr RGB.
  Finally, send the pixel data to the Meggy

  Requires the complementary MeggyJr_Video firmware loaded on the Meggy
*/

import processing.video.*;
import processing.serial.*;

Serial myPort;  // Create object from Serial class
Capture myCapture; // set up a video capture object

// the LEDs respond with varied brightness.
// this adjustment will color correct them.
// feel free to experiment.
float[] adj = {0.6,1.0,0.5}; // red,green,blue

void setup() {
  size(300,300);

  // open a serial port to the Meggy. you may need to change the 0. check the list in the console.
  String portName = Serial.list()[0];
  myPort = new Serial(this, portName, 57600);

  // start a video capture:
  myCapture = new Capture(this, 8, 8, 30);

  // if you have trouble with the above, uncomment the below and replace "Camera Name" with the appropriate device.
  // println(Capture.list());
  // myCapture = new Capture(this, width, height, "Camera Name", 30);
}

void draw() {
  // create an array for the pixel data:
  // one header byte and 192 bytes for pixels (64 pixels * 3 bytes / pixel)
  byte[] c = new byte[193];

  // read the pixels from the video capture:
  myCapture.loadPixels();

  // header byte:
  c[0] = (byte)128;

  // loop through all the pixels in the video image
  // adjust the colors and pack the bytes in the correct order:
  for(int i = 0; i < 64; i++){
    // adjust colors:
    // for an explanation of what's going on, look up "bit shifting" on the Processing site.
    int r = (int)(adj[0]*((myCapture.pixels[i]>>16) & 0xFF));
    int g = (int)(adj[1]*((myCapture.pixels[i]>>8) & 0xFF));
    int b = (int)(adj[2]*((myCapture.pixels[i]) & 0xFF));

    // the Meggy expects 0-15 values only.
    // right shift ">>4" essentially divides our 8-bit values in Processing (0-255) by 16 giving a range of 0-15.
    // the Meggy packs the pixels into a long array, similar to the pixels in Processing.
    // however, each pixel is stored in 3 separate bytes rather than one big integer.
    // the (messy) conversion is below. we're also adding 1 to the index to account for the header byte.
    c[24*(i/8)+i%8+17] = (byte)(r>>4); // r: index + 16
    c[24*(i/8)+i%8+9] = (byte)(g>>4); // g: index + 8
    c[24*(i/8)+i%8+1] = (byte)(b>>4);  // b: index
  }

  // send the whole frame data out via serial:
  myPort.write(c);

  // draw the current frame to the computer screen for preview:
  image(myCapture,0,0,width,height);
}

// read in a new frame of video if available:
void captureEvent(Capture myCapture) {
  myCapture.read();
}

Tags: , , , , , , ,

Leave a Reply