Last active
August 29, 2015 14:03
-
-
Save CLCL/87eed626c63b0c2a5df1 to your computer and use it in GitHub Desktop.
Adafruit CharLCDPlateのPythonサンプルをPerl(HiPi)に書き直したとりあえず最低限
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
# Raspberry Pi用Adafruit RGB-backlit LCD plateのPythonライブラリ | |
# https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/e28539734dfbd6965395f0f0478ecad44ca0eaea/Adafruit_CharLCDPlate/Adafruit_CharLCDPlate.py | |
# から文字の表示とバックライトの変更の部分だけ最低限移植 | |
# Perl用I2C/SMBusアクセスライブラリHiPiを使用 | |
# http://raspberry.znix.com/p/install.html | |
# | |
# This is essentially a complete rewrite, but the calling syntax | |
# and constants are based on code from lrvick and LiquidCrystal. | |
# lrvic - https://github.com/lrvick/raspi-hd44780/blob/master/hd44780.py | |
# LiquidCrystal - https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp | |
use strict; | |
use warnings; | |
use HiPi::Device::I2C; | |
use HiPi::BCM2835::I2C; | |
use utf8; | |
use Encode; | |
my $lcd = Adafruit_CharLCDPlate->new(); | |
$lcd->init(); | |
$lcd->begin(16, 2); | |
$lcd->clear(); | |
$lcd->message("Heiwa na hibi...\nZzz..."); | |
sleep(13); | |
$lcd->clear(); | |
$lcd->backlight( $lcd->{color}->{RED} ); | |
$lcd->message(encode('cp932', "Apacheガツマッタ!\nMaxClients=50")); | |
sleep(10); | |
$lcd->clear(); | |
$lcd->stop(); | |
exit; | |
package Adafruit_CharLCDPlate; | |
use strict; | |
use warnings; | |
use HiPi::Device::I2C; | |
use HiPi::BCM2835::I2C; | |
use constant { | |
# Port expander registers, | |
# ポートエキスパンダ(MCP23017) のレジスタ | |
MCP23017_IOCON_BANK0 => 0x0A, # IOCON when Bank 0 active / Bank 0がアクティブ時のIOCONレジスタアドレス | |
MCP23017_IOCON_BANK1 => 0x15, # IOCON when Bank 1 active / Bank 1がアクティブ時のIOCONレジスタアドレス | |
# These are register addresses when in Bank 1 only:, | |
# 以下は、Bank 1がアクティブのみ有効なレジスタアドレス | |
MCP23017_GPIOA => 0x09, | |
MCP23017_IODIRB => 0x10, | |
MCP23017_GPIOB => 0x19, | |
# Port expander input pin definitions | |
# ポートエキスパンダ(MCP23017) の入力ピンの定義 | |
SELECT => 0, | |
RIGHT => 1, | |
DOWN => 2, | |
UP => 3, | |
LEFT => 4, | |
# LED colors | |
# LCDパネルのバックライトLEDの色 | |
OFF => 0x00, | |
RED => 0x01, | |
GREEN => 0x02, | |
BLUE => 0x04, | |
}; | |
use constant { | |
YELLOW => RED + GREEN, | |
TEAL => GREEN + BLUE, | |
VIOLET => RED + BLUE, | |
WHITE => RED + GREEN + BLUE, | |
ON => RED + GREEN + BLUE, | |
# LCD Commands | |
# LCD(ポートエキスパンダの先につながっているキャラクタLCDドライバHD44780用の命令) | |
LCD_CLEARDISPLAY => 0x01, | |
LCD_RETURNHOME => 0x02, | |
LCD_ENTRYMODESET => 0x04, | |
LCD_DISPLAYCONTROL => 0x08, | |
LCD_CURSORSHIFT => 0x10, | |
LCD_FUNCTIONSET => 0x20, | |
LCD_SETCGRAMADDR => 0x40, | |
LCD_SETDDRAMADDR => 0x80, | |
# Flags for display on/off control | |
# ディスプレイOn/Offコントロールフラグ(HD44780用)3bits | |
LCD_DISPLAYON => 0x04, | |
LCD_DISPLAYOFF => 0x00, | |
LCD_CURSORON => 0x02, | |
LCD_CURSOROFF => 0x00, | |
LCD_BLINKON => 0x01, | |
LCD_BLINKOFF => 0x00, | |
# Flags for display entry mode | |
# ディスプレイエントリモードフラグ(HD44780用)2bits | |
LCD_ENTRYRIGHT => 0x00, | |
LCD_ENTRYLEFT => 0x02, | |
LCD_ENTRYSHIFTINCREMENT => 0x01, | |
LCD_ENTRYSHIFTDECREMENT => 0x00, | |
# Flags for display/cursor shift | |
# カーソル/ディスプレイシフトフラグ(HD44780用)2bits | |
LCD_DISPLAYMOVE => 0x08, | |
LCD_CURSORMOVE => 0x00, | |
LCD_MOVERIGHT => 0x04, | |
LCD_MOVELEFT => 0x00, | |
}; | |
sub new { | |
my $class = shift; | |
my %args = ( @_ ); | |
my $self = {}; | |
$self->{addr} = 0x20; | |
$self->{i2c} = HiPi::Device::I2C->new( address => $self->{addr}); | |
$self->{color} = { | |
OFF => OFF, | |
RED => RED, | |
GREEN => GREEN, | |
BLUE => BLUE, | |
YELLOW => YELLOW, | |
TEAL => TEAL, | |
VIOLET => VIOLET, | |
WHITE => WHITE, | |
ON => ON, | |
}; | |
# The LCD data pins (D4-D7) connect to MCP pins 12-9 (PORTB4-1), in | |
# that order. Because this sequence is 'reversed,' a direct shift | |
# won't work. This table remaps 4-bit data values to MCP PORTB | |
# outputs, incorporating both the reverse and shift. | |
# LCDモジュールのDATAピン(D4-D7)は、MCP23017の12-9ピン(PORTB4-1) | |
# にこの順番で結線されている。逆順になっているので、通常のシフト | |
# はうまく動かない。次のテーブルは、シフト・反転用に対応したMPC23017 | |
# のPORTBに出力する値を再定義します。 | |
$self->{flip} = [ | |
0b00000000, 0b00010000, 0b00001000, 0b00011000, | |
0b00000100, 0b00010100, 0b00001100, 0b00011100, | |
0b00000010, 0b00010010, 0b00001010, 0b00011010, | |
0b00000110, 0b00010110, 0b00001110, 0b00011110, | |
]; | |
# The speed of LCD accesses is inherently limited by I2C through the | |
# port expander. A 'well behaved program' is expected to poll the | |
# LCD to know that a prior instruction completed. But the timing of | |
# most instructions is a known uniform 37 mS. The enable strobe | |
# can't even be twiddled that fast through I2C, so it's a safe bet | |
# with these instructions to not waste time polling (which requires | |
# several I2C transfers for reconfiguring the port direction). | |
# The D7 pin is set as input when a potentially time-consuming | |
# instruction has been issued (e.g. screen clear), as well as on | |
# startup, and polling will then occur before more commands or data | |
# are issued. | |
# LCDアクセスの速度は、I2Cで接続されるポートエキスパンダ(MCP23017) | |
# で制限されます。 | |
# 「行儀のよいプログラム」は先に実行した命令の完了をLCDからポーリング | |
# します。しかし、ほとんどのプログラムは37msのウェイトで済ませています。 | |
# enable信号は、I2Cによってそれほど速く切り替えることができません。 | |
# したがって、それは時間ポーリング(それはポート方向の再構成のために、 | |
# いくつかのI2Cが移ることを必要とする)を浪費しないためにこれらの指 | |
# 示を備えた安全策です。 | |
# D7ピンは、潜在的に時間を消費する命令が出された場合(例えばスクリー | |
# ンクリア)だけでなく、起動時などに入力に設定され、複数のコマンド | |
# あるいはデータが出される場合、ポーリングの必要が生じます。 | |
$self->{pollables} = [ LCD_CLEARDISPLAY, LCD_RETURNHOME ]; | |
# I2C is relatively slow. MCP output port states are cached | |
# so we don't need to constantly poll-and-change bit states. | |
# self.porta, self.portb, self.ddrb = 0, 0, 0b00010000 | |
# I2Cのレスポンスは比較的遅い。MCP23017の出力ポートの変更状態 | |
# はキャッシュ($self->{poata}, $self->{portb}, $self->{ddrb}) | |
# で管理するので、書き込み後いちいちポーリングして確認する | |
# 必要はありません。 | |
$self->{porta} = 0; | |
$self->{portb} = 0; | |
$self->{ddrb} = 0b00010000; | |
# Set MCP23017 IOCON register to Bank 0 with sequential operation. | |
# If chip is already set for Bank 0, this will just write to OLATB, | |
# which won't seriously bother anything on the plate right now | |
# (blue backlight LED will come on, but that's done in the next | |
# step anyway). | |
# MCP23017のIOCONレジスタをBank0、シーケンシャル処理を有効に設定し | |
# ます。Bank 1になっている想定で決め打ちのアドレスで書き込むため、 | |
# すでにMCP23017チップがBank 0に切り替わっていた場合、OLATBに書き | |
# 込まれることになりますが、特に問題はありません(青いバックライ | |
# トが点灯しますが、次のステップで解決します)。 | |
# 0x80 BANK 0=bank 0を選択 | |
# 0x20 SEQOP シーケンシャルオペレーション 0=アドレスのオートインクリメントをする | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK1, 0 ); | |
# Brute force reload ALL registers to known state. This also | |
# sets up all the input pins, pull-ups, etc. for the Pi Plate. | |
# 総当たりですべてのレジスタに設定を読み込ませます。また、 | |
# Pi Plateの入力ピン設定や、プルアップ設定も行います。 | |
$self->{i2c}->smbus_write_i2c_block_data(0, [ | |
0b00111111, # IODIRA R+G LEDs=outputs, buttons=inputs | |
$self->{ddrb} , # IODIRB LCD D7=input, Blue LED=output | |
0b00111111, # IPOLA Invert polarity on button inputs | |
0b00000000, # IPOLB | |
0b00000000, # GPINTENA Disable interrupt-on-change | |
0b00000000, # GPINTENB | |
0b00000000, # DEFVALA | |
0b00000000, # DEFVALB | |
0b00000000, # INTCONA | |
0b00000000, # INTCONB | |
0b00000000, # IOCON | |
0b00000000, # IOCON | |
0b00111111, # GPPUA Enable pull-ups on buttons | |
0b00000000, # GPPUB | |
0b00000000, # INTFA | |
0b00000000, # INTFB | |
0b00000000, # INTCAPA | |
0b00000000, # INTCAPB | |
$self->{porta}, # GPIOA | |
$self->{portb}, # GPIOB | |
$self->{porta}, # OLATA 0 on all outputs; side effect of | |
$self->{portb} # OLATB turning on R+G+B backlight LEDs. | |
]); | |
# Switch to Bank 1 and disable sequential operation. | |
# From this point forward, the register addresses do NOT match | |
# the list immediately above. Instead, use the constants defined | |
# at the start of the class. Also, the address register will no | |
# longer increment automatically after this -- multi-byte | |
# operations must be broken down into single-byte calls. | |
# MCP23017をBank 1に切り替えて、シーケンシャル処理を無効にします。 | |
# これ以降は、レジスタのアドレスはこのメソッドの最初のレジスタの順番と | |
# 一致しません。このメソッドの最初で定義した定数を使うこと。 | |
# 更に、アドレスレジスタは、これ以降は自動的にインクリメントしません。 | |
# マルチバイトの操作は、シングルバイトの呼び出しに分割処理する必要 | |
# があります。 | |
# 0x80 BANK 1=bank 1を選択 | |
# 0x20 SEQOP シーケンシャルオペレーション 1=アドレスのオートインクリメントしない | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK0, 0b10100000); | |
$self->{displayshift} = (LCD_CURSORMOVE | LCD_MOVERIGHT); | |
$self->{displaymode} = (LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT); | |
$self->{displaycontrol} = (LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF); | |
return bless $self, $class; | |
} | |
sub init { | |
my $self = shift; | |
$self->write(0x33); # Init | |
$self->write(0x32); # Init | |
$self->write(0x28); # 2 line 5x8 matrix | |
# 0x20 Function set | |
# 0x10 DL データ長 0=4-bit length | |
# 0x08 NL 行数 1=2行 | |
# 0x04 F フォント 0=5x8 dotsフォント | |
$self->write(LCD_CLEARDISPLAY); | |
$self->write(LCD_CURSORSHIFT | $self->{displayshift}); | |
$self->write(LCD_ENTRYMODESET | $self->{displaymode}); | |
$self->write(LCD_DISPLAYCONTROL | $self->{displaycontrol}); | |
$self->write(LCD_RETURNHOME); | |
} | |
# ---------------------------------------------------------------------- | |
# Write operations | |
# Low-level 4-bit interface for LCD output. This doesn't actually | |
# write data, just returns a byte array of the PORTB state over time. | |
# Can concatenate the output of multiple calls (up to 8) for more | |
# efficient batch write. | |
# LCD出力用のローレベル4bitインタフェース。配列にあるbyteデータをPORTBへ | |
# 直接書き込めないので、4bitデータの4回書き込みに変換する。 | |
# まとめて書き込む場合最大8個連結することができる。 | |
sub out4 { | |
my $self = shift; | |
my $bitmask = shift; | |
my $value = shift; | |
my $hi = $bitmask | $self->{flip}->[ $value >> 4 ]; | |
my $lo = $bitmask | $self->{flip}->[ $value & 0x0F]; | |
return [ $hi | 0b00100000, $hi, $lo | 0b00100000, $lo]; | |
} | |
# Write byte, list or string value to LCD | |
# LCDに設定値の配列または文字列を書き込む | |
sub write { | |
my $self = shift; | |
my $value= shift; | |
my $char_mode = shift || 0; | |
# """ Send command/data to LCD """ | |
# If pin D7 is in input state, poll LCD busy flag until clear. | |
# もしD7ピンがinputになってるなら、LCDのbusyフラグ(D7ピン)がCLEARになるまでポーリングする。 | |
if ($self->{ddrb} & 0b00010000 ) { | |
my $lo = ( $self->{portb} & 0b00000001) | 0b01000000; | |
my $hi = $lo | 0b00100000; # E=1 (strobe) | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $lo); | |
while (1) { | |
# Strobe high (enable) | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $hi ); | |
my $bits = $self->{i2c}->smbus_read_byte(); | |
# Strobe low, high, low. Second nybble (A3) is ignored. | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, [$lo, $hi, $lo]); | |
last if ( $bits & 0b00000010) == 0; # D7=0, not busy | |
} | |
$self->{portb} = $lo; | |
# Polling complete, change D7 pin to output | |
# ポーリングが終わったら、D7ピンをoutputに変更する | |
$self->{ddrb} &= 0b11101111; | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IODIRB, $self->{ddrb}); | |
} | |
my $bitmask = $self->{portb} & 0b00000001; | |
$bitmask |= 0b10000000 if $char_mode; # Set data bit if not a command | |
# If string or list, iterate through multiple write ops | |
# 引数が文字列または配列のリファレンスの場合、複数書き込みとして処理する | |
if ( ( $value ^ $value ) ne '0' ) { # 文字列 | |
my $last = length($value) - 1; | |
my $data = []; | |
my $i = 0; | |
foreach my $v ( split (//, $value) ) { | |
# Append 4 bytes to list representing PORTB over time. | |
# First the high 4 data bits with strobe (enable) set | |
# and unset, then same with low 4 data bits (strobe 1/0). | |
# 表わすPORTBを時間にわたってリストするために4バイトを追加してください。 | |
# 最初に、strobe(Enable)を1/0に設定した上位4bitデータ、 | |
# その後、下位4bitデータ(strobe 1/0) | |
# (つまり8bitデータを4回に分けて送る:MCPとLCDのデータ線が4bitなため) | |
push @$data, @{$self->out4($bitmask, ord($v))}; | |
# I2C block data write is limited to 32 bytes max. | |
# If limit reached, write data so far and clear. | |
# Also do this on last byte if not otherwise handled. | |
# I2Cブロック・データ書き込みは最大32バイトに制限されています。 | |
# 限界が達した場合は、ここまでのデータを書いて空にしてください。 | |
# 限界でない場合でも、処理されていないデータがあれば、最後のバイトで | |
# 書き込みを行ってください。 | |
if ( ( $#{$data} >= 31 ) || ($i == $last) ) { | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; # Save state of last byte out | |
$data = []; # Clear list for next iteration | |
} | |
$i++; | |
} | |
} | |
elsif ( ref $value eq 'ARRAY' ) { # 配列のリファレンス | |
## Same as above, but for list instead of string | |
## 上と同じルーチンだけど配列用の処理として用意 | |
my $last = length($value) - 1; | |
my $data = []; | |
my $i = 0; | |
foreach my $v ( @$value ) { | |
push @$data, @{$self->out4($bitmask, $v)}; | |
if ( ( $#{$data} >= 31) || ($i == $last) ) { | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; # Save state of last byte out | |
$data = []; # Clear list for next iteration | |
} | |
$i++; | |
} | |
} | |
else { | |
# Single byte | |
# シングルバイト | |
my $data = $self->out4($bitmask, $value); | |
$self->{i2c}->smbus_write_block_data( MCP23017_GPIOB, $data ); | |
$self->{portb} = $data->[-1]; | |
} | |
# If a poll-worthy instruction was issued, reconfigure D7 | |
# pin as input to indicate need for polling on next call. | |
# ポーリング結果で、ポーリング指示が出された場合は、次の呼び出しでポーリングする必要 | |
# があるので、D7ピンを入力に設定する。 | |
if ( (!$char_mode) && (grep {$_ eq $value } @{$self->{pollables}} ) ) { | |
$self->{ddrb} |= 0b00010000; | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IODIRB, $self->{ddrb} ); | |
} | |
} | |
# ---------------------------------------------------------------------- | |
# Utility methods | |
sub begin { | |
my $self = shift; | |
my $cols = shift; | |
my $lines = shift; | |
$self->{currline} = 0; | |
$self->{numlines} = $lines; | |
$self->clear(); | |
} | |
# Puts the MCP23017 back in Bank 0 + sequential write mode so | |
# that other code using the 'classic' library can still work. | |
# Any code using this newer version of the library should | |
# consider adding an atexit() handler that calls this. | |
sub stop { | |
my $self = shift; | |
$self->{porta} = 0b11000000; # Turn off LEDs on the way out | |
$self->{portb} = 0b00000001; | |
HiPi::BCM2835::I2C->delay(15); | |
$self->{i2c}->smbus_write_byte_data( MCP23017_IOCON_BANK1, 0); | |
$self->{i2c}->smbus_write_i2c_block_data(0, [ | |
0b00111111, # IODIRA | |
$self->{ddrb}, # IODIRB | |
0b00000000, # IPOLA | |
0b00000000, # IPOLB | |
0b00000000, # GPINTENA | |
0b00000000, # GPINTENB | |
0b00000000, # DEFVALA | |
0b00000000, # DEFVALB | |
0b00000000, # INTCONA | |
0b00000000, # INTCONB | |
0b00000000, # IOCON | |
0b00000000, # IOCON | |
0b00111111, # GPPUA | |
0b00000000, # GPPUB | |
0b00000000, # INTFA | |
0b00000000, # INTFB | |
0b00000000, # INTCAPA | |
0b00000000, # INTCAPB | |
$self->{porta}, # GPIOA | |
$self->{portb}, # GPIOB | |
$self->{porta}, # OLATA | |
$self->{portb}, | |
]); # OLATB | |
} | |
sub clear { | |
my $self = shift; | |
$self->write(LCD_CLEARDISPLAY); | |
} | |
sub home { | |
my $self = shift; | |
$self->write(LCD_RETURNHOME); | |
} | |
sub message { | |
my $self = shift; | |
my $text = shift; | |
# """ Send string to LCD. Newline wraps to second line""" | |
my @lines = split(/\n/, $text); # Split at newline(s) | |
my $i = 0; | |
foreach my $line ( @lines ) { # For each substring... | |
if ( $i > 0 ) { # If newline(s), | |
$self->write( 0xC0 ); # set DDRAM address to 2nd line | |
} | |
$self->write( $line, 1 ); # Issue substring | |
$i++; | |
} | |
} | |
sub backlight { | |
my $self = shift; | |
my $color = shift; | |
my $c = ~$color; | |
$self->{porta} = ($self->{porta} & 0b00111111) | (($c & 0b011) << 6); | |
$self->{portb} = ($self->{portb} & 0b11111110) | (($c & 0b100) >> 2); | |
# Has to be done as two writes because sequential operation is off. | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOA, $self->{porta}); | |
$self->{i2c}->smbus_write_byte_data( MCP23017_GPIOB, $self->{portb}); | |
} | |
1; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment