Download the code directly here: LED_Matrix_TTT4.pde
Some general notes:
1. The AI is based on game theory taken from a book I read about 20 years ago. Unfortunately I don’t remember the name of it. It uses a Minimax algorithm as well as Alpha-beta pruning to decrease the number of nodes that need to be evaluated. Wikipedia offers a concise description of these with many external links. I spent about a month learning the algorithms and coding them, so I’m sure they can be greatly enhanced, especially since more than 20 years has past. However, I am proud to say that pitting this program up against another 3D TTT I found online, that mine trounced the other.
2. I wanted the LED matrix cube to be constantly displaying the player LED spot selection, the current cube moves, or the computer thinking (LEDs going on and off). In order to do this, even while the Arduino is off doing other things, I needed to use interrupts. Using the MsTimer2 library I was able to set up a routine, disp_LED_cube, that was called every 200 ms with 1 of 4 states:
3. The current program uses a keypad to make the moves. The keys 1-4 are for selection of the player’s move. It first uses a ‘wall’ to select the z-direction, with the keys moving the wall. After the proper wall is selected, the ’5′ key will then lock-in that wall, and display a column that represents the y-direction. Again, the 1-4 keys allow for the selection of the wished-upon column. Player then pushes ’5′ key to lock-in column. Finally, user selects for the specific LED in column they wish to use as their move. Pressing the ’5′ key will begin the Arduino deciding its move. I’m currently working on code to use a thumbstick rather than the keypad.
4. The code is GPL. Please use it as you like and definitely improve upon it. I would, of course, really like you to credit me for the work I’ve put into it should you use it.
Here is the code:
/*
LED_Matrix_TTT4.C - An AI version of Tic-Tac-Toe, played on a 4x4x4 LED matrix
powered by an Arduino
Copyright (C) 2011 Jeffrey R. Gilmour, Ph.D.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Sprite.h>
#include <Matrix.h>
#include <MsTimer2.h> // Uses pins 3 & 11, so DON'T USE those
const int loadPin = 4;
const int clockPin = 5;
const int dataPin = 6;
Matrix myMatrix = Matrix(dataPin, clockPin, loadPin);
#define FALSE 0
#define TRUE !FALSE
boolean OLedsOn = TRUE;
char cube0[4][4][4], cube1[4][4][4], cube2[4][4][4], cube3[4][4][4], human_symbol='X', comp_symbol='O';
const int period = 200;
const int numRows = 2;
const int rowPins[numRows] = { 7, 8 }; // Keypad pins 1 & 2, attached to Arduino pins 7 & 8
const int numCols = 3;
const int colPins[numCols] = { 9, 10, 12 }; // Keypad pins 5, 6 & 7
const int debounceTime = 200;
const char keymap[numRows][numCols] = { // USE PINS 1, 2, 5, 6, 7 on keypad
{ '1', '2', '3' },
{ '4', '5', '6' }
};
char key = 0;
int state = 0, state1x = 5, state1y = 5, state1z = 5;
int bestmovex, bestmovey, bestmovez;
char getKey() {
key = 0;
for (int column = 0; column < numCols; column++) {
digitalWrite(colPins[column], LOW);
for (int row = 0; row < numRows; row++) {
if(digitalRead(rowPins[row]) == LOW) {
delay(debounceTime);
while(digitalRead(rowPins[row]) == LOW) ;
key = keymap[row][column];
}
}
digitalWrite(colPins[column], HIGH);
}
return key;
}
char check(char board[4][4]) {
int i;
for (i=0;i<4;i++) /* check rows */
if (board[i][0]==board[i][1] &&
board[i][0]==board[i][2] &&
board[i][0]==board[i][3]) if (board[i][0]!=' ')
return board[i][1];
for (i=1;i<=4;i++) /* check columns */
if (board[0][i]==board[1][i] &&
board[0][i]==board[2][i] &&
board[0][i]==board[3][i]) if (board[0][i]!=' ')
return board[1][i];
/* test diagonals */
if (board[0][0]==board[1][1] && board[1][1]==board[2][2] &&
board[2][2]==board[3][3] && board[0][0]!=' ')
return board[1][1];
if (board[0][3]==board[1][2] && board[1][2]==board[2][1] &&
board[2][1]==board[3][0] && board[0][3]!=' ')
return board[1][2];
return ' ';
}
void disp_LED_cube(void) {
if (state == 0) { // Default display of all moves
for (int z=0;z<4;z++)
for (int y=0;y<4;y++)
for (int x=0;x<4;x++) {
if (OLedsOn) {
if (cube0[x][y][z] == 'O') {
if (z == 0) {
myMatrix.write(y, x, LOW);
} else if (z == 1) {
myMatrix.write(y, x+4, LOW);
} else if (z == 2) {
myMatrix.write(y+4, x+4, LOW);
} else if (z == 3) {
myMatrix.write(y+4, x, LOW);
}
}
} else {
if (cube0[x][y][z] != ' ') {
if (z == 0) {
myMatrix.write(y, x, HIGH);
} else if (z == 1) {
myMatrix.write(y, x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, x, HIGH);
}
}
}
}
OLedsOn = !OLedsOn;
}
if (state == 1) { // setting up player's move
myMatrix.clear();
if (state1z < 5) {
if (state1z == 0) {
myMatrix.write(state1y, state1x, HIGH);
} else if (state1z == 1) {
myMatrix.write(state1y, state1x+4, HIGH);
} else if (state1z == 2) {
myMatrix.write(state1y+4, state1x+4, HIGH);
} else if (state1z == 3) {
myMatrix.write(state1y+4, state1x, HIGH);
}
} else
if (state1y < 5) {
for (int z=0;z<4;z++)
if (z == 0) {
myMatrix.write(state1y, state1x, HIGH);
} else if (z == 1) {
myMatrix.write(state1y, state1x+4, HIGH);
} else if (z == 2) {
myMatrix.write(state1y+4, state1x+4, HIGH);
} else if (z == 3) {
myMatrix.write(state1y+4, state1x, HIGH);
}
} else
if (state1x < 5) {
for (int z=0;z<4;z++)
for (int y=0;y<4;y++) {
if (z == 0) {
myMatrix.write(y, state1x, HIGH);
} else if (z == 1) {
myMatrix.write(y, state1x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, state1x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, state1x, HIGH);
}
}
}
}
if (state == 3) { // blinking last computer move
if (OLedsOn) {
if (bestmovez == 0) {
myMatrix.write(bestmovey, bestmovex, LOW);
} else if (bestmovez == 1) {
myMatrix.write(bestmovey, bestmovex+4, LOW);
} else if (bestmovez == 2) {
myMatrix.write(bestmovey+4, bestmovex+4, LOW);
} else if (bestmovez == 3) {
myMatrix.write(bestmovey+4, bestmovex, LOW);
}
} else {
if (bestmovez == 0) {
myMatrix.write(bestmovey, bestmovex, HIGH);
} else if (bestmovez == 1) {
myMatrix.write(bestmovey, bestmovex+4, HIGH);
} else if (bestmovez == 2) {
myMatrix.write(bestmovey+4, bestmovex+4, HIGH);
} else if (bestmovez == 3) {
myMatrix.write(bestmovey+4, bestmovex, HIGH);
}
}
OLedsOn = !OLedsOn;
}
if (state == 4) { // computer "thinking"
myMatrix.clear();
for (int z=0;z<4;z++)
for (int y=0;y<4;y++)
for (int x=0;x<4;x++) {
if (OLedsOn) {
if (cube3[x][y][z] == 'O') {
if (z == 0) {
myMatrix.write(y, x, LOW);
} else if (z == 1) {
myMatrix.write(y, x+4, LOW);
} else if (z == 2) {
myMatrix.write(y+4, x+4, LOW);
} else if (z == 3) {
myMatrix.write(y+4, x, LOW);
}
}
} else {
if (cube3[x][y][z] != ' ') {
if (z == 0) {
myMatrix.write(y, x, HIGH);
} else if (z == 1) {
myMatrix.write(y, x+4, HIGH);
} else if (z == 2) {
myMatrix.write(y+4, x+4, HIGH);
} else if (z == 3) {
myMatrix.write(y+4, x, HIGH);
}
}
}
}
OLedsOn = !OLedsOn;
}
}
char check_cube(char cube[4][4][4]) {
int x, y, z, n=0;
char done, board[4][4];
for (z=0;z<4;z++) { /* boards in Z direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][y][z];
done=check(board);
if (done!=' ') return done;
}
for (z=0;z<4;z++) { /* boards in Y direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][z][y];
done=check(board);
if (done!=' ') return done;
}
for (z=0;z<4;z++) { /* boards in X direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[z][y][x];
done=check(board);
if (done!=' ') return done;
}
/* 3D diagonal 1 */
if (cube[0][0][0]==cube[1][1][1] && cube[0][0][0]==cube[2][2][2]
&& cube[0][0][0]==cube[3][3][3] && cube[0][0][0]!=' ')
return cube[0][0][0];
/* 3D diagonal 2 */
if (cube[0][0][3]==cube[1][1][2] && cube[0][0][3]==cube[2][2][1]
&& cube[0][0][3]==cube[3][3][0] && cube[0][0][3]!=' ')
return cube[0][0][3];
/* 3D diagonal 3 */
if (cube[3][0][0]==cube[2][1][1] && cube[3][0][0]==cube[1][2][2]
&& cube[3][0][0]==cube[0][3][3] && cube[3][0][0]!=' ')
return cube[3][0][0];
/* 3D diagonal 4 */
if (cube[0][3][0]==cube[1][2][1] && cube[0][3][0]==cube[2][1][2]
&& cube[0][3][0]==cube[3][0][3] && cube[0][3][0]!=' ')
return cube[0][3][0];
for (z=0;z<4;z++) /* if no spaces are empty, game is a draw */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
if (cube[x][y][z]==' ') n++;
if (n==0) {
// Game is a draw - how to communicate to player?
loop();
}
return done;
}
void copy_cube(char b2[4][4][4], char b1[4][4][4]) {
int x,y,z;
for (z=0;z<4;z++)
for (y=0;y<4;y++)
for (x=0;x<4;x++) b2[x][y][z]=b1[x][y][z];
}
void get_computer_move(void) {
long int s0=-100000, s1, s2;
int test=0, x1, y1, z1, x2, y2, z2, x3, y3, z3, NoGood, NoGood2;
int score;
char done;
state = 4; // computer "thinking" LED display
for (z1=0;z1<4;z1++)
for (y1=0;y1<4;y1++)
for (x1=0;x1<4;x1++) {
s1=100000;
NoGood=FALSE;
if (cube0[x1][y1][z1]==' ') {
copy_cube(cube1,cube0);
cube1[x1][y1][z1]=comp_symbol;
done = check_cube(cube1); // Computer has WON!
if (done==comp_symbol) {
bestmovex = x1;
bestmovey = y1;
bestmovez = z1;
state = 3; // blinking last move of computer
while (getKey() == 0) ;
state = 0;
copy_cube(cube0,cube1);
while (getKey() == 0) ;
loop();
}
for (z2=0;z2<4 && !NoGood;z2++)
for (y2=0;y2<4 && !NoGood;y2++)
for (x2=0;x2<4 && !NoGood;x2++) {
s2=-100000;
NoGood2=FALSE;
if (cube1[x2][y2][z2]==' ') {
copy_cube(cube2,cube1);
cube2[x2][y2][z2]=human_symbol;
for (z3=0;z3<4 && !NoGood2;z3++)
for (y3=0;y3<4 && !NoGood2;y3++)
for (x3=0;x3<4 && !NoGood2;x3++) {
if (cube2[x3][y3][z3]==' '){
copy_cube(cube3,cube2);
cube3[x3][y3][z3]=comp_symbol;
score=score_cube(cube3);
if (s2<score) {
s2=score;
if (s2>s1) NoGood2=TRUE;
}
}
}
if (s1>s2) {
s1=s2;
if (s1<s0) NoGood=TRUE;
}
}
}
if (s0<s1) {
s0=s1;
bestmovex=x1;
bestmovey=y1;
bestmovez=z1;
}
}
}
cube0[bestmovex][bestmovey][bestmovez]=comp_symbol;
myMatrix.clear();
state = 3; // blinking last move of computer
while (getKey() == 0) ; // wait until player has pressed a button, then display regular cube
state = 0;
}
int score_cube(char cube[4][4][4]) {
int scores=0, x, y, z, tally_comp=0, tally_human=0;
int comp[5]={0,0,0,0,0}, human[5]={0,0,0,0,0};
char board[4][4];
for (z=0;z<4;z++) { /* boards in Z direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][y][z];
scores+=score(board);
}
for (z=0;z<4;z++) { /* boards in Y direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[x][z][y];
scores+=score(board);
}
for (z=0;z<4;z++) { /* boards in X direction */
for (y=0;y<4;y++)
for (x=0;x<4;x++)
board[x][y]=cube[z][y][x];
scores+=score(board);
}
for (z=0;z<4;z++) { /* 3D diagonal 1 */
tally_comp+=(cube[z][z][z]==comp_symbol);
tally_human+=(cube[z][z][z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 2 */
tally_comp+=(cube[3-z][z][z]==comp_symbol);
tally_human+=(cube[3-z][z][z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 3 */
tally_comp+=(cube[z][3-z][z]==comp_symbol);
tally_human+=(cube[z][3-z][z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (z=0;z<4;z++) { /* 3D diagonal 4 */
tally_comp+=(cube[z][z][3-z]==comp_symbol);
tally_human+=(cube[z][z][3-z]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
scores+=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
return scores;
}
int score(char board[4][4]) {
int x, y, tally_comp=0, tally_human=0, comp[5]={0,0,0,0,0}, human[5]={0,0,0,0,0};
for (y=0;y<4;y++) { /* Rows */
for (x=0;x<4;x++) {
tally_comp+=(board[x][y]==comp_symbol);
tally_human+=(board[x][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
}
for (x=0;x<4;x++) { /* Columns */
for (y=0;y<4;y++) {
tally_comp+=(board[x][y]==comp_symbol);
tally_human+=(board[x][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
}
for (y=0;y<4;y++) { /* Down diagonal */
tally_comp+=(board[y][y]==comp_symbol);
tally_human+=(board[y][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
tally_comp=0;
tally_human=0;
for (y=0;y<4;y++) { /* Up diagonal */
tally_comp+=(board[3-y][y]==comp_symbol);
tally_human+=(board[3-y][y]==human_symbol);
}
if (tally_comp>0 && tally_human>0) {
tally_comp=0;
tally_human=0;
}
comp[tally_comp]++;
human[tally_human]++;
y=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
/* Needs to be the same evaluation function as in 'score_cube' function */
return y;
}
void init_cube (void) {
int x,y,z;
for(z=0;z<4;z++)
for(y=0;y<4;y++)
for(x=0;x<4;x++) cube0[x][y][z]=' ';
}
void get_player_move(void) {
int x,y,z;
while (getKey() == 0) ;
state = 1;
state1x = 5;
state1y = 5;
state1z = 5;
do {
while (getKey() == 0) ;
if (key < '5')
state1x = (key - '0') - 1;
} while (key != '5' || state1x > 3 );
x = state1x;
do {
while (getKey() == 0) ;
if (key < '5')
state1y = (key - '0') - 1;
} while (key != '5' || state1y > 3 );
y = state1y;
do {
while (getKey() == 0) ;
if (key < '5')
state1z = (key - '0') - 1;
} while (key != '5' || state1z > 3 );
z = state1z;
if (cube0[x][y][z]!=' ') { // Invalid move - redo entry
get_player_move();
}
cube0[x][y][z]=human_symbol;
state = 0;
}
void setup() {
for (int row = 0; row < numRows; row++) {
pinMode(rowPins[row], INPUT);
digitalWrite(rowPins[row], HIGH);
}
for (int column = 0; column < numCols; column++) {
pinMode(colPins[column], OUTPUT);
digitalWrite(colPins[column], HIGH);
}
}
void loop() {
char done, Buf[12];
int x,y,z;
state = 1; // Pre-game cube LED flashing
done = ' ';
myMatrix.clear();
init_cube();
MsTimer2::set(period, disp_LED_cube);
MsTimer2::start();
while (getKey() == 0) ; // Press 1 to go first
state = 0; // Normal display of cube LED
if (key != '1') {
human_symbol='O';
comp_symbol='X';
get_computer_move();
}
do {
state = 0;
get_player_move();
done=check_cube(cube0);
if (done != ' ') break;
get_computer_move();
done=check_cube(cube0);
} while (done==' ');
if (done==human_symbol) ; // Player has WON!
else ; // Compute has WON!
}
As always, if you have any questions, need further assistance or require additional pictures or diagrams, please let me know in the comments and I’ll do my best to address them.
Up Next: Figuring Out How To Select Player Moves (determining pin combinations for particular keys on an unknown keypad, and hard coding)
]]>The 4x4x4 LED matrix is controlled by a Maxim MAX7219 display driver IC. Using the MAX7219 reduces the number of Arduino pins needed to control and power the matrix from 16 down to 5. The 3D TicTacToe sketch uses the Arduino Matrix library by Nicholas Zimbetti and simple coding from his hello_matrix sketch. This library should already be included with all up-to-date arduino installations. The design of the protoboard is based on Michael Margolis’s Arduino Cookbook project 7.13 “Controlling an Array of LEDs by Using MAX72xx Shift Registers” and the Maxim MAX7219 datasheet.
Shopping List:
Connections:
To power the matrix connect the Arduino’s 5V pin directly to the MAX7219 V+ (pin 19) and through a ~30 kΩ resistor to ISET (pin 18). I used a 33 kΩ resistor on mine. Connect the Arduino’s GND to the MAX7219 GND pins (4 and 9). Insert 0.1 and 10 μF capacitors between the 5V and GND to prevent noise spikes.
To control the matrix connect Arduino pins 4, 5, and 6 to the MAX7219 pins 12 (LOAD), 13 (CLK), and 1 (DIN), respectively.
The anode wires of the LED matrix will be connected to the MAX7219 SEG pins A-G, plus SEG DP, while the cathode wires will be connected to DIG pins 0-7. Use 24 AWG wires that are long enough to reach where they need to go on the LED matrix. Mine were a bit long at ~20 cm, but the extra should fit in the box you use or can be trimmed later. I suggest using one color for the anode and a different color for cathode. Solder 8 wires of one color to SEG pins A-DP and the 8 other wires to DIG pins 0-7.
If you want a nice looking 3D matrix display you’ll want a box to contain the Arduino, protoboard and wires, and a top for the LED matrix to sit on. I used the same printout as used to drill the board holes to determine where to drill small holes in the cigar box top for the wires. You’ll only need the seven side holes, but make the corner hole about 1.5x as big as the others. Once the wires are soldered to the protoboard, thread them through the appropriate holes of the cigar box top. Connect the wires to the appropriate lines on the LED matrix. You’ll probably want to attach the wires temporarily to the LED matrix for testing before permanently soldering them.
Below are shots of the protoboard. I know they’re difficult to make out with the gaggle of wires everywhere, and so I’ve taken shots from various directions.
Shot A:
shows 33 kΩ resistor from pin 18 to 5V, and pin 19 straight to 5V.
Shot B:
difficult to see pins 4 (buried) and 9 to GND
Shot C:
MAX7219 pins 12 (LOAD) and 13 (CLK)
If you have any questions, need further assistance or require additional pictures or diagrams, please let me know in the comments and I’ll do my best to address them.
Up Next: Programming the Arduino To Play 3D TicTacToe
]]>
The continuation of my 3D TicTacToe LED Matrix project. See Introduction.
The matrix is based on an 8×8 LED matrix design, where it’s been divided into fourths and wired to work on four levels. Here’s a diagram of an 8×8 matrix:
The structure of the 3D matrix is entirely composed of the anode LED leads soldered together, the cathode LED leads soldered together and a few extra wires to make connections. Here’s a picture of the semi-final product (note: from a previous build):
Four 4×4 LED matrices need to be created. Practically any LEDs will do, as I initially just used the cheapest ones I could find, but the current build is using 350 MCD yellow ones (see shopping list). Note also that different LEDs have different lead lengths. You want lengths that are 25 mm or more. I tried using shorter length LEDs, but the resulting matrices were too small to work with. My 4×4 matrices are modeled after the above 8×8 matrix. There’s an Instructables.com project by Electronics Man where he suggests drilling holes in a board to place the LEDs properly aligned at the correct distances from each other. I used his example to make the board and securely place the 16 LEDs for soldering. However, I believe his overall LED matrix design is different from mine, and will likely not work with the circuitry and software used in my project. Here’s an OpenDocument drawing template of the 21 mm-spaced LED matrix holes that I printed out and taped on the board. I then used a punch to make a small dent at each of the sixteen points before drilling. I used a hand-powered drill starting with the smallest size drill bit and worked up in bit size until the LEDs fit snugly into the holes.
Fit the LEDs into the holes with the cathode (shorter) leads above the anode leads at about 45 degrees.
Bend the cathode leads down right at the base and push them so they’re all pointing to the top of the board.
There should be plenty of space between the anode and cathode leads. Now with small needle nose pliers bend the anode (longer) leads about 3 mm above the LED base – perpendicular to the cathode leads. They should be well above the cathodes.
Adjust all of the leads so they overlap appropriately and solder them together. Gently prod out the LEDs from the other side of the board, and you should have a very stable 4×4 matrix. Make three more identical matrices, except two should have the cathodes pointing down instead of up. The two pointing down are levels 1 & 3, the the two pointing up are levels 2 & 4.
The four matrices now need to be soldered together. I differentiate between the LEADS (the extra lead sticking out that’s not soldered to the next lead) and the LINE of soldered-together leads. It’s not straight forward so consult the following:
Top Level 4 Anode Leads: attach to anode line of Level 3. Bend the leads down so they can be soldered to the level below. Note that you can attach additional anode connections anywhere along the Level 4 anode line to the corresponding anode line below it on Level 3 – this will add support to the structure (see picture below).
Top Level 4 Cathode Leads: attach to cathode line of Level 1 (bottom). Do this last, after all of the other levels have been soldered together. You’ll need to use wire to make the connections long enough. I recommend 24 AWG.
Level 3 Anode Leads: leave alone for now.
Level 3 Cathode Leads: attach to cathode line of Level 2. As before, bend the leads down so they can be soldered to the level below, and attach addititonal cathode connections to support structure.
Level 2 Anode Leads: leave alone for now.
Level 2 Cathode Leads: leave alone for now.
Bottom Level 1 Anode Leads: bend leads up to solder to Level 2 line. Attach additional anode connections to support structure.
Bottom Level 1 Cathode Leads: leave alone for now.
I’ll admit soldering the levels together is much more of an art than a science. It’s warping a single 8×8 matrix into 4 levels and you need to be careful to solder the correct leads and lines. I still haven’t developed a technique that I’m really happy with. This picture shows the final soldered together structure including a support attachment:
Once all of the levels have been soldered together, and a few structural connections added for stability, the wires that connect the matrix to the controlling prototype board can be added. I used 8 green for the cathodes and 8 red for the anodes. Make the wires long enough to reach the board – I suggest starting with 25 cm to be sure it’s long enough. Use somewhere around 24 AWG solid (not stranded) to make it somewhat pliable. Solder these wires to the leads above that were left alone. Finally, clip all extraneous leads and wires off the matrix. Finished matrix:
Up Next: Developing The Protoboard To Control The Matrix
]]>
The project has several parts to it:
In the next several posts I’ll go over each aspect of the project, so that if you’re interested you can create your own (and hopefully modify to make even better!). The source code for the game-playing AI is included as well. Note that I’m new to soldering, wiring and electronics in general, so it took me dozens of hours to design and put together a functioning semi-final product. There were frustrating hours where the LED matrix was unstable and some LEDs weren’t lighting up; I destroyed two $8 MAX7219 ICs I’m guessing from either static electricity or short-circuiting; I initially used cheap 26 AWG stranded wire where the thins strands short-circuited the matrix; etc. Also, I made extensive use of Michael Margolis’s Arduino Cookbook, especially for controlling the matrix and putting together the electronics. It’s a great book and I highly recommend it.
(I used mostly Jameco because it’s local, but of course there are dozens of other great online stores to get these from):
Check out the upcoming posts for more details…
Video of it doing its thing:
]]>]]>
As you’ve probably heard, the Revo gives you more than your usual money’s worth by including an onboard Nvidia Ion graphics chip. The Nvidia engineers have very kindly released drivers which allow the graphics chip to do the video decoding rather than bogging down the rather slow Intel Atom 230 CPU with the chore. This results in only ~20% CPU utilization with near flawless playback of HD videos, instead of 100% CPU overload and very choppy video. A $200 computer that can do this is a very good deal.
I recommend a full install of Ubuntu Karmic 9.10. Other sites have instructions for installing a “minimal” gnome setup. I think this is silly. It strips the system of a host of useful applications, while providing non-discernible “improvements”. With the normal full installation of Ubuntu not only will you be able to play video and audio (movies and music) files, but should you choose, also browse the Internet, do email, perform normal office tasks (word processing, spreadsheets, presentations) and more. Provided, the Acer Revo is not a powerhouse and I wouldn’t recommend it for editing photos or videos.
Note that I am NOT an expert on any of this. I’ve been an avid Linux user since Linus announced it back in 1991, but XBMC, the Revo, and the VDPAU driver are all relatively new to me. I will attempt to provide appropriate links for more information on all of these. In fact, all of the information here has been gleamed from numerous websites and postings from other generous XBMC users. I’ll try and note that as possible. Also, I’m assuming at least some competency with a Linux system. If there are an questions feel free to ask them in the comments section. Read this entire article, including the notes at the end, before attempting.
Reference System and Hardware: this is a list of my setup and all of the equipment I used that was necessary to make it work. Of course, other equipment will likely work, but this is what I used.
INSTALLING UBUNTU KARMIC 9.10 ON THE ACER ASPIRE REVO
The Acer Revo doesn’t have an optical drive, so the OS will need to be installed via an USB optical drive or a USB flashdrive. I recommend a USB flashdrive installation and that’s what I’ll describe here.
Congratulations! You’ve got a full Ubuntu Karmic operating system.
INSTALLING THE LATEST NVIDIA VIDEO DRIVERS
sudo apt-get update
sudo apt-get upgrade [Note: hold off on reboot]
sudo apt-get dist-upgrade [Note: hold off on reboot]
It may take a while to download and install all the latest updates. Note that throughout the rest of this installation the system may pop up boxes asking you to reboot, update or install a graphics driver. You can safely close and ignore them.
sudo add-apt-repository ppa:nvidia-vdpau/ppa
sudo apt-get update [Note: DO NOT upgrade]
sudo apt-get install nvidia-glx-195 nvidia-195-modaliases
SELECT THE HDMI AUDIO DRIVER
By default, Ubuntu uses the standard audio ports for output. In order to get sound from your HDMI-attached TV do the following:
Right-click on the Sound icon in the upper panel. Select “Sound Preferences” and then the Hardware tab. From the Profile menu select “Digital Stereo (HDMI) Output”.
Click the “Close” button. That’s it – you’ve got TV sound.
INSTALL XBMC
Installation couldn’t be much easier. From the terminal run:
sudo add-apt-repository ppa:team-xbmc
sudo apt-get update
sudo apt-get install xbmc
sudo apt-get update
You’ll now find XBMC from Applications -> Sound & Video -> XBMC Media Center
Everything should be working. To verify that your Nvidia chip is doing the decoding, hit “o” while running a video. The second line from the top should include something similar to “dc:ff-h264_vdpau”.
For more help on all things XBMC, visit their official website at http://xbmc.org , the Wiki at http://xbmc.org/wiki/?title=XBMC_Online_Manual, and the XBMC forums at http://xbmc.org/forum
BTW, I highly recommend the Aeon theme. It really shines on HDTV.
ADDITIONAL INFORMATION
I also purchased an MCE remote (Mediagate model #GP-IR02BK). This is an infrared remote control designed for controlling a media center. It makes the whole Revo/XBMC feel like an actual appliance since you won’t be using the keyboard and mouse much afterwards. I believe practically any MCE remote will work. Along with the remote you also get an IR receiver that plugs into a USB port. To get mine working I had to install the drivers with “sudo apt-get install lirc”. After that the remote magically controlled XBMC.
Like I noted in the parts section, I swapped out the computer’s initial 1 GB memory card for a 2 GB memory card. I also increased the shared video memory from 256 KB to 512 KB in BIOS. There are some reports that 2 GB and 512 KB shared is necessary to get properly working VPDAU. I have not verified this as I installed the 2 GB before installing Ubuntu. I’d like to hear if anyone has problems.
[NOTE: Added on February 2, 2010]
I just realized that since I swapped out the original 1 GB memory module for a new 2 GB module, that the 1 GB module is just sitting around on my desk unused. So, if anyone’s interested in using it to upgrade their system to 2 GB, I’ll give it to the first person to request it in the comments.
]]>sudo apt-get install gimp-ufraw
(typed in a terminal window)
Gimp will now see and open RAW files. The RAW files will first open up into a UFRaw plugin dialog. In this dialog you can adjust color balance, exposure, curves, etc. Selecting ‘OK’ then imports the image into the normal Gimp editing window.
I’m using a Nikon D70 DSLR camera with NEF RAW files and everything worked flawlessly under the just released Karmic Koala Ubuntu 9.10.
]]>
This HOWTO assumes you have a basic understanding of Linux/Unix, and commands such as fdisk and mount.
The process can be divided up into 5 main parts:
1) Using a LiveCD Linux disk to set up all the partitions.
2) Installing Kubuntu
3) Copying the GRUB booter to a memory card / flashdrive
4) Installing M$ Windows XP
5) Copying linux.bin to C:\ and editing C:\boot.ini
and that should give a dual booting system.
OK, now for all of the details:
1) Use a LiveCD Linux disk to set up all the partitions.
Since the Eee 1000 does not have an optic drive, I use UNetbootin to convert the distribution’s LiveCD image file to a bootable USB flashdrive. Get UNetbootin here [1] and the latest Kubuntu LiveCD image here [2].
Using your Kubuntu LiveCD flashdrive boot up Linux on the Eee 1000. Using fdisk set up the partitions to your liking. There’s an infinite number of ways of setting up the two systems, but like I said, I wanted Linux on the 8 GB drive and XP on the second. If you put XP on the second drive then you need at least 1 cylinder on the first drive to put the booter. Here’s the partitions I created:
Partition Cylinders Type (Hex code) Blocks sda1 1-980 Linux (83) 7871818 sda2 981-981 NTFS (7) 8032 sdb1 1-2438 NTFS (7) 19583203 sdb2 2439-3875 Linux (83) 11542702 sdb3 3876-3924 Linux swap (82) 393592
fdisk complained something about the sda drive when I exited, but it still worked. This should work out to about an 8 GB Linux system with an 11.8 GB /home and 403 MB swap, and a 20.1 GB NTFS partition for Windows XP.
2) Install Kubuntu
Now hit the Install icon to start the installation!
On Step 4 use Manual disk setup, select the appropriate partitions, and set their mount points and file systems:
sda1: / (Ext3 journaling file system) sdb2: /home (Ext3) sdb3: swap
On Step 6 select “Advanced…”, and set “Install boot loader” and specify the Device as /dev/sda1. Hit the OK button to return to Step 6 and then the Install button.
When it’s done installing reboot into your fresh Kubuntu system! Visit this excellent HOWTO [3] to install an Eee 1000-specific kernel to get all the drivers functioning (wireless, etc) – this step can be done after the dual boot installation is finished. NOTE: wireless, bluetooth and a few other things are disabled by default in the BIOS. Enable them in BIOS to have them recognized by the kernel.
3) Copy the GRUB booter to a memory card / flashdrive
Initially, after you’ve installed M$ Windows XP you can no longer boot into Linux. This next step allows you to subsequently tell M$ Windows how to dual boot.
Stick a flashdrive or SD card preformatted to VFAT into the machine and mount it. I just used the Kubuntu LiveCD flashdrive that I installed the system with. Then run the following command:
dd if=/dev/sda1 of=/(usb-drive)/linux.bin bs=512 count=1
where, of course, “usb-drive” is where you’ve mounted the drive.
4) Install M$ Windows XP
I kludged an internal CD drive into a USB drive using a cheap SATA to USB converter – not pretty, but it worked. Do whatever is necessary to get M$ Windows XP installed. Here’s what’s important:
M$ Windows won’t boot from the second (32 GB) drive without an NTFS partition on the first (8 GB) drive. During installation, tell it to use the 8 MB NTFS for the MBR and the 20 GB NTFS for the system.
Unfortunately, you’ll end up with a miniscule C:\ drive with several hidden files and then the main installation on D:\. Nothing major, but it’s not a single drive and you’ll have to get used to installing everything to the D:\ drive.
5) Copy linux.bin to C:\ and edit C:\boot.ini
All of the information here I pulled from this great article [4] by Ed Park.
First, copy the linux.bin file from the USB drive to C:\.
Second, run notepad and edit C:\boot.ini. Note that C:\boot.ini is a hidden system file, so it probably won’t show up in Windows Explorer. To edit the file, try: Start->Run and enter: notepad C:\boot.ini. Add the following line at the end: c:\linux.bin=”Linux”
To make C:\boot.ini writable, you can either :
For reference, here is a copy of Ed’s boot.ini file.
Finally, reboot again. You should be able to pick either Linux or M$ Windows. Selecting Linux will start GRUB.
I hope this HOWTO is as useful to others as the following websites were to me. I’ll update it as necessary to make it more useful. Ask any questions in the comments below. I’m not an expert, but I’ll try to answer as possible.
References:
[1] UNetbootin – http://unetbootin.sourceforge.net
[2] Kubuntu LiveCD image – http://www.kubuntu.org/getkubuntu/download
[3] http://www.chrishorsnell.co.uk/2008/09/asus-eee-1000-perfect-install-with-ubuntu/
[4] http://www.geocities.com/epark/linux/grub-w2k-HOWTO.html
]]>It sounds like a heck of a lot of fun to me, and since I’ve always wanted to write my own Great American Novel, I’ve signed up! I plan on blogging about my experience here, and even putting up my nightly additions to the novel (possibly in a separate self-contained website). Stay tuned as the writing commences at midnight November 1st..
Ever wanted to write your own novel? Get more information at NaNoWriMo.org.