Delphi から USB-I2C interface module(Devantech社)とTMS Async を使ってHMC6352 デジタルコンパスへアクセスする

# きっかけ
最近、Raspberry Pi とI2Cセンサーを接続してそのデータを表示するアプリケーションを[TMSSoftware WebCore(の中の Miletus)](https://www.tmssoftware.com/site/tmswebcore.asp)を用いて作成しています。
しかしどうせなら使い慣れているPCアプリケーションとしても作成したいと考えました。
> TMSSoftware Blog(Miletus brings Raspberry Pi target in Delphi)
> https://www.tmssoftware.com/site/blog.asp?post=843
> TMSSoftware Blog(Tracking High Altitude Balloons with Delphi and TMS Miletus)
> https://www.tmssoftware.com/site/blog.asp?post=1021

# Honeywell HMC6352 (2軸、I2C) デジタルコンパスモジュールを Delphi から使いたい
数年前に購入して埃をかぶっていた Strawberry Linux の HMC6352 デジタルコンパスモジュール(2軸、I2C)を Arduino や Raspberry Piではなく Delphi から使ってみます。
Honeywell HMC6352 は、2軸磁気抵抗センサーと方位計算に必要なアナログおよびデジタルサポート回路、マイクロプロセッサ、アルゴリズムを組み合わせた完全統合型コンパスモジュールです(HMC6352データシートより)。
>Strawberry Linux の製品ページ
>https://strawberry-linux.com/catalog/items?code=12106
>HMC6352データシート
>https://strawberry-linux.com/pub/HMC6352A.pdf

## 1. Windows10 PC からI2Cデバイスへアクセスする
良いデバイスはないかなあとネット検索していてこちらのブログを参考にさせて頂きました。
> [DelphiでLCDに描画する](https://note.com/kumajiisan/n/nd7d8366bba16)

ArduinoやRaspberry Pi のように直接接続することはできませんので今回は[通販ショップ エレファイン](https://www.elefine.jp/SHOP/USB-I2C.html)から購入した
[Devantech社のUSB-I2C interface module](https://www.robot-electronics.co.uk/products/usb-to-i2c-interface-module.html)を使用しました。

![usb-i2c-b.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/3739b902-5329-5faa-2908-ecbf392051e6.jpeg)
このUSB-I2Cインターフェイスを用いることでシリアル通信経由で HMC6352 と簡単にI2C通信できるソフトウェアを作成することができます。

<details><summary>[仕様]</summary>

1. USB I2Cマスターに変換
パソコンのUSBに接続し、I2Cマスターとして使用することができます。 通信速度は、100kHz固定です。
2. 複数のI2Cデバイスを接続することができます。
3. バスマスターとしてのみ使用できます。SCLとSDAは、4.7kΩでプルアップされています。バス電圧は、5Vです。
5. Widows 7でも使用可能 Windows XPはもちろん、Windows 7 32ビット、64ビット、Windows 2000、Windows MEでも使用できます。
6. Linux, Mac OSにも対応
Mac OS8, OS9, OSXにも対応しております。
7. 仮想COMポートとして動作
パソコンのUSBに接続すると、仮想COMポート(VCP)として動作しますので、RS232C通信と同様になり、HID(キーボード等のHuman Interface Device)仕様のものと違って、比較的簡単にプログラムを作成することができます。
</details>

## 2. シリアル通信用コンポーネント
[TMSSoftware](https://www.tmssoftware.com/site/default.asp?s=home) の VCLコンポーネントである [TMS Async](https://www.tmssoftware.com/site/tmsasync.asp) を使いました。Single developper license で 80€です。
![TMS_Async.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/506a891a-d823-ed04-8d39-405ab59cbef8.gif)
TMS ALL-ACCESS サブスクリプションに含まれています。
<details><summary>[TMS software ホームページの機能説明]</summary>

TMS Async は、Windows でシリアル ポートへのアクセスを提供するコンポーネントを含む通信パッケージです。イベント ドリブン アーキテクチャは最高のパフォーマンスを提供し、すべてのツールをバックグラウンドで実行できます。
1.Proven concept used multiple years already in lots of industrial applications
2.Advanced class object structure.
3.Advanced but easy to use design interface.
4.Optimized even t-driven architecture.
5.Provides the highest possible performance.
6.Supports all important transfer protocols.
7.Components to build advanced comport servers.
8.Easy to use with any type of serial hardware.
9.Links directly to your EXE, no runtime.
10.Built-in support for XModem, YModem & ZModem protocols
11.Codeless terminal component with extendable emulation interface
12.Component for codeless visualisation of status
</details>

# 3. USB-I2Cインターフェイスとの接続確認とコマンド
私の環境では FTDI FT232R USBチップ用のドライバーは自動的に読み込まれました。
* ターミナルソフト
[RS232Cテストツール](https://www.vector.co.jp/soft/winnt/hardware/se411276.html)を用いました。

* COMポート設定
1. ボーレート: 19200
2. データビット: 8
3. パリティ: None
4. ストップビット: 2

* **コマンド**
**I2C_SGL: 0x53** -> Philips PCF8574 I/O チップなどnon-registered デバイス用の 1 バイトの読み取り/書き込み。**今回の HMC6352 が該当**
**I2C_MUL: 0x54** -> アドレスを設定せずに複数のバイトを読み取り (EEPROM、Honeywell 圧力センサーなど) **今回の HMC6352 が該当**
I2C_AD1: 0x55 -> 1 バイトのアドレス指定されたデバイスの単一バイトまたは複数バイトの読み取り/書き込み (ほとんどのデバイスがこれを使用)
I2C_AD2: 0x56 -> 2 バイトアドレスのデバイス、32kbit (4kx8) 以上の EEPROM の単一バイトまたは複数バイトの読み取り/書き込み
**I2C_USB: 0x5A** -> USB-I2C モジュールへのコマンド　0x01 を続けるとRevision Numberを返す 

* Revision の確認(ターミナルソフトから送信)
```text
送-> <5A><01>
->受 <9>
```
これで USB-I2CインターフェイスのFirmware Revision Number が帰ってきます。
今回は0x09が帰ってきましたので 9.0ということになります。

# 4. USB-I2CインターフェイスからHMC6352へアクセス
HMC6352 のデフォルトでは'A'を送ると方位データを2バイトで返してきます。
また送信後の Time delay が 6000 μsec 必要のようです
RS232Cテストツールから以下を送ります。
```text!
送-> <53><42><41>　//間隔あける　　　　<54><43><02>
->受 <01><04><13>
```
0x53(I2C_SGL)はDirect Read/Write command
0x42は HMC6352のI2Cアドレス(8ビット)
0x41は Data byte で 'A'になります。
※上記でHMC6352に1バイトのデータをWriteします。書込レジスタアドレス指定は必要なし
次に HMC6352 からデータを受信します。
0x54(I2C_MUL)はRead multiple bytes without setting new address
0x43 はI2Cアドレス7bit+Write(=1)です
0x02 は2バイトのデータを読み取ります。

一連のバイトデータの間には少しDelayがないとうまくデータが返ってきませんでした。
ここでは3バイト帰ってきています。これはUSB-I2Cインターフェイスが先頭に読取り成功のフラグとして<01>を付加するからです。
つまりは残りの0x04, 0x13 が方位データということになります。
方位の算出方法は、データシートよりMSBの0x04 を左に8ビットシフトしてLSBの0x13を足すと10進数で 1043 となり、これを10で割った数値 104.3度が方位ということになります。

# 5. Delphi での実装とI2C通信のデバッグ
ここまでで方位データの読取りが確認できましたので実際にTMS Asyncを用いてコードを入力していきます。
ほとんどコンポーネントの機能を利用することで追加することは余りありませんでした。
しかし[ZEROPLUS社のArduino starter kit with Logic Analyzer](http://www.zeroplus.com.tw/logic-analyzer_en/products.php?pdn=1&product_id=711) 附属のロジックアナライザーを使用してUSB-I2CインターフェイスとHMC6352の間のI2C通信を解析することでスムースにデバッグすることができました。
![LA Edu 45 side.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/9f539e53-00ca-8583-864e-d2a6369bf33b.jpeg)

ZEROPLUS社のロジックアナライザー(上)
・チャンネル数：8
・インターフェース：USB1.1(2.0対応)
・サンプリング周波数：
　内部(タイミング)：100MHz
　外部(ステート)：75MHz

![HMC6352_I2C_Response.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/3e2e12c2-21c1-6c1e-b7cc-279fc2fb5afa.png)
ZP-Studio(初心者向け解析ソフト) 
> [ＡＲＤＵＩＮＯスターターキットｗｉｔｈロジックアナライザ(秋月電子通商)](https://akizukidenshi.com/catalog/g/gM-13368/)
ロジックアナライザー自体は初心者向けに簡素化、機能制限がされているようですが素人の電子工作用途には必要十分なものでした。
![PXL_20230101_083357804.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/15741272-d7a4-6024-40c3-a69371c050e2.jpeg)
デバッグ風景(緑丸印が USB-I2Cインターフェイス、赤丸印がHMC6352デジタルコンパス、その間にZEROPLUS社のロジックアナライザーを接続)
HMC6352は 5Vでも動いたのですがデータシート上はMax5.2vだったので双方向レベルコンバーター(FXMA2102)を挟んでいます。

1. TVaComm コンポーネントをフォームへDrop
![TMSAsyncコンポーネント.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/4542bd64-7d81-a3fb-94d3-b1a6f15cbb07.png)

2. COMポート設定: TVaConfigDialogコンポーネントを使用することでいつでも設定変更可能です。
```pascal
procedure TForm1.ComPortSettings;
begin
  // COMポート設定
  VaComm1.Baudrate := br19200;
  VaComm1.Databits := db8;
  VaComm1.Stopbits := sb2;
  VaComm1.Parity := paNone;
end;
```
　　　
3. HMC6352へコマンドを送信

コマンドの設定
```pascal
var
  Form1: TForm1;

  CMD_A_SET: array [0 .. 2] of BYTE = (
    $53, // USB-I2Cコマンド(I2C_SGL): 1バイト書き込み
    $42, // HMC6352の I2C address: 0x42(7ビット+ 0(Write))
    $41 // 'A'
  );
  CMD_GET_Direction: array [0 .. 2] of BYTE = (
    $54, // USB-I2Cコマンド(I2C-MUL) 複数バイトを読み取る
    $43, // i2cアドレス7bit+1(Read)
    $02 // HMC6352から2バイト読み取る
  );
```

```pascal
///
/// HMC6352 へのコマンド
/// i2c address = 0x42(8bit)
procedure TForm1.btnSendClick(Sender: TObject);
begin
  SendCommand;
  Sleep(100);
end;

procedure TForm1.SendCommand;
var
  OK: Integer;
begin
  OK := 0;
  VaComm1.PurgeReadWrite;

  if not VaComm1.Active then
    Memo1.Lines.Add('Can not open Com Port!!');
  // ’A’コマンド(Get Data. Compensate and Calculate New Heading)
  VaComm1.WriteBuf(CMD_A_SET, 3);

  if not OK = length(CMD_A_SET) then
    Memo1.Lines.Add('Writing Error!!');

  // Directionデータの読み込み
  OK := VaComm1.WriteBuf(CMD_GET_Direction, 3);
  if not OK = length(CMD_GET_Direction) then
    Memo1.Lines.Add('Writing Error!!');
end;
```   
Sleep(100)と調整するとこちらの環境ではロジアナ上で良い結果が得られました。
　　　　
4. WriteBuf()を呼び出すとVaCommコンポーネントのVaCommRxCharイベントが発生するので、バッファーから受信データを取り出す

```pascal
procedure TForm1.VaComm1RxChar(Sender: TObject; Count: Integer);
var
  LBuffer: array [0 .. 2] of BYTE;
  LBufCount: Integer;
  I: Integer;
  RetOK: Integer;
  MSB: Integer;
  LSB: Integer;
  Direction: Extended;
begin
  MSB := 0;
  LSB := 0;
  Direction := 0.0;
  // バッファーからの読取り
  if VaComm1.ReadBufUsed > 0 then
  begin
    ReceivingPortNumberMsg;
    LBufCount := VaComm1.ReadBuf(LBuffer, 3); // 3バイト読み取る
    if LBufCount <> 3 then
    begin
      ReadErrorMsg;
    end
    else
    begin
      for I := 0 to 2 do
      begin
        if I = 0 then
        begin
          RetOK := LBuffer[I];
          if RetOK = 1 then
            DeviceResponseOK
          else
            DeviceNoResponse;
        end
        else if I = 1 then
        begin
          MSB := Integer(LBuffer[I]);
          MSB := MSB shl 8;
        end
        else
        begin
          LSB := LBuffer[I];
        end;
        Memo1.Lines.Add('Buffer= ' + IntToHex(LBuffer[I]));
      end;
      CalculateDirection(MSB, LSB, Direction);
      ShowDirectionMsg(Direction);
    end;
  end

end;

```

　　　　　　
5. 方位の算出
```pascal
procedure TForm1.CalculateDirection(MSB: Integer; LSB: Integer;
  var Direction: Extended);
begin
  Direction := (MSB + LSB) / 10;
end;
```
　　　　　　
6. 結果の表示
![HMC6352からのデータ表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254476/8df2f58b-940d-3cc1-ca81-7d46deb6d361.png)
　　　　　
7. (おまけ)COMポート設定
TMS Async では TVaConfigDialog コンポーネントを用いるとコードを書かずに簡単に設定変更ダイアログが出せて便利です。

# まとめ
これまでは ArduinoやRaspberry Pi上でC++、Pythonを用いて細々と開発していたのですが、TMS Miletusを用いてDelphi IDE 上でそれ(Raspberry Pi)を行うことが出来るようになってきました。
それでもやはり使い慣れたWindows PC上でセンサーデータやI2Cデバイスの操作をしたいという気持ちもありました。
これからさらに容易にI2Cデバイスを扱えるようになると楽しみが広がりそうです。
        
:::note warn
コードについては趣味レベルですのであくまでご参考程度にお願いします。
:::