/*
2420823 for ACDB5002. 2026AD
Switch based control for T-Junction traffic lights.
Based on PIC16F877A, using 20MHz clock.
Pin RB0 used as pedestrian interrupt button.
Timer1 interrupt used for timing, totalling 1s actions.
*/

//--------------------------------------------Setup---------------------------*/
#include <16F877A.h>
#fuses HS, NOWDT, NOLVP       
#use delay(clock = 20000000)  //20MHz

//--------------------------------------------Pin Definitions-----------------*/
// Inputs
//Update tris at start of main when changing inputs.
#define Ped_Input    PIN_B0

// Outputs
// Port A - Pedestrians
#define Ped_Red      PIN_A0
#define Ped_Green    PIN_A1
#define Ped_Pushed   PIN_B7 //Using B7, avoids analogue pins, reducing setupcode.

// Port B - East / West
#define East_Red     PIN_B1
#define East_Amber   PIN_B2
#define East_Green   PIN_B3
#define West_Red     PIN_B4
#define West_Amber   PIN_B5
#define West_Green   PIN_B6
// Pin B7 used in pedestrian lights

// Port C - 7-Segment Tens
#define A1_7Seg      PIN_C0
#define B1_7Seg      PIN_C1
#define C1_7Seg      PIN_C2
#define D1_7Seg      PIN_C3
#define E1_7Seg      PIN_C4
#define F1_7Seg      PIN_C5
#define G1_7Seg      PIN_C6
//Pin C7 is overwriten by Display_Update()

// Port D - 7-Segment Units
#define A2_7Seg      PIN_D0
#define B2_7Seg      PIN_D1
#define C2_7Seg      PIN_D2
#define D2_7Seg      PIN_D3
#define E2_7Seg      PIN_D4
#define F2_7Seg      PIN_D5
#define G2_7Seg      PIN_D6
//Pin D7 is overwriten by Display_Update()

// Port E - North
#define North_Red    PIN_E0
#define North_Amber  PIN_E1
#define North_Green  PIN_E2

//-------------------------------------------------------BitFlags--------------*/
//Bitflags used for setting direction light head conditions. (Direction_Set(Flag 1 | Flag 2 | Flag 3). Contain all necessary.
#define BLACKOUT 0b000  //Pedestrian blackout.
#define RED     0b001   //RED for any lights
#define AMBER   0b010   //AMBER for any lights
#define GREEN   0b100   //GREEN for any lights

//-------------------------------------------------------ENUM------------------*/
//Enum for Stages, masking numbers. Simplicity for reading. USED IN MAIN LOOP SWITCH.
typedef enum {
   Stage_Pedestrian,
   IS_Pedestrian_One,
   Stage_One,
   IS_One_Two,
   Stage_Two,
   IS_Two_Three,
   Stage_Three,
   IS_Three_Pedestrian
} Stage;
Stage Active_Stage = Stage_Pedestrian; //Define globally for ISR comparison and set for first stage.
//-------------------------------------------------------Time------------------*/
volatile int8 Time = 27;      //Start time 3 seconds before beginning of ped green stage, allowing start up time compensation. volatile due to read/modified in multiple places.
int8 Max_Time = 40;  //Maximum time where the system resets back to 0.

//-------------------------------------------------------7-SEG-----------------*/
const int seg[10] = { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90}; // 0 to 9 for common anode 7-seg displays.

//-------------------------------------------------------FLAGS-----------------*/
int1 Ped_Pressed_Flag = 0; //Pedestrian button. 1 = Pressed since last pedestrian crossing.
//-------------------------------------------------------Functions-------------*/
//---------------------INTERRUPT_Functions-------------------------------------*/
int8 Timer_Overflow_Count = 0; //Counting times Timer 1 resets, keeping track for actiopns after 1 second.

#int_ext
void INT_Ped_Button(){
   //Since the button raises a flag, extra activations from bouncing will not affect the code, except on the very edge case of being pressed as the cycle counts over.
   //This edge case can be cleared with an extra reset of the flag
   if(Active_Stage != Stage_Pedestrian){
      //While the pedestrian stage is active (pedestrians can cross). the flag doesnt need to be activated.
      //This also prevents the light from turning on, or the flag beign raised, while the crossing is active.
      Ped_Pressed_Flag = 1;      //Raise flag for pedestrian waiting
      output_high(Ped_Pushed);   //Turn on pedestrian button light.
   }// if != Stage pedestrian
   clear_interrupt(INT_EXT);
}//INT_EXT

#INT_TIMER1
void INT_Timer_One(){//For an interrupt every second.
   /* At 20MHz, 1 instruction cycle = 0.2us 1:8 scaler set in main = 1.6us.
      1s requires 625,000 instructions. Maximum set = 65,536. 625,000/10 = 62500 per cycle. (largest before overflow) 65535 - 62500 = 3035
   */
   clear_interrupt(INT_TIMER1);
   if(Timer_Overflow_Count < 9) {
      Timer_Overflow_Count++;
   } else {
      //1 second hit.
      Timer_Overflow_Count = 0;//reset
      Time ++;//Add 1 to time.
      if (Time >= Max_Time) {Time = 0;} //if time maxed, reset to 0 after 0 is showed on 7 segs.
   }// if(Timer_Overflow_Count
   set_timer1(3035);//Load for the next overflow cycle.
}//INT_Timer_One 


//-------------------------------Functions-------------------------------------*/
/*Direction_Set(INPUT)
   Each direction has its own function to set the outputs for each traffic head.
   Inputs are the colour to be shown on the head, using RED | AMBER | GREEN and BLACKOUT (Pedestrian only)
   All lights are turned off, and then those required are turned back on. (Red turning off and back on is unnoticeable to human eye).
*/
void East_Set(int8 Light_Colour){
      output_low(East_Red);
      output_low(East_Amber);
      output_low(East_Green);
      
      if (Light_Colour & RED) {output_high(East_Red);}
      if (Light_Colour & AMBER) {output_high(East_Amber);}
      if (Light_Colour & GREEN) {output_high(East_Green);}
}//East_Set

void West_Set(int8 Light_Colour){
      output_low(West_Red);
      output_low(West_Amber);
      output_low(West_Green);
      
      if (Light_Colour & RED) {output_high(West_Red);}
      if (Light_Colour & AMBER) {output_high(West_Amber);}
      if (Light_Colour & GREEN) {output_high(West_Green);}
}//West_Set

void North_Set(int8 Light_Colour){
      output_low(North_Red);
      output_low(North_Amber);
      output_low(North_Green);
      
      if (Light_Colour & RED) {output_high(North_Red);}
      if (Light_Colour & AMBER) {output_high(North_Amber);}
      if (Light_Colour & GREEN) {output_high(North_Green);}
}//North_Set

void Pedestrian_Set(int8 Pedestrian_Colour){
   output_low(Ped_Red);
   output_low(Ped_Green);

   if (Pedestrian_Colour & RED) {
      output_high(Ped_Red);
   }
   //BLACKOUT turns all off, done at the start so does not need to be defined.
   if (Pedestrian_Colour & GREEN) {
      //Ensure all vehicular traffic is off.
      East_Set(RED);
      West_Set(RED);
      North_Set(RED);
      output_high(Ped_Green);
      output_low(Ped_Pushed);
      Ped_Pressed_Flag = 0; //Clear flag.
   }//Green
}//Pedestrian_Set

void Button_Checks(){
   //should be handled by ISR INT_EXT, however not functioning reliably on PIC testing with. Functioned correctly on different pic.
   //Since only raising same flag, for same action, does not cause conflict, so SHOULD NOT cause issues.
   if(input(Ped_Input) == 0){ //If the pull-up button is pressed.
      if(Active_Stage != Stage_Pedestrian){
         //While the pedestrian stage is active (pedestrians can cross). the flag doesnt need to be activated.
         //This also prevents the light from turning on, or the flag beign raised, while the crossing is active.
         Ped_Pressed_Flag = 1;      //Raise flag for pedestrian waiting
         output_high(Ped_Pushed);   //Turn on pedestrian button light.
   }// if != Stage pedestrian
   };//Input
}//Button_Checks
void Display_Update(){ //Flip displays
      output_d(seg[(Max_Time - Time)/10]);//output division of 10 for time
      output_c(seg[(Max_Time - Time)%10]);//output ones of time.
}//Display_Update
//-------------------------------------------------------MAIN------------------*/
void main(){

   // Pin setup
   // For unused pins, output > input, protecting the PIC.
   //Starting conditions written later to explicitly set pin definitions. prevents issues when pins change.
   set_tris_a(0x00); //No inputs
   output_a(0x00);   //Write all low.

   set_tris_b(0x01); //Input on B0 only.
   output_b(0x00);   //Write all low.
   port_b_pullups(true);//enable pullups

   set_tris_c(0x00); //No inputs
   output_c(0x00);   //Write all low.
   
   set_tris_d(0x00); //No inputs
   output_d(0x00);   //Write all low.

   set_tris_e(0x00); //no inputs
   output_e(0x00);   //write all low
   
   //Turn 7-seg pins high, turning off common anode displays.
   output_high(A1_7Seg);
   output_high(B1_7Seg);
   output_high(C1_7Seg);
   output_high(D1_7Seg);
   output_high(E1_7Seg);
   output_high(F1_7Seg);
   output_high(G1_7Seg);
   
   output_high(A2_7Seg);
   output_high(B2_7Seg);
   output_high(C2_7Seg);
   output_high(D2_7Seg);
   output_high(E2_7Seg);
   output_high(F2_7Seg);
   output_high(G2_7Seg);
//-------------------------------------------------------INTERRUPTS-------------*/
   //Enable interrupts
      enable_interrupts(INT_EXT);     //Enable external interrupt, ped button
      enable_interrupts(INT_TIMER1);  //Enable Timer 1 interrupt, 1 second count
      enable_interrupts(GLOBAL);      //Enable global interrupts, list last. required for all interrupts
   
   //EXT_INT
      ext_int_edge(H_TO_L);           //External interrupt edge from high to low
  
   //Timer1
      setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); //Setup for timer 1, using the internal clock and a 1:8 pre-scaler.
      set_timer1(3035);               //Pre-load timer for the 1s interrupts
      clear_interrupt(INT_TIMER1);    //Clear timer one flag bit
      
//-------------------------------------------------------Start conditions-------*/
/* Starting conditions: Unknown.
   Begin with traffic at amber, warning of red incoming. keep pedestrian lights off in blackout. if any pedestrians are crossing, a red could cause panic. green is not safe for those which are not already crossing.
   After Amber state finishes, make traffic red and give pedestrians green to cross. Any already crossing wont panic *hopefully*. Enter main loop and switch at pedestrian stage.
*/
   East_Set(AMBER);
   West_Set(AMBER);
   North_Set(AMBER);
   //Delay acceptable since action set. Should not change.
   delay_ms(3000);

   // Initial stage
   Active_Stage = Stage_Pedestrian; //Backup to ensure the starting position is pedestrians.

//-------------------------------------------------------MAIN LOOP-------------*/
   while(TRUE){
   //Continuous actions
      Button_Checks();  //Check all inputs. backup to INT_EXT failing.
      Display_Update(); //Update the displays every change.
      //debug /Proteus sim delay only (Proteus cant handle constant running)*
      delay_ms(10);
      //debug /Proteus sim delay only (Proteus cant handle constant running)*
      //----------------------------------STATE MACHINE------------------------*/
      /*Switch constantly running.
         Time comparisons if ((Start <= time) && (Time < when next stage starts))
         breaks; in every if statement since tasks complete for time value.
      */
      switch(Active_Stage){
      
         default: //If switch goes wrong, reset. Safe and easiest method.
            reset_cpu();
      
         case Stage_Pedestrian:
            //Pedestrian green
            if((31 <= Time) && (Time < 38)){
               Pedestrian_Set(GREEN);
            }
            //pedestrian blackout, progress to IS.
            else if (38 <= Time){
               Pedestrian_Set(BLACKOUT);   
               Active_Stage = IS_Pedestrian_One;
            }
            
            break;

         case IS_Pedestrian_One:
            //Pedestrian Blackout
            if((38<= Time) && (Time < 40)){
               Pedestrian_Set(BLACKOUT);
            }
            //Pedestrian Red
            else if ((40 <= Time) && (Time < 41)){
               Pedestrian_Set(RED);
            }
            //East Red Amber.
            else if ((0 <= Time ) && (Time < 2)){
               East_Set(RED | AMBER);
               Pedestrian_Set(RED);
            }
            //East green. Progress to stage 1.
            else if (2 <= Time){
               East_Set(GREEN);
               Active_Stage = Stage_One;
            }
            
            break;

         case Stage_One:
            //East green
            if((2 <= Time) && (Time < 7)){
               East_Set(GREEN);
            }
            //pedestrian blackout, progress to IS.
            else if (7 <= Time){
               East_Set(AMBER);
               Active_Stage = IS_One_Two;
            }
            break;

         case IS_One_Two:
            //East Amber
            if((7<= Time) && (Time < 10)){
               East_Set(AMBER);
            }
            //East Red, West Red Amber
            else if ((10 <= Time) && (Time < 12)){
               East_Set(RED);
               West_Set(RED | AMBER);
            }
            //West green. Progress to stage 2.
            else if (12 <= Time){
               West_Set(GREEN);
               Active_Stage = Stage_Two;
            }
            
            break;

         case Stage_Two:
            //West green
            if((12 <= Time) && (Time < 17)){
               West_Set(GREEN);
            }
            //West AMber, Progress to IS.
            else if (17 <= Time){
               West_Set(AMBER);
               Active_Stage = IS_Two_Three;
            }
            
            break;

         case IS_Two_Three:
            //West Amber
            if((17<= Time) && (Time < 20)){
               West_Set(AMBER);
            }
            //West Red, North Red Amber
            else if ((20 <= Time) && (Time < 22)){
               West_Set(RED);
               North_Set(RED | AMBER);  
            }
            //North green. Progress to stage 3.
            else if (22 <= Time){
               North_Set(GREEN);
               Active_Stage = Stage_Three;
            }
            
            break;

         case Stage_Three:
            //North green
            if((22 <= Time) && (Time < 27)){
               North_Set(GREEN);
               break;
            }
            //North Amber, Progress to IS.
            else if (27 <= Time){
               North_Set(AMBER);
               Active_Stage = IS_Three_Pedestrian;
            }
            
            break;

         case IS_Three_Pedestrian:
            //North Amber
            if((27<= Time) && (Time < 30)){
               North_Set(AMBER);
            }
            //North Red
            else if ((30 <= Time) && (Time < 31)){
               North_Set(RED);
               //If no pedestrians have pressed the button, skip pedestrians. Reset time and move into the correct IS.
               if(Ped_Pressed_Flag == 0){
                  Time = Max_Time;//reset to max time, when breaking out the loop, time resets and loop rebeginns after the pedestrians.
                  Active_Stage = IS_Pedestrian_One;
               }//if Ped_Pressed_flag
            }//30<=Time
            //Pedestrian green. Progress to stage Pedestrian.
            else if (31 <= Time){
               Pedestrian_Set(GREEN);
               Ped_Pressed_Flag = 0;
               Active_Stage = Stage_Pedestrian;
            }
            
            break;

      }//switch

   }//while

}//main
