/**
 * @file main.c
 * @brief ISR continuous conversion using timer
 *
 * Timer B0 periodically (2Hz) triggers the conversion
 * on channel A0 of ADC12, which is connected to a potentiometer.
 * 8 bits of the conversion result are sent to double 7seg LED,
 * at the same time defining the duty cycle of PWM on TA0CCR2 OUT.
 *
 * @date 2021
 * @author Marija Bezulj (meja@etf.bg.ac.rs)
 *
 * @version [1.0 @ 05/2021] Initial version
 */
#include <msp430.h>
#include <stdint.h>
#include "function.h"

/**
 * @brief Timer period for ADC12 conversion triggering
 *
 * Timer is clocked by ACLK (32768Hz).
 * A 8x divider is set.
 * We want ~500ms period, so use 2048 for CCR1
 */
#define CONV_PERIOD        (2048)  /* 500ms */
/**
 * @brief Display mux period
 *
 * ACLK is used
 */
#define MUX_PERIOD        (163)  /* ~5ms (4.97ms)  */
/**
 * @brief PWM period 1s
 */
#define PWM_PERIOD      (32768)  /* 1s */

/** variable where conversion result is placed */
volatile uint8_t data = 0;

/** new data read flag */
volatile uint8_t ndrf = 0;

/** variable where duty cycle is placed */
volatile uint16_t dutyclc = 0;

/**
 * @brief array of digits used for display
 */
static volatile uint8_t digits[2] = {0};


/**
 * @brief Main function
 *
 * ports are set for 7seg usage
 * TA1 does display multiplexing
 * TB0 drives the conversion with 2Hz
 * ADC12 works in continious mode
 * TA0 drives the PWM on CCR2 OUT
 */
int main(void)
{
    WDTCTL = WDTPW | WDTHOLD;       // Stop watchdog timer

    // sevenseg 1
    P7DIR |= BIT0;              // set P7.0 as out (SEL1)
    P7OUT |= BIT0;              // disable display 1
    // sevenseg 2
    P6DIR |= BIT4;              // set P6.4 as out (SEL2)
    P6OUT |= BIT4;              // disable display 2
    // a,b,c,d,e,f,g
    P2DIR |= 0x48;              // configure P2.3 and P2.6 as out
    P3DIR |= BIT7;              // configure P3.7 as out
    P4DIR |= 0x09;              // configure P4.0 and P4.3 as out
    P8DIR |= 0x06;              // configure P8.1 and P8.2 as out
    // init TA1 as compare in up mode
    TA1CCR0 = MUX_PERIOD;     // set timer period in CCR0 register
    TA1CCTL0 = CCIE;            // enable interrupt for TA1CCR0
    TA1CTL = TASSEL__ACLK | MC__UP; //clock select and up mode

    /* ADC12_A channel A0 init */
    P6SEL |= BIT0;              // input pin P6.0, pot2
                                // set pin P6.0 as alternate function - A0 analog

    ADC12CTL0 &= ~ADC12ENC;     // disable ADC before configuring
    /* 32 cycles for sampling, ref voltage is set by REF module, ADC ON */
    ADC12CTL0 |= ADC12ON;
    ADC12CTL1 |= ADC12SHS_2 + ADC12SSEL_3 + ADC12CONSEQ_2;
                                      // start address ADC12MEM0, timer starts conv
                                      // single channel continuous conversion, SMCLK clock
                                      // SAMPCON sourced from the sampling timer
    ADC12CTL2 &= ~ADC12RES_2;   // 8 bit conversion result
    ADC12MCTL0 |= ADC12INCH_0;   //reference AVCC and AVSS, channel A0
    ADC12CTL0 |= ADC12ENC;      // enable ADC12
    ADC12IE |= ADC12IE0;        // enable interrupt when MEM0 is written


    /* initialize Timer B0 for ADC12 trigger */
    // CBOUT CCR0 TB0 -> ADC12SHS2 this is internal, ports and pins not used (P5.6)
    TB0CCR0 = (CONV_PERIOD/2  - 1);// f_OUT = 2 Hz, f_ACLK = 32768 Hz ID=8 => T_OUT = 2048.
                                   // TOGGLE outmod => T_OUT = 2 * T_CCR0 =>
                                   // T_CCR0 = T_OUT/2 = 1024 (TA0CCR0 = T_CCR0 - 1)
    TB0CCTL0 |= OUTMOD_4;    // toggle mode
    TB0CTL |= TBSSEL__ACLK + ID__8 + MC__UP;

    /* init timerA0 with PWM out on LD2 through TA0 CCR2 */
    TA0CCR0 = PWM_PERIOD - 1;       // init pwm period
    // timer is in compare mode with active OUT signal
    TA0CCR2 = dutyclc;          // initial state is no pulse
    TA0CCTL2 = OUTMOD_7;        // outmode is Reset/Set
                                // CCR2 value defines the pulse width
    // init P1.3 pin as alternate function pin
    P1SEL |= BIT3;              // alternate function
    P1DIR |= BIT3;              // P1.3 is TA0.2 pin
    // activate timer
    TA0CTL = TASSEL__ACLK | MC__UP;

    __enable_interrupt();

    uint8_t tmp;
    while(1){
        if (ndrf != 0)
        {
            ndrf = 0;
            for (tmp = 0; tmp < 2; tmp++)
            {
                digits[tmp] = data % 16;
                data /= 16;
            }
        }

    }
}

/**
 * @brief TA1CCR0 ISR
 *
 * Multiplex the 7seg display. Each ISR activates one digit.
 */
void __attribute__ ((interrupt(TIMER1_A0_VECTOR))) TA1CCR0ISR (void)
{
    static uint8_t current_digit = 0;

    /* algorithm:
     * - turn off previous display (SEL signal)
     * - set a..g for current display
     * - activate current display
     */
    if (current_digit == 1)
    {
        P6OUT |= BIT4;          // turn off SEL2
        WriteLed(digits[current_digit]);    // define seg a..g
        P7OUT &= ~BIT0;         // turn on SEL1
    }
    else if (current_digit == 0)
    {
        P7OUT |= BIT0;
        WriteLed(digits[current_digit]);
        P6OUT &= ~BIT4;
    }
    current_digit = (current_digit + 1) & 0x01;

    return;
}

/**
 * @brief ADC12_A ISR
 *
 * When conversion is over, modify the TA0 PWM duty cycle
 */
void __attribute__ ((interrupt(ADC12_VECTOR))) ADC12ISR (void)
{
    switch (ADC12IV)
    {
    case ADC12IV_ADC12IFG0:
        // change TA0CCR2 duty cycle on the run, timer stop not needed
        data = ADC12MEM0 & 0xff;
        ndrf = 1;
        dutyclc = data * (PWM_PERIOD/0xff);
        TA0CCR2 = dutyclc;
        break;
    default:
        break;
    }
}
