#########################################################################################
# Package HiPi::BCM2835::I2C
# Description: I2C Connection
# Copyright : Copyright (c) 2013-2019 Mark Dootson
# License : This work 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 3 of the License, or any later
# version.
#########################################################################################
package HiPi::BCM2835::I2C;
#########################################################################################
use strict;
use warnings;
use parent qw( HiPi::Class );
use HiPi 0.80;
use HiPi qw( :rpi :i2c );
use HiPi::BCM2835 qw( :registers :i2c :clock );
use Carp;
__PACKAGE__->create_accessors( qw(
_hipi_baseaddr peripheral address _function_mode _clock_divider _baud_reference readmode
));
our $VERSION ='0.66';
our @EXPORT = ();
our @EXPORT_OK = ();
our %EXPORT_TAGS = ( all => \@EXPORT_OK );
use constant {
BB_I2C_PERI_0 => 0x10,
BB_I2C_PERI_1 => 0x20,
BB_I2C_RESULT_SUCCESS => BCM2835_I2C_REASON_OK,
BB_I2C_RESULT_NACKRCV => BCM2835_I2C_REASON_ERROR_NACK,
BB_I2C_RESULT_CLOCKTO => BCM2835_I2C_REASON_ERROR_CLKT,
BB_I2C_RESULT_DATAERR => BCM2835_I2C_REASON_ERROR_DATA,
BB_I2C_CLOCK_100_KHZ => 2500,
BB_I2C_CLOCK_400_KHZ => 626,
BB_I2C_CLOCK_1667_KHZ => 150,
BB_I2C_CLOCK_1689_KHZ => 148,
};
{
my @const = qw(
BB_I2C_PERI_0
BB_I2C_PERI_1
BB_I2C_RESULT_SUCCESS
BB_I2C_RESULT_NACKRCV
BB_I2C_RESULT_CLOCKTO
BB_I2C_RESULT_DATAERR
BB_I2C_CLOCK_100_KHZ
BB_I2C_CLOCK_400_KHZ
BB_I2C_CLOCK_1667_KHZ
BB_I2C_CLOCK_1689_KHZ
);
push @EXPORT_OK, @const;
$EXPORT_TAGS{i2c} = \@const;
}
sub set_baudrate {
my ($objorclass, $newval ) = @_;
$newval ||= 100000;
$newval = 3816 if $newval < 3816;
# As instance method store cdiv
if(ref($objorclass)) {
$objorclass->_baud_reference($newval);
my $cdiv = (BCM2835_CORE_CLK_HZ / $newval) & 0x3FFFFE;
$objorclass->_clock_divider( $cdiv );
return 1;
}
# As class method set global
# make sure library is initialised
HiPi::BCM2835::bcm2835_init();
my $channel = ( RPI_BOARD_REVISION == 1 ) ? BB_I2C_PERI_0 : BB_I2C_PERI_1;
my $baseaddress = ( $channel == BB_I2C_PERI_0 )
? BCM2835_BSC0_BASE
: BCM2835_BSC1_BASE;
HiPi::BCM2835::bcm2835_hipi_i2c_set_baudrate( $baseaddress, $newval );
return 1;
}
sub get_baudrate {
my ($objorclass) = @_;
# As instance method return our own baudrate
if(ref($objorclass)) {
return $objorclass->_baud_reference;
}
my $channel = ( RPI_BOARD_REVISION == 1 ) ? BB_I2C_PERI_0 : BB_I2C_PERI_1;
my $cdiv = _get_current_clockdivider($channel);
return _get_baudrate_from_clockdivider($cdiv);
}
sub _get_current_clockdivider {
my $channel = shift;
# make sure library is initialised
HiPi::BCM2835::bcm2835_init();
# force some default values / ranges
unless( defined($channel) && ( ( $channel == BB_I2C_PERI_0 ) || ( $channel == BB_I2C_PERI_1 ) ) ){
croak('channel must be defined as constant BB_I2C_PERI_0 or BB_I2C_PERI_1');
}
my $baseaddress = ( $channel == BB_I2C_PERI_1 )
? BCM2835_BSC1_BASE
: BCM2835_BSC0_BASE;
my $readaddess = $baseaddress + BCM2835_BSC_DIV;
my $cdiv = HiPi::BCM2835::bcm2835_peri_read($readaddess);
return $cdiv;
}
sub _get_baudrate_from_clockdivider {
my $cdiv = shift;
return ( BCM2835_CORE_CLK_HZ / $cdiv ) & 0x3FFFFE;
}
sub new {
my ($class, %userparams ) = @_;
my %params = (
address => 0,
peripheral => ( RPI_BOARD_REVISION == 1 ) ? BB_I2C_PERI_0 : BB_I2C_PERI_1,
_function_mode => 'hipi',
readmode => I2C_READMODE_SYSTEM,
);
# get user params
foreach my $key( keys (%userparams) ) {
$params{$key} = $userparams{$key};
}
# force PERI_I2C_0 on revision 1 board
if( RPI_BOARD_REVISION == 1 ) {
$params{peripheral} = BB_I2C_PERI_0;
}
# initialise
HiPi::BCM2835::bcm2835_init();
$params{_hipi_baseaddr} = ( $params{peripheral} == BB_I2C_PERI_1 )
? BCM2835_BSC1_BASE
: BCM2835_BSC0_BASE;
my $self = $class->SUPER::new(%params);
unless( $self->i2c_begin() ) {
croak('cannot initialise i2c functions of BCM2835 library');
}
if( $self->_function_mode eq 'corelib' ) {
carp qq(USING BCM2835 CORE FUNCTIONS);
}
{
my $cdiv = _get_current_clockdivider($self->peripheral);
my $baudrate = _get_baudrate_from_clockdivider( $cdiv );
$self->_clock_divider( $cdiv );
$self->_baud_reference( $baudrate );
}
return $self;
}
sub i2c_begin {
my $self = shift;
# note that set_I2C_X does the right thing
# according to board revision
# ALSO sets pull up resistor on / off
my $rval;
if ( $self->peripheral == BB_I2C_PERI_1 ) {
$rval = HiPi::BCM2835::hipi_set_I2C1(1);
} else {
$rval = HiPi::BCM2835::hipi_set_I2C0(1);
}
return $rval;
}
# i2c_end - we don't call this automatically
# as removing the pu resistors may be
# unexpected as may changing output type
sub i2c_end {
my $self = shift;
# note that set_I2C_X does the right thing
# according to board revision
# ALSO sets pull up resistor on / off
if ( $self->peripheral == BB_I2C_PERI_1 ) {
HiPi::BCM2835::hipi_set_I2C1(0);
} else {
HiPi::BCM2835::hipi_set_I2C0(0);
}
}
sub i2c_write {
my( $self, @bytes ) = @_;
my $writebuffer = pack('C*', @bytes);
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error = ( $self->_function_mode eq 'corelib' )
? HiPi::BCM2835::bcm2835_i2c_write( $writebuffer )
: HiPi::BCM2835::_hipi_i2c_write( $self->_hipi_baseaddr, $writebuffer, scalar @bytes );
croak qq(i2c_write failed with return value $error) if $error;
}
# expect write to error - e.g. on software reset
sub i2c_write_error {
my( $self, @bytes ) = @_;
my $writebuffer = pack('C*', @bytes);
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error = ( $self->_function_mode eq 'corelib' )
? HiPi::BCM2835::bcm2835_i2c_write( $writebuffer )
: HiPi::BCM2835::_hipi_i2c_write( $self->_hipi_baseaddr, $writebuffer, scalar @bytes );
return $error;
}
sub i2c_read {
my( $self, $numbytes ) = @_;
$numbytes ||= 1;
my $readbuffer = chr(0) x ( $numbytes + 1 );
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error = ( $self->_function_mode eq 'corelib' )
? HiPi::BCM2835::bcm2835_i2c_read( $readbuffer, $numbytes )
: HiPi::BCM2835::_hipi_i2c_read($self->_hipi_baseaddr, $readbuffer, $numbytes);
croak qq(i2c_read failed with return value $error) if $error;
my $template = ( $numbytes > 1 ) ? 'C' . $numbytes : 'C';
my @values = unpack($template, $readbuffer);
return @values;
}
sub i2c_read_register {
my( $self, $register, $numbytes ) = @_;
$numbytes ||= 1;
my $writebuffer = pack('C', $register);
my $readbuffer = '0' x $numbytes;
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error;
if( $self->_function_mode eq 'corelib' ) {
$error = HiPi::BCM2835::bcm2835_i2c_write( $writebuffer );
croak qq(i2c_read_register failed with return value $error) if $error;
$error = HiPi::BCM2835::bcm2835_i2c_read( $readbuffer, $numbytes );
} else {
$error = HiPi::BCM2835::_hipi_i2c_read_register($self->_hipi_baseaddr, $writebuffer, $readbuffer, $numbytes);
}
croak qq(i2c_read_register failed with return value $error) if $error;
my $template = ( $numbytes > 1 ) ? 'C' . $numbytes : 'C';
my @values = unpack($template, $readbuffer);
return @values;
}
sub i2c_read_register_rs {
my( $self, $register, $numbytes) = @_;
$numbytes ||= 1;
my $writebuffer = pack('C', $register);
my $readbuffer = '0' x ( $numbytes + 1 );
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error = ( $self->_function_mode eq 'corelib' )
? HiPi::BCM2835::bcm2835_i2c_read_register_rs( $writebuffer, $readbuffer, $numbytes )
: HiPi::BCM2835::_hipi_i2c_read_register_rs( $self->_hipi_baseaddr, $writebuffer, $readbuffer, $numbytes );
croak qq(i2c_read_register_rs failed with error $error) if $error;
my $template = ( $numbytes > 1 ) ? 'C' . $numbytes : 'C';
my @values = unpack($template, $readbuffer);
return @values;
}
sub i2c_write_read_rs {
my( $self, $register, $numbytes) = @_;
$numbytes ||= 1;
my $writebuffer = pack('C', $register);
my $readbuffer = '0' x ( $numbytes + 1 );
HiPi::BCM2835::_hipi_i2c_set_transfer_params( $self->_hipi_baseaddr, $self->address, $self->_clock_divider );
my $error = ( $self->_function_mode eq 'corelib' )
? HiPi::BCM2835::bcm2835_i2c_write_read_rs( $writebuffer, 1, $readbuffer, $numbytes )
: HiPi::BCM2835::_hipi_i2c_write_read_rs( $self->_hipi_baseaddr, $writebuffer, 1, $readbuffer, $numbytes );
croak qq(i2c_write_read_rs failed with error $error) if $error;
my $template = ( $numbytes > 1 ) ? 'C' . $numbytes : 'C';
my @values = unpack($template, $readbuffer);
return @values;
}
sub delay {
my($class, $millis) = @_;
HiPi::BCM2835::bcm2835_delay( $millis );
}
sub delayMicroseconds {
my($class, $micros) = @_;
HiPi::BCM2835::bcm2835_delayMicroseconds( $micros );
}
#-------------------------------------
# Common I2C busmode methods
# bus_write
# bus_read
# bus_write_bits
# bus_read_bits
#-------------------------------------
sub bus_write { shift->i2c_write( @_ ); }
sub bus_read {
my( $self, $cmdval, $numbytes ) = @_;
my @returnvals;
if( !defined($cmdval) ) {
@returnvals = $self->i2c_read( $numbytes );
} elsif($self->readmode == I2C_READMODE_REPEATED_START ) {
@returnvals = $self->i2c_read_register_rs($cmdval, $numbytes);
} else {
@returnvals = $self->i2c_read_register($cmdval, $numbytes);
}
return @returnvals;
}
sub bus_read_bits {
my($self, $regaddr, $numbytes) = @_;
$numbytes ||= 1;
my @bytes = $self->bus_read($regaddr, $numbytes);
my @bits;
while( defined(my $byte = shift @bytes )) {
my $checkbits = 0b00000001;
for( my $i = 0; $i < 8; $i++ ) {
my $val = ( $byte & $checkbits ) ? 1 : 0;
push( @bits, $val );
$checkbits *= 2;
}
}
return @bits;
}
sub bus_write_bits {
my($self, $register, @bits) = @_;
my $bitcount = @bits;
my $bytecount = $bitcount / 8;
if( $bitcount % 8 ) { croak(qq(The number of bits $bitcount cannot be ordered into bytes)); }
my @bytes;
while( $bytecount ) {
my $byte = 0;
for(my $i = 0; $i < 8; $i++ ) {
my $bit = shift @bits;
$byte += ( $bit << $i );
}
push(@bytes, $byte);
$bytecount --;
}
$self->i2c_write($register, @bytes);
}
sub busmode { return 'bcm2835'; }
1;