/*FLOWMETER FIRMWARE
 * AUTHOR: JINDRICH SINDELAR
 *
 * This program is firmware for flow and temperature meter prototype with MSP430F5529LP. It uses simple state machine
 * with states {"sleep", "start", "measurement", "stop"}. After initialization of peripherals like clock, REF module,
 * A/D converter, timer B and unused processor pins is performed USB initialization depending on state of USB_CDC constant.
 * After that, infinite loop with case-switch statement for state machine function is entered. To conserve energy,
 * REF module and A/D converter are turned off by default. They are switched on in Timer B interrupt routine and then
 * switched off again in A/D converter interrupt routine.
 *
 * Default state of state machine is 'sleep'. Timer B periodically wakes up the MCU, if flow is considered to be greater than
 * zero, the state machine switches to 'Start' state to set up the full measurement. The timer B wake-up period is set
 * to TB_SHORT constant to achieve sampling rate of 20 Hz. Circuit for temperature measurement is turned on.
 *
 * After 'Start' state is following 'Measurement' state, in which the MCU is periodically waken up from timer B with frequency
 * of 20 Hz and A/D conversion is performed. After A/D conversion, SEND_flag is set. After that, the data are send through
 * USB CDC interface and SEND_flag is cleared.
 *
 * When the flow value is considered to be zero, the state machine is switched into 'Stop' state. The timer B wake-up value
 * is set to TB_LONG constant to achieve wake-up frequency of 2 Hz. This ensures lower power consumption. Circuit for temperature
 * measurement is turned off.
 *
 * The MCU is then waiting for another ocurence of non-zero flow value and previous sequence is ran again.
 *
 * THE sprintf() FUNCTION FROM stdio.h DELIVERED WITH CCS6 DOES NOT SUPPORT FULL FORMAT FUNCTIONALITY
 * (MEASURED DATA ARE REQUIRED TO BE PROPERLY ALIGNED FOR PROPER CDC DATA SENDING). THEREFORE THERE IS
 * WRITTEN FUNCTION CALLED NumberPad() FOR PROPER NUMBER ALIGNMENT INTO STRING.
 *
 * THE PROJECT HAS TO HAVE PROPERLY SET INCLUDE DIRECTORIES, ESPECIALLY FOR USB DESCRIPTORS .h FILES ({project directory}/USB_config)
 */

// general includes
#include "driverlib.h"
#include <msp430f5529.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>

// USB function related includes from USB API stack
#include "descriptors.h"
#include "USB_API/USB_Common/device.h"
#include "USB_API/USB_Common/usb.h"                     //USB-specific functions
#include "USB_API/USB_CDC_API/UsbCdc.h"
#include "USB_app/usbConstructs.h"

#define	fMCLK			8000000							// maser clock value

#define	USB_CDC			1								// set 1 if USB communication is required, otherwise 0
#define	GO_TO_SLEEP		((USB_CDC == 1) ? LPM0 : LPM3)	// when using USB, the MSP430 can not go under LPM0

#define	enable_GI		__bis_SR_register(GIE)			// macro for global interrupt enable
#define	disable_GI		__bic_SR_register(GIE)			// macro for global interrupt disable

// application defines
#define	fMCLK			8000000									// master clock frequency

#define	faclk			32768									// auxiliary clock frequency
#define	check_period	0.5										// wake-up period in 'sleep' state
#define	measure_period	0.05									// wake-up period in 'measurement' state
#define	TB_LONG			(int)floor((check_period*faclk))+1		// timer B value for wake-up in 'sleep' state
#define	TB_SHORT		(int)floor((measure_period*faclk))+1	// timer B value for wake-up in 'measurement' state

#define	FLOW_THRES		5	// flow threshold value - if the A/D conversion result is lower, flow is considered zero

#define	DataSize		22	// length of string for CDC communication

// application global variables
volatile bool SEND_flag = false;	// user flag is set when new data are ready to send
uint8_t DataToSend[DataSize];		// string for sending data

volatile uint16_t flow;				// global variable for flow A/D measurement
volatile uint16_t temp;				// global variable for temperature A/D measurement
volatile uint16_t Vcc;				// global variable for supply voltage measurement

// global variables for driverlib clock setting
uint32_t checkCLK = 0;				// variable to check master clock frequency


//application function prototypes
void init_clocks(uint32_t mclkFreq);	// Unified Clock System initialization function
void init_REF(void);					// REF module initialization function
void init_ADC(void);					// A/D converter initialization function
void init_timerB(void);					// timer B initialization function
void init_ports(void);					// initialization for unsused pins

void Start_f(void);						// function called in 'Start' state - to set up the measurement
void Send_f(void);						// function for sending data through CDC interface
void Stop_f(void);						// function called in 'Stop' state - to stop measurement and go to sleep state
void NumberPad(char* outstr, uint16_t number);	// function for number padding - sprintf() in CCS6 does not work properly

enum states {sleep, start, measurement, stop};
enum states state = sleep;				// initial state of state machine

// main function
int main(void) {
    WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer

    //init functions
    init_clocks(fMCLK);			// initialization of Unified Clock System module
    init_ports();				// initialization of unused ports
    init_REF();					// initialization of REF module
    init_ADC();					// initialization of A/D converter
    init_timerB();				// initialization of timer B

    if (USB_CDC == 1)
    {
    	PMM_setVCore(PMM_CORE_LEVEL_2);		// USB interface requires higher core voltage
    	USB_setup(TRUE,TRUE);  				// Init USB & events; if a host is present, connect
    }
    else
    {
    	PMM_setVCore(PMM_CORE_LEVEL_0);
    }

    enable_GI;
    //end of init functions

    _nop();						// for debugger

    while(1)					// infinite loop for state machine
    {
    	switch (state){			// state machine code

    	case sleep:
    		P1OUT &= ~(BIT2 + BIT3);	// for time measurement
    		GO_TO_SLEEP;
    		break;

    	case start:
    		Start_f();
    		state = measurement;
    		break;

    	case measurement:
    		if (SEND_flag == true)
    		{
    			P1OUT |= BIT5;
    			Send_f();
    			P1OUT &= ~BIT5;		// for time measurement
    		}
    		else
    		{
    			P1OUT &= ~(BIT2 + BIT3);	// for time measurement
    			GO_TO_SLEEP;
    		}
    		break;

    	case stop:
    		Stop_f();
    		state = sleep;
    		break;
    	}

    }

	return 0;
}

void Start_f(void)
{
	disable_GI;					// disable global interrupts

	TB0CTL &= ~(MC0 + MC1);		// stop the timer B
	TB0R = 0x00;				// clear value of TB counter
	TB0CCR0 = TB_SHORT;			// set short wake-up period for 'measurement' state
	TB0CTL |= MC_1;				// start the timer B again

	TB0CCTL0 &= ~CCIFG;			// clear timer B interrupt flag
	ADC12IV = 0x00;				// clear ADC interrupt flags

	enable_GI;					// enable global interrupts
}

void Stop_f(void)
{
	disable_GI;					// disable global interrupts

	TB0CTL &= ~(MC0 + MC1);		// stop the timer B
	TB0R = 0x00;				// clear value of TB counter
	TB0CCR0 = TB_LONG;			// set long wake-up period for 'sleep' state
	TB0CTL |= MC_1;				// start the timer B again

	P8OUT &= ~BIT2;				// turn OFF NTC measurement

	TB0CCTL0 &= ~CCIFG;
	ADC12IV = 0x00;				// clear ADC interrupt flags

	enable_GI;					// enable global interrupts
}

void Send_f(void)
{
	SEND_flag = false;			// clear SEND_flag
	volatile char VccStr[5];	// string for supply voltage value
	volatile char tempStr[5];	// string for temperature value
	volatile char flowStr[5];	// string for flow value

	disable_GI;							// disable global interrupts to ensure atomicity
	NumberPad((char*)VccStr, Vcc);		// formatting supply voltage value into given string
	NumberPad((char*)tempStr, temp);	// formatting temperature value into given string
	NumberPad((char*)flowStr, flow);	// formatting flow value into given string
	enable_GI;							// enable global interrupts

	sprintf((char*)DataToSend, "V=%s;T=%s;F=%s;\n", VccStr, tempStr, flowStr);	// insert formated strings into final string for sending data

	if (USBCDC_sendDataInBackground(DataToSend, DataSize, CDC0_INTFNUM, 1000))	// USB API function - send data through CDC
	{
		_NOP();		// If it fails, it'll end up here.  Could happen if
					// the cable was detached after the connectionState()
	}				// check, or if somehow the retries failed
}

void NumberPad(char* outstr, uint16_t number)		// function for formating numbers into strings - sprintf() is not working properly in CCS6
{
	if (number < 1000)
	{
		if (number > 100)
		{
			sprintf(outstr, " %d\0", number);
		}
		else if (number > 10)
		{
			sprintf(outstr, "  %d\0", number);
		}
		else
		{
			sprintf(outstr, "   %d\0", number);
		}
	}
	else
	{
		sprintf(outstr, "%d\0", number);
	}
}

void init_clocks(uint32_t mclkFreq)		// driverlib functions for setting clocks
{
    UCS_initClockSignal(
            UCS_FLLREF,
            UCS_REFOCLK_SELECT,
            UCS_CLOCK_DIVIDER_1);

    // Enable XT1
    P5SEL |= BIT4+BIT5;                       // Port select XT1
    UCSCTL6 &= ~(XT1OFF);                     // XT1 On
    UCSCTL6 |= XCAP_3;                        // Internal load cap

    // Loop until XT1 & DCO stabilizes
    do
    {
      UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG);
                                              // Clear XT2,XT1,DCO fault flags
      SFRIFG1 &= ~OFIFG;                      // Clear fault flags
    }while (SFRIFG1&OFIFG);                   // Test oscillator fault flag

    UCSCTL6 &= ~(XT1DRIVE_3);                 // Xtal is now stable, reduce drive
                                              // strength

    UCS_initClockSignal(
            UCS_ACLK,
            UCS_XT1CLK_SELECT,
            UCS_CLOCK_DIVIDER_1);


    UCS_initFLLSettle(
            mclkFreq / 1000,
            mclkFreq / 32768);


    checkCLK = UCS_getMCLK();			// check value of master clock frequency
}

void init_REF(void)						//REF module initialization - generating reference voltage for A/D converter
{
	REFCTL0 |= (REFMSTR + REFTCOFF);	// REF system controlled by REFCTL register, temperature sensor disabled to save power, REF module enabled.
    REFCTL0 &= ~(REFVSEL_0 + REFOUT);	// reference voltage set to 1.5V, no reference outputs

    REFCTL0 &= ~REFON;					// turn REF module off, will be turned on in Timer B ISR
}

void init_ADC(void)						// A/D converter initializaion
{
	P6SEL |= (BIT0 + BIT4);									// P6.0, P6.4 and P7.0 as ADC inputs
	P7SEL |= BIT0;
	ADC12CTL0 |= (ADC12SHT0_10 + ADC12MSC + ADC12ON);		// 512 ADC12CLK cycles for S/H time ( = 1 us), automatic following conversions in sequence (start sequence with one SHI), ADC12 enable
	ADC12CTL1 |= (ADC12CONSEQ_1 + ADC12SHP);				//sequence of channels selected, SAMPCON sourced from sampling timer
	/* the clock signal for ADC12_A is set as default - MODCLK, internal oscilator which is turned on on request and is powered
	 * down when not needed. Typical frequency of this oscilator is 4,8MHz. This has to be considered when setting ADC12SHT0_x
	 */

	ADC12MCTL0 |= (ADC12SREF_1 + ADC12INCH_0);
	ADC12MCTL1 |= (ADC12SREF_1 + ADC12INCH_4);
	ADC12MCTL2 |= (ADC12SREF_1 + ADC12INCH_12 + ADC12EOS);	// setting memory spaces with selected input channels, end of sequence on A12 input channel
	ADC12IE = BIT2;                           				// Enable ADC12IFG.2
	ADC12CTL0 &= ~ADC12ENC;                    				// disable conversions, will be enabled in Timer B ISR
}

void init_timerB(void)					// timer B initialization
{
	TBCCTL0 = CCIE;                           	// TBCCR0 interrupt enabled
	TB0CCR0 = TB_LONG;							// set long wake-up period - default value
	TB0CTL = TBSSEL_1 + MC_1 + TBCLR;           // ACLK as clock source for timer B, upmode, clear timer B register on interrupt
}

void init_ports(void)
{
	// switching unused ports to output direction - for lower power consumption
	P1OUT = 0x00;P2OUT = 0x00;P3OUT = 0x00;P4OUT = 0x00;P5OUT = 0x00;P8OUT = 0x00; PJOUT = 0x00;
	P1DIR = 0xFF;P2DIR = 0xFF;P3DIR = 0xFF;P4DIR = 0xFF;P5DIR = 0xFF;P8DIR = 0xFF;PJDIR = 0xFF;
}

#pragma vector=TIMERB0_VECTOR
__interrupt void TIMERB1_ISR(void)			// timer B interrupt routine
{
	P1OUT |= (BIT2 + BIT4);					// for time measurement

	LPM3_EXIT;								// exit LPM3 low power mode
	TB0CCTL0 &= ~CCIFG;						// clear timer B inerrupt flag
	TBCCTL0 &= ~CCIE;						//TBCCTL0 interrupt disable

	if (state == measurement)
	{
		P8OUT |= BIT2;						// turn ON NTC measurement
	}
	else
	{
		P8OUT &= ~BIT2;						// turn OFF NTC measurement
	}

	REFCTL0 |= REFON;						// REF module ON
	ADC12CTL0 |= ADC12ENC;					// A/D converter ON

	ADC12CTL0 |= ADC12SC;					// starting A/D conversion
}


#pragma vector=ADC12_VECTOR					// A/D converter interrupt routine
__interrupt void ADC12ISR (void)
{
    switch(__even_in_range(ADC12IV,34))
  {
  case  0: break;                           // Vector  0:  No interrupt
  case  2: break;                           // Vector  2:  ADC overflow
  case  4: break;                           // Vector  4:  ADC timing overflow
  case  6: break;                           // Vector  6:  ADC12IFG0
  case  8: break;                           // Vector  8:  ADC12IFG1
  case 10:		                            // Vector 10:  ADC12IFG2

	  LPM3_EXIT;							// DRIVE BYLO NA KONCI ISR!!!

	  P1OUT |= BIT3;						// for time measurement

	  if (ADC12MEM0 > FLOW_THRES)			// conditions for switching states of state machine
	  {
		  if (state == measurement)			// perform measurement, write A/D results into global variables
		  {
			  flow = ADC12MEM0;
			  Vcc = ADC12MEM1;
			  temp = ADC12MEM2;
			  SEND_flag = true;
		  }
		  else
		  {
			  state = start;				// if flow is higher than threshold and measurement was not started yet, start measurement
		  }
	  }
	  else
	  {
		  if (state == measurement)			// if flow is lower then threshold, stop the measurement
		  {
			  state = stop;
		  }
	  }

	  P8OUT &= ~BIT2;						// turn OFF NTC measurement

	  TB0CTL &= ~(MC0 + MC1);				//stop the timer B
	  TB0R = 0x00;							// clear value of TB counter
	  TB0CTL |= MC_1;						// start the timer B
	  TB0CCTL0 &= ~CCIFG;					// clear timer B interrupt
	  TBCCTL0 |= CCIE;						//TBCCTL0 interrupt enabled
	  ADC12IFG &= ~BIT2;					// clear ADC12IFG2

	  ADC12CTL0 &= ~ADC12SC;				// must be cleared to enssure proper triggering of another sequence

	  REFCTL0 &= ~REFON;					// REF module OFF
	  ADC12CTL0 &= ~ADC12ENC;				// A/D converter OFF

	  P1OUT &= ~BIT4;						// for time measurement

	  break;
  case 12: break;                           // Vector 12:  ADC12IFG3
  case 14: break;                           // Vector 14:  ADC12IFG4
  case 16: break;                           // Vector 16:  ADC12IFG5
  case 18: break;                           // Vector 18:  ADC12IFG6
  case 20: break;                           // Vector 20:  ADC12IFG7
  case 22: break;                           // Vector 22:  ADC12IFG8
  case 24: break;                           // Vector 24:  ADC12IFG9
  case 26: break;                           // Vector 26:  ADC12IFG10
  case 28: break;                           // Vector 28:  ADC12IFG11
  case 30: break;                           // Vector 30:  ADC12IFG12
  case 32: break;                           // Vector 32:  ADC12IFG13
  case 34: break;                           // Vector 34:  ADC12IFG14
  default: break;
  }
}




