#include <msp430.h>
#include <stdint.h>
/*
 *                            COPYRIGHT
 *
 *  bmp581.c
 *  Copyright (C) 2026 Exstrom Laboratories LLC
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  A copy of the GNU General Public License is available on the internet at:
 *  http://www.gnu.org/copyleft/gpl.html
 *
 *  or you can write to:
 *
 *  The Free Software Foundation, Inc.
 *  675 Mass Ave
 *  Cambridge, MA 02139, USA
 *
 *  Exstrom Laboratories LLC contact:
 *  stefan(AT)exstrom.com
 *
 *  Exstrom Laboratories LLC
 *  Longmont, CO 80503, USA
 *
 */
// bmp581 defines
#define BMP581_ADDR      0x47    // BMP581 i2c slave address
#define REG_ID           0x01    // id register should read 0x50
#define REG_STATUS       0x28    // status register
#define REG_OSR_CONFIG   0x36    // oversampling register
#define REG_ODR_CONFIG   0x37    // output data rate register
#define REG_TEMP_DATA    0x1D    // temperature data register, 3 bytes: XLSB, LSB, MSB
#define REG_PRESS_DATA   0x20    // pressure data register, 3 bytes: XLSB, LSB, MSB
#define OSR_CFG_VALUE    0x72    // press=64x  temp=4x
#define ODR_NORMAL_1HZ   0x71    // ODR[4:0]=0x1C = 1 Hz, MODE[1:0]=01 normal */

#define DEBOUNCE_TICKS  10

// LCD defines
#define pos1 4          // Digit A1 - L4
#define pos2 6          // Digit A2 - L6
#define pos3 8          // Digit A3 - L8
#define pos4 10         // Digit A4 - L10
#define pos5 2          // Digit A5 - L2
#define pos6 18         // Digit A6 - L18

uint8_t buf[6] = {0}; // i2c buffer
uint8_t count = 0;

float refpress = 101325.0;  // reference pressure for altitude calculation
float press = 101325.0;  // last pressure reading
float ctemp = 25.0;  // last temperature reading in Celsius
float ftemp = 77.0;  // last temperature reading in Fahrenheit
float z = 0.0;  // last altitude reading

enum state {temperature, pressure, altitude};
enum state tpa = temperature;

enum tunit {celsius, fahrenheit};
enum tunit cf = fahrenheit;

enum zunit {feet, meter};
enum zunit fm = feet;

uint8_t d[6] = {0}; // lcd digits
const char digit[10] =  // lcd digit definitions
  {
    0xFC,      // "0"
    0x60,      // "1"
    0xDB,      // "2"
    0xF3,      // "3"
    0x67,      // "4"
    0xB7,      // "5"
    0xBF,      // "6"
    0xE4,      // "7"
    0xFF,      // "8"
    0xF7       // "9"
  };

void DisplayTemp(float x)
{
  if(x<0){ x = -x; LCDMEM[5] = 0x04; }
  else LCDMEM[5] = 0x00;
  LCDMEM[3] = 0x01;  // set decimal point
  LCDMEM[pos1] = 0x00;
  LCDMEM[pos2] = 0x00;
  LCDMEM[pos3] = 0x00;

  uint8_t i;
  uint32_t xi = (uint32_t)x;
  uint32_t xf = 100*(x - xi);  // only keep 2 decimal places
  d[0] = xf % 10;
  d[1] = xf / 10;
  for(i=2; i<5; ++i){
      d[i] = xi % 10;
      xi /= 10;
  }
  if(d[0] > 5){
    ++d[1];
    if(d[1] == 10){
      d[1] = 0;
      ++d[2];
      if(d[2] == 10){
        d[2] = 0;
        ++d[3];
        if(d[3] == 10){
          d[3] = 0;
          ++d[4];
        }
      }
    }
  }
  LCDMEM[pos6] = digit[d[1]];
  LCDMEM[pos5] = digit[d[2]];
  LCDMEM[pos4] = digit[d[3]];
  LCDMEM[pos3] = (d[4] == 0 ? 0x00 : digit[d[4]]);
}

void DisplayPress(float x)
{
  if(x<0){ x = -x; LCDMEM[5] = 0x04; }
  else LCDMEM[5] = 0x00;
  LCDMEM[3] = 0x00;  // clear decimal point

  uint8_t i;
  uint32_t xi = (uint32_t)x;
  for(i=0; i<6; ++i){
      d[i] = xi % 10;
      xi /= 10;
  }
  LCDMEM[pos6] = digit[d[0]];
  LCDMEM[pos5] = digit[d[1]];
  LCDMEM[pos4] = digit[d[2]];
  LCDMEM[pos3] = digit[d[3]];
  LCDMEM[pos2] = digit[d[4]];
  LCDMEM[pos1] = (d[5] == 0 ? 0x00 : digit[d[5]]);
}

int main(void)
{
  union {
    int32_t value;
    uint8_t bytes[4];
  } data;

  WDTCTL = WDTPW | WDTHOLD;
  P4SEL0 |= BIT1 | BIT2;                            // P4.2~P4.1: crystal pins
  do{
    CSCTL7 &= ~(XT1OFFG | DCOFFG);                  // Clear XT1 and DCO fault flag
    SFRIFG1 &= ~OFIFG;
  }while (SFRIFG1 & OFIFG);                         // Test oscillator fault flag
  CSCTL6 = (CSCTL6 & ~(XT1DRIVE_3)) | XT1DRIVE_2;   // drive strength for XT1 oscillator

  SYSCFG2 |= LCDPCTL;                                        // R13/R23/R33/LCDCAP0/LCDCAP1 pins selected
  LCDPCTL0 = 0xFFFF;
  LCDPCTL1 = 0x07FF;
  LCDPCTL2 = 0x00F0;                                         // L0~L26 & L36~L39 pins selected
  LCDCTL0 = LCDSSEL_0 | LCDDIV_7;                            // flcd ref freq is xtclk
  LCDVCTL = LCDCPEN | LCDREFEN | VLCD_6 | (LCDCPFSEL0 | LCDCPFSEL1 | LCDCPFSEL2 | LCDCPFSEL3);
  LCDMEMCTL |= LCDCLRM;                                      // Clear LCD memory
  LCDCSSEL0 = 0x000F;                                        // Configure COMs and SEGs
  LCDCSSEL1 = 0x0000;                                        // L0, L1, L2, L3: COM pins
  LCDCSSEL2 = 0x0000;
  LCDM0 = 0x21;                                              // L0 = COM0, L1 = COM1
  LCDM1 = 0x84;                                              // L2 = COM2, L3 = COM3
  LCDMEM[pos1] = 0x00;
  LCDMEM[pos2] = 0x00;
  LCDMEM[pos3] = digit[0];
  LCDMEM[pos4] = digit[0];
  LCDMEM[pos5] = digit[0];
  LCDMEM[pos6] = digit[0];
  LCDCTL0 |= LCD4MUX | LCDON;                                // Turn on LCD, 4-mux selected

// Setup button on p1.2 — internal pull-up, falling-edge interrupt
  P1DIR  &= ~BIT2;
  P1REN  |=  BIT2;
  P1OUT  |=  BIT2;
  P1IES  |=  BIT2;
  P1IE   |=  BIT2;

// Setup button on p2.6 — internal pull-up, falling-edge interrupt
  P2DIR  &= ~BIT6;
  P2REN  |=  BIT6;
  P2OUT  |=  BIT6;
  P2IES  |=  BIT6;
  P2IE   |=  BIT6;

  UCB0CTLW0 |= UCSWRST;  // put UCB0 into software reset
  UCB0CTLW0 |= UCSSEL_3; // select SMCLK clock source
  UCB0CTLW0 |= UCSYNC; // synchronus mode
  UCB0CTLW0 |= UCMODE_3 | UCMST; // put into i2c master mode
  UCB0CTLW1 |= UCASTP_2; // generate stop after UCB0TBCNT bytes transmitted
  UCB0I2CSA = BMP581_ADDR; // set slave address
  UCB0BRW = 10; // clock divider
  P5SEL0 |= BIT2; // set primary module function for P5.2
  P5SEL0 |= BIT3; // set primary module function for P5.3

  PMMCTL0_H = PMMPW_H;  // Allow write to PMM registers
  PM5CTL0 &= ~LOCKLPM5; // unlock GPIO
  P1IFG  &= ~BIT2;  // clear any spurious interrupt flag on P1.2
  P2IFG  &= ~BIT6;  // clear any spurious interrupt flag on P1.2

  UCB0CTLW0 &= ~UCSWRST; // take UCB0 out of software reset
  UCB0IE |= UCTXIE0;  // enable transmit interrupt
  UCB0IE |= UCRXIE0;  // enable recieve interrupt
  __enable_interrupt();

  // Set over sampling rate
  buf[0] = REG_OSR_CONFIG;
  buf[1] = OSR_CFG_VALUE;
  count = 0;
  UCB0TBCNT = 2; // number of bytes to transmit before generating stop
  UCB0CTLW0 |= UCTR; // set to transmit mode
  UCB0CTLW0 |= UCTXSTT; // generate start
  while((UCB0IFG & UCSTPIFG) == 0){}  // wait for stop flag
  UCB0IFG &= ~UCSTPIFG;  // clear stop flag

  // Set output data rate
  buf[0] = REG_ODR_CONFIG;
  buf[1] = ODR_NORMAL_1HZ;
  count = 0;
  UCB0TBCNT = 2;  // number of bytes to transmit before generating stop
  UCB0CTLW0 |= UCTR; // set to transmit mode
  UCB0CTLW0 |= UCTXSTT; // generate start
  while((UCB0IFG & UCSTPIFG) == 0){}  // wait for stop flag
  UCB0IFG &= ~UCSTPIFG;  // clear stop flag

  while(1){
    // send slave address then register address
    buf[0] = REG_TEMP_DATA;
    count = 0;
    UCB0TBCNT = 1;  // we are only writing the register address
    UCB0CTLW0 |= UCTR;  // put into transmit mode
    UCB0CTLW0 |= UCTXSTT;  // issue start
    while((UCB0IFG & UCSTPIFG) == 0){}  // wait for stop flag
    UCB0IFG &= ~UCSTPIFG;  // clear stop flag

    // send slave address then read data in register
    count = 0;
    UCB0TBCNT = 6;  // we want to read 6 bytes
    UCB0CTLW0 &= ~UCTR;  // put into recieve mode
    UCB0CTLW0 |= UCTXSTT;  // issue start
    while((UCB0IFG & UCSTPIFG) == 0){}  // wait for stop flag
    UCB0IFG &= ~UCSTPIFG;  // clear stop flag

    // decode temperature data
    data.bytes[0] = buf[0];
    data.bytes[1] = buf[1];
    data.bytes[2] = buf[2];
    data.bytes[3] = (buf[2] < 128 ? 0x00 : 0xFF);
    ctemp = (float)data.value/65536.0;  // temperature in Celsius
    ftemp = 1.8*ctemp+32.0;  // temperature in Fahrenheit

    // decode pressure data
    data.bytes[0] = buf[3];
    data.bytes[1] = buf[4];
    data.bytes[2] = buf[5];
    data.bytes[3] = (buf[5] < 128 ? 0x00 : 0xFF);
    press = (float)data.value/64.0;  // pressure in Pascals

    switch(tpa){
      case temperature:
      {
        DisplayTemp(cf == celsius ? ctemp : ftemp);
        break;
      }
      case pressure:
      {
        DisplayPress( press );
        break;
      }
      case altitude:
      {
        z = refpress/press;
        z = 3*(z-1)*(z+1)/((z+1)*(z+3)-2); // calculate ln(z)
        z = 29.358*(ctemp+273.15)*z;  // elevation in meters
        DisplayPress(fm == feet ? 3.28084*z : z);
        break;
      }
      default:
        break;
    }
  }
}

#pragma vector = USCI_B0_VECTOR
__interrupt void USCIB0_ISR(void)
{
  switch(UCB0IV){
  case 0x16:   // RXIFG0
    buf[count] = UCB0RXBUF;
    ++count;
    break;
  case 0x18:   // TXIFG0
    UCB0TXBUF = buf[count];
    ++count;
    break;
  default:
    break;
  }
}

#pragma vector = PORT1_VECTOR
__interrupt void Port1_ISR(void)
{
  if (P1IFG & BIT2){
    P1IFG &= ~BIT2;  /* Clear flag                       */
    /* Disable P1.2 interrupt so that bounces are ignored while timer runs */
    P1IE  &= ~BIT2;
    TA0CTL = TASSEL__ACLK    // Clock source: ACLK (VLO
          | ID_3            // Divide clock by 8
          | MC_0            // Halt timer
          | TACLR;          // Reset TAR and dividers
    TA0EX0   = TAIDEX_3;        // Divide clock by another 4 for total divide by 32
    TA0CCR0  = DEBOUNCE_TICKS;  // Compare value → ~22 ms
    TA0CCTL0 = CCIE;            // Enable CCR0 interrupt
    TA0CTL  |= MC__UP;          // Start timer in Up mode
  }
}

#pragma vector = PORT2_VECTOR
__interrupt void Port2_ISR(void)
{
  if (P2IFG & BIT6){
    P2IFG &= ~BIT6;  /* Clear flag                       */
    /* Disable P2.6 interrupt so that bounces are ignored while timer runs */
    P2IE  &= ~BIT6;
    TA0CTL = TASSEL__ACLK    // Clock source: ACLK (VLO
          | ID_3            // Divide clock by 8
          | MC_0            // Halt timer
          | TACLR;          // Reset TAR and dividers
    TA0EX0   = TAIDEX_3;        // Divide clock by another 4 total divide by 32
    TA0CCR0  = DEBOUNCE_TICKS;  // Compare value → ~22 ms
    TA0CCTL0 = CCIE;            // Enable CCR0 interrupt
    TA0CTL  |= MC__UP;          // Start timer in Up mode
  }
}

#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR(void)
{
  /* Stop the timer */
  TA0CTL   &= ~MC_3;                 /* MC = 0 → halt                    */
  TA0CCTL0 &= ~CCIE;                 /* Disable CCR0 interrupt           */

  /* Validate: button still pressed? (pin low = pressed) */
  if (!(P1IN & BIT2)){
    if(tpa == temperature)
      tpa = pressure;
    else if(tpa == pressure)
      tpa = altitude;
    else
      tpa = temperature;
  }

  if (!(P2IN & BIT6)){
    if(tpa == temperature){
      if(cf == celsius)
        cf = fahrenheit;
      else
        cf = celsius;
    }
    else if(tpa == pressure)
      refpress = press;
    else{
      if(fm == feet)
        fm = meter;
      else
        fm = feet;
    }
  }

  P1IFG &= ~BIT2;   // Clear p1.2 interrupt flag
  P1IE  |=  BIT2;   // Re-enable p1.2 interrupt
  P2IFG &= ~BIT6;  // p2.6 interrupt flag
  P2IE  |=  BIT6;  // Re-enable p2.6 interrupt
}
