/* LED driver source.
	Copyright (c) 2004-2005 by Timothy J. Weber.
	For BoostC.
	See LedDriver.txt for module documentation.
*/

#include <system.h>
#include <eeprom.h>

typedef unsigned char byte;

#include "LedDriver.h"


#pragma DATA _CONFIG, _BODEN_OFF & _BOREN_OFF & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_OFF & _INTOSC_OSC_NOCLKOUT

// Table of ASCII values, in segment bits.
#pragma DATA 0x2100, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 
#pragma DATA 0x2110, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 
#pragma DATA 0x2120, 0x00, 0x06, 0x22, 0x7E, 0x6D, 0x76, 0x46, 0x02, 0x06, 0x30, 0x63, 0x46, 0x0C, 0x40, 0x80, 0x52 
#pragma DATA 0x2130, 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x41, 0x44, 0x06, 0x48, 0x30, 0x53 
#pragma DATA 0x2140, 0x7F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71, 0x7D, 0x76, 0x06, 0x0E, 0x76, 0x38, 0xD4, 0x54, 0x3F 
#pragma DATA 0x2150, 0x73, 0xBF, 0x50, 0x6D, 0x78, 0x3E, 0x3E, 0x3E, 0x76, 0x72, 0x5B, 0x39, 0x30, 0x0F, 0x01, 0x08 
#pragma DATA 0x2160, 0x20, 0x5F, 0x7C, 0x58, 0x5E, 0x7B, 0x71, 0x6F, 0x74, 0x04, 0x0E, 0x76, 0x06, 0xD4, 0x54, 0x5C 
#pragma DATA 0x2170, 0x73, 0x67, 0x50, 0x6D, 0x78, 0x1C, 0x1C, 0x9C, 0x76, 0x6E, 0x5B, 0x39, 0x06, 0x0F, 0x01, 0x00 


// The device ID is stored at position 127.
// This overwrites the segment code for DEL, but who cares?
#define DEVICE_ID_ADDRESS  127


#define MAX_MODULES  6  // Limited by the available pins.
#define NUM_BANKS  2  // Banks of LED modules: The regular bank and the 'flash' bank
#define BANK_LENGTH  16  // The length of one bank of modules.  Internal use, != the number of modules used.
#define FLASH_BANK  1  // The bank used for the "flashing" content.

// Module polarity input
// High = common-cathode; low = common-anode.
#define MP_PIN  5
#define MP_MASK  (1 << MP_PIN)

bit MP@PORTA.MP_PIN;

// Globals

byte rawBytesReceived;

enum CommandMode { Untargeted, IgnoringRaw, Targeted, RawSegment, RawHex, RawAscii }; 
byte commandMode;

byte displayBuffer[NUM_BANKS][BANK_LENGTH];

byte activeModules;

byte cursorIndex;
byte cursorBank;

byte displayBank;
byte displayModule;


void PutSegment(byte b)
{
	byte bank, i;
	
	if (activeModules > 0 && cursorIndex >= activeModules) {
		// Shift the display contents left one position.
		for (bank = 0; bank < NUM_BANKS; bank++) 
			for (i = 0; i < activeModules; i++) 
				displayBuffer[bank][i] = displayBuffer[bank][i + 1];

		// Move back to write to the last position.		
		--cursorIndex;
	}
	
	displayBuffer[cursorBank][cursorIndex] = b;
	displayBuffer[FLASH_BANK][cursorIndex] = b;
	
	if (activeModules > 0)
		cursorIndex++;
}

inline void PutAsciiChar(char c)
{
	PutSegment(read_eeprom(c));
}

void PutNibble(byte n)
{
	if (n <= 9)
		PutAsciiChar('0' + n);
	else
		PutAsciiChar('A' + n - 0xA);
}

void Do_CLEAR()
{
	byte bank, i;
	
	// Clear the display buffer.
	for (bank = 0; bank < NUM_BANKS; bank++)
		for (i = 0; i < MAX_MODULES; i++)
			displayBuffer[bank][i] = 0;
			
	// Reset the cursor.
	cursorIndex = 0;
	cursorBank = 0;
}

void Do_RESET_FLASH()
{
	tmr1h = 0;
	tmr1l = 0;
	pir1.TMR1IF = 0;
	displayBank = 0;
}

void ProcessBusInput(byte data)
{
	byte doFlash, flashIndex;
	
	if (commandMode == IgnoringRaw) {
		// Nothing other than 0xFF is interesting.
		rawBytesReceived++;
		if (rawBytesReceived > 0 && data == 0xFF)
			// That pops us out.
			commandMode = Untargeted;
	}
	
	else if (commandMode == RawSegment) {
		if (rawBytesReceived++ > 0 && data == 0xFF)
			commandMode = Targeted;
		else
			PutSegment(data);
	}
	
	else if (commandMode == RawHex) {
		if (rawBytesReceived++ > 0 && data == 0xFF)
			commandMode = Targeted;
		else {
			PutNibble((data & 0xF0) >> 4);
			PutNibble(data & 0x0F);
		}
	}
	
	else if (commandMode == RawAscii) {
		if (rawBytesReceived++ > 0 && data == 0xFF)
			commandMode = Targeted;
		else {
			// Flash this module if the high bit is set.
			// But, do it AFTER we've done whatever we would normally have done.
			doFlash = data & 0x80;
			data = data & 0x7F;
			flashIndex = cursorIndex;
				
			switch (data) {
			case '\b':
				if (cursorIndex > 0)
					--cursorIndex;
				break;
			case '\r':
				cursorIndex = 0;
				break;
			case '\n':
				Do_CLEAR();
				break;
			case 0x10:  // DLE = Bank 0
				cursorBank = 0;
				break;
			case 0x11:  // DC2 = Bank 1
				cursorBank = 1;
				break;	
            case 0x16:  // SYN = Reset flash
                Do_RESET_FLASH();
                break;
				
			case '.':
				if ((cursorIndex > 0) && !(displayBuffer[cursorBank][cursorIndex - 1] & 0x80)) {
					// The decimal point is off in the previous module.
					// Just turn it on, and don't do anything else.
					displayBuffer[cursorBank][cursorIndex - 1] |= 0x80;
					if (!doFlash)
						displayBuffer[FLASH_BANK][cursorIndex - 1] |= 0x80;
					break;
				}
				// Otherwise, fall through to the default case.
			
			default:
				PutAsciiChar(data & 0x7F);
				
				// Now, overwrite anything that went into the flash bank.
				if (doFlash)
					displayBuffer[1][flashIndex] = 0;
			}
		}
	} 
		
	else if (data & 0x80) {
		// Global bus commands.
		switch(data & 0b11100000) {
		case BUS_TARGET:
			if (data == 0x9F  // "TARGET ALL"
				|| (data & 0b00011111) == read_eeprom(DEVICE_ID_ADDRESS))
				commandMode = Targeted;
			else
				commandMode = Untargeted;
			break;
		
		case BUS_SETID:
			write_eeprom(DEVICE_ID_ADDRESS, data & 0b00011111);
			break;
		
		case BUS_RAW:
			if (commandMode == Targeted) {
				switch(data & 0x03) {
				case LED_RAW_HEX: 
					commandMode = RawHex;
					break;
				case LED_RAW_ASCII:
					commandMode = RawAscii;
					break;
				default:
					commandMode = RawSegment;
					break;
				}
			} else
				// commandMode must be Untargeted
				commandMode = IgnoringRaw;
				
			rawBytesReceived = 0;
			break;				
		}
	} 
	
	else if (data == LED_RESET) {
		activeModules = MAX_MODULES;
		Do_CLEAR();
	} 
	
	else if (data == LED_CLEAR)
		Do_CLEAR();
		
	else if (data == LED_FLASH_RESET)
        Do_RESET_FLASH();

	else if ((data & 0b11111110) == LED_BANK)
		cursorBank = data & 1;
	
	else switch (data & 0b11110000) {
	case LED_MODULES:
		activeModules = data & 0x0F;
		break;
	case LED_NIBBLE:
		PutNibble(data & 0x0F);
		break;
	case LED_FLASH:
		displayBuffer[FLASH_BANK][data & 0x0F] = 0;
		break;
	case LED_CURSOR:
		cursorIndex = data & 0x0F;
		break;
	} 
}

void Refresh()
{
	byte SS, SA, SB, MS, MA, MB;  // the segment and module lines, aligned with ports A and B
	bit temp;
	
	// Move to the next display.
	if (++displayModule >= activeModules)
		displayModule = 0;
	
	// Set up to output to that module.
	
	// Get the activated segment lines for this module.
	SS = displayBuffer[displayBank][displayModule];
	
	// PORTA has:
	// 11xx 1111
	// 11         segments G and F
    //              = SS & 01100000
	//   x        module polarity, e.g., common cathode
	//    x       module select 0
	//      1111  segments DCBA
    //              = SS & 00001111
	SA = ((SS & 0x60) << 1)
        | (SS & 0x0F);
	
	// PORTB has:
	// xxxx x1x1
	// xxxx x     module select 5-1
	//       1    segment dp
	//              = SS & 10000000
	//        x   data input
	//         1  segment E
	//              = SS & 00010000
    SB = ((SS & 0x80) >> 5)
        | ((SS & 0x10) >> 4);
    
    // Get the module select line for this module.
    MS = 1 << displayModule;

	// PORTA has:
	// xxx0 xxxx
	// xx         segments G and F
	//   x        module polarity, e.g., common cathode
	//    0       module select 0
    //              = MS & 00000001
	//      xxxx  segments DCBA
    MA = ((MS & 0x01) << 4);

	// PORTB has:
	// 0000 0xxx
	// 0000 0     module select 5-1
	//              = MS & 00111110
	//       x    segment dp
	//        x   data input
	//         x  segment E
	MB = ((MS & 0x3E) << 2);
	
	// Complement as needed for module polarity.
	if (MP) {
        // Common-cathode.
        MA = (~MA) & 0x10;
        MB = (~MB) & 0xF8;
    } else {
        // Common-anode.
        SA = (~SA) & 0xCF;
        SB = (~SB) & 0x05;
    }

	// Set the ports.
	porta = SA | MA;
	portb = SB | MB;
}

inline void SwapBanks()
{
	displayBank ^= 1;
}

void main()
{
// Initialization

	// Disable unused peripherals.
	cmcon = 0x07;  // voltage comparator off

	// Set up timer 0 as a LED service interrupt.
	// Our target refresh rate is at least 100 Hz for each module.
	// Since each module is off 8/9ths of the time for the maximum number of modules,
	// we need at least a 900 Hz refresh rate overall.
	option_reg = 0b11010001;  // Timer 0 internal mode, prescaled by 4.
		// This will produce a timer interrupt every 256 * 4 instructions,
		// or at 977 Hz with a clock speed of 4 MHz.  (4,000,000 / 4 / 256 / 4)

	intcon.T0IE = 1;  // Enable interrupts.

	// Set up timer 1 as a bank-switching interrupt (for flashing).
	// Our target bank-switch rate is 2 Hz.
	// We can't get that low with timer 0, but we can with timer 1, so use it separately.
	tmr1h = 0;
	tmr1l = 0;
	t1con = 0b00110001;  // Prescale by 1:8, turn timer on.
		// This will produce a timer interrupt every 65536 * 8 instructions,
		// or at 1.9 Hz with a clock speed of 4 MHz.  (4,000,000 / 4 / 65536 / 8)
		// It's used for flashing.
	pie1.TMR1IE = 1;  // Enable interrupts.
		
    // Set up the output lines.
    trisa = MP_MASK;  // just bit 5 is input, as it always is.
    trisb = 1 << 1;  // bit 1 is DI for the serial port.
    
    // Initialize to all segments on all modules as a minimal POST
    // while waiting for the bus.
    porta = 0xCF;
    portb = 0x05;

	delay_ms(250);

	// Start up the serial port.
	txsta.BRGH = 1;  // Set baud rate to...
	spbrg = 25;  // 9600 baud, in case of asynchronous mode.
	rcsta.CREN = 1;  // Enable continuous receive.
	rcsta.SPEN = 1;  // Enable the serial port.
	pie1.RCIE = 1;  // Enable interrupts.

	// Initialize globals
	commandMode = Untargeted;
	activeModules = MAX_MODULES;
	cursorIndex = 0;
	cursorBank = 0;
	displayBank = 0;
	displayModule = 0;
	
	int i, j;
	for (i = 0; i < 2; i++)
		for (j = 0; j < 16; j++)
			displayBuffer[i][j] = 0xFF;
	
	#ifdef DEBUG
	displayBuffer[0][0] = read_eeprom('L');
	displayBuffer[0][1] = read_eeprom('E');
	displayBuffer[0][2] = read_eeprom('D');
	displayBuffer[0][3] = read_eeprom(' ');
	displayBuffer[0][4] = read_eeprom('U');
	displayBuffer[0][5] = read_eeprom('P');
	#endif
	
	// Activate interrupts.
	intcon.PEIE = 1;
	intcon.GIE = 1;

	// Wait for interrupts.
	while (1) {
    }
}

void interrupt()
{
    // Route commands to the command processor.
	if (pir1.RCIF) {
		#if 1
		ProcessBusInput(rcreg);
		#else
		// Diagnostic mode - just prints raw bytes received.
		byte data = rcreg;
		PutNibble((data & 0xF0) >> 4);
		PutNibble(data & 0x0F);
		#endif
	}

   	// Route timer 0 interrupts to the refresh routine.
	if (intcon.T0IF) {
		Refresh();

		intcon.T0IF = 0;  // Clear the interrupt flag.
	}

	// Route timer 1 interrupts to the flashing routine.
	if (pir1.TMR1IF) {
		SwapBanks();

		pir1.TMR1IF = 0;  // Clear the interrupt flag.
	}
}

