#
# http://www.toolz.com - software@toolz.com
# 
# $Id: LCD.pm,v 1.13 2023/08/22 17:55:13 todd Exp $
#
# Adapted from a Perl script posted by anita2R 
# on PerlMonks May 24, 2016 at 16:28 UTC
#
# Forget trying to make I2C to LCD work in 8-bit mode
# $Id: LCD.pm,v 1.13 2023/08/22 17:55:13 todd Exp $
#
use warnings;
package RPi::I2C::LCD;
our $VERSION = '1.13';
require Exporter;
our @ISA = qw(Exporter);
@EXPORT = qw(writeByte sendByte prntStr display lookup);
use Time::HiRes;

# LCD Address
$DEVADDR = 0x27;	# default address on i2c bus
my $dataMode = 0x01;    # Mode - send data
my $cmdMode  = 0x00;    # Mode - send command
my $blStatus = 0x08;    # Backlight mask On=0x08 - Off=0x00
my $loMask   = 0xF0;    # Masks off low-order bits in byte
my $sEN      = 0x04;    # 0000 0100 - mask to set enable bit
my $cEN      = 0x0B;    # 0000 1011 - mask to clear enable bit & data

%LINES = (	# DDRAM address for each line
    1 => 0x80,
    2 => 0xC0,
    3 => 0x94,
    4 => 0xD4
	 );

sub new
{
	my ($class,%args) = @_;
	my $key;

	# defaults
	my %params = (
		devaddr		=>	$DEVADDR,
		busno			=>	1,	# 0, 1
		width			=>	16,
		rows			=>	2,
		backlight	=>	1,
		defwait		=>	0.0001,
		justify		=>	'center',
		version		=> "$VERSION",
	);

	# default overrides
	foreach $key (keys %args)
	{
		$params{$key} = $args{$key};
	}
	$params{bus} = 		RPi::I2C->new ($params{devaddr});
	foreach $key (keys %params) { $self->{$key} = $params{$key}; }
	bless $self, $class;
	if (not $self->{backlight}) { $blStatus = 0; }

	&writeByte( $self, 0x30, $cmdMode );	# 8-bit
	&writeByte( $self, 0x30, $cmdMode );
	&writeByte( $self, 0x20, $cmdMode );	# 4-bit
	# now in 4-bit mode
	# 4-bit, 1 lines, 5x7 pixel font
	if ($self->{rows} == 1) { &sendByte( $self, 0x20, $cmdMode ); }
	# 4-bit, 2 lines, 5x7 pixel font
	elsif ($self->{rows} == 2) { &sendByte( $self, 0x28, $cmdMode ); }
	sleep($self->{defwait});
	$self->display('ON');
	sleep($self->{defwait});
	$self->display('CLEAR');

	return $self;
}

# *************** Write Byte *************** #
sub writeByte 
{
	my ($self,$byte) = @_;
	#
	# writes byte to i2c object (LCD)
	# Enable 'control port' toggled high-low
	#
	# write data with enable set
	$self->{bus}->write( $byte | $sEN );
	sleep($self->{defwait});

	# clear enable, clear data, keep backlight & mode
	$self->{bus}->write( $byte | $cEN );
	sleep($self->{defwait});
}
#
# **************** Send byte *************** #
sub sendByte 
{
	my ($self,$data,$mode) = @_;
	# splits data into high & low-order
	#  puts each nibble into high-order bits
	#  then adds mode & backlight status bits into low-order bits
	#
	# mask off 4 low-order bits & 'add' mode and backlight
	my $data_high = (( $data & $loMask ) | $mode | $blStatus );

	# shift 4 low bits to high bits, mask-off low order bits 
	#  & 'add' mode and backlight
	my $data_low = ((( $data << 4 ) & $loMask ) | $mode | $blStatus );

	# Send both nibbles of data to write routine
	&writeByte( $self,$data_high );
	&writeByte( $self,$data_low );

	sleep($self->{defwait});
}
#
# ************* Print a String ************* #
sub prntStr 
{
	my ($self,$message,$line) = @_;
	my ($i);
	my $ddaddr = $LINES{$line};
	# set DDRAM address
	&sendByte($self, $ddaddr, $cmdMode );

	# iterate through message string
	my @str = split //,substr(justify($self->{justify},$self->{width},$message),0,$self->{width});
	for ( $i = 0 ; $i < $self->{width} ; $i++ ) 
	{
		# send bytes to be displayed (use character values)
		&sendByte( $self,ord( $str[$i] ), $dataMode );
	}
}

sub display	# OFF, ON, CLEAR
{
	my ($self,$cmd) = @_;

	if ("$cmd" eq 'OFF')			
	{ 
		$self->sendByte(0x08,$cmdMode); 
		sleep($self->{defwait});
	}
	elsif ("$cmd" eq 'ON')		
	{ 
		$self->sendByte(0x0C,$cmdMode); 
		sleep($self->{defwait});
	}
	elsif ("$cmd" eq 'CLEAR')	
	{ 
		$self->sendByte(0x01,$cmdMode); 
		sleep(0.005);
	} 
}

sub lookup
{
	my ($self,$var) = @_;
	return $self->{$var};
}

sub justify
{
	my $mode = shift;
	my $width = shift;
	my $string = shift;
	my $more;

	if ("$mode" eq 'left')
	{
		return sprintf("%-*s",$width,$string);
	}
	elsif ("$mode" eq 'center')
	{
		my $pad = ' ' x (($width - length("$string")) / 2);
		if (length($string) % 2) { $more = ' ' } else { $more = ''; }
		return sprintf("%s%s%s%s","$pad","$string","$pad","$more");
	}
	elsif ("$mode" eq 'right')
	{
		return sprintf("%*s",$width,$string);
	}
}

1;

