/*

This software is released under a modified beer-ware license:
 * <dan@appliedcarbon.org> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a rootbeer in return.

( A rum and coke is also acceptable. :D )

-------------------------------------------------------------------------------------------------------------------------------------------------------
This project uses an ATtiny2313 (interal osc) to drive a Holtek HT1632C display driver IC.

This is the firmware for the 32x8display project on <appliedcarbon.org>.
This code implements a serial interface that allows an attached device to arbitrarily
change which segments/digits are on and off. There are also a number of 'convenience'
functions to make handling the display easier and faster.

KNOWN ISSUES:
	- The current code does not detect malformed instructions. Sending an instruction
		shorter than the perscribed length will result in undefined behavior.
-------------------------------------------------------------------------------------------------------------------------------------------------------

The plan is to have the 2313 use a different protocol depending on the status of the solder jumpers S0 through S3:
S3	S2	S1	S0	Function
-	-	-	-	Serial Connection, 2400 baud
-	B	-	-	Serial Connection, 9600 baud
B	-	-	-	Serial Connection, 57600 baud
B	B	-	-	Serial Connection, 115200 baud

X = don't care
- = pads clear/seperated
B = pads bridged
Mode is selected on power on; changing mode setting while running has no effect.

Note: At one point there were plans to implement an SPI and I2C interface, hence the low utilization of the settings jumpers.


Serial commands:
Command							Returns				Purpose
AT								OK					Verify functioning of device
V								<string>			Build number (version) of the firmware and other information

DC								OK					Clears display. (All segments off.)

DON								OK					Turns on display
DOF								OK					Turns off display (default state)

BON								OK					Enables blinking of display. Very annoying.
BOF								OK					Disables blinking of display. (default state)

PSzz							OK					Set the PWM brightness to the specified value, where 'zz' is a value (in ASCII) between 0 and 15 (zero padded, i.e. 00, 01, 02, 03)

DBSaaayyy						OK					Sets status of the segments of a given digit (aaa) to the specified pattern (yyy).
DBGaaa							yyy					Gets the status of the segments of a given digit on the display. Same data format as above.

DNSaaaw							OK					Displays the provided value (w) on the specified digit (aaa).


NOTE: Commands must be followed with a single newline ('\n', 0x0A) character. Return messages will be terminated with the same character.

'aaa' indicates a display digit between 0 and 255, as an ASCII integer. (Note: Display only has 32 digits; extra address space is provided for .)
'yyy' is the integer value of a byte whose bits map to seven segments and the decimal point. See table below for bit-segment mapping.
'w' is one of the following ASCII character: 0-9, A-F. 

Binary bit/segment lookup table:
BIT		SEGMENT		COMn PIN
0		G			COM0
1		C			COM1
2		DP			COM2
3		D			COM3
4		E			COM4
5		B			COM5
6		F			COM6
7		A			COM7

Note: The LED driver was wired for convenience rather than for lining up segment A with COM0, segment B with COM1, and so forth.

Digit/Displays are counted from left to right, top to bottom.
(e.g. Digit on upper left is digit zero, digit on bottom right is 31.)

How do the ROWn pins on the LED driver match up to the digits? I'm glad you asked:
ROWn	HIGH	LOW		DIGIT
0		0x01	0x00	4
1		0x03	0x02	5
2		0x05	0x04	6
3		0x07	0x06	7
4		0x09	0x08	22
5		0x0B	0x0A	2
6		0x0D	0x0C	1
7		0x0F	0x0E	0
8		0x11	0x10	3
9		0x13	0x12	16
10		0x15	0x14	20
11		0x17	0x16	21
12		0x19	0x18	17
13		0x1B	0x1A	18
14		0x1D	0x1C	19
15		0x1F	0x1E	23
16		0x21	0x20	27
17		0x23	0x22	31
18		0x25	0x24	30
19		0x27	0x26	29
20		0x29	0x28	28
21		0x2B	0x2A	24
22		0x2D	0x2C	25
23		0x2F	0x2E	26
24		0x31	0x30	11
25		0x33	0x32	15
26		0x35	0x34	14
27		0x37	0x36	13
28		0x39	0x38	12
29		0x3B	0x3A	10
30		0x3D	0x3C	9
31		0x3F	0x3E	8



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

The Holtek chip is attached via Port B. Specificly:
PB0: Chip Select (active low)
PB1: Read Data Clock (active low). RAM data is clocked out on a falling edge.
PB2: Write Data Clock. Data is latched into RAM on rising edge.
PB3: Data I/O bus

The display memory is 64x4 bits (since we're operating at 32 ROW by 8 COM in this case.) 
See page 5 of the data sheet for a diagram discribing the relation between memory location and ROW/COM.

In our display, COMn is used to sink (activate) a specific element of a 7+1 segment LED display.
ROWn is used to select which digit lights up.

Thus, pushing '1' (high) to all bit in RAM will light all digits on the display.

According to the data sheet, the basic command flow is thus:
 - Power on
 - SYS DIS - System Disable, turn off both system oscillator and LED duty cycle generator
 - COM Option - Choose either N-MOS or P-MOS, and either 8 or 16 common sinks.
 - Swtich to Master or Slave mode (controls where the clock signal comes from)
 - SYS EN - System Enable, turns on system oscillator
 - LED ON - Turn on LED duty cycle generator.
At this point the HT1632C is configured for use. This microcontroller may now read/write data from/to memory by issuing the approprate commands.
It should be noted that the HT1632 power on default is such that it is running in Master mode, with N-MOS open drain outputs with 8 common sinks, and as if SYS DIS had been run (system oscillator and LED duty cycle generators are off).
If these default are fine for your purpose, in theory all the use would need to do to start slinging data is run SYS EN and LED ON.

Data is clocked into the driver MSB first.

Display module Zero is attached to ROWs 7 to 4 inclusive (Digits 1 to 4 respectively).
Segments map as follows:
	A	COM7
	B	COM5
	C	COM1
	D	COM3
	E	COM4
	F	COM6
	G	COM0
	DP	COM2

So to display '8.' on Digit 1 on Display 0, we would need two write commands packed with 1's.
They would be send to the following RAM address: 0x0F, 0x0E
*/

//This define is needed by the delay function.
#define F_CPU 11059200	//11.0592 MHz

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/pgmspace.h>




//---------- SERIAL FUNCTIONS ---------------------------------------------------------------------------
/*Standard serial print function. Given a string, it prints it to the serial port.
Requires the serial port to be initialized. Will block while printing.
Strings must be null-terminated.*/
void uart_print (const char* string) {

	while (*string != 0) {	//While we're not looking at a null character...
		while ( (UCSRA & _BV(UDRE)) == 0 ) { ; }	//Block until the transmit buffer is free again...
		UDR = *string;		//Send traffic
		string++;			//Increment pointer
	}
}

//Given an unsigned value (up to 8 bits in length), it prints it as ASCII characters to the serial port.
void uart_u8print (uint8_t number) {
	/*Given number, take mod 10.
	Add 0x30 (offset for 0 in the ASCII table) to result, send to printer.
	Divide number by 10, round down, store in number.
	Repeat three times (256 numbers, or three digits).*/

	char outstring[3];
	uint8_t loopcount;

	//Note that I use the for loop variable as an index for the output string. Hence the strange number; strings are index from left to right (0 to 4 in this case).
	for (loopcount = 3; loopcount != 0; loopcount--) {
		outstring[loopcount-1] = (char) (number % 10) + 0x30;	//Mod 10, add 0x30, cast as char, load into string.
		number = (number - (number % 10))/10;	//I subtract the remainder before dividing by ten because I'm not sure how this compiler handles squeezing floats into ints.
	}

	//Print!
	for (loopcount = 0; loopcount != 3; loopcount++) {
		while ( (UCSRA & _BV(UDRE)) == 0 ) { ; }	//Block until the transmit buffer is free again...
		UDR = outstring[loopcount];	//Send traffic
	}

}

//Print a single 8-bit value to the serial port.
void uart_sendbyte (uint8_t data) {
		while ( (UCSRA & _BV(UDRE)) == 0 ) { ; }	//Block until the transmit buffer is free again...
		UDR = data;		//Send traffic
}

//Define some useful strings:
const char str_copybuild[] PROGMEM = "(c) appliedcarbon.org, 32x8display, rev 1, build 441\n\0";
//Why aren't more strings in flash? I'm running out of space both in flash and SRAM. This works so I'm not going to muck with it.

//----------------------------------------------------------------------------------




// DRIVER CHIP FUNCTIONS AND DATA ----------------------------------------------------------------------------------------------------------

// The idea here is the user can bitwise-OR these defines together (occationally with other numbers) to build vaild commands.

//14 bit commands
#define WRITEID	0x2800	//XX_101_0000000_0000

//12 bit commands
#define SYSDIS		0x0800	//XXXX_100_0000_0000_X
#define SYSEN		0x0802	//XXXX_100_0000_0001_X

#define LEDOFF		0x0804	//XXXX_100_0000_0010_X
#define LEDON		0x0806	//XXXX_100_0000_0011_X

#define BLINKOFF	0x0810	//XXXX_100_0000_1000_X
#define BLINKON		0x0812	//XXXX_100_0000_1001_X

#define SLAVEMODE	0x0820	//XXXX_100_0001_0XXX_X
#define RCMASTMODE	0x0830	//XXXX_100_0001_10XX_X
#define EXTMASTMODE	0x0838	//XXXX_100_0001_11XX_X

#define PWM1		0x0940	//XXXX_100_101X_0000_X
/*#define PWM2		0x0942	//XXXX_100_101X_0001_X
#define PWM3		0x0944	//XXXX_100_101X_0010_X
#define PWM4		0x0946	//XXXX_100_101X_0011_X
#define PWM5		0x0948	//XXXX_100_101X_0100_X
#define PWM6		0x094A	//XXXX_100_101X_0101_X
#define PWM7		0x094C	//XXXX_100_101X_0110_X
#define PWM8		0x094E	//XXXX_100_101X_0111_X
#define PWM9		0x0950	//XXXX_100_101X_1000_X
#define PWM10		0x0952	//XXXX_100_101X_1001_X
#define PWM11		0x0954	//XXXX_100_101X_1010_X
#define PWM12		0x0956	//XXXX_100_101X_1011_X
#define PWM13		0x0958	//XXXX_100_101X_1100_X
#define PWM14		0x095A	//XXXX_100_101X_1101_X
#define PWM15		0x095C	//XXXX_100_101X_1110_X*/
#define PWM16		0x095E	//XXXX_100_101X_1111_X

#define SETNMOS8COM	0x0840	//XXXX_100_0010_00XX_X
#define SETPMOS8COM	0x0850	//XXXX_100_0010_10XX_X

//10 bit commands
#define READID		0x0300	//110_0000000

//PORTB |= _BV(PB0);		//Set bit.
//PORTB &= ~_BV(PB0);		//Clear bit.


//We also define the digit look-up table. The index is the digit number from the users view (left-right, top-bottom), while the stored number is the high nibble address of the respective ROWn.
const uint8_t digitmap[32] = {0x0F, 0x0D, 0x0B, 0x11, 0x01, 0x03, 0x05, 0x07, 0x3F, 0x3D, 0x3B, 0x31, 0x39, 0x37, 0x35, 0x33, 0x13, 0x19, 0x1B, 0x1D, 0x15, 0x17, 0x09, 0x1F, 0x2B, 0x2D, 0x2F, 0x21, 0x29, 0x27, 0x25, 0x23};


/*The following is a look-up table for digits and selected letters. The byte contents of each element is the high/low nibble that should be pushed directly into driver memory to display that character.
The user interface accepts an ASCII character, so we first subtract 48 (0x30). Numerials are in order in memory, so this is quick look-up.
If the resulting value is greater than 57 (0x39) the value must represent a letter. Thus we sutract 17 (65, the start of the letters, minus 48, the end of the numbers in the ASCII table) from the value to get the lookup index.

These are the values and their associated bit patterns:
VALUE	BIT PATTERN		HEX
0 		0b11110101		0xF5
1		0b01000100		0x44
2		0b11011001		0xD9
3		0b01011101		0x5D
4		0b01101100		0x6C
5		0b00111101		0x3D
6		0b10111101		0xBD
7		0b01010100		0x54
8		0b11111101		0xFD
9		0b01111101		0x7D
A		0b11111100		0xFC
B		0b10101101		0xAD
C		0b10110001		0xB1
D		0b11001101		0xCD
E		0b10111001		0xB9
F		0b10111000		0xB8
*/
const uint8_t ascii_lookup[16] = {0xF5, 0x44, 0xD9, 0x5D, 0x6C, 0x3D, 0xBD, 0x54, 0xFD, 0x7D, 0xFC, 0xAD, 0xB1, 0xCD, 0xB9, 0xB8};
//Note: This array and digitmap array bove should probably be stored in flash, however we're almost out of flash space but still have some SRAM to work with

// Commands are either 12 or 14 bits long, thus we have 'send 12' and 'send 14' functions that take a 16 bit value and clock it out.*/
void send12(uint16_t data) {
	uint8_t count = 12;
	data = data << 4;	//Skip the first four bits

	while (count > 0) {
		PORTB &= ~_BV(PB2);		//Bring Write Clock line low.

		//Mask off all bits except the MSB and see what we get back
		if ((data & 0x8000) == 0) {
			//Okay, set the data line low.
			PORTB &= ~_BV(PB3);		//Deassert Data line
		} else {
			//Set data line high
			PORTB |= _BV(PB3);		//Assert Data line
		}

		count--;			//Count down
		PORTB |= _BV(PB2);	//Bring Write Clock line high.
		data = data << 1;	//Shift bits over for next go-around

		//And we're ready for the next bit
	}
}//End send12
void send14(uint16_t data) {

	uint8_t count = 14;
	data = data << 2;	//Skip the first two bits

	while (count > 0) {
		PORTB &= ~_BV(PB2);		//Bring Write Clock line low.

		//Mask off all bits except the MSB and see what we get back
		if ((data & 0x8000) == 0) {
			//Okay, set the data line low.
			PORTB &= ~_BV(PB3);		//Deassert Data line
		} else {
			//Set data line high
			PORTB |= _BV(PB3);		//Assert Data line
		}

		count--;			//Count down
		PORTB |= _BV(PB2);	//Bring Write Clock line high.
		data = data << 1;	//Shift bits over for next go-around

		//And we're ready for the next bit
	}
}//End send14

//Given an eight bit memory address (MA), grab the full byte (nibble from MA, nibble from MA+1) and return it
//The read command returns a nibble LSB first.
uint8_t readmem(uint8_t address) {
	uint8_t readdata = 0;
	uint8_t count = 10;

	uint16_t command = READID | (address & 0x7F);		//Note that only the bottom seven bits of the address are used by the driver chip
	command = command << 6;		//It's a 10 bit command in a 16 bit field; we need to shift it over.

	while (count > 0) {
		PORTB &= ~_BV(PB2);			//Bring Write Clock line low.

		//Mask off all bits except the MSB and see what we get back
		if ((command & 0x8000) == 0) {
			//Okay, set the data line low.
			PORTB &= ~_BV(PB3);		//Deassert Data line
		} else {
			//Set data line high
			PORTB |= _BV(PB3);		//Assert Data line
		}

		PORTB |= _BV(PB2);			//Bring Write Clock line high.
		//Data is clocked into the driver on the low-high transition.

		count--;					//Count down
		command = command << 1;		//Shift bits over for next go-around

		//And we're ready for the next bit
	}

	/*Once the memory address is clocked out, we need to run the Read Line clock for eight cycles,
	and sample the state of the Data Line during that time.
	In order to do that, however, we need to switch the Data Line to an input pin in the DDRB register.
	*/
	
	PORTB &= ~_BV(PB1);		//Bring Read Clock line low - not sure if I need to do this here.
	DDRB  &= ~_BV(PB3);		//Set DDRB Pin 3 (Data) to zero (input)
	PORTB &= ~_BV(PB3);		//Make sure the pull-ups are off.

	count = 8;		//One nibble at Address and another at Address+1

	while (count > 0) {

		PORTB &= ~_BV(PB1);		//Bring Read Clock line low.
		_delay_us(5);
		PORTB |= _BV(PB1);		//Bring Read Clock line high.		

		readdata = readdata >> 1;	//Shift value over for next cycle

		//Get status of bit 3 of PORTB, store it in the MSB of readdata
		readdata = readdata | ((PINB << 4) & 0x80);

		count--;				//Count down
	}

	DDRB |= _BV(PB3);			//Set DDRB Pin 3 (Data) to 1 (output mode)


	// -------------------------------------------------------------------------------------------------------------------------------------------------------
	/*KNOWN BUG/WORKAROUND:
	The HT1632C datasheet states that given address N, when put in read mode it'll read back the nibble from N, N+1, N+2 etc in the following format:
	(In this notation, the bit on the left is clocked out first, and the bit on the right is clocked out last.)
		N[0], N[1], N[2], N[3], N+1[0], N+1[1], N+1[2], N+1[3], N+2[0], N+2[1], N+2[2], N+2[3], and so forth.
	In practice, the data is clocked out in the following pattern:
		N[3], N[2], N[1], N[0], N+1[3], N+1[2], N+1[1], N+1[0], and so forth.
	Note the bit numbers. The nibble is mirrored.
	Due to my code above (which is built on the datasheet), the resulting location of the bits in the final/returned byte is as followed:
		N+1[0], N+1[1], N+1[2], N+1[3], N[0], N[1], N[2], N[3]
	For the sake of clarity, let's number each bit from 7 to 0, with 7 being the MSB and 0 being the LSB:
		4, 5, 6, 7, 0, 1, 2, 3
	The following code fixes this issue. I elected to make it a seperate block so that I can easily remove it in the future in case Holtek change how their chips work.*/

	uint8_t flippeddata = 0;
	uint8_t i;

	//Swap high and low nibble
	flippeddata = (readdata << 4) & 0xF0;
	flippeddata = flippeddata | ((readdata >> 4) & 0x0F);

	// At this point: 45670123 is now 01234567

	readdata = 0;

	//Now we want to mirror the byte
	for (i=0; i<8; i++) {
		readdata = readdata << 1;						//readdata is where our data is going to end up eventually
		readdata = readdata | (flippeddata & 0x01);		//Copy the LSB of flippeddata to readdata
		flippeddata = flippeddata >> 1;					//Shift flippeddata to the right, while the contents of readdata is shifted left on the next loop.
	}

	// At this point: 01234567 is now 76543210. Mirrored.
	// -------------------------------------------------------------------------------------------------------------------------------------------------------


	return readdata;
}//End readmem()



//Turn all segments off by pushing zero into driver memory.
void cleardisplay(void) {
	uint8_t row_address;
	uint16_t temp;
	for (row_address = 0; row_address < 0x40; row_address++) {

		temp = (row_address << 4) |
					WRITEID |			//XX_101_0000000_0000
					0x0000;				//XX_XXX_XXXXXXX_0000

		PORTB &= ~_BV(PB0);		//Bring CS line low
		send14(temp);
		PORTB |= _BV(PB0);		//Bring CS line back up.
	}
}//End cleardisplay()


//Give three characters, 0 thru 9, in ASCII, return the expressed value in a single byte
uint8_t decodeascii(char high, char med, char low) {
	//We can reconstruct the full value by converting each back into a value and then multiplying it by a power of ten:
	uint8_t tempbyte =
		(((uint8_t)high - 0x30) * 100) +
		(((uint8_t)med - 0x30) * 10) +
		((uint8_t)low - 0x30);

	return tempbyte;	
}

//-------------------------------------------------------------------------------------------------------







void xmitokay(void) {
	uart_sendbyte('O');
	uart_sendbyte('K');
	uart_sendbyte('\n');
}



//Interrupt vector to grab new data from the UART receiver
ISR (USART_RX_vect) {

	static char inbuff[10];
	static uint8_t inbuff_index = 0;
	char tempchar;

	//Used by code below as a scratch space when assembling driver chip commands.
	uint8_t tempbyte;
	uint8_t tempaddress;

	if (inbuff_index > 10) { inbuff_index = 0; }	//Overflow protection.
	//It's set at 19 so we still have space to put a null character in the buffer

	/*The plan here is to push data into a string until the string overflows or a newline character is detected.
	On an overflow, the index returns to 0. If the user has issues with random characters appearing on the serial
	line they are advised to begin their command with a newline character ('\n') which will clear the buffer.*/

	//UDR is the address for both the xmit and recv buffers. A write command will write data to the transmitter, while a read command will return data from the receiver.
	tempchar = UDR;		//BUG WARNING: Trying to load UDR into inbuff[inbuff_index], and probably any array, doesn't appear to always work. It'll look like it works, but the 'if' statements below won't work.

	inbuff[inbuff_index] = tempchar;	//Work around to the above mentioned problem.

	//Check to see if we just got a newline (line feed)
	if (tempchar == '\n') {

		inbuff[inbuff_index] = 0;		//Set a null character so string functions (if any) don't fail.
		inbuff_index = 0;				//Reset the index counter for the next round

		//AT - Is it just an attention command? That's easy to work with.
		if (inbuff[0] == 'A' && inbuff[1] == 'T') {
			xmitokay();
		}


		//V - Print version number
		else if (inbuff[0] == 'V') {
			//uart_print ("appliedcarbon.org, 32x8display, rev 1, build 347\n\0");

			tempaddress = 0;

			while ((tempbyte = pgm_read_byte_near(str_copybuild + tempaddress)) != 0) {
				while ( (UCSRA & _BV(UDRE)) == 0 ) { ; }	//Block until the transmit buffer is free again...
				UDR = tempbyte;								//Send traffic
				tempaddress++;								//Increment address counter
			}
		}


		//DC - Clear the display
		else if (inbuff[0] == 'D' && inbuff[1] == 'C') {
			cleardisplay();
			xmitokay();
		}


		//DON - Turn on the display
		else if (inbuff[0] == 'D' && inbuff[1] == 'O' && inbuff[2] == 'N') {
			PORTB &= ~_BV(PB0);
			send12(LEDON);
			PORTB |= _BV(PB0);

			xmitokay();
		}
		//DOF - Turn off the display
		else if (inbuff[0] == 'D' && inbuff[1] == 'O' && inbuff[2] == 'F') {
			PORTB &= ~_BV(PB0);
			send12(LEDOFF);
			PORTB |= _BV(PB0);

			xmitokay();
		}


		//BON - Turn on blinking
		else if (inbuff[0] == 'B' && inbuff[1] == 'O' && inbuff[2] == 'N') {
			PORTB &= ~_BV(PB0);
			send12(BLINKON);
			PORTB |= _BV(PB0);

			xmitokay();
		}
		//BOF - Turn off blinking
		else if (inbuff[0] == 'B' && inbuff[1] == 'O' && inbuff[2] == 'F') {
			PORTB &= ~_BV(PB0);
			send12(BLINKOFF);
			PORTB |= _BV(PB0);

			xmitokay();
		}


		//PSzz - Set the PWM brightness to the specified value, where 'zz' is a value (in ASCII) between 0 and 15 (zero padded, i.e. 00, 01, 02, 03)
		else if (inbuff[0] == 'P' && inbuff[1] == 'S') {

			//PWM command form: XXXX_100_101X_????_X
			//inbuff[2] and inbuff[3] contain a character between 0 and 9 (hopefully)
			
			tempbyte = decodeascii('0', inbuff[2], inbuff[3]) & 0x0F;

			//Note that the AND operation below limits the max value that can be expressed.
			PORTB &= ~_BV(PB0);
			send12(PWM1 | (tempbyte << 1));
			PORTB |= _BV(PB0);

			xmitokay();
		}


		//DBSaaayyy - Sets status of the segments of a given digit (aaa) to the specified pattern (yyy).
		else if (inbuff[0] == 'D' && inbuff[1] == 'B' && inbuff[2] == 'S') {
			/* inbuff[3:5] is a three character string that represents the address of the digit to manipulate
			inbuff[6:8] is a three character string that represents the data byte; raw binary to push into the display driver*/

			//Calculate and map the base memory address for the resulting value
			tempaddress = digitmap[decodeascii(inbuff[3], inbuff[4], inbuff[5])];

			//Decode the data byte
			tempbyte = decodeascii(inbuff[6], inbuff[7], inbuff[8]);

			//High nibble
			PORTB &= ~_BV(PB0);					//Select/enable LED driver
			send14(WRITEID |					//XX_101_0000000_0000	Command
				(tempaddress << 4) |			//XX_XXX_???????_XXXX	Address
				((tempbyte >> 4) & 0x0F)		//XX_XXX_XXXXXXX_????	Data
			);
			PORTB |= _BV(PB0);					//Deselect LED driver

			//Low nibble
			PORTB &= ~_BV(PB0);
			send14(WRITEID |					//XX_101_0000000_0000
				((tempaddress-1) << 4) |		//XX_XXX_???????_XXXX
				(tempbyte & 0x0F)				//XX_XXX_XXXXXXX_????
			);
			PORTB |= _BV(PB0);
			_delay_us(5);

			xmitokay();
		}
		//DBGaaa - Gets the status of the segments of a given digit on the display. Same data format as above.
		else if (inbuff[0] == 'D' && inbuff[1] == 'B' && inbuff[2] == 'G') {
			//We need to calculate the base address, grab both nibbles, add them together, and then convert them to ASCII.

			/*The sequential read command for the driver chip starts at a low address and goes up (i.e. 0x00, 0x01, 0x02, 0x03...)
			However our digimap array lists the high addresses only. So, load the memory address and then subtract one.
			readmem() reads from the given address and address+1 and returns both in a full byte.*/
			PORTB &= ~_BV(PB0);	//Chip select!
			tempbyte = readmem(digitmap[decodeascii(inbuff[3], inbuff[4], inbuff[5])] - 1);
			PORTB |= _BV(PB0);

			//Now we need to convert into ASCII - a number between 0 and 255. Thankfully, I have a function that does that.
			uart_u8print(tempbyte);
			uart_sendbyte('\n');
			
		}



		//DNSaaaw - Displays the provided value (w) on the specified digit (aaa).
		else if (inbuff[0] == 'D' && inbuff[1] == 'N' && inbuff[2] == 'S') {
			/*inbuff[3:5] is a three character string that represents the address of the digit to manipulate
			inbuff[6] is one of the following characters (in ASCII): 0123456789ABCDEF*/

			tempaddress = digitmap[decodeascii(inbuff[3], inbuff[4], inbuff[5])];

			if (inbuff[6] < 0x3A) { tempbyte = inbuff[6] - 0x30; }		//Is it a number? Convert it from ASCII to a value.
			else {
				//Okay, this is a bit odd. 'A' is at 0x41 but we want it to represent 10. So 0x41 - 0x0A = 0x37
				tempbyte = inbuff[6] - 0x37;
			}
			tempbyte = ascii_lookup[tempbyte];

			//High nibble
			PORTB &= ~_BV(PB0);				//Select/enable LED driver
			send14(WRITEID |				//XX_101_0000000_0000	Command
				(tempaddress << 4) |		//XX_XXX_???????_XXXX	Address
				((tempbyte >> 4) & 0x0F)	//XX_XXX_XXXXXXX_????	Data
			);
			PORTB |= _BV(PB0);				//Deselect LED driver

			//Low nibble
			PORTB &= ~_BV(PB0);
			send14(WRITEID |
				((tempaddress-1) << 4) |
				(tempbyte & 0x0F)
			);
			PORTB |= _BV(PB0);
			_delay_us(5);

			xmitokay();
		}
		//DNGaaa - Returns the value of a given digit on the display. Same data format as above.
		//NOTE: This function was removed so the compiled firmware would fit on the ATTiny 2313A
/*		else if (inbuff[0] == 'D' && inbuff[1] == 'N' && inbuff[2] == 'G') {
			//We need to calculate the base address, grab both nibbles, add them together, and then convert them to ASCII.

			//The sequential read command for the driver chip starts at a low address and goes up (i.e. 0x00, 0x01, 0x02, 0x03...)
			//However our digimap array lists the high addresses only. So, load the memory address and then subtract one.
			//readmem() reads from the given address and address+1 and returns both in a full byte.
			PORTB &= ~_BV(PB0);	//Chip select!
			tempbyte = readmem(digitmap[decodeascii(inbuff[3], inbuff[4], inbuff[5])] - 1);
			PORTB |= _BV(PB0);

			// Okay, we know what the segment pattern is - we now need to look up that pattern in the ascii_lookup table.

			uint8_t i;
			tempaddress = 127;	//There are only 16 elements in the ascii_lookup table; if tempaddress is still equal to 127 when the for loop is done then we know we didn't find anything.

			//Loop through those 16 elements and see if any match the provided pattern
			for (i=0; i<16; i++) {
				if (ascii_lookup[i] == tempbyte) { tempaddress = i; break; }
			}

			if (tempaddress == 127) {
				// We did not find a match. Provide the user with notice of this fact.
				uart_sendbyte('?');
				uart_sendbyte('\n');

			} else {
				// Oh hey, we found a matching pattern! We need to calculate the ASCII code for that character now.

				if (tempaddress < 10) {
					//Just add 0x30 to it and cast as a character.
					uart_sendbyte((char)tempaddress + 0x30);
					uart_sendbyte('\n');
				} else {
					//If it is over 9, it is in the range of letters, so we add (0x41-0x0A) = 0x37
					uart_sendbyte((char)tempaddress + 0x37);
					uart_sendbyte('\n');
				}
			}
		}*/

	} else {
		inbuff_index++;		//Wasn't a newline reset? Cool, increment that counter.
	}

}


int main (void)
{
	//Port B setup --------------------------------------------------------------
	//CS, WR, RD, and DATA all idle HIGH
	PORTB = 0x0F;
	/*Writing a 1 to the approprate bit in the DDR register sets that pin as an output. (Rest of the register defaults to 0's thus leaving these pins in input mode (tri-state/hi-z).)
	Writing a 0 to the PORT register will indicate 'Output Low' - it will sink current rather than source.*/
	DDRB = 0x0F;		//bXXXX1111


	//Port D setup --------------------------------------------------------------
	//Set PD2 thru PD5 to input and enable the internal pull-up resistors; these are the setting jumpers.
	PORTD = 0x3C;	//0b0011 1100
	DDRD  = 0x3C;

	//Mask off the extra bits, move it over to make it easier to use, and store for later reference.
	uint8_t func_jumpers = (PIND & 0x3C) >> 2;
	//Remember! Pins are HIGH if they are not bridged!





	//Setup serial port ---------------------------------------------------------------------

	/*Set baud rate.
	The crystal frequency was carefully selected to cover a wide range of popular serial baud rates.
	The display supports 2400, 9600 57600, and 115200 baud.*/


	//2400 baud; UBRR = 287 (0x011F)
	if (func_jumpers == 0x0F) {
		UBRRH = 0x01;
		UBRRL = 0x1F;
	}

	//9600 baud; UBRR = 71
	else if (func_jumpers == 0x0E) {

		/*As an example:
		Let's configure the UART to operate at the 9600 rate, 8 data bits, no parity bit, one stop bit (8N1), no flow control.
		We're going to use Asynchronous Normal Mode (U2X = 0), and thus our UBRR register can be calculated with the following:
			UBBR = (Fosc / (16*buad))-1
			UBBR = (11059200 / (16*9600))-1
			UBBR = 71
		*/
		UBRRH = 0;
		UBRRL = 71;
	}

	//57600 baud; UBRR = 11 
	else if (func_jumpers == 0x0D) {
		UBRRH = 0;
		UBRRL = 11;
	}

	//115200 baud; UBRR = 5
	else if (func_jumpers == 0x0B) {
		UBRRH = 0;
		UBRRL = 5;
	}


	//UCSRA, U2X defaults to zero so no action is needed on our part.

	//Enable the RX Complete Interrupt, Enable receiver, Enable transmitter
	UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);

	/*Set frame format: 8 data, 1 stop bit, no parity
	No need to fiddle with UMSEL bits; starts in async mode.
	Same with UPM bits; defaults to no parity bits.
	USBS controls stop bits and defaults to 0 (1 stop bit).
	Finally, set the character size to 8 bit (0b011).*/
	UCSRC = (3<<UCSZ0);
	




	//LED driver setup --------------------------------------------------
	//Some of these delays may not be strictly needed, however the following code works so I'm not inclined to mess with it.


	//Perform a reset
	PORTB &= ~_BV(PB0);		//Bring CS line low
	_delay_us(10);			//Wait 10 microseconds
	PORTB |= _BV(PB0);		//Bring CS line back up.

	_delay_us(5);

	//Send System Disable command
	PORTB &= ~_BV(PB0);		//Bring CS line low
	send12(SYSDIS);
	PORTB |= _BV(PB0);		//Bring CS line back up.

	_delay_us(5);

	//Explicitly set the MOS drivers and output mode
	PORTB &= ~_BV(PB0);		//Bring CS line low
	send12(SETNMOS8COM);
	PORTB |= _BV(PB0);		//Bring CS line back up.

	_delay_us(5);

	//Set master mode, internal clock source
	PORTB &= ~_BV(PB0);
	send12(RCMASTMODE);
	PORTB |= _BV(PB0);

	_delay_us(5);

	//Enable system
	PORTB &= ~_BV(PB0);
	send12(SYSEN);
	PORTB |= _BV(PB0);

	_delay_us(5);


	//Turn off LEDs
	PORTB &= ~_BV(PB0);
	send12(LEDOFF);
	PORTB |= _BV(PB0);

	//REMEMBER! Turn on display when you reboot.

	_delay_us(5);

	//Set LED PWM to full
	PORTB &= ~_BV(PB0);
	send12(PWM16);
	PORTB |= _BV(PB0);

	_delay_us(5);


	//Make sure all the segments are turned off
	cleardisplay();


	// ------------------------------------------------------------------


	// And we're set!

	


	sei ();	//Enables interrupts by setting the global interrupt mask.

	for (;;)
		sleep_mode();	//Puts uC to sleep until the next interrupt to save power.

    return (0);
}

