はじめに
あちらこちらの箱に散在していたパーツ類を整理していたら、かなり前に購入した「マルツパーツ館 16文字x2行表示 キャラクタLCDモジュール」が出てきた。購入したことすら記憶に残っていなかったが、デュポンケーブルでRaspberry Pi Zeroに直接接続したところ、他に何もしなくても簡単に表示できたので、もう少し遊んでみようと思う。
つい先日、aitendo製LCDの記事をアップしたが、今回の記事とは関係なくLCDつながりも単なる偶然。
1. マルツパーツ館
「○にツ」のマルツパーツ館のロゴが入った取扱説明書と一緒に出てきた。ピンヘッダをハンダ付けしたものの、そのまま放置しいつしか記憶からも消え去ったと思われる。
「マルツパーツ館」をネットで検索すると、2014年10月に現マルツにリニューアルしたというブログがヒットした。今もロゴは「○にツ」ですね。
「MI2CLCD-01」を検索すると、現在のマルツの商品がヒットする。おそらく同じLCDモジュールだと思われる。
2. Raspberry Pi Zeroに接続
取説には、バスライン(SDA/SCL)は、
・ ハンダジャンパで基板上の2.2KΩのプルアップを有効化できる
・ 外付けする場合は、2.2〜4.7KΩの抵抗でプルアップすること
と、説明があり、もしかして、Raspberry Pi の1.8KΩのプルアップでも動作するかも?と半信半疑でデュポンケーブルでRaspberry Pi Zeroに接続したところ、すんなり表示できた。
使用したプログラムは、aitendo製LCDの記事で使ったものそのもの。ピンアサインは以下の通り。(GNDが交差しなければ、1・3・5・7・9と一直線。惜しい)
LCDモジュール | Raspberry Pi (ピン番号) |
---|---|
VDD | 3V3 (1) |
GND | GND (9) |
SDA | BCM2 (3) |
SCL | BCM3 (5) |
RST | BCM4 (7) |
3. サイズがRaspberry Pi Zeroと同じ
このLCDを見つけて手にしたときに、すぐに思ったのが「サイズがRaspberry Pi Zeroと同じじゃ?」。基板同士を重ねてみるとほぼ同じ。
マルツのサイトにサイズが載っていたので、並べるとホントに同じです。
このLCDモジュールができた頃は、Raspberry Pi Zeroはまだ存在していないと思うので、偶然なんでしょうね。専用HATを自作しようかと思うほどジャストサイズ。
4. 遊んでみる
(a) アイコンを表示する
このLCDは、9種類のアイコンを表示できるので試してみた。
アイコンを表示する関数は以下の通り。念のため、adrは0〜15、bitは0〜31の範囲にマスクしている。
def lcd_setIcon(adr, bit):
lcd_cmd(0x39)
lcd_cmd(0x40 | (adr & 0xF))
lcd_data(bit & 0x1F)
lcd_cmd(0x38)
このadrとbitで表示できるアイコンはどれ?、取説にもST7032のデータシートにも記載がない。「st7032 アイコン」で検索すると、ストロベリー・リナックスが販売している「I2C低電圧キャラクタ液晶モジュール(16x2行)」の参考資料「アプリケーションノート」がヒットし詳しく載っていた。ストロベリー・リナックスのLCDはBO1602B、マルツのLCDのBO1602Dと、ほぼ一緒なんでしょうね。下表に引用させていただきました。
例えば、バッテリー満タンのアイコンを表示したい場合は、adr=0xD、bit=0x1Eを指定します。消す場合は bit=0。

上の写真のようにちゃんと表示できました。昔のガラケーを思いださせる懐かしいアイコンです。
バッテリー充電中をイメージしたアイコンを設定してみた。↓それっほく見えますか?
本来ならタイマーイベントで処理するところを、サボって単純ループで実装しています。
while True:
for bit in [0x02, 0x12, 0x0a, 0x06]:
lcd_setIcon(0xd, bit)
time.sleep(0.5)
lcd_setIcon(0xd, 0)
time.sleep(0.5)
(b) CGRAMにビットパターンを定義して表示する
(動画の)上の写真でアイコンの行の下に1行空いて、2行目にTM、十字架、§・・・が表示されていますが、本当は、1行目に下表に示した左端の図形が表示されるはずなんですが・・・(ST7032データシートから引用)
ですが、これは、OPR1=1&OPR2=1 の場合で、何も表示されていないということは、このLCDはOPR1=0&OPR2=0であることを意味していると思われ、この場合は、CGRAMに好きな図形を定義して表示させることができるようです。
1文字分のビットパターンは、横5ドッド x 縦8ライン。縦の8ライン目はカーソルの表示でも使われるので、0にしておくのが無難でしょう。つまり、5x7ドット図形。
CGRAMにビットパターンを設定する関数は以下の通り。adrは0x0〜0xF、patternは1バイトx8の配列で指定します。1バイトの下5ビットで1ラインのデータを指定し、それを8行分。
def lcd_setCGRAM(adr, pattern):
adr &= 0xF
lcd_cmd(0x40 | (adr << 3))
for char in pattern:
lcd_data(char)
# ↑
lcd_setCGRAM(0x5, [0b00100, 0b01110, 0b10101, 0b00100, \
0b00100, 0b00100, 0b00100, 0b00000])
では、上表の0x0〜0xFの16個のビットパターンを自分で定義して表示させてみます。しかし、
patterns = [[0b00010, 0b00101, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000], \
[0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b10100, 0b01000, 0b00000], \
[0b00000, 0b00000, 0b01010, 0b10101, 0b10101, 0b01010, 0b00000, 0b00000], \
[0b11111, 0b10001, 0b10010, 0b10100, 0b11000, 0b10000, 0b00000, 0b00000], \
[0b00001, 0b00001, 0b00101, 0b01001, 0b11111, 0b01000, 0b00100, 0b00000], \
[0b00100, 0b01110, 0b10101, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000], \
[0b00100, 0b00100, 0b00100, 0b00100, 0b10101, 0b01110, 0b00100, 0b00000], \
[0b00000, 0b00100, 0b00010, 0b11111, 0b00010, 0b00100, 0b00000, 0b00000], \
[0b00000, 0b00100, 0b01000, 0b11111, 0b01000, 0b00100, 0b00000, 0b00000], \
[0b11111, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b00000], \
[0b11111, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00000], \
[0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111, 0b00000], \
[0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b11111, 0b00000], \
[0b00000, 0b00000, 0b00000, 0b01100, 0b01100, 0b00000, 0b00000, 0b00000], \
[0b01110, 0b10011, 0b10101, 0b10011, 0b10101, 0b10101, 0b01110, 0b00000], \
[0b01110, 0b11011, 0b10101, 0b10111, 0b10101, 0b11011, 0b01110, 0b00000]]
for adr in range(16):
lcd_setCGRAM(adr, patterns[adr])

なぜか、後半の8文字分が繰り返し表示されるだけ??
データシートをよく読むと、
OPR1=0&OPR2=0は8文字のようです。前半の8文字の設定に変えたら下記写真の通り。16文字使えないのはちょっとガッカリ。OPR1=1&OPR2=0も同じ8文字なので、こっちかもしれない。(16文字だと思っていたが、別なコントローラICの仕様だったようだ)

(c) CGRAMを動的に書き換えたら表示はどうなる?
前項の16個の図形のビットパターンのうち、前半の8個分をCGRAMに定義して、その文字を表示させた状態で後半の8個分をCGRAMに再定義すると、表示済みの文字はどうなるのか実験してみた。
想定される動作としては、次のどちらか。
- CGRAMの再定義によって、表示済みの文字も書き変わる
- 表示済みの文字は変わらず、新しく書いた文字が再定義後の図形になる
もし後者なら、使い道が広がるが、おそらく前者ではないかと予想する。
確認するプログラムは以下の通り。①最初に前半の8個分をCGRAMに定義、②1行目に文字コード0x0〜0x7の文字を出力。③後半の8個分をCGRAMに再定義。ここで1行目の表示に変化があるか ないか。④2行目に文字コード0x0〜0x7の文字を出力。1行目と2行目の文字に差が出るか 出ないか。
patterns = [[0b00010, 0b00101, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000], \
[0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b10100, 0b01000, 0b00000], \
[0b00000, 0b00000, 0b01010, 0b10101, 0b10101, 0b01010, 0b00000, 0b00000], \
[0b11111, 0b10001, 0b10010, 0b10100, 0b11000, 0b10000, 0b00000, 0b00000], \
[0b00001, 0b00001, 0b00101, 0b01001, 0b11111, 0b01000, 0b00100, 0b00000], \
[0b00100, 0b01110, 0b10101, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000], \
[0b00100, 0b00100, 0b00100, 0b00100, 0b10101, 0b01110, 0b00100, 0b00000], \
[0b00000, 0b00100, 0b00010, 0b11111, 0b00010, 0b00100, 0b00000, 0b00000], \
[0b00000, 0b00100, 0b01000, 0b11111, 0b01000, 0b00100, 0b00000, 0b00000], \
[0b11111, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b00000], \
[0b11111, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00000], \
[0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111, 0b00000], \
[0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b11111, 0b00000], \
[0b00000, 0b00000, 0b00000, 0b01100, 0b01100, 0b00000, 0b00000, 0b00000], \
[0b01110, 0b10011, 0b10101, 0b10011, 0b10101, 0b10101, 0b01110, 0b00000], \
[0b01110, 0b11011, 0b10101, 0b10111, 0b10101, 0b11011, 0b01110, 0b00000]]
# ①
for adr in range(8):
lcd_setCGRAM(adr, patterns[adr])
# ②
lcd_cmd(0x01) # clear display
for char in range(8):
lcd_data(char)
print("first 8 CGRAM")
time.sleep(5)
# ③
for adr in range(8, 16):
lcd_setCGRAM(adr & 0x7, patterns[adr])
# ④
lcd_move(0x40) # move next line
for char in range(8):
lcd_data(char)
print("last 8 CGRAM")
やはり予想した通りの↓結果であった。しかし、瞬時に描き変わるのでこれはこれで使い道があるかも。
5. 実用的なプログラムを書く
サイズ感がフィットしているので、Raspberry Pi Zero WHに常設し普段使いしようと思い、以下のプログラムを書くことにした。
- (IPアドレス取得後に)自身のホスト名とIPアドレスを表示
- バッテリーアイコンを使って、CPU使用率を表示(1秒間隔で更新)
- 数分経過したら、ホスト名とIPアドレスの代わりに、現在時刻とCPU使用率を数字で表示
- GPIOに取り付けたスイッチを押すことで、「ホスト名・IPアドレス」表示と「現在時刻・CPU使用率」表示を切り替え
ここをクリックすると、プログラム全体を表示する
import os
import psutil
import smbus
import time
import datetime
import RPi.GPIO as GPIO
import signal
import threading
BUSY = False
STOP = False
def handle_exit(signal_number, stack):
global STOP
STOP = True
lcd_clear()
lcd_puts("bye!")
GPIO.cleanup()
print("bye!")
exit()
def ip_adr():
cmd = "ifconfig | grep 192.168 "
result = os.popen(cmd).read().split()
return "" if len(result) == 0 else result[1]
def hostname():
cmd = "hostname"
result = os.popen(cmd).read().split()
return result[0] + ".local"
LAST_CPU = 0.0
def cpu_usage():
global LAST_CPU
LAST_CPU = psutil.cpu_percent(interval=1)
return LAST_CPU
def timer_scheduler():
global STOP
if not STOP:
t = threading.Timer(1, timer_scheduler)
t.start()
cpu = cpu_usage()
i2c = smbus.SMBus(1) # 1 is bus number
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)
def delay(ms):
time.sleep(ms / 1000)
def lcd_cmd(cmd):
global BUSY
BUSY = True
i2c.write_byte_data(i2cadr, 0x00, cmd)
delay(2)
BUSY = False
def lcd_data(char):
global BUSY
BUSY = True
i2c.write_byte_data(i2cadr, 0x40, char)
BUSY = False
delay(2)
def lcd_init():
# reset
delay(500)
GPIO.setup(resetPin, GPIO.OUT)
GPIO.output(resetPin, False)
delay(10)
GPIO.output(resetPin, True)
delay(10)
# LCD initialize
delay(40)
lcd_cmd(0x38) # function set
lcd_cmd(0x39) # function set
lcd_cmd(0x14) # interval osc
lcd_cmd(0x70 | (contrast & 15)) # contrast low
lcd_cmd(0x5c | (contrast >> 4 & 3)) # contrast high / icon / power
lcd_cmd(0x6c) # follower control
delay(300)
lcd_cmd(0x38) # function set
lcd_cmd(0x0c) # display on
delay(2)
def lcd_puts(str):
charSet = []
for char in str:
charSet.append(ord(char))
i2c.write_i2c_block_data(i2cadr, 0x40, charSet)
def lcd_setCGRAM(adr, pattern):
adr &= 0xF
lcd_cmd(0x40 | (adr << 3))
for char in pattern:
lcd_data(char)
def lcd_move2ndline():
lcd_cmd(0x80 | 0x40)
def lcd_setIcon(adr, bit):
lcd_cmd(0x39) # function set for Extended
lcd_cmd(0x40 | (adr & 0xF))
lcd_data(bit & 0x1F)
lcd_cmd(0x38) # function set for NORMAL
def lcd_resetAllIcons():
for adr in [0x0, 0x2, 0x4, 0x6, 0x7, 0x9, 0xB, 0xD, 0xF]:
lcd_setIcon(adr, 0)
def lcd_clear():
lcd_cmd(0x01) # clear display
def lcd_output(line1, line2=""):
lcd_clear()
lcd_puts(line1)
if len(line2) == 0:
return
lcd_move2ndline()
lcd_puts(line2)
def hostname_ipadr():
lcd_output(hostname(), ip_adr())
def cpuUsage_clock():
global LAST_CPU
cpu = '{:>16}'.format(f'{LAST_CPU:.1f}%')
now = datetime.datetime.now()
now_str = now.strftime('/%d %H:%M:%S')
if now.month >= 10:
now_str = chr(now.month - 10) + now_str
else:
now_str = str(now.month) + now_str
lcd_output(cpu, '{:>16}'.format(now_str))
GPIO.setmode(GPIO.BOARD)
resetPin = 7
i2cadr = 0x3e
contrast = 40
lcd_init()
m10_11_12 = [[0b10010, 0b10101, 0b10101, 0b10101, 0b10101, 0b10010, 0b00000, 0b00000], \
[0b10010, 0b10010, 0b10010, 0b10010, 0b10010, 0b10010, 0b00000, 0b00000], \
[0b10010, 0b10101, 0b10001, 0b10010, 0b10100, 0b10111, 0b00000, 0b00000]]
for adr in range(3):
lcd_setCGRAM(adr, m10_11_12[adr])
def setCPUusageIcon():
global LAST_CPU
cpu = LAST_CPU
if cpu < 25.0:
lcd_setIcon(0x0D, 0x02)
elif cpu < 50.0:
lcd_setIcon(0x0D, 0x12)
elif cpu < 75.0:
lcd_setIcon(0x0D, 0x1A)
elif cpu < 95.0:
lcd_setIcon(0x0D, 0x1E)
else:
for _ in range(2):
lcd_setIcon(0x0D, 0x1E)
delay(300)
lcd_setIcon(0x0D, 0)
delay(300)
lcd_setIcon(0x0D, 0x1E)
t = threading.Thread(target = timer_scheduler)
t.start()
lcd_output("Please wait soon", " for network act")
time.sleep(1)
while len(ip_adr()) == 0:
setCPUusageIcon()
time.sleep(1)
hostname_ipadr()
for _ in range(180):
setCPUusageIcon()
time.sleep(1)
sw_pin = 37
GPIO.setup(sw_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP)
CLOCK = True
def clock_or_hostname():
global CLOCK
if CLOCK:
cpuUsage_clock()
else:
hostname_ipadr()
def handle_button(pin):
global CLOCK, BUSY
CLOCK = not CLOCK
if not BUSY:
clock_or_hostname()
GPIO.add_event_detect(sw_pin, GPIO.FALLING, handle_button, bouncetime=1000)
while True:
clock_or_hostname()
setCPUusageIcon()
time.sleep(1)
適当な負荷を掛けるのが難しいので、すぐに100%になってしまうけど、ちゃんと表示できていそう。また、スイッチを押すと表示がちゃんと切り替わりますね。
もし、上の埋め込み動画が表示されていない場合は、こちらからどうぞ。
これを/etc/rc.local
から起動するように設定した。
6. マウントベースとスペーサを設計
Raspberry Pi ZeroとこのLCDを重ねて取り付けるためのベースとスペーサを設計した。ピンヘッダとデュポンケーブルが高さ方向の幅をとるので、HATのようにピッタとはいかずLCDが浮いた取り付けとなってしまうが仕方ない。
さっそく3DPRで出力して上の動画で使っていますが、真上から撮影しているので見えないですね。斜めからの写真を載せておきます。

このLCDにバックライトがないのが残念!!
基板にはバックライトLED用の制限抵抗やの給電方法を選択するためのジャンパランド、スルーホールまでも用意されている。
ストロベリー・リナックは同サイズの液晶にバックライト付きのLCDを販売しているので、それをマルツの基板に移し替える!?。てか、マルツもバックライト付きをラインナップすればいいのに・・・
2022.4.21訂正
マルツの基板にはバックライトLEDの制限抵抗(チップ抵抗)をハンダ付けするためのランドが用意されていますが、抵抗自体がハンダ付けされているわけではありません。
おわりに
今回はST7032iについて調べLCD出力に関して勉強するよい機会となりました。また、普段余り使っていないpythonで色々な処理の書き方なども勉強になリました。
1000円足らずでこんなに遊べるなんて、ホント便利な時代になりました。みなさんに感謝です。
この記事も何かの参考になれば幸いです。
以上