プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

C#でSerial通信をする

C#でArudinoと通信したい

ArduinoはGPIOがあるため、モーターやLEDを駆動したりスイッチの状態や電圧を読んだりすることが出来ます。
一方、C#ではWindows上でリッチなGUIアプリを構成することが出来ます。
これらを組み合わせることが出来れば、Windowsからデバイスを制御したり、デバイスの状態をWindows上で確認することが出来ます。
C#ではSerial通信が簡単につかえて、以下を参考にしました。
www.atsumitakeshi.com

ちなみにシリアル通信とは、Arduinoであればシリアルモニタに文字列を表示できるものを思い浮かべるかと思いますが、これはArduino固有のものではなく一般的に使える通信の仕組みの一つです。

Arduinoから文字列を送信する

まずはシンプルにArduinoから文字列を送信してみます。
Arduinoに接続したスイッチを押したとき「Switch pushed.」という文字列を送ります。
(スイッチのピンはプルアップにしてあります)

if (digitalRead(SwPin) == LOW) {
  delay(400);//チャタリング防止
  while (digitalRead(SwPin) == LOW) {}
  Serial.print("Switch pushed.");
}

これをC#で受け取り、TextBoxに追記します。
C#でのシリアルの受信はstring data = serialPort1.ReadExisting();のようにします。

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    try
    {
        string data = serialPort1.ReadExisting();   // ポートから文字列を受信する
        if (!string.IsNullOrEmpty(data))
        {
            Invoke((MethodInvoker)(() =>    // 受信用スレッドから切り替えてデータを書き込む
            {
                textBoxLog.AppendText(data+ System.Environment.NewLine);
                Application.DoEvents();
                Thread.Sleep(1);
            }));
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

このような少し長めの「文字列」を送信すると、本来終わりでないところでいったん受信が切れているように見えます。

送った文字列は最終的にはすべて送信され、C#で受信されてはいますが「一塊を誤認識」しているような形になってしまいます。
ここでは文字列を受信したら、これをTextBoxに書き出し改行を加えていますが、文字列が分割して受信されているようです。

これを防止するためには、「文字列」が”ここまで”ということを示す「文字」=「終端文字」='\0'を文字列の最後に追加します。

if (digitalRead(SwPin) == LOW) {
  delay(400);
  while (digitalRead(SwPin) == LOW) {}
  Serial.print("Switch pushed.");
  Serial.print('\0');
}

終端文字を受信側で認識して改行に置き換えます。

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    try
    {
        string data = serialPort1.ReadExisting();   // ポートから文字列を受信する
        if (!string.IsNullOrEmpty(data))
        {
            Invoke((MethodInvoker)(() =>    // 受信用スレッドから切り替えてデータを書き込む
            {
                data = data.Replace("\0", System.Environment.NewLine);
                textBoxLog.AppendText(data);
                Application.DoEvents();
                Thread.Sleep(1);
    }));
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}


C#から文字列を送信する

今度はWindowsC#)から送信してみます。
Arduino側では、受け取るものは主に「数値」のような気がしますので、数値に限定して送信することを考えます。
ここでは3桁の数値を送ることを考えます。
Windows側では数値は「全角」と「半角」がありますので、これらをすべて半角に変換し、数値以外は無視するように以下のような処理をいれました。
このように変換した文字列をserialPort1.Write(value);で送信します。
こちらも、終端文字を送信したいのですがC#には終端文字が無いようなので、/( Asciiで47) を送信することにします。
(一般的な慣習があれば教えてください。)

private void DataSend()
{
    string data = textBoxValue.Text;

    //全角数字を半角数字に変換
    data = data.Replace("0", "0");
    data = data.Replace("1", "1");
    data = data.Replace("2", "2");
    data = data.Replace("3", "3");
    data = data.Replace("4", "4");
    data = data.Replace("5", "5");
    data = data.Replace("6", "6");
    data = data.Replace("7", "7");
    data = data.Replace("8", "8");
    data = data.Replace("9", "9");

    //正規表現で数値を取得
    System.Text.RegularExpressions.MatchCollection mc = System.Text.RegularExpressions.Regex.Matches(data, @"\d+");
    {
        foreach (System.Text.RegularExpressions.Match m in mc)
        {
            string value = m.ToString();
            while (value.Length < 3) value = "0" + value.ToString(); //3桁の文字列に変換して送る
            serialPort1.Write(value);
            serialPort1.Write("/"); //終端文字?
            break;
        }
    }
}

これをArudinoで受信します。
受信した数値は3桁であるという前提にしているので、以下のように100の位、10の位、1の位という順番に受信しますので、もとの数値を復元します。
Windows側でも正しく送信されたことを確認するためにSerial.print(count); Serial.print('\0');のように数値を返還します。

void loop() {
  SerialRead = Serial.available();  // シリアルポートにデータがあるかを確認
  if (SerialRead > 0) 
  {
    // Serial.print("L");
    inByte[digit++] = Serial.read(); // データを読み込む

    if (digit == 47) //"/"を受信したら
    { 
      //3桁のデータを受信したら、文字列を数値に
      count += (inByte[0] - 48) * 100;
      count += (inByte[1] - 48) * 10;
      count += inByte[2] - 48;

      for (int i = 0; i < count; i++) {
        digitalWrite(LED_BUILTIN, HIGH);
        delay(300);
        digitalWrite(LED_BUILTIN, LOW);
        delay(300);
      }
      Serial.print(count);
      Serial.print('\0');

      delay(1000);  // 通信用の待機時間
      count = 0;
      digit = 0;
    }
  }

まとめ

C#とArudinoでシリアル通信を実装してみました。
Serial通信は基本的な通信技術なので、使い道はいろいろあると思います。
github.com