Close

take the hard path when you only have to take it once

A project log for Persistence of Vision LED Matrix

Because every project needs a twist.

matthew-james-bellafaireMatthew James Bellafaire 10/15/2018 at 21:470 Comments

I think this might have started as a bit of a joke to myself "well I don't want to make all those patterns for the matrix, might as well have a program do it for me". Regardless, it happened anyway after about 4 hours of work in NetBeans I now have a decent AWT matrix array generator that works. 

So yeah, that happened... but I’m glad I bothered to make this, it's incredibly easy now to generate the arrays that control the images. this whole project runs on byte arrays that operate essentially as "Frames", so this tool gives me the ability to program more complex animations and make them look better. Tedious things are no fun and messing with a bunch of conditional statements and manually editing the values in the arrays would have taken me equally as long for a decent animation as this tool did (and it would have been way less fun). I'll have the java source code attached to this project, in addition to the end of this log (I don't feel comfortable downloading an antonymous jar file and I don't expect you to be either). The only reason this whole thing was written using AWT was that it's something I’m familiar with thanks to all the time spent in high school making java games. The program essentially just has a grid of boxes, when you click a box it changes color in the order black>red>green>blue. Hitting "Add" will have that frame included in the output, "clear current" just changes all the boxes back to black. Hitting "Generate" takes all the images that were made and writes the code for them then dumps that into a text file which still must be manually copied and pasted into the sketch. "Animation count" just keeps track of the number of arrays created.

Now, on the Arduino side of things a few changes have been made. Whereas before the board would store all the arrays in ram they are now kept directly in the flash memory. This was achieved by utilizing the pgmspace.h library, which allowed me to keep all the byte arrays in flash memory and then read from them at will. making this change has opened the capability of this project by quite a bit. Now using the matrix generator and the new structure for the arrays the ATmega on the board can store about 180 byte arrays with 110 values each. which is a significant upgrade from the previous ~10 or so. 

At this point I'm glad I chose the ATmega328 for this project, sure a better micro-controller with more speed and more memory would have made this whole project go a lot faster, but it wouldn't have been this fun. I've learned a lot so far just trying to pull every gram of performance out of this $3 chip and let me say it's been a fun challenge every step of the way. see you in the next log! (also feel free to use this code however you want) 

package matrixarraygenerator;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 *
 * @author James
 */
public class MatrixArrayGenerator extends JPanel {

    public static int squareWidth = 60;
    public static int squareHeight = 60;
    public static int gridRightBound = 10 + (10 * squareWidth);
    public static int outputFileCount = 1;

    static File outputFile;
    static FileWriter outputWriter;
    static boolean writeflag = false;

    public static int[] currentArray
            = {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, 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, 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, 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
            };

    //look, its not a professional piece of software, i'm probably never going to touch it again after this project. i don't care how much memory it eats. 
    public static int[][] savedArrays = new int[110][1000];
    public static int savedArrayIndex = 0;

    public void mouseClicked(MouseEvent e) throws IOException {
        int x = e.getX();
        int y = e.getY();
        System.out.println(x + "," + y);//these co-ords are relative to the component

        if (clickedButton(x, y) < 110) {
            currentArray[clickedButton(x, y)] = 1;
        }
    }

    public static int clickedButton(int x, int y) throws IOException {
        for (int a = 0; a < 10; a++) {
            for (int b = 0; b < 11; b++) {
                if (x < 10 + squareWidth * (a + 1) && x > 10 + squareWidth * (a) && y > 35 + squareHeight * b && y < 35 + squareHeight * (b + 1)) {
                    return a + b * 10;
                }
            }
        }
        if (x > 10 + gridRightBound && x < 10 + 300 + gridRightBound && y > 35 && y < 160) {
            add();
            return 110;
        }
        if (x > 10 + gridRightBound && x < 310 + gridRightBound && y > 170 && y < 290) {
            writeflag = true;
            generate();
            writeflag = false;
        }
        if (x > 10 + gridRightBound && x < 310 + gridRightBound && y > 300 && y < 360) {
            clear();
        }
        return 120;
    }

    public static void clear() {
        for (int a = 0; a < 110; a++) {
            currentArray[a] = 0;
        }
    }

    public static void generate() throws IOException {
        outputFile = new File("Generated Matrix Array" + outputFileCount + ".txt");
        outputFileCount++;
        outputWriter = new FileWriter(outputFile, false);
        String output = "//Generated Arrays created in MatrixArrayGenerator by james bellafaire\n";
        for (int a = 0; a < savedArrayIndex; a++) {
            output = output + "const PROGMEM byte array" + a + "[] = {";
            for (int b = 0; b < 110; b++) {
                output = output + savedArrays[b][a];
                if (b != 109) {
                    output = output + ", ";
                }
            }
            output = output + "};\n";
        }
        output = output + "\n\n\n//generated array coppying created in MatrixArrayGenerator by james bellafaire\n void changeImage() {\n  if (millis() > Time + animationDisplayTime) {\n"
                + "    Time = millis();\n";
        output = output + "if (animationCount == 0) {\n"
                + "      memcpy_PF(RenderImage," + "array0" + ", 110);\n"
                + "      animationCount++;\n"
                + "    }";
        for (int a = 1; a < savedArrayIndex; a++) {
            output = output + " else if (animationCount == " + a + ") {\n"
                    + "      memcpy_PF(RenderImage, array" + a + ", 110);\n"
                    + "      animationCount++;\n"
                    + "    }";
        }
        output = output + "if (animationCount == " + (savedArrayIndex) + "){ animationCount = 0;}\n}\n}";
        outputWriter.write(output);
        outputWriter.close();
    }

    public static void add() {
        for (int a = 0; a < 110; a++) {
            savedArrays[a][savedArrayIndex] = currentArray[a];
        }
        savedArrayIndex++;
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        //well i gotta get the data out somehow 
        JFrame frame = new JFrame("matrix generator tool");
        MatrixArrayGenerator pane = new MatrixArrayGenerator();
        frame.add(pane);
        frame.setSize(940, 720);
        pane.setBackground(Color.black);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
        frame.setResizable(false);
        frame.addMouseListener(new MouseAdapter() {// provides empty implementation of all
            // MouseListener`s methods, allowing us to
            // override only those which interests us
            public void mousePressed(MouseEvent e) {
                int x = e.getX();
                int y = e.getY();
                try {
                    //                System.out.println(e.getX() + "," + e.getY());
//                System.out.println(clickedButton(x, y));
                    if (clickedButton(x, y) < 110) {
                        switch (currentArray[clickedButton(x, y)]) {
                            case 0:
                                currentArray[clickedButton(x, y)] = 1;
                                break;
                            case 1:
                                currentArray[clickedButton(x, y)] = 2;
                                break;
                            case 2:
                                currentArray[clickedButton(x, y)] = 3;
                                break;
                            case 3:
                                currentArray[clickedButton(x, y)] = 0;
                                break;
                            default:
                                break;
                        }

                    }
                } catch (IOException ex) {
                    Logger.getLogger(MatrixArrayGenerator.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        while (true) {
            pane.repaint();
        }
    }

    public MatrixArrayGenerator() {
        setFocusable(true);  
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.white);

        //draw grid lines
        for (int a = 0; a < 11; a++) {
            g.drawLine(10 + (a * squareWidth), 10, 10 + (a * squareWidth), 10 + 11 * squareHeight);
        }
        for (int a = 0; a < 12; a++) {
            g.drawLine(10, 10 + a * squareHeight, 10 + squareWidth * 10, 10 + a * squareHeight);
        }

        //render buttons
        Font buttonFont = new Font("Courier", Font.PLAIN, 26);
        g.drawRect(10 + gridRightBound, 10, 300, 120);
        g.setFont(buttonFont);
        g.drawString("Add", 135 + gridRightBound, 80);

        g.drawRect(10 + gridRightBound, 140, 300, 120);
        g.setFont(buttonFont);
        g.drawString("Generate", 110 + gridRightBound, 210);

        g.drawRect(10 + gridRightBound, 270, 300, 60);
        g.setFont(buttonFont);
        g.drawString("Clear Current", 80 + gridRightBound, 310);
        g.drawString("Animation Count " + savedArrayIndex, 10 + gridRightBound, 380);
        //color the boxes with respect to their selected color
        g.setColor(Color.red);
        for (int a = 0; a < 10; a++) {
            for (int b = 0; b < 11; b++) {
                //select color appropriate to color array
                switch (currentArray[a + b * 10]) {
                    case 0:
                        g.setColor(Color.black);
                        break;
                    case 1:
                        g.setColor(Color.red);
                        break;
                    case 2:
                        g.setColor(Color.green);
                        break;
                    case 3:
                        g.setColor(Color.blue);
                        break;
                    default:
                        break;
                }
                g.fillRect(a * squareHeight + 11, b * squareWidth + 11, squareWidth - 1, squareHeight - 1);
            }
        }
        if (writeflag) {
            g.drawString("Writing to file", 10 + gridRightBound, 700);
        }
    }

}

 "ha, these programs literally write themselves" 

Discussions