/* 	===========================================================================
P_Ch3 Phase 1: 
Counter_mod10 with CE, UD_L and 7-segment output 

An example on how to implement a sequential system using the [Unit 2.3] style 
(current_state is an enumeration of states) 

TARGET MICROCONTROLLER: ATMEGA8535 
(but the example is fully compatible with many other Atmel processors)
 
Example hardware circuit in Proteus 
http://digsys.upc.es/csd/P12/Counter_mod_10_digit.pdsprj 

NOTE: This code is not optimised, but in some way extended in order to clarify 
and make easy the understanding of the basic operations. 
  	======================================================================== */

#include <avr/io.h>
//#include <stdlib.h>    
#include <avr/interrupt.h>

// Let's see where all the ATmega8535 microcontroller declarations are located.

// C:\Program Files (x86)\Atmel\Atmel Studio 6.2\devices\ATmega8535.xml
/* #include "Atmel Toolchain/AVR8 GCC/Native/3.4.1056/avr8-gnu-toolchain
							/avr/include/avr/iom8535.h"
*/
/* 	===========================================================================
	Definitions 
   	======================================================================== */

// ------------------ Inputs definitions ----------------------
// Example hardware:  http://digsys.upc.es/csd/P12/Counter_mod_10_digit.pdsprj
// 
// The port PORTA where the input "CE" is located
#define CEnable_PORT	PINA 	
// The pin number (PA1) where the CE is located is the mask
#define CEnable_pin		0b00000010		

// The port PORTA where the input "UD_L" is located
#define UD_L_PORT	PINA
// The pin number (PA0) where the UD_L is located is the mask
#define UD_L_pin	0b00000001

// The CLK input, can be defined the same way, however, it won't be read but 
// instead, the interface of this input will be conducted through 
// the INT0 interrupt (edge detection, programmable rising/falling edge)

// The PORTC where the input CLK is located
#define CLK_PORT PIND		
// The pin number where the input CLK is located (PD2/INT0)
#define CLK_pin	0b00000100	

// ------------------- Outputs definitions ----------------------

// The port where 7-segment outputs of the counter are connected,
// and so it will be written
#define Value_BCD_PORT PORTC		

// Other definitions related to the counter system:
// The list of counter states 
#define 	Num_0 		'A'		//States enumeration. "Char"
#define 	Num_1 		'B'
#define 	Num_2 		'C'
#define 	Num_3 		'D'
#define 	Num_4 		'E'
#define 	Num_5 		'F'
#define 	Num_6 		'G'
#define 	Num_7 		'H'
#define 	Num_8 		'I'
#define 	Num_9 		'J'

/* 	===========================================================================
	Function prototypes
   	======================================================================== */

//#  define ISR(vector, [attributes]) 
// Already defined in the header <avr/interrupt.h>
void init_system (void);
char state_logic(void);
char output_logic(void);

void read_inputs(void);
void write_outputs(void);

char BCD_to_7seg (void);
/* ============================================================================
	Global variables (RAM memory) to make the system compatible and as 
	hardware-independent as possible. 
	For easy watching/debugging and processing in C 
  	======================================================================== */
char Value_TC10;	// [0 0 0 0 0 0 0 TC10] 
char Value_BCD;		// [0 0 0 0 Q3 Q2 Q1 Q0] to store the number in BCD
char Value_7seg;	// [0 a b c d e f g] to store each segment

char Value_DP = 0;	// [0 0 0 0 0 0 0 DP] defined, but not used in this phase

char CLK_Flag;	//The flag that will be set from the interrupt service routine
				// to indicate that a new falling edge has arrived

char Value_CE;  //The variable to be watched easily which will be polled 

char Value_UD_L;  //The variable to be watched easily which will be polled 

	// The state variable of the FSM which will be updated ("asynchronously")
	// every time that "state_logic()" is executed
char current_state;		

/* 	===========================================================================
	Atmega8535 Configuration bit Fuses (datasheet page 238)
  	=========================================================================== 
 This definitions are copied from the "iom8535.h" header file:
	   #define FUSE_MEMORY_SIZE 2
  // Low Fuse Byte 
	   //CKSEL(3..0) = "1110" External resonator or crystal up to 8 MHz
	   #define FUSE_CKSEL0      (unsigned char)~_BV(0)	
	   #define FUSE_CKSEL1      (unsigned char)~_BV(1)
	   #define FUSE_CKSEL2      (unsigned char)~_BV(2)
	   #define FUSE_CKSEL3      (unsigned char)~_BV(3)
	   //SUT(1..0) = "11" Crystal Oscillator, slowly rising power
	   #define FUSE_SUT0        (unsigned char)~_BV(4)	
	   #define FUSE_SUT1        (unsigned char)~_BV(5)
	   // Brown out detector enable
	   #define FUSE_BODEN       (unsigned char)~_BV(6)	
	   #define FUSE_BODLEVEL    (unsigned char)~_BV(7)// BODLEVEL = "0" ==> 4V
// Brown-out Reset. The MCU is reset when the supply voltage VCC is below the
// Brown-out Reset threshold (VBOT) and the Brown-out Detector is enabled.
	   
#define LFUSE_DEFAULT (FUSE_CKSEL1 & FUSE_CKSEL2 
		& FUSE_CKSEL3 & FUSE_SUT0 & FUSE_SUT1)
			// "0001" "00" Internal 1 MHz calibrated oscillator, 
	   // High Fuse Byte 
	   #define FUSE_BOOTRST     (unsigned char)~_BV(0)	//Select Reset Vector
	   //The default value of BOOTSZ1..0 results in maximum Boot Size
	   #define FUSE_BOOTSZ0     (unsigned char)~_BV(1)	
	   #define FUSE_BOOTSZ1     (unsigned char)~_BV(2)	
	   //EEPROM memory is preserved through the Chip Erase
	   #define FUSE_EESAVE      (unsigned char)~_BV(3)	
	   // When CKOPT is programmed, the Oscillator output will oscillate will 
	   // a full rail-to-rail swing on the output.
	   #define FUSE_CKOPT       (unsigned char)~_BV(4)	
	   // Enable Serial Programming
	   #define FUSE_SPIEN       (unsigned char)~_BV(5)	
	   // Enable the Watch Dog timer
	   #define FUSE_WDTON       (unsigned char)~_BV(6)	
	   	// Compatible to the AT90S8535
	   #define FUSE_S8535C      (unsigned char)~_BV(7)
	   #define HFUSE_DEFAULT (FUSE_BOOTSZ0 & FUSE_BOOTSZ1 & FUSE_SPIEN)
*/

// ============================================================================
// So, let's program the configuration bits for a given 
// developing board and AVR chip. Do not change these values
    FUSES =
    {
		// let's use an external oscillator "1110", "01", and BODEN:
			    //.low = (FUSE_BODEN & FUSE_BODLEVEL),
		.low = (FUSE_CKSEL0 & FUSE_SUT1 & FUSE_BODEN & FUSE_BODLEVEL),
		.high = (FUSE_BOOTSZ0 & FUSE_BOOTSZ1 & FUSE_SPIEN & FUSE_EESAVE),
    }; 

/* 	===========================================================================
	Main function 
  	======================================================================== */
int main(void){
    init_system ();
    while(1) {                      /* loop forever the FSM */
            output_logic();
            state_logic();
    }
}	
/* 	===========================================================================
	Function definitions
  	======================================================================== */

//*****************************************************************************
//Initialisation operations (setup) 
//*****************************************************************************

void init_system (void){

// Hardware configuration bits for polling/writing and interrupts

// Application program starts here:
	PORTA 	= 0x00;		// Let's not use the Pull-up resistor in the inputs 
						// (we'll wire external resistors like R4)
	DDRA 	= 0b11111100; // All outputs except PA1 and PA0
	
	PORTD 	= 0x00;		// Let's not use the Pull-up resistor in PD2
						// Our CLK input will include R_CLK pull-up resistor
// All outputs except the PD2 pin which is our CLK (interrupt INT0) 
	DDRD 	= 0b11111011;
								
	PORTC 	= 0x00;
	DDRC	= 0xFF; // All outputs 

	GICR = 1<<INT0;					// Enable INT0
	// Trigger INT0 on falling edge (interrupt after clicking the pushbutton)
	MCUCR = 1<<ISC01 | 0<<ISC00;	
		
	sei(); // Global interrupts allowed
	
	current_state = Num_0;
	
    CLK_Flag = 0;		// The flag to detect an INT0 interrupt

	// Set the initial output values
	Value_TC10 = 0;
	Value_BCD = 0;
	
	write_outputs();
}

//*****************************************************************************
//State variable logic routine 
//*****************************************************************************
char state_logic(void){
	char Value_error = 0;
// A behavioural way to interpret and translate the truth table of the CC1 
	read_inputs (); // Lets poll all the variables which have to be sampled  
					// (level sensitive)
	// Do something only when there is an CLK's active edge detection
	if (CLK_Flag == 1){
		if (Value_CE == 1){	// The system has to count			
			if (Value_UD_L == 1){	// Count UP
				switch (current_state) {
					case Num_0:
					current_state = Num_1;
					break;
					case Num_1:
					current_state = Num_2;
					break;
					case Num_2:
					current_state = Num_3;
					break;
					case Num_3:
					current_state = Num_4;
					break;
					case Num_4:
					current_state = Num_5;
					break;
					case Num_5:
					current_state = Num_6;
					break;
					case Num_6:
					current_state = Num_7;
					break;
					case Num_7:
					current_state = Num_8;
					break;
					case Num_8:
					current_state = Num_9;
					break;
					case Num_9:
					current_state = Num_0;
					break;

					default:
					Value_error = 1;
					// If no states have been selected, there is an Value_error
					// and the systems must be stopped
					break;
				}
			}
			else{	// Down 
				switch (current_state) {
					case Num_0:
					current_state = Num_9;
					break;
					case Num_1:
					current_state = Num_0;
					break;
					case Num_2:
					current_state = Num_1;
					break;
					case Num_3:
					current_state = Num_2;
					break;
					case Num_4:
					current_state = Num_3;
					break;
					case Num_5:
					current_state = Num_4;
					break;
					case Num_6:
					current_state = Num_5;
					break;
					case Num_7:
					current_state = Num_6;
					break;
					case Num_8:
					current_state = Num_7;
					break;
					case Num_9:
					current_state = Num_8;
					break;

					default:
					Value_error = 1;
					// If no states have been selected, there is an Value_error
					// and the systems must be stopped
					break;
				}
			}
		
		}
		// Very important --> Clear the CLK_flag, 
		// so it can be set again in the next falling edge in PD2
		CLK_Flag = 0;	
	}
	return (Value_error);
}
	
//*****************************************************************************
//Output logic routine 
//*****************************************************************************
char output_logic(void){
	unsigned char Value_error = 0;
	
// Calculate the outputs
	
	switch (current_state) {	// Set the outputs for every state:a truth table
		case Num_0:
			Value_BCD = 0;
		break;
		case Num_1:
			Value_BCD = 1;
		break;
		case Num_2:
			Value_BCD = 2;
		break;
		case Num_3:
			Value_BCD = 3;
		break;
		case Num_4:
			Value_BCD = 4;
		break;
		case Num_5:
			Value_BCD = 5;
		break;
		case Num_6:
			Value_BCD = 6;
		break;
		case Num_7:
			Value_BCD = 7;
		break;
		case Num_8:
			Value_BCD = 8;
		break;
		case Num_9:
			Value_BCD = 9;
		break;
		
		default:
			Value_error = 1;	// If no states have been selected, there is an Value_error
		// and the systems must be stopped
		break;
	}
	
	// Lets set the value of the Value_TC10:
	if ( (current_state == Num_9 && Value_UD_L == 1 && Value_CE == 1 ) || 
		 (current_state == Num_0 && Value_UD_L == 0 && Value_CE == 1 ) ) 
		Value_TC10 = 1;
	else
		Value_TC10 = 0;

	BCD_to_7seg (); // Convert to 7 segments

	write_outputs(); // Write the ports:


/*  So, as you see, even if it takes more code, the process to code in C our 
	applications will be:  
	1) Process or calculate the variables (to be able to watch them), and 
	2) Write the outputs to the corresponding pins.
	
	In this way, the program will be compatible for multiple microcontrollers 
	and boards, except for init_system() and read and write ports functions, 
	which will behave like a kind of a driver for the specific hardware.  */	
	return (Value_error);
}


//*****************************************************************************
//Interrupt Service Routine to attend the external INT0 (The PD2 pin)
//*****************************************************************************

ISR(INT0_vect){
	cli();		// clear all sources of interrupt while attending this one

    CLK_Flag = 1;		
/*	Let's set our convenient "software" flag, so we'll update the state every 
time that there is an interrupt (the arrows (state transitions) of the state 
digram will be executed every time that there is a CLK falling edge) */

//Enable general interrupts, so that the system can be interrupted again	                     
	sei();	
}

//*****************************************************************************
// Write the output variables to the corresponding pins and ports 
//*****************************************************************************

void write_outputs(void){
	char Value_out; // A convenient local buffer 
	
	// This is a convenient way to start writing a function
	__asm ("nop");	// Do nothing by now (assembler for "no operate"). 
		
	// Example of bitwise port control: a method of changing one or more bits 
	// of a parallel port without affecting the other bits of the port.

	// This line demonstrates using the OR operator to force two bits 
	// of the port to '1' without affecting the other bits of the port:
	//	PORTB = PINB | 0b01100000;		// The bits 6 and 5 are set to '1'

	// This is an example on how to force a bit of the port to '0' by ANDing 
	// the bit with a 0 without affecting the other bits of the port:
	// PORTB = PINB & 0b11111110;		// The bit 0 is reset to '0'
	
	// Poll and save all the pins except the PA3 which is reset
	// (because the unused pins may be used in later developments) 
	//Value_out = PINA & 0b11110111 ; 
	//if (Value_TC10 == 1){
		//// TC10 bit to '0'	(which means that the LED goes ON)
		//// because the hardware is active low. 
		//Value_out = Value_out & 0b11110111 ;
		//PORTA = Value_out;	
	//}
	//else{
		//// TC10 bit to '1' (which means that the LED goes OFF)
		//Value_out = Value_out | 0b00001000;
		//PORTA = Value_out;	
	//}
	
	PORTA = ~(Value_TC10 << 3); // Simple way: write directly because it is 
							 // the only output in this port 

	// Write the 7segment code and the DP bit (all active low):
	Value_out = Value_7seg | (Value_DP <<7); 
	Value_out = ~Value_out;	// bitwise NOT and writes all the port pins 
	Value_BCD_PORT = Value_out; 
	
};
	
//*****************************************************************************
// Read (poll) the input pins (level sensitive) 
//*****************************************************************************

void read_inputs (void){
	__asm ("nop");	// do nothing by now

//  Polling --> Read the ports and set the "char" variable
// Let's read the CE input pin and set the convenient variable "Value_CE" 
// using the masking technique: 
	if ( CEnable_PORT & CEnable_pin )	
		Value_CE = 1;
	else
		Value_CE = 0;
//*****************************************************************************
// Let's read the UD_L input pin and set the convenient variable "Value_UD_L"
// using the masking technique:
	if ( UD_L_PORT & UD_L_pin )
		Value_UD_L = 1;
	else
		Value_UD_L = 0;
};


//*****************************************************************************
// Convert from BCD to seven segments (active high). A simple truth table
//*****************************************************************************
	
char BCD_to_7seg (void){
	char Value_error = 0;
	switch (Value_BCD) {
		case 0:
			//				abcdefg
			//              -------
			Value_7seg = 0b01111110;
		break;
		case 1:
			Value_7seg = 0b00110000;
		break;
		case 2:
			Value_7seg = 0b01101101;
		break;
		case 3:
			Value_7seg = 0b01111001;
		break;
		case 4:
			Value_7seg = 0b00110011;
		break;
		case 5:
			Value_7seg = 0b01011011;
		break;
		case 6:
			Value_7seg = 0b01011111;
		break;
		case 7:
			Value_7seg = 0b01110000;
		break;
		case 8:
			Value_7seg = 0b01111111;
		break;
		case 9:
			Value_7seg = 0b01110011;
		break;
			
		default:
			Value_error = 1;	// If the code is not BCD --> Value_error 
			break;
	}	
	return (Value_error);
};
		