Z Looking Glass: Project SmartSurfaces

A insider's observation of Project SmartSurface

Peggy2: High and Low December 19, 2009

Filed under: Uncategorized — zilinzen @ 3:48 pm

First and foremost, big thanks to Damien for her time and effort on soldering 265 LEDs on peggy2 circuit board, which made my programming part possible to continue.

Instead of soldering LEDs directly onto the Peggy2 board, we have to use Ethernet cables as “extensions” in order to let LEDs fill our smartsurface. Single Red, Green, Blue, and White LEDs are grouped as a module and then soldered on a small printed circuit board, which is connected to one end of a Ethernet cable while the other end is soldered to peggy2 circuit board that provides us means to control each LED in the matrix through programming. The Ethernet theory was confirmed by the Peggy2’s vendor, Evil Mad Scientist and the longest cable we are using is about 3 ft, whose resistance should not caused any trouble for us to light up the LEDs.

Red, Green, Blue, White LEDs module

The finished Peggy2 looked more like a octopus than a matrix. Besides the joy from seeing the completed device, I felt a bit concerned about possible shorting among those touching modules.

Peggy2 LED "Octopus"

However, after we powered the board with the power adapter purchased from the same vendor, nearly half of the unit didn’t light up and there isn’t a single unit that has all its RGBW LEDs lighted up. They either had only one or two on. I didn’t upload any new sketch. So according to the manual, factory pre-programmed sketch is supposed to uniformly light up all the LEDs. The bright side is that at least we confirmed that cable+LED works as well as LED on its own.
Here comes the worst part: the two LED driver chips heated up very fast (they got super hot within ~5 sec). I checked the components and everything is soldered correctly according to the instruction except my teammate placed the socket for 328 microcontroller in the opposite direction but the microcontroller itself is in the correct position with its half-moon matched to the drawing on the board. I personally don’t think the mis-orientated socket is the source of the problem because a electric socket are just whole bunch of metals and plastic that holds a electronic component.

We then tried to test each connection on the board with a single DC power supply (30V 1A), with a voltage of 2.7V and a current of 0.01A. Sometime we saw when connecting to a LED, let’s say R2 of the RGBW unit#2 shown below, the LED R1 of unit#1 that is two row above dimly lighted up, even though nothing was connected to it. I’m not sure if it’s normal or not.

Row 1: W1, B1
Row 2. R1, G1
Row 3. W2, B2
Row 4. R2, G2

After hours of trouble-shooting, we decided it’s easier to map all the LED to their corresponding rows and columns by taping the “Octopus” to a wall, so hopefully we can find some “patterns” among those malfunctioned LEDs.

Troubleshooting: Mapping the rows and columns

Troubleshooting: mapping the rows and columns

After trying different codes and circuit configurations for another couple hours, things stayed the same. Now it’s the time to get helps from pros on forums. First, special thanks to Windell on Evil Mad Scientist Forum for his bullet-speed reply at 2am in the morning.

It turned out that someone put the 2 LED driver chips at the wrong place. So the chips that are supposed to be on socket U4 U5 were placed on socket U2 U3 and vice versa. So the overheating chips. So the overheating chips I mentioned earlier are actually 74HC154 chips that control the columns of the display. Basically, someone switched chips that controls rows of the matrix with chips that controls columns. And here is what Windell said:

Yikes– That’ll do it! If you plug those in at the LED driver locations, one of their outputs is connected to ground– a short circuit that may explain the overheating. Those chips are not “power” chips, and I don’t believe that they have any thermal shutdown mechanism; they could easily be damaged. The other two chips (LED drivers) could also be damaged the same way. (Also: your LEDs will not light up anywhere near uniformly– I’ll guarantee that.)
The 74HC154 chips control the rows, and the LED driver chips control the columns. If you have rows out then it could be due to a problem with those chips. In any case, I’d strongly recommend replacing all four chips as a first step just to make sure that everything else is okay. If it is, then you could swap in the old chips one at a time (in the right places) to see if they are okay. Only the AVR (the ATmega328P) has firmware on board, so you can replace the other four chips with off-the-shelf components (or all five chips, if you have an AVR ISP programmer).

Since we only have three days till our final installation in the gallery, we decided to order some chips from Evil Mad Scientist. Good news is that they have chips in stock. Unfortunately, because of they are on sale, the shipping must be delayed up to one week while we only have three days. I then email sales manager Lenore M. Edman and thanks to him, our chips can be shipped on next day with FedEx overnight shipping.

Now it seems things are getting better. However, good days only lasted till the chips arrived on the next day. After I carefully replaced those delicate chips from the huge “Octopus” LED matrix taped on the wall, the whole matrix were still malfunctioning. Well, I must say some rows lit up but certainly, the repaired peggy2 board was far from meeting what we need.

After spending nearly 50 hours trying to fix poor peggy, we aborted any further attempt, because time was running out. Driven by the hope that “I might fix the board with another hour”, I actually spent the time budgeted for CMUcam3, which turned out to be even more complicated than a “Octupos” LED matrix.

Eric and Damien came up with peggy2 plan B. With some copper tape and some transistors, they managed to get a small LED “Octopus” that could only light up a row of LEDs altogether. Even though this matrix might not be as sophisticated as the original one that allows you to control each LED. But it’s a lot better than not having any LED on our final surface. Moreover, it’s very impressive for them to pull this off in just several hours. Good job!

Here is the code for RGBW color gradient diffusion demonstrated in the video.

////////////////////////////////////////////////////////////////////////////////////////////
// FPS must be high enough to not have obvious flicker, low enough that main loop has
// time to process one byte per pass.
// ~140 seems to be about the absolute max for me (with this code on avr-gcc 4.2, -Os),
// but compiler differences might make this maximum value larger or smaller.
// if the value is too high errors start to occur or it will stop receiving altogether
// conversely, any lower than 60 and flicker becomes apparent.
// note: further code optimization might allow this number to
// be a bit higher, but only up to a point...  
// it *must* result in a value for OCR0A in the range of 1-255

// Suggested refresh rates: 80 Hz, 60 Hz.
#define FPS 80
//#define FPS 60     // Can be changed somewhat...  A tradeoff between faster refresh versus
 // faster computations.

// 25 rows * 13 bytes per row == 325
#define DISP_BUFFER_SIZE 325
#define MAX_BRIGHTNESS 15

////////////////////////////////////////////////////////////////////////////////////////////
uint8_t frameBuffer[DISP_BUFFER_SIZE];

uint8_t *currentRowPtr = frameBuffer;
uint8_t currentRow=0;
uint8_t currentBrightness=0;

// Note: the refresh code has been optimized heavily from the previous version.
SIGNAL(TIMER0_COMPA_vect)
{     

 // there are 15 passes through this interrupt for each row per frame.
 // ( 15 * 25) = 375 times per frame.
 // during those 15 passes, a led can be on or off.
 // if it is off the entire time, the perceived brightness is 0/15
 // if it is on the entire time, the perceived brightness is 15/15
 // giving a total of 16 average brightness levels from fully on to fully off.
 // currentBrightness is a comparison variable, used to determine if a certain
 // pixel is on or off during one of those 15 cycles.   currentBrightnessShifted
 // is the same value left shifted 4 bits:  This is just an optimization for
 // comparing the high-order bytes.
 if (++currentBrightness >= MAX_BRIGHTNESS)  
 {
 currentBrightness=0;
 if (++currentRow > 24)
 {
 currentRow =0;
 currentRowPtr = frameBuffer;
 }
 else
 {
 currentRowPtr += 13;
 }
 }

 ////////////////////  Parse a row of data and write out the bits via spi
 uint8_t currentBrightnessShifted = currentBrightness <<4;

 uint8_t *ptr = currentRowPtr + 12;  // its more convenient to work from right to left
 uint8_t p, bits=0;

 // optimization: by using variables for these two masking constants, we can trick gcc into not
 // promoting to 16-bit int (constants are 16 bit by default, causing the
 // comparisons to get promoted to 16bit otherwise)].  This turns out to be a pretty
 // substantial optimization for this handler
 uint8_t himask = 0xf0;  
 uint8_t lomask = 0x0f;

 // Opimization: interleave waiting for SPI with other code, so the CPU can do something useful
 // when waiting for each SPI transmission to complete

 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=1;
 SPDR = bits;

 bits=0;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=64;
 if ((p & himask) > currentBrightnessShifted)    bits|=128;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=16;
 if ((p & himask) > currentBrightnessShifted)    bits|=32;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=4;
 if ((p & himask) > currentBrightnessShifted)    bits|=8;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=1;
 if ((p & himask) > currentBrightnessShifted)    bits|=2;

 while (!(SPSR & (1<<SPIF)))  {
 } // wait for prior bitshift to complete
 SPDR = bits;

 bits=0;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=64;
 if ((p & himask) > currentBrightnessShifted)    bits|=128;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=16;
 if ((p & himask) > currentBrightnessShifted)    bits|=32;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=4;
 if ((p & himask) > currentBrightnessShifted)    bits|=8;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=1;
 if ((p & himask) > currentBrightnessShifted)    bits|=2;

 while (!(SPSR & (1<<SPIF)))  {
 } // wait for prior bitshift to complete
 SPDR = bits;

 bits=0;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=64;
 if ((p & himask) > currentBrightnessShifted)    bits|=128;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=16;
 if ((p & himask) > currentBrightnessShifted)    bits|=32;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=4;
 if ((p & himask) > currentBrightnessShifted)    bits|=8;
 p = *ptr--;
 if ((p & lomask) > currentBrightness)              bits|=1;
 if ((p & himask) > currentBrightnessShifted)    bits|=2;

 while (!(SPSR & (1<<SPIF)))  {
 }// wait for prior bitshift to complete
 SPDR = bits;

 ////////////////////  Now set the row and latch the bits

 uint8_t portD;

 if (currentRow < 15)
 portD = currentRow+1;
 else
 portD = (currentRow -14)<<4;

 while (!(SPSR & (1<<SPIF)))  {
 } // wait for last bitshift to complete

 //if (currentBrightness == 0)
 PORTD = 0;                // set all rows to off
 PORTB |= _BV(1); //  latch it, values now set
 //if (currentBrightness == 0)
 PORTD = portD;     // set row
 PORTB &= ~(_BV(1)); // reset latch for next time

 // notes to self, calculations from the oscope:
 // need about minimum of 6us total to clock out all 4 bytes
 // roughly 1.5ms per byte, although some of that is
 // idle time taken between bytes.  6=7us therefore is our
 // absolute minimum time needed to refresh a row, not counting calculation time.
 // Thats just if we do nothing else when writing out SPI and toggle to another row.
 //Measured values from this routine    
 // @ 144 fps the latch is toggled every 19us with an actual 4byte clock out time of 12-13us
 // @ 70 fps the latch is toggle every 39us, with a clock out time of 13-14us
 // times do not count setup/teardown of stack frame

 // one byte @ 115k takes 86us (max) 78us (min) , measured time
 // one byte @ 230k takes 43us (max) 39us (min) , measured time
 // so 230k serial might barely be possible, but not with a 16mhz crystal (error rate to high)
 // 250k might just barely be possible
}

void displayInit(void)
{
 // need to set output for SPI clock, MOSI, SS and latch.  Eventhough SS is not connected,
 // it must apparently be set as output for hardware SPI to work.
 DDRB =  (1<<DDB5) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1);
 // set all portd pins as output
 DDRD = 0xff; 

 PORTD=0; // select no row

 // enable hardware SPI, set as master and clock rate of fck/2
 SPCR = (1<<SPE) | (1<<MSTR);
 SPSR = (1<<SPI2X); 

 // setup the interrupt.
 TCCR0A = (1<<WGM01); // clear timer on compare match
 TCCR0B = (1<<CS01); // timer uses main system clock with 1/8 prescale
 OCR0A  = (F_CPU >> 3) / 25 / 15 / FPS; // Frames per second * 15 passes for brightness * 25 rows
 TIMSK0 = (1<<OCIE0A);    // call interrupt on output compare match

 for (uint8_t i=0; i < 4; i++)
 {
 SPDR = 0;
 while (!bit_is_set(SPSR, SPIF)) {
 }
 }
}

void SetPx(byte x,byte y, byte red, byte green,byte blue, byte white)
{
 /*  A "Pixel" is a 2x2 unit cell, consisting of a RED, GREEN, BLUE, and WHITE LED.
 Looking at the top left square, we have
 NW: White
 NE: Red
 SW: Green
 SE: Blue
 */

//  unsigned int Offset = 13*(y << 1) + x;

 unsigned int Offset = y << 1;
 Offset *= 13;
 Offset += x;

 if (x < 13)
 frameBuffer[Offset] = white | (red << 4);  // x overflow, drawing "red" dot if x=12 is ok.

 if (y < 12)
 frameBuffer[13 + Offset] = green | (blue << 4);

}

void SetPxSeq(byte x,byte y, byte step, byte white)
{

 byte redTemp, greenTemp, blueTemp;

 if (step <= 15)
 {
 redTemp = step;
 greenTemp = 0;
 blueTemp = 15 - step;
 }
 else  if (step <= 31)
 {
 redTemp = 31 - step;
 greenTemp = step - 16;
 blueTemp = 0;
 }
 else  if (step <= 47)
 {
 redTemp = 0;
 greenTemp = 47 - step;
 blueTemp = step - 32;
 }

SetPx(x,y,redTemp,greenTemp,blueTemp,white);

}

//byte StepArray[13][13];

byte  r,g,b,w;

byte stepoffset;

float xCtr,yCtr;

byte debounce,buttonPressed, color;

void setup()                    // run once, when the sketch starts
{ 

 float radius;

 // Enable pullups for buttons/i2c
 PORTB |= _BV(0);
 PORTC = _BV(5) | _BV(4) | _BV(3) | _BV(2) | _BV(1) | _BV(0);

 UCSR0B =0; // turn OFF serial RX/TX, necessary if using arduino bootloader 

 displayInit(); 

 sei( );

 // clear display and set to test pattern
 // pattern should look just like the "gray test pattern" from EMS

 uint8_t v = 0;
 for (int i =0; i < DISP_BUFFER_SIZE; i++)
 {
 v = (v+2) % 16;
 // set to 0 for blank startup display
 // low order bits on the left, high order bits on the right
 //    frameBuffer[i]= v + ((v+1)<<4);  
 frameBuffer[i]=0;

 } 

 r = 0;
 g = 0;
 b = 0;
 w = 0;

 byte i,j;
 i= 0;

 while (i < 13)
 {

 j = 0;
 while (j < 13)
 {
 SetPx(i,j,15,15,15,15);
 SetPx(i,j,1,1,1,1);
 j++;

 }

 i++;
 }

 SetPx(2,12,15,15,15,15);

 stepoffset = 0;
 xCtr = 5.5;
 yCtr = 5.5;
 buttonPressed = 0;
 debounce = 0;
 color = 4;
}

void loop()                     // run over and over again
{

 /* Example usage of the SetPx routine.  In each case, a "pixel" is a 2x2 LED square, and the 0,0 block
 is in the upper left hand corner.  RGBW values may each be as high as 15.

 SetPx(0,0,0,0,0,15);    // Set 0,0, white.   Works
 SetPx(0,1,0,0,0,15);    // Set 0,1, white.
 SetPx(3,3,15,15,15,15);    // Set 3,3, RGBW
 SetPx(11,11,15,15,15,15);    // Set 3,3, RGBW

 */

byte i,j,k;
float temp;

if (buttonPressed)
{

if ((PINB & 1U) == 0)
{
 debounce = 1;
 buttonPressed = 1;
}
else if (debounce == 1)
{
 debounce = 0;
 color++;
 if (color > 4)
 color = 0;

 i= 0;

 while (i < 13)
 {

 j = 0;
 while (j < 13)
 {

 if (color == 0)  
 SetPx(i,j,15,0,0,0);  
 if (color == 1)  
 SetPx(i,j,0,15,0,0);  
 if (color == 2)  
 SetPx(i,j,0,0,15,0);  
 if (color == 3)  
 SetPx(i,j,0,0,0,15);       
 if (color == 4)  
 SetPx(i,j,15,15,15,15);       

 j++;

 }

 i++;
 } 

}

}
else  // i.e., no buttons pressed-- in the RGB waves part of the program
{      // RGB waves part:

if ((PINB & 1U) == 0)
{
 debounce = 1;
 buttonPressed = 1;
}

if (rand() & 1)
{
if (rand() & 1)
 xCtr += 0.1;
else
 xCtr -= 0.1;

 if (xCtr > 11)
 xCtr = 11;

 if (xCtr < 2)
 xCtr = 2;
}

if (rand() & 1)
{
if (rand() & 1)
 yCtr += 0.1;
else
 yCtr -= 0.1;

 if (yCtr > 11)
 yCtr = 11;

 if (yCtr < 2)
 yCtr = 2;
}

w =0;

 if (stepoffset > 0)
 stepoffset--;
 else
 stepoffset = 47;

 i= 0;

 while (i < 13)
 {

 j = 0;

 while (j < 13)
 {

 k = 3*sqrt((i-xCtr)*(i-xCtr)+(j-yCtr)*(j-yCtr)) + stepoffset;
 if (k > 47)
 k -= 47;

 SetPxSeq(i,j,k,w);    

 j++;
 }

 i++;
 }

}
}
Advertisements
 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s