LoginSignup
1
0

More than 1 year has passed since last update.

マルツ製キャラクタLCDモジュールをRaspberry Pi Zeroで遊ぶ

Last updated at Posted at 2022-04-20

はじめに

あちらこちらの箱に散在していたパーツ類を整理していたら、かなり前に購入した「マルツパーツ館 16文字x2行表示 キャラクタLCDモジュール」が出てきた。購入したことすら記憶に残っていなかったが、デュポンケーブルでRaspberry Pi Zeroに直接接続したところ、他に何もしなくても簡単に表示できたので、もう少し遊んでみようと思う。

つい先日、aitendo製LCDの記事をアップしたが、今回の記事とは関係なくLCDつながりも単なる偶然。

1. マルツパーツ館

取扱説明書

「○にツ」のマルツパーツ館のロゴが入った取扱説明書と一緒に出てきた。ピンヘッダをハンダ付けしたものの、そのまま放置しいつしか記憶からも消え去ったと思われる。
「マルツパーツ館」をネットで検索すると、2014年10月に現マルツにリニューアルしたというブログがヒットした。今もロゴは「○にツ」ですね。

MARUTSU    MI2CLCD-01

「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)

このadrbitで表示できるアイコンはどれ?、取説にもST7032のデータシートにも記載がない。「st7032 アイコン」で検索すると、ストロベリー・リナックスが販売している「I2C低電圧キャラクタ液晶モジュール(16x2行)」の参考資料「アプリケーションノート」がヒットし詳しく載っていた。ストロベリー・リナックスのLCDはBO1602B、マルツのLCDのBO1602Dと、ほぼ一緒なんでしょうね。下表に引用させていただきました。
icon adr/bit
例えば、バッテリー満タンのアイコンを表示したい場合は、adr=0xD、bit=0x1Eを指定します。消す場合は bit=0。

IMG_9800.png

上の写真のようにちゃんと表示できました。昔のガラケーを思いださせる懐かしいアイコンです。
バッテリー充電中をイメージしたアイコンを設定してみた。↓それっほく見えますか?
charging.gif
本来ならタイマーイベントで処理するところを、サボって単純ループで実装しています。

バッテリー充電中
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データシートから引用)
スクリーンショット 2022-04-18 21.45.34.png
ですが、これは、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)
使用例(文字コード=5に上向き矢印)
# ↑
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文字分が繰り返し表示されるだけ??

データシートをよく読むと、
スクリーンショット 2022-04-18 23.12.54.png
OPR1=0&OPR2=0は8文字のようです。前半の8文字の設定に変えたら下記写真の通り。16文字使えないのはちょっとガッカリ。OPR1=1&OPR2=0も同じ8文字なので、こっちかもしれない。(16文字だと思っていたが、別なコントローラICの仕様だったようだ)

(c) CGRAMを動的に書き換えたら表示はどうなる?

前項の16個の図形のビットパターンのうち、前半の8個分をCGRAMに定義して、その文字を表示させた状態で後半の8個分をCGRAMに再定義すると、表示済みの文字はどうなるのか実験してみた。
想定される動作としては、次のどちらか。

  1. CGRAMの再定義によって、表示済みの文字も書き変わる
  2. 表示済みの文字は変わらず、新しく書いた文字が再定義後の図形になる

もし後者なら、使い道が広がるが、おそらく前者ではないかと予想する。
確認するプログラムは以下の通り。①最初に前半の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")

やはり予想した通りの↓結果であった。しかし、瞬時に描き変わるのでこれはこれで使い道があるかも。
IMG_9815.JPG

5. 実用的なプログラムを書く

サイズ感がフィットしているので、Raspberry Pi Zero WHに常設し普段使いしようと思い、以下のプログラムを書くことにした。

  1. (IPアドレス取得後に)自身のホスト名とIPアドレスを表示
  2. バッテリーアイコンを使って、CPU使用率を表示(1秒間隔で更新)
    CPU使用率
  3. 数分経過したら、ホスト名とIPアドレスの代わりに、現在時刻とCPU使用率を数字で表示
  4. 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円足らずでこんなに遊べるなんて、ホント便利な時代になりました。みなさんに感謝です。
この記事も何かの参考になれば幸いです。

以上

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0