VB用MSCOMM控件与单片机经RS232串口实现通讯。一方为主发,一方为应答。规定好通讯格式。VB程序利用MSCOMM的ONCOMM事件进行接收。
Output
属性示例
下面的例子说明如何将用户键入的每一个字符送到串行端口:
Private
Sub
Form_KeyPress
(KeyAscii
As
Integer)
Dim
Buffer
as
Variant
'
设置并打开串口
MSComm1CommPort
=
1
MSComm1PortOpen
=
True
Buffer
=
Chr$(KeyAscii)
MSComm1Output
=
Buffer
End
Sub
同样在串口打开时,给MSCOMM的属性Output赋值,就向串口写入数据
串口传送数据实质是传送ASCII码,对于数值数据的处理:通常以16位二进制数字传递,而小数点不传递,仅乘一定倍率来实现同时16位二进制数字按两个8位(ASCII码值)输送,上位机通过程序代码处理成10进制数
串口通信中数制转换必须相当重视。
详细请参阅MSDN和人民邮电出版社的"UISUAL
BASIC
串口通讯工程开发实例导航"一书其中有详细介绍重点第一章“串口调试精灵”。其代码修改后可用作通讯模块。
通俗的说,中断方式下单片机可以放心干其他的活,一旦串口有一个字节有效接收数据或者发送一个字节完毕,串口会立即通知CPU进行后续处理。特点是可靠、及时、占用CPU资源少。
查询方式就是CPU间断地(连续的就不用指望干其他活了)查询串口的接受发送标志,决定是否进行后续处理,在查询的间隙里做其他工作。如果串口波特率较高,CPU在间隙里的任务比较重,工作时间比较长,就会出现丢失接收数据现象,因为串口接收缓冲器只有一个字节,不及时读取就会被覆盖。
一、QtSerialPort简介
1、串口通信基础
目前使用最广泛的串口为DB9接口,适用于较近距离的通信。一般小于10米。DB9接口有9个针脚。
串口通信的主要参数如下:
A、波特率:衡量通信速度的参数,表示每秒钟传送的bit的个数。例如9600波特表示每秒钟发送9600个bit。
B、数据位:衡量通信中实际数据位的参数,当计算机发送一个信息包,实际包含的有效数据位个数。
C、停止位:用于表示单个包的最后一位。典型的值为1和2位。
D、奇偶校验位:串口通信中一种检错方式。常用的检错方式有:偶、奇校验。
2、QtSerialPort模块简介
QtSerialPort模块是QT5中附加模块的一个模块,为硬件和虚拟的串口提供统一的接口。
串口由于其简单和可靠,目前在像嵌入式系统、机器人等工业中依旧用得很多。使用QtSerialPort模块,开发者可以大大缩短开发串口相关的应用程的周期。
Qt SerialPort提供了基本的功能,包括配置、I/O *** 作、获取和设置RS-232引脚的信号。
Qt SerialPort模块暂不支持以下特性:
A、终端的特性,例如回显,控制CR/LF等等
B、文本模式
C、读或写 *** 作的超时和延时配置
D、当RS-232引脚信号变化通知
#include <QtSerialPort/QtSerialPort>
要链接QtSerialPort模块,需要在pro文件中添加如下内容:
QT += serialport
二、QSerialPort
1、QSerialPort简介
QSerialPort提供了访问串口的接口函数。使用辅助类QSerialPortInfo可以获取可用的串口信息。将QSerialPortInfo辅助类对象做为参数,使用setPort()或setPortName()函数可以设置要访问的串口设备。
设置好端口后,可以使用open()函数以只读、只写或读写的模式打开使用。
注意,串口使用独占方式打开。
使用close()函数关闭串口并且取消IO *** 作。
串口成功打开后,QSerialPort会尝试确定串口的当前配置并初始化。可以使用setBaudRate()、setDataBits()、setParity()、setStopBits()和setFlowControl()函数重新配置端口设置。
有一对名为QSerialPort::dataTerminalReady、QSerialPort::requestToSend的属性
QSerialPort提供了中止正在调用线程直到信号触发的一系列函数。这些函数用于阻塞串口。
waitForReadyRead():阻塞调用,直到有新的数据可读
waitForBytesWritten():阻塞调用,直到数据以及写入串口
阻塞串口编程与非阻塞串口编程完全不同。阻塞串口不会要求时间循环并且通常会简化代码。然而,在GUI程序中,为了避免冻结用户界面,阻塞串口编程只能用于非GUI线程。
QSerialPort也能使用QTextStream和QDataStream的流 *** 作符。在试图使用流 *** 作符>>读时,需要确保有足够可用的数据。
2、QSerialPort成员函数
QSerialPort::QSerialPort(QObject parent = Q_NULLPTR)
QSerialPort::QSerialPort(const QString &name, QObject parent = Q_NULLPTR)
QSerialPort::QSerialPort(const QSerialPortInfo &serialPortInfo, QObject parent = Q_NULLPTR)
[virtual] bool QSerialPort::atEnd() const
[signal] void QSerialPort::baudRateChanged(qint32 baudRate, QSerialPort::Directions directions)
[virtual] qint64 QSerialPort::bytesAvailable() const
[virtual] qint64 QSerialPort::bytesToWrite() const
[virtual] void QSerialPort::close()
void QSerialPort::setPort(const QSerialPortInfo &serialPortInfo)
void QSerialPort::setPortName(const QString &name)
三、QSerialPortInfo
1、QSerialPortInfo简介
QSerialPortInfo类提供已有串口设备的信息。使用QSerialPortInfo类的静态成员函数生成QSerialPortInfo对象的链表。链表中的每个QSerialPortInfo对象代表一个串口,每个串口可以使用端口名、系统定位、描述、制造商查询。QSerialPortInfo类对象也可以用做QSerialPort类的setPort()成员函数的参数。
2、QSerialPortInfo成员函数
QSerialPortInfo::QSerialPortInfo(const QSerialPort &port)
QSerialPortInfo::QSerialPortInfo(const QString &name)
QSerialPortInfo::QSerialPortInfo(const QSerialPortInfo &other)
[static] QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
QString QSerialPortInfo::description() const
bool QSerialPortInfo::hasProductIdentifier() const
bool QSerialPortInfo::hasVendorIdentifier() const
bool QSerialPortInfo::isBusy() const
QString QSerialPortInfo::manufacturer() const
QString QSerialPortInfo::portName() const
quint16 QSerialPortInfo::productIdentifier() const
QString QSerialPortInfo::serialNumber() const
[static] QList<qint32> QSerialPortInfo::standardBaudRates()
void QSerialPortInfo::swap(QSerialPortInfo &other)
QString QSerialPortInfo::systemLocation() const
quint16 QSerialPortInfo::vendorIdentifier() const
3、QSerialPortInfo显示串口信息实例
1 mscommvbx通信控件描述
mscommvbx通信控件可直接从vb的toolbox中加入窗体form,即可用其进行通信。若toolbox中无此控件,则用tools的custom controls 将mscommvbx从windows的system子目录中加入vb的toolbox中。
11 通信方式
mscommvbx有2种不同的方式来处理和解决各类通信软件的开发和设计问题
1、事件驱动。它与c/c++写windows 软件时的窗口回调函数类似,是1种功能强大的处理问题的方法。在实际工作中,往往要处理许多通信中的相关事件,例如:当线路数据到达本端或cd线和rts信号线状态发生变化时,要求我们使用相应的事件来跟踪和处理,该控件是使用oncomm事件来实现的,它也包括检测和处理通信错误等方面的问题,commevent 值返回最近的通信事件或错误的数字代码。通信控件详细的错误和事件举例有:
mscomm-er-break 收到1个break signal
mscomm-er-cdto cd 信号超时
……
mscomm-ev-cd cd信号改变
……
2、查询方式。由程序设计者负责读取commevent的值并处理所发生的错误或事件。通常简单的应用程序设计可采用这种办法。
12 通信控件的属性
利用通信控件编制通信程序,关键是准确理解设置通信控件的属性。mscommvbx提供了27个关于通信控件方面的属性,例如:
commport:设置或返回通信口编号。
settings:设置或返回以字符串形式出现的数据通信格式:波特率、校验、数据位和停 止位。
portopen:设置或返回通信口状态(包括打开和关闭1个通信口)
……
3、 实例
本程序应用背景为dcc95型静电除尘器自动监控系统软件,解决1个pc工控机(主站)与32个单片机(子站)之间的通信问题。主站与子站之间这总线式网络结构,采用rs-485通信标准,以问答方式进行数据通信。由于32个子站与主站发送通信命令(下行命令),主站在接收子站发回的相应回答命令(上行命令)后继续发送下行命令的通信形式。根据系统功能的要求,主站需发送2种类型的命令:(1)同期命令,它由定时器触发引起,每隔ls周期发送1次;(2)非周期性命令,它由 *** 作者按动相应命令按钮引起,非周期性发送。自动监控系统软件安装在主站上,而通信程序作为自动监控系统软件的一部分也安装在主站上。
本文仅列出调试通信程序时进行试验用的基本演示程序清单。试验时,用1台pc机作为主站,另一台pc机模拟32个子站的工作,两台pc机之间采用rs232c串口通信。往主站的通信演示程序窗体(form)中加入1个通信控件、2个定时器控件和1个命令按钮控件,通信控件(mscomm1)用于访问串口,发送和接收数据;periodic定时器控件(periodic)用于控制每秒由主站向各子站发送周期性命令;命令按钮控件(nonperiodic-command)与nonperiodic定时器控件(nonperiodic)用于发送非周期性命令。数据传送采用事件驱动的通信方式,根据不同的发送命令设置rtreshlod属性,从而引起oncomm事件以接收数据。
21 窗体各控件初始化程序
设置通信串口工作参数,设置periodic定时器的在断间隔为ls, nonperiodic定时器的中断间隔为05s。
sub form-load ()
mscomm1commport=2 ’选用com2串行口
mscomm1settings="9600,n8,1" ’波特率9600,无奇偶校验位,8位数据位1位停止位
mscomm1inputlen=0 ’input将读取接收缓冲区的全部内容
mscomm1inbuffersize=1024 ’设置接收缓冲区的字节长度
mscomm1portopen=true ’打开通信口
mscomm1inbuffercount=0 ’清除发送缓冲区数据
mscomm1outbuffercount=0 ’清除接收缓冲区数据
periodicinteval=100 ’设置ls定时间隔,使遥测命令每隔ls发送1次
nonperiodicinteval=500 ’设置05s定时间隔,查询命令按钮是否处于激活状态以确定是否发送周期性命令
command-pressed=false ’命令按钮为未激活状态
during- periodic=false ’周期性命令数据传输尚未开始
during- nonperiodic=false ’非周期性命令数据传输尚未开始
end sub
22 非周期性命令发送程序
根据命令按钮状态及周期性命令数据传输状态,在nonperiodic定时器的中断程序中发送非周期性命令。
sub nonperiodic-command-click ()
command-pressed=true ’命令按钮激活
end sub
sub nonperiodic-timer ()
if during- periodic=true or command-pressed=false
then exit sub ’若周期性命令数据传输尚未结束或命令按钮处于激活状态,则退出发送非周期性命令程序。
command-pressed=false ’命令按钮恢复为未激活状态
call senddata (nonperiodic-command) ’发送非周期性命令
mscomm1rthreshold=r-nonperiodic-byte’发送非周期性命令后,设置rthreshold属性,使主站接收所设定的字节数后引发oncomm事件
end sub
23 periodic定时器程序
在periodic定时器的中断程序中发送周期性命令:
sub periodic-timer ()
if during- nonperiodic=true then exit sub ’若非周期性命令数据传输尚未结束,则退出发送非周期性命令程序。
during-periodic=true ’设置周期性命令数据传输状态为正在进行中
call senddata (periodic-command) ’发送周期性命令
mscomm1rthreshold=r-periodic-byte ’发送周期性命令后,主站接收r-remot- edata-byte个字节,可引发oncomm 事件
end sub
24 oncomm事件程序
根据rthreshold属性设置值,当接收缓存区内接收到相应字节的字符时,引发oncomm事件,在中断程序中接收数据。
sub mscomm1-oncomm ()
select case mscomm1commevent ’在此可插入处理各种不同错误或事件的代码
case mscomm-ev-receive
receivestring$=mscomm1input
select case mscomm1rthreshold
case r-periodic-byte ’周期性命令的应答数据
call disposedata(periodic-command) ’处理接收数据
during periodic=false ’设置周期性命令数据传输状态为结束
case r-nonperiodic-byte ’非周期性命令的应答数据
call disposedata(nonperiodic-command) ’处理接收数据
during-nonperiodic=false ’设置非周期性命令数据传输状态为结束
end select
end select
end sub
随着vb版本的不断升级,vb将成为最快速、易用、强劲的应用开发工具,是企业级客户/服务器应用软件开发的首选工具之一。
1、 mscomm控件安装。
Mscomm控件默认存在于delphi的ActiveX面板上,如果不存在,需要先导入该控件,步骤 :通过菜单 component---Import Activex component 打开Import Acitvex对话框。如下图:
如果列表中找不到该Microsoft Comm Control,那么点Add按钮找到Mscomm32ocx,最后点Install即可。
2、 需要了解的属性:
需要了解的几个属性:
CommPort:设置通信端口号,用该串口与外界通信。
Setting:以字符串的形式设置数据传输速率、奇偶校验、数据比特、停止比特;
PortOpen:设置并返回通信端口的状态,用来可以打开和关闭端口;
Input:从接收缓冲区返回和删除字符;
Output:向传输缓冲区定一个字符串)
RThreshold:设置接收几个字符触发控件响应函数。
CTSHolding:该属性确定是否可通过查询CTS线的状态发送数据。CTS是调制解调器发送到相连计算机的信号,指示传输可以进行。该属性在设计时无效,在运行时为只读。
如果CTS线为低电平(CTSHolding = FALSE)并且超时时,MSComm控件设置CommEvent属性为comEventCTSTO(Clear To Send Timeout)并产生OnComm事伯。CTS线用于RTS/CTS硬件握手。如果需要确定CTS线的状态,CTSHolding属性给出一种手工查询方法。
SThreshold:设置发送缓冲区中有几个字符时候触发oncomm
InputMode:用于设置或者返回传输数据的类型。
ComInputModeText(缺省) 0 通过Input属性以文本方式取回数据
ComInputModeBinary 1 通过Input属性以二进制方式取回数据
InputLen:该属性用于设置并返回Input属性从接收缓冲区读取的字符数。
InBuffersize:设置输入缓冲区的大小,默认值为1024字节。
InBufferCount: InBufferCount属性用于返回输入缓冲区内的等待读取的字节个数,可以通过该属性值为0来清除接收缓冲区。
Output:void SetOutput(const VARIANT & newValue);
Output属性用于向发送缓冲区写数据流。注意:Output属性可以发送文本数据或二进制数据。传输文本数据时,应该将字符型 数据放入VARIANT变量中;传输二进制数据(即按字节传送)时,应将字节型数据放入VARIANT型变量中。如果通常给应用程序发送ANSI字符串,可以以文本方式发送。如果数据包含了内嵌控制字符、NULL字符等,必须将其作为二进制传递过去。
DTREnable:确定在通信时是否使用DTR线有效,DTR是计算机发送到调制解调器的信号,表明计算机在等待数据传输。
RTSEnable:确定是否使用RTS线有效。一般情况下,由计算机发送RTS信号到连接的调制解调器,请求允许发送数据。
EOFEnable:确定在输入过程中MSComm控件是否寻找文件结尾(EOF)字符。如果找到EOF字符,将停止输入并激活OnComm事件,此时CommEvent属性设置为comEvEOF,这里bNewValue为布尔表达式,确定当找到EOF字符时,OnComm事件是否被激活。当bNewValue的设置值TRUE时,EOF字符找到时OnComm事件被激活。否则当VALUE值设为FALSE(默认)时,EOF字符找到时OnComm事件不被激活。
注意:当EOFEnable属性设置为FALSE时,OnComm控件将不在输入流中寻找EOF字符。
CDHolding:通过查询CD线的状态确定当前是否有传输。CD是从调制解调器发送到相连计算机的一个信号,指示调制解调器正在联机。该属性在设计时无效,在运行时为只读。属性的设置值为:当bNewValue为TRUE时,CD线为高电平;当bNewValue为FALSE时,CD线为低电平。注意当CD线为高电平(CDHolding=TRUE)且超时时,MSComm控件设置CommEvent属性为comEventCDTO(CD超时错误),并产生OnComm事件。
注意:在主机应用程序中捕获一个丢失的传输是特别重要的,例如一个公告板,因为呼叫者可以随时挂起(放弃传输)。CD也被称为Receive Line Signal Detect(RLSD)。
DSRHolding:确定DSR线的状态。DSR信号由调制解调器发送到相连计算机,指示作好 *** 作准备。该属性在设计时无效,在运行时为只读。DSRHolding属性返回为TRUE时,表示DSR线高,返回FALSE时,表示DSR线低。当DSR线为高电平时(DSRHolding=TRUE)超时时,MSComm控件设置CommEvent属性为comEventDSRTO(数据准备超时)并产生OnComm事件。当为DTE(Data Terminal Equipment)机器写DSR/DTR握手例程时该属性是分有用的。
3、 控件安装后,在AcitveX面板上可以看到一个电话机样式的图标,把它拖到delphi工程窗体上设置属性后就可以使用了
打开串口: mscomm1portopen:=true;
关闭串口: mscomm1portopen:=false;
处理接收的数据,在oncomm事件中,下面给出点提示例子(只是一个函数,缺乏桩函数不能运行,只是用来说明使用)
procedure TF_ComMSCommComm(Sender: TObject);
var
i,j,itemp:integer;
pcurDeviceSer:pdevicepro;
begin
if mscommCommEvent = 2 then
begin
inBuf:=F_ComMSCommInput;
for i:=0 to vararrayhighbound(inBuf,1) do
begin
if m_sourceLen > 500 then
m_sourceLen := 0;
m_pSourceBuf[m_sourceLen]:=inBuf[i];
m_sourceLen:=m_sourceLen+1;
end;
if(CommDataAnalysis()) then //如果分帧成功
begin
m_ReSendTimes := 0; //重发次数为0
for j:=0 to m_nRecDataLength-1 do
showstr:=showstr + inttohex(inBuf[j],2)+' '; // _GetByte(inBuf[i]);
if not bPauseFlag then
showframe(showstr,true)
else
begin
if length(showstr)>5000 then
showstr:='';
end;
pcurDeviceSer:=nil;
if(m_nRecDataLength = 5) then//固定帧处理
begin
for itemp:=0 to numberofdec do // numberofdec//装置个数 ,遍历,查找需要的装置
begin
if BDeviceSer[itemp]addr=RecBuf[2] then
begin
pcurDeviceSer:=@BDeviceSer[itemp]; //指针指向被召唤的装置的结构体
break;
end;
end;
if pcurDeviceSer=nil then //所召唤的装置不存在
else
FrameFix(pcurDeviceSer);
end
else //可变帧处理
begin
for itemp:=0 to numberofdec do // numberofdec//装置个数 ,遍历,查找需要的装置
begin
if BDeviceSer[itemp]addr=RecBuf[4] then
begin
pcurDeviceSer:=@BDeviceSer[itemp];
break;
end;
end;
if pcurDeviceSer=nil then //所召唤的装置不存在
else
FrameUnFix(pcurDeviceSer);
end;
end;
end;
// end;
end;
发送串口数据:
procedure SendComData();
var
i,j:integer;
begin // Main_FormdqcConvertStr2Hex(tempstr,SendBuf,m_nSendDataLength);
if (SendBuf[0]=$10) then
m_nSendDataLength:=5
else
m_nSendDataLength:=((SendBuf[1] and $ff)+6); //+号比&(按位与运算符)优先级高
outBuf := VarArrayCreate([0,m_nSendDataLength-1], varByte);
for i:=0 to m_nSendDataLength-1 do
outBuf[i]:=SendBuf[i];
F_ComMSCommOutput:=outBuf;
上面的例子只是为了说明使用方法,并不可运行。
嵌入式系统或传感器网络的很多应用和测试都需要通过PC机与嵌入式设备或传感器节点进行通信 其中 最常用的接口就是RS 串口和并口(鉴于USB接口的复杂性以及不需要很大的数据传输量 USB接口用在这里还是显得过于奢侈 况且目前除了SUN有一个支持USB的包之外 我还没有看到其他直接支持USB的Java类库) SUN的CommAPI分别提供了对常用的RS 串行端口和IEEE 并行端口通讯的支持 RS C(又称EIA RS C 以下简称RS )是在 年由美国电子工业协会(EIA)联合贝尔系统 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准 RS 是一个全双工的通讯协议 它可以同时进行数据接收和发送的工作
常见的Java串口包
目前 常见的Java串口包有SUN在 年发布的串口通信API m jar(Windows下) m jar(Linux/Solaris);IBM的串口通信API以及一个开源的实现 鉴于在Windows下SUN的API比较常用以及IBM的实现和SUN的在API层面都是一样的 那个开源的实现又不像两家大厂的产品那样让人放心 这里就只介绍SUN的串口通信API在Windows平台下的使用
串口包的安装(Windows下)
到SUN的网站下载javam win zip 包含的东西如下所示
按照其使用说明(l)的说法 要想使用串口包进行串口通信 除了设置好环境变量之外 还要将win dll复制到 \bin目录下;将m jar复制到 \lib;把m properties也同样拷贝到 \lib目录下 然而在真正运行使用串口包的时候 仅作这些是不够的 因为通常当运行 java MyApp 的时候 是由JRE下的虚拟机启动MyApp的 而我们只复制上述文件到JDK相应目录下 所以应用程序将会提示找不到串口 解决这个问题的方法很简单 我们只须将上面提到的文件放到JRE相应的目录下就可以了
值得注意的是 在网络应用程序中使用串口API的时候 还会遇到其他更复杂问题 有兴趣的话 你可以查看CSDN社区中 关于网页上Applet用javam 读取客户端串口的问题 的帖子
串口API概览
m CommPort
这是用于描述一个被底层系统支持的端口的抽象类 它包含一些高层的IO控制方法 这些方法对于所有不同的通讯端口来说是通用的 SerialPort 和ParallelPort都是它的子类 前者用于控制串行端口而后者用于控这并口 二者对于各自底层的物理端口都有不同的控制方法 这里我们只关心SerialPort
m CommPortIdentifier
这个类主要用于对串口进行管理和设置 是对串口进行访问控制的核心类 主要包括以下方法
l 确定是否有可用的通信端口
l 为IO *** 作打开通信端口
l 决定端口的所有权
l 处理端口所有权的争用
l 管理端口所有权变化引发的事件(Event)
m SerialPort
这个类用于描述一个RS 串行通信端口的底层接口 它定义了串口通信所需的最小功能集 通过它 用户可以直接对串口进行读 写及设置工作
串口API实例
大段的文字怎么也不如一个小例子来的清晰 下面我们就一起看一下串口包自带的例子 SerialDemo中的一小段代码来加深对串口API核心类的使用方法的认识
列举出本机所有可用串口
void listPortChoices() { CommPortIdentifier portId; Enumeration en = CommPortIdentifier getPortIdentifiers(); // iterate through the ports while (en hasMoreElements()) { portId = (CommPortIdentifier) en nextElement(); if (portId getPortType() == CommPortIdentifier PORT_SERIAL) { System out println(portId getName()); } } portChoice select(parameters getPortName()); }
以上代码可以列举出当前系统所有可用的串口名称 我的机器上输出的结果是 和
串口参数的配置
串口一般有如下参数可以在该串口打开以前配置进行配置
包括波特率 输入/输出流控制 数据位数 停止位和齐偶校验
SerialPort sPort; try { sPort setSerialPortParams(BaudRate Databits Stopbits Parity); //设置输入/输出控制流 sPort setFlowControlMode(FlowControlIn | FlowControlOut); } catch (UnsupportedCommOperationException e) {}
串口的读写
对串口读写之前需要先打开一个串口
CommPortIdentifier portId = CommPortIdentifier getPortIdentifier(PortName); try { SerialPort sPort = (SerialPort) portId open( 串口所有者名称 超时等待时间); } catch (PortInUseException e) {//如果端口被占用就抛出这个异常 throw new SerialConnectionException(e getMessage()); } //用于对串口写数据 OutputStream os = new BufferedOutputStream(sPort getOutputStream()); os write(int data); //用于从串口读数据 InputStream is = new BufferedInputStream(sPort getInputStream()); int receivedData = is read();
读出来的是int型 你可以把它转换成需要的其他类型
这里要注意的是 由于Java语言没有无符号类型 即所有的类型都是带符号的 在由byte到int的时候应该尤其注意 因为如果byte的最高位是 则转成int类型时将用 来占位 这样 原本是 的byte类型的数变成int型就成了 这是很严重的问题 应该注意避免
串口通信的通用模式及其问题
终于唠叨完我最讨厌的基础知识了 下面开始我们本次的重点 串口应用的研究 由于向串口写数据很简单 所以这里我们只关注于从串口读数据的情况 通常 串口通信应用程序有两种模式 一种是实现SerialPortEventListener接口 监听各种串口事件并作相应处理;另一种就是建立一个独立的接收线程专门负责数据的接收 由于这两种方法在某些情况下存在很严重的问题(至于什么问题这里先卖个关子J) 所以我的实现是采用第三种方法来解决这个问题
事件监听模型
现在我们来看看事件监听模型是如何运作的
l 首先需要在你的端口控制类(例如SManager)加上 implements SerialPortEventListener
l 在初始化时加入如下代码
try { SerialPort sPort addEventListener(SManager); } catch (TooManyListenersException e) { sPort close(); throw new SerialConnectionException( too many listeners added ); } sPort notifyOnDataAvailable(true);
l 覆写public void serialEvent(SerialPortEvent e)方法 在其中对如下事件进行判断
BI 通讯中断
CD 载波检测
CTS 清除发送
DATA_AVAILABLE 有数据到达
DSR 数据设备准备好
FE 帧错误
OE 溢位错误
OUTPUT_BUFFER_EMPTY 输出缓冲区已清空
PE 奇偶校验错
RI振铃指示
一般最常用的就是DATA_AVAILABLE 串口有数据到达事件 也就是说当串口有数据到达时 你可以在serialEvent中接收并处理所收到的数据 然而在我的实践中 遇到了一个十分严重的问题
首先描述一下我的实验 我的应用程序需要接收传感器节点从串口发回的查询数据 并将结果以图标的形式显示出来 串口设定的波特率是 川口每隔 毫秒返回一组数据(大约是 字节左右) 周期(即持续时间)为 秒 实测的时候在一个周期内应该返回 多个字节 而用事件监听模型我最多只能收到不到 字节 不知道这些字节都跑哪里去了 也不清楚到底丢失的是那部分数据 值得注意的是 这是我将serialEvent()中所有处理代码都注掉 只剩下打印代码所得的结果 数据丢失的如此严重是我所不能忍受的 于是我决定采用其他方法
串口读数据的线程模型
这个模型顾名思义 就是将接收数据的 *** 作写成一个线程的形式:
public void startReadingDataThread() { Thread readDataProcess = new Thread(new Runnable() { public void run() { while (newData != ) { try { newData = is read(); System out println(newData); //其他的处理过程 ……… } catch (IOException ex) { System err println(ex); return; } } readDataProcess start(); }
在我的应用程序中 我将收到的数据打包放到一个缓存中 然后启动另一个线程从缓存中获取并处理数据 两个线程以生产者—消费者模式协同工作 数据的流向如下图所示
这样 我就圆满解决了丢数据问题 然而 没高兴多久我就又发现了一个同样严重的问题 虽然这回不再丢数据了 可是原本一个周期( 秒)之后 传感器节电已经停止传送数据了 但我的串口线程依然在努力的执行读串口 *** 作 在控制台也可以看见收到的数据仍在不断的打印 原来 由于传感器节点发送的数据过快 而我的接收线程处理不过来 所以InputStream就先把已到达却还没处理的字节缓存起来 于是就导致了明明传感器节点已经不再发数据了 而控制台却还能看见数据不断打印这一奇怪的现象 唯一值得庆幸的是最后收到数据确实是 左右字节 没出现丢失现象 然而当处理完最后一个数据的时候已经快 分半钟了 这个时间远远大于节点运行周期 这一延迟对于一个实时的显示系统来说简直是灾难!
后来我想 是不是由于两个线程之间的同步和通信导致了数据接收缓慢呢于是我在接收线程的代码中去掉了所有处理代码 仅保留打印收到数据的语句 结果依然如故 看来并不是线程间的通信阻碍了数据的接收速度 而是用线程模型导致了对于发送端数据发送速率过快的情况下的数据接收延迟 这里申明一点 就是对于数据发送速率不是如此快的情况下前面者两种模型应该还是好用的 只是特殊情况还是应该特殊处理
第三种方法
痛苦了许久(Boss天天催我L)之后 偶然的机会 我听说TinyOS中(又是开源的)有一部分是和我的应用程序类似的串口通信部分 于是我下载了它的 x版的Java代码部分 参考了它的处理方法 解决问题的方法说穿了其实很简单 就是从根源入手 根源不就是接收线程导致的吗 那好 我就干脆取消接收线程和作为中介的共享缓存 而直接在处理线程中调用串口读数据的方法来解决问题(什么 为什么不把处理线程也一并取消 都取消应用程序界面不就锁死了吗所以必须保留)于是程序变成了这样
public byte[] getPack(){ while (true) { // PacketLength为数据包长度 byte[] msgPack = new byte[PacketLength]; for(int i = ; i < PacketLength; i++){ if( (newData = is read()) != ){ msgPack[i] = (byte) newData; System out println(msgPack[i]); } } return msgPack; } }
在处理线程中调用这个方法返回所需要的数据序列并处理之 这样不但没有丢失数据的现象行出现 也没有数据接收延迟了 这里唯一需要注意的就是当串口停止发送数据或没有数据的时候is read()一直都返回 如果一旦在开始接收数据的时候发现 就不要理它 继续接收 直到收到真正的数据为止
结束语
lishixinzhi/Article/program/Java/hx/201311/26605
c或vb中有mscomm控件很好用,我常用,以下是我用delphi写的
with mscomm1 do begin
commport:=1; //端口号
settings:=9600,n,8,1; //端口设置
InputMode:=0; //设置或返回Input属性取回的数据的类型 0:文本 1:二进制
RThreshold:=1; //每次接收到字符即产生OnComm事件
SThreshold:=0; //传输缓冲区允许最小字符数
InBufferCount:=0; //清空接收缓冲区内容
InputLen:=0; //为0时 Input一次读取整个缓冲区内容
DTREnable:=true; //数据终端准备好
RTSEnable:=true; //发送请求
end;
mscomm1Output:=edit1text;//发送
str:=mscomm1input; //接收
ROS节点程序运行过程中需要获取机器人的传感器信息和发送控制指令,因此不可避免要与机器人进行通信,常见的通讯方式有串口、CAN和网口等,其中串口最为普遍。
ROS通过自带的 serial 包连接串口设备,进行串口通信,需要提前安装 serial 包:
sudo apt-get install ros-melodic-serial
串口通信根据数据传输方向可以分为 串口发送 和 串口接收 。
通过串口发送数据时,需要明确发送机制,常见的发送机制有:
定时发送是通过设定定时器,以 固定频率 发送数据包;
触发条件发送是通过条件判断语句,判断某个标志位或某个事件发生后,再发送数据包,特点是 频率不固定 。
发送数据分为两个步骤:
由此可知buffer数据结构需要可同时被这两个步骤访问,buffer需要为全局变量。
首先根据通信协议定义buffer中的帧头、帧尾等固定内容,然后接收别的Topic,获取待发送数据,把获取的数据填入到协议的数据段,最后根据数据段计算校验码。
1 订阅发送数据的Topic
serial_sub = nhsubscribe("/joy", 10, &DecodeFrame::serial_sub_callback,this);
2 根据Topic填入数据段和校验
1 创建定时回调函数
首先需要创建一个定时器,设定发送频率,并指定回调函数名称
write_rs232_timer = nhcreateTimer(ros::Duration(001), &DecodeFrame::CB_write_rs232_Cycle, this);
2 通过 serial 将buffer数据写入串口设备
实例化串口对象,并打开串口
发送数据到串口设备上
注意1:发送数据节点挂掉
若发送数据Topic的节点挂掉后,由于buffer是全局变量,buffer的数据段会一直是上一帧的数据,不会再改变,为避免上述情况,在将数据写入串口设备后, 将数据段清零。
注意2:joystick包发送机制
在使用PS3/PS4、Xbox手柄时,使用ros-melodic-joy包获取摇杆数据,手柄的遥杆或者按键如果一直处于同一位置(初始零位和最大值)只会发送一帧数据,不会连续发送, 只有当摇杆数据变化时,才会发送数据。
因此使用 rostopic hz /joy 会显示没有msg信息传输,所以串口程序不会进回调函数获取发送数据,但是数值确实是一直保持的, 所以就不能将数据段清零。
触发条件发送与定时发送相比的最大不同之处在于发送频率不同,不需要设置定时器,在满足条件后直接将数据写入串口即可。
以上就是关于向串口写数据 VB 源码全部的内容,包括:向串口写数据 VB 源码、您好,能用实例给我讲解一下单片机串口通讯的串口中断方式和查询方式的区别吗、QT开发(五十)——QT串口编程基础等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)