/**************************************************************************************************
*
*  LIS3LV02DQ
*
*   Version:      1.0.1 - Mai 2009
*   Author:       Christoph Wartmann / chair for caad - ETH Zürich  /  wartmann[at].arch.ethz.ch
*                 Etienne Ribeiro    / tutorial assistant caad      /  eribeiro[at]ethz.ch
*
*   Desc:         Module to plug an LIS3LV02DQ Accelerator to your project. This Module makes it
*                 easy to run a LIS3LV02DQ Accelerator in either +-2g or +- 6g mode.
*
*   Protocol:     SPI
*
*   Pins:         VCC: 3.3V
*                 GND
*                 SDO:  Wiring: 27    Arduino Pro Mini: 12     MISO (SDO)
*                 SDA:  Wiring: 26    Arduino Pro Mini: 11     MOSI (SDA)
*                 SCL:  Wiring: 25    Arduino Pro Mini: 13     SCK (SCL)
*                 CS:   Wiring: 24    Arduino Pro Mini: 10     SS (CS)
*
*   Not yet implemented:
*                 - interrepts
*                 - I2C Interface
*                 - high pass filter
*                 - 16 bit mode (Big Endian Mode)
*                 - Self Test
*
*   Methodes:     boolean LIS3L_AppendSensor (int dataout, int datain, int spiclock, int slaveselect)
*                    Append Sensor. Must be called in setup()
*                    dataout, datain, spiclock, slaveselect: SPI-Pins (all digital pins)
*                    Starts Sensor in +-2g Mode.
*                 boolean LIS3L_AppendSensor (int dataout, int datain, int spiclock, int slaveselect, boolean use6g)
*                    Append Sensor. Must be called in setup()
*                    dataout, datain, spiclock, slaveselect: SPI-Pins (all digital pins)
*                    use6g: true = +-6g Mode, false = +-2g Mode
*                 void LIS3L_ConfigR1 (boolean turnOnDevice, byte datarate, boolean enableSelftest, boolean enableZ, boolean enableY, boolean enableX)
*                    Configuration Register 1. See Datasheet if you want to change any settings.
*                 void LIS3L_ConfigR2 (boolean use6g, boolean blockData, boolean bigEndian, boolean rebootMemory, boolean interruptEnable, boolean enableDataReadyGeneration, boolean Use3WireMode, boolean LeftJustified16bit)
*                    Configuration Register 2. See Datasheet if you want to change any settings.
*                 void LIS3L_ConfigR3 (boolean useExternalClock, boolean enableDirectionDetection, boolean enableFreeFallAndWakeUp, boolean enabledFilteredData, byte highPassFrequency)
*                    Configuration Register 3. See Datasheet if you want to change any settings.
*                 int LIS3L_Read(char which)
*                    Read values
*                    'x', 'y', 'z': Acceleration
*                    's': Current state of Sensor
*                    'a', 'b', 'c': Get Configuration
*                    'w': Get whoAmI. should be 3A
*                 int LIS3L_ParseToG (int val)
*                    Parse value to G. Call val = LIS3L_Read('x') and LIS3L_ParseToG(val).
*                 void LIS3L_PrintState (byte state)
*                    Print current state of your sensor to command line. Call state = LIS3L_Read ('s') and LIS3L_PrintState(state)
*
***************************************************************************************************/





//
// Const

#define whoAmI 15 // 0xF
#define reg1 32 // 0x20
#define reg2 33 // 0x21
#define reg3 34 // 0x22
#define stateReg 39 // 0x27
#define outXlow 40 // 0x28
#define outXhigh 41 // 0x29
#define outYlow 42 // 0x2A
#define outYhigh 43// 0x2B
#define outZlow 44// 0x2C
#define outZhigh 45// 0x2D
//
#define LIS3L_id 58// 0x3A
#define LIS3L_2g_factor 1024 // Divide value from register with this value to get 'g'
#define LIS3L_6g_factor 340 // Divide value from register with this value to get 'g'


//
// Var
int lis3l_DATAIN;       // Wiring: 27    Arduino Pro Mini: 12     MISO (SDO)
int lis3l_DATAOUT;      // Wiring: 26    Arduino Pro Mini: 11     MOSI (SDA)
int lis3l_SPICLOCK;     // Wiring: 25    Arduino Pro Mini: 13     SCK (SCL)
int lis3l_SLAVESELECT;  // Wiring: 24    Arduino Pro Mini: 10     SS (CS)
boolean lis3l_2g_mode = true;



// *************************
//
//  Functions
//


//
// LIS3L_AppendSensor

boolean LIS3L_AppendSensor (int dataout, int datain, int spiclock, int slaveselect) {

        LIS3L_AppendSensor (dataout, datain, spiclock, slaveselect, false);

}
boolean LIS3L_AppendSensor (int dataout, int datain, int spiclock, int slaveselect, boolean use6g) {

        // Variables
        lis3l_DATAOUT = dataout;
        lis3l_DATAIN = datain;
        lis3l_SPICLOCK = spiclock;
        lis3l_SLAVESELECT = slaveselect;


        // Pins
        pinMode (lis3l_DATAOUT, OUTPUT);
        pinMode (lis3l_DATAIN, INPUT);
        pinMode (lis3l_SPICLOCK,OUTPUT);
        pinMode (lis3l_SLAVESELECT,OUTPUT);
        digitalWrite (lis3l_SLAVESELECT,HIGH); //disable device


        // SPCR = 01010000
        // Set the SPCR register to 01010000
        //interrupt disabled,spi enabled,msb 1st,master,clk low when idle,
        //sample on leading edge of clk,system clock/4 rate
        SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPOL)|(1<<CPHA);


        // Config Device
        delay (10);
        LIS3L_ConfigR1 (true, 0, false, true, true, true);
        LIS3L_ConfigR2 (use6g, false, false, false, false, false, false, false);
        LIS3L_ConfigR3 (false, false, false, false, 0);


        // query the WHO_AM_I register of the LIS3LV02DQ
        // this should return 0x3A, a factory setting
        byte in_byte = read_register(whoAmI);
        if (in_byte != LIS3L_id)
        return false;


        // Return
        return true;

}


//
// LIS3L_ConfigR1
//  default: false, 0, false, true, true, true

void LIS3L_ConfigR1 (boolean turnOnDevice, byte datarate, boolean enableSelftest, boolean enableZ, boolean enableY, boolean enableX) {

        byte cmd = B00000000;
        if (turnOnDevice) // Turn on/off device
        cmd |= B11000000;
        if (datarate == 0) // 40 Hz (default)
        cmd |= B00000000;
        if (datarate == 1) // 160 Hz
        cmd |= B00010000;
        if (datarate == 2) // 640 Hz
        cmd |= B00100000;
        if (datarate == 3) // 2560 Hz
        cmd |= B00110000;
        if (enableSelftest) // Selftast on/off
        cmd |= B00001000;
        if (enableZ) // enable Z
        cmd |= B00000100;
        if (enableY) // enable Y
        cmd |= B00000010;
        if (enableX) // enable X
        cmd |= B00000001;


        // Write
        write_register(reg1, cmd);

}


//
// LIS3L_ConfigR2
//  default: false, false, false, false, false, false, false, false

void LIS3L_ConfigR2 (boolean use6g, boolean blockData, boolean bigEndian, boolean rebootMemory, boolean interruptEnable, boolean enableDataReadyGeneration, boolean Use3WireMode, boolean LeftJustified16bit) {

        byte cmd = B00000000;
        if (use6g){ // +-2g or +-6g (+-2g is default)
                cmd |= B10000000;
                lis3l_2g_mode = false;
        } else {
                lis3l_2g_mode = true;
        }
        if (blockData) // if for any reason it is not sure to read faster than output data rate it is recommended to set this bit to '1'
        cmd |= B01000000;
        if (bigEndian) // Little Endian (12bit, default) or Big Endian mode (16bit)
        cmd |= B00100000;
        if (rebootMemory) // Reboot Memory (Reload Registers from flash memory, same is done at power up)
        cmd |= B00010000;
        if (interruptEnable) // DRDY now is used for Interrupts (enableDataReadyGeneration must be true)
        cmd |= B00001000;
        if (enableDataReadyGeneration) // DRDY ebabled. By default DRDY-Pins is HIGH when new data is available, or if interruptEnable is true, DRDY-Pin is used as interrupt
        cmd |= B00000100;
        if (Use3WireMode) // Use SPI 4-wire (default) or SPI 3-wire mode
        cmd |= B00000010;
        if (LeftJustified16bit) //
        cmd |= B00000001;


        // Write
        write_register(reg2, cmd);

}


//
// LIS3L_ConfigR3
//  default: false, false, false, false, 0

void LIS3L_ConfigR3 (boolean useExternalClock, boolean enableDirectionDetection, boolean enableFreeFallAndWakeUp, boolean enabledFilteredData, byte highPassFrequency) {

        byte cmd = B00001000; // leave the 1!
        if (useExternalClock) // Use External Clock
        cmd |= B10000000;
        if (enableDirectionDetection) // enable Direction Detection
        cmd |= B01000000;
        if (enableFreeFallAndWakeUp) // enalbe Free-Fall and Wake-Up
        cmd |= B00100000;
        if (enabledFilteredData) // enable Filtered Data Selection
        cmd |= B00010000;
        if (highPassFrequency == 0) // Hpc = 512 (default) Frequency for DirectionDetection and FreeFallAndWakeUp
        cmd |= B00000000;
        if (highPassFrequency == 1) // Hpc = 1024
        cmd |= B00000001;
        if (highPassFrequency == 2) // Hpc = 2048
        cmd |= B00000010;
        if (highPassFrequency == 3) // Hpc = 4096
        cmd |= B00000011;


        // Write
        write_register(reg3, cmd);

}


//
// LIS3L_Read
//   'x', 'y', 'z', 's'->state, 'w'->whoAmI (8 Bit Dummie Identification for this Sensor), 'a'->config Reg1, 'b'->config Reg2, 'c'->config Reg3

int LIS3L_Read(char which) {

        //
        int val = 0;
        byte val_h;
        byte val_l;


        //
        switch(which) {
                case 'x':
                case 'X':

                val_h = read_register(outXhigh);
                val_l = read_register(outXlow);
                val = val_h;
                val <<= 8;
                val += val_l;

                break;

                case 'y':
                case 'Y':

                val_h = read_register(outYhigh);
                val_l = read_register(outYlow);
                val = val_h;
                val <<= 8;
                val += val_l;
                break;

                case 'z':
                case 'Z':

                val_h = read_register(outZhigh);
                val_l = read_register(outZlow);
                val = val_h;
                val <<= 8;
                val += val_l;
                break;

                case 's': // State
                case 'S':

                val_h = read_register(stateReg);
                val = val_h;
                break;

                case 'a': // Config Reg1
                case 'A':

                val_h = read_register(reg1);
                val = val_h;
                break;

                case 'b': // Config Reg2
                case 'B':

                val_h = read_register(reg2);
                val = val_h;
                break;

                case 'c': // Config Reg3
                case 'C':

                val_h = read_register(reg3);
                val = val_h;
                break;

                case 'w': // whoAmI
                case 'W':

                val_h = read_register(whoAmI);
                val = val_h;
                break;

                default:
                return 0; // Exit Function
                break;
        }


        // return
        return val;

}


//
// LIS3L_ParseToG
//   Parse Values from Register to mG

int LIS3L_ParseToG (int val) {

        long tmpval = val;
        if (lis3l_2g_mode)
        return tmpval * 1000 / LIS3L_2g_factor;
        return tmpval * 1000 / LIS3L_6g_factor;

}


//
// LIS3L_PrintState
//   Print state in natural language

void LIS3L_PrintState (byte state) {

        Serial.print("Status: ");
        Serial.print(state, BIN);
        Serial.print(": ");
        if (int(1<<7 & state) != 0) // byte 1000 0000
        Serial.print (" data overrun!");
        if (int(1<<6 & state) != 0) // byte 0100 0000
        Serial.print (" Z axis data overrun!");
        if (int(1<<5 & state) != 0) // byte 0010 0000
        Serial.print (" Y axis data overrun!");
        if (int(1<<4 & state) != 0) // byte 0001 0000
        Serial.print (" X axis data overrun!");
        if (int(1<<3 & state) != 0) // byte 0000 1000
        Serial.print (" new data available!");
        if (int(1<<2 & state) != 0) // byte 0000 0100
        Serial.print (" Z axis new data available!");
        if (int(1<<1 & state) != 0) // byte 0000 0010
        Serial.print (" X axis new data available!");
        if (int(1<<0 & state) != 0) // byte 0000 0001
        Serial.print (" Y axis new data available!");

}





// *************************
//
//  SPI Functions
//


char spi_transfer(volatile char data) {
        /*
        Writing to the SPDR register begins an SPI transaction
        */
        SPDR = data;
        /*
        Loop right here until the transaction is complete. the SPIF bit is
        the SPI Interrupt Flag. When interrupts are enabled, and the
        SPIE bit is set enabling SPI interrupts, this bit will set when
        the transaction is finished.
        */
        while (!(SPSR & (1<<SPIF)))
        {};
        // received data appears in the SPDR register
        return SPDR;
}

// reads a register
char read_register(char register_name) {
        char in_byte;
        // need to set bit 7 to indicate a read
        register_name |= 128;
        // SS is active low
        digitalWrite(SLAVESELECT, LOW);
        // send the address of the register we want to read first
        spi_transfer(register_name);
        // send nothing, but here's when the device sends back the register's value as an 8 bit byte
        in_byte = spi_transfer(0);
        // deselect the device
        digitalWrite(SLAVESELECT, HIGH);
        return in_byte;
}

// write to a register
void write_register(char register_name, byte data) {
        // char in_byte;
        // clear bit 7 to indicate we're doing a write
        register_name &= 127;
        // SS is active low
        digitalWrite(SLAVESELECT, LOW);
        // send the address of the register we want to write
        spi_transfer(register_name);
        // send the data we're writing
        spi_transfer(data);
        digitalWrite(SLAVESELECT, HIGH);
        //return in_byte;
}