Skip to content

Instantly share code, notes, and snippets.

@sounisi5011
Created December 19, 2018 05:48
Show Gist options
  • Save sounisi5011/003dd8fca50e6ab0b843f7e95b6e31b2 to your computer and use it in GitHub Desktop.
Save sounisi5011/003dd8fca50e6ab0b843f7e95b6e31b2 to your computer and use it in GitHub Desktop.
MicroPythonでキャラクタLCDを駆動するクラス / MicroPython Character LCD Display control code
# character lcd
# tested:
# + TC1602E-25A
import pyb
class Lcd:
# Unicode文字とLCD用文字コードの対応テーブル
# http://www.bs21-lab.com/products/p015-KanaLCD/#charset
CHAR_TABLE = {
# ギリシア文字
0x03B1: 0xE0, # α
0x03B2: 0xE2, # β
0x03B5: 0xE3, # ε
0x03BC: 0xE4, # μ
0x03C3: 0xE5, # σ
0x03C1: 0xE6, # ρ
0x03B8: 0xF2, # θ
0x03A9: 0xF4, # Ω
0x03A3: 0xF6, # Σ
0x03C0: 0xF7, # π
# ダイアクリティカルマーク付きラテン文字
0x00E4: 0xE1, # ä
0x00F1: 0xEE, # ñ
0x00F6: 0xEF, # ö
0x00FC: 0xF5, # ü
# 漢字
0x5343: 0xFA, # 千
0X4E07: 0xFB, # 万
0X5186: 0xFC, # 円
# 記号
0x221A: 0xE8, # √
0x221E: 0xF3, # ∞
0x00F7: 0xFD, # ÷
# 濁音を書き込む
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/u3040.html#voicing
0x3099: 0xDE,
0x309B: 0xDE,
# 半濁音を書き込む
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/u3040.html#voicing
0x309A: 0xDF,
0x309C: 0xDF,
# 円記号を書き込む
# https://ja.wikipedia.org/wiki/%E5%86%86%E8%A8%98%E5%8F%B7#%E7%AC%A6%E5%8F%B7%E4%BD%8D%E7%BD%AE
0x00A5: 0x5C,
0xFFE5: 0x5C,
# セント記号を書き込む
# https://ja.wikipedia.org/wiki/%E3%82%BB%E3%83%B3%E3%83%88_(%E9%80%9A%E8%B2%A8)#%E7%AC%A6%E5%8F%B7%E4%BD%8D%E7%BD%AE
0x00A2: 0xEC,
0xFFE0: 0xEC,
# ポンド記号を書き込む
# https://ja.wikipedia.org/wiki/%C2%A3#.E3.82.B3.E3.83.B3.E3.83.94.E3.83.A5.E3.83.BC.E3.82.BF.E3.81.A7.E3.81.AE.E6.89.B1.E3.81.84
0x00A3: 0xEE,
0xFFE1: 0xEE,
# →
# https://ja.wikipedia.org/wiki/%E7%9F%A2%E5%8D%B0#%E7%9F%A2%E5%8D%B0%E3%81%AE%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html#hsv
0x2192: 0x7E,
0xFFEB: 0x7E,
# ←
# https://ja.wikipedia.org/wiki/%E7%9F%A2%E5%8D%B0#%E7%9F%A2%E5%8D%B0%E3%81%AE%E6%96%87%E5%AD%97%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html#hsv
0x2190: 0x7F,
0xFFE9: 0x7F,
# ■
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html#hsv
0x25A0: 0xFF,
0x2588: 0xFF,
0xFFED: 0xFF,
# ⁻¹
0x207B: {
0x00B9: 0xE9,
},
# x̄
0x0078: {
0x0304: 0xF8,
},
}
# 全角カナと半角カナの対応テーブル
# http://www.unicode.org/charts/PDF/U30A0.pdf
# https://www.unicode.org/Public/11.0.0/ucd/DerivedNormalizationProps.txt
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/u30a0.html
KATAKANA_TABLE = {
0x30A1: 'ァ',
0x30A2: 'ア',
0x30A3: 'ィ',
0x30A4: 'イ',
0x30A5: 'ゥ',
0x30A6: 'ウ',
0x30A7: 'ェ',
0x30A8: 'エ',
0x30A9: 'ォ',
0x30AA: 'オ',
0x30AB: 'カ',
0x30AC: 'ガ',
0x30AD: 'キ',
0x30AE: 'ギ',
0x30AF: 'ク',
0x30B0: 'グ',
0x30B1: 'ケ',
0x30B2: 'ゲ',
0x30B3: 'コ',
0x30B4: 'ゴ',
0x30B5: 'サ',
0x30B6: 'ザ',
0x30B7: 'シ',
0x30B8: 'ジ',
0x30B9: 'ス',
0x30BA: 'ズ',
0x30BB: 'セ',
0x30BC: 'ゼ',
0x30BD: 'ソ',
0x30BE: 'ゾ',
0x30BF: 'タ',
0x30C0: 'ダ',
0x30C1: 'チ',
0x30C2: 'ヂ',
0x30C3: 'ッ',
0x30C4: 'ツ',
0x30C5: 'ヅ',
0x30C6: 'テ',
0x30C7: 'デ',
0x30C8: 'ト',
0x30C9: 'ド',
0x30CA: 'ナ',
0x30CB: 'ニ',
0x30CC: 'ヌ',
0x30CD: 'ネ',
0x30CE: 'ノ',
0x30CF: 'ハ',
0x30D0: 'バ',
0x30D1: 'パ',
0x30D2: 'ヒ',
0x30D3: 'ビ',
0x30D4: 'ピ',
0x30D5: 'フ',
0x30D6: 'ブ',
0x30D7: 'プ',
0x30D8: 'ヘ',
0x30D9: 'ベ',
0x30DA: 'ペ',
0x30DB: 'ホ',
0x30DC: 'ボ',
0x30DD: 'ポ',
0x30DE: 'マ',
0x30DF: 'ミ',
0x30E0: 'ム',
0x30E1: 'メ',
0x30E2: 'モ',
0x30E3: 'ャ',
0x30E4: 'ヤ',
0x30E5: 'ュ',
0x30E6: 'ユ',
0x30E7: 'ョ',
0x30E8: 'ヨ',
0x30E9: 'ラ',
0x30EA: 'リ',
0x30EB: 'ル',
0x30EC: 'レ',
0x30ED: 'ロ',
0x30EF: 'ワ',
0x30F2: 'ヲ',
0x30F3: 'ン',
0x30F4: 'ヴ',
0x30F7: 'ヷ',
0x30FA: 'ヺ',
0x30FB: '・',
0x30FC: 'ー',
}
def __init__(self, *, rs='X12', rw='X10', e='X11', db4='Y8', db5='Y7', db6='Y5', db7='Y6', debug=False):
"""
LCDを初期化する。
:param rs: RS(レジスタ選択信号)に対応するピン番号を指定する
:param rw: R/W(リード/ライト選択信号)に対応するピン番号を指定する
:param e: E(リード/ライト・イネーブル信号)に対応するピン番号を指定する
:param db4: DB4(データ(ビット4))に対応するピン番号を指定する
:param db5: DB5(データ(ビット5))に対応するピン番号を指定する
:param db6: DB6(データ(ビット6))に対応するピン番号を指定する
:param db7: DB7(データ(ビット7))に対応するピン番号を指定する
:param debug: Trueを指定すると、内部データをコンソールに出力する
"""
self.pins = {
'RS': rs,
'RW': rw,
'E': e,
'DB4': db4,
'DB5': db5,
'DB6': db6,
'DB7': db7,
}
self.debug = debug
pyb.delay(45) # > 40ms
self.__write_data(0b0011, is_half=True)
pyb.delay(5) # > 4.1ms
self.__write_data(0b0011, is_half=True)
pyb.udelay(200) # > 100μs
self.__write_data(0b0011, is_half=True)
self.__write_data(0b0010, is_half=True)
self.__write_data(0b00101000)
self.display_control(False, False, False)
self.clear()
self.entry_mode(True, False)
self.display_control(True, True, True)
def clear(self):
"""
表示内容を全て消し、カーソルを初期位置に戻す。
"""
self.__write_data(0b00000001)
pyb.delay(2) # >1.52ms
def reset_pos(self):
"""
カーソル位置と表示内容のスクロール量を初期状態に戻す
"""
self.__write_data(0b00000010)
def entry_mode(self, cursor_move_right=True, content_move=False):
"""
データの読み込み/書き込みを行ったあとに、
カーソル、または表示内容を、左右どちらに移動するのか決定する。
:param cursor_move_right: カーソルを右に移動する場合はTrue、左に移動する場合はFalseを指定
:param content_move: カーソルを動かさずに、表示内容を動かす場合はTrue。表示内容は、カーソルとは逆に動く。
"""
data = 0b00000100
if cursor_move_right:
data |= 0b010
if content_move:
data |= 0b001
self.__write_data(data)
def display_control(self, display=True, cursor=True, blink=True):
"""
表示に関する設定を行う。
:param display: ディスプレイのON/OFF。ONの場合はTrue、OFFの場合はFalse
:param cursor: カーソルを表示する場合はTrue、カーソルを表示しない場合はFalseを指定
:param blink: カーソルの位置で点滅表示させる場合はTrue、点滅表示させない場合はFalse
"""
data = 0b00001000
if display:
data |= 0b100
if cursor:
data |= 0b010
if blink:
data |= 0b001
self.__write_data(data)
def cursor_move_or_scroll(self, content_move=False, move_right=True):
"""
カーソルの移動、または、表示内容のスクロールを行う。
:param content_move: 表示内容をスクロールする場合はTrue、カーソルを動かす場合はFalse
:param move_right: カーソル、または表示内容を右に移動する場合はTrue、左に移動する場合はFalseを指定
"""
data = 0b00010000
if content_move:
data |= 0b1000
if move_right:
data |= 0b0100
self.__write_data(data)
def set_cursor_pos(self, row, col):
"""
指定された位置にカーソルを移動する。
:param row: 移動先の行数。0が一行目、1が二行目。Noneを指定した場合は、現在の位置の値を流用する
:param col: 移動先の列数。左端を0とした、最大39までの数値。Noneを指定した場合は、現在の位置の値を流用する
"""
ddram_addr = 0
if row is None or col is None:
# rowとcolのどちらかがNoneの場合、現在のアドレスを取得する
bf, ddram_addr = self.__read_bf_addr()
if row is not None:
ddram_addr = (row % 2 * 0x40) | ((ddram_addr & 0x3F) % 0x28)
if col is not None:
ddram_addr = (ddram_addr & 0x40) | (col % 0x28)
# DDRAMのアドレスを設定する
self.__set_ddram_addr(ddram_addr)
def get_cursor_pos(self):
"""
現在のカーソルの位置を取得し、返す。
:return: カーソルの位置を示すタプル。0番目の要素が行数、1番目の要素が列数
"""
# 現在のアドレスを取得する
bf, ddram_addr = self.__read_bf_addr()
return (((ddram_addr & 0x40) >> 6), ((ddram_addr & 0x3F) % 0x28))
def print(self, *chars):
"""
文字列を書き込む。
:param chars: 書き込む文字。文字列、または文字コードを示す数値
"""
if len(chars) == 1:
# 引数charsの要素数が1の場合、文字の表示を行う
char = chars[0]
if type(char) is str:
# 文字列の場合、全体を表示し終えるまでループする
start_index = 0
while start_index < len(char):
start_index += self.__write_char(char[start_index:])
else:
self.__write_char(char)
else:
# 引数charsの要素数が2以上または0の場合、各要素を処理する
# 連続する文字列は一旦結合する
str_buffer = []
for char in chars:
if type(char) is str:
str_buffer.append(char)
else:
if len(str_buffer) > 0:
self.print(''.join(str_buffer))
str_buffer.clear()
self.print(char)
if len(str_buffer) > 0:
self.print(''.join(str_buffer))
def get_char(self, dont_move_cursor=True):
"""
カーソルが置かれた位置に表示されている文字を取得する。
:param dont_move_cursor: Trueを指定した場合、カーソルの位置を変更しない
"""
if dont_move_cursor:
# 現在のアドレスを取得する
bf, ddram_addr = self.__read_bf_addr()
char_code = self.__read_data(char_mode=True)
if dont_move_cursor:
# DDRAMのアドレスを設定する
self.__set_ddram_addr(ddram_addr)
return char_code
# http://jsdiy.web.fc2.com/lcdclock/
def set_custom_char(self, code, charmap):
"""
ユーザー定義文字を設定する。
:param code: ユーザー定義文字の文字コード。0x00〜0x07の範囲で指定する
:param charmap: ユーザー定義文字の文字情報
"""
# 現在のアドレスを取得する
bf, ddram_addr = self.__read_bf_addr()
for line in range(8):
# 行のデータを取得
char_line_data = charmap[line] if line < len(charmap) else 0
# Set CGRAM Address コマンドを送信して、CGRAMのアドレスを設定
self.__set_cgram_addr((code & 0b111) << 3 | (line & 0b111))
# Write Data to RAM コマンドを送信して、CGRAMにデータを書き込む
self.__write_data(char_line_data & 0b11111, char_mode=True)
# Set DDRAM Address コマンドを送信して、DDRAMモードに戻す
self.__set_ddram_addr(ddram_addr)
def get_custom_char(self, code):
"""
ユーザー定義文字を取得する。
:param code: ユーザー定義文字の文字コード。0x00〜0x07の範囲で指定する
:return: ユーザー定義文字の文字情報
"""
# 現在のアドレスを取得する
bf, ddram_addr = self.__read_bf_addr()
charmap = []
for line in range(8):
# Set CGRAM Address コマンドを送信して、CGRAMのアドレスを設定
self.__set_cgram_addr((code & 0b111) << 3 | (line & 0b111))
# Read Data from RAM コマンドを送信して、CGRAMからデータを読み込む
charmap.insert(line, self.__read_data(char_mode=True) & 0b11111)
# Set DDRAM Address コマンドを送信して、DDRAMモードに戻す
self.__set_ddram_addr(ddram_addr)
return tuple(charmap)
def __write_char(self, char):
"""
文字を書き込む。
:param char: 書き込む文字。文字列、または文字コードを示す数値
:return: 引数charが文字列の場合、書き込みに使用した文字数を返す。通常は1だが、2が返される場合もある。
"""
start_index = 0
if type(char) is int:
# 数値の場合、文字コードとしてデータを送信する
self.__write_data(char, char_mode=True)
elif type(char) is str and 1 <= len(char):
# 文字列の場合、文字コードに変換する
char_str = char
# 文字を、対応するUnicodeコードポイントへ変換する
char_codepoint = ord(char_str[start_index:(start_index + 1)])
start_index += 1
if 0x20 <= char_codepoint <= 0x7E and char_codepoint not in [0x5C, 0x7E]:
# ASCII文字を書き込む
# Note: バックスラッシュに対応するコードには円記号が、チルダに対応するコードには右矢印が割り当てられているため、書き込めない
# https://ja.wikipedia.org/wiki/ASCII#ASCII%E5%8D%B0%E5%AD%97%E5%8F%AF%E8%83%BD%E6%96%87%E5%AD%97
self.__write_char(char_codepoint)
elif 0xFF01 <= char_codepoint <= 0xFF5E:
# 全角ASCII文字を書き込む
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html#fav
self.__write_char(char_codepoint - 0xFEE0)
elif 0xFF61 <= char_codepoint <= 0xFF9F:
# 半角カナを書き込む
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/uff00.html#hcp
self.__write_char(char_codepoint - 0xFEC0)
elif char_codepoint in self.KATAKANA_TABLE:
# 全角カナを書き込む
for katakana_char in self.KATAKANA_TABLE[char_codepoint]:
self.__write_char(katakana_char)
elif char_codepoint in self.CHAR_TABLE:
# その他の文字を書き込む
char_code_1 = self.CHAR_TABLE[char_codepoint]
if type(char_code_1) is int:
self.__write_char(char_code_1)
elif (start_index + 1) <= len(char_str) and type(char_code_1) is dict:
char_table_2 = char_code_1
char_codepoint_2 = ord(char_str[start_index:(start_index + 1)])
if char_codepoint_2 in char_table_2:
char_code_2 = char_table_2[char_codepoint_2]
if type(char_code_2) is int:
self.__write_char(char_code_2)
start_index += 1
return start_index
def __set_cgram_addr(self, addr):
"""
CGRAMのアドレスを設定する。
:param addr: アドレスを示すビットデータの数値
"""
self.__write_data(0b01000000 | (addr & 0b111111))
def __set_ddram_addr(self, addr):
"""
DDRAMのアドレスを設定する。
:param addr: アドレスを示すビットデータの数値
"""
self.__write_data(0b10000000 | (addr & 0b1111111))
def __read_bf_addr(self):
"""
ビジー・フラグとアドレス・カウンタの値を読み取る。
:return: それぞれの値を格納したタプル。0番目の要素がビジー・フラグ、1番目の要素がアドレス・カウンタの値
"""
for i in range(100):
bf_addr_data = self.__read_data()
if not (bf_addr_data & 0b10000000):
return (0, bf_addr_data & 0b1111111)
pyb.udelay(10)
return (1, 0)
def __write_data(self, data, *, char_mode=False, is_half=False):
"""
データの書き込み処理を行う。
:param data: 書き込むデータ。DB7〜DB0に相当するビットデータの数値
:param char_mode: 文字の書き込みに関する処理の場合はTrue、コマンド処理の場合はFalseを指定。この引数はRSピンの出力値に相当する
:param is_half: 初期化処理用。データの下位4ビットを、DB7〜DB4に一度だけ送信する場合はTrue
"""
if self.debug:
print(('send {{rs={} rw={} data={:04b}}}' if is_half else 'send {{rs={} rw={} data={:08b}}}').format(int(char_mode), 0, data))
pin_rs = self.__get_pin('RS')
pin_rw = self.__get_pin('RW')
pin_e = self.__get_pin('E')
pin_db4 = self.__get_pin('DB4')
pin_db5 = self.__get_pin('DB5')
pin_db6 = self.__get_pin('DB6')
pin_db7 = self.__get_pin('DB7')
pin_rs.value(char_mode)
pin_rw.value(0)
pin_e.value(0)
if not is_half:
pin_db7.value(data & 0b10000000)
pin_db6.value(data & 0b01000000)
pin_db5.value(data & 0b00100000)
pin_db4.value(data & 0b00010000)
self.__data_send(pin_e)
pin_db7.value(data & 0b00001000)
pin_db6.value(data & 0b00000100)
pin_db5.value(data & 0b00000010)
pin_db4.value(data & 0b00000001)
self.__data_send(pin_e)
def __read_data(self, *, char_mode=False):
"""
データの読み込み処理を行う。
:param char_mode: 文字の読み込みに関する処理の場合はTrue、コマンド処理の場合はFalseを指定。この引数はRSピンの出力値に相当する
:return: 読み込んだデータ。DB7〜DB0に相当するビットデータの数値
"""
data = 0
pin_rs = self.__get_pin('RS')
pin_rw = self.__get_pin('RW')
pin_e = self.__get_pin('E')
pin_db4 = self.__get_pin('DB4', True)
pin_db5 = self.__get_pin('DB5', True)
pin_db6 = self.__get_pin('DB6', True)
pin_db7 = self.__get_pin('DB7', True)
pin_rs.value(char_mode)
pin_rw.value(1)
pin_e.value(0)
self.__data_read_start(pin_e)
if pin_db7.value():
data |= 0b10000000
if pin_db6.value():
data |= 0b01000000
if pin_db5.value():
data |= 0b00100000
if pin_db4.value():
data |= 0b00010000
self.__data_read_end(pin_e)
self.__data_read_start(pin_e)
if pin_db7.value():
data |= 0b00001000
if pin_db6.value():
data |= 0b00000100
if pin_db5.value():
data |= 0b00000010
if pin_db4.value():
data |= 0b00000001
self.__data_read_end(pin_e)
if self.debug:
print('get {{rs={} rw={}}} -> data=0b{:08b}'.format(int(char_mode), 1, data))
return data
def __data_send(self, pin_e):
"""
イネーブルピンを操作し、制御信号を送る
:param pin_e: イネーブルピンに対応付けられたpyb.Pinオブジェクト
"""
pyb.udelay(1) # > 60ns
pin_e.value(1)
pyb.udelay(1) # > 450ns
pin_e.value(0)
pyb.udelay(50) # > 38μs
def __data_read_start(self, pin_e):
"""
イネーブルピンを操作し、制御信号の送信を開始する
:param pin_e: イネーブルピンに対応付けられたpyb.Pinオブジェクト
"""
pyb.udelay(1) # > 60ns
pin_e.value(1)
pyb.udelay(1) # > 360ns
def __data_read_end(self, pin_e):
"""
イネーブルピンを操作し、制御信号の送信を完了する
:param pin_e: イネーブルピンに対応付けられたpyb.Pinオブジェクト
"""
pyb.udelay(1) # > 450ns
pin_e.value(0)
pyb.udelay(50) # > 38μs
def __get_pin(self, pin, is_in=False):
"""
ピンを制御するためのpyb.Pinオブジェクトを取得する
:param pin: ピンに対応する名前
:param is_in: Trueを指定した場合、ピンを入力用として設定する
:return: ピンに対応付けられたpyb.Pinオブジェクト
"""
if is_in:
return pyb.Pin(self.pins[pin], pyb.Pin.IN, pyb.Pin.PULL_DOWN) # TODO: PULL_DOWNへの変更を検討する
else:
return pyb.Pin(self.pins[pin], pyb.Pin.OUT_PP)
# ----- #
lcd = Lcd()
print(lcd.get_cursor_pos())
lcd.print('ヴェルジュルザ')
print(lcd.get_cursor_pos())
pyb.delay(1000)
lcd.set_cursor_pos(3, None)
print(lcd.get_cursor_pos())
pyb.delay(1000)
lcd.print(*range(8))
print(lcd.get_cursor_pos())
pyb.delay(1000)
for i in range(4):
lcd.cursor_move_or_scroll(True, False)
pyb.delay(100)
pyb.delay(500)
default_00_cc = lcd.get_custom_char(0x00)
lcd.set_custom_char(0x00, [
0b00000,
0b01010,
0b01010,
0b00000,
0b00000,
0b01010,
0b00100,
0b00000,
])
pyb.delay(1000)
for i in range(4):
lcd.cursor_move_or_scroll(True, True)
print(lcd.get_cursor_pos())
lcd.set_cursor_pos(None, 0)
print(lcd.get_cursor_pos())
pyb.delay(3000)
lcd.set_custom_char(0x00, default_00_cc)
print(lcd.get_cursor_pos())
while True:
for i in range(10):
lcd.set_cursor_pos(0, i)
char_hex_str = '0x{:02X}'.format(lcd.get_char())
lcd.set_cursor_pos(1, 1)
lcd.print(char_hex_str)
lcd.set_cursor_pos(0, i)
pyb.delay(1000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment