第1章串行通讯之.NET SerialPort    2

1 枚举串口    2

2 打开/关闭串口    2

3 写数据    3

3.1 写二进制数据    3

3.2 写文本数据    4

4 读数据    5

4.1 读二进制数据    6

4.2 读一个字节    7

4.3 读一个字符    7

4.4 读全部文本    7

4.5 读文本到某个字符串    8

4.6 读一行文本    8

4.7 DataReceived事件    8

5 流控制    9

5.1 软件流控制(XON/XOFF)    10

5.2 硬件流控制(RTS/CTS)    10

6 输入信号    11

第1章串行通讯之.NET SerialPort

.NET 库中类System.IO.Ports.SerialPort用于串行通讯,本文对其使用进行简要说明。

1 枚举串口

函数System.IO.Ports.SerialPort.GetPortNames将获得系统所有的串口名称。C#代码如下:

string[] arrPort = System.IO.Ports.SerialPort.GetPortNames();

foreach (string s in arrPort)

{

}

2 打开/关闭串口

下面的 C# 代码将打开 COM100:1200,N,8,1

System.IO.Ports.SerialPort m_sp = new System.IO.Ports.SerialPort();

private void btnOpen_Click(object sender, EventArgs e)

{

m_sp.PortName = "COM100";            //串口

m_sp.BaudRate = 1200;                 //波特率:1200

m_sp.Parity = System.IO.Ports.Parity.None;    //校验法:无

m_sp.DataBits = 8;                     //数据位:8

m_sp.StopBits = System.IO.Ports.StopBits.One;     //停止位:1

try

{

m_sp.Open();        //打开串口

m_sp.DtrEnable = true;     //设置DTR为高电平(含义见下文)

m_sp.RtsEnable = true;     //设置RTS为高电平(含义见下文)

}

catch (System.Exception ex)

{//打开串口出错,显示错误信息

MessageBox.Show(ex.Message);

}

}

下面的 C# 代码将关闭打开的串口

if(m_sp.IsOpen) //判断串口是否已经被打开

{

m_sp.Close(); //关闭串口

}

3 写数据

System.IO.Ports.SerialPort用于写串口数据的成员函数有四个,如下所示:

函数

说明

void Write(byte[] buffer, int offset, int count);

void Write(char[] buffer, int offset, int count);

写二进制数据

void Write(string text);

写文本数据

void WriteLine(string text);

写一行文本

3.1 写二进制数据

void Write(byte[] buffer, int offset, int count);和void Write(char[] buffer, int offset, int count);用于写二进制数据。它们的区别仅仅在于第一个参数不同:byte[]是无符号的,char[]是有符号的。对于二进制数据而言,byte、char没有实质的区别。

下面的C#代码,将写1024个00H:

try

{

if(m_sp.IsOpen)

{

byte[] bt = new byte[1024];

m_sp.Write(bt,0,bt.Length); //写 1024 个 00H

}

}

catch (System.Exception ex)

{//显示错误信息

MessageBox.Show(ex.Message);

}

注意:

1、Write函数是同步的。以上面的代码为例,1024个00H在发送完之前,Write函数是不会返回的。波特率1200,发送1024个字节大概要耗时9秒。如果这段代码在主线程里,那么这9秒内整个程序将处于假死状态:无法响应用户的键盘、鼠标输入;

2、WriteTimeout属性用于控制Write函数的最长耗时。它的默认值为System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含义为:Write函数不将所有数据写完绝不返回。可以修改此属性,如下面的代码:

try

{

if(m_sp.IsOpen)

{

byte[] bt = new byte[1024];

m_sp.WriteTimeout = 5000; //Write 函数最多耗时 5000 毫秒

m_sp.Write(bt,0,bt.Length); //写 1024 个 00H

}

}

catch (System.Exception ex)

{//显示错误信息

MessageBox.Show(ex.Message);

}

上面的代码中,设置WriteTimeout属性为5秒。所以Write写数据时最多耗时5秒,超过这个时间未发的数据将被舍弃,Write函数抛出异常TimeoutException后立即返回。

3.2 写文本数据

下面是void Write(string text)的示例代码

try

{

if(m_sp.IsOpen)

{

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

m_sp.Write("串行通讯");

}

}

catch (System.Exception ex)

{//显示错误信息

MessageBox.Show(ex.Message);

}

首先设置代码页为936(即GBK码),Write(string text)函数根据代码页把字符串"串行通讯"转换为二进制数据,如下所示:

字符串

内码

B4 AE

D0 D0

CD A8

D1 B6

然后把二进制数据B4 AE D0 D0 CD A8 D1 B6发送出去。

函数void WriteLine(string text);等价于void Write(text + NewLine)。参考下面的代码:

try

{

if(m_sp.IsOpen)

{

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

m_sp.NewLine = "\r\n";

m_sp.WriteLine("串行通讯");

}

}

catch (System.Exception ex)

{//显示错误信息

MessageBox.Show(ex.Message);

}

代码m_sp.NewLine = "\r\n";设置行结束符为回车(0DH)换行(0AH)。m_sp.WriteLine("串行通讯");等价于m_sp.Write("串行通讯"+m_sp.NewLine);也就是m_sp.Write("串行通讯\r\n");

最终,发送出去的二进制数据为B4 AE D0 D0 CD A8 D1 B6 0D 0A。

4 读数据

System.IO.Ports.SerialPort用于读串口数据的成员函数有七个,如下所示:

函数

说明

int ReadByte();

读取一个字节

int ReadChar();

读取一个字符

int Read(byte[] buffer, int offset, int count);

int Read(char[] buffer, int offset, int count);

读取二进制数据

string ReadExisting();

读取全部文本

string ReadTo(string value);

读取文本到某个字符串

string ReadLine();

读取一行文本

4.1 读二进制数据

下面的C#代码,将读取 3 个字节的串口数据

if(m_sp.IsOpen)

{

try

{

byte[] b = new byte[3];

int n = m_sp.Read(b,0,3); //返回值是读取到的字节数

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

注意:

1、Read函数是同步的。以上面的代码为例,3个字节的数据被读取之前,Read函数是不会返回的。如果这段代码在主线程里,那么整个程序将处于假死状态;

2、ReadTimeout属性用于控制Read函数的最长耗时。它的默认值为System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含义为:Read函数未读取到串口数据之前是不会返回的。可以修改此属性,如下面的代码:

if(m_sp.IsOpen)

{

try

{

byte[] b = new byte[3];

m_sp.ReadTimeout = 2000;

int n = m_sp.Read(b,0,3); //返回值是读取到的字节数

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

上面的代码中,设置ReadTimeout属性为2秒。所以Read函数读数据时最多耗时2秒。超过这个时间未读取到数据,Read函数将抛出异常TimeoutException,然后返回。

4.2 读一个字节

int ReadByte();与int Read(byte[] buffer, int offset, int count);类似,它的特点就是只读取一个字节的串口数据。

4.3 读一个字符

int ReadChar();是读取一个字符,这个稍微复杂些。它可能读取1~3个字节的数据,然后合为一个字符。

如:m_sp.Encoding = System.Text.Encoding.GetEncoding(936);即字符串编码为GBK。给m_sp发送"串"的GBK编码B4 AE。ReadChar首先读取一个字节得到B4H。这是一个汉字的区码,还得读取一个字节得到位码。最终ReadChar读取的是B4 AE。ReadChar的返回值是Unicode编码,即返回前会把GBK编码B4 AE转换为Unicode编码0x4E32。

再如:m_sp.Encoding = System.Text.Encoding.UTF8;即字符串编码为UTF8。给m_sp发送"串"的UTF8编码E4 B8 B2。ReadChar会读取三个字节的串口数据E4 B8 B2,然后将其转换为Unicode编码0x4E32,并返回这个数值。

4.4 读全部文本

函数string ReadExisting();读取串口输入缓冲区中的所有二进制数据,然后将其转换为字符串,最后返回字符串。

注意:

1、ReadExisting会立即返回。如果输入缓冲区内没有数据,直接返回长度为零的空字符串;

2、ReadExisting读取输入缓冲区后,有时会留几个字节。参考下面的代码:

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

string s = m_sp.ReadExisting();

int n = m_sp.BytesToRead; //输入缓冲区剩余的字节数

"串"、"行"的GBK编码分别为 B4 AE和D0 D0。

首先发送 B4 AE D0 给m_sp,运行上述代码。ReadExisting将获得B4 AE D0,"B4 AE"会被解释为"串",D0是汉字的区码,所以ReadExisting会将D0保留在输入缓冲区内。上述代码的运行结果就是:s为"串",n为1;

然后发送D0 给m_sp,运行上述代码。ReadExisting将获得D0 D0,"D0 D0"会被解释为"行"。上述代码的运行结果就是:s为"行",n为0。

4.5 读文本到某个字符串

函数string ReadTo(string value);将在串口输入缓冲区内查找字符串value。找到了,就返回value之前的字符串,同时清除缓冲区内value及其之前的数据;未找到,就一直等待,直至超时。

4.6 读一行文本

函数string ReadLine();等价于ReadTo(NewLine)。使用前,请设置NewLine属性,指定行结束符。

4.7 DataReceived事件

串口输入缓冲区获得新数据后,会以DataReceived事件通知System.IO.Ports.SerialPort对象,可以在此时读取串口数据。请参考下面两段代码:

m_sp.ReceivedBytesThreshold = 1;

m_sp.DataReceived+=new System.IO.Ports.SerialDataReceivedEventHandler(m_sp_DataReceived);

void m_sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)

{

int nRead = m_sp.BytesToRead;

if (nRead > 0)

{

byte[] data = new byte[nRead];

m_sp.Read(data, 0, nRead);

}

}

m_sp.ReceivedBytesThreshold = 1;的含义:串口输入缓冲区获得新数据后,将检查缓冲区内已有的字节数,大于等于ReceivedBytesThreshold就会触发DataReceived事件。这里设置为1,显然就是一旦获得新数据后,立即触发DataReceived事件。

m_sp.DataReceived+=new System.IO.Ports.SerialDataReceivedEventHandler(m_sp_DataReceived);的含义:对于DataReceived事件,用函数m_sp_DataReceived进行处理。

回调函数m_sp_DataReceived用于响应DataReceived事件,通常在这个函数里读取串口数据。它的第一个参数sender就是事件的发起者。上面的代码中,sender其实就是m_sp。也就是说:多个串口对象可以共用一个回调函数,通过sender可以区分是哪个串口对象。

回调函数是被一个多线程调用的,它不在主线程内。所以,不要在这个回调函数里直接访问界面控件。如下面的代码将将读取到的串口数据转换为字符串,然后显示在按钮Open上。红色代码处将产生异常。

void m_sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)

{

int nRead = m_sp.BytesToRead;

if (nRead > 0)

{

byte[] data = new byte[nRead];

m_sp.Read(data, 0, nRead);

btnOpen.Text = System.Text.Encoding.Default.GetString(data);

}

}

可使用Invoke或BeginInvoke改进上面的红色代码:

BeginInvoke(new Action<string>((x)=>{btnOpen.Text = x;})

,new Object[] {System.Text.Encoding.Default.GetString(data)});

5 流控制

串行通讯的双方,如果有一方反应较慢,另一方不管不顾的不停发送数据,就可能造成数据丢失。为了防止这种情况发生,需要使用流控制。

流控制也叫握手,System.IO.Ports.SerialPort的Handshake属性用于设置流控制。它有四种取值:

取值

说明

System.IO.Ports.Handshake.None

System.IO.Ports.Handshake.XOnXOff

软件

System.IO.Ports.Handshake.RequestToSend

硬件

System.IO.Ports.Handshake.RequestToSendXOnXOff

硬件和软件

5.1 软件流控制(XON/XOFF)

串口设备A给串口设备B发送数据。B忙不过来时(B的串口输入缓冲区快满了)会给A发送字符XOFF(一般为13H),A将暂停发送数据;B的串口输入缓冲区快空时,会给A发送字符XON(一般为11H),A将继续发送数据。

软件流控制最大的问题在于:通讯双方不能传输字符XON和XOFF。

5.2 硬件流控制(RTS/CTS)

RTS/CTS流控制是硬件流控制的一种,需要按下图连线:

图1

串口设备A给串口设备B发送数据。B忙不过来时(B的串口输入缓冲区快满了)会设置自己的RTS为低电平,这样A的CTS也变为低电平。A发现自己的CTS为低电平后,会停止发送数据;B的串口输入缓冲区快空时,会设置自己的RTS为高电平,这样A的CTS也变为高电平。A发现自己的CTS为高电平后,会继续发送数据。

相同的道理,DTR/DSR也可以做硬件流控制。

现在再来看看如下代码:

m_sp.Open();

m_sp.DtrEnable = true;

m_sp.RtsEnable = true;

为什么打开串口时需要设置DTR、RTS为高电平呢?原因就在于:如果对方设置了硬件流控制,而这边的DTR、RTS为低电平,那么对方就不会给这边发送数据。

需要注意的是:RTS/CTS硬件流控制下,RTS的电平由系统自行调整。调用m_sp.RtsEnable = true;改变RTS的电平将会导致异常。

6 输入信号

上一节中,属性DtrEnable、RtsEnable可以控制输出信号DTR、RTS。与之相应的,属性CDHolding、CtsHolding、DsrHolding可读取输入信号。

CtsHolding 为 true,说明对方的RTS为高电平(请按图1所示连线)。

DsrHolding 为 true,说明对方的DTR为高电平(请按图1所示连线)。

串行通讯之.NET SerialPort的更多相关文章

  1. 串行通讯之.NET SerialPort异步写数据

    目录 第1章说明    2 1 为什么需要异步写数据?    2 2 异步写数据的代码    2 3 源代码    4 第1章说明 1 为什么需要异步写数据? 如下图所示,以波特率300打开一个串口. ...

  2. 【Arduino】使用C#实现Arduino与电脑进行串行通讯

    在给Arduino编程的时候,因为没有调试工具,经常要通过使用串口通讯的方式调用Serial.print和Serial.println输出Arduino运行过程中的相关信息,然后在电脑上用Arduin ...

  3. 串行通讯之Qt

    目录 第1章 Qt 串行通讯    1 1.1 配置.pro文件    1 1.2 查询串口信息    1 1.3 配置.打开串口    3 1.4 setRequestToSend在Windows上 ...

  4. 串行通讯之UARTLoopback

    目录 第1章串行通讯之UARTLoopback    2 1 USB转串口    2 2 USB Accessory    2 3 连入手机    3 4 代码改进    4 5 打开串口    4 ...

  5. COM口,串行通讯端口,RS-232接口 基础知识

    COM口即串行通讯端口. COM口的接口标准规范和总线标准规范是RS-232,有时候也叫做RS-232口.电脑上的com口多为9针,最大速率115200bps.通常用于连接鼠标(串口)及通讯设备(如连 ...

  6. STM32L476应用开发之三:串行通讯实验

    在我们的项目需求中,有两个串口应用需求,一个是与炭氢传感器的通讯,另一个是与显示屏的通讯.鉴于此,我们需要实验串行通讯. 1.硬件设计 串行通讯一个采用RS232接口,另一个直接采用TTL方式.我们在 ...

  7. 基于51的串行通讯原理及协议详解(uart)

    串行与并行通讯方式并行:控制简单,传输速度快.线多,长距离成本较高且同时接受困难.串行:将数据字节分成一位一位的行驶在一条传输线上进行传输.如图:   同步与异步串行通讯方式同步串行通讯方式:同步通讯 ...

  8. STM32学习笔记——SPI串行通讯(向原子哥学习)

    一.SPI  简介 SPI是 Serial Peripheral interface 的缩写,就是串行外围设备接口.SPI 接口主要应用在  EEPROM, FLASH,实时时钟,AD 转换器,还有数 ...

  9. boost库在工作(40)串行通讯

    现代的计算机技术进步很快,各种的通讯也日新月异,像USB.网络.蓝牙.WIFI等通讯技术飞速地出现,改变了整个计算机的通讯能力,速度已经达到GBit级别.但是有一种最原始的通讯方式,还是保留了30年, ...

随机推荐

  1. C#中另类自定义公式计算 字符串转换为计算公式,并得出计算结果

    [csharp] view plain copy print? //方法一 利用DataTable中的Compute方法 例如:1*2-(4/1)+2*4=6 , , , ); DataTable d ...

  2. 土豪聪要请客(stol)

    土豪聪要请客(stol) 众所周知,聪哥(ndsf)是个土豪,不过你们不知道的是他的MZ和他的RMB一样滴多…… 某天土豪聪又赚了10^10000e的RMB,他比较开心,于是准备请客.他在自己在XX星 ...

  3. 从exchange2010上面删除特定主题或特定时间的邮件

    昨天在上班的公交上接到同事电话,说他的的部门老大发错了一封邮件到另外一个同事邮箱了,问我能不 能去那个同事的邮箱里面删除,我一想,之前在网上看到过资料,到了公司趁那个误接收邮件的同事还没有来,在服务器 ...

  4. Cheatsheet: 2013 09.01 ~ 09.09

    .NET Multi Threaded WebScraping in CSharpDotNetTech .NET Asynchronous Patterns An Overview of Projec ...

  5. ThinkPHP 模型(Model)命名规范

    一个小问题搞了好久:如果数据库的表名中有下划线,那么在用thinkphp做自动完成时注意Model类的命名要变成驼峰法,文件名和类名都要变.( 另外注意:只有使用create方法创建数据时才能调用到自 ...

  6. php中一个"异类"语法: $a && $b = $c;

    php中一个"异类"语法: $a && $b = $c;     $a = 1;$b = 2;$c = 3;$a && $b = $c;echo & ...

  7. XML学习笔记(二)-- DTD格式规范

    标签(空格分隔): 学习笔记 XML的一个主要目的是允许应用程序之间自由交换结构化的数据,因此要求XML文档具有一致的结构.业务逻辑和规则.可以定义一种模式来定义XML文档的结构,并借此验证XML文档 ...

  8. Why And When To Use Pre-Update and Pre-Insert Triggers In Oracle Forms

    Whenever we commit after entering data in Oracle Forms, many triggers fires during this event and al ...

  9. Creating HTML table with vertically oriented text as table header 表头文字方向

    AS an old question, this is more like info or reminder about vertical margin or padding in % that ta ...

  10. iOS - Swift Dictionary 字典

    前言 public struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConverti ...