mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-22 08:11:44 +00:00
add 20_FRM_LCD.pm, update Device::Firmata
git-svn-id: https://svn.fhem.de/fhem/trunk@2871 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
ad56fdf5cd
commit
52f3f7a15e
@ -28,7 +28,7 @@ sub FRM_Initialize($) {
|
||||
require "$main::attr{global}{modpath}/FHEM/DevIo.pm";
|
||||
|
||||
# Provider
|
||||
$hash->{Clients} = ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:";
|
||||
$hash->{Clients} = ":FRM_IN:FRM_OUT:FRM_AD:FRM_PWM:FRM_I2C:FRM_SERVO:OWX:FRM_LCD:";
|
||||
$hash->{ReadyFn} = "FRM_Ready";
|
||||
$hash->{ReadFn} = "FRM_Read";
|
||||
|
||||
@ -159,7 +159,6 @@ sub FRM_Ready($) {
|
||||
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
print "ready: $name\n";
|
||||
if ($name=~/^^FRM:.+:\d+$/) { # this is a closed tcp-connection, remove it
|
||||
TcpServer_Close($hash);
|
||||
delete $main::defs{$hash->{SNAME}}{FirmataDevice} if (defined $hash->{SNAME} && defined $main::defs{$hash->{SNAME}}{FirmataDevice});
|
||||
|
246
fhem/FHEM/20_FRM_LCD.pm
Executable file
246
fhem/FHEM/20_FRM_LCD.pm
Executable file
@ -0,0 +1,246 @@
|
||||
#############################################
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Device::Firmata;
|
||||
use Device::Firmata::Constants qw/ :all /;
|
||||
|
||||
#####################################
|
||||
|
||||
my %sets = (
|
||||
"text" => "",
|
||||
"home" => "",
|
||||
"clear" => "",
|
||||
"display" => "on,off",
|
||||
"cursor" => "",
|
||||
"scroll" => "left,right",
|
||||
);
|
||||
|
||||
my %gets = (
|
||||
);
|
||||
|
||||
sub
|
||||
FRM_LCD_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = "FRM_Client_Define";
|
||||
$hash->{InitFn} = "FRM_LCD_Init";
|
||||
$hash->{SetFn} = "FRM_LCD_Set";
|
||||
$hash->{UndefFn} = "FRM_LCD_Undef";
|
||||
$hash->{AttrFn} = "FRM_LCD_Attr";
|
||||
|
||||
$hash->{AttrList} = "IODev model backLight:on,off blink:on,off autoClear:on,off autoBreak:on,off loglevel:0,1,2,3,4,5 $main::readingFnAttributes";
|
||||
# autoScroll:on,off direction:leftToRight,rightToLeft do not work reliably
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_LCD_Init($)
|
||||
{
|
||||
my ($hash,$args) = @_;
|
||||
my $u = "wrong syntax: define <name> FRM_LCD <type> <size-x> <size-y> [<address>]";
|
||||
|
||||
return $u if(int(@$args) < 3);
|
||||
|
||||
$hash->{type} = shift @$args;
|
||||
$hash->{sizex} = shift @$args;
|
||||
$hash->{sizey} = shift @$args;
|
||||
$hash->{address} = shift @$args if (@$args);
|
||||
|
||||
return "no IODev set" unless defined $hash->{IODev};
|
||||
return "no FirmataDevice assigned to ".$hash->{IODev}->{NAME} unless defined $hash->{IODev}->{FirmataDevice};
|
||||
|
||||
if (($hash->{type} eq "i2c") and defined $hash->{address}) {
|
||||
require LiquidCrystal_I2C;
|
||||
my $lcd = LiquidCrystal_I2C->new($hash->{address},$hash->{sizex},$hash->{sizey});
|
||||
$lcd->attach($hash->{IODev}->{FirmataDevice});
|
||||
$lcd->init();
|
||||
$hash->{lcd} = $lcd;
|
||||
my $name = $hash->{NAME};
|
||||
# FRM_LCD_Apply_Attribute($name,"backlight");
|
||||
# FRM_LCD_Apply_Attribute($name,"autoscroll");
|
||||
# FRM_LCD_Apply_Attribute($name,"direction");
|
||||
# FRM_LCD_Apply_Attribute($name,"blink");
|
||||
if (! (defined AttrVal($name,"stateFormat",undef))) {
|
||||
$main::attr{$name}{"stateFormat"} = "text";
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub FRM_LCD_Attr(@) {
|
||||
my ($command,$name,$attribute,$value) = @_;
|
||||
my $hash = $main::defs{$name};
|
||||
if ($command eq "set") {
|
||||
$main::attr{$name}{$attribute}=$value;
|
||||
FRM_LCD_Apply_Attribute($name,$attribute);
|
||||
}
|
||||
}
|
||||
|
||||
sub FRM_LCD_Apply_Attribute {
|
||||
my ($name,$attribute) = @_;
|
||||
my $lcd = $main::defs{$name}{lcd};
|
||||
if (defined $lcd) {
|
||||
ATTRIBUTE_HANDLER: {
|
||||
$attribute eq "backLight" and do {
|
||||
if (AttrVal($name,"backLight","on") eq "on") {
|
||||
$lcd->backlight();
|
||||
} else {
|
||||
$lcd->noBacklight();
|
||||
}
|
||||
last;
|
||||
};
|
||||
$attribute eq "autoScroll" and do {
|
||||
if (AttrVal($name,"autoScroll","on") eq "on") {
|
||||
$lcd->autoscroll();
|
||||
} else {
|
||||
$lcd->noAutoscroll();
|
||||
}
|
||||
last;
|
||||
};
|
||||
$attribute eq "direction" and do {
|
||||
if (AttrVal($name,"direction","leftToRight") eq "leftToRight") {
|
||||
$lcd->leftToRight();
|
||||
} else {
|
||||
$lcd->rightToLeft();
|
||||
}
|
||||
last;
|
||||
};
|
||||
$attribute eq "blink" and do {
|
||||
if (AttrVal($name,"blink","off") eq "on") {
|
||||
$lcd->blink();
|
||||
} else {
|
||||
$lcd->noBlink();
|
||||
}
|
||||
last;
|
||||
};
|
||||
}
|
||||
} else {
|
||||
main::Log (3, "no lcd found");
|
||||
}
|
||||
}
|
||||
|
||||
sub FRM_LCD_Set(@) {
|
||||
my ($hash, @a) = @_;
|
||||
return "Need at least one parameters" if(@a < 2);
|
||||
my $command = $a[1];
|
||||
my $value = $a[2];
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
|
||||
if(!defined($sets{$command}));
|
||||
my $lcd = $hash->{lcd};
|
||||
COMMAND_HANDLER: {
|
||||
$command eq "text" and do {
|
||||
if (AttrVal($hash->{NAME},"autoClear","on") eq "on") {
|
||||
$lcd->clear();
|
||||
}
|
||||
if (AttrVal($hash->{NAME},"autoBreak","on") eq "on") {
|
||||
my $sizex = $hash->{sizex};
|
||||
my $sizey = $hash->{sizey};
|
||||
my $start = 0;
|
||||
my $len = length $value;
|
||||
for (my $line = 0;$line<$sizey;$line++) {
|
||||
$lcd->setCursor(0,$line);
|
||||
if ($start<$len) {
|
||||
$lcd->print(substr $value, $start, $sizex);
|
||||
} else {
|
||||
last;
|
||||
}
|
||||
$start+=$sizex;
|
||||
}
|
||||
} else {
|
||||
$lcd->print($value);
|
||||
}
|
||||
main::readingsSingleUpdate($hash,"text",$value,1);
|
||||
last;
|
||||
};
|
||||
$command eq "home" and do {
|
||||
$lcd->home();
|
||||
last;
|
||||
};
|
||||
$command eq "clear" and do {
|
||||
$lcd->clear();
|
||||
main::readingsSingleUpdate($hash,"text","",1);
|
||||
last;
|
||||
};
|
||||
$command eq "display" and do {
|
||||
if ($value ne "off") {
|
||||
$lcd->display();
|
||||
} else {
|
||||
$lcd->noDisplay();
|
||||
}
|
||||
last;
|
||||
};
|
||||
$command eq "cursor" and do {
|
||||
my ($x,$y) = split ",",$value;
|
||||
$lcd->setCursor($x,$y);
|
||||
last;
|
||||
};
|
||||
$command eq "scroll" and do {
|
||||
if ($value eq "left") {
|
||||
$lcd->scrollDisplayLeft();
|
||||
} else {
|
||||
$lcd->scrollDisplayRight();
|
||||
}
|
||||
last;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub
|
||||
FRM_LCD_Undef($$)
|
||||
{
|
||||
my ($hash, $name) = @_;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="FRM_I2C"></a>
|
||||
<h3>FRM_I2C</h3>
|
||||
<ul>
|
||||
represents an integrated curcuit connected to the i2c-pins of an <a href="http://www.arduino.cc">Arduino</a>
|
||||
running <a href="http://www.firmata.org">Firmata</a><br>
|
||||
Requires a defined <a href="#FRM">FRM</a>-device to work.<br>
|
||||
this FRM-device has to be configures for i2c by setting attr 'i2c-config' on the FRM-device<br>
|
||||
it reads out the ic-internal storage in intervals of 'sampling-interval' as set on the FRM-device<br><br>
|
||||
|
||||
<a name="FRM_I2Cdefine"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> FRM_I2C <i2c-address> <register> <bytes-to-read></code> <br>
|
||||
Specifies the FRM_I2C device.<br>
|
||||
<li>i2c-address is the (device-specific) address of the ic on the i2c-bus</li>
|
||||
<li>register is the (device-internal) address to start reading bytes from.</li>
|
||||
<li>bytes-to-read is the number of bytes read from the ic</li>
|
||||
</ul>
|
||||
|
||||
<br>
|
||||
<a name="FRM_I2Cset"></a>
|
||||
<b>Set</b><br>
|
||||
<ul>
|
||||
N/A<br>
|
||||
</ul>
|
||||
<a name="FRM_I2Cget"></a>
|
||||
<b>Get</b><br>
|
||||
<ul>
|
||||
N/A<br>
|
||||
</ul><br>
|
||||
<a name="FRM_I2Cattr"></a>
|
||||
<b>Attributes</b><br>
|
||||
<ul>
|
||||
<li><a href="#IODev">IODev</a><br>
|
||||
Specify which <a href="#FRM">FRM</a> to use. (Optional, only required if there is more
|
||||
than one FRM-device defined.)
|
||||
</li>
|
||||
<li><a href="#eventMap">eventMap</a><br></li>
|
||||
<li><a href="#readingFnAttributes">readingFnAttributes</a><br></li>
|
||||
</ul>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
=end html
|
||||
=cut
|
@ -60,7 +60,7 @@ sub open {
|
||||
# We're going to try and create the device connection first...
|
||||
my $package = "Device::Firmata::Platform";
|
||||
eval "require $package";
|
||||
my $device = $package->open($serial_port,$opts);
|
||||
my $device = $package->open($serial_port,$opts) or die "Could not connect to Firmata Server";
|
||||
|
||||
# Figure out what platform we're running on
|
||||
$device->probe;
|
||||
|
@ -28,6 +28,7 @@ use constant (
|
||||
PIN_SHIFT => 5,
|
||||
PIN_I2C => 6,
|
||||
PIN_ONEWIRE => 7,
|
||||
PIN_STEPPER => 8,
|
||||
|
||||
PIN_LOW => 0,
|
||||
PIN_HIGH => 1,
|
||||
|
@ -351,11 +351,12 @@ sub sysex_handle {
|
||||
|
||||
=head2 probe
|
||||
|
||||
Request the version of the protocol that the
|
||||
target device is using. Sometimes, we'll have to
|
||||
wait a couple of seconds for the response so we'll
|
||||
try for 2 seconds and rapidly fire requests if
|
||||
we don't get a response quickly enough ;)
|
||||
On device boot time we wait 3 seconds for firmware name
|
||||
that the target device is using.
|
||||
If not received the starting message, then we wait for
|
||||
response another 2 seconds and fire requests for version.
|
||||
If the response received, then we store protocol version
|
||||
and analog mapping and capability.
|
||||
|
||||
=cut
|
||||
|
||||
@ -364,46 +365,33 @@ sub probe {
|
||||
# --------------------------------------------------
|
||||
my ($self) = @_;
|
||||
|
||||
my $proto = $self->{protocol};
|
||||
my $io = $self->{io};
|
||||
$self->{metadata}{firmware} = '';
|
||||
$self->{metadata}{firmware_version} = '';
|
||||
|
||||
# Wait for 10 seconds only
|
||||
my $end_tics = time + 10;
|
||||
|
||||
# Query every .5 seconds
|
||||
my $query_tics = time;
|
||||
# Wait for 5 seconds only
|
||||
my $end_tics = time + 5;
|
||||
$self->firmware_version_query();
|
||||
while ( $end_tics >= time ) {
|
||||
|
||||
if ( $query_tics <= time ) {
|
||||
|
||||
# Query the device for information on the firmata firmware_version
|
||||
$self->firmware_version_query();
|
||||
select (undef,undef,undef,0.1);
|
||||
|
||||
# Try to get a response
|
||||
$self->poll;
|
||||
|
||||
if ( $self->{metadata}{firmware}
|
||||
&& $self->{metadata}{firmware_version} )
|
||||
{
|
||||
$self->{protocol}->{protocol_version} = $self->{metadata}{firmware_version};
|
||||
|
||||
$self->analog_mapping_query();
|
||||
$self->capability_query();
|
||||
while ($end_tics >= time) {
|
||||
if (($self->{metadata}{analog_mappings}) and ($self->{metadata}{capabilities})) {
|
||||
return 1;
|
||||
}
|
||||
$self->poll();
|
||||
select( undef, undef, undef, 0.2 ); # wait for response
|
||||
if ( $self->poll && $self->{metadata}{firmware} && $self->{metadata}{firmware_version} ) {
|
||||
$self->{protocol}->{protocol_version} = $self->{metadata}{firmware_version};
|
||||
if ( $self->{metadata}{capabilities} ) {
|
||||
if ( $self->{metadata}{analog_mappings} ) {
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$self->analog_mapping_query();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
$query_tics = time + 0.5;
|
||||
else {
|
||||
$self->capability_query();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$self->firmware_version_query() unless $end_tics - 2 >= time; # version query on last 2 sec only
|
||||
}
|
||||
select (undef,undef,undef,0.1);
|
||||
}
|
||||
return undef;
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 pin_mode
|
||||
@ -436,10 +424,7 @@ sub pin_mode {
|
||||
last;
|
||||
};
|
||||
|
||||
( $mode == PIN_PWM || $mode == PIN_I2C || $mode == PIN_ONEWIRE || $mode == PIN_SERVO ) and do {
|
||||
$self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode ));
|
||||
last;
|
||||
};
|
||||
$self->{io}->data_write($self->{protocol}->message_prepare( SET_PIN_MODE => 0, $pin, $mode ));
|
||||
};
|
||||
$self->{pin_modes}->{$pin} = $mode;
|
||||
return 1;
|
||||
@ -786,7 +771,7 @@ sub poll {
|
||||
|
||||
# --------------------------------------------------
|
||||
my $self = shift;
|
||||
my $buf = $self->{io}->data_read(100) or return;
|
||||
my $buf = $self->{io}->data_read(512) or return;
|
||||
my $messages = $self->{protocol}->message_data_receive($buf);
|
||||
$self->messages_handle($messages);
|
||||
return $messages;
|
||||
@ -833,8 +818,7 @@ sub observe_i2c {
|
||||
|
||||
sub observe_onewire {
|
||||
my ( $self, $pin, $observer, $context ) = @_;
|
||||
return undef unless ($self->is_supported_mode($pin,PIN_INPUT));
|
||||
return undef unless ($self->is_supported_mode($pin,PIN_OUTPUT));
|
||||
return undef unless ($self->is_supported_mode($pin,PIN_ONEWIRE));
|
||||
$self->{onewire_observer}[$pin] = {
|
||||
method => $observer,
|
||||
context => $context,
|
||||
|
@ -305,7 +305,11 @@ sub sysex_parse {
|
||||
$return_data = $self->handle_scheduler_response($sysex_data);
|
||||
last;
|
||||
};
|
||||
|
||||
|
||||
$command == $protocol_commands->{RESERVED_COMMAND} and do {
|
||||
$return_data = $sysex_data;
|
||||
last;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@ -313,6 +317,11 @@ sub sysex_parse {
|
||||
command_str => $command_str,
|
||||
data => $return_data
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command => $command,
|
||||
data => $sysex_data
|
||||
}
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
|
433
fhem/FHEM/lib/LiquidCrystal_I2C.pm
Normal file
433
fhem/FHEM/lib/LiquidCrystal_I2C.pm
Normal file
@ -0,0 +1,433 @@
|
||||
# www.DFRobot.com
|
||||
# last updated on 21/12/2011
|
||||
# Tim Starling Fix the reset bug (Thanks Tim)
|
||||
# wiki doc http://www.dfrobot.com/wiki/index.php?title=I2C/TWI_LCD1602_Module_(SKU:_DFR0063)
|
||||
# Support Forum: http://www.dfrobot.com/forum/
|
||||
# Compatible with the Arduino IDE 1.0
|
||||
# Library version:1.1
|
||||
|
||||
# When the display powers up, it is configured as follows:
|
||||
#
|
||||
# 1. Display clear
|
||||
# 2. Function set:
|
||||
# DL = 1; 8-bit interface data
|
||||
# N = 0; 1-line display
|
||||
# F = 0; 5x8 dot character font
|
||||
# 3. Display on/off control:
|
||||
# D = 0; Display off
|
||||
# C = 0; Cursor off
|
||||
# B = 0; Blinking off
|
||||
# 4. Entry mode set:
|
||||
# I/D = 1; Increment by 1
|
||||
# S = 0; No shift
|
||||
#
|
||||
# Note, however, that resetting the Arduino doesn't reset the LCD, so we
|
||||
# can't assume that its in that state when a sketch starts (and the
|
||||
# LiquidCrystal constructor is called).
|
||||
|
||||
package LiquidCrystal_I2C;
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
#Basic commands and constants
|
||||
use constant LCD_CLEARDISPLAY => 0x01;
|
||||
use constant LCD_RETURNHOME => 0x02;
|
||||
use constant LCD_ENTRYMODESET => 0x04;
|
||||
use constant LCD_DISPLAYCONTROL => 0x08;
|
||||
use constant LCD_CURSORSHIFT => 0x10;
|
||||
use constant LCD_FUNCTIONSET => 0x20;
|
||||
use constant LCD_SETCGRAMADDR => 0x40;
|
||||
use constant LCD_SETDDRAMADDR => 0x80;
|
||||
|
||||
# flags for display entry mode
|
||||
use constant LCD_ENTRYRIGHT => 0x00;
|
||||
use constant LCD_ENTRYLEFT => 0x02;
|
||||
use constant LCD_ENTRYSHIFTINCREMENT => 0x01;
|
||||
use constant LCD_ENTRYSHIFTDECREMENT => 0x00;
|
||||
|
||||
# flags for display on/off control
|
||||
use constant LCD_DISPLAYON => 0x04;
|
||||
use constant LCD_DISPLAYOFF => 0x00;
|
||||
use constant LCD_CURSORON => 0x02;
|
||||
use constant LCD_CURSOROFF => 0x00;
|
||||
use constant LCD_BLINKON => 0x01;
|
||||
use constant LCD_BLINKOFF => 0x00;
|
||||
|
||||
# flags for display/cursor shift
|
||||
use constant LCD_DISPLAYMOVE => 0x08;
|
||||
use constant LCD_CURSORMOVE => 0x00;
|
||||
use constant LCD_MOVERIGHT => 0x04;
|
||||
use constant LCD_MOVELEFT => 0x00;
|
||||
|
||||
# flags for function set
|
||||
use constant LCD_8BITMODE => 0x10;
|
||||
use constant LCD_4BITMODE => 0x00;
|
||||
use constant LCD_2LINE => 0x08;
|
||||
use constant LCD_1LINE => 0x00;
|
||||
use constant LCD_5x10DOTS => 0x04;
|
||||
use constant LCD_5x8DOTS => 0x00;
|
||||
|
||||
# flags for backlight control
|
||||
use constant LCD_BACKLIGHT => 0x08;
|
||||
use constant LCD_NOBACKLIGHT => 0x00;
|
||||
|
||||
use constant En => 0b00000100; # Enable bit
|
||||
use constant Rw => 0b00000010; # Read / Write bit
|
||||
use constant Rs => 0b00000001; # Register select bit
|
||||
|
||||
sub print($$) {
|
||||
my ($self,$c) = @_;
|
||||
my @buf = unpack "c*",$c;
|
||||
foreach my $s (@buf) {
|
||||
$self->write($s);
|
||||
}
|
||||
};
|
||||
|
||||
sub write($$) {
|
||||
my ( $self, $value ) = @_;
|
||||
$self->send( $value, Rs );
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub new($$$$) {
|
||||
my ( $class, $lcd_Addr, $lcd_cols, $lcd_rows ) = @_;
|
||||
return bless {
|
||||
Addr => $lcd_Addr,
|
||||
cols => $lcd_cols,
|
||||
rows => $lcd_rows,
|
||||
backlightval => LCD_NOBACKLIGHT,
|
||||
}, $class;
|
||||
}
|
||||
|
||||
sub init($) {
|
||||
my $self = shift;
|
||||
$self->init_priv();
|
||||
}
|
||||
|
||||
sub attach($$) {
|
||||
my ($self,$dev) = @_;
|
||||
$self->{FirmataDevice} = $dev;
|
||||
}
|
||||
|
||||
sub init_priv($) {
|
||||
my $self = shift;
|
||||
|
||||
$self->{displayfunction} =
|
||||
LCD_4BITMODE | LCD_1LINE |
|
||||
LCD_5x8DOTS;
|
||||
$self->begin( $self->{cols}, $self->{rows} );
|
||||
}
|
||||
|
||||
sub begin($$$$) {
|
||||
my ( $self, $cols, $lines, $dotsize ) = @_;
|
||||
if ( $lines > 1 ) {
|
||||
$self->{displayfunction} |= LCD_2LINE;
|
||||
}
|
||||
$self->{numlines} = $lines;
|
||||
|
||||
# for some 1 line displays you can select a 10 pixel high font
|
||||
if ( (defined $dotsize) && ($dotsize != 0) && ( $lines == 1 ) ) {
|
||||
$self->{displayfunction} |= LCD_5x10DOTS;
|
||||
}
|
||||
|
||||
# SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
|
||||
# according to datasheet, we need at least 40ms after power rises above 2.7V
|
||||
# before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
|
||||
select( undef, undef, undef, 0.050 );
|
||||
|
||||
# Now we pull both RS and R/W low to begin commands
|
||||
$self->expanderWrite( $self->{backlightval} )
|
||||
; # reset expanderand turn backlight off (Bit 8 =1)
|
||||
select( undef, undef, undef, 1 );
|
||||
|
||||
#put the LCD into 4 bit mode
|
||||
# this is according to the hitachi HD44780 datasheet
|
||||
# figure 24, pg 46
|
||||
|
||||
# we start in 8bit mode, try to set 4 bit mode
|
||||
$self->write4bits( 0x03 << 4 );
|
||||
select( undef, undef, undef, 0.0045 ); # wait min 4.1ms
|
||||
|
||||
# second try
|
||||
$self->write4bits( 0x03 << 4 );
|
||||
select( undef, undef, undef, 0.0045 ); # wait min 4.1ms
|
||||
|
||||
# third go!
|
||||
$self->write4bits( 0x03 << 4 );
|
||||
select( undef, undef, undef, 0.00015 );
|
||||
|
||||
# finally, set to 4-bit interface
|
||||
$self->write4bits( 0x02 << 4 );
|
||||
|
||||
# set # lines, font size, etc.
|
||||
$self->command(
|
||||
LCD_FUNCTIONSET | $self->{displayfunction} );
|
||||
|
||||
# turn the display on with no cursor or blinking default
|
||||
$self->{displaycontrol} =
|
||||
LCD_DISPLAYON | LCD_CURSOROFF |
|
||||
LCD_BLINKOFF;
|
||||
$self->display();
|
||||
|
||||
# clear it off
|
||||
$self->clear();
|
||||
|
||||
# Initialize to default text direction (for roman languages)
|
||||
$self->{displaymode} =
|
||||
LCD_ENTRYLEFT |
|
||||
LCD_ENTRYSHIFTDECREMENT;
|
||||
|
||||
# set the entry mode
|
||||
$self->command(
|
||||
LCD_ENTRYMODESET | $self->{displaymode} );
|
||||
|
||||
$self->home();
|
||||
|
||||
}
|
||||
|
||||
#********** high level commands, for the user!
|
||||
|
||||
sub clear($) {
|
||||
my $self = shift;
|
||||
$self->command(LCD_CLEARDISPLAY)
|
||||
; # clear display, set cursor position to zero
|
||||
select( undef, undef, undef, 0.002 ); # this command takes a long time!
|
||||
}
|
||||
|
||||
sub home($) {
|
||||
my $self = shift;
|
||||
$self->command(LCD_RETURNHOME)
|
||||
; # set cursor position to zero
|
||||
select( undef, undef, undef, 0.002 ); # this command takes a long time!
|
||||
}
|
||||
|
||||
sub setCursor($$$) {
|
||||
my ( $self, $col, $row ) = @_;
|
||||
my @row_offsets = ( 0x00, 0x40, 0x14, 0x54 );
|
||||
if ( $row > $self->{numlines} ) {
|
||||
$row = $self->{numlines} - 1; # we count rows starting w/0
|
||||
}
|
||||
$self->command(
|
||||
LCD_SETDDRAMADDR | ( $col + $row_offsets[$row] ) );
|
||||
}
|
||||
|
||||
# Turn the display on/off (quickly)
|
||||
sub noDisplay($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} &=
|
||||
~LCD_DISPLAYON; #TODO validate '~'
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
sub display($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} |= LCD_DISPLAYON;
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
# Turns the underline cursor on/off
|
||||
sub noCursor($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} &=
|
||||
~LCD_CURSORON; #TODO validate '~'
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
sub cursor($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} |= LCD_CURSORON;
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
# Turn on and off the blinking cursor
|
||||
sub noBlink($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} &=
|
||||
~LCD_BLINKON; #TODO validate '~'
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
sub blink($) {
|
||||
my $self = shift;
|
||||
$self->{displaycontrol} |= LCD_BLINKON;
|
||||
$self->command(
|
||||
LCD_DISPLAYCONTROL | $self->{displaycontrol} );
|
||||
}
|
||||
|
||||
# These commands scroll the display without changing the RAM
|
||||
sub scrollDisplayLeft($) {
|
||||
my $self = shift;
|
||||
$self->command( LCD_CURSORSHIFT |
|
||||
LCD_DISPLAYMOVE |
|
||||
LCD_MOVELEFT );
|
||||
}
|
||||
|
||||
sub scrollDisplayRight($) {
|
||||
my $self = shift;
|
||||
$self->command( LCD_CURSORSHIFT |
|
||||
LCD_DISPLAYMOVE |
|
||||
LCD_MOVERIGHT );
|
||||
}
|
||||
|
||||
# This is for text that flows Left to Right
|
||||
sub leftToRight($) {
|
||||
my $self = shift;
|
||||
$self->{displaymode} |= LCD_ENTRYLEFT;
|
||||
$self->command(
|
||||
LCD_ENTRYMODESET | $self->{displaymode} );
|
||||
}
|
||||
|
||||
# This is for text that flows Right to Left
|
||||
sub rightToLeft($) {
|
||||
my $self = shift;
|
||||
$self->{displaymode} &=
|
||||
~LCD_ENTRYLEFT; #TODO validate '~'
|
||||
$self->command(
|
||||
LCD_ENTRYMODESET | $self->{displaymode} );
|
||||
}
|
||||
|
||||
# This will 'right justify' text from the cursor
|
||||
sub autoscroll($) {
|
||||
my $self = shift;
|
||||
$self->{displaymode} |= LCD_ENTRYSHIFTINCREMENT;
|
||||
$self->command(
|
||||
LCD_ENTRYMODESET | $self->{displaymode} );
|
||||
}
|
||||
|
||||
# This will 'left justify' text from the cursor
|
||||
sub noAutoscroll($) {
|
||||
my $self = shift;
|
||||
$self->{displaymode} &=
|
||||
~LCD_ENTRYSHIFTINCREMENT; #TODO validate '~'
|
||||
$self->command(
|
||||
LCD_ENTRYMODESET | $self->{displaymode} );
|
||||
}
|
||||
|
||||
# Allows us to fill the first 8 CGRAM locations
|
||||
# with custom characters
|
||||
sub createChar($$$) {
|
||||
my ( $self, $location, $charmap ) = @_;
|
||||
$location &= 0x7; # we only have 8 locations 0-7
|
||||
$self->command( LCD_SETCGRAMADDR | ( $location << 3 ) );
|
||||
for ( my $i = 0 ; $i < 8 ; $i++ ) {
|
||||
$self->write( @$charmap[$i] );
|
||||
}
|
||||
}
|
||||
|
||||
# Turn the (optional) backlight off/on
|
||||
sub noBacklight($) {
|
||||
my $self = shift;
|
||||
$self->{backlightval} = LCD_NOBACKLIGHT;
|
||||
$self->expanderWrite(0);
|
||||
}
|
||||
|
||||
sub backlight($) {
|
||||
my $self = shift;
|
||||
$self->{backlightval} = LCD_BACKLIGHT;
|
||||
$self->expanderWrite(0);
|
||||
}
|
||||
|
||||
#*********** mid level commands, for sending data/cmds
|
||||
|
||||
sub command($$) {
|
||||
my ( $self, $value ) = @_;
|
||||
$self->send( $value, 0 );
|
||||
}
|
||||
|
||||
#************ low level data pushing commands **********
|
||||
|
||||
# write either command or data
|
||||
sub send($$$) {
|
||||
my ( $self, $value, $mode ) = @_;
|
||||
my $highnib = $value & 0xf0;
|
||||
my $lownib = ( $value << 4 ) & 0xf0;
|
||||
$self->write4bits( ($highnib) | $mode );
|
||||
$self->write4bits( ($lownib) | $mode );
|
||||
}
|
||||
|
||||
sub write4bits($$) {
|
||||
my ( $self, $value ) = @_;
|
||||
$self->expanderWrite($value);
|
||||
$self->pulseEnable($value);
|
||||
}
|
||||
|
||||
sub expanderWrite($$) {
|
||||
my ( $self, $data ) = @_;
|
||||
|
||||
$self->{FirmataDevice}->i2c_write($self->{Addr},($data) | $self->{backlightval});
|
||||
}
|
||||
|
||||
sub pulseEnable($$) {
|
||||
my ( $self, $data ) = @_;
|
||||
$self->expanderWrite( $data | En ); # En high
|
||||
select( undef, undef, undef, 0.000001 ); # enable pulse must be >450ns
|
||||
$self->expanderWrite( $data & ~En )
|
||||
; # En low TODO: validate '~'
|
||||
select( undef, undef, undef, 0.000050 ); # commands need > 37us to settle
|
||||
}
|
||||
|
||||
# Alias functions
|
||||
|
||||
sub cursor_on($) {
|
||||
my $self = shift;
|
||||
$self->cursor();
|
||||
}
|
||||
|
||||
sub cursor_off($) {
|
||||
my $self = shift;
|
||||
$self->noCursor();
|
||||
}
|
||||
|
||||
sub blink_on($) {
|
||||
my $self = shift;
|
||||
$self->blink();
|
||||
}
|
||||
|
||||
sub blink_off($) {
|
||||
my $self = shift;
|
||||
$self->noBlink();
|
||||
}
|
||||
|
||||
sub load_custom_character($$$) {
|
||||
my ( $self, $char_num, $rows ) = @_;
|
||||
$self->createChar( $char_num, $rows );
|
||||
}
|
||||
|
||||
sub setBacklight($$) {
|
||||
my ( $self, $new_val ) = @_;
|
||||
if ($new_val) {
|
||||
$self->backlight(); # turn backlight on
|
||||
}
|
||||
else {
|
||||
$self->noBacklight(); # turn backlight off
|
||||
}
|
||||
}
|
||||
|
||||
# unsupported API functions
|
||||
sub off($) { }
|
||||
sub on($) { }
|
||||
sub setDelay ($$$) { }
|
||||
|
||||
sub status($) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub keypad ($) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub init_bargraph($$) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub draw_horizontal_graph($$$$$) { }
|
||||
sub draw_vertical_graph($$$$$) { }
|
||||
sub setContrast($$) { }
|
||||
|
||||
1;
|
Loading…
x
Reference in New Issue
Block a user