#!/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 = ''; my $group = ''; # 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/SCL 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 off 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 seconds (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 seconds (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, RW=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 & add 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 ); } #### # 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 order $objI2cT->bus_write( $i2cAddrT, oct( "0b" . $calcConfigT[0] ), oct( "0b" . $calcConfigT[1] ) ); # set write address for configuration register (0x00) $objI2cB->bus_write( $i2cAddrB, 0x00 ); # write configuration register bits as two binary values, msb-lsb order $objI2cB->bus_write( $i2cAddrB, oct( "0b" . $calcConfigB[0] ), oct( "0b" . $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 'eval' 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 configuration 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 'eval' 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 ; }