C#中SerialPort的使用教程详解
最近在学习C#的SerialPort ,关于SerialPort 的使用,做如下总结:
1.可以通过函数System.IO.Ports.SerialPort.GetPortNames() 将获得系统所有的串口名称。C#代码如下:
string[] sPorts = SerialPort.GetPortNames();
foreach(string port in sPorts)
{
var serialPort = new SerialPort();
serialPort.PortName = port;
serialPort.Open();
serialPort.WriteLine("ATI"); // this will ask the port to issue an ident string which you can match against
}
2.列出所有的串口:
private void comboBox1_Click(object sender, EventArgs e)
{
string[] portNamesArray = SerialPort.GetPortNames();
this.comboBox1.Items.Clear();
foreach (var item in portNamesArray)
{
this.comboBox1.Items.Add(item);
}
this.comboBox1.Items.Add("");
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
selectedPortName = this.comboBox1.SelectedItem.ToString();//获取选中的port
}
3. 打开/关闭串口:
SerialPort port = new SerialPort();
port.BaudRate = 1200;//波特率
port.PortName = "COM1";
port.Parity = Parity.None;//校验法:无
port.DataBits = 8;//数据位:8
port.StopBits = StopBits.One;//停止位:1
try
{
port.Open();//打开串口
port.DtrEnable = true;//设置DTR为高电平
port.RtsEnable = true;//设置RTS位高电平
}
catch (Exception ex)
{
//打开串口出错,显示错误信息
MessageBox.Show(ex.Message);
}
if (port.IsOpen)
{
port.Close();//关闭串口
}
4.写数据:
函数说明void Write(byte[] buffer, int offset, int count);
void Write(char[] buffer, int offset, int count);写二进制数据void Write(string text);写文本数据void WriteLine(string text); 写一行数据
(1)写二进制数据:
void Write(byte[] buffer, int offset, int count);和void Write(char[] buffer, int offset, int count);用于写二进制数据。它们的区别仅仅在于第一个参数不同:byte[]是无符号的,char[]是有符号的。对于二进制数据而言,byte、char没有实质的区别。
下面的C#代码,将写1024个00H:
if (port.IsOpen)
{
byte[] bt = new byte[1024];
port.Write(bt, 0, bt.Length);//写1024个00H
}
注意:
1、Write函数是同步的。以上面的代码为例,1024个00H在发送完之前,Write函数是不会返回的。波特率1200,发送1024个字节大概要耗时9秒。如果这段代码在主线程里,那么这9秒内整个程序将处于假死状态:无法响应用户的键盘、鼠标输入;
2、WriteTimeout属性用于控制Write函数的最长耗时。它的默认值为System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含义为:Write函数不将所有数据写完绝不返回。可以修改此属性,如下面的代码:
if (port.IsOpen)
{
byte[] bt = new byte[1024];
port.WriteTimeout = 5000;//Write 函数最多耗时 5秒
port.Write(bt, 0, bt.Length);//写1024个00H
}
上面的代码中,设置WriteTimeout属性为5秒。所以Write写数据时最多耗时5秒,超过这个时间未发的数据将被舍弃,Write函数抛出异常TimeoutException后立即返回。
(2)写文本数据
void Write(string text)的示例:
if (port.IsOpen)
{
port.Encoding = System.Text.Encoding.GetEncoding(936);
port.Write("串行通讯");
}
首先设置代码页为936(即GBK码),Write(string text)函数根据代码页把字符串"串行通讯"转换为二进制数据,如下所示:
字符串串行通讯内码B4 AED0 D0CD A8D1 B6
然后把二进制数据B4 AE D0 D0 CD A8 D1 B6发送出去。
函数void WriteLine(string text);等价于void Write(text + NewLine)。参考下面的代码:
if (port.IsOpen)
{
port.Encoding = System.Text.Encoding.GetEncoding(936);
port.NewLine = "
";
port.WriteLine("串行通讯");
}
代码port.NewLine = "
";设置行结束符为回车(0DH)换行(0AH)。port.WriteLine("串行通讯");等价于port.Write("串行通讯"+port.NewLine);也就是port.Write("串行通讯
");
最终,发送出去的二进制数据为B4 AE D0 D0 CD A8 D1 B6 0D 0A。
5.读数据:
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(); 读取一行文本
(1)读二级制
读取 3 个字节的串口数据:
try
{
byte[] b = new byte[3];
int n = port.Read(b, 0, 3); //返回值是读取到的字节数
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
注意:
1、Read函数是同步的。以上面的代码为例,3个字节的数据被读取之前,Read函数是不会返回的。如果这段代码在主线程里,那么整个程序将处于假死状态;
2、ReadTimeout属性用于控制Read函数的最长耗时。它的默认值为System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含义为:Read函数未读取到串口数据之前是不会返回的。可以修改此属性,如下面的代码:
byte[] b = new byte[3];
port.ReadTimeout = 2000;
int n = port.Read(b, 0, 3); //返回值是读取到的字节数
上面的代码中,设置ReadTimeout属性为2秒。所以Read函数读数据时最多耗时2秒。超过这个时间未读取到数据,Read函数将抛出异常TimeoutException,然后返回。
(2) 读一个字节
int ReadByte();与int Read(byte[] buffer, int offset, int count);类似,它的特点就是只读取一个字节的串口数据。
(3) 读一个字符
int ReadChar();是读取一个字符,这个稍微复杂些。它可能读取1~3个字节的数据,然后合为一个字符。
如:port.Encoding = System.Text.Encoding.GetEncoding(936);即字符串编码为GBK。给port发送"串"的GBK编码B4 AE。ReadChar首先读取一个字节得到B4H。这是一个汉字的区码,还得读取一个字节得到位码。最终ReadChar读取的是B4 AE。ReadChar的返回值是Unicode编码,即返回前会把GBK编码B4 AE转换为Unicode编码0x4E32。
再如:port.Encoding = System.Text.Encoding.UTF8;即字符串编码为UTF8。给port发送"串"的UTF8编码E4 B8 B2。ReadChar会读取三个字节的串口数据E4 B8 B2,然后将其转换为Unicode编码0x4E32,并返回这个数值。
(4) 读全部文本
函数string ReadExisting();读取串口输入缓冲区中的所有二进制数据,然后将其转换为字符串,最后返回字符串。
注意:
1、ReadExisting会立即返回。如果输入缓冲区内没有数据,直接返回长度为零的空字符串;
2、ReadExisting读取输入缓冲区后,有时会留几个字节。参考下面的代码:
port.Encoding = System.Text.Encoding.GetEncoding(936);
string s = port.ReadExisting();
int nn = port.BytesToRead; //输入缓冲区剩余的字节数
"串"、"行"的GBK编码分别为 B4 AE和D0 D0。
首先发送 B4 AE D0 给port,运行上述代码。ReadExisting将获得B4 AE D0,"B4 AE"会被解释为"串",D0是汉字的区码,所以ReadExisting会将D0保留在输入缓冲区内。上述代码的运行结果就是:s为"串",n为1;
然后发送D0 给port,运行上述代码。ReadExisting将获得D0 D0,"D0 D0"会被解释为"行"。上述代码的运行结果就是:s为"行",n为0。
(5) 读文本到某个字符串
函数string ReadTo(string value);将在串口输入缓冲区内查找字符串value。找到了,就返回value之前的字符串,同时清除缓冲区内value及其之前的数据;未找到,就一直等待,直至超时。
(6) 读一行文本
函数string ReadLine();等价于ReadTo(NewLine)。使用前,请设置NewLine属性,指定行结束符。
(7) DataReceived事件
串口输入缓冲区获得新数据后,会以DataReceived事件通知System.IO.Ports.SerialPort对象,可以在此时读取串口数据。请参考下面两段代码:
port.ReceivedBytesThreshold = 1;
port.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived);
void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int nRead = port.BytesToRead;
if (nRead > 0)
{
byte[] data = new byte[nRead];
port.Read(data, 0, nRead);
}
}
port.ReceivedBytesThreshold = 1;的含义:串口输入缓冲区获得新数据后,将检查缓冲区内已有的字节数,大于等于ReceivedBytesThreshold就会触发DataReceived事件。这里设置为1,显然就是一旦获得新数据后,立即触发DataReceived事件。
port.DataReceived+=new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived);的含义:对于DataReceived事件,用函数port_DataReceived进行处理。
回调函数port_DataReceived用于响应DataReceived事件,通常在这个函数里读取串口数据。它的第一个参数sender就是事件的发起者。上面的代码中,sender其实就是port。也就是说:多个串口对象可以共用一个回调函数,通过sender可以区分是哪个串口对象。
回调函数是被一个多线程调用的,它不在主线程内。所以,不要在这个回调函数里直接访问界面控件。如下面的代码将将读取到的串口数据转换为字符串,然后显示在按钮Open上。红色代码处将产生异常。
void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int nRead = port.BytesToRead;
if (nRead > 0)
{
byte[] data = new byte[nRead];
port.Read(data, 0, nRead);
btnOpen.Text = System.Text.Encoding.Default.GetString(data);
}
}
可使用Invoke或BeginInvoke改进上面的红色代码:
this.Invoke(new MethodInvoker(() =>
{
btnOpen.Text = System.Text.Encoding.Default.GetString(data);
}));
BeginInvoke(new Action
6.流控制
串行通讯的双方,如果有一方反应较慢,另一方不管不顾的不停发送数据,就可能造成数据丢失。为了防止这种情况发生,需要使用流控制。
流控制也叫握手,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硬件和软件
(1) 软件流控制(XON/XOFF)
串口设备A给串口设备B发送数据。B忙不过来时(B的串口输入缓冲区快满了)会给A发送字符XOFF(一般为13H),A将暂停发送数据;B的串口输入缓冲区快空时,会给A发送字符XON(一般为11H),A将继续发送数据。
软件流控制最大的问题在于:通讯双方不能传输字符XON和XOFF。
(2)硬件流控制(RTS/CTS)
RTS/CTS流控制是硬件流控制的一种,需要按下图连线:
串口设备A给串口设备B发送数据。B忙不过来时(B的串口输入缓冲区快满了)会设置自己的RTS为低电平,这样A的CTS也变为低电平。A发现自己的CTS为低电平后,会停止发送数据;B的串口输入缓冲区快空时,会设置自己的RTS为高电平,这样A的CTS也变为高电平。A发现自己的CTS为高电平后,会继续发送数据。
相同的道理,DTR/DSR也可以做硬件流控制。
以上就是C#中SerialPort的使用教程详解的详细内容,更多关于C# SerialPort的资料请关注脚本之家其它相关文章!
您可能感兴趣的文章:
- .NET Core系列之MemoryCache 初识
- 007手机一键Root(安机网一键Root) v3.0 官方最新版 一键ROOT您的Android手机
- 12306密码被盗了怎么办?12306密码外泄解决方法
- 12个字的qq网名
- 150M迷你型无线路由器怎么设置?
- 192.168.1.1打不开怎么办?路由器192.168.1.1打不开的原因以及解决办法
- 2011年电子报合订本 电子报 编辑部 中文 PDF版 [84M]
- 2015年1月15日小米新旗舰发布会现场图文直播
- 2016.3.1vivo Xplay5新品发布会现场视频直播 优酷直播
- 2016华为P9发布会视频直播地址 4月15日华为P9国行发布会直播