/* ============================================================================
 UPC - EETAC - CSD - http://digsys.upc.edu 

	Tutorials on applying MCC Melody
	
	Project design phase #3;
	Project:Basic PWM with an LCD display with I2C interface and A/D converter
	for acquiring the pulse width (PW) that sets the duty cycle (DC)
	DC = var_PW/var_WP

- ST_L push-button at RB0/INT0 to generate an INT0 interrupt to start/stop
- MSSP2 module to set I2C interface SCL2, SDA2 where the LCD kit is connected

- Lamp pilot at RB5 that will toggle its value every sampling period 
to visualise the A/D sampling frequency (fs) waveform T_Lamp = 2·Ts
- Analogue input value V0 connected at RA0/AN0
- PWM_out output at RE2/CCP5 pin 

- Switch WP at RC4 is used to read a single bit for fixing two PWM 
frequencies: WP = 500 us (2 kHz) or WP = 2 ms (500 Hz)
By default, from the MCC we will configure the PWM frequency to 2 kHz.
To configure the PWM frequency we have to modify the timer period register 
(we have to see how this hardware can be abstracted) 

Prototype adapted to the CSD_PICstick training board
		https://digsys.upc.edu/csd/units/CSD_PICstick/CSD_PICstick.html
Target microcontroller: PIC18F46K22 
		https://www.microchip.com/en-us/product/PIC18F46K22 

=============================================================================*/

/* ===============================================
Compile with XC8 v3.1 compiler or newer (standard C99)
https://www.microchip.com/en-us/tools-resources/develop/mplab-xc-compilers/xc8
Generate compilation output files COFF for Proteus simulations or ELF for 
programming the prototype

Microchip documentation:
www.microchip.com/en-us/tools-resources/configure/mplab-code-configurator

Project documentation location:
	https://digsys.upc.edu/csd/DEE/dimmer/dimmer.html#B2
	https://digsys.upc.edu/csd/DEE/dimmer/phaseB/PIC/Dimmer_PIC.html#ph3
	======================================================================== */

/* On disabling some warnings: Disable warning 520 (function is never called)! 
To enable it just remove or comment the line below  */
#pragma warning disable 520
/* Disable WARNING (759) expression generates no code
To enable it just remove or comment the line below  */
#pragma warning disable 759
#pragma warning disable 2098

/* Other warnings can be disabled as compiler options: 
Properties --> XC8 Compiler options --> Additional options 
	-Xparser -Wno-implicit-int-float-conversion  -Wno-implicit-int-conversion -Wno-#warnings
*/
/* ============================================================================
	Headers
============================================================================ */
// This general header "system.h" includes the "pic18f46k22.h" and all the 
// resources used in this application, automatically managed by MCC Melody.
		//#include "./PWM_LCD_AD_prj.X/mcc_generated_files/system/system.h"
// Accessing these libraries also allows accessing to the "system.h" header:
#include "./PWM_LCD_AD_prj.X/PCF8574.h"
#include "./PWM_LCD_AD_prj.X/I2C_LCD.h"

#include <stdio.h>	//include stdio header file, required for 'printf' function
#include <stdint.h> //defines values associated with exact-width types
/* ============================================================================
	Definitions 
============================================================================ */
// Our system has 2 states defined as follows:
#define Idle					'A'
#define Set_Acq_TimerandPWM		'B'
#define Wait_Sample_Pulse		'C'
#define Acquire_AN0				'D'
#define Generate_PWM			'E'
#define Stop_Acq_TimerandPWM	'F'

/* A RAM memory position (a single byte or 'char') "var_current_state" type 
"uint8_t" is used to encode state labels as ASCII capital letters for easy 
debugging. 
A single byte can encode up to 256 states, which is enough for our projects.
Remember that encoding FSM states was done using "sequential", "Gray", 
"one-hot", "Johnson", etc. in our previous Chapter 2 on VHDL and FPGA. */

/* ============================================================================
	Function prototypes (not included in the MCC generated file structure
============================================================================ */
static void INT0_PB_CallBack(void); // INT0 interrupt handler 
static void Sampling_CLK_CallBack(void); //TMR0 interrupt handler
void main(void); // main control flow

// The basic function to organise the code to emulate a FSM
// state logic and output logic in a switch case statement
uint8_t FSM(void); // return 0 when OK

// The auxiliary functions to read/poll and write port pins and be able to
// organise the software variables, to make the main program hardware
// independent and compatible for many microcontrollers. 
void write_outputs(void);
void read_inputs(void);

/* ============================================================================
	Global variables and data types
Even if some variables are 'bit' type, we use 'uint8_t' to make them easy to 
watch while debugging.
============================================================================ */
static uint8_t var_Lamp;
static uint8_t var_WP; // To read the SW1, waveform period (500 us or 2 ms)
static uint16_t var_PW; // Pulse width from the A/D 

static float var_DC; // Duty cycle (up to 1000 values) 
//static uint8_t var_DC_ASCII[7]; 
static char var_DC_ASCII[7]; /*string of char to represent duty cycle. 
It has to have enough space to hold the floating point values with one decimal*/

static uint16_t var_PW_prev; // Previous duty cycle value 
/* We will update the Pulse width period register only when a PW new value 
is captured from the A/D */

//The flag that will be set in the interrupt service routine to indicate that 
// a new push-button edge is detected 
static uint8_t var_ST_flag;

//The flag that will be set in the interrupt service routine to indicate that 
// a new A/D sampling period has elapsed to trigger a new acquisition
static uint8_t var_SP_flag; 

/* The state variable to be updated ("asynchronously") every time that 
"FSM()" is executed. It is the FSM memory equivalent to the state 
register in Chapter 2	*/
static uint8_t var_current_state;

// TODO: Replace TimerX with name of const struct TIMER_INTERFACE, 
// from MCC Generated Files > timer > tmrx.c 
// This is the memory address where the timer callback is saved. 
static const struct TIMER_INTERFACE *Timer = &Sampling_CLK; 

// I2C structure 
const i2c_host_interface_t *I2C = &i2c2_host_host_interface;


/* Writing the I2C peripherals takes time, it is a slow operation. 
We need to introduce a flag so that we will write only when new data is 
available for printing */
static uint8_t var_LCD_flag; 


/* ============================================================================
	main function 
============================================================================ */
void main(void) {
	SYSTEM_Initialize(); // Automated and maintained by MCC Melody

	// Key elements to install interrupts: A pointer to our interrupt handler, 
	// in a way that we do not need to modify a single MCC generated file. 
	INT0_InterruptHandler = *INT0_PB_CallBack;

	// This structure element points to our TMR0 interrupt handler
	Timer->TimeoutCallbackRegister(Sampling_CLK_CallBack);

	// Initial values 
	var_Lamp = 0;

	var_ST_flag = 0;
	var_SP_flag = 0;
	write_outputs();
	var_current_state = Idle;
	var_PW = ADC_GetConversion(0); // to check that this module works
	if (var_PW > 1000) var_PW = 1000; // cut invalid values

	INTERRUPT_PeripheralInterruptEnable();
	// Enable the Global Interrupts 
	INTERRUPT_GlobalInterruptEnable();

	// Do not use the sampling period timer (TMR0) when Idle. 
	Sampling_CLK_Stop();

/* I2C and LCD functions adapted from its tutorial: 
I2C communication for the I2C 
I2C client PCF8574 device port pins connected to the LM1602L  
https://digsys.upc.edu/csd/DEE/lab07/PIC18F/Counter_BCD_1digit_LCD/Counter_BCD_1digit_LCD.html#A3
*/ 
	LCD_Init(PCF8574_I2C_CLIENT2_ADDR);
	__delay_ms(500);
	LCD_Backlight_OFF();
	__delay_ms(500);
	LCD_Backlight_ON();
	__delay_ms(500);
	LCD_Set_Cursor(2,5);
	LCD_Write_Char('A');

	__delay_ms(500);
	LCD_Set_Cursor(1,1);
	LCD_Write_String("-DEE-PWM-LCD-A/D");
	LCD_Set_Cursor(2,1);
	LCD_Write_String("Phase #3: A/D-PW");
	__delay_ms(1000);
	LCD_Set_Cursor(1,1);
	LCD_Write_String("DEE - PWM Dimmer");
	LCD_Set_Cursor(2,1);
	LCD_Write_String("Click ST to run ");	
	var_LCD_flag = 1; // to print the initial message when Idle


	while (1) { // the FSM running in the infinite loop
		read_inputs(); // poll input switch value
		if (FSM()) break;
		write_outputs(); //The interface with the hardware to convert variables
	} // into voltages at a given microcontroller pin. 
	while (1) {
		__nop(); // Assembler instruction meaning "do nothing"
	// It simply wastes 4 OSC cycles  
		/* The system has crashed in an illegal state and needs attention.
	This second infinite loop is never reached in normal operation. If by any 
	chance the system enters this loop, the only way to scape it is by
	clicking reset MCLR_L button (asyn CD_L) or provoking a software reset*/
	}
}

/* 	===========================================================================
	FSM function 
	// State variable logic The combinational circuit CC1.
	// State transitions and loops: arrows in the state diagram 

	// Output logic: The combinational circuit CC2. 
	// Outputs in brackets in the state diagram. 
============================================================================ */
uint8_t FSM(void) {
	uint8_t error = 0;

	switch (var_current_state) {
		case Idle:
			/*----------------------  CC1 state logic  ----------------------*/
			if (var_ST_flag == 1) {
				var_current_state = Set_Acq_TimerandPWM;
				var_ST_flag = 0;
			} else var_current_state = Idle;

			/*----------------------  CC2 output logic  ---------------------*/
			var_Lamp = 0;
			if (var_LCD_flag==1){
				LCD_Set_Cursor(1,1);
				LCD_Write_String("DEE - PWM Dimmer");
				LCD_Set_Cursor(2,1);
				LCD_Write_String("Click ST to run ");
				var_LCD_flag = 0; 
			}
		break;

		case Set_Acq_TimerandPWM:
		/*----------------------  CC1 state logic  ----------------------*/
			var_current_state = Wait_Sample_Pulse;

		/*----------------------  CC2 output logic  ---------------------*/
			var_Lamp = 1;
			Sampling_CLK_PeriodSet(50000); //10 Hz
			Sampling_CLK_Start();
			var_PW = ADC_GetConversion(0); // to get the first PW value 
			if (var_PW > 1000) var_PW = 1000; // cut invalid values			
			var_PW_prev = 0; 
//			var_DC = var_PW*0.1;

		// Set the PWM parameters 
			CCP5_Initialize(); // with the waveform frequency 
			CCP5_LoadDutyValue(var_PW);
			TMR2_Initialize();
			if (var_WP == 0){	// PWM_out frequency = 500 Hz
				T2CON = (2 << _T2CON_T2CKPS_POSN)   // T2CKPS 1:16
						| (1 << _T2CON_TMR2ON_POSN)   // TMR2ON on
						| (0 << _T2CON_T2OUTPS_POSN);  // T2OUTPS 1:1
			}
			else{				// PWM_out frequency = 2 kHz
				T2CON = (1 << _T2CON_T2CKPS_POSN)   // T2CKPS 1:4
						| (1 << _T2CON_TMR2ON_POSN)   // TMR2ON on
						| (0 << _T2CON_T2OUTPS_POSN);  // T2OUTPS 1:1
			}			
			var_LCD_flag = 1; // Let us print the current DC on the LCD
			
		break;

		case Wait_Sample_Pulse:
/* This state can be replaced adding other features while waiting for samples*/
		/*----------------------  CC1 state logic  ----------------------*/
			if (var_SP_flag == 1){  // When TMRx overflows ...
			var_current_state = Acquire_AN0;
			var_SP_flag = 0;
			}
			else{
				var_current_state = Wait_Sample_Pulse;
			}

		/*----------------------  CC2 output logic  ---------------------*/
			var_Lamp = var_Lamp; // Do nothing;
		break;

		case Acquire_AN0:
		/*----------------------  CC1 state logic  ----------------------*/
			var_current_state = Generate_PWM;

		/*----------------------  CC2 output logic  ---------------------*/
			var_PW = ADC_GetConversion(0);
			if (var_PW > 1000) var_PW = 1000; // cut invalid values			
			
		break;


		case Generate_PWM:
/* This state can be replaced adding other features for processing data */
		/*----------------------  CC1 state logic  ----------------------*/
			if (var_ST_flag == 1){
			var_current_state = Stop_Acq_TimerandPWM;
			var_ST_flag = 0;
			}
			else{
				var_current_state = Wait_Sample_Pulse;
			}

		/*----------------------  CC2 output logic  ---------------------*/
// In this case, we simply toggle the Lamp value
			var_Lamp = (var_Lamp ^0x11111111)& 0b00000001;

			if (var_PW != var_PW_prev){
				CCP5_LoadDutyValue(var_PW);
				var_PW_prev = var_PW;
				var_LCD_flag = 1; // Let us print the new DC on the LCD
				var_DC = var_PW*100.0/1000; //(10-bit resolution)
			}

			if (var_LCD_flag == 1) {
				LCD_Set_Cursor(1,1);
				LCD_Write_String(" PWM Duty cycle ");
				LCD_Set_Cursor(2,1);
				LCD_Write_String("DC value = ");
				if (var_PW < 1000){
					sprintf(var_DC_ASCII, "%.1f%% ", var_DC);
					LCD_Set_Cursor(2,12);//cursor position where to write data
					LCD_Write_String(var_DC_ASCII);// update with the 
													//numerical info sprintf
				}
				else // to debug the DC value 100% 
					LCD_Write_String("100 %");
				var_LCD_flag = 0; 
			}
		break;

		case Stop_Acq_TimerandPWM:
		/*----------------------  CC1 state logic  ----------------------*/
			var_current_state = Idle;

		/*----------------------  CC2 output logic  ---------------------*/
		// Disable the peripheral PWM 
			var_Lamp = 0;
			CCP5_LoadDutyValue(0); 
			TMR2_PeriodSet(0);		//In this way, RE2 = 0
		// Disable the sampling clock 
			Sampling_CLK_Stop();
			var_LCD_flag = 1; // to print the initial message when Idle
		break;
			
		default:
			// If no states have been selected, there is an error
			// and the systems must be stopped
			error = 1;
			break;
	}
	return (error);
}

/* ****************************************************************************
	//Interrupt Service Routine for attending interrupts from INT0
******************************************************************************/
void INT0_PB_CallBack(void) {
	var_ST_flag = 1;
}

//*****************************************************************************
//Interrupt Service Routine for attending the sampling CLK overflow
// You can choose six TMRx devices to implement this function. 
// MCC Melody abstracts this device management
//*****************************************************************************
static void Sampling_CLK_CallBack(void){
	var_SP_flag = 1;
}


/* ============================================================================
	Driving the output pins. Let's drive the output ports, converting 
microcontroller variables into electrical signals.
As a rule in CSD we will write all the port pins in a single instruction
preserving (re-writing) the pins of no interest 
NOTE: LATx and PORTx registers are quite similar but not identical. 
Let us use PORTx for reading data and LATx for writing data out
============================================================================ */
void write_outputs(void) {
// These are convenient local variables to save partial results while operating
	volatile uint8_t var_buff, var_buff2;

/* This is the way we write a given output pin:
	- Read and mask to preserve all the bits of no interest.
	- Clean all bits to be written.
	- Compose and write the port in a single instruction

// There is 1 LED at PORTB(5) used for displaying the Lamp pilot */
	var_buff = (PORTB & 0b11011111);
	var_buff2 = var_Lamp << 5;
	var_buff = var_buff | var_buff2;
	LATB = var_buff; // write the port
}

/* ============================================================================
Scan, read, poll or capture inputs and process the bits so that we can set 
our convenient variables for running the FSM.
Many of the variables are "uint8_t" because we can easily monitor them using
the watch window for debugging purposes.
============================================================================ */
void read_inputs(void) {
	// As we did in P9 (L9_3): 
	// read the port, mask the bit of interest and save the variable 
	var_WP = (PORTA & 0b00010000)>>4;
}

/* end of file */