С1.83的网址
In this article, I will tell you how to read data and control a USB connected Arduino from .Net and UWP applications. You can do this without using third-party libraries. All you’ll need is a virtual COM port or any other virtual serial port solution like com0com (Note: the free solution does not support 64x Windows).
在本文中,我将告诉您如何从.Net和UWP应用程序读取数据并控制USB连接的Arduino 。 您可以在不使用第三方库的情况下执行此操作。 您只需要一个虚拟COM端口或任何其他虚拟串行端口解决方案(例如 com0com) (注意:免费解决方案不支持64x Windows)。
First, let's write a sketch for Arduino. A string with a text containing the value of a variable should be sent to the virtual port. The text will constantly change in a cycle (thus simulating data received from a sensor).
首先,让我们为Arduino写一个草图。 带有包含变量值的文本的字符串应发送到虚拟端口。 文本将在一个周期中不断变化(从而模拟从传感器接收到的数据)。
Also, we will read data from the port. If receiving "1", we will turn on the built-in LED, which is located on the 13th pin and is marked on the board with the Latin letter L. When getting “0”, we’ll turn the LED off.
另外,我们将从端口读取数据。 如果接收到“ 1”,我们将打开内置LED,该LED位于第13引脚上,并且在板上用拉丁字母L标记。当获得“ 0”时,我们将关闭LED。
int i = 0; // a variable for a counter simulating the sensor data
int led = 13;
void setup() {
Serial.begin(9600); // set the data rate
pinMode(led, OUTPUT); // and the mode of the 13th digital pin used as an output
}
void loop() {
i = i + 1; // so we can see that the data changed
String stringOne = "Info from Arduino ";
stringOne += i; // concatenation
Serial.println(stringOne); // send the string to the port
char incomingChar;
if (Serial.available() > 0)
{
// record the value received from the port to the variable
incomingChar = Serial.read();
// depending on the value of the variable, turn the LED on or off
switch (incomingChar)
{
case '1':
digitalWrite(led, HIGH);
break;
case '0':
digitalWrite(led, LOW);
break;
}
}
delay(300);
}
A WPF application
WPF应用程序
Now, let’s create a WPF application. The markup is pretty simple. There will be two buttons and a label to display the text received from the port:
现在,让我们创建一个WPF应用程序。 标记非常简单。 将有两个按钮和一个标签,以显示从端口接收的文本:
<StackPanel Orientation="Vertical">
<Label x:Name="lblPortData" FontSize="48" HorizontalAlignment="Center" Margin="0,20,0,0">No data</Label>
<Button x:Name="btnOne" Click="btnOne_Click" Width="100" Height="30" Margin="0,10,0,0">Send 1</Button>
<Button x:Name="btnZero" Click="btnZero_Click" Width="100" Height="30" Margin="0,10,0,0">Send 0</Button>
</StackPanel>
Add 2 namespaces:
添加2个名称空间:
using System.Timers;
using System.IO.Ports;
And 2 variables with a delegate to the class scope:
还有2个变量与类作用域的委托:
System.Timers.Timer aTimer;
SerialPort currentPort;
private delegate void updateDelegate(string txt);
Implement the event Window_Loaded. It will allow us to check all available ports, listen to them, and see whether any port displays a message with the text “Info from Arduino”. If we find the port sending such a message, it means it’s the Arduino port. In this case, we can set its parameters, open the port, and start the timer.
实现事件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); // wait a bit
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;
To read data from the port and compare it with the data I was looking for, I use the ArduinoDetected function:
要从端口读取数据并将其与我正在寻找的数据进行比较,我使用了ArduinoDetected函数:
private bool ArduinoDetected()
{
try
{
currentPort.Open();
System.Threading.Thread.Sleep(1000);
// a short pause
string returnMessage = currentPort.ReadLine();
currentPort.Close();
// void loop() in the sketch must contain the code Serial.println("Info from Arduino");
if (returnMessage.Contains("Info from Arduino"))
{
return true;
}
else
{
return false;
}
}
catch (Exception e)
{
return false;
}
}
Now, all that’s left is to implement the timer event handling. The OnTimedEvent method can be generated using Intellisense. Its contents will be as follows:
现在,剩下的就是实现计时器事件处理了。 可以使用Intellisense生成OnTimedEvent方法。 其内容如下:
private void OnTimedEvent(object sender, ElapsedEventArgs e)
{
if (!currentPort.IsOpen) return;
try // as after the window closing the timer can still be run or the waiting limit can be exceeded
{
// clear the buffer
currentPort.DiscardInBuffer();
// read the last value
string strFromPort = currentPort.ReadLine();
lblPortData.Dispatcher.BeginInvoke(new updateDelegate(updateTextBox), strFromPort);
}
catch { }
}
private void updateTextBox(string txt)
{
lblPortData.Content = txt;
}
We read the value from the port and output it as a label text. But since the timer works in a thread other than the UI thread, we need to use Dispatcher.BeginInvoke. This is where the delegate declared at the beginning of the code will come in handy.
我们从端口读取值并将其输出为标签文本。 但是由于计时器在UI线程以外的线程中工作,因此我们需要使用Dispatcher.BeginInvoke。 这是在代码开头声明的委托派上用场的地方。
After finishing the work with the port it is desirable to close it. But since you work with it all the time while the application is open, it is logical that you close it when you close the application. Now, let’s add the handling of the Closing event to our window:
完成端口的工作后,最好将其关闭。 但是,由于在打开应用程序的过程中一直在使用它,因此在关闭应用程序时将其关闭是合乎逻辑的。 现在,将关闭事件的处理添加到我们的窗口中:
private void Window_Closing(object sender, EventArgs e)
{
aTimer.Enabled = false;
currentPort.Close();
}
Done.
做完了
Now, we send a message with the text “1” or “0” (depending on the button press) to the port and can start testing the application. It's simple:
现在,我们向端口发送一条带有文本“ 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");
}
By the way, to create a WinForms application is even faster and easier. It is enough to drag the SerialPort element onto the form from the toolbar (as an option, you can drag the Timer element from the toolbar as well). After that, you can open the port (in the particular part of the code), read data from it, and write to it in approximately the same way as in a WPF application.
顺便说一句,创建WinForms应用程序甚至更快,更容易。 将SerialPort元素从工具栏拖动到窗体上就足够了(作为一种选择,您也可以从工具栏拖动Timer元素)。 之后,您可以打开端口(在代码的特定部分),从中读取数据,并以与WPF应用程序大致相同的方式对其进行写入。
To allow the work with a COM port, there should be the following declaration in the application manifest:
为了允许使用COM端口,应用程序清单中应包含以下声明:
<Capabilities>
<Capability Name="internetClient" />
<DeviceCapability Name="serialcommunication">
<Device Id="any">
<Function Type="name:serialPort"/>
</Device>
</DeviceCapability>
</Capabilities>
In C# code, we will need 4 namespaces:
在C#代码中,我们将需要4个名称空间:
using Windows.Devices.SerialCommunication;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;
using System.Threading.Tasks;
And one variable in the class scope:
在类范围内有一个变量:
string deviceId;
When loading, we’ll record to it the ID value of the port to which the Arduino board is connected:
加载时,我们将在其中记录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;
}
}
The following Task will read 64 bytes from the port and display the text in the field named txtPortData:
以下任务将从端口读取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;
}
}
}
}
In UWP applications written in C#, there is no SerialPort.DiscardInBuffer method. Therefore, one of the possible options is to read the data by reopening the port every time, as demonstrated in this example. If you try it, you can see that the count always starts from one. Just about the same thing happens in the Arduino IDE when opening Serial Monitor. This method, of course, isn’t much convenient but if you rarely need to read the data, it will be a solution. In addition, the example written this way is shorter and clearer.
在用C#编写的UWP应用程序中,没有SerialPort.DiscardInBuffer方法。 因此,一种可能的选择是通过每次重新打开端口来读取数据,如本示例所示。 如果您尝试一下,您会发现计数总是从1开始。 打开串行监视器时,在Arduino IDE中几乎会发生同样的事情。 当然,这种方法不太方便,但是如果您很少需要读取数据,那么它将是一个解决方案。 此外,以这种方式编写的示例更短,更清晰。
It’s recommended not to declare the port each time again, but to declare it once, for example, at loading. But in this case, it will be necessary to regularly read the data from the port so that it does not fill up with the old one and the data remains relevant. I suppose that the concept of not being able to clear the buffer is that being constantly asynchronously read, data does not really load the system. As soon as the required number of bytes is recorded into the buffer, the next code is executed.
建议不要再次声明端口,而要在加载时声明一次。 但是在这种情况下,有必要定期从端口读取数据,以免旧端口填满数据并保持相关性。 我认为无法清除缓冲区的概念是,不断地异步读取数据并不会真正加载系统。 一旦将所需的字节数记录到缓冲区中,就会执行下一个代码。
To send data to the port, you can use a similar code:
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;
}
}
}
}
As you can see, after the initialization of the port and the installation of parameters, there were added 1 second pauses. Without those pauses, it was impossible to make Arduino react. Again, note that it will be better to open the port once, and not to reopen/close it constantly. In this case, you will need no pauses.
如您所见,在端口初始化和参数安装之后,添加了1秒的暂停。 没有这些停顿,就不可能让Arduino做出React。 同样,请注意,最好一次打开该端口,而不要不断重新打开/关闭该端口。 在这种情况下,您将无需暂停。
Conclusion
结论
Working from UWP with Arduino directly through a virtual serial port is possible without using third-party libraries.
无需使用第三方库,就可以直接通过虚拟串行端口从UWP与Arduino一起工作。
I would also note that Microsoft is cooperating closely with Arduino, therefore there are a lot of different libraries and communication technologies that simplify the development process. Probably the most popular solution is the Windows Remote Arduino library that works with the Firmata protocol.
我还要指出,微软正在与Arduino紧密合作,因此有很多不同的库和通信技术可以简化开发过程。 可能最流行的解决方案是与Firmata协议一起使用的Windows Remote Arduino库 。
翻译自: https://www.experts-exchange.com/articles/33241/Working-with-Arduino-from-a-application.html
С1.83的网址