Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Re^2: i2c bus contention

by anita2R (Scribe)
on Mar 30, 2020 at 14:16 UTC ( #11114804=note: print w/replies, xml ) Need Help??


in reply to Re: i2c bus contention
in thread i2c bus contention

@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.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://11114804]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (7)
As of 2020-11-23 15:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?