Delphi で Arduino につなぐ (シリアル通信)

# はじめに

これは [Delphi Advent Calendar 2017](https://qiita.com/advent-calendar/2017/delphi) 16 日目の記事です。

まさか [3 日目のやましょうさんの記事](https://i65000.blogspot.jp/2017/12/delphi-102-tokyo232c.html)とカブるとは思わなかったのでボツにしようかとも思ったのですが、折角書いたので公開する事にしました。

記事の趣旨としては Arduino とシリアル通信してみようというものです。

**See Also:**

 - [Arduino (arduino.cc)](https://www.arduino.cc/)
 - [Arduino (DEKO のアヤシいお部屋)](https://ht-deko.com/arduino/)

# Delphi 用のシリアル通信コンポーネント/ライブラリ

Delphi は標準でシリアル通信コンポーネントを持っていませんが、GetIt パッケージマネージャでインストールできるものがあります。

## Turbo Pack Async Pro (VCL)

 - [Turbo Pack Async Pro - VCL](https://github.com/TurboPack/AsyncPro)
 
ライセンスは MPL です。VCL アプリケーションで使えます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/b4517b10-2585-bfd0-4f5f-1e53686fc3ce.png)

## TMS Async (VCL)

 - [TMS Async](https://www.tmssoftware.com/site/async32.asp)

有償のコンポーネントです。GetIt にあるのはトライアル版です。VCL アプリケーションで使えます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/45183c58-cc4f-82fa-c446-53a8772b2765.png)

## Uni232C (VCL / FireMonkey)

 - [Uni232C (有限会社 シー・エス・ディー)](http://www.csd.co.jp/uni232c/)

やましょうさんトコのコンポーネント。マルチプラットフォームで動作。有償ですが、GetIt のは Windows 限定版 (WUni232C) で無償で使えます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/f915e587-9674-4201-2ea0-6385a6ab89fc.png)


## Communication Lab (VCL / FireMonkey)

 - [Communication Lab (Mitov Software)](http://www.mitov.com/products/communicationlab)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/7284caf5-3404-7f9a-0b45-5a54dfbfd48f.png)

有償のコンポーネントです。FireMonkey でも動作します。

以前は GetIt にあった気がするのですが...そして、以前は **Free for non-commercial use.** の表記があったと記憶しているのですが、今では確認できません。ライセンスが変更されたのでしょうか？

余談ですが、作者の Mitov さんは [Visuino](https://www.visuino.com/) の作者としても知られています。

## Comport Library (VCL)

 - [Comport Library](https://sourceforge.net/projects/comport/)

ライセンスはパブリックドメインとなっています。VCL アプリケーションで使えます。新しいバージョン用のパッケージは[こちら](https://ht-deko.com/delphiforum/?vasthtmlaction=viewtopic&t=1804.0)にあります、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/477bce95-db15-0fb2-5508-4c98cfd1613a.png)

以前これで [PC-E500 用の転送ソフト](https://ht-deko.com/pce500/)を作りました。

## ComPort for xxxxxx (FireMonkey)

 - [ComPort for FireMonkey (Winsoft) - FireMonkey](https://www.winsoft.sk/fcomport.htm)
 - [ComPort for macOS (Winsoft) - FireMonkey](https://www.winsoft.sk/xcomport.htm)
 - [ComPort for Android (Winsoft) - FireMonkey](https://www.winsoft.sk/acomport.htm)

これも以前は GetIt にあった気がするのですが...有償です。FireMonkey アプリケーションで使えます。

# Ararat Synapse

今回ご紹介するのは **Ararat Synapse** です。自由に使ってよいそうです (BDS style license って？)。

 - [Ararat Synapse](http://synapse.ararat.cz/doku.php/)
 - [license (Ararat Synapse)](http://www.ararat.cz/synapse/doku.php/license)
 - [synapse (GitHub: @geby)](https://github.com/geby/synapse)

Ararat Synapse は通信ライブラリで、その機能の一部にシリアル通信 (SynaSer) があります。


## インストール

Delphi は[無償の Starter Edition](https://community.embarcadero.com/blogs/entry/starter-edition-delphi-c-builder-10-2-tokyo-starter-edition-japan) を使います。

ライブラリは C:\Delphi_Lib\Synapse に置く事にします。

 [ファイル | バージョン管理リポジトリから開く]

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/7f615be2-c5c3-1a5b-8bba-aa7a5db5bc08.png)

**Subversion** を選択。[OK] ボタンを押す。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/0f6efbf2-8ed6-e5f9-5576-24d6a1f06821.png)

以下のように指定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/282583a7-7172-f220-17f5-c4c3fff042c9.png)

コピー先は Syanapse ライブラリの保存先なので適当な場所に変更してもらって構いません。

| 設定項目|内容|
|:-----------------|:-----------------|
| リポジトリの URL  |svn://svn.code.sf.net/p/synalist/code/trunk|
| コピー先          |C:\Delphi_lib\Synapse|
| 再帰的            |チェック|


[OK] を押すとチェックアウトが開始されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/98a7fb08-46ca-4183-44e6-bf4129afba7d.png)

チェックアウトが終了したら、[ツール | オプション] で ▶環境オプション ▶Delphi オプション ▶ライブラリと辿ります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/9b10a216-c606-cd5b-1e92-3a82c588f58f.png)

[ライブラリパス] の右側の […] ボタンを押します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/e32be622-6765-9deb-a8ba-b943c004ddf4.png)

Syanapse のパスを入力し、[追加] ボタンを押し、[OK] ボタンを押します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/1efb98e7-9556-3b3e-537c-85a7878ca599.png)

これで利用準備ができました。

:::note
シリアル通信だけなら次の 4 つのファイルがあれば動作します。

 - jedi.inc
 - synafpc.pas
 - synaser.pas
 - synautil.pas

※`synautil.pas` の MyMonthNames が文字化けしてコンパイルできない場合は、以下のコードを貼り付けて UTF-8 で保存してください。GitHub にある最新版はコンパイルが通りますが、MyMonthNames の文字列が失われています。

```pascal
var
  MyMonthNames: array[0..6, 1..12] of String =
    (
    ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',  //rewrited by system locales
     'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'),
    ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',  //English
     'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'),
    ('jan', 'fév', 'mar', 'avr', 'mai', 'jun', //French
     'jul', 'aoû', 'sep', 'oct', 'nov', 'déc'),
    ('jan', 'fev', 'mar', 'avr', 'mai', 'jun',  //French#2
     'jul', 'aou', 'sep', 'oct', 'nov', 'dec'),
    ('Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun',  //German
     'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'),
    ('Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',  //German#2
     'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'),
    ('Led', 'Úno', 'Bøe', 'Dub', 'Kvì', 'Èen',  //Czech
     'Èec', 'Srp', 'Záø', 'Øíj', 'Lis', 'Pro')
     );
```
:::

## シリアル通信 (受信)

uses に Synaser を追加すれば、シリアル通信ライブラリが使えるようになります。

```pascal:
unit Unit1;

interface

uses
  ..., Synaser; // <-- 追加


  ...
```

受信を試すには送信側を作る必要がありますね。これを Arduino (UNO) でやります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/06492431-a69e-c974-2675-a76da7208264.png)

スケッチはこんな感じです。

```cpp:Serial_Out.ino
void setup() {
  Serial.begin(115200);
  while(!Serial);
}

void loop() {
  Serial.println("Hello, world.");
  delay(1000);
}
```

一秒おきに "Hello,world." を送信します。通信速度は **115200 bps** です。Arduino UNO は **COM6** で認識されているようです。

...という事は、Delphi では COM6 をオープンして 115200 bps で受信すればいいわけです。

こんな感じでコンポーネントを貼り付け、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/18b8977e-9a1e-2adf-f6f2-a0d000dc3d60.png)

Timer1 は最初止めておきます。1 秒おきにデータが来るので 250ms (1/4 秒) くらいの頻度にします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/0c85572e-4d36-9e20-18a2-11812e849bac.png)

コードはこんな感じになります。TBlockSerial クラスを操作します。

 - [synaser.TBlockSerial (synapse.ararat.cz)](http://synapse.ararat.cz/doc/help/synaser.TBlockSerial.html)

```pascal:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Synaser;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    btnStart: TButton;
    btnStop: TButton;
    Edit1: TEdit;
    Label1: TLabel;
    Timer1: TTimer;
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private 宣言 }
    ser: TBlockSerial;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}


procedure TForm1.btnStartClick(Sender: TObject);
// Start ボタン押下時
begin
  if Assigned(ser) then
    Exit;
  ser := TBlockSerial.Create;
  ser.Connect(Edit1.Text);                       // COM ポートの指定を伴って接続
  ser.Config(115200, 8, 'N', SB1, False, False); // ボーレート他の設定
  Timer1.Enabled := True;
end;

procedure TForm1.btnStopClick(Sender: TObject);
// Stop ボタン押下時
begin
  Timer1.Enabled := False;
  if not Assigned(ser) then
    Exit;
  ser.Free;
  ser := nil;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
// タイマーイベント
var
  buf: string;
begin
  if not Assigned(ser) then
    Exit;
  if not ser.CanRead(0) then
    Exit;
  buf := ser.Recvstring(0);
  Memo1.Lines.Append(buf);
end;

end.
```

Arduino UNO を起動しておき、実行して [Start] ボタンを押すと Arduino UNO からのデータを受信できます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/0ee4b01d-6aa2-0950-558f-67cec612c2bb.png)




## シリアル通信 (送信)

今度は逆です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/4f466b6d-e407-c33a-01dd-6109e74ace34.png)


Arduino 側は "hello" を受信すると内蔵 LED (13) をちょっとの間光らせます。

```cpp:Serial_In.ino
void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  Serial.begin(115200);
  while(!Serial);
}

void loop() {
  String str = Serial.readStringUntil('Q'); 
  if (str == "hello") {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(2000);
  }
  digitalWrite(LED_BUILTIN, LOW);
}
```

Delphi 側はこんな感じでコンポーネントを配置して、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/8be63dd0-fd98-fe85-7706-c11099a02e88.png)

コードはこんな感じになります。

```pascal:
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Synaser;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Edit1: TEdit;
    btnStrat: TButton;
    btnStop: TButton;
    edSend: TEdit;
    btnSend: TButton;
    procedure btnStratClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure btnSendClick(Sender: TObject);
  private
    { Private 宣言 }
    ser: TBlockSerial;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnStratClick(Sender: TObject);
// Start ボタン押下時
begin
  if Assigned(ser) then
    Exit;
  ser := TBlockSerial.Create;
  ser.Connect(Edit1.Text);                       // COM ポートの指定を伴って接続
  ser.Config(115200, 8, 'N', SB1, False, False); // ボーレート他の設定
end;

procedure TForm1.btnStopClick(Sender: TObject);
// Stop ボタン押下時
begin
  if not Assigned(ser) then
    Exit;
  ser.Free;
  ser := nil;
end;

procedure TForm1.btnSendClick(Sender: TObject);
// Send ボタン押下時
begin
  if not Assigned(ser) then
    Exit;

  ser.SendString(edSend.Text);
end;

end.
```

送るだけなので簡単です。

Arduino を先に起動しておき、[Start] ボタンを押してから Edit に適当な文字を入れて [Send] ボタンを押してみてください。文字列が "hello" なら Arduino UNO の内蔵 LED がちょっとだけ点灯します。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/88897266-44c2-1da5-8f8a-3f563a631ea1.png)


## FireMonkey の場合

SynaSer (Synapse) はフレームワークに依存しないので...

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/7a17de1b-90bb-95b3-c5b4-810ac3ed96a3.png)


![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/8a5aaa7b-1a22-39f4-649d-24c1912e32fd.png)

こんな感じで FireMonkey フォームを作って、

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/066a3c57-c526-7f7f-f696-dd2c9b7f8e0f.png)

こんな感じのコードを書けば、

```pascal:
unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls,
  FMX.Edit, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, Synaser;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Label1: TLabel;
    Edit1: TEdit;
    btnStart: TButton;
    btnStop: TButton;
    Timer1: TTimer;
    procedure btnStartClick(Sender: TObject);
    procedure btnStopClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { private 宣言 }
    ser: TBlockSerial;
  public
    { public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

procedure TForm1.btnStartClick(Sender: TObject);
// Start ボタン押下時
begin
  if Assigned(ser) then
    Exit;
  ser := TBlockSerial.Create;
  ser.Connect(Edit1.Text);                       // COM ポートの指定を伴って接続
  ser.Config(115200, 8, 'N', SB1, False, False); // ボーレート他の設定
  Timer1.Enabled := True;
end;

procedure TForm1.btnStopClick(Sender: TObject);
// Stop ボタン押下時
begin
  Timer1.Enabled := False;
  if not Assigned(ser) then
    Exit;
  ser.DisposeOf;
  ser := nil;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
// タイマーイベント
var
  buf: string;
begin
  if not Assigned(ser) then
    Exit;
  if not ser.CanRead(0) then
    Exit;
  buf := ser.Recvstring(0);
  Memo1.Lines.Append(buf);
end;

end.
```

VCL と同じように FireMonkey (Windows) でも動作します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/ddac62ca-3978-1f4c-a261-45d9ff142a1a.png)

# おわりに

シリアル通信はチョー簡単でしたね。

なお、冒頭でお断りしたように Syanapse は**通信ライブラリ**でして、例えば **blcksock** を uses に加えると TBlockSocket クラスを用いて UDP 通信ができます。まぁ、Delphi には最初から **Indy ライブラリ**があるので出番は少ないかもしれませんが。

なお、先日 Delphi 10.2.2 がリリースされました。Starter Edition (無償版) も更新できますヨ！

 - [RAD Studio / Delphi / C++Builder 10.2.2 Tokyo へのアップデート (ht_deko@Qiita)](https://qiita.com/ht_deko/items/cd51409742383333e47a)