【MSX0】LCD.LIB

# はじめに
MSX0 (MSX-DOS) で使える Liquid Crystal Display (LCD) ライブラリを作ってみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/2a9b67d8-4506-4154-08d3-34cd34b4c06b.png)


# ライブラリ

ライブラリは Arduino の Liquid Crystal ライブラリ互換となっています。

 - [LiquidCrystal (Github)](https://github.com/arduino-libraries/LiquidCrystal/tree/master)
 - [LiquidCrystal (arduino.cc)](https://www.arduino.cc/reference/en/libraries/liquidcrystal/)
 - [Liquid Crystal Displays (LCD) with Arduino (Arduino Documentation)](https://docs.arduino.cc/learn/electronics/lcd-displays)

## LCD.LIB

LCD ライブラリです。

 - I<sup>2</sup>C タイプの LCD に対応しています
 - LCD モジュールを PortA に接続して使います
 - 一般的な 1602 LCD の他に 2004 LCD も使えます
 - 別途、[IOT.LIB](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c) が必要です

| ファイル名 | 依存ライブラリ |
|:---|:---|
| LCD.LIB | [SYSUTILS.LIB](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c#sysutilslib), [IOT.LIB](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c#iotlib)|


```pascal:LCD.LIB
{
  LCD.LIB
}

type
  LCDStr  = string[40];
  TChrDef = array [0..7] of byte;
  TLCDRec = record
              Node: LibStr;
              Cols: byte;
              Rows: byte; 
              CharSize: byte; 
              Backlight: byte;
              Display: byte;
              DispFunc: byte;
              EntryMode: byte;
            end;

var
  _lcd: TLCDRec;

procedure LCD_Send(data, m: byte);
  procedure WriteData(data: byte);
  const
    En = 4;
  var
    s: string[2];
  begin
    s[0] := Chr(2); { SetLength(s, 2) }
    s[1] := Chr(data or En);
    s[2] := Chr(data);
    IoTPutStr(_lcd.Node, s);
  end;
begin
  WriteData(data and $F0 or _lcd.Backlight or m);
  WriteData(data shl 4   or _lcd.Backlight or m);
end;

procedure LCD_Command(data: byte);
begin
  LCD_Send(data, 0);
end;

procedure LCD_WriteData(data: byte);
begin
  LCD_Send(data, 1);
end;

function LCD_ColsInRange(col: byte): byte;
begin
  if col < 0 then 
    col := 0;
  if col > Pred(_lcd.Cols) then 
    col := Pred(_lcd.Cols);
  LCD_ColsInRange := col;
end;

function LCD_RowsInRange(row: byte): byte;
begin
  if row < 0 then 
    row := 0;
  if row > Pred(_lcd.Rows) then 
    row := Pred(_lcd.Rows);
  LCD_RowsInRange := row;
end;

procedure LCD_Begin(Node: LibStr; Cols, Rows: byte; Use5x10: boolean);
const
  FS = $20;
  CS: array [boolean] of byte = ($00, $04);
begin
  _lcd.Node      := Node;
  _lcd.CharSize  := CS[Use5x10];
  _lcd.Cols      := Pred(40);
  _lcd.Cols      := Succ(LCD_ColsInRange(Pred(Cols)));
  _lcd.Rows      := Pred(4);
  _lcd.Rows      := Succ(LCD_RowsInRange(Pred(Rows)));
  _lcd.BackLight := $08; { ON }
  _lcd.Display   := $0C; { ON }
  _lcd.EntryMode := $06; { Inclement }
  _lcd.DispFunc  := $00;
  if _lcd.Rows > 1 then
    _lcd.DispFunc := _lcd.DispFunc or $08; { 2 Line }
  if (_lcd.CharSize <> 0) and (_lcd.Rows = 1) then
    _lcd.DispFunc := _lcd.DispFunc or $04; { 5x10 }
  LCD_Command($33);                 { Function Set: 8bit }
  LCD_Command($32);                 { Function Set: 8bit }
  LCD_Command(FS or _lcd.DispFunc); { Function Set: 4bit }
  LCD_Command(_lcd.Display);
  LCD_Command($01);
  LCD_Command(_lcd.EntryMode);
end;

procedure LCD_Clear;
begin
  LCD_Command($01);
end;

procedure LCD_Home;
begin
  LCD_Command($02);
end;

procedure LCD_SetCursor(col, row: byte);
const
  Offset: array [0..3] of byte = ($00, $40, $14, $54);
begin
  col := LCD_ColsInRange(col);
  row := LCD_RowsInRange(row);
  LCD_Command($80 or (Offset[row] + col));
end;

procedure LCD_Write(s: LCDStr);
var
  i: integer;
begin
  for i:=1 to Length(s) do
    LCD_WriteData(Ord(s[i]));
end;

procedure LCD_Cursor;
begin
  _lcd.Display := _lcd.Display or $02;
  LCD_Command(_lcd.Display)
end;

procedure LCD_NoCursor;
begin
  _lcd.Display := _lcd.Display and not $02;
  LCD_Command(_lcd.Display);
end;

procedure LCD_Blink;
begin
  _lcd.Display := _lcd.Display or $01;
  LCD_Command(_lcd.Display);
end;

procedure LCD_NoBlink;
begin
  _lcd.Display := _lcd.Display and not $01;
  LCD_Command(_lcd.Display);
end;

procedure LCD_Display;
begin
  _lcd.Display := _lcd.Display or $04;
  LCD_Command(_lcd.Display);
end;

procedure LCD_NoDisplay;
begin
  _lcd.Display := _lcd.Display and not $04;
  LCD_Command(_lcd.Display);
end;

procedure LCD_ScrollDisplayLeft;
begin
  LCD_Command($18);
end;

procedure LCD_ScrollDisplayRight;
begin
  LCD_Command($1C);
end;

procedure LCD_Autoscroll;
begin
  _lcd.EntryMode := _lcd.EntryMode or $01;
  LCD_Command(_lcd.EntryMode);
end;

procedure LCD_NoAutoscroll;
begin
  _lcd.EntryMode := _lcd.EntryMode and not $01;
  LCD_Command(_lcd.EntryMode);
end;

procedure LCD_LeftToRight;
begin
  _lcd.EntryMode := _lcd.EntryMode and not $02;
  LCD_Command(_lcd.EntryMode);
end;

procedure LCD_RightToLeft;
begin
  _lcd.EntryMode := _lcd.EntryMode or $02;
  LCD_Command(_lcd.EntryMode);
end;

procedure LCD_CreateChar(loc: byte; data: TChrDef);
var
  i: integer;
begin
  loc := loc and $07;
  LCD_Command($40 or (loc shl 3));
  for i:=0 to 7 do
    LCD_WriteData(data[i]);
end;
```


### ルーチン

| 名前 | 定義 | 説明 | 
|:---|:---|:---|
|LCD_Begin | **procedure** LCD_Begin(Node: LibStr; Cols, Rows: byte; Use5x10: boolean); | LCD の使用を開始します |
|LCD_Clear | **procedure** LCD_Clear; | LCD の 画面をクリアし、カーソルをホームポジションに移動します |
|LCD_Home | **procedure** LCD_Home; | カーソルをホームポジション (左上) へ移動します |
|LCD_SetCursor | **procedure** LCD_SetCursor(col, row: byte); | カーソルの位置を指定します (0 オリジン) |
|LCD_Write | **procedure** LCD_Write(s: LCDStr); | カーソル位置に文字列を表示します |
|LCD_Cursor | **procedure** LCD_Cursor; | カーソルを表示します |
|LCD_NoCursor | **procedure** LCD_NoCursor; | カーソルを非表示にします |
|LCD_Blink | **procedure** LCD_Blink; | カーソルの点滅を開始します |
|LCD_NoBlink | **procedure** LCD_NoBlink; | カーソルの点滅を停止します |
|LCD_Display | **procedure** LCD_Display; | 画面を表示します |
|LCD_NoDisplay | **procedure** LCD_NoDisplay; | 画面を非表示にします |
|LCD_ScrollDisplayLeft | **procedure** LCD_ScrollDisplayLeft; | 画面を 1 文字分左へスクロールします |
|LCD_ScrollDisplayRight | **procedure** LCD_ScrollDisplayRight; | 画面を 1 文字分右へスクロールします |
|LCD_Autoscroll | **procedure** LCD_Autoscroll; | 自動スクロール機能を有効にします |
|LCD_NoAutoscroll | **procedure** LCD_NoAutoscroll; | 自動スクロール機能を無効にします |
|LCD_LeftToRight | **procedure** LCD_LeftToRight; | テキスト表示の向きを左から右に設定します |
|LCD_RightToLeft | **procedure** LCD_RightToLeft; | テキスト表示の向きを右から左に設定します |
|LCD_CreateChar | **procedure** LCD_CreateChar(loc: byte; data: TChrDef); | カスタム文字の字形を指定します。ASCII コード表の 0x00～0x07 の文字を書き替えられます |

:::note
Arduino の LiquidCrystal ライブラリにある `print()` は実装されていません。`print()` には数値表示、書式、基数変換等の機能があります。`Write()` はシンプルな文字列出力のみです。
:::

### 型

| 名前 | 定義 | 説明 | 
|:---|:---|:---|
| LCDStr | **type** LCDStr  = string[40]; | 文字列出力用 |
| TChrDef | **type** TChrDef = array [0..7] of byte; | カスタム文字の定義用 |
| TLCDRec  | **type** TLCDRec =<br>**record**<br>&nbsp;&nbsp;Node: LibStr;<br>&nbsp;&nbsp;Cols: byte;<br>&nbsp;&nbsp;Rows: byte;<br>&nbsp;&nbsp;CharSize: byte;<br>&nbsp;&nbsp;Backlight: byte;<br>&nbsp;&nbsp;Display: byte;<br>&nbsp;&nbsp;DispFunc: byte;<br>&nbsp;&nbsp;EntryMode: byte;<br>**end**; | ステータス保存用 |

### 変数

| 名前 | 定義 | 説明 | 
|:---|:---|:---|
| _lcd | **var** _lcd: TLCDRec; | ステータス保存用 |

### サンプル

冒頭の画像と同じものを出力するサンプルです。

```pascal:LCDTEST.pas
program LCDTEST;
type
  LibStr = string[80];
{$I SYSUTILS.LIB}
{$I IOT.LIB}
{$I LCD.LIB}
const
  Monster: TChrDef = ($00, $0E, $1F, $15, $1F, $1F, $15, $00);
  Pacman:  TChrDef = ($0E, $0F, $1E, $1C, $1E, $0F, $0E, $00);
begin
  LCD_Begin('device/i2c_a/27', 16, 2, False); { I2C Address 0x27 / 1602 Display}
  LCD_Write('Hello,MSX0world!');
  LCD_CreateChar($00, Monster);
  LCD_CreateChar($01, Pacman);
  LCD_SetCursor(1, 1);
  LCD_Write(#$00#$00' '#$00'   '#$01);
end.
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/9400883c-0a61-a0f9-a4aa-b0ba9f3edbce.png)

:::note
サンプル例では I<sup>2</sup>C アドレス が 0x27 の LCD を使っています。お手持ちの LCD の I<sup>2</sup>C アドレスが判らない場合には I<sup>2</sup>C スキャナを使って調べてください。
:::

**See also:**

 - [M0I2CSCN - 【MSX0】IOT.LIB (Qiita)](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c#-m0i2cscn)

### 参考資料
ライブラリを作るにあたって参考にしたサイトです。

 - [LiquidCrystal (Github)](https://github.com/arduino-libraries/LiquidCrystal/tree/master)
 - [Arduino LiquidCrystal I2C library (Github: @fdebrabander)](https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library)
 - [【PIC】I2CでLCDに文字を表示する (rikeden.net)](https://rikeden.net/?p=81)
 - [Raspberry Pi Python I2C LCD1602表示テスト (jh7ubc.web.fc2.com)](http://jh7ubc.web.fc2.com/Raspberry_Pi/I2C_LCD_1602.html)


# おわりに
簡単に I<sup>2</sup>C 接続 LCD が使えて便利ですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3d401672-73e0-2386-05bd-3a22439c8bd7.png)

**See also:**

 - [【Arduino】1602 LCD デジタルインベーダー (電卓インベーダー) (Qiita)](https://qiita.com/ht_deko/items/4c72124ac86192bbe010)
 - [[I2C] 1602 LCD ディスプレイ (HD44780 + PCF8574A) (ht-deko.com)](https://ht-deko.com/arduino/lcd_i2c.html)
 - [Tetris on Arduino + LCD (HACKADAY.IO)](https://hackaday.io/project/10761-tetris-on-arduino-lcd)