ina219 userspace i2c driver
[linux-gpio-pinout] / i2c-userspace / ina219.c
diff --git a/i2c-userspace/ina219.c b/i2c-userspace/ina219.c
new file mode 100644 (file)
index 0000000..2247c8b
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+* INA219 util - part of PowerCape by AndiceLabs
+*
+* Copyright (C) 2014  AndiceLabs admin@andicelabs.com  http://andicelabs.com
+* Copyright (C) 2014  Zig Fisher flyrouter@gmail.com   http://zftlab.org
+*
+* 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.
+*
+*
+* Example run and output:
+*
+* OpenWRT:~# ina219 -b 0 -i 60
+*
+* 22:49 12168mV  134.2mA
+* 22:50 12168mV  239.9mA
+* 22:51 12168mV  134.7mA
+*
+*/
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <endian.h>
+#include <string.h>
+#include <time.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <linux/i2c-dev.h>
+
+#define CONFIG_REG          0
+#define SHUNT_REG           1
+#define BUS_REG             2
+#define POWER_REG           3
+#define CURRENT_REG         4
+#define CALIBRATION_REG     5
+
+#define AVR_ADDRESS         0x21
+#define INA_ADDRESS         0x40
+
+typedef enum {
+    OP_DUMP,
+    OP_VOLTAGE,
+    OP_CURRENT,
+    OP_MONITOR,
+    OP_NONE
+} op_type;
+
+op_type operation = OP_DUMP;
+
+int interval = 60;
+int i2c_bus = 0;
+int i2c_address = INA_ADDRESS;
+int handle;
+int whole_numbers = 0;
+
+
+void msleep( int msecs )
+{
+    usleep( msecs * 1000 );
+}
+
+
+int i2c_read( void *buf, int len )
+{
+    int rc = 0;
+
+    if ( read( handle, buf, len ) != len )
+    {
+        printf( "I2C read failed: %s\n", strerror( errno ) );
+        rc = -1;
+    }
+
+    return rc;
+}
+
+
+int i2c_write( void *buf, int len )
+{
+    int rc = 0;
+
+    if ( write( handle, buf, len ) != len ) 
+    {
+        printf( "I2C write failed: %s\n", strerror( errno ) );
+        rc = -1;
+    }
+
+    return rc;
+}
+
+
+int register_read( unsigned char reg, unsigned short *data )
+{
+    int rc = -1;
+    unsigned char bite[ 4 ];
+
+    bite[ 0 ] = reg;
+    if ( i2c_write( bite, 1 ) == 0 )
+    {
+        if ( i2c_read( bite, 2 ) == 0 )
+        {
+            *data = ( bite[ 0 ] << 8 ) | bite[ 1 ];
+            rc = 0;
+        }
+    }
+
+    return rc;
+}
+
+
+int register_write( unsigned char reg, unsigned short data )
+{
+    int rc = -1;
+    unsigned char bite[ 4 ];
+
+    bite[ 0 ] = reg;
+    bite[ 1 ] = ( data >> 8 ) & 0xFF;
+    bite[ 2 ] = ( data & 0xFF );
+
+    if ( i2c_write( bite, 3 ) == 0 )
+    {
+        rc = 0;
+    }
+
+    return rc;
+}
+
+
+void show_usage( char *progname )
+{
+    fprintf( stderr, "Usage: %s <mode> \n", progname );
+    fprintf( stderr, "   Mode (required):\n" );
+    fprintf( stderr, "      -h --help           Show usage.\n" );
+    fprintf( stderr, "      -i --interval       Set interval for monitor mode.\n" );
+    fprintf( stderr, "      -w --whole          Show whole numbers only. Useful for scripts.\n" );
+    fprintf( stderr, "      -v --voltage        Show battery voltage in mV.\n" );
+    fprintf( stderr, "      -c --current        Show battery current in mA.\n" );
+    fprintf( stderr, "      -a --address <addr> Override I2C address of INA219 from default of 0x%02X.\n", i2c_address );
+    fprintf( stderr, "      -b --bus <i2c bus>  Override I2C bus from default of %d.\n", i2c_bus );
+    exit( 1 );
+}
+
+
+void parse( int argc, char *argv[] )
+{
+    while( 1 )
+    {
+        static const struct option lopts[] =
+        {
+            { "address",    0, 0, 'a' },
+            { "bus",        0, 0, 'b' },
+            { "current",    0, 0, 'c' },
+            { "help",       0, 0, 'h' },
+            { "interval",   0, 0, 'i' },
+            { "voltage",    0, 0, 'v' },
+            { "whole",      0, 0, 'w' },
+            { NULL,         0, 0, 0 },
+        };
+        int c;
+
+        c = getopt_long( argc, argv, "a:b:chi:vw", lopts, NULL );
+
+        if( c == -1 )
+            break;
+
+        switch( c )
+        {
+            case 'a':
+            {
+                errno = 0;
+                i2c_address = (int)strtol( optarg, NULL, 0 );
+                if ( errno != 0 )
+                {
+                    fprintf( stderr, "Unknown address parameter %s.\n", optarg );
+                    exit( 1 );
+                }
+                break;
+            }
+
+            case 'b':
+            {
+                errno = 0;
+                i2c_bus = (int)strtol( optarg, NULL, 0 );
+                if ( errno != 0 )
+                {
+                    fprintf( stderr, "Unknown bus parameter %s.\n", optarg );
+                    exit( 1 );
+                }
+                break;
+            }
+
+            case 'c':
+            {
+                operation = OP_CURRENT;
+                break;
+            }
+
+            default:
+            case 'h':
+            {
+                operation = OP_NONE;
+                show_usage( argv[ 0 ] );
+                break;
+            }
+
+            case 'i':
+            {
+                operation = OP_MONITOR;
+                interval = atoi( optarg );
+                if ( ( interval == 0 ) && ( errno != 0 ) )
+                {
+                    fprintf( stderr, "Invalid interval value\n" );
+                    exit( 1 );
+                }
+                break;
+            }
+
+            case 'v':
+            {
+                operation = OP_VOLTAGE;
+                break;
+            }
+
+            case 'w':
+            {
+                whole_numbers = 1;
+                break;
+            }
+        }
+    }
+}
+
+
+int get_voltage( float *mv )
+{
+    short bus;
+
+    if ( register_read( BUS_REG, (unsigned short*)&bus ) != 0 )
+    {
+        return -1;
+    }
+
+    *mv = ( float )( ( bus & 0xFFF8 ) >> 1 );
+    return 0;
+}
+
+
+int get_current( float *ma )
+{
+    short shunt;
+
+    if ( register_read( SHUNT_REG, &shunt ) != 0 )
+    {
+        return -1;
+    }
+
+    *ma = (float)shunt / 10;
+    return 0;
+}
+
+
+void show_current( void )
+{
+    float ma;
+
+    if ( get_current( &ma ) )
+    {
+        fprintf( stderr, "Error reading current\n" );
+        return;
+    }
+
+    if ( whole_numbers )
+    {
+        printf( "%4.0f\n", ma );
+    }
+    else
+    {
+        printf( "%04.1f\n", ma );
+    }
+}
+
+
+void show_voltage( void )
+{
+    float mv;
+
+    if ( get_voltage( &mv ) )
+    {
+        fprintf( stderr, "Error reading voltage\n" );
+        return;
+    }
+    printf( "%4.0f\n", mv );
+}
+
+
+void show_voltage_current( void )
+{
+    float mv, ma;
+
+    if ( get_current( &ma ) || get_voltage( &mv ) )
+    {
+        fprintf( stderr, "Error reading voltage/current\n" );
+        return;
+    }
+
+    if ( whole_numbers )
+    {
+        printf( "%04.0fmV  %4.0fmA\n", mv, ma );
+    }
+    else
+    {
+        printf( "%04.0fmV  %04.1fmA\n", mv, ma );
+    }
+    fflush(stdout);
+}
+
+
+void monitor( void )
+{
+    struct tm *tmptr;
+    time_t seconds;
+
+    while ( 1 )
+    {
+        seconds = time( NULL );
+        tmptr = localtime( &seconds );
+        printf( "%04d-%02d-%02dT%02d:%02d:%02d ",
+               tmptr->tm_year + 1900, tmptr->tm_mon +1, tmptr->tm_mday,
+               tmptr->tm_hour, tmptr->tm_min, tmptr->tm_sec );
+        show_voltage_current();
+        sleep( interval );
+    }
+}
+
+
+int main( int argc, char *argv[] )
+{
+    char filename[ 20 ];
+
+    parse( argc, argv );
+
+    snprintf( filename, 19, "/dev/i2c-%d", i2c_bus );
+    handle = open( filename, O_RDWR );
+    if ( handle < 0 ) 
+    {
+        fprintf( stderr, "Error opening bus %d: %s\n", i2c_bus, strerror( errno ) );
+        exit( 1 );
+    }
+
+    if ( ioctl( handle, I2C_SLAVE, i2c_address ) < 0 ) 
+    {
+        fprintf( stderr, "Error setting address %02X: %s\n", i2c_address, strerror( errno ) );
+        exit( 1 );
+    }
+
+    switch ( operation )
+    {
+        case OP_DUMP:
+        {
+            show_voltage_current();
+            break;
+        }
+
+        case OP_VOLTAGE:
+        {
+            show_voltage();
+            break;
+        }
+
+        case OP_CURRENT:
+        {
+            show_current();
+            break;
+        }
+
+        case OP_MONITOR:
+        {
+            monitor();
+            break;
+        }
+
+        default:
+        case OP_NONE:
+        {
+            break;
+        }
+    }
+
+    close( handle );
+    return 0;
+}
+