#!/usr/bin/perl # # writeI2cEeprom.pl # Version 1.01 # # 05 March 2015 # # A script to write to the AT24C32 eeprom often found on # real time clock boards such as the DS3231. # The RTC and eeprom are attached to the i2c bus # For newer Raspberry Pi's this is bus #1 # and the eeprom is at address 0x57 (change these as required) # # Data for the first page to be written is tested for a 32 byte boundary # if the data does not start on or end on a 32 byte boundary, the page # is first read, then required sections are replaced before # writing the page back to the eeprom. # Blocks of data starting and ending on 32 byte boundaries are written 'as is', # and the last page is just written up to the end of the source data. # # The AT24C32 uses 12 address bits - with max address 0xFFF # The datasheet states that when used at 3.3 volts, # the i2c bus maximum speed is 100kHz # # Parameters: # -a the start address for the write (mandatory) # in decimal or hex in the format 0xFFF # -t the text to write or a filename for a file containing the text (optional) # if -t is not used (or is empty), standard input is used as the data source # this allows data to be piped to this script and written to eeprom # e.g. date | writeI2cEeprom.pl -a 0 will write the standard date/time string # to the eeprom starting at address 0 # # The script must be called as root, but lowers permissions once # the i2c object has been created # use Getopt::Std; use HiPi::Utils; use HiPi::BCM2835::I2C qw( :all ); use File::Slurp; # use strict; # # get the address (-a) and text(-t) parameters getopt('at:'); our ( $opt_a, $opt_t ); # # setup regular user & group id's for lowering permissions my $user = ''; my $group = ''; # #setup bus number, EEPROM address on i2c bus and max eeprom address my $i2cBus = BB_I2C_PERI_1; my $i2cAddr = 0x57; my $eeMax = 0xFFF; # # delay required after each write (milliseconds) my $delay = 3; # # create i2c device object my $objI2c = HiPi::BCM2835::I2C->new( peripheral => $i2cBus, address => $i2cAddr ); # HiPi::Utils::drop_permissions_name( $user, $group ); # $objI2c->set_baudrate( $i2cBus, BB_I2C_CLOCK_100_KHZ ); # # Test & handle parameters # -a address data - must be present - does not default to 0! if ( $opt_a eq "" ) { # error out if no address exit 1; } elsif ( $opt_a > $eeMax ) { # start address exceeds size of eeprom exit 2; } # # -t is text or name of file with text or if neither it can be # standard input from a pipe my @Data; # array to hold input data if ( $opt_t eq "" ) { # need to timeout in case there is no data from standard input # ( and no -t parameter or no text attached to -t) $SIG{ALRM} = sub { die "timeout" }; eval { alarm(3); # get any input from a pipe (<> is standard input) my @dataIn = <>; # split each string of data into bytes foreach (@dataIn) { push( @Data, split( '', $_ ) ); } alarm(0); }; } # # if no piped data, test for file, or use data passed as text if ( !scalar(@Data) ) { if ( -f $opt_t ) { # file exists - read it into array @Data = split( "", read_file($opt_t) ); } else { # use -t as the text to be written and put it into array @Data = split( "", $opt_t ); } } # # make sure we have got some data - from somewhere! if ( !scalar(@Data) ) { exit 3; } # # convert data bytes to numeric values (decimal) my @DataCodes; foreach (@Data) { push @DataCodes, ord($_); } # # length of data my $dataLen = scalar(@DataCodes); # # chop data if it would exceed the last eeprom address # and potentially wrap round to 0x000 if ( $opt_a + $dataLen > $eeMax ) { $dataLen = $dataLen - ( $opt_a + $dataLen - $eeMax ); } # # convert start address if hex value used if ( substr( $opt_a, 0, 2 ) == "0x" ) { $opt_a = hex($opt_a); } # # create the first page start address and the offset from the page boundary # addresses are 12 bits and pages are at 32 byte boundaries my $pageStAddr = $opt_a & 0b0000111111100000; my $dataOfst = $opt_a - $pageStAddr; # # calculate number of 32 byte pages that have to be written # (have to account for addresses that start after a 32 byte boundary) my $pages = int( ( $dataOfst + $dataLen ) / 33 ) + 1; # # set initial data pointer my $dataPtrSt = 0; # # test if start of first block of data is on a 32 byte boundary my $fbSSt; if ( $dataOfst != 0 ) { $fbSSt = 0; } else { $fbSSt = 1; } # # test if end of first block of data is before the next 32 byte boundary my $fbESt; if ( ( $dataOfst + $dataLen ) >= 32 ) { $fbESt = 1; } else { $fbESt = 0; } # # loop through the blocks of data, put data into array for write sub. my @WriteEE; for ( my $n = 1 ; $n <= $pages ; $n++ ) { # handle first and last blocks differently if ( $n == 1 ) { # first page - test status (complete 32 bytes of data or not) if ( $fbSSt && $fbESt ) { # a complete page, so write page 'as is' # use first 32 bytes of data @WriteEE = @DataCodes[ $dataPtrSt .. 31 ]; # write data &WriteEE( $pageStAddr, @WriteEE ); # increment data pointer $dataPtrSt = $dataPtrSt + 32; } else { # an incomplete page calculate length of data my $dataEnd; if ( $dataLen + $dataOfst >= 31 ) { # data extends to end of page or beyond $dataEnd = 32; } else { # data does not extend to end of page $dataEnd = $dataLen + $dataOfst; } # read in existing 32 bytes of data my @ReadEE = &ReadEE($pageStAddr); # merge new data into existing data for ( my $i = 0 ; $i < 32 ; $i++ ) { if ( ( $i < $dataOfst ) || ( $i >= $dataEnd ) ) { # haven't reached start of new data # or beyond end of it, so use existing data @WriteEE[ $i ] = @ReadEE[ $i ]; } else { # use new data @WriteEE[ $i ] = @DataCodes[ $i - $dataOfst ]; } } # write merged data &WriteEE( $pageStAddr, @WriteEE ); # increment data pointer $dataPtrSt = 32 - $dataOfst; } } elsif ( $n == $pages ) { # last page - use remaining data (it can't be more than 32 bytes) @WriteEE = @DataCodes[ $dataPtrSt .. ( $dataLen - 1 ) ]; # write data &WriteEE( $pageStAddr, @WriteEE ); } else { # all other pages # just use next 32 bytes of data and write 'as is' @WriteEE = @DataCodes[ $dataPtrSt .. ( $dataPtrSt + 31 ) ]; # write data &WriteEE( $pageStAddr, @WriteEE ); # increment data pointer $dataPtrSt = $dataPtrSt + 32; } # increment page address after every write $pageStAddr = $pageStAddr + 32; # delay between writes to allow eeprom to complete the write $objI2c->delay($delay); } # exit 0; # ######################################################################### # # # Subroutines & Functions # # # ######################################################################### # ######################################################################### # # # Read a 32 byte block # # # ######################################################################### # sub ReadEE { # get page address (my $readAddr) = @_; my $addrMSB = int( $readAddr / 256 ); my $addrLSB = $readAddr - ( $addrMSB * 256 ); # write page address $objI2c->bus_write( $addrMSB, $addrLSB ); # Read 32 byte page my @Read = $objI2c->i2c_read(32); return @Read; } # ######################################################################### # # # Write a 32 byte block # # # ######################################################################### # sub WriteEE { # get page address and data ( my $writeAddr, my @Write ) = @_; # my $addrMSB = int( $writeAddr / 256 ); my $addrLSB = $writeAddr - ( $addrMSB * 256 ); # # write-> page address followed by data # any more than 32 bytes are wrapped round to start address # @Write should never be more than 32 bytes anyway $objI2c->bus_write( $addrMSB, $addrLSB, @Write ); } #