【MSX0】I2C.PAS【Turbo Pascal】

# はじめに
『MSX0 Stack』付属の IoT BASIC サンプルを『Turbo Pascal』へ移植してみます。

 - [【MSX0】IoT BASIC サンプルの移植【Turbo Pascal】(Qiita)](https://qiita.com/ht_deko/items/2362488c5c99aec4afbf)

# I2C.BAS
◆I2Cアクセス

| 項目 | 説明 |
|:---|:---|
| 概要 | DHT20 に対して直接 I<SUP>2</SUP>C アクセスを行い温度を取得します。 |
| ファイル名 | I2C.BAS |
| 対応デバイス | DHT20 (黒色)|
| コメント | Seeed Grove Kit の DHT20（黒色）温度湿度センサを Port A に接続してください。温度取得の際にCRCチェックなどは省略しています。 |

:::note warn
このサンプルを動作させるには外部センサーが必要です。
:::

**See also:**

 - [『MSX0 Stack』の操作方法 - ハードウェア (Qiita)](https://qiita.com/ht_deko/items/cdb1594e9879a5e9e660)
 - [Grove Beginner Kit for Arduino (Switch Science)](https://www.switch-science.com/products/6361)

## MSX0 Stack での挙動

まず事前にセンサーをつないでおきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/62f448b7-f814-57be-061a-9a9014eab192.png)

右中央のセンサーが温湿度センサーなのですが、 [『Grove Beginner Kit for Arduino』](https://www.seeedstudio.com/Grove-Beginner-Kit-for-Arduino-p-4549.html)の製造ロットによって、センサーの種類が異なります。具体的には 2022/04 以降製造分に DHT20 (黒色) が付属するようです。DHT11 (水色) は I<SUP>2</SUP>C 接続のセンサーではないので使えません。

| センサー | 接続方法 | Grove ポート |
|:---|:---|:---|
| ~~[D3] DHT11 (水色)~~ | ~~Digital~~ | ~~Port C (水色)~~ |
| [I<SUP>2</SUP>C] DHT20 (黒)| I<SUP>2</SUP>C  | Port A (赤) |

:::note
『Grove Beginner Kit for MSX0』には DHT20 が付属します。
:::

`I2C.BAS` をロードして、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/f153e21f-10b5-fe54-9593-197f78fc973c.png)

実行してみました。アレ？行番号 160 のトコでエラーになりますね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/144052b3-b1c9-4f51-c962-1fb17a95fcd8.png)

```
160   FORI I=0 TO C-1:IF A$(I)="38" THEN 180 ELSE NEXTI
```

`I` が一つ多いようです。

```
160   FOR I=0 TO C-1:IF A$(I)="38" THEN 180 ELSE NEXTI
```

今度は正しく表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/335c8daf-bc25-0487-f6ed-e452e65cfe4c.png)

センサーが正しく接続されていないとその旨を表示して終了します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/36b7e7f2-b39a-f629-dab9-591673a16cf5.png)

:::note info
プログラムは〔Ctrl〕+〔Stop〕(リモートコントロールパネルからは〔Ctrl〕+〔F12〕)で中断できます。
:::

**See also:**

 - [Grove Beginner Kit for Arduinoの温湿度センサーがアップデート (Seeed K.K. エンジニアブログ)](https://lab.seeed.co.jp/entry/2022/12/26/120000)

## Turbo Pascal へ移植

別途、MDL-LIB と `SYSUTILS.LIB`、`IOT.LIB` が必要です。

 - [MDL-LIB version 2.2 の使い方 (Qiita)](https://qiita.com/ht_deko/items/6f15aee0bd25b550e4df)
 - [【MSX0】IOT.LIB (Qiita)](https://qiita.com/ht_deko/items/a5c0bf6e7969093beb3c)


```pascal:I2C.PAS
program I2C;
type
  LibStr = string[80];
{$I SYSUTILS.LIB}
{$I IOT.LIB}
var
  List, Last, p: PItem;
  N, S: LibStr;
  T: Real;
  Flg: Boolean;
begin
  ClrScr;
  Flg := False;
  List := nil;
  N := 'device/i2c_a';
  IoTFind(N, List, Last);
  p := List;
  while (p <> nil) and (not Flg) do
  begin
    if p^.Data = '38' then
    begin
      Flg := True;
      N := N + '/' + p^.Data;
    end;
    p := p^.Next;
  end;
  ClearListItem(List);
  if not Flg then
  begin
    Writeln('DHT20 is not found...');
    Exit;
  end;
  while True do
  begin
    IotPutStr(N, #$71);
    S := IoTGetStr(N);
    repeat
      IotPutStr(N, #$AC#$33#$00);
      S := IoTGetStr(N);
      if KeyPressed then
        Exit;
    until (Length(S) >= 6) and (Ord(S[1]) and $18 = $18);
    T := Ord(S[4]) and $0F;
    T := T * 256 + Ord(S[5]);
    T := T * 256 + Ord(S[6]);
    T := T / 1048576.0 * 200 - 50;
    Writeln(T:5:2);
  end;
end.
```

実行してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/7ad5a604-1282-7d13-6dc2-297511370118.png)

センサーが正しく接続されていれば温度で値が変動します。

:::note info
任意のキーで中断できます。
:::

### 解説

#### 文字列 -> 配列変換
Turbo Pascal の文字列は文字型配列としてアクセスできるので、文字列 -> 配列変換は行っていません。

#### ループ (1)
GOSUB で記述されていたものをループで書き替えました。何をやっているか解りやすくなっているかもです。

#### ループ (2)
**repeat** のループは次のようになっています。

```pascal
    repeat
      IotPutStr(N, #$AC#$33#$00);
      S := IoTGetStr(N);
      if KeyPressed then
        Exit;
    until (Length(S) >= 6) and (Ord(S[1]) and $18 = $18);
```

Turbo Pascal 3.0 は式の評価が標準 Pascal と同じ完全論理評価なので、Length(S) が 0 の場合 (文字列が存在しない場合) でも S[1] にアクセスしてしまう問題を孕んでいます。

:::note info
例えば次のコードは i が 11 になった時でも `(a[i] = 0)` が評価されてしまい、実行時エラーになります。

```pascal:EvalTest.pas
program EvalTest;
{$R+}
var  
  i: Integer;
  a: array [1..10] of Byte;
begin
  i := 0;
  repeat
    i := i + 1;
  until (i > 10) or (a[i] = 0);
end.
```
:::

なので、万全を期すなら２重ループでなくてはいけません。

```pascal
    repeat
      repeat
        IotPutStr(N, #$AC#$33#$00);
        S := IoTGetStr(N);
        if KeyPressed then
          Exit;
      until Length(S) >= 6;
    until Ord(S[1]) and $18 = $18;
```

ただ、Turbo Pascal 3.0 の文字列型はサイズが決まっており s[1] は範囲外ではないため、Length(S) が 0 の時に S[1] へアクセスしても問題ありません。

#### べき乗
BASIC での温度計算は次のようになっています。

```
370   T=D(3) AND &HF
380   T=T*256+D(4)
390   T=T*256+D(5)
400   T=T/(2^20)*200-50
```

Pascal にはべき乗演算子 (`^`) がないので、展開した `1048576` という値を使っています。ただ、そのままだと整数値として扱われてしまう (オーバーフローでエラー) ので、小数点を付けて `1048576.0` にし、実数型の定数としています。

べき乗の部分は次のようにする事もできますが、速度が目に見えて落ちます。やはり展開した実数型の定数を使うのが正解だと思います。

```pascal
    T := T / Exp(Ln(2) * 20)  * 200 - 50;
```

**See also:**

 - [4.1. 代入文 (Assignment Statement) と式 (Expressions) (Qiita)](https://qiita.com/ht_deko/items/05af01a81f7c3bb5eeb4#41-%E4%BB%A3%E5%85%A5%E6%96%87-assignment-statement-%E3%81%A8%E5%BC%8F-expressions)

# おわりに
GOTO 抜き＆マルチステートメントなしのプログラムなので、オリジナルの BASIC ソースに比べると少々長くなってはいますが、その分読みやすいプログラムにはなっているかと思います。