C#串口通讯类

C#串口通讯类


C# SerialPort 串口通信

1 简介

随着USB的流行,串口通讯已经应用到日常生活的很多方面了,USB是一种高速的串口通讯协议,USB接口非常复杂,通常被用在需要传输大量数据数据的地方,如U盘、相机、打印机等。除了USB这种较为奢侈的串口外,在工控和嵌入式行业,大量被使用的是另一种古老的串口协议,RS-232串口。RS-232是一种非常简洁的低速串口通讯接口,它可以同时进行数据接收和发送的工作。

2 .NET 2.0对串口的支持

.NET 2.0提供了对串口通信功能的支持,有关类可以在命名空间System.IO.Ports下找到,这其中最为重要的是SerialPort类。

通过创建一个新的SerialPort 对象,我们就可以在.NET程序中控制串口通讯的全过程。

3 使用 SerialPort 设置串口属性

进行串口通讯时,需要设置一些相关参数,可以通过设置SerialPort 类的属性来进行。串口属性主要包括

 

.PortName 串口名称,COM1, COM2等。

.BaudRate 波特率,也就是串口通讯的速度,进行串口通讯的双方其波特率需要相同,如果用PC连接其他非PC系统,一般地,波特率由非PC系统决定。

.Parity 奇偶校验。可以选取枚举Parity中的值

.DataBits 数据位

.StopBits 停止位,可以选取枚举StopBits中的值

.Handshake 握手方式,也就是数据流控制方式,可以选取枚举Handshake中的值

4 打开与关闭串口

在创建一个SerialPort 对象,设置串口属性后,可以通过 Open()方法打开串口。数据读写完成后,可以通过Close()方法关闭串口。

根据经验,对于有些系统,在打开串口后,还需要将RtsEnable设置为True,这样才能读写数据,否则不能正常读写数据。

5 读写行数据

    双方通讯时,一般都需要定义通讯协议,即使最简单的通过串口发送文本聊天的程序。

通常是在当一方按下回车时,将其所数据的文本连同换行符发给另一方。在这个通讯事例中,协议桢是通过换行符界定的,每一桢数据都被换行符隔开,这样就很容易识别出通讯双发发送的信息。

 

在以上的例子中,可以用WriteLine()来发送数据,用ReadLine()来读取数据。WriteLine发送完数据后,会将换行符作为数据也发送给对方。ReadLine()读取数据时,直至遇到一个换行符,然后返回一个字符串代表一行信息。换行符可以通过SerialPort 的属性NewLine来设置。一般地,Windows将CrLn作为换行符,而在Linux下,换行符则只用一个Ln表示。

    ReadLine()方法是阻塞的,直至遇到一个换行符后返回。在读取数据时,如果一直没有遇到换行符,那么在等待ReadTimeout时间后,抛出一个TimeoutException。默认情况下,ReadTimeout为InfiniteTimeout。这样,ReadLine一直处于阻塞状态,直至有新一行数据到达。

    WriteLine()方法也是阻塞的,如果另一方不能及时接收数据,就会引起TimeoutException异常。

    由于ReadLine()和WriteLine()方法都是阻塞式的,在程序使用SerialPort 进行串口通讯时,一般应该把读写操作交由其他线程处理,避免因为阻塞而导致程序不响应。

6 读写字节或字符数据

    对于字节或字符数据,用Read()方法来读数据,该方法需要一个字节或字符数组作为参数来保存读取的数据,结果返回实际读取的字节或字符数。写数据使用Write()方法,该方法可以将字节数组、字符数据或字符串发送给另一方。

    如果通讯双方交换的数据位字节流数据,要构建一个使用的串口通讯程序,那么双方应该定义数据桢格式。通常数据桢由桢头和桢尾来界定。

    发送数据比较简单,只需要将构造好的数据用Write()方法发送出去即可。

    接收数据则比较复杂,通讯是以字节流的形式到达的,通过调用一次Read()方法并不能确保所读取的数据就是完整一桢。因此需要将每次读取的数据整合在一起,对整合后的数据进行分析,按照定义的桢格式,通过桢头和桢尾,将桢信息从字节流中抽取出来,这样才能获取有意义的信息。

    除了利用Read()方法来读数据,还可以使用ReadExisting()方法来读取数据。该方法读取当前所能读到的数据,以字符串的形式返回。

7 事件

    SerialPort 提供了DataReceived事件。当有数据进入时,该事件被触发。该事件的触发由操作系统决定,当有数据到达时,该事件在辅助线程中被触发。辅助线程的优先级比较低,因此并不能确保每个字节的数据到达时,该事件都被触发。

    在使用该事件接收数据时,最好对定义通讯协议格式,添加桢头和桢尾。在DataReceived事件中接收数据时,把数据放在数组中或字符串中缓冲起来,当接收的包含桢头和桢尾的完整数据时,在进行处理,另外,为了有效地接收数据,可以在每次读取数据后,加入System.Threading.Thread.Sleep方法进行演示。

8 其他

    用跳线使串口的第2、3针连接,可以在本地计算机上实现串口通信,所以,通过串口的第2、3针的连接可以对程序进行检测。

.BytesToRead 该属性返回能够读到的字节数。

方 法 名 称

 说  明

Close

 关闭端口连接,将 IsOpen 属性设置为False,并释放内部 Stream 对象

Open

 打开一个新的串行端口连接

Read

 从 SerialPort 输入缓冲区中读取数据字节数

ReadByte

 从 SerialPort 输入缓冲区中同步读取一个字节

ReadChar

 从 SerialPort 输入缓冲区中同步读取一个字符

ReadLine

 一直读取到输入缓冲区中的 NewLine 值

ReadTo

 一直读取到输入缓冲区中指定 value 的字符串 

Write

 已重载。将数据写入串行端口输出缓冲区

WriteLine

 将指定的字符串和 NewLine 值写入输出缓冲区

DiscardInBuffer

DiscardOutBuffer

清空接收缓冲区数据

清空输出缓冲去数据

属性说明

名  称

说  明

BaseStream

获取 SerialPort 对象的基础 Stream 对象

BaudRate

获取或设置串行波特率

BreakState

获取或设置中断信号状态

BytesToRead

获取接收缓冲区中数据的字节数

BytesToWrite

获取发送缓冲区中数据的字节数

CDHolding

获取端口的载波检测行的状态

CtsHolding

获取“可以发送”行的状态

DataBits

获取或设置每个字节的标准数据位长度

DiscardNull

获取或设置一个值,该值指示 Null 字节在端口和接收缓冲区之间传输时是否被忽略

DsrHolding

获取数据设置就绪 (DSR) 信号的状态

DtrEnable

获取或设置一个值,该值在串行通信过程中启用数据终端就绪 (DTR) 信号

Encoding

获取或设置传输前后文本转换的字节编码

Handshake

获取或设置串行端口数据传输的握手协议

IsOpen

获取一个值,该值指示 SerialPort 对象的打开或关闭状态

NewLine

获取或设置用于解释 ReadLine( )和WriteLine( )方法调用结束的值

Parity

获取或设置奇偶校验检查协议

ParityReplace

获取或设置一个字节,该字节在发生奇偶校验错误时替换数据流中的无效字节

PortName

获取或设置通信端口,包括但不限于所有可用的 COM 端口

ReadBufferSize

获取或设置 SerialPort 输入缓冲区的大小 

ReadTimeout

获取或设置读取操作未完成时发生超时之前的毫秒数

ReceivedBytesThreshold

获取或设置 DataReceived 事件发生前内部输入缓冲区中的字节数

RtsEnable

获取或设置一个值,该值指示在串行通信中是否启用请求发送 (RTS) 信号

StopBits

获取或设置每个字节的标准停止位数

WriteBufferSize

获取或设置串行端口输出缓冲区的大小 

WriteTimeout

获取或设置写入操作未完成时发生超时之前的毫秒数

注意:C#中SerialPort类对串口供电需要设置DtrEnable和RtsEnable两个属性在开发中有些串口设备需要串口供电,使用C#中的SerialPort类默认情况下不会出发DataReceived函数,但使用超级终端却可以接收到数据,这是因为SerialPort类的DtrEnable和RtsEnable两个属性默认是false,设为true即可接收数据了,如下:

this.m_SerialPort.DtrEnable = true; //启用控制终端就续信号

this.m_SerialPort.RtsEnable = true; //启用请求发送信号














using System;
using System.Runtime.InteropServices;

namespace JustinIO {
    class CommPort {

       public string PortNum;
       public int BaudRate;
       public byte ByteSize;
       public byte Parity; // 0-4=no,odd,even,mark,space
       public byte StopBits; // 0,1,2 = 1, 1.5, 2
       public int ReadTimeout;
      
       //comm port win32 file handle
       private int hComm = -1;
      
       public bool Opened = false;
       
       //win32 api constants
         private const uint GENERIC_READ = 0x80000000;
         private const uint GENERIC_WRITE = 0x40000000;
         private const int OPEN_EXISTING = 3;      
         private const int INVALID_HANDLE_VALUE = -1;
      
       [StructLayout(LayoutKind.Sequential)]
       public struct DCB {
          //taken from c struct in platform sdk
          public int DCBlength;            // sizeof(DCB)
          public int BaudRate;             // 指定当前波特率 current baud rate
          // these are the c struct bit fields, bit twiddle flag to set
          public int fBinary;           // 指定是否允许二进制模式,在windows95中必须主TRUE binary mode, no EOF check
          public int fParity;           // 指定是否允许奇偶校验 enable parity checking
          public int fOutxCtsFlow;       // 指定CTS是否用于检测发送控制,当为TRUE是CTS为OFF,发送将被挂起。 CTS output flow control
          public int fOutxDsrFlow;       // 指定CTS是否用于检测发送控制 DSR output flow control
          public int fDtrControl;        // DTR_CONTROL_DISABLE值将DTR置为OFF, DTR_CONTROL_ENABLE值将DTR置为ON, DTR_CONTROL_HANDSHAKE允许DTR"握手" DTR flow control type
          public int fDsrSensitivity;    // 当该值为TRUE时DSR为OFF时接收的字节被忽略 DSR sensitivity
          public int fTXContinueOnXoff; // 指定当接收缓冲区已满,并且驱动程序已经发送出XoffChar字符时发送是否停止。TRUE时,在接收缓冲区接收到缓冲区已满的字节XoffLim且驱动程序已经发送出XoffChar字符中止接收字节之后,发送继续进行。 FALSE时,在接收缓冲区接收到代表缓冲区已空的字节XonChar且驱动程序已经发送出恢复发送的XonChar之后,发送继续进行。XOFF continues Tx
          public int fOutX;           // TRUE时,接收到XoffChar之后便停止发送接收到XonChar之后将重新开始 XON/XOFF out flow control
          public int fInX;            // TRUE时,接收缓冲区接收到代表缓冲区满的XoffLim之后,XoffChar发送出去接收缓冲区接收到代表缓冲区空的XonLim之后,XonChar发送出去 XON/XOFF in flow control
          public int fErrorChar;      // 该值为TRUE且fParity为TRUE时,用ErrorChar 成员指定的字符代替奇偶校验错误的接收字符 enable error replacement
          public int fNull;           // eTRUE时,接收时去掉空(0值)字节 enable null stripping
          public int fRtsControl;      // RTS flow control
                                  /*RTS_CONTROL_DISABLE时,RTS置为OFF
                                    RTS_CONTROL_ENABLE时, RTS置为ON
                                 RTS_CONTROL_HANDSHAKE时,
                                 当接收缓冲区小于半满时RTS为ON
                                     当接收缓冲区超过四分之三满时RTS为OFF
                                 RTS_CONTROL_TOGGLE时,
                                 当接收缓冲区仍有剩余字节时RTS为ON ,否则缺省为OFF*/

          public int fAbortOnError;    // TRUE时,有错误发生时中止读和写操作 abort on error
          public int fDummy2;         // 未使用 reserved
         
          public uint flags;
          public ushort wReserved;           // 未使用,必须为0 not currently used
          public ushort XonLim;              // 指定在XON字符发送这前接收缓冲区中可允许的最小字节数 transmit XON threshold
          public ushort XoffLim;             // 指定在XOFF字符发送这前接收缓冲区中可允许的最小字节数 transmit XOFF threshold
          public byte ByteSize;            // 指定端口当前使用的数据位    number of bits/byte, 4-8
          public byte Parity;              // 指定端口当前使用的奇偶校验方法,可能为:EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY   0-4=no,odd,even,mark,space
          public byte StopBits;            // 指定端口当前使用的停止位数,可能为:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS   0,1,2 = 1, 1.5, 2
          public char XonChar;             // 指定用于发送和接收字符XON的值 Tx and Rx XON character
          public char XoffChar;            // 指定用于发送和接收字符XOFF值 Tx and Rx XOFF character
          public char ErrorChar;           // 本字符用来代替接收到的奇偶校验发生错误时的值 error replacement character
          public char EofChar;             // 当没有使用二进制模式时,本字符可用来指示数据的结束 end of input character
          public char EvtChar;             // 当接收到此字符时,会产生一个事件 received event character
          public ushort wReserved1;          // 未使用 reserved; do not use
       }

       [StructLayout(LayoutKind.Sequential)]
       private struct COMMTIMEOUTS {  
         public int ReadIntervalTimeout;
         public int ReadTotalTimeoutMultiplier;
         public int ReadTotalTimeoutConstant;
         public int WriteTotalTimeoutMultiplier;
         public int WriteTotalTimeoutConstant;
       }    

       [StructLayout(LayoutKind.Sequential)]   
       private struct OVERLAPPED {
           public int   Internal;
           public int   InternalHigh;
           public int   Offset;
           public int   OffsetHigh;
           public int hEvent;
       }  
      
       [DllImport("kernel32.dll")]
       private static extern int CreateFile(
         string lpFileName,                          // 要打开的串口名称
         uint dwDesiredAccess,                       // 指定串口的访问方式,一般设置为可读可写方式
         int dwShareMode,                           // 指定串口的共享模式,串口不能共享,所以设置为0
         int lpSecurityAttributes, // 设置串口的安全属性,WIN9X下不支持,应设为NULL
         int dwCreationDisposition,                 // 对于串口通信,创建方式只能为OPEN_EXISTING
         int dwFlagsAndAttributes,                  // 指定串口属性与标志,设置为FILE_FLAG_OVERLAPPED(重叠I/O操作),指定串口以异步方式通信
         int hTemplateFile                         // 对于串口通信必须设置为NULL
       );
       [DllImport("kernel32.dll")]
       private static extern bool GetCommState(
         int hFile,   //通信设备句柄
         ref DCB lpDCB     // 设备控制块DCB
       );   
       [DllImport("kernel32.dll")]
       private static extern bool BuildCommDCB(
         string lpDef,   // 设备控制字符串
         ref DCB lpDCB      // 设备控制块
       );
       [DllImport("kernel32.dll")]
       private static extern bool SetCommState(
         int hFile,   // 通信设备句柄
         ref DCB lpDCB     // 设备控制块
       );
       [DllImport("kernel32.dll")]
       private static extern bool GetCommTimeouts(
         int hFile,                   // 通信设备句柄 handle to comm device
         ref COMMTIMEOUTS lpCommTimeouts   // 超时时间 time-out values
       );   
       [DllImport("kernel32.dll")]   
       private static extern bool SetCommTimeouts(
         int hFile,                   // 通信设备句柄 handle to comm device
         ref COMMTIMEOUTS lpCommTimeouts   // 超时时间 time-out values
       );
       [DllImport("kernel32.dll")]
       private static extern bool ReadFile(
         int hFile,                 // 通信设备句柄 handle to file
         byte[] lpBuffer,              // 数据缓冲区 data buffer
         int nNumberOfBytesToRead,   // 多少字节等待读取 number of bytes to read
         ref int lpNumberOfBytesRead, // 读取多少字节 number of bytes read
         ref OVERLAPPED lpOverlapped     // 溢出缓冲区 overlapped buffer
       );
       [DllImport("kernel32.dll")]   
       private static extern bool WriteFile(
         int hFile,                     // 通信设备句柄 handle to file
         byte[] lpBuffer,                 // 数据缓冲区 data buffer
         int nNumberOfBytesToWrite,      // 多少字节等待写入 number of bytes to write
         ref int lpNumberOfBytesWritten,   // 已经写入多少字节 number of bytes written
         ref OVERLAPPED lpOverlapped         // 溢出缓冲区 overlapped buffer
       );
       [DllImport("kernel32.dll")]
       private static extern bool CloseHandle(
         int hObject    // handle to object
       );
       [DllImport("kernel32.dll")]
       private static extern uint GetLastError();
      
       public void Open()
       {
      
             DCB dcbCommPort = new DCB();
             COMMTIMEOUTS ctoCommPort = new COMMTIMEOUTS();   
           
           // 打开串口 OPEN THE COMM PORT.
             hComm = CreateFile(PortNum ,GENERIC_READ | GENERIC_WRITE,0, 0,OPEN_EXISTING,0,0);
           // 如果串口没有打开,就打开 IF THE PORT CANNOT BE OPENED, BAIL OUT.
          if(hComm == INVALID_HANDLE_VALUE)
          {
               throw(new ApplicationException("非法操作,不能打开串口!"));
          }
      
          // 设置通信超时时间 SET THE COMM TIMEOUTS.
          GetCommTimeouts(hComm,ref ctoCommPort);
          ctoCommPort.ReadTotalTimeoutConstant = ReadTimeout;
          ctoCommPort.ReadTotalTimeoutMultiplier = 0;
          ctoCommPort.WriteTotalTimeoutMultiplier = 0;
          ctoCommPort.WriteTotalTimeoutConstant = 0;  
          SetCommTimeouts(hComm,ref ctoCommPort);
      
          // 设置串口 SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS.
          GetCommState(hComm, ref dcbCommPort);
          dcbCommPort.BaudRate=BaudRate;
          dcbCommPort.flags=0;
          //dcb.fBinary=1;
          dcbCommPort.flags|=1;
          if (Parity>0)
          {
             //dcb.fParity=1
             dcbCommPort.flags|=2;
          }
          dcbCommPort.Parity=Parity;
          dcbCommPort.ByteSize=ByteSize;
          dcbCommPort.StopBits=StopBits;
          if (!SetCommState(hComm, ref dcbCommPort))
          {
           //uint ErrorNum=GetLastError();
            throw(new ApplicationException("非法操作,不能打开串口!"));
          }
         //unremark to see if setting took correctly
         //DCB dcbCommPort2 = new DCB();
         //GetCommState(hComm, ref dcbCommPort2);
          Opened = true;
       }
      
       public void Close() {
          if (hComm!=INVALID_HANDLE_VALUE) {
             CloseHandle(hComm);
          }
       }
      
       public byte[] Read(int NumBytes) {
          byte[] BufBytes;
          byte[] OutBytes;
          BufBytes = new byte[NumBytes];
          if (hComm!=INVALID_HANDLE_VALUE) {
             OVERLAPPED ovlCommPort = new OVERLAPPED();
             int BytesRead=0;
             ReadFile(hComm,BufBytes,NumBytes,ref BytesRead,ref ovlCommPort);
             OutBytes = new byte[BytesRead];
             Array.Copy(BufBytes,OutBytes,BytesRead);
          }
          else {
             throw(new ApplicationException("串口未打开!"));
          }
          return OutBytes;
       }
      
       public void Write(byte[] WriteBytes) {
          if (hComm!=INVALID_HANDLE_VALUE) {
             OVERLAPPED ovlCommPort = new OVERLAPPED();
             int BytesWritten = 0;
             WriteFile(hComm,WriteBytes,WriteBytes.Length,ref BytesWritten,ref ovlCommPort);
          }
          else {
             throw(new ApplicationException("串口未打开!"));
          }      
       }
    }

    class HexCon {
    // 把十六进制字符串转换成字节型和把字节型转换成十六进制字符串 converter hex string to byte and byte to hex string
       public static string ByteToString(byte[] InBytes) {
          string StringOut="";
          foreach (byte InByte in InBytes) {
             StringOut=StringOut + String.Format("{0:X2} ",InByte);
          }
          return StringOut;
       }
       public static byte[] StringToByte(string InString) {
          string[] ByteStrings;
          ByteStrings = InString.Split(" ".ToCharArray());
          byte[] ByteOut;
          ByteOut = new byte[ByteStrings.Length-1];
          for (int i = 0;i==ByteStrings.Length-1;i++) {
             ByteOut[i] = Convert.ToByte(("0x" + ByteStrings[i]));
          }
          return ByteOut;
       }
    }

你可能感兴趣的:(C#串口通讯类)