#include <msp430.h>
#include <stdint.h>

/* Slave Address */
#define Slave_Addr 0x28

/* I2C Communication States */
typedef enum
{
    Idle,
    NACK,
    TX_Reg_Index,
    RX_Data,
    TX_Data,
    Switch_to_RX,
    Timeout
}I2C_State;

/* I2C Write and Read Functions */
I2C_State I2C_Write_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt);
I2C_State I2C_Read_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt);

/* Global Variables */
uint8_t I2C_Reg_Index = 0;
uint8_t *I2C_Reg_Data;
uint8_t TXByteCtr = 0;
uint8_t RXByteCtr = 0;
I2C_State Comm_State = Idle;
uint8_t Write_buffer[10];
uint8_t Read_buffer[10];


int main(void){

    WDTCTL = WDTPW | WDTHOLD;   // Stop watch dog timer

    /* Initialize clock system*/
    BCSCTL1=CALBC1_8MHZ;
    DCOCTL=CALDCO_8MHZ;
    BCSCTL2|=DIVS_2;            //SMCLK divider = 4;

    /* Initialize I2C pins */
    P1SEL|=BIT6+BIT7;
    P1SEL2|=BIT6+BIT7;          // P1.6=SCL (I2C) P1.7=SDA (I2C)

    /* Initialize USCI_B for I2C Communication */
    UCB0CTL1 |= UCSWRST;                      // Enable SW reset
    UCB0CTL1 |=UCSSEL_3 + UCTR;               // select SMCLK, transmitter mode
    UCB0CTL0 |=UCMODE_3 + UCMST + UCSYNC;     // set I2C MODE MASTER MODE
    UCB0BR0=20;                               // clk divider from SMCLK, 100kHz
    UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
    UCB0I2CIE |= UCNACKIE;                    // Enable NACK interrupt

    /* Initialize TimerA for timeout detection */
    TACCR0 = 10000;         // 5ms
    TACCTL0 |= CCIE;        // Enable CCR0 interrupts
    TACTL |= TASSEL_2;      // SMCLK = 2MHz

    __delay_cycles(7000000);    // Wait for slave to setup
    __enable_interrupt();       // Enable global interrupts

    /* Example of writing to slave */
    Write_buffer[0] = 0x80;
    Write_buffer[1] = 0x0C;
    I2C_Write_Reg(Slave_Addr, 0x3F, &Write_buffer[0], 1);   // Write 1 byte to slave register 0x3F
    if(Comm_State != Idle)
    {
        // Error during transmission
    }

    I2C_Write_Reg(Slave_Addr, 0x3D, &Write_buffer[1], 1);   // Write 1 byte to slave register 0x3D
    if(Comm_State != Idle)
    {
        // Error during transmission
    }

    /* Example of reading from slave */
    I2C_Read_Reg(Slave_Addr, 0x20, Read_buffer, 1);         // Read 1 byte from register 0x20
    if(Comm_State != Idle)
    {
        // Error during transmission
    }

    I2C_Read_Reg(Slave_Addr, 0x20, Read_buffer, 8);         // Read 8 bytes from register 0x20
    if(Comm_State != Idle)
    {
        // Error during transmission
    }

    __bis_SR_register(CPUOFF);                              // Enter LPM0 w/ interrupts
}



/* Input:

    dev_addr - slave I2C address

    reg_addr - address of the first register to be read

    *reg_data - pointer to the location where received data will be stored

    *cnt - number of bytes to be read

*/
I2C_State I2C_Read_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt)
{
    /* Initialize state machine */
    Comm_State = TX_Reg_Index;
    I2C_Reg_Index = reg_addr;
    I2C_Reg_Data = reg_data;
    RXByteCtr = cnt;
    TXByteCtr = 0;

    /* Initialize slave address and interrupts */
    UCB0I2CSA = dev_addr;
    IFG2 &= ~(UCB0TXIFG + UCB0RXIFG);       // Clear any pending interrupts
    IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
    IE2 |= UCB0TXIE;                        // Enable TX interrupt

    /* Start I2C communication */
    TACTL |= MC_1 + TACLR;                  // Start timeout count (5ms)
    while (UCB0CTL1 & UCTXSTP)              // Ensure stop condition got sent
    {
        if(Comm_State == Timeout)
            return Comm_State;
    }
    TACTL &= ~MC_1;                         // Stop timer
    TACTL |= MC_1 + TACLR;                  // Clear and restart timeout
    UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
    __bis_SR_register(CPUOFF);              // Enter LPM0 w/ interrupts
    TACTL &= ~MC_1;                         // Stop timer

    return Comm_State;

}

/* Input:

    dev_addr - slave I2C address

    reg_addr - address of the register to write

    *reg_data - pointer to the location with data to write

    *cnt - number of bytes to write

*/
I2C_State I2C_Write_Reg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t cnt)
{
    /* Initialize state machine */
    Comm_State = TX_Reg_Index;
    I2C_Reg_Index = reg_addr;
    I2C_Reg_Data = reg_data;
    TXByteCtr = cnt;
    RXByteCtr = 0;

    /* Initialize slave address and interrupts */
    UCB0I2CSA = dev_addr;
    IFG2 &= ~(UCB0TXIFG + UCB0RXIFG);       // Clear any pending interrupts
    IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
    IE2 |= UCB0TXIE;                        // Enable TX interrupt

    /* Start I2C communication */
    TACTL |= MC_1 + TACLR;                  // Start timeout count (5ms)
    while (UCB0CTL1 & UCTXSTP)              // Ensure stop condition got sent
    {
        if(Comm_State == Timeout)
            return Comm_State;
    }
    TACTL &= ~MC_1;                         // Stop timer
    TACTL |= MC_1 + TACLR;                  // Clear and restart timeout
    UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
    __bis_SR_register(CPUOFF);              // Enter LPM0 w/ interrupts
    TACTL &= ~MC_1;                         // Stop timer

    return Comm_State;
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
    TACTL &= ~MC_1;         // Disable timer
    Comm_State = Timeout;   // Signal that a timeout occurred
    __bic_SR_register_on_exit(CPUOFF);  // Exit LPM0
}

/* Start / Stop / NACK ISR */
#pragma vector = USCIAB0RX_VECTOR
__interrupt void USCIAB0RX_ISR(void)
{
    if ((UCB0STAT & UCNACKIFG))
    {//NACK received
        UCB0STAT &= ~UCNACKIFG;             // Clear NACK Flags
        UCB0CTL1 |= UCTXSTP;                // Send stop condition
        Comm_State = NACK;                  // Stop Communication and signal NACK
        IFG2 &= ~UCB0TXIFG;                 // Clear TXIFG USCI25 Workaround
        IE2 &= ~(UCB0TXIE + UCB0RXIE);      // Disable RX and TX interrupts
        __bic_SR_register_on_exit(CPUOFF);  // Exit LPM0
    }
}

/* RX and TX ISR */
#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_ISR(void)
{
    static uint8_t temp;
    if(IFG2 & UCB0RXIFG)
    { //RX Interrupt

        //must read RXBUF first for USCI30 Workaround
        temp = UCB0RXBUF;

        if(RXByteCtr)
        { //Receive byte
            *I2C_Reg_Data++ = temp;
            RXByteCtr--;
        }

        if(RXByteCtr == 1)
        { // Stop needs to be sent at N-1 byte
            UCB0CTL1 |= UCTXSTP;
        }
        else if(RXByteCtr == 0)
        { // Received last byte, disable interrupts and return
            IE2 &= ~UCB0RXIE;
            Comm_State = Idle;
            __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
        }
    }
    else if (IFG2 & UCB0TXIFG)
    { //TX Interrupt

        /* Handle TXIFG based on state of communication */
        switch(Comm_State)
        {
            case TX_Reg_Index: // Transmit register index to slave
                UCB0TXBUF = I2C_Reg_Index;    // Send data byte
                if(RXByteCtr)
                    Comm_State = Switch_to_RX;    // Next state is to receive data
                else
                    Comm_State = TX_Data;         // Next state is to transmit data
                break;

            case Switch_to_RX:  // Switch to receiver
                IE2 |= UCB0RXIE;              // Enable RX interrupt
                IE2 &= ~UCB0TXIE;             // Disable TX interrupt
                UCB0CTL1 &= ~UCTR;            // Switch to receiver
                Comm_State = RX_Data;         // State state is to receive data
                UCB0CTL1 |= UCTXSTT;          // Send repeated start
                if(RXByteCtr == 1)
                { // Only receiving 1 byte, need to send stop immediately after start
                    while((UCB0CTL1 & UCTXSTT));
                    UCB0CTL1 |= UCTXSTP;     // Send stop condition
                }
                break;

            case TX_Data: // Transmit byte to slave
                if(TXByteCtr)
                { // Send byte
                    UCB0TXBUF = *I2C_Reg_Data++;
                    TXByteCtr--;
                }
                else
                { // Done transmitting data
                    UCB0CTL1 |= UCTXSTP;     // Send stop condition
                    Comm_State = Idle;
                    IE2 &= ~UCB0TXIE;                       // disable TX interrupt
                    __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
                }
                break;

            default:
                __no_operation();
                break;
        }
    }
}

/* Trap ISR for USCI29 Workaround */
#pragma vector= TRAPINT_VECTOR
__interrupt void TRAPINT_ISR(void)
{
    __no_operation();   // Add breakpoint
}

