/*
mrb.c

Written by Windell Oskay, http://www.evilmadscientist.com/

Copyright 2007 Windell H. Oskay
Distributed under the terms of the GNU General Public License, please see below.

This avr-gcc program for the Atmel ATTiny2313 
displays an alphanumeric string on a 16-segment LED display. 
This is the default firmware for the LED Micro-Readerboard Kit, kit v. 2.0.
It also includes (optional) updated code for kit v 1.0.

IMPORTANT NOTE: The default settings in this firmware are for a common-cathode display,
like the ones that we're shipping in the version 2.0 kits (with BLACK) face paint.
if you want to use a common-anode display, see below under "COMPILE OPTIONS"
 
 
More information about this project is at 
http://www.evilmadscientist.com/article.php/microreader2

-------------------------------------------------
USAGE: How to compile and install

You will need an AVR programmer and appropriate target board to use this firmware.  

A makefile is provided to compile and install this program using AVR-GCC and avrdude.

To use it, follow these steps:
1. Update the header of the makefile as needed to reflect the type of AVR programmer that you use.
2. Open a terminal window and move into the directory with this file and the makefile.  
3. At the terminal enter
		make clean   <return>
		make all     <return>
		make install <return>
4. Make sure that avrdude does not report any errors.  If all goes well, the last few lines output by avrdude
should look something like this:

avrdude: verifying ...
avrdude: XXXX bytes of flash verified

avrdude: safemode: lfuse reads as 62
avrdude: safemode: hfuse reads as DF
avrdude: safemode: efuse reads as FF
avrdude: safemode: Fuses OK

avrdude done.  Thank you.


If you a different programming environment, make sure that you copy over 
the fuse settings from the makefile.


-------------------------------------------------

This code should be relatively straightforward, so not much documentation is provided.  If you'd like to ask 
questions, suggest improvements, or report success, please use the evilmadscientist forum:
http://www.evilmadscientist.com/forum/

-------------------------------------------------


Revision hitory:

6/1/2007
Version 2.0 release

Tweaked to work nicely with common-anode displays.  
Battery life looks very good with the multiplexed option, expect > 1 month of run time. (w00t.)




2/25/2007 
Version 2.0b

Using common-cathode display now; add option to switch between common-anode and
common cathode displays.

Reconfigure clock:  Now using 4 MHz RC clock source with divide by 8, giving 0.5 MHz system clock.
Typical active MCU power consumption (not counting that used by LEDs!) should be about 0.4 mA now,
which is awfully small compared to the power applied to the LEDs. 
	 
Added compile option for using either sequentially multiplexed display (one LED on at a time) or parallel
(non-multiplexed) display, to be used to reach either extreme of efficiency or brightness.

Added power-up test:  If PD4 (which could otherwise control the decimal point) is wired up to ground at
start up, the firmware will pick from a different set of strings.  You could hook a switch up....


The fuse settings should be as follows:
	lfuse: 0x62
	hfuse: 0xDF
	efuse: 0xff

The fuse settings are built into the makefile; if you use AVR-GCC and 
the makefile included with this C file, you do not need to worry about these settings.
If you use any other setup, please enter the fuse settings manually.

12/17/2008
Version 2.1

Removed one string and shortened the max string length so that it will
 compile and program with the latest version of the AVR toolchain; 
 we were slightly above the memory space limitations of the '2313; 
 now we're very slightly below.






-------------------------------------------------


 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 2 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, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA


*/





#include <avr/io.h>
#include <avr/pgmspace.h> 
#include <avr/eeprom.h> 

/******** COMPILE OPTIONS ********/

//A common cathode display is assumed for kit v 2.0.   You can use a common-anode display instead, but
// the "holiday option" is only available for common-cathode displays right now. 
//
//Uncomment the following line to use a common-anode LED display (e.g., for kit v. 1.0):
//#define commonanode 1			


			
//You have the choice whether to run the LEDs sequentially (multiplexed) or simultaneously.
// Multiplexed display is much more power efficient, but much less bright.
// Comment out the following line to disable multiplexing:
#define multiplexed 1
									
#define MultiplexHoldTime 270U			// How long to display each letter (for multiplexed operation) -- was 310 previously!

//The next parameter controls the brightness (and duration) of the display of each letter.
// This is a very short delay, of n CPU cycles ("nops")-- short enough that you probably don't want
// to use a standard C loop for it.

#define shortdelay 			asm("nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t" \
								"nop\n\t"::);

#define ParallelHoldTime 12000U			// How long to display each letter (for non-multiplexed operation)
								
/******** END COMPILE OPTIONS ********/


/******** BEGIN FONT AND STRING DEFINITIONS ********/

const unsigned int font_table[] PROGMEM = 
{
1081,63940,12689,		//A,B,C
47556,12697,16793,		//D,E,F
31121,51225,12740,		//G,H,I
47105,1561,12305,		//J,K,L
35377,35889,47505,		//M,N,O
49561,48529,50585,		//P,Q,R
31128,452,47121,		//S,T,U
531,35859,1570,			//V,W,X
548,13186,				//Y,Z
18030,					//Asterisk ('*') character.
514						//Forward slash ('/') character.
}; 

/* We have a rather limited font table above, consisting of 26 upper-case letters 
as well as an asterisk ('*'), forward slash ('/'), and the space (' ') char, which does not need an entry in the table.

The whole character set, for the purpose of adding new strings is " ABCDEFGHIJKLMNOPQRSTUVWXYZ*" plus "/" 
Of course, you can restructure the font, reconfigure the table for a different sort of display,
or add additional characters however you like, within the limits of flash memory.

Strings are limited to 75 characters in length, including spaces.  

If you wish to increase or decrease the number of
strings (within the limits of flash memory), you may do so, provided that 
you change the following three things below: 

1. The constant "NoOfStrings," 
2. the list of strings, and
3. the definition of StringSet[].

(For whichever string set you want to add it to.)

String Set 2 is used for holiday option

*/


const char String0[] PROGMEM = "I AM SHOWING OFF MY PROGRAMS    ";  //String0 will rotate through all strings.

const char String1[] PROGMEM = "PLEASE CHANGE MY BATTERIES    JUST KIDDING   HAVE A NICE DAY    ";
const char String2[] PROGMEM = "HELLO WORLD     ";
const char String3[] PROGMEM = "I SOLDERED THIS MYSELF     ";

const char String4[] PROGMEM = "MAKE   TECHNOLOGY ON YOUR TIME     MAKEZINE DOT COM     ";
const char String5[] PROGMEM = "HELLO   I AM AN OPEN SOURCE MICRO READERBOARD     ";
const char String6[] PROGMEM  = "MAKE    ";
const char String7[] PROGMEM  = "MAKE IT    ";
const char String8[] PROGMEM = "THIS DISPLAY USES ONE HUNDRED PERCENT RECYCLED ELECTRONS     ";
const char String9[] PROGMEM  = "MAKE DONT TAKE    ";
const char String10[] PROGMEM = "MAKER    ";
const char String11[] PROGMEM = "MAKE STUFF     "; 
const char String12[] PROGMEM = "EVIL MAD SCIENTIST DOT COM    ";
const char String13[] PROGMEM = "I HEART ELECTRONICS    ";
const char String14[] PROGMEM = "DUDE    NICE BLINKYLIGHTS      ";
const char String15[] PROGMEM = "JOIN THE RESISTANCE      USE KIRCHHOFFS RULES AND OHMS LAW       ";
 
const char String16[] PROGMEM  = "ALL YOUR GIFT ARE BELONG TO US      ";
const char String17[] PROGMEM = "THESE ARE NOT THE PRESENTS YOU ARE LOOKING FOR   MOVE ALONG     "; 
const char String18[] PROGMEM = "WARNING  WARNING   THIS TREE WILL SELF DESTRUCT    ";
const char String19[] PROGMEM  = "I AM IN UR TREE BLINKIN UR LITEZ      ";	
const char String20[] PROGMEM = "ORNAMENT     ";
const char String21[] PROGMEM = "LET IT SNOW  LET IT SNOW  LET IT SNOW    "; 
const char String22[] PROGMEM  = "I HOPE YOU SAVED SOME PRESENTS FOR US ORNAMENTS    "; 
const char String23[] PROGMEM = "HAPPY NEW YEAR     ";
const char String24[] PROGMEM = "ALL I WANT FOR XMAS IS A MICROCONTROLLER PROGRAMMER    ";
const char String25[] PROGMEM = "I AM THE VERY BEST ORNAMENT ON THIS TREE     ";
const char String26[] PROGMEM = "SANTA CLAUS IS COMING TO TOWN    LOOK BUSY      ";
const char String27[] PROGMEM = "BAH  HUMBUG      ";
const char String28[] PROGMEM  = "HAPPY HANUKKAH   ";

PGM_P StringSet1[] PROGMEM = 
{String0,  String1,  String2,  String3,  String4, 
 String5,  String6,  String7,  String8,  String9, 
 String10, String11, String12, String13, String14, String15
 }; 

PGM_P StringSet2[] PROGMEM = 
{String0,  
String1,  
String2,  
String3,  String17,  
String4,  String18,
String5,  String19,
String6,  String20,
String7,  String21,
String8,  String22,
String9,  String23,
String10, String24,
String11, String25,
String12, String26,
String13, String27,
String14, String28,
String15, 
String16
 }; 
#define NoOfStrings1 sizeof(StringSet1)>>1
#define NoOfStrings2 sizeof(StringSet2)>>1

 
		

/********  END FONT AND STRING DEFINITIONS ********/


uint16_t eepromWord __attribute__((section(".eeprom")));

int main (void)
{
unsigned int FontWord, m, n;				//16-bit unsigned integers
unsigned char  hiByte;  
uint8_t stringNo, stringNoCopy,  altset, NoOfStrings;		//8-bit unsigned integers
uint8_t PA, PB, PD;						
PGM_P p;	

// no need to eat up ram to copy strings....
//char buf[75];				//Set number of characters per string: this eats most of the SRAM!

	
//Initialization routine: Clear watchdog timer-- this can prevent several things from going wrong.		
MCUSR &= 0xF7;		//Clear WDRF Flag
WDTCSR	= 0x18;		//Set stupid bits so we can clear timer...
WDTCSR	= 0x00;

DIDR = 3U;			//Disable analog comparator-- save power.


#ifdef commonanode
	PORTA = 255U;
	PORTB = 255U;
	PORTD =  255U;
	//PORTD |= _BV(PD4);	
#endif	

//Init routine: Check to see if pin D4 is connected to ground.  If it is, then we will use
// StringSet2 instead of StringSet1 as our list of valid strings. 
// In the default firmware, this is used to choose whether we should use the holiday phrases or not.
// How it's done: Configure pin D4 as an input, with pull-up resistor turned on.
// Then, read input value.  If input is low, D4 has been pulled low by an external force.

// This routine is ONLY active for common cathode configuration.  You could rig up a similar one
// for the common anode, checking to see if the DP pin were connected to +V instead of ground.

#ifndef commonanode

DDRD &= ~_BV(PD4);		// D4: Input
PORTD |= _BV(PD4);		// D4 High
 
asm("nop");				// Wait one cycle for input to be ready -- this is important.

if (bit_is_clear(PIND,4))
{  
	 altset = 1;		// Use alternate set of strings (string2 set)
	 NoOfStrings = NoOfStrings2;
	}
else	
{ 
	altset = 0;
	NoOfStrings = NoOfStrings1;
	 }
	 
#else

 	altset = 0;
	NoOfStrings = NoOfStrings1;
	
#endif	



	 
	 


//Data direction register: DDRD
//Set all ports to output *EXCEPT* PA2 (not used)
	DDRA = 3U;
	DDRB = 255U;	
	DDRD = 127U;


//Turn all LEDs off to begin with:

#ifdef commonanode
	PORTA = 255U;
	PORTB = 255U;
	PORTD =  255U;
	//PORTD |= _BV(PD4);	
#else
	PORTA = 0;
	PORTB = 0;
	PORTD = 0;
#endif	
	
	
	
// Read string number from EEPROM data memory. If good, increment it and save
// the new value to EEPROM.  This allows us to cycle through the set of strings in flash, 
// using an new one each time that the unit is reset.  

if ( eeprom_read_word(&eepromWord)  > (uint16_t) NoOfStrings)
	stringNo = 0U;
else	
	stringNo = (uint8_t) (eeprom_read_word(&eepromWord)) ;
	
	
stringNoCopy = 	stringNo;
	
if (++stringNo >= NoOfStrings)	
	stringNo = 0U;

eeprom_write_word(&eepromWord, stringNo);

stringNo = stringNoCopy;

for (;;)  // main loop
{


if (altset)
	memcpy_P(&p, &StringSet2[stringNo], sizeof(PGM_P));
else
	memcpy_P(&p, &StringSet1[stringNo], sizeof(PGM_P));
	
//	strcpy_P(buf, p);		
	
	
	
//if ((stringNoCopy == 0) && (++stringNo >= NoOfStrings))	//old version
//	stringNo = 0U;
 
 
 //Loop through strings in Demo mode!	
	if	(stringNoCopy == 0) {
		if (++stringNo >= NoOfStrings)
			stringNo = 0U;
	}
	 
 
 
 
	//   i = 0; 
   char c;
   
   while((c = (char)pgm_read_byte(p++)) != '\0' )		// i.e., while the string has not ended
	{  
		

//Add a short delay-- an off period-- between characters: 
//Default delay time value: 2000U, but you can change this value in the range (0,65535U). 
//This delay is important because it gives visual separation between repeating letters.
//Without the delay, the word "TREE" looks like it just says "TRE"

	m = 0; 
	while (m <= 850U)		//Change the length of the short delay here.	
		{
	
#ifdef commonanode
	PORTA = 255U;
	PORTB = 255U;
	PORTD = 255U;
	//PORTD |= _BV(PD4);	
#else
	PORTA = 0;
	PORTB = 0;
	PORTD = 0;
#endif	
			m++;
		}


//Retrieve a 16-bit word from the font table up above that describes how the sixteen
//segments of the LED display will be lit up.  
//(Here, word means two bytes-- 16 bits-- not a collection of letters.)
				
	if (c == ' ')
			FontWord = 0; 
		else if (c == '*')
			FontWord = (uint16_t)pgm_read_word(&font_table[26]); 
		else if (c == '/')
			FontWord = (uint16_t)pgm_read_word(&font_table[27]); 
		else
			FontWord = (uint16_t)pgm_read_word(&font_table[c - 'A']); 
			//Note: The ASCII char 'A' is element 0 of the font table.

			
		hiByte =  FontWord >> 8;		//Want to work with one byte at a time, here.
	
//Calculate mapping from fonts onto output pin ports:
				PB = (255U & FontWord);
				PD = ((hiByte & 15U) | ((hiByte & 48U) << 1));
				PA = ((hiByte & 192U) >> 6);	



	m = 0;

		
#ifdef multiplexed
	
	
while (m < MultiplexHoldTime)		// Display an individual letter
		{
		
		#ifdef commonanode

	
		n = 0;
		while (n < 8U){
			PORTB = ~(PB & (1U << n));
			shortdelay 
			PORTB = 255U;	
			n++;
			}
	
			n = 0;
		while (n < 7U){
	
			PORTD = ~(PD & (1U << n));	
			shortdelay 
			PORTD = 255U;
			n++;
			}	
	
			PORTA = ~(PA & (1U));
			shortdelay
			PORTA = 255U;	
			
			PORTA = ~(PA & (2U));
			shortdelay
			PORTA = 255U;	
	
		#else		// Common cathode version follows:

			n = 0;
		while (n < 8U){
	
			PORTB = PB & (1U << n);	
			shortdelay 
			PORTB = 0;	
			n++;
			}
	
			n = 0;
		while (n < 7U){
	
			PORTD = PD & (1U << n);	
			shortdelay
			PORTD = 0;	
			n++;
			}
	
			PORTA = PA & (1U);
			shortdelay
			PORTA = 0;	
			
			PORTA = PA & (2U);
			shortdelay
			PORTA = 0;	
		
		#endif

	
	
		m++;
	}

		
			
					
#else		
	// if not multiplexed, we write the letters *all at once*
		
		
	while (m < ParallelHoldTime)		// Display an individual letter
		{		
		

// Turn on the intended segments:		
#ifdef commonanode
			    PORTB = ~PB;
				PORTD = ~PD;
				PORTA = ~PA;		
#else
		        PORTB = PB;
				PORTD = PD;
				PORTA = PA;	
#endif	

			asm("nop");
			asm("nop");
			asm("nop");
			asm("nop");

//Turn off all of the segments, to save the batteries...
#ifdef commonanode
	PORTA = 255U;
	PORTB = 255U;
	PORTD =  255U;
	//PORTD |= _BV(PD4);	
#else
	PORTA = 0;
	PORTB = 0;
	PORTD = 0;
#endif	

			asm("nop");
			asm("nop");
			asm("nop");
			asm("nop");
	m++;
	
}

#endif			// end "if multiplexed" structure.

			
	//		i++; 
	}
	}	//End main loop


	return 0;

}
