@stevieb The code for the lcd module runs to 75K, so you probably don't want to see all of it!
Most of it is not relevent to i2c bus access
The object setup code and the calls to the bus are shown below.
#!/usr/bin/perl
...
use HiPi::Utils;
use HiPi::Device::I2C;
...
use strict;
use warnings;
# ********************************************************** #
# *** User entered values required before running script *** #
# ********************************************************** #
# Setup regular user & group id's for permission drop-back
my $user = '<myname>';
my $group = '<mygroup>';
# LCD backpack's serial to parallel i2c device address & i2c bus
my $i2cAddr = 0x3F; # SainSmart's backpack address
my $i2cDev = '/dev/i2c-3'; # alternate i2c bus - SDA on gpio 22/S
+CL on gpio 23
...
# ********************************************************** #
# **************** Setup i2c & lcd constants *************** #
# ********************************************************** #
# setup values specific to the 2 row 16 character SainSmart i2c LCD
my $D_WIDTH = 16; # Displayed characters per line
my $M_WIDTH = 40; # Memory size per line
my $DATA_MODE = 0x09; # Mode - send data
my $CMD_BMODE = 0x08; # Mode - send command with backlight on
my $CMD_XMODE = 0x00; # Mode - send command with backlight of
+f
my $LINE_1 = 0x80; # Address command for the 1st line
my $LINE_2 = 0xC0; # Address command for the 2nd line
my $BL_STATUS = 0x08; # Backlight mask On=0x08 - Off=0x00
my $LO_MASK = 0xF0; # Masks off low-order bits in byte
my $SET_EN = 0x04; # 0000 0100 - mask to set enable bit
my $CLR_EN = 0x0B; # 0000 1011 - mask to clear enable bit
+& data
my $DISP_CLR = 0x01; # Clears display & home's cursor
my $CUR_HOME = 0x02; # Home the cursor
my $DISP_OFF = 0x08; # Display off
my $DISP_ON = 0x0C; # Display on
my $MOVE_L = 0x18; # Move display Left
my $MOVE_R = 0x1C; # Move display Right
...
# hold/wait times (microseconds) are in the script but do not
# seem to be required, perhaps the delay inherent in 12c serial
# to parallel conversion is sufficient
my $EN_HOLD = 0; # hold enable high
my $WAIT_WR = 0; # wait before next write
my $WAIT_CMD = 0; # wait after sending instruction
...
# ********************************************************** #
# **** create i2C object as root then drop permissions ***** #
# ********************************************************** #
my $objI2c = HiPi::Device::I2C->new(
devicename => $i2cDev,
address => $i2cAddr,
busmode => 'i2c'
);
HiPi::Utils::drop_permissions_name( $user, $group )
...
# *********************** Write Byte *********************** #
sub writeByte {
my $byte = $_[0];
# writes byte to i2c object (LCD)
# 'Enable' toggled high-low to latch byte
# timed to avoid i2c bus contention with readI2cAmpsTB.pl
# only write at 'safe' time -> 1, 2 or 3 seconds after each 5 second
+s (0,5,10 etc.)
my $wait = 1;
while( $wait ) {
my ($yr, $mo, $dy, $hr, $mi, $se) = Today_and_Now();
my $secAfter = $se - ( int( $se /5 ) *5 );
if( $secAfter == 1 || $secAfter == 2 || $secAfter == 3 ) {
# write data with enable high
$objI2c->bus_write( $byte | $SET_EN );
$objI2c->delayMicroseconds( $EN_HOLD );
# clear enable - keep backlight and mode bits
$objI2c->bus_write( $byte & $CLR_EN );
$objI2c->delayMicroseconds( $WAIT_WR );
# end while loop
$wait = 0;
} else {
# wait
select(undef,undef,undef, .1);
}
}
return();
# *********************** Read Bytes *********************** #
sub readBytes {
my ($line, $posn, $readN) = @_;
# reads bytes from i2c object (LCD) at address set by write command
# 'Enable' control port toggled high-low
# i2c_read command must have bits to be read set high
# LCD controller increments address after each read
# set RAM address to start position - wait for command to complete
sendByte( $line + $posn -1, $CMD_BMODE );
$objI2c->delay( $WAIT_WR );
my (@dataH, @dataL);
my $dataStr;
for( my $n = 0; $n < $readN; $n++ ) {
# wait for transfer of RAM data to data register
$objI2c->delayMicroseconds( $WAIT_WR );
# only write at 'safe' time -> 1, 2 or 3 seconds after each 5 se
+conds (0,5,10 etc.)
my $wait = 1;
while( $wait ) {
my ($yr, $mo, $dy, $hr, $mi, $se) = Today_and_Now();
my $secAfter = $se - ( int( $se /5 ) *5 );
if( $secAfter == 1 || $secAfter == 2 || $secAfter == 3 ) {
# read MSB
# enable high
$objI2c->bus_write( 0xFF ); # data bits all high & BL=1,
+ EN=1, RW=1, RS=1
$objI2c->delayMicroseconds( $WAIT_WR );
# read the data - 1 byte
# in 4-bit mode this will be the MSB of the data byte
@dataH = $objI2c->i2c_read( 1 );
# set enable low
$objI2c->bus_write( 0x0B ); #(0000 1011); #BL=1, EN=0, R
+W=1, RS=1
$objI2c->delayMicroseconds( $WAIT_WR );
# read LSB
# enable high
$objI2c->bus_write( 0xFF ); # data bits all high & BL=1,
+ EN=1, RW=1, RS=1
# read the data - 1 byte
# in 4-bit mode this will be the LSB of the data byte
@dataL = $objI2c->i2c_read( 1 );
# set enable low
$objI2c->bus_write( 0x0B ); # BL=1, EN=0, RW=1, RS=1
$objI2c->delayMicroseconds( $WAIT_WR );
# put the high nibbles from each read into one byte & ad
+d to data string
$dataStr =
$dataStr . chr(( $dataH[0] & 0xF0 ) | ( $dataL[0]
+ >> 4 & 0x0F ));
# end while loop
$wait = 0;
} else {
# wait
select( undef, undef, undef, .1 );
}
}
}
return( $dataStr );
}
The volts/amps script is essentially the same. There are two devices and the objects are created like this:
# setup alternate i2c bus - SDA on gpio 22/SCL on gpio 23
# and INA219 addresses
my $i2cDev = '/dev/i2c-3';
my $busMode = 'i2c';
my $i2cAddrT = 0x41;
my $i2cAddrB = 0x40;
...
# Create two i2c device objects
my $objI2cT = HiPi::Device::I2C->new(
devicename => $i2cDev,
address => $i2cAddrT,
busmode => $busMode
);
my $objI2cB = HiPi::Device::I2C->new(
devicename => $i2cDev,
address => $i2cAddrB,
busmode => $busMode
);
# Now drop back to regular user
HiPi::Utils::drop_permissions_name( $user, $group );
...
# ********************************************************* #
# initialize both devices
# compute configuration bits (2 bytes msb, lsb)
my @calcConfigT = Config( $shuntResT );
my @calcConfigB = Config( $shuntResB );
# set write address for configuration register (0x00)
$objI2cT->bus_write( $i2cAddrT, 0x00 );
# write configuration register bits as two binary values, msb-lsb orde
+r
$objI2cT->bus_write( $i2cAddrT, oct( "0b" . $calcConfigT[0] ), oct( "0
+b" . $calcConfigT[1] ) );
# set write address for configuration register (0x00)
$objI2cB->bus_write( $i2cAddrB, 0x00 );
# write configuration register bits as two binary values, msb-lsb orde
+r
$objI2cB->bus_write( $i2cAddrB, oct( "0b" . $calcConfigB[0] ), oct( "0
+b" . $calcConfigB[1] ) );
# ********************************************************* #
# read & calculate bus voltage
sub RcBus {
my ($objI2C, @calcConfig) = @_;
# read 2 bytes of data from bus voltage register (address 0x02)
# as this sometimes creates an error and kills the script, use 'ev
+al'
my @bvr;
eval{
@bvr = $objI2C->bus_read( 0x02, 2 );
1;
}
or do {
$bvr[0] = 0;
$bvr[1] = 0;
};
# result in upper 13 bits - data in msb-lsb (big-endian) order
my $busV = pack 'C2', $bvr[0], $bvr[1];
$busV = ( unpack 'S>', $busV ) >> 3;
# scale bus voltage register value according to voltage configurat
+ion bit
my ($bv, $bvScale);
if( $calcConfig[3] == 0 ) {
$bv = 16;
$bvScale = 4000;
} else {
$bv = 32;
$bvScale = 8000;
}
$busV = $bv / $bvScale * $busV;
printf "Bus voltage: %0.02f\n",$busV if $verbose;
return $busV ;
}
# ********************************************************* #
# read & calculate shunt current
sub RcAmps {
my ($objI2C, $shuntRes, @calcConfig) = @_;
# read 2 bytes of data from shunt voltage register (address 0x01)
# as this sometimes creates an error and kills the script, use 'ev
+al'
my @svr;
eval{
@svr = $objI2C->bus_read( 0x01, 2 );
1;
}
or do {
$svr[0] = 0;
$svr[1] = 0;
};
# result in 14 low-order bits (big-endian & result always +ve)
my $shuntV = pack 'C2', $svr[0], $svr[1];
$shuntV = ( unpack 'S>', $shuntV ) & 0b0011111111111111;
# use $smv (shunt mV setting for calculation)
$shuntV = $calcConfig[2] / ( $calcConfig[2] * 100 ) * $shuntV;
printf "Shunt voltage: %0.02fmV\n", $shuntV if $verbose;
# calculate current (amps) with specified shunt resistor
my $shuntA = $shuntV / $shuntRes / 1000;
printf "Shunt current: %0.02f amps\n\n", $shuntA if $verbose;
return $shuntA ;
}
The timing for reading at 0, 5, 10, 15 etc. seconds past the minute is controlled by an outer loop, so both devices are read for current and voltage every 5 seconds, on the assumption that the four reads and associated computation can be completed within one second. The initial write to the configuration registers only happens once when the script is started.
As I am not using the standard sda/scl gpio's I have external pull-up resistors.
In the past I tried using two i2c busses with the lcd on one and the INA219's on another. Interestingly, it did not solve the problem, so there is some common code that drives different i2c busses.