We <3 the MiniPOV. But we thought that we'd make it a bit easier to get into, by writing a custom firmware that doesn't require you to have the WinAVR/gcc-avr toolchain set up in order to play about with it.
The way the MiniPOV works is by attaching the control lines on the serial port (normally used to signify when the PC or device are transmitting) to the programming pins of the AVR. In this way, you can waggle the control lines on the serial port and send the right signals to the chip to program it. The AVR programming program AVRDUDE has a mode called DASA which supports this “bit banging” mode to program the AVR.
The effect this has is that if you want to change what the device displays, you have to reporgram the entire firmware! Including all of the program logic. This seems like it might be a pain if you just want to change the message (though it does mean that you can reporgram it to do pretty much whatever you like). We wanted to make it simpler to change the message.
The stock MiniPOV firmware uses an array to store the “image” which is sent to the LEDs, i.e. PORTB on the micro. This allows you total flexibility in what you want to display (doesn't just have to be letters, can be basic pictures too). However, most of the time we'd like to display alphanumeric messages. So rather than programming an “image” into the flash, instead we opted for the approach taken by sparr on the Adafruit forums and use a table of characters, and then use a text string to look up the characters to display.
We have shamelessly ripped much of his code. Hope they don't mind! We modified it slightly, instead of reading the text string out of the flash program memory (PROGMEM), we read the string out of EEPROM (of which there are about 128 bytes of on the attiny2313). This means that instead of having to reflash the entire program memory, we just have to program the EEPROM with the message we want to display.
We also wrote a little python script that will generate the .hex file to put into the EEPROM.
So, you've just assembled one of our MiniPOV kits, with a chip which is preprogrammed with our firmware. Now you want to try programming it with a new string. Here's how:
Prerequisites: You need to have Python installed. If you're on Linux/Mac OSX, you'll probably already have it installed. If you're on Windows, you can grab a copy of Active Python. You'll also need AVRDude installed, which comes with WinAVR if you're on Windows.
Where /dev/ttyUSB2 is the name of the serial port
This should be fairly quick. If you've got any issues, just come to an OpenLab or email kits@tinkersoc.org and we'll give you a hand. Please DON'T email adafruit, as they've got nothing to do with this software and they won't help you!
You can also reflash the firmware with any other that you feel like, as described in the MiniPOV Software page. There are a few nice ones on the forums, like the Evil Mad Scientist Cylon Firmware.
// original code by LadyAda (and other contributors?) // modified with font/string support by sparr // subject to the license of the MiniPOV3 project // // Also modified by Chris R <chris@tinkersoc.org> // * Added EEPROM loading #include <avr/io.h> // this contains all the IO port definitions #include <avr/interrupt.h> #include <avr/pgmspace.h> #include <avr/delay.h> #include <avr/eeprom.h> // for eeprom things // TinkerSoc // Chris R <chris@tinkersoc.org> #define TIMER1_PRESCALE_1 1 // store all the font data in program memory (ROM) // instead of RAM (the default) const char font[64*5] PROGMEM; // defined below // store the text to be displayed uint8_t text_string[64]; // special pointers for reading from ROM memory PGM_P font_p PROGMEM = font; #define NUM_ELEM(x) (sizeof (x) / sizeof (*(x))) // this function is called when timer1 compare matches OCR1A uint16_t j = 0; SIGNAL( SIG_TIMER1_COMPA ) { uint8_t out; out = *(text_string + j/6); // the character we are on if (out == '\0'){ // at the end of the string! j = 0; } else { // read one column from ROM if(j%6<5) { out = pgm_read_byte( font_p + ( (out>='0'&&out<='9')?(out-'0'): (out>='A'&&out<='Z')?(out-'A'+10): (out>='a'&&out<='z')?(out-'a'+36): (out==' ')?(62):(63) ) * 5 + j % 6); // one column from the character } else { out = 0; // blank column between letters } PORTB = out; // display the loaded column j++; } } int main(void) { DDRB = 0xFF; // set all 8 pins on port B to outputs PORTB = 0x2A; // load string out of EEPROM eeprom_read_block( ( void* ) &text_string, ( const void* )0x00 , 62); PORTB = 0x00; /* desired freq = 400Hz clock freq = 20MHz 20MHz / (400Hz * 2) = 25000 */ TCCR1B = (1 << WGM12) | TIMER1_PRESCALE_1; OCR1A = (uint16_t)25000; TIMSK |= 1 << OCIE1A; // Output Compare Interrupt Enable (timer 1, OCR1A) sei(); // Set Enable Interrupts while(1); } // font inspired by many 5x8 fonts from various sources const char font[64*5] PROGMEM = { 0b01111110, 0b10100001, 0b10011001, 0b10000101, 0b01111110, //0 0b00000000, 0b11111111, 0b10000010, 0b10000000, 0b00000000, //1 0b10000010, 0b11000001, 0b10100001, 0b10010001, 0b10001110, //2 0b01000010, 0b10000001, 0b10001001, 0b10001001, 0b01110110, //3 0b00011000, 0b00010100, 0b00010010, 0b11111111, 0b00010000, //4 0b01001111, 0b10001001, 0b10001001, 0b10001001, 0b01110001, //5 0b01111110, 0b10001001, 0b10001001, 0b10001001, 0b01110010, //6 0b00000001, 0b11000001, 0b00110001, 0b00001101, 0b00000011, //7 0b01110110, 0b10001001, 0b10001001, 0b10001001, 0b01110110, //8 0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01111110, //9 0b11111110, 0b00001001, 0b00001001, 0b00001001, 0b11111110, //A 0b11111111, 0b10001001, 0b10001001, 0b10001001, 0b01110110, //B 0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01000010, //C 0b11111111, 0b10000001, 0b10000001, 0b10000001, 0b01111110, //D 0b11111111, 0b10001001, 0b10001001, 0b10001001, 0b10000001, //E 0b11111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, //F 0b01111110, 0b10000001, 0b10000001, 0b10010001, 0b11110010, //G 0b11111111, 0b00001000, 0b00001000, 0b00001000, 0b11111111, //H 0b00000000, 0b10000001, 0b11111111, 0b10000001, 0b00000000, //I 0b01100000, 0b10000000, 0b10000001, 0b01111111, 0b00000000, //J 0b11111111, 0b00001000, 0b00010100, 0b00100010, 0b11000001, //K 0b11111111, 0b10000000, 0b10000000, 0b10000000, 0b10000000, //L 0b11111111, 0b00000110, 0b00111000, 0b00000110, 0b11111111, //M 0b11111111, 0b00000110, 0b00011000, 0b01100000, 0b11111111, //N 0b01111110, 0b10000001, 0b10000001, 0b10000001, 0b01111110, //O 0b11111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, //P 0b01111110, 0b10000001, 0b10100001, 0b01000001, 0b10111110, //Q 0b11111111, 0b00001001, 0b00001001, 0b00011001, 0b11100110, //R 0b01000110, 0b10001001, 0b10001001, 0b10001001, 0b01110010, //S 0b00000001, 0b00000001, 0b11111111, 0b00000001, 0b00000001, //T 0b01111111, 0b10000000, 0b10000000, 0b10000000, 0b01111111, //U 0b00011111, 0b01100000, 0b10000000, 0b01100000, 0b00011111, //V 0b11111111, 0b01100000, 0b00011100, 0b01100000, 0b11111111, //W 0b11000011, 0b00110100, 0b00001000, 0b00110100, 0b11000011, //X 0b00000011, 0b00001100, 0b11110000, 0b00001100, 0b00000011, //Y 0b11000001, 0b10100001, 0b10011001, 0b10000101, 0b10000011, //Z 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, //a 0b01111111, 0b01000100, 0b01000100, 0b01000100, 0b00111000, //b 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00101000, //c 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b01111111, //d 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b01001000, //e 0b00000100, 0b01111110, 0b00000101, 0b00000001, 0b00000000, //f 0b10011000, 0b10100100, 0b10100100, 0b10100100, 0b01111000, //g 0b01111111, 0b00000100, 0b00000100, 0b00000100, 0b01111000, //h 0b00000000, 0b00000000, 0b01111010, 0b00000000, 0b00000000, //i 0b00000000, 0b01000000, 0b10000000, 0b01111010, 0b00000000, //j 0b01111111, 0b00010000, 0b00101000, 0b01000100, 0b00000000, //k 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, //l 0b01111000, 0b00000100, 0b00001000, 0b00000100, 0b01111000, //m 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, //n 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, //o 0b11111100, 0b00100100, 0b00100100, 0b00100100, 0b00011000, //p 0b00011000, 0b00100100, 0b00100100, 0b00100100, 0b11111100, //q 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00000100, //r 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100100, //s 0b00000000, 0b00000100, 0b01111111, 0b01000100, 0b00000000, //t 0b00111100, 0b01000000, 0b01000000, 0b01000000, 0b00111100, //u 0b00001100, 0b00110000, 0b01000000, 0b00110000, 0b00001100, //v 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, //w 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, //x 0b10011100, 0b10100000, 0b10100000, 0b10100000, 0b01111100, //y 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, //z 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, //blank 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111 //unknown };
Note you'll also need the IntelHex class for Python for this to run.
from intelhex import IntelHex import os print "MiniPOV Message Reprogrammer" print "by Chris R <chris@tinkersoc.org>" print "-----------------------------------" print "Please make sure you've plugged the MiniPOV into a serial port of some description" print "You must also make sure that avrdude is installed and in the current path" portname = raw_input("Name of your serial port:") string = raw_input("New POV string:") print string h = IntelHex("") i = 0 for c in string: h[i] = ord(c) i = i + 1 h[i] = 0x20 # add a space at the end h[i+1] = 0 # null terminate h.writefile("pov.eep") prompt = raw_input("Okay to reprogram the MiniPOV now?") if (not cmp(prompt,"")): if (prompt[0] == "y" or prompt[0] == "Y"): print "Run this command:" print "avrdude -p attiny2313 -c dasa -P %s -U eeprom:w:pov.eep" % ( portname )