![](https://habrastorage.org/files/f6e/38b/409/f6e38b409f82458dac772d094fc991d3.png)
この記事では、データを読み取り、USBポート経由で接続されたArduinoボードを.NetアプリケーションおよびUWPアプリケーションから制御する方法について説明します。
これは、サードパーティのライブラリを使用せずに実行できます。 実際、仮想COMポートのみを使用します。
最初にArduinoのスケッチを書きましょう。 ポートに変数の値を含むテキストを含む文字列を送信します。この値はループ内で常に変化します(したがって、センサーから取得したデータをシミュレートします)。 また、ポートからデータを読み取り、「1」というテキストが表示されたら、ボードに組み込まれているLEDをオンにします。 これは13番目のピンにあり、ボード上でラテン文字Lでマークされています。「0」が表示されたら、オフにします。
int i = 0; // int led = 13; void setup() { Serial.begin(9600); // pinMode(led, OUTPUT); // 13- } void loop() { i = i + 1; // String stringOne = "Info from Arduino "; stringOne += i; // Serial.println(stringOne); // char incomingChar; if (Serial.available() > 0) { // incomingChar = Serial.read(); // LED switch (incomingChar) { case '1': digitalWrite(led, HIGH); break; case '0': digitalWrite(led, LOW); break; } } delay(300); }
WPFアプリケーション
次に、WPFアプリケーションを作成します。 マークアップは非常に簡単です。 ポートから受け取ったテキストを表示するための2つのボタンとラベルがあれば十分です。
<StackPanel Orientation="Vertical"> <Label x:Name="lblPortData" FontSize="48" HorizontalAlignment="Center" Margin="0,20,0,0"> </Label> <Button x:Name="btnOne" Click="btnOne_Click" Width="100" Height="30" Margin="0,10,0,0"> 1</Button> <Button x:Name="btnZero" Click="btnZero_Click" Width="100" Height="30" Margin="0,10,0,0"> 0</Button> </StackPanel>
2つの名前空間を追加します。
using System.Timers; using System.IO.Ports;
そして、デリゲートを持つクラス2変数のスコープ内:
System.Timers.Timer aTimer; SerialPort currentPort; private delegate void updateDelegate(string txt);
Window_Loadedイベントを実装します。 その中で、利用可能なすべてのポートを調べ、それらを聞いて、ポートに「Info from Arduino」というメッセージが表示されるかどうかを確認します。 そのようなメッセージを送信しているポートが見つかった場合、Arduinoポートが見つかりました。 この場合、パラメーターを設定し、ポートを開いてタイマーを開始できます。
bool ArduinoPortFound = false; try { string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { currentPort = new SerialPort(port, 9600); if (ArduinoDetected()) { ArduinoPortFound = true; break; } else { ArduinoPortFound = false; } } } catch { } if (ArduinoPortFound == false) return; System.Threading.Thread.Sleep(500); // currentPort.BaudRate = 9600; currentPort.DtrEnable = true; currentPort.ReadTimeout= 1000; try { currentPort.Open(); } catch { } aTimer = new System.Timers.Timer(1000); aTimer.Elapsed += OnTimedEvent; aTimer.AutoReset = true; aTimer.Enabled = true;
ポートからデータを取得し、検索したデータと比較するために、ArduinoDetected関数を使用しました。
private bool ArduinoDetected() { try { currentPort.Open(); System.Threading.Thread.Sleep(1000); // , SerialPort string returnMessage = currentPort.ReadLine(); currentPort.Close(); // void loop() Serial.println("Info from Arduino"); if (returnMessage.Contains("Info from Arduino")) { return true; } else { return false; } } catch (Exception e) { return false; } }
現在、タイマーイベントの処理を実装する必要があります。 OnTimedEventメソッドは、Intellisenseを使用して生成できます。 その内容は次のようになります。
private void OnTimedEvent(object sender, ElapsedEventArgs e) { if (!currentPort.IsOpen) return; try // { // currentPort.DiscardInBuffer(); // string strFromPort = currentPort.ReadLine(); lblPortData.Dispatcher.BeginInvoke(new updateDelegate(updateTextBox), strFromPort); } catch { } } private void updateTextBox(string txt) { lblPortData.Content = txt; }
ポートから値を読み取り、ラベルテキストとして表示します。 ただし、タイマーはUIストリーム以外のストリームで機能するため、Dispatcher.BeginInvokeを使用する必要があります。 これは、コードの先頭で宣言されたデリゲートが便利な場所です。
ポートでの作業が終了したら、ポートを閉じることを強くお勧めします。 しかし、アプリケーションが開いている間は常に作業するため、アプリケーションが閉じられたときに閉じることは論理的です。 Closingイベント処理をウィンドウに追加します。
private void Window_Closing(object sender, EventArgs e) { aTimer.Enabled = false; currentPort.Close(); }
できた これで、ボタンのクリックに応じてテキスト「1」または「0」を含むメッセージをポートに送信することができ、アプリケーションをテストできます。 簡単です:
private void btnOne_Click(object sender, RoutedEventArgs e) { if (!currentPort.IsOpen) return; currentPort.Write("1"); } private void btnZero_Click(object sender, RoutedEventArgs e) { if (!currentPort.IsOpen) return; currentPort.Write("0"); }
結果の例はGitHubで入手できます 。
ちなみに、WinFormsアプリケーションはさらに高速かつ簡単に作成されます。 SerialPort要素をツールバーからフォームにドラッグするだけで十分です(必要に応じて、ツールバーからTimer要素もドラッグできます)。 その後、コードの適切な場所で、ポートを開き、そこからデータを読み取り、WPFアプリケーションとほぼ同じようにポートに書き込むことができます。
UWPアプリ
シリアルポートの使用方法を理解するために、次の例を見てみました。SerialSample
COMポートでの作業を有効にするには、アプリケーションのマニフェストに次の宣言を含める必要があります。
<Capabilities> <Capability Name="internetClient" /> <DeviceCapability Name="serialcommunication"> <Device Id="any"> <Function Type="name:serialPort"/> </Device> </DeviceCapability> </Capabilities>
C#コードでは、4つの名前空間が必要です。
using Windows.Devices.SerialCommunication; using Windows.Devices.Enumeration; using Windows.Storage.Streams; using System.Threading.Tasks;
そして、クラスのスコープ内の1つの変数:
string deviceId;
ロード時には、Arduinoボードが接続されているポートのid値を考慮します。
private async void Page_Loaded(object sender, RoutedEventArgs e) { string filt = SerialDevice.GetDeviceSelector("COM3"); DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(filt); if (devices.Any()) { deviceId = devices.First().Id; } }
次のタスクはポートから64バイトを読み取り、txtPortDataという名前のフィールドにテキストを表示します
private async Task Listen() { using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId)) { if (serialPort != null) { serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000); serialPort.BaudRate = 9600; serialPort.Parity = SerialParity.None; serialPort.StopBits = SerialStopBitCount.One; serialPort.DataBits = 8; serialPort.Handshake = SerialHandshake.None; try { using (DataReader dataReaderObject = new DataReader(serialPort.InputStream)) { Task<UInt32> loadAsyncTask; uint ReadBufferLength = 64; dataReaderObject.InputStreamOptions = InputStreamOptions.Partial; loadAsyncTask = dataReaderObject.LoadAsync(ReadBufferLength).AsTask(); UInt32 bytesRead = await loadAsyncTask; if (bytesRead > 0) { txtPortData.Text = dataReaderObject.ReadString(bytesRead); txtStatus.Text = "Read operation completed"; } } } catch (Exception ex) { txtStatus.Text = ex.Message; } } } }
C#UWPアプリケーションにはSerialPort.DiscardInBufferメソッドがありません。 したがって、オプションの1つは、ポートを再度開くたびにデータを読み取ることで、これはこの場合に示されています。 試してみると、カウントダウンが毎回1から始まることがわかります。 Arduino IDEでシリアルモニターを開くと、ほぼ同じことが起こります。 もちろん、オプションはまあまあです。 毎回ポートを開くことはそうではありませんが、データをめったに読み取る必要がない場合、このメソッドは機能します。 さらに、記録された例は、このように短く、より明確に見えます。
推奨されるオプションは、毎回ポートを再度宣言するのではなく、たとえばブート時に一度宣言することです。 ただし、この場合、ポートからデータを定期的に読み取る必要があります。これにより、データがジャンクでいっぱいにならず、データが適切であることがわかります。 UWPアプリケーションの例でこれがどのように行われるかをご覧ください。 バッファをクリアする機能がないという概念は、常に非同期的にキャプチャされたデータが実際にシステムにロードしないということだと思います。 必要なバイト数がバッファに読み込まれるとすぐに、書き込まれたコードが続きます。 このような継続的な監視では何も見逃すことはありませんが、一部の(私を含む)データを一度「読み取る」通常の機会に欠けているという事実にはプラスがあります。
ポートにデータを書き込むには、同様のコードを使用できます。
private async Task sendToPort(string sometext) { using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId)) { Task.Delay(1000).Wait(); if ((serialPort != null) && (sometext.Length != 0)) { serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000); serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000); serialPort.BaudRate = 9600; serialPort.Parity = SerialParity.None; serialPort.StopBits = SerialStopBitCount.One; serialPort.DataBits = 8; serialPort.Handshake = SerialHandshake.None; Task.Delay(1000).Wait(); try { using (DataWriter dataWriteObject = new DataWriter(serialPort.OutputStream)) { Task<UInt32> storeAsyncTask; dataWriteObject.WriteString(sometext); storeAsyncTask = dataWriteObject.StoreAsync().AsTask(); UInt32 bytesWritten = await storeAsyncTask; if (bytesWritten > 0) { txtStatus.Text = bytesWritten + " bytes written"; } } } catch (Exception ex) { txtStatus.Text = ex.Message; } } } }
ポートを初期化し、パラメーターを設定した後、1秒の一時停止が追加されることに気付くかもしれません。 これらの一時停止がなければ、Arduinoに強制的に反応させることはできませんでした。 シリアルポートは本当に大騒ぎを許容しないようです。 繰り返しますが、ポートを一度開いて、常に開いたり閉じたりしない方が良いことを思い出します。 この場合、一時停止は必要ありません。
上記で説明したWPF .Netアプリケーションに似たUWPアプリケーションの簡易バージョンは、 GitHubで入手できます。
その結果、 仮想COMポートを介してArduinoでUWPを直接操作することは 、異常ではありますが、サードパーティのライブラリを接続しなくても非常に可能であるという結論に達しました。
MicrosoftはArduinoと非常に緊密に連携しているため、開発を簡素化するさまざまなライブラリと通信テクノロジが存在することに注意してください。 もちろん最も人気のあるものは、Firmataプロトコルで動作するWindowsリモートArduinoライブラリです。