初めに
前回の記事で、「ラズパイ pico」でディスプレイに文字を表示させました。
その中で、「LDC]というライブラリを使用しました。
今回の記事は、この「LDC]ライブラリの詳しい解説をしていこうと思います。
ライブラリの解体(上級者向け)
ということで、前回ディスプレイを使うのに「LDC」というライブラリを使用しました。
この「LDC」とは何かというと、Liquid Crystal Displayの略で液晶ディスプレイです
ただし、今回使ったLEDは少し違います。
本来ディスプレイを表示させるのには、16ピン必要です
それが、今回使用したI2Cモジュール(裏についている黒いうもの)を使用することでたったの4ピンで使うことができます。
画像(通信モジュール)
このモジュールを使うのに、必要な必要なLDCライブラリの解説です。
本来は16ピンのところ4ピンで文字を表示させるライブラリの仕組みを見ていきましょう。
import machine
import time
class LCD():
def __init__(self, addr=None, blen=1):
sda = machine.Pin(6)
scl = machine.Pin(7)
self.bus = machine.I2C(1,sda=sda, scl=scl, freq=400000)
#print(self.bus.scan())
self.addr = self.scanAddress(addr)
self.blen = blen
self.send_command(0x33) # Must initialize to 8-line mode at first
time.sleep(0.005)
self.send_command(0x32) # Then initialize to 4-line mode
time.sleep(0.005)
self.send_command(0x28) # 2 Lines & 5*7 dots
time.sleep(0.005)
self.send_command(0x0C) # Enable display without cursor
time.sleep(0.005)
self.send_command(0x01) # Clear Screen
self.bus.writeto(self.addr, bytearray([0x08]))
def scanAddress(self, addr):
devices = self.bus.scan()
if len(devices) == 0:
raise Exception("No LCD found")
if addr is not None:
if addr in devices:
return addr
else:
raise Exception(f"LCD at 0x{addr:2X} not found")
elif 0x27 in devices:
return 0x27
elif 0x3F in devices:
return 0x3F
else:
raise Exception("No LCD found")
def write_word(self, data):
temp = data
if self.blen == 1:
temp |= 0x08
else:
temp &= 0xF7
self.bus.writeto(self.addr, bytearray([temp]))
def send_command(self, cmd):
# Send bit7-4 firstly
buf = cmd & 0xF0
buf |= 0x04 # RS = 0, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
# Send bit3-0 secondly
buf = (cmd & 0x0F) << 4
buf |= 0x04 # RS = 0, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
def send_data(self, data):
# Send bit7-4 firstly
buf = data & 0xF0
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
# Send bit3-0 secondly
buf = (data & 0x0F) << 4
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
def clear(self):
self.send_command(0x01) # Clear Screen
def openlight(self): # Enable the backlight
self.bus.writeto(self.addr,bytearray([0x08]))
# self.bus.close()
def write(self, x, y, str):
if x < 0:
x = 0
if x > 15:
x = 15
if y < 0:
y = 0
if y > 1:
y = 1
# Move cursor
addr = 0x80 + 0x40 * y + x
self.send_command(addr)
for chr in str:
self.send_data(ord(chr))
def message(self, text):
#print("message: %s"%text)
for char in text:
if char == '\n':
self.send_command(0xC0) # next line
else:
self.send_data(ord(char))
これが、LDCのライブラリの中身です。
この「LDC」モジュールは様々なディスプレイに対応できるように設計されているので、不要な部分を削ってみました。
それが以下のようになります。
import machine
import time
class LCD_easy():
def __init__(self,addr=None,blen=1):
sda=machine.Pin(0)
scl=machine.Pin(1)
self.bus=machine.I2C(0,sda=sda,scl=scl,freq=400000)
self.addr=self.scanAddress(addr)
self.blen=blen
#ディスプレイの指定(2行の5x7どっと)
self.send_command(0x28)
time.sleep(0.005)
#コンソールを表示しないコマンド
self.send_command(0x0c)
time.sleep(0.005)
#スクリーンをクリアするコマンド
self.send_command(0x01)
time.sleep(0.005)
self.bus.writeto(self.addr,bytearray([0x08]))
def scanAddress(self,addr):
devices=self.bus.scan()
if len(devices)==0:
raise Exception('No LCD found')
if addr is not None:
if addr in devices:
return addr
else:
raise Exception(f'LCD at 0x{addr:2x}not found')
elif 0x27 in devices:
return 0x27
elif 0x3F in devices:
return 0x3F
else:
raise Exception('No LCD found')
def write_word(self,data):
temp=data
if self.blen==1:
temp |=0x08
else:
temp &=0xf7
self.bus.writeto(self.addr,bytearray([temp]))
def send_command(self,cmd):
buf=cmd & 0xF0
buf |=0x04
self.write_word(buf)
time.sleep(0.002)
buf &=0xFB
self.write_word(buf)
buf=(cmd & 0x0F) <<4
buf |=0x04
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB
self.write_word(buf)
def send_data(self, data):
# Send bit7-4 firstly
buf = data & 0xF0
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
# Send bit3-0 secondly
buf = (data & 0x0F) << 4
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
def clear(self):
self.send_command(0x01)
def openlight(self):
self.bus.writeto(self.addr,bytearray([0x08]))
def message(self, text):
#print("message: %s"%text)
for char in text:
if char == '\n':
self.send_command(0xC0)
else:
self.send_data(ord(char))
lcd=LCD_easy()
string='kouduki\n'
lcd.message(string)
string='lab'
lcd.message(string)
こんな感じになります。
ということで大きく分けて5つで解説していきます。
ブロックごとに解説!
__init__
まずは、「__init__」についてです。
これは、特殊メソッドでクラスが呼び出しされると初期値を渡します。
では、詳しく見ていきます。
下記のプログラムも見ながら、みてみてください。
def __init__(self,addr=None,blen=1):
sda=machine.Pin(0)
scl=machine.Pin(1)
self.bus=machine.I2C(0,sda=sda,scl=scl,freq=400000)
self.addr=self.scanAddress(addr)
self.blen=blen
#ディスプレイの指定(2行の5x7どっと)
self.send_command(0x28)
time.sleep(0.005)
#コンソールを表示しないコマンド
self.send_command(0x0c)
time.sleep(0.005)
#スクリーンをクリアするコマンド
self.send_command(0x01)
time.sleep(0.005)
self.bus.writeto(self.addr,bytearray([0x08]))
__init__()の中にあるselfといれることで、どこからでもこの関数の中にある値にアクセスできるようになります。
そして、addr=Noneとは最初は空の状態で何も入れないということを表しています。
blenはデフォルトで1が入っていて、特定の条件下で0か1に変化します。
このディスプレイでは変更する必要がないので変わっていません。
SDA-,SCL-これらは、I2C通信(2本の線を使った通信方式)で使用するピンのを定義します。
sdaとは、データを送る線。sclはクロック線と言ってタイミングを計るものです。
sdaに電気が流れているか、いないかで0か1を渡します。
sclは今からデータ送るよ!と教える線です。
self.bus=machine.I2C(0,sda=sda,scl=scl,freq=400000)
これは、I2Cの通信の定義をします。()の最初の0はI2Cのどの線を使うかを定義するものです
以下の画像のI2C0かI2C1をつなぐかによって、変わります。
sda,sclは最初に定義したものを使います。
ここにピンの配線図
self.addr=self.scanAddress(addr)
これは、scanAddress関数を実行して帰ってきた値をaddrに入れます
self.blen=blen
これは値をそのまま代入します。
self.send_command(0x28)
time.sleep(0.005)
これは、send_command関数を用いて、ディスプレイに0x28(2進数で0010 1000)を送ります。
この0x28とは、ディスプレイに2行の5×7ドットディスプレイだよね?
と確認するためのコマンドです。ほかにも3行のディスプレイや1行のディスプレイの確認コマンドも存在しますが、今回使ったのが、2行ディスプレイなので、ほかのコマンドは削除しました。
その後、読み込みの時間5ms待機します。
self.send_command(0x0c),self.send_command(0x01)
これらは、それぞれカーソルを表示しない、画面をクリアにするコマンドを送るものです。
このコマンドは、ディスプレイのほうで決まっています。
データシートなどを確認すると、コマンドについて詳しく書いています。
self.bus.writeto(self.addr,bytearray([0x08]))
これはコマンドではなく、データとしてディスプレイに送ります。
0x08はディスプレイのバックライトを制御することができます。
scanadress
続いてscanAdressについて、見ていきましょう。
def scanAddress(self,addr):
devices=self.bus.scan()
if len(devices)==0:
raise Exception('No LCD found')
if addr is not None:
if addr in devices:
return addr
else:
raise Exception(f'LCD at 0x{addr:2x}not found')
elif 0x27 in devices:
return 0x27
elif 0x3F in devices:
return 0x3F
これは、I2Cで通信するデバイスを探すプログラムです。
devices=self.bus.scan()
まずは、ラズパイにつながっているモジュールを探します。
もし、デバイスが見つかったら、devices配列に入れます。
if len(devices)==0:で配列の中身が空だった場合は、以下のプログラムで割り込み処理をしてエラーメッセージを表示します。
if addr is not None:
raise Exception(f'LCD at 0x{addr:2x}not found')
もし、addrが空でない場合は、そのアドレスがdevicesの中にあるか確認します。
含まれている場合は、addrの値を返します。
含まれていない場合は、割り込み処理でエラーメッセージを表示します
elif 0x27 in devices:
return 0x27
elif 0x3F in devices:
return 0x3F
もし、0x27と0x3fがdevicesに含まれている場合は、それらをデフォルトとして値を返します。
write_word
def write_word(self,data):
temp=data
if self.blen==1:
temp |=0x08
else:
temp &=0xf7
self.bus.writeto(self.addr,bytearray([temp]))
blenが1の場合は、orビット演算をして書き込み、
blenが0場合は、andビット演算をして書き込みを行います。
temp=0000 0011
0x08=0000 1000
temp |=0x08
temp=0000 1011
temp &=0x08
temp =0000 0000
orビット演算はどちらかが1の場合に1を入れて、andビット演算の場合はどちらも1の場合に1を入れます。
send_command
モジュールにコマンドを送信するためのコマンドです。
def send_command(self,cmd):
#前半
buf=cmd & 0xF0
buf |=0x04
self.write_word(buf)#今から送るデータの長さを教える
time.sleep(0.002)
buf &=0xFB
self.write_word(buf)#本物のデータを送る
#後半
buf=(cmd & 0x0F) <<4
buf |=0x04
self.write_word(buf)#今から送るデータの長さを教える
time.sleep(0.002)
buf &= 0xFB
self.write_word(buf)#本物のデータを送る
コマンドを前半と後半に分けて、それぞれ2回ずつ通信します。
なぜかというと、「今からこれだけのデータ送るよー」と合図をします。
そのあとに、コードを送ります。
なぜ2回に分けるかというと1回で送れるコマンドの量に限りがあるからです。
send_data
def send_data(self, data):
#前半
buf = data & 0xF0
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
#後半
buf = (data & 0x0F) << 4
buf |= 0x05 # RS = 1, RW = 0, EN = 1
self.write_word(buf)
time.sleep(0.002)
buf &= 0xFB # Make EN = 0
self.write_word(buf)
これも先ほどと同じように前半後半で分けています。
ビット演算のプログラムが違うのは、今から送るお物がコマンドなのかデータなのかを区別するためです。
clear・openlight
def clear(self):
self.send_command(0x01)
def openlight(self):
self.bus.writeto(self.addr,bytearray([0x08]))
これは、画面を初期化、バックライトをつけるコマンドです。
このコマンドは、デバイスであらかじめ決められているので、データシートを確認してください。
message
def message(self, text):
#print("message: %s"%text)
for char in text:
if char == '\n':
self.send_command(0xC0)
else:
self.send_data(ord(char))#アスキーコードに変換して送信
これは、入力された文字を画面に表示させるプログラムです。
まずは、入力されたものをすべてアスキーコードというものに直して、モジュールに送ります。
改行の\nだけは例外なので、特別に定義します。
以下がアスキーコードになります
アルファベットを2進数で表すことができるため、モジュールに2本線だけで送信できるようになります。
ローマ字 | ASCIIコード(10進数) | ASCIIコード(2進数) |
---|---|---|
A | 65 | 01000001 |
B | 66 | 01000010 |
C | 67 | 01000011 |
D | 68 | 01000100 |
E | 69 | 01000101 |
F | 70 | 01000110 |
G | 71 | 01000111 |
H | 72 | 01001000 |
最後に
どうだったでしょうか?
今回は、前回使ったデバイスのライブラリ解体を行いました!
少し専門的な内容ですね
まだ、自分もシリアル通信をマスターしていないのでかなり苦労しました。
もっと勉強が必要ですね!
ということで、また次回お会いしましょう!