这是我3年前的一个例子,最近翻出来回忆一下。
串口是计算机上一种非常通用设备通信的协议。大多数计算机包含两个基于RS232的串口,现在配电脑好像只有一个。串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。串口通信在工控领域用途很广。
串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总常不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。
典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:
a,波特率:这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
b,数据位:这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
c,停止位:用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
d,奇偶校验位:在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
串口通信还有一个参数是单工、半双工和双工。
如果在通信过程的任意时刻,信息只能由一方A传到另一方B,则称为单工。
如果在任意时刻,信息既可由A传到B,又能由B传A,但只能由一个方向上的传输存在,称为半双工传输。
如果在任意时刻,线路上存在A到B和B到A的双向信号传输,则称为全双工。
电话线就是二线全双工信道。 由于采用了回波抵消技术,双向的传输信号不致混淆不清。双工信道有时也将收、发信道分开,采用分离的线路或频带传输相反方向的信号,如回线传输。
在调试时可以用串口助手和windows的超级终端,不要带电插拔串口,容易烧毁。
在VB.NET中提供了IO.Ports类,是我们的编程变得很简单,下面是我的例子:
Form1.vb
- ImportsSystem.IO.Ports
- PublicClassForm1
- DimWithEventsRS232AsSerialPort
- DelegateSubSetTextCallback(ByValInputStringAsString)'声明一个代理
- PrivateSubForm1_Load(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesMyBase.Load
- ForEachspAsStringInSerialPort.GetPortNames()
- cmbCom.Items.Add(sp)
- Next
- cmbCom.Sorted=True
- cmbCom.SelectedIndex=0
- EndSub
- PrivateSubbtnStart_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnStart.Click
- DimmBaudRateAsInteger
- DimmParityAsIO.Ports.Parity
- DimmDataBitAsInteger
- DimmStopBitAsIO.Ports.StopBits
- DimmPortNameAsString
- mPortName=cmbCom.SelectedItem.ToString
- mBaudRate=9600
- mParity=Parity.None
- mDataBit=8
- mStopBit=StopBits.One
- RS232=NewIO.Ports.SerialPort(mPortName,mBaudRate,mParity,mDataBit,mStopBit)
- IfNotRS232.IsOpenThen
- RS232.Open()
- btnSend.Enabled=True
- RS232.ReceivedBytesThreshold=1'设置引发事件的门限值
- Else
- MsgBox("通讯端口打开错误!",MsgBoxStyle.Critical)
- EndIf
- EndSub
- PrivateSubRS232_DataReceived(ByValsenderAsObject,ByValeAsSystem.IO.Ports.SerialDataReceivedEventArgs)HandlesRS232.DataReceived
- DimInByte()AsByte,ReadCountAsInteger,strReadAsString
- IfRS232.BytesToRead<=0ThenExitSub
- ReDimInByte(RS232.BytesToRead-1)
- ReadCount=RS232.Read(InByte,RS232.BytesToRead)
- strRead=""
- IfReadCount=0Then
- ExitSub
- Else
- ForEachbDataAsByteInInByte
- strRead+=bData.ToString&vbCrLf'若有数据则加到接收文本框
- DisplayText(strRead)
- Next
- EndIf
- EndSub
- '*************************************************
- '代理子程序
- '处理上述通信端口的接收事件
- '由于欲将数据显示到接收文本框中,因此必须检查
- '是否由另外得Thread所调用的,若是,则必须先
- '建立代理对象
- 'Invoke用于在拥有控件基础窗口控制代码的线程上
- '运行代理
- '*************************************************
- PrivateSubDisplayText(ByValcomDataAsString)
- '如果调用txtReceive的另外的线程,返回true
- IfMe.txtReceive.InvokerequiredThen
- '利用代理类型建立对象,并指定代理的函数
- DimdAsNewSetTextCallback(AddressOfDisplayText)
- Me.Invoke(d,NewObject(){comData})'以指定的自变量列表调用函数
- Else'相同的线程
- 'showstring(comData)'将收到的数据填入接收文本框中
- Me.txtReceive.Text+=comData
- EndIf
- EndSub
- PrivateSubbtnClose_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnClose.Click
- IfRS232IsNothingOrElseNotRS232.IsOpenThen'尚未打开
- MsgBox("通讯端口尚未打开",MsgBoxStyle.CriticalOrMsgBoxStyle.OkCancel)
- Else
- RS232.Close()
- btnStart.Enabled=False
- btnClose.Enabled=False
- RS232=Nothing
- EndIf
- EndSub
- PrivateSubbtnEnd_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnEnd.Click
- IfNotRS232IsNothingThen
- IfRS232.IsOpenThenRS232.Close()
- EndIf
- End
- EndSub
- PrivateSubbtnSend_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnSend.Click
- DimbDataOut(0)AsByte
- Try
- bDataOut(0)=CType(Me.txtSend.Text,Byte)'将类型转换为字节
- RS232.Write(bDataOut,1)
- CatchexAsException
- MessageBox.Show("输入数值错误:"+ex.ToString,"错误通知:",MessageBoxButtons.OK,MessageBoxIcon.Exclamation)
- EndTry
- EndSub
- PrivateSubcmbCom_KeyPress(ByValsenderAsObject,ByValeAsSystem.Windows.Forms.KeyPressEventArgs)HandlescmbCom.KeyPress
- e.KeyChar=ChrW(0)'禁止用户在其中输入任何文字
- EndSub
- PrivateSubButton1_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesButton1.Click
- Me.Hide()
- Form2.Show()
- EndSub
- EndClass
- '**************************************************************
- '时间:2008-11-08
- '作者:lincyang
- '实际应用中,串行通信的数据可能一次发送大量的数据,
- '发送之前就必须将数据先编码,
- '将其编成我们需要的字节数组数据,
- '才能将这些数据以字节的方式发送出去
- '目前操作系统使用的字符数据是Unicode:所有的字符均使用
- '两个字节来表示一个字符
- '**************************************************************
- ImportsSystem.IO.Ports
- ImportsSystem.Text
- PublicClassForm2
- DimRS232AsSerialPort
- PrivateSubForm2_Load(ByValsenderAsSystem.Object,mStopBit)
- IfNotRS232.IsOpenThen
- RS232.Open()
- btnSend.Enabled=True
- Timer1.Interval=100
- Timer1.Enabled=True
- Else
- MsgBox("通讯端口打开错误!",MsgBoxStyle.Critical)
- EndIf
- EndSub
- PrivateSubTimer1_Tick(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesTimer1.Tick
- DimInByte()AsByte,ReadCountAsInteger
- IfRS232.BytesToRead<=0ThenExitSub
- ReDimInByte(RS232.BytesToRead-1)
- ReadCount=RS232.Read(InByte,RS232.BytesToRead)
- IfReadCount=0Then
- ExitSub
- Else
- ForEachbDataAsByteInInByte
- Me.txtReceive.Text+=bData.ToString&vbCrLf'若有数据则加到接收文本框
- Next
- EndIf
- EndSub
- PrivateSubbtnClose_Click(ByValsenderAsSystem.Object,MsgBoxStyle.CriticalOrMsgBoxStyle.OkCancel)
- Else
- RS232.Close()
- btnStart.Enabled=False
- btnClose.Enabled=False
- Timer1.Enabled=False
- RS232=Nothing
- EndIf
- EndSub
- PrivateSubbtnSend_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnSend.Click
- DimbDataOut()AsByte,BufAsString
- DimiSentCountAsInteger
- DimEncode1AsEncoding=Encoding.ASCII'声明编码对象,使用ASCII
- Try
- Buf=txtSend.Text.Trim()
- bDataOut=Encode1.GetBytes(Buf)'将字符串转换为字节数组
- iSentCount=bDataOut.GetLength(0)'发送总字节数
- '显示出总字节数
- lblSentCount.Text="总传输量:"&iSentCount.ToString&"字节"
- RS232.Write(bDataOut,iSentCount)
- CatchexAsException
- MessageBox.Show("输入数值错误:"+ex.ToString,"错误通知",MessageBoxIcon.Exclamation)
- EndTry
- EndSub
- PrivateSubbtnEnd_Click(ByValsenderAsSystem.Object,ByValeAsSystem.EventArgs)HandlesbtnEnd.Click
- IfNotRS232IsNothingThen
- IfRS232.IsOpenThenRS232.Close()
- EndIf
- End
- EndSub
- PrivateSubcmbCom_KeyPress(ByValsenderAsObject,ByValeAsSystem.Windows.Forms.KeyPressEventArgs)HandlescmbCom.KeyPress
- e.KeyChar=ChrW(0)'禁止用户在其中输入任何文字
- EndSub
- EndClass
一不小心看到一高手写的C#串中操作系列的文章,很不错,可惜我辈不懂C#,于是顺便改成用VB.NET2010
其中几乎都有注解。
顺便说明,再发送16进制不是很完善,有懂的兄弟,说明一下
005.
Imports
System.Drawing
006.
Imports
System.Linq