/*
               Microcontrollers NOT ALLOWED
      Or how to subtly troll your college instructor)
  
  Firmware Revision A - Written by Ahmad@UltraKeet.com.au
  
  This firmware is designed to simulate most features of a
  74LS47 display driver on a PIC16F1503, and displays some
  pre-defined strings when the firmware is left idle.
  
  It's far from optimal, written late, don't judge me, etc.
  
  Please see:
  ultrakeet.com.au/write-ups/microcontrollers-not-allowed
  for a full write-up and functional description.
  
  Compile with the exceptionally shitty MikroC for PICs
  demo software, available from MikroE.com.
  
  Real men use HitechC or XC8.
  
  Configuration (all other bits default):
  
   --> Disable WDT
   --> User internal FRC
   --> MCLR disabled
     
*/

// Define our segment connections
#define SEGA    LATC0_bit
#define SEGB    LATC1_bit
#define SEGC    LATC2_bit
#define SEGD    LATC4_bit
#define SEGE    LATC3_bit
#define SEGF    LATA1_bit
#define SEGG    LATA2_bit

// Define our BCD input connections
#define INA     RC5_bit
#define INB     RA5_bit
#define INC     RA4_bit
#define IND     RA3_bit

// Store our current and last BCD values
unsigned int inputValue=0;
unsigned int oldValue=0;

// Generic counter variable used for all sorts of shit.
unsigned int x=0;

// The words we'll display when the firmware is idle
// 255 (0xFF) is a blank character with no segments displayed
unsigned char words[5][8]={
    {'H','E','L','L','O',255},
    {'P','E','N','I','S',255},
    {'A','S','S',255},
    {'B','A','L','L','S',255},
    {'P','O','O','P',255}
};

// Store our word lengths
unsigned char wordCount[5]={5,5,3,5,4};

// Stores our word offset
unsigned char helloIndex=0;

// stores our character offset
unsigned char wordIndex=0;

// display our words
void doHello(void);

// display our characters (called by doHello)
void displayStuff(unsigned int displayDigit);

// stores our idle delay count (reset when active)
unsigned long delayCount=0;

// stores our inter-character delay
unsigned int helloDelay=0;


void main() {

   // Disable all ADC pins (all to digital)
   ANSELA=0x0000;

   // Setup our segment tristates
   TRISC0_bit=0x00; // SEG.A
   TRISC1_bit=0x00; // SEG.B
   TRISC2_bit=0x00; // SEG.C
   TRISC4_bit=0x00; // SEG.D
   TRISC3_bit=0x00; // SEG.E
   TRISA1_bit=0x00; // SEG.F
   TRISA2_bit=0x00; // SEG.G
   
   // Setup our BCD tristates
   TRISC5_bit=0x01; // BCD1
   TRISA5_bit=0x01; // BCD2
   TRISA4_bit=0x01; // BCD3
   TRISA3_bit=0x01; // BCD4
   
   // Setup our timer, interrupts and pullups
   TMR2IE_bit=1;	// Enable timer2 interrupts
   IOCIE_bit=0;		// Disable interrupt-on-change (IOC) interrupts
   IOCAN3_bit=0;	// Disable IOC on AN3
   PEIE_bit=1;		// Enable peripheral interrupts
   GIE_bit=1;		// Enable global interrupts
   PR2=0x06;		// Set our Timer2 period
   T2CON=0x07;		// Set our Timer2 paramaters

   OSCCON=0x78;		// Setup our oscillator
   
   C1ON_bit=0;		// Disable onboard comparators
   C1OE_bit=0;

   WPUA3_bit=1;		// Enable weak pull-ups on A3 ~ A5
   WPUA4_bit=1;
   WPUA5_bit=1;
   
   // Initialize our idle delay variable
   delayCount=200000;
   
   do{
		// Read our BCD pins
		inputValue=(8 * IND) + (4 * INC) + (2 * INB) + INA;

		// Check if our BCD value has changed, and if so, display the value
		// Also reset our idle timer
		if(inputValue != oldValue){
		  displayStuff(inputValue);
		  delayCount=200000;
		// Otherwise if our idle timer hits zero, start displaying our words
		} else if(!delayCount){
			doHello();
		} else {
		// Otherwise display whatever our BCD value is anyway
			displayStuff(inputValue);
		}
		// Store the last input value as old value
		oldValue=inputValue;
	   
	   // loop forever
   } while(1);
}

// Displays our words when the idle timer reaches zero 
// I initially intended the firmware to just display "Hello"
// Which is why the variable names are fucked up.

void doHello(void){
	// Blank the display
    displayStuff(255);
	// Delay for 500mS
    helloDelay=500;
    while(helloDelay);
	// Display our word
    displayStuff(words[wordIndex][helloIndex]);
	// Delay 2000ms
    helloDelay=2000;
    while(helloDelay);
	// Blank the display
    displayStuff(255);
	// Delay 500ms
    helloDelay=500;
    while(helloDelay);
	// Check our word index, increment to the next word, check for bounds errors
	// When done, display the current BCD value. Also a nasty redundant hack.
    if(helloIndex<wordCount[wordIndex]){
        helloIndex++;
    } else {
        if(wordIndex<4){
            wordIndex++;
            helloIndex=0;
            delayCount=50000;
            inputValue=(8 * IND) + (4 * INC) + (2 * INB) + INA;
            displayStuff(inputValue);
        } else {
            wordIndex=0;
            helloIndex=0;
            delayCount=50000;
            inputValue=(8 * IND) + (4 * INC) + (2 * INB) + INA;
            displayStuff(inputValue);
        }
    }
}

// Displays our character on the display, active low
// Essentially a lookup table for segments to characters

void displayStuff(unsigned int displayDigit){

    switch(displayDigit){
        case 0:
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=1;
        break;

        case 1:
           SEGA=1;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=1;
           SEGF=1;
           SEGG=1;
        break;

        case 2:
           SEGA=0;
           SEGB=0;
           SEGC=1;
           SEGD=0;
           SEGE=0;
           SEGF=1;
           SEGG=0;
        break;

        case 3:
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=1;
           SEGF=1;
           SEGG=0;
        break;

        case 4:
           SEGA=1;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=1;
           SEGF=0;
           SEGG=0;
        break;

        case 5:
           SEGA=0;
           SEGB=1;
           SEGC=0;
           SEGD=0;
           SEGE=1;
           SEGF=0;
           SEGG=0;
        break;

        case 6:
           SEGA=0;
           SEGB=1;
           SEGC=0;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;

        case 7:
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=1;
           SEGF=0;
           SEGG=1;
        break;

        case 8:
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;

        case 9:
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=1;
           SEGF=0;
           SEGG=0;
        break;
        
        case 'H':
           SEGA=1;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;
        
        case 'E':
           SEGA=0;
           SEGB=1;
           SEGC=1;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;
        
        case 'L':
           SEGA=1;
           SEGB=1;
           SEGC=1;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=1;
        break;

        case 'O':
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=1;
        break;
        
        case 255:
           SEGA=1;
           SEGB=1;
           SEGC=1;
           SEGD=1;
           SEGE=1;
           SEGF=1;
           SEGG=1;
        break;

        case 'P':
           SEGA=0;
           SEGB=0;
           SEGC=1;
           SEGD=1;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;

        case 'N':
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=0;
           SEGF=0;
           SEGG=1;
        break;

        case 'I':
           SEGA=1;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=1;
           SEGF=1;
           SEGG=1;
        break;

        case 'S':
           SEGA=0;
           SEGB=1;
           SEGC=0;
           SEGD=0;
           SEGE=1;
           SEGF=0;
           SEGG=0;
        break;

        case 'A':
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=1;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;

        case 'B':
           SEGA=0;
           SEGB=0;
           SEGC=0;
           SEGD=0;
           SEGE=0;
           SEGF=0;
           SEGG=0;
        break;
        
        default:
           SEGA=1;
           SEGB=1;
           SEGC=1;
           SEGD=1;
           SEGE=1;
           SEGF=1;
           SEGG=0;
        break;
    }
}

// Our Interrupt Service Routines

void ISR() iv 0x0004 ics ICS_AUTO {
	 
	 // First our timer ISR
     if(TMR2IF_bit){
		// If our counter variables are greater than zero, decrement them
        if(delayCount>0) delayCount--;
        if(helloDelay>0) helloDelay--;
		// Clear the interrupt flag
        TMR2IF_bit=0;
     }
     
	 // Now our IOC interrupt (no longer used)
     if(IOCIF_bit || IOCAF){
        IOCIF_bit=0;
        IOCAF=0;
     }
}