65.9K
CodeProject 正在变化。 阅读更多。
Home

使用WPF、RS232和PIC进行串行通信

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (29投票s)

2010年11月23日

CPOL

7分钟阅读

viewsIcon

197544

downloadIcon

28132

使用 WPF 从 PC 到 PIC 的 RS232 通信。

引言

本文将介绍如何使用WPF实现RS232串行通信。其整体布局和设计很简单,因为代码是我希望关注的重点。特别是,我解决了与PIC进行串行通信的关键障碍,例如Dispatcher.Invoke方法和ASCII到HEX的转换。

假设知识

本文期望开发者对C#和WPF有基本的了解。

背景

我花了7年时间愉快地用C#编程。大约5个月前,我决定转向WPF,这真是一段有趣的旅程。我是一名电气工程师,大部分时间都在让我的电脑与外部世界进行通信。我当时正在做一个DMX项目,并决定在WPF中使用XAML界面,而不是那些枯燥且功能有限的C# Forms。

所以,这一切的想法是让一个WPF项目与PIC微控制器进行通信。在C#中,这通过利用System.IO.Ports库来实现,而在WPF中也没有什么不同。然而,任何C#背景的用户都会遇到一些独特的问题。这就是缺乏Invoke方法,这稍微有点不准确,因为它隐藏在里面,但已被Dispatcher类继承。

Using the Code

代码的布局非常简单,几乎没有注意命名约定等。这将在以后解决,但我希望先将基础知识发布出来,以便人们可以开始使用代码。

首先,我们将从界面开始。这非常基础,只是一个用于COM端口和波特率的ComboBox,一个用于发送数据的Textbox,以及一个用于显示从COM端口接收数据的Richtextbox,还有几个用于操作的按钮。我没有展示这部分的代码,因为它可以在源代码中下载,而且布局并不重要。

基本样式

ScreenShot.JPG

高级样式

Screenshot2.JPG

XAML数据提供程序

第一个重要因素是COM端口名称和波特率的来源。现在,有办法从你正在使用的计算机获取相关数据,但我想要更多的控制权,所以我将所有标题放在一个名为CommsData.xml的XML文件中。这些数据由“数据提供程序”提供,每个数据提供程序都放置在Windows.Resource元素内。XPath决定了我在查看哪个元素,而x:Key是我在代码中引用的。

   <Window.Resources>
        <XmlDataProvider x:Key="ComPorts" Source="CommsData.xml" XPath="/Comms/Ports" />
        <XmlDataProvider x:Key="ComSpeed" Source="CommsData.xml" XPath="/Comms/Baud" />
    </Window.Resources>

为了进一步理解,我们需要检查CommsData.xml的内容。

<Comms>
  <Ports>COM1</Ports>
  <Baud>921600</Baud>
</Comms>

<Comms>是我的根元素,数据文件中只能有一个。<Ports><Baud>子元素分隔文本,因此我的数据提供程序只能看到我希望它们看到的内容。每个子元素可以有自己的子元素;你只需要正确设置数据提供程序来显示你想要的数据。如果你想格式化这些数据的呈现方式,请搜索数据模板的使用。

数据提供程序通过绑定每个ComboBoxItemsSource属性来使用。其中ComPorts是我为数据提供程序选择的x:Key

ItemsSource="{Binding Source={StaticResource ComPorts}}"

设置串行端口

在C#环境中,有两种方法可以实现这一点——你可以将一个元素拖到窗体上并编辑其属性,或者你可以硬编码。在WPF中,不幸的是,你必须硬编码COM端口,但这非常容易做到。

首先,我们必须包含IO端口库的引用。

using System.IO.Ports;

现在,我们可以创建一个串行端口。为了方便起见,我将其命名为serial,但如果你使用了更多的端口,应该更改命名约定。

SerialPort serial = new SerialPort();

串行端口有几个属性可以编辑。这发生在源代码中按下“连接”按钮时,但也可以在任何地方设置,只要在打开端口之前完成即可。

serial.PortName = Comm_Port_Names.Text; //Com Port Name                
serial.BaudRate = Convert.ToInt32(Baud_Rates.Text); //COM Port Sp
serial.Handshake = System.IO.Ports.Handshake.None;
serial.Parity = Parity.None;
serial.DataBits = 8;
serial.StopBits = StopBits.One;
serial.ReadTimeout = 200;
serial.WriteTimeout = 50;

有关这些属性的信息,请参阅RS-232通信协议。这些设置应该满足大多数应用程序的需求,但有两个重要的属性。ReadTimeout是指读取串行端口接收数据的允许时间。如果设置得太低,可能会发生错误,消息可能被截断。WriteTimeout是指从串行端口写出数据的允许时间——如果你尝试写入长字符串,这可能会导致错误。

我没有找到这些值的完美公式,需要尝试和错误的方法,同时要记住,允许的时间越长,你的程序就会继承越多的延迟。

接收数据

现在我们有了串行端口,重要的是为每次串行端口有数据可读时设置一个函数调用。这比创建一个线程、轮询数据并等待超时异常要高效得多。要做到这一点,我们只需引入以下代码:

serial.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(Recieve);

这将在每次接收到数据时调用Recieve函数。在该函数中,我们将数据读取到一个名为recieved_dataString中,然后调用一个函数将此数据写入我们的窗体。要启用Invoke,我们必须包含:

using System.Windows.Threading;
private void Recieve(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    // Collecting the characters received to our 'buffer' (string).
    recieved_data = serial.ReadExisting();
    Dispatcher.Invoke(DispatcherPriority.Send, 
	new UpdateUiTextDelegate(WriteData), recieved_data);
}

现在在WPF中,我们必须使用Dispatcher.Invoke方法。它的目的很简单,主窗体由其自己的线程控制。当串行端口宣布已收到数据时,会创建一个新线程来读取这些数据。Invoke方法允许在可能防止任何错误的情况下,将数据字符串从串行数据接收线程传递到窗体线程。

数据可以写入任何能够显示文本的控件。在这个例子中,我们通过RichTextbox显示它。在WPF中,你只能将Flow Document写入RichTextbox,所以你必须创建一个Paragraph来写入数据,然后将这个Paragraph添加到Flow Document中,然后再显示它。比C#复杂,但并非无法处理。

FlowDocument mcFlowDoc = new FlowDocument();
Paragraph para = new Paragraph();
private void WriteData(string text)
{
    // Assign the value of the plot to the RichTextBox.
    para.Inlines.Add(text);
    mcFlowDoc.Blocks.Add(para);
    Commdata.Document = mcFlowDoc;
}

发送数据

在这里,如果要在计算机之间进行通信,情况可能会发生变化,协议将有所不同,你可以使用标准的serial.Write(string)方法发送数据,就像在C#中一样。但是,如果你要与PIC微控制器通信,则必须将文本转换为HEX字节。

SerialData TextBox中保存的文本被发送到SerialCmdSend函数。该函数在其编码数据字符串的过程中需要几个步骤。第一步是检查串行端口是否已打开serial.IsOpen。这样做是必不可少的,否则会报错。在try{} catch{}方法中是编码例程。

 using System.Threading; 
if (serial.IsOpen)
{
    try
    {
        // Send the binary data out the port
        byte[] hexstring = Encoding.ASCII.GetBytes(data);
        foreach (byte hexval in hexstring)
        {
            byte[] _hexval = new byte[] { hexval }; 	// need to convert byte 
						// to byte[] to write
            serial.Write(_hexval, 0, 1);
            Thread.Sleep(1);
        }
    }
    catch (Exception ex)
    {
        para.Inlines.Add("Failed to SEND" + data + "\n" + ex + "\n");
        mcFlowDoc.Blocks.Add(para);
        Commdata.Document = mcFlowDoc;
    }
}

现在重要的是要理解这里发生的事情以及为什么。第一步是将字符串十六进制值转换为字节数组。例如,“123”将变成:

[1]-49 
[2]-50  
[3]-51

现在在计算机到计算机的通信中,可以直接使用:

serial.Write(hexstring, 0, hexstring.length);

然而,在PIC通信中,这种方法存在一个问题。这可能只在使用更高的波特率时才显现,但这是由计算机的时序问题引起的,因为计算机将尝试连续发送数据,而PIC将被压垮。PIC上的程序将崩溃并需要重新启动,在实际应用中我们不能容忍这种情况。

诀窍在于一次只发送1个字节,确保字节之间有延迟。延迟只需要1毫秒,但如果不使用它,PIC就会崩溃。这是在下面再次显示的循环中完成的。在这个循环中,将每个字节转换为字节数组(byte[])的额外步骤是恼人地必需的,因为所使用的serial.Write方法只发送字节数组。

foreach (byte hexval in hexstring)
{
    byte[] _hexval = new byte[] { hexval }; // need to convert byte to byte[] to write
    serial.Write(_hexval, 0, 1);
    Thread.Sleep(1);
}

最后,你就拥有了一个工作的WPF串行通信平台,用于RS-232,请稍后查看完整的可用版本以及使用Microsoft Expression Blend重新设计的版本。

历史

  • 版本1.0 基本样式和源代码
  • 版本1.1 高级样式和源代码
© . All rights reserved.