After some talk, we decided to present a LED cube controlled by a calculator on this year's Maker Faire. The idea is to send the LED data through the link port as serial data and decode it with a pair of shift registers. This enables a wide range of possibilities, such as a simple GUI for users to control the LED cube with the calculator, pre-programmed animations and potentially more. Any brainstorming on the subject is welcome.

The current plan is that Kerm will assemble (or already assembled?) a LED cube, and will post the hardware specifications here. If I can replicate it with the hardware I have on hand and feel like I am up to the challenge, I will then build a similar cube for testing, and use the Axe Parser to implement an interactive demonstration program for visitors to try out at our booth.

I already have an Arduino program to display animations in a relatively simple-to-input format, which I believe I can mostly port to Axe.
The animation format is as follows:
Every animation is in a separate header file. This is probably not strictly necessary, but I found it to be useful fro readability. The animation data files are #include'd in the main program. A sample animation file with two frames:

Code:
// the file contains a byte with the number of frames an array with the duration for every frame (in ms) and an array with the animation data itself
// the array lengths must match the specified size
// I chose to name the variables as size1/duration1/animation1, size2/duration2/animation2, ...

extern const byte size1 = 2;
// 2 frames

extern const int duration1 = {
200, // 0
100, // 1
}
// this array would show frame 0 for 200 ms and frame 1 for 100 ms

// the animation data itself is an array of 2D arrays
// every 2D array represents a frame, and they are shown in order
extern const byte animation1[][9][3] = {
  { // 0
  {1, 1, 1}, {0, 1, 0}, {0, 0, 0},
  {1, 1, 1}, {1, 1, 1}, {0, 1, 0},
  {1, 1, 1}, {0, 1, 0}, {0, 0, 0}
  },
  { // 1
  {0, 0, 0}, {0, 1, 0}, {0, 0, 0},
  {0, 1, 0}, {1, 1, 1}, {0, 1, 0},
  {0, 0, 0}, {0, 1, 0}, {0, 0, 0}
  },
}
// with this combination of whitespace, the frames form three squares
// the left square corresponds with the bottom layer, the middle one with the middle layer and the right one with the top layer
// a 0 means the LED is off, a 1 means the LED is on

// this animation would show a pyramid for 200 ms and a diamond for 100 ms before ending


Here we go for the actual code and animations:

LED_Cube.ino (main file)

Code:
// all animation files must be included
#include "A1_RotateY.h"
#include "A2_RotateX.h"
#include "A3_HideMiddleLayer.h"
#include "A4_Jump.h"
#include "A5_MakeLine.h"
#include "A6_DrawLayers.h"
#include "A7_MoveToMiddleLayer.h"

void setup() {
  // initialization "hidden" in function
  setupPins();
}

void loop() {
  // play all animations in order, loop when done
 
  // RotateY
  // rotates a plane along the Y axis
  dispAnimation(animation1, duration1, 10, size1);
 
  // show middle layer for some extra time for smooth transition between animations
  // frame is already present in animation, extracted individually to save memory
  dispFrame(animation1[0], 500);
 
  // RotateX
  // rotates a plane along the X axis
  dispAnimation(animation2, duration2, 10, size2);
 
  // HideMiddleLayer
  // transition animation - smoothly turns off LEDs in middle layer
  dispAnimation(animation3, duration3, 1, size3);
 
  // Jump
  // "jumps" single lit LED around
  dispAnimation(animation4, duration4, 5, size4);

  // MakeLine
  // transition animation - creates a line from single lit LED
  dispAnimation(animation5, duration5, 1, size5);
 
  // DrawLayers
  // gradually lights up various layers
  dispAnimation(animation6, duration6, 5, size6);

  // MoveToMiddleLayer
  // transition animation - moves layer from the bottom to the middle
  dispAnimation(animation7, duration7, 1, size7);
}

Cube_Driver.ino (the implementation of the "framework")

Code:
// Wiring:
// 3 LEDs linked at the anodes make up a column, 9 in total
// 9 LEDs linked at the cathodes make up a layer, 3 in total
// column anodes are connected to digital pins 2 - 10 and set HIGH to turn on the column
// layer cathodes are connected to GND through transistors as low side switches
// the transistor bases are connected to digital pins 11 - 13 and set to HIGH to turn on the layer


// single use initialization function
void setupPins() {
  for (int i = 2; i <= 13; i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW);
  }
}


// play an animation given an array of frames, durations, the number of times to play the animation and the number of frames
void dispAnimation(byte animationData[][9][3], int aDuration[], byte aCount[], byte aSize) {
  // for loop repeats animation for the specified number of times
  for (byte count = 0; count < aCount; count++) {
    // for loop shows every frame in the data array
    for (byte i = 0; i < aSize; i++) {
      dispFrame(animationData[i], aDuration[i]);
    }
  }
}


// display frame given array of LED states and duration in ms
// displaying a frame once takes 1 ms, duration directly translates to number of times frame is displayed
void dispFrame(byte frame[9][3], int count) {
  // map 0, 1 and 2 to the pins for the transistor bases
  // they were not connected in order
  const byte layPins[3] = {12, 13, 11};

  // for loop repeats frame for the specified time
  for (int i = 0; i < count; i++) {
    // LEDs are wired as a matrix -> not every LED can be accessed individually
    // show frame layer by layer to light up any combination of LEDs
    for (byte lay = 0; lay < 3; lay++) {
      // cache layer pin, probably redundant
      byte layPin = layPins[lay];
      // activate layer
      digitalWrite(layPin, HIGH);

      // extract LED states from frame data array, activate/deactivate columns accordingly
      digitalWrite( 4, frame[0 + lay][0]); // pin  4 = col 0
      digitalWrite( 3, frame[0 + lay][1]); // pin  3 = col 1
      digitalWrite( 2, frame[0 + lay][2]); // pin  2 = col 2
      digitalWrite( 9, frame[3 + lay][0]); // pin  9 = col 3
      digitalWrite(10, frame[3 + lay][1]); // pin 10 = col 4
      digitalWrite( 5, frame[3 + lay][2]); // pin  5 = col 5
      digitalWrite( 6, frame[6 + lay][0]); // pin  6 = col 6
      digitalWrite( 7, frame[6 + lay][1]); // pin  7 = col 7
      digitalWrite( 8, frame[6 + lay][2]); // pin  8 = col 8

      // 3 layers, 1 ms per frame = 1/3 m/s per frame and layer
      delayMicroseconds(333);
      // deactivate layer after done displaying
      digitalWrite(layPin, LOW);
    }
  }
}

A1_RotateY.h (animation 1)

Code:
// number of frames
extern const byte size1 = 8;

// duration for every frame
extern const int duration1[] = {
  100, // 0
  100, // 1
  100, // 2
  100, // 3
  100, // 4
  100, // 5
  100, // 6
  100, // 7
};

// array of frames
extern const byte animation1[][9][3] = {
  { // 0
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
  { // 1
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1},
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1},
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1}
  },
  { // 2
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0},
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0},
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0}
  },
  { // 3
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0},
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0},
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0}
  },
  { // 4
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
  { // 5
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1},
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1},
  {1, 0, 0},  {0, 1, 0},  {0, 0, 1}
  },
  { // 6
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0},
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0},
  {0, 1, 0},  {0, 1, 0},  {0, 1, 0}
  },
  { // 7
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0},
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0},
  {0, 0, 1},  {0, 1, 0},  {1, 0, 0}
  },
};

A2_RotateX.h (animation 2)

Code:
// number of frames
extern const byte size2 = 8;

// duration for every frame
extern const int duration2[] = {
  100, // 0
  100, // 1
  100, // 2
  100, // 3
  100, // 4
  100, // 5
  100, // 6
  100, // 7
};

// array of frames
extern const byte animation2[][9][3] = {
  { // 0
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
  { // 1
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1}
  },
  { // 2
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {1, 1, 1},  {1, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 3
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 4
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
  { // 5
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1}
  },
  { // 6
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {1, 1, 1},  {1, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 7
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
};

A3_MiddleLayer.h (animation 3)

Code:
// number of frames
extern const byte size3 = 6;

// duration for every frame
extern const int duration3[] = {
  500, // 0
  100, // 1
  100, // 2
  100, // 3
  100, // 4
  500, // 5
};

// array of frames
extern const byte animation3[][9][3] = {
  { // 0
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
  { // 1
  {0, 0, 0},  {0, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 0},  {0, 0, 0}
  },
  { // 2
  {0, 0, 0},  {0, 0, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 0, 0},  {0, 0, 0}
  },
  { // 3
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 4
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 1, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 5
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
};

A4_Jump.h (animation 4)

Code:
// number of frames
extern const byte size4 = 15;

// duration for every frame
extern const int duration4[] = {
  200, // 0
  100, // 1
  300, // 2
  100, // 3
  300, // 4
  100, // 5
  300, // 6
  100, // 7
  300, // 8
  100, // 9
  300, // 10
  100, // 11
  300, // 12
  100, // 13
  100, // 14
};

// array of frames
extern const byte animation4[][9][3] = {
  { // 0
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 1
  {0, 1, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 2
  {0, 0, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 3
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 1, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 4
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0}
  },
  { // 5
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 6
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 7
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 1, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 8
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 1}
  },
  { // 9
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 1, 0},  {0, 0, 0}
  },
  { // 10
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 11
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 1, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 12
  {0, 0, 0},  {0, 0, 0},  {0, 0, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 13
  {0, 0, 0},  {0, 1, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 14
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
};

A5_MakeLine.h (animation 5)

Code:
// number of frames
extern const byte size5 = 4;

// duration for every frame
extern const int duration5[] = {
  500, // 0
  100, // 1
  100, // 2
  500, // 3
};

// array of frames
extern const byte animation5[][9][3] = {
  { // 0
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 1
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 2
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 3
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
};

A6_DrawLayers.h (animation 6)

Code:
// number of frames
extern const byte size6 = 16;

// duration for every frame
extern const int duration6[] = {
  100, // 0
  500, // 1
  100, // 2
  500, // 3
  100, // 4
  500, // 5
  100, // 6
  500, // 7
  100, // 8
  500, // 9
  100, // 10
  500, // 11
  100, // 12
  500, // 13
  100, // 14
  500, // 15
};

// array of frames
extern const byte animation6[][9][3] = {
  { // 0
  {1, 0, 0},  {1, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {1, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {1, 0, 0},  {0, 0, 0}
  },
  { // 1
  {1, 0, 0},  {1, 0, 0},  {1, 0, 0},
  {1, 0, 0},  {1, 0, 0},  {1, 0, 0},
  {1, 0, 0},  {1, 0, 0},  {1, 0, 0}
  },
  { // 2
  {0, 0, 0},  {1, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {1, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {1, 0, 0},  {1, 0, 0}
  },
  { // 3
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 0, 0}
  },
  { // 4
  {0, 0, 0},  {0, 0, 0},  {1, 1, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 0},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 0}
  },
  { // 5
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {1, 1, 1}
  },
  { // 6
  {0, 0, 0},  {0, 0, 0},  {0, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 1, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 1, 1}
  },
  { // 7
  {0, 0, 0},  {0, 0, 0},  {0, 0, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 1},
  {0, 0, 0},  {0, 0, 0},  {0, 0, 1}
  },
  { // 8
  {0, 0, 0},  {0, 0, 1},  {0, 0, 1},
  {0, 0, 0},  {0, 0, 1},  {0, 0, 1},
  {0, 0, 0},  {0, 0, 1},  {0, 0, 1}
  },
  { // 9
  {0, 0, 1},  {0, 0, 1},  {0, 0, 1},
  {0, 0, 1},  {0, 0, 1},  {0, 0, 1},
  {0, 0, 1},  {0, 0, 1},  {0, 0, 1}
  },
  { // 10
  {0, 0, 1},  {0, 0, 1},  {0, 0, 0},
  {0, 0, 1},  {0, 0, 1},  {0, 0, 0},
  {0, 0, 1},  {0, 0, 1},  {0, 0, 0}
  },
  { // 11
  {0, 0, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 0, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 12
  {0, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {0, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 13
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 14
  {1, 1, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 15
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
};

A7_MoveToMiddleLayer.h (animation 7)

Code:
// number of frames
extern const byte size7 = 8;

// duration for every frame
extern const int duration7[] = {
  100, // 0
  100, // 1
  500, // 2
  100, // 3
  100, // 4
  100, // 5
  100, // 6
  500, // 7
};

// array of frames
extern const byte animation7[][9][3] = {
  { // 0
  {1, 1, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 0},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 1
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 0, 0},  {0, 0, 0},  {0, 0, 0}
  },
  { // 2
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 3
  {0, 1, 1},  {1, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 4
  {0, 0, 1},  {1, 1, 0},  {0, 0, 0},
  {0, 1, 1},  {1, 0, 0},  {0, 0, 0},
  {1, 1, 1},  {0, 0, 0},  {0, 0, 0}
  },
  { // 5
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 1},  {1, 1, 0},  {0, 0, 0},
  {0, 1, 1},  {1, 0, 0},  {0, 0, 0}
  },
  { // 6
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 1},  {1, 1, 0},  {0, 0, 0}
  },
  { // 7
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0},
  {0, 0, 0},  {1, 1, 1},  {0, 0, 0}
  },
};
Thanks for posting this topic, Nik. Although it's untested, below find the schematic I'm envisioning, and here are a few details about it:
  • It uses two chained 74HC595 SIPO shift registers
  • Because these shift registers are kind enough to include dual buffering, it uses a bit reaching the "carry" of the second register in order to push the bits set from the internal buffers to the output buffers.
  • It tries to be clever with chaining logic to clear the shift registers, but I realized this won't work as drawn, because the clear line will never switch states after it's set the first time.


I think I can do this.

From a "regular" LED cube, I already have the cube itself (including LEDs and current limiting resistors), as well as the NPN transistors. I am also happy to say that I managed to source the shift registers (74HC595), PNP transistors (C556B) and current limiting resistors for the transistors (1.43 kOhm, though).
I also spent some time playing with the transistors, because I am comfortable with MOSFETs but not so with BJTs. Tomorrow I will familiarize myself with the shift registers, and I expect to come up with a finalized schematic and perhaps even solder it.

A thing to note is that I am leaving next Tuesday for two to three weeks for vacations in Norway, also passing through Denmark and Sweden. I will definitely take my calculator with me, and while I have not decided whether I will also bring the LED cube itself with me, there is a chance that this project will be multinational. Razz
That would be fun! Also, on my ongoing quest to make a relatively simple, reasonable circuit for this, I have pressed a 555 into service to provide a monostable multivibrator for our reset for us, on the necessary time delay to allow our output to propagate. When we load the LED cube's bits into the shift registers, we make sure that we clock a series "110" at the beginning of any valid LED cube pattern. It will momentarily cause a misaligned output in the cube, but it will be so short as to be invisible and irrelevant to the human eye:
  1. The first 1 bit coming out of QH' on the second shift register will assert RCLK, making (on the second shift register) QH=1, QG=1, QF=0 (ignore), QE=1 (ignore).
  2. The QG=1 output will reset the 555-based multivibrator, which will already have stopped asserting SRCLR-bar (ie, its output will be electrical high).
  3. Shifting once: now QH' will still be asserting RCLK
  4. So QH=1, QG=0, QF=1 (ignore), QE=x (ignore). The output of the 555 in monostable mode will be a fixed-length pulse after a very short delay, which (inverted by the NPN transistor right after it) will assert SRCLR-bar, clearing the internal register (but not clearing the output!).
  5. Another set of bits can now be clocked in.

Please let me know if this either is (a) unclear or (b) clearly wrong. And Nik, I hope you have a 555 handy! Smile

I think I found a simpler solution to this problem.

Let's begin with the SRCLR line: I believe it can be left tied high. The data in the storage register does not need to be cleared if you can just overwrite it. Next, there is the problem of moving the data from the shift register to the storage register for displaying it. But I believe this can be solved with as little as four passive components (and nothing else).

Consider the following schematic (apologies for my drawing and handwriting skills, or the lack thereof):


The SRCLK line is connected to the RCLK line through a diode. Additionally, there is a capacitor C1 and two resistors R1 and R2 connected to RCLK. Between data transmissions, the SRCLK line would be held high. This ensures that C1 is charged and RCLK is high too. During a transmission, SRCLK carries a square wave signal with a duty cycle reasonably close to 50%. This period lasts only 10 us, as discussed via IRC. If C1 and R2 are chosen appropriately, this means that RCLK still retains a logic high state meanwhile. The trick is after the transmission: If SRCLK is held low for 0.1 ms or so, C1 dischrages through R2 and RCLK drops to a logic low. Next, SRCLK can be driven high and held there until the next transmission starts. C1 charges (R1 is a current limiting resistor here), RCLK goes high, and on the rising edge, the shift register contents are transferred into the storage resistor, so that the frame updates.

Assuming a diode forward voltage drop of 0.7 V, and thus a maximum voltage of 4.3 V on RCLK, I calculated that the values of C1 and R2 should be chosen so that R2 * C1 > 9.495e-6. With this minimal value, RCLK will reach 1.5 V, which is the minimum logic high voltage as per TI's datasheet, after 10 us of continuous discharging. 20e-6 sounds like a decent value to me, and indeed I have found a 8.24 kOhm resistor and a 2.1 nF capacitor, which combine to R2 * C1 = 17.2e-6.

EDIT: I just noticed that R1 also matters during the discharging process. Assuming 1.43 kOhm fro R1 (see below), (R1 + R2) * C1 = 20.2e-6, which is also a reasonable value.

Finally, R1 should be chosen small enough that C1 can be charged between data transmissions, but large enough that charging it does not draw too much current. The recommended maximum value for the current as per TI's datasheet is 6 mA, and I have a whole lot of 1.43 kOhm resistors laying around, so that's what I will use. The maximum current flowing through the diode should be about 3.5 mA assuming a voltage of 4.3 V and accounting for both R1 (1.43 kOhm) and R2 (8.24 kOhm).
After some testing and research, I found that this circuit does not quite work, because I mixed things up. I've fixed it and redrawn the schematic in Eagle (Sorry for the different labeling, my libraries must be different from yours).



It works essentially the same way, except that I replaced the diode with a resistor, and removed what was R1 in the previous schematic. The issue with the previous circuit was that RCLK/RCK would go high immediately with SRCLK/SCK. The resistor fixes that. The old R1 is not needed too, as the current is already limited by the new R1.

I also changed the values of the resistors and the capacitor to more standard ones. While I did recreate this circuit on a breadboard and somewhat checked it with an oscilloscope, my setup and equipment was not good at all, so I ran a simulation at falstad.com instead. Apparently, when the values are chosen this way, the capacitor charges from 0 V to a maximum of 4.4 V within 10 us and discharges from 4.4 V to 1.5 V within 20 us. If discharged continuously from 4.4 V for 10 us, its voltage falls just below 2.6 V. I consider this setup suitable to perform as described in my previous post.

And there is one more thing I changed. Instead of using the outputs QF- QH on the second shift register, I figured that some time could be saved during frame updates by using QB - QD instead. That way, only 12 bits need to be shifted into the register (as opposed to the full 16 bits that would have to be transmitted in order to use the output QH).

If I missed something obvious, please scream at me. Very Happy
Your logic seems reasonable on this, so I've gone ahead and prototyped it. Unfortunately, I can't find my full collection of capacitors and resistors, so I approximated the resistor as 4.7K+3.3K = 8.0K ohms, and the capacitor as two 1nF capacitors in parallel = 2.0nF. I have not yet tested it.

*bump* I sat down for an hour or so with Geekboy1011, and we pair-programmed a core interrupt to write layers out to this cube:

https://github.com/KermMartian/CalcLEDCube/

Testing is pending me having both the breadboard pictured and a Doors CS-enabled TI-83 Plus in front of me.
Uhm checking the data sheet, the turn on voltage for the shift register is ~3.0V that means that the rise time on the supplied circuit is about 4us instead of the 10us you estimated, Changing the 2.2nF capacitor to a 10nf(.01uF) capacitor will yield about a 8us rise time to the ~3.0V turn on voltage. Which will ensure that with the supplied interrupt there is enough headroom to make sure you don't accidentally trip the output stage to soon.
In doing some testing, I ran into:
1) A self-reminder about how PNP transistors work, namely that they conduct E->C when a current can flow from B to ground, not from +V to B. Whoops, the equivalent of a missing semicolon in software. That just means we'll need to invert our column bits, which is no big deal.
2) Even so, and with tying RCLK high, no success yet. I'll run our code with PTI and make sure the interrupt is firing properly.
geekboy1011 wrote:
Uhm checking the data sheet, the turn on voltage for the shift register is ~3.0V that means that the rise time on the supplied circuit is about 4us instead of the 10us you estimated, Changing the 2.2nF capacitor to a 10nf(.01uF) capacitor will yield about a 8us rise time to the ~3.0V turn on voltage. Which will ensure that with the supplied interrupt there is enough headroom to make sure you don't accidentally trip the output stage to soon.


It is not the rise time that matters, but rather the fall time. Between and during data shifting, the line is supposed to be held high, and after shifting it should be pulled low and back high to create a rising edge. The fall time should be chosen long enough that the line does not reach logic low level from the voltage changes that happen during data shifting, but short enough to allow comfortably pulling the line low when needed. The rise time happens after data shifting and does not matter much - as long as the line has enough time to go high between two frames.
(Punting on the above for geekboy to look at. Smile )

I fixed some very small bugs in the implementation that were preventing any bits from going out properly; unfortunately, the display remains stubbornly blank. Inquiring minds will inspect my circuit.



Edit: Turned out the issue was that RCLK needs an edge; I was trying to hold it high for debugging, but that's not good enough. geekboy is double-checking those component values he mentioned (I'm afraid that Nik's values experimentally don't appear to do the trick, unless the interrupt isn't sending consistent values).
KermM Made a few more small changes the clock/data output now matches what the data sheet expects to a T.

Top is SER/Data
Bottom is Clock/SRCLK
To expand on geekboy's post a bit, here's what we did on HCWP last night:
  1. We discovered that Kerm, in the years since he last wrote link port software, forgot that asserting the link port lines produces electrical ground as logical high. The software was updated as of 4ba7e85f.
  2. Thanks to use of geekboy's We discovered that since the calculator's link lines only get up to about 3.3V (not 5V), the RCLK circuit wasn't behaving as expected (ie, it couldn't charge up beyond a peak of about 3.15V, which makes sense). We tried using pullup resistors on the clock and data lines from the calculator, but this was not a complete success. I propose using a pair of NPN transistors to drive the circuit at VCC from the calculator's link lines.
  3. We continued debugging the RCLK circuit timing, but with the voltage problems in the previous point, our results are inconclusive.
I totally support the transistor solution. Simple, cheap, easy to explain, no mess.

I've not had much time to continue progress on the UI for the LED cube control, but I will have time on Saturday and Sunday - and I might even finish the program then.
KermMartian wrote:
To expand on geekboy's post a bit, here's what we did on HCWP last night:
  1. We discovered that Kerm, in the years since he last wrote link port software, forgot that asserting the link port lines produces electrical ground as logical high. The software was updated as of 4ba7e85f.
  2. Thanks to use of geekboy's We discovered that since the calculator's link lines only get up to about 3.3V (not 5V), the RCLK circuit wasn't behaving as expected (ie, it couldn't charge up beyond a peak of about 3.15V, which makes sense). We tried using pullup resistors on the clock and data lines from the calculator, but this was not a complete success. I propose using a pair of NPN transistors to drive the circuit at VCC from the calculator's link lines.
  3. We continued debugging the RCLK circuit timing, but with the voltage problems in the previous point, our results are inconclusive.

Actually its not even getting up to 3v3

Its only getting up to about 2.9 which means that it is never actually asserting SER or SRCLK either, Hopefully dropping VCC down to 3.3v OR Using a level shifter will solve the problem nicely.
After some talk, me and geekboy settled on providing the LED data to the interrupt via a pointer stored in {tempSwapArea + 0x04}. That would point to the first (0'th) of six bytes the interrupt expects.
*bump* geekboy wants to make this project cleaner for the next Maker Faire, specifically by making a board that both supports the LED cube and contains the circuits on the breadboard. Through some discussion, we were reminded that a core problem with the timer/trigger circuit was that the calculator I/O lines put out 3.3V, not 5V, so we ended up running the entire circuit off 3.3V (which we should verify with a quick test, to make sure the LEDs are bright enough with the resistors that the Radio Shack design used). We also verified that Nik's circuit plus my transistor drivers is essentially what we used, but we need to check the actual passive values we ended up using.
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 1 of 1
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement