Library to communicate with LDC1614

Dependencies:   SHTx

Dependents:   Inductive_Sensor_3

Fork of LDC1101 by Bob Giesberts

LDC1614.cpp

Committer:
bobgiesberts
Date:
2016-10-05
Revision:
36:6a4e28dadae6
Parent:
35:00c9c01f0c0f

File content as of revision 36:6a4e28dadae6:

/** LDC1614 library
* @file LDC1614.cpp
* @brief this C++ file contains all required
* functions to interface with Texas
* Instruments' LDC1614.
*
* @author Bob Giesberts
*
* @date 2016-08-09
*
* @code
* Serial pc(USBTX, USBRX);
* LDC1614 ldc(PTC5, PTC4, PTC6, 16E6, 2, 120E-12);
* int main(){
*    while(1) {
*       while( !ldc.is_ready() ) {}
*    
*       pc.printf("sensor 1: %d | sensor 2: %d\r\n", ldc.get_Data(0), ldc.get_Data(1) );
*    }
* }
* @endcode
*/

#include "LDC1614.h"

LDC1614::LDC1614(PinName sda, PinName scl, PinName sd, uint8_t f_CLKIN, int channels, uint8_t capacitor) : _i2c(sda, scl), _shutdown_pin(sd)
{
    // settings
    _channels = channels;     // number of sensors
    _cap      = capacitor;
    _fCLKIN   = f_CLKIN;

    _Offset         = 0;      // highest 16-bit of 32-bit number (so e.g. 100E6 / 65536 = 1525) 
    _Rcount         = 0xffff; // maximum for greatest precision (0xffff)
    _SettleCount    = 50;     // CHx_SETTLECOUNT = t_settle * f_REFx/16 = 50 (p.12)
    _DriveCurrent   = 23;     // max = 31, automatically settles at 22
    _dividerREF     = 2;      // 2 (f_REF = f_CLKIN/2  =  40/2 = 20  < 35) (p.10)
    _dividerIN      = 2;      // 2 (f_IN  = f_SENSOR/2 = 6.5/2 = 3.4 < 5 = 20/4 = f_REF/4
    
    for(int i = 0; i < channels; i++)
        error[i] = 0;
       
    // start communication
    _i2c.setFrequency( 400000 ); // max 400 kHz (p.6)
    
    // Initilialize the LDC1614
    init();
}

LDC1614::~LDC1614()
{

}

void LDC1614::init()
{
    /********* SETTINGS *****************
    ** C_sensor     =   120 pF
    ** L_sensor     =     5 uH
    ** Rp_min       =  1500 Ohm
    **
    ** f_sensor_min =     6.4 MHz (d = inf)
    ** f_sensor_max =    10   MHz (d = 0)
    ** 
    ************************************/
    
    // Configuring setup, set LDC in configuration modus
    sleep();
    
    for(int i = 0; i < _channels; i++)
    {
        // set Reference Count to highest resolution
        setReferenceCount( i, _Rcount );

        // set the settling time
        // t_settle = (settlecount * 16) /f_REF
        // settlecount > Q * f_REF / (16 * f_SENSOR)
        setSettlecount( i, _SettleCount );
        
        // set Divider to 1 (for large range / ENOB / resolution)
        setDivider( i, _dividerIN, _dividerREF ); 
        
        // set the drive current during sampling 
        setDriveCurrent( i, _DriveCurrent );   // (p. 15 | Figure 14)
        
        // shift the signal down a bit
        setOffset( i, _Offset );
    }

    // error_config (does not work?)
    set( ERROR_CONFIG, UR_ERR2OUT,    1 );
    set( ERROR_CONFIG, OR_ERR2OUT,    1 );
    set( ERROR_CONFIG, WD_ERR2OUT,    1 );
    set( ERROR_CONFIG, AH_ERR2OUT,    1 );
    set( ERROR_CONFIG, AL_ERR2OUT,    1 );    
    
    // mux_config    
    set( MUX_CONFIG, AUTOSCAN_EN,     _channels > 1 );
    set( MUX_CONFIG, RR_SEQUENCE,     ( ( _channels - 2 > 0 ) ? _channels - 2 : 0 ) );
    set( MUX_CONFIG, DEGLITCH,        DEGLITCH_10M );
    
    // override Rp and use own Drive Current to reduce power consumption
    set( CONFIG, ACTIVE_CHAN,         0 ); // CH0. Will be overruled when _channels > 1
    set( CONFIG, RP_OVERRIDE_EN,      1 );
    set( CONFIG, SENSOR_ACTIVATE_SEL, 1 );
    set( CONFIG, AUTO_AMP_DIS,        1 );
    set( CONFIG, REF_CLK_SRC,         1 ); // external f_CLKIN
    set( CONFIG, INTB_DIS,            1 );
    set( CONFIG, HIGH_CURRENT_DRV,    0 );
    
    // Done configuring settings, set LDC1614 in measuring modus
    wakeup();
}


void LDC1614::func_mode( LDC_MODE mode )
{ 
    switch (mode)
    {
        case LDC_MODE_ACTIVE:
        case LDC_MODE_SLEEP:

            // turn on LDC
            _shutdown_pin.write( 0 );
            wait_us(10);
            set( CONFIG, SLEEP_MODE_EN, mode );
            wait_us(377); // Wait 16384 f_INT clock cycles (0.377 ms) (p.16)
            wait_ms(1);
            break;
        
        case LDC_MODE_SHUTDOWN:
            _shutdown_pin.write( 1 );
            break;
    }
}
void LDC1614::sleep( void )    { func_mode( LDC_MODE_SLEEP );    }
void LDC1614::wakeup( void )   { func_mode( LDC_MODE_ACTIVE );   }
void LDC1614::shutdown( void ) { func_mode( LDC_MODE_SHUTDOWN ); }

void LDC1614::setReferenceCount( uint8_t channel, uint16_t rcount )
{
    writeI2Cregister( RCOUNT_CH0 + channel, rcount );
    // debug("[ReferenceCount channel %d: 0x%02x]\r\n", channel, get_ReferenceCount( channel ) );
}

void LDC1614::setOffset( uint8_t channel, uint16_t offset )
{
    _Offset = offset;
    writeI2Cregister( OFFSET_CH0 + channel, offset );
}

void LDC1614::setSettlecount( uint8_t channel, uint16_t settlecount )
{
    writeI2Cregister( SETTLECOUNT_CH0 + channel, settlecount );    
}

void LDC1614::setDivider( uint8_t channel, uint8_t divIN, uint8_t divREF )
{
    // make sure the values fit
    _dividerIN  = (( divIN <  15) ? (( divIN > 1) ? divIN  : 1) :  15 ); // 4 bit
    _dividerREF = ((divREF < 255) ? ((divREF > 1) ? divREF : 1) : 255 ); // 8 bit
    writeI2Cregister( CLOCK_DIVIDERS_CH0 + channel, uint16_t ((_dividerIN << CHx_FIN_DIVIDER) | (_dividerREF << CHx_FREF_DIVIDER)) );
} 

void LDC1614::setDriveCurrent( uint8_t channel, uint8_t idrive )
{   
    _DriveCurrent = ((idrive < 31) ? idrive : 31 ); // 5-bit (b1 1111)
    regchange( DRIVE_CURRENT_CH0 + channel, CHx_IDRIVE, _DriveCurrent, 31 );    
    // debug("[DriveCurrent channel %d: 0x%02x]\r\n", channel, get_DriveCurrent( channel ) );
}

uint8_t LDC1614::autoDriveCurrent( uint8_t channel )
{
    // set automatic calculation mode
    sleep();
    set( CONFIG, AUTO_AMP_DIS, 0);
    wakeup();

    // find the right register based on the channel
    uint8_t tempDriveCurrent = 0;
    ADDR addr;
    switch ( channel ){        
        case 1:  addr = DRIVE_CURRENT_CH1; break;    
        case 2:  addr = DRIVE_CURRENT_CH2; break;    
        case 3:  addr = DRIVE_CURRENT_CH3; break;    
        default: addr = DRIVE_CURRENT_CH0; break;    
    }
    
    // read data until DriveCurrent settles
    for(int i = 0; i < 10; i++)
    {
        while( !is_ready( channel ) ) {  }
        get_Data( channel );        
        tempDriveCurrent = get( addr, CHx_INIT_IDRIVE, 31 );
        
        debug( "[poging %d] new: %d, old: %d\r\n", i, tempDriveCurrent, _DriveCurrent );
        /*
        if ( _DriveCurrent == tempDriveCurrent )
        {
            break;
        }else{
            _DriveCurrent = tempDriveCurrent;
        }
        */
    }
    
    _DriveCurrent = tempDriveCurrent;
    
    // set back to manual configuration
    sleep();
    setDriveCurrent( channel, _DriveCurrent );
    set( CONFIG, AUTO_AMP_DIS, 1);
    wakeup();

    return _DriveCurrent;
}

void LDC1614::set( ADDR addr, SETTING setting, uint8_t value )
{
    uint8_t mask = 1;
    if ( addr == MUX_CONFIG )
    {
        switch (setting){
            case AUTOSCAN_EN: mask = 1; break;  // 1
            case RR_SEQUENCE: mask = 3; break;  // 11
            case DEGLITCH:    mask = 7; break;  // 111
        }    
    }
    regchange( addr, setting, value, mask );
}





/* GETTING DATA FROM SENSOR */

uint8_t LDC1614::get( ADDR addr, SETTING setting, uint8_t mask )
{
    if ( addr == MUX_CONFIG )
    {
        switch (setting){
            case AUTOSCAN_EN: mask = 1; break;  // 1
            case RR_SEQUENCE: mask = 3; break;  // 11
            case DEGLITCH:    mask = 7; break;  // 111
        }    
    }
    
    uint16_t data[1]; 
    readI2C( data, addr ); 
    return ( data[0]>>setting ) & mask;    
}

uint16_t LDC1614::get_config()
{
    uint16_t data[1];
    readI2C( data, CONFIG );
    return data[0];
}

uint16_t LDC1614::get_error_config()
{
    uint16_t data[1];
    readI2C( data, ERROR_CONFIG );
    return data[0];
}

uint16_t LDC1614::get_status( void )
{
    uint16_t data[1]; 
    readI2C( data, STATUS );  
    return data[0];
}
bool LDC1614::is_ready( uint8_t channel )
{
    uint8_t status = get_status();
    if( channel < 4 )
    {
        return ( status>>(3-channel)) & 1; // this specific channel is ready
    }else{
        return ( status>>DRDY ) & 1;       // all channels are ready
    }
}
bool LDC1614::is_error( uint8_t status )
{
    // DOES NOT WORK PROPERLY YET!!
    // STATUS is reset after reading DATA_MSB_CHx
    if( status == 17 ) { status = get_status(); }
    return ((( status>>ERR_ZC ) & 7) != 0);
}
uint8_t LDC1614::what_error( uint8_t channel )
{
    // DOES NOT WORK PROPERLY YET!!
    // STATUS is reset after reading DATA_MSB_CHx
    uint8_t status = get_status();
    if ( ( ( status>>ERR_CHAN ) & 2 ) == channel )
    {
        if ((( status>>ERR_AHE ) & 1) != 0) return 1; // Amplitude High Error
        if ((( status>>ERR_ALE ) & 1) != 0) return 2; // Amplitide Low Error
        if ((( status>>ERR_ZC  ) & 1) != 0) return 3; // Zero Count Error
    }
    return 0;
}
uint8_t LDC1614::get_error( uint8_t channel )
{
    if( ( (error[channel]>>(CHx_ERR_UR - CHx_ERR_AE)) & 1) == 1 ) { debug( "Sensor %d: Under-range Error\r\n", channel ); }
    if( ( (error[channel]>>(CHx_ERR_OR - CHx_ERR_AE)) & 1) == 1 ) { debug( "Sensor %d: Over-range Error\r\n", channel ); }
    if( ( (error[channel]>>(CHx_ERR_WD - CHx_ERR_AE)) & 1) == 1 ) { debug( "Sensor %d: Watchdog Timeout Error\r\n", channel ); }
    if( ( (error[channel]>>(CHx_ERR_AE - CHx_ERR_AE)) & 1) == 1 ) { debug( "Sensor %d: Amplitude Error\r\n", channel ); }
    return error[channel];
}


uint16_t LDC1614::get_ReferenceCount( uint8_t channel )
{
    uint16_t rcount[1]; 
    readI2C( rcount, RCOUNT_CH0 + channel ); 
    return rcount[0];
}

uint8_t LDC1614::get_DriveCurrent( uint8_t channel )
{
    ADDR addr;
    switch ( channel ){        
        case 1:  addr = DRIVE_CURRENT_CH1; break;    
        case 2:  addr = DRIVE_CURRENT_CH2; break;    
        case 3:  addr = DRIVE_CURRENT_CH3; break;    
        default: addr = DRIVE_CURRENT_CH0; break;    
    }    
    return get( addr, CHx_IDRIVE, 31 );
}

uint32_t LDC1614::get_Data( uint8_t channel )
{
    uint16_t data[2];
    readI2C( data, DATA_MSB_CH0 + 2*channel, 2 );    
    error[channel] = ((data[0]>>CHx_ERR_AE) & 0x0f);
    // debug("[Error channel %d (0x%02X): 0x%01X] 0x%04X %04X\r\n", channel, DATA_MSB_CH0 + 2*channel, error[channel], data[0], data[1] );
    return ( (data[0] & 0x0fff)<<16 ) | data[1]; // MSB + LSB
}

uint16_t LDC1614::get_device_ID( void )
{
    uint16_t ID[1]; 
    readI2C( ID, DEVICE_ID, 1 );
    return ID[0];

}

uint16_t LDC1614::get_manufacturer_ID( void )
{
    uint16_t ID[1]; 
    readI2C( ID, MANUFACTURER_ID, 1 );
    return ID[0];
}


/* REGISTER FUNCTIONS (READ / WRITE)  */

void LDC1614::readI2C( uint16_t *data, uint8_t address, uint8_t length )
{
    for( int i = 0; i < length; i++ )
    {
        // start sequence (Device ID + Register Address + write)
        _i2c.start();
        _i2c.write( ( 0x2A << 1 ) | 0 );
        _i2c.write( address + i );
        
        // start sequence (Register Address + read)
        _i2c.start();
        _i2c.write( ( 0x2A << 1 ) | 1 );        

        // Build up 16 bit result
        data[i] = _i2c.read(1) << 8;    // MSB
        data[i] |= _i2c.read(0);        // LSB
        // debug("Read from 0x%02X : 0x%04X\r\n", address + i, data[i]);
                
        // Stop command
        _i2c.stop();
    }        
}

void LDC1614::writeI2C( uint16_t *data, uint8_t address, uint8_t length )
{
    for ( int i = 0; i < length; i++ )
    {
        _i2c.start();
        _i2c.write( ( 0x2A << 1 ) | 0 );   // 7 bit 0x2A + 0 (write) = 0x54
        _i2c.write( address + i );
        
        _i2c.write( ( data[i] & 0xff00 ) >> 8 ); // MSB
        _i2c.write( ( data[i] & 0x00ff ) >> 0 ); // LSB
        _i2c.stop();
    }
}

void LDC1614::writeI2Cregister(uint8_t reg, uint16_t value)
{
    writeI2C( &value, reg ); 
}

void LDC1614::regchange( uint8_t addr, uint8_t setting, uint8_t value, uint8_t mask )
{
    uint16_t config[1];
    readI2C( config, addr );
    writeI2Cregister( addr, uint16_t ( (config[0] & ~(mask<<setting)) | (value<<setting)) ); // replace bits with number SETTING
}



/* CALCULATE STUFF WITH SENSOR DATA */

float LDC1614::get_fsensor( uint32_t LData )
{    
    _fsensor = _dividerIN * ((_fCLKIN*1E6)/_dividerREF) * (LData / 268435456.0); // (p.14)
    return _fsensor;
}   

float LDC1614::get_Inductance( uint32_t Ldata )
{  
    float fsensor = get_fsensor( Ldata );
    _inductance = 1.0 / ((_cap*1E-12) * 4 * PI*PI * fsensor*fsensor ); // ???
    return _inductance;
}



// EXTRA test: Get&print values of all variables to verify (to calculate the induction)
// The data will be printed on the screen using RealTerm: baud 9600.
// Begin ***********************************************************
    float LDC1614::get_fCLKIN()             {return _fCLKIN;}    
    uint8_t LDC1614::get_dividerIN()        {return _dividerIN;}
    uint8_t LDC1614::get_dividerREF()       {return _dividerREF;}
    uint16_t LDC1614::get_Offset()          {return _Offset;}
    float LDC1614::get_cap()                {return _cap;}
// END ***********************************************************