Spcomm共实现了三个类:串口类Tcomm、读线程类TreadThread以及写线程类TwziteThread[1]。Tcomm的某个实例在方法StartComm中打开串口,并实例化了一个读线程ReadThread和一个写线程WriteThread,它们和主线程之间进行消息的传递,实现串口通信。
3.1 Spcomm控件的基本属性、方法和事件
Spcomm串口通信控件的基本属性、方法和事件说明如下:
CommName属性:计算机串口端口号的名字,COM1、COM2……等,在打开串口前,必须填写好此值。
Parity属性:校验位 None、Odd、Even、Mark、Space等。
BaudRate:设定支持串口通信用的波特率9600,4800等,根据实际需要来定,在串口打开后也可更改波特率,实际波特率随之更改。
ByteSize属性:表示一个字节中,使用多少个数据位收发数据,根据具体情况设定5、6、7、8等。
StopBits属性:表示一个字节中,使用停止位的位数,根据具体情况设定1、1.5、2等。
SendDataEmpty属性:布尔属性,为True时表示发送缓存为空,或者发送队列里没有信息;为False时表示表示发送缓存不为空,或者发送队列里有信息。
StartComm方法:用来打开通信串口,开始通信。如果失败,则会导致串行口错误。错误类型大致分为串行口己处于打开状态,所以不能打开串行口,不能创建读写进程,不能建立串行口缓冲区等。
StopComm方法:用来停止通信串行口的所有进程,关闭串口。
WriteCommData
(pDataToWriteChar;dwSizeofDataToWrite:Word)方法是带有布尔型返回值的函数,其中参量pszStr-ingToWrite是要写入串行口的字符串,DwSizeaf- DataToWrite是要写入的字符串的长度。该函数通过一个写线程向串行口输出缓冲区发送数据。发送操作将在后台默认执行。如果写线程PostMessage成功,则返回值是True,若写线程失败,返回值是False。
OnReceiveData
(Bufferointer;BufferLength:Word),其中Buffer是指向输入缓冲区的指针。BufferLength是从缓冲区收到的数据长度。当输入缓冲区收到数据时,该事件被触发。当输入缓存有数据时将触发该事件,对从串口收到的数据进行处理。
3.2 Spcomm串口通信的实现
Spcomm串行通信控件具有多线程的特性,接收和发送数据分别在两个线程内完成,
接收线程负责收到数据时触发OnReceiveData事件;用WriteCommData()函数将待发送的数据写入输出缓冲器
,发送线程在后台完成数据发送工作。
在接收和发送数据前需要初始化串口,用StartComm方法打开串口,退出程序时用StopComm方法关闭串口。
实现PC机与单片机之间的数据发送及接收需要以下步骤:
(1)初始化并打开串口
需要选择本次通信使用的串口,确定通信协议,即设置波特率、校验方式、数据位、停止位等属性,打开该串口。示例代码如下:
//初始化并打开串口
Comm1.BaudRate:=9600;//波特率9600bps
Comm1.Parity:=None;//奇偶检验无
Comm1.ByteSize:=8;//数据位8
Comm1.StopBits:=1;//停止位1
Comm1.StartComm; //打开串口
(2)建立握手信号
实现PC机与单片机之间的通信,首先要调通它们之间的握手信号,握手信号可以随意选择某特定字符串, 当PC发出这样一帧数据后,通过接收事件能收到单片机返回的这一帧数据或特定的某字符串,则表示握手成功,系统通信正常。两者之间就可以按照协议相互传输数据。否则需重新建立握手信号。
(3)发送数据
在编写基于串口的计算机工业测控时,通常需要由PC机向下位机发送命令以控制下位机的行为,同时向下位机发送有关数据。利用Spcomm串口控件向下位机发送数据示例代码如下:
//发送数据和控制字程序
procedure senddata;
var
i:integer; commflg : Boolean;
begin
commflg:=true ;
for i:=1 to 8 do
begin
if not fcomm comml writecommdata(sendbutter,i) then
begin
Commflg=false;
break;
end;
end;
end;
(4) 接收数据
在编写基于串口的计算机工业测控时,通常需要由下位机向PC机发送数据以使PC机了解系统的测试数据或下位机的运行状态,并进而控制下位机的行为。利用Spcomm串口控件接收下位机发送的数据信息的示例代码如下:
//事件驱动方式接收数据程序
procedure TForm1.CommlReceiveData(Sender:Tobject;
Bufferointer; bufferLength:Word);
var
receivedata:array of byte;
begin
sleep(100);//等待100ms,保证接收到所有数据
move(buffef ,receivedata,bufferlength);
//将接收缓存区中的数据转移到数组中
……
end;
(5) 关闭串口
在系统开发中,应注意在不使用串口时应及时关闭串口,释放系统资源,否则可能会影响系统的其它应用。关闭串口的代码如下:
procedure TForm1.FormClose ( Sender;TObj ect:var Action:TCIoseAction );
begin
comml.StopComm ;
end;
4 Spcomm串口通信的关键技术问题
Spcomm应用的核心在于主线程、读线程和写线程之间的消息传递机制,而通信数据相关信息的传递也是以消息传递的方式进行的。在使用Spcomm进行串口通信编程,除按照说明使用外,还需要特别注意以下两个问题。
首先,Spcomm是通过ReadIntervalTimeout属性的设置,来确定所接收到的数据是否属子同一帧数据,其默认值是100ms,也就是说,只要任何两个字节到达的时间
间隔小于1OOms
,都被认为是属于同一帧数据,在与单片机协同工作时,要特别注意这个问题[2]。
另外,Spcomm的默认属性设置是支持软件流控制的,用于流控制的字符是13H(XoffChar)和11H(XonChar),当单片机以二进制方式发送数据时,
必须要禁用Spcomm对于软件流控制的支持,否则,在数据帧中出现的13H,11H会被Spcomm作为控制字符而加以忽略。
3.1 串口调试助手的概要设计
端口初始化就是对波特率、校验、数据位和停止位的设置。当端口打开时指示灯为绿色,当端口为打开时指示灯为红色。
当接收数据后对数据进行变换时,要先判断接收的数据是否符合变换的条件,例如当以十进制显示时,接收的数据不是十六进制数就会报错。
发送数据之前必须保证串口打开,所以先判断端口有没有打开。由于发送空内容没有意义,所以还要保证发送内容不能为空。
自动发送当选中CheckAutosend时,串口调试助手以设置好的时间间隔发送数据。
procedure TForm1.Button2Click(Sender: TObject);
var
i ,j,TextLen: Integer;
//aucBuf : array[0..4096] of byte;
SendBuf : string;
strbuf : string;
begin
strbuf :=Memo2.text;
sendbuf := '';
if HexSendFlag = True then
begin
strbuf := StringReplace(strbuf, #10, '', [rfReplaceAll]);
strbuf := StringReplace(strbuf, #13, '', [rfReplaceAll]);
strbuf := StringReplace(strbuf, ' ', '', [rfReplaceAll]);
strbuf := StringReplace(strbuf, #9, '', [rfReplaceAll]);
TextLen := Length(strbuf);
i:=1;
while (i <= TextLen) and (strbuf[i] in ['0'..'9','A'..'F','a'..'f']) do
inc(i);
if i <= TextLen then
begin
ShowMessage('非法的十六进制数');
Exit;
end;
if TextLen > 0 then
begin
for j:=0 to (TextLen div 2 - 1) do
begin
//aucBuf[j] := Byte(StrToIntDef('$' + strbuf[2*j + 1] + strbuf[2*j + 2], 0));
SendBuf := SendBuf + Char(StrToIntDef('$' + strbuf[2*j + 1] + strbuf[2*j + 2], 0));
end;
if TextLen mod 2 <> 0 then
begin
//aucBuf[j] := Byte(StrToIntDef('$0'+ strbuf[2*j + 1], 0));
SendBuf := SendBuf + Char(StrToIntDef('$0'+ strbuf[2*j + 1], 0));
end;
//comm1.writecommdata(@aucBuf, TextLen div 2 + textLen mod 2);
comm1.writecommdata(pchar(SendBuf), TextLen div 2 + textLen mod 2);
//ShowMessage(IntToStr(TextLen div 2 + TextLen mod 2));
SendLen := SendLen + TextLen div 2 + textLen mod 2;
end;
end
else
begin
if Length(Memo2.Text) > 0 then
begin
comm1.writecommdata(pchar(strbuf), Length(Memo2.Text));
end;
if CheckBox6.Checked = true then
begin
strbuf := #13;
comm1.writecommdata(pchar(strbuf), 1);
SendLen := SendLen + 1;
end;
if CheckBox7.Checked = true then
begin
strbuf := #10;
comm1.writecommdata(pchar(strbuf), 1);
SendLen := SendLen + 1;
end;
SendLen := SendLen + Length(Memo2.Text);
end;
StatusBar1.Panels[0].Text := 'S:' + IntToStr(SendLen);
end;
j:=trunc((length(s1)/2))-1;
for i:=0 to j do
//s2:= s2+char(strtoint('$'+(copy(s1,2*i+1,2))));
s3[i]:=char(strtoint('$'+(copy(s1,2*i+1,2))));
comm1.writecommdata(s3,trunc((length(s1)/2)));
procedure sendhex(s:string);
var
i,j,TextLen:integer;
sendbuf,strbuf:string;
p:pchar;
begin
strbuf:='';
sendbuf:= '';
for i:=1 to length(s) do
begin
if ((copy(s,i,1)>='0') and (copy(s,i,1)<='9'))or((copy(s,i,1)>='a') and (copy(s,i,1)<='f'))or((copy(s,i,1)>='A') and (copy(s,i,1)<='F')) then
begin
strbuf:=strbuf+copy(s,i,1);
end;
end;
showmessage(strbuf);
TextLen:= Length(strbuf);
showmessage(inttostr(TextLen));
p:=pchar(strbuf);
showmessage(p);
try
comm1.writecommdata(p,TextLen-1);
except
exit;
end;
if TextLen > 0 then
begin
for j:=0 to (TextLen div 2 - 1) do
begin
//aucBuf[j] := Byte(StrToIntDef('$' + strbuf[2*j + 1] + strbuf[2*j + 2], 0));
SendBuf:= SendBuf + Char(StrToIntDef('$' + strbuf[2*j + 1] + strbuf[2*j + 2], 0));
end;
if TextLen mod 2 <> 0 then
begin
//aucBuf[j] := Byte(StrToIntDef('$0'+ strbuf[2*j + 1], 0));
SendBuf:= SendBuf + Char(StrToIntDef('$0'+ strbuf[2*j + 1], 0));
end;
showmessage(SendBuf);
//comm1.writecommdata(@aucBuf, TextLen div 2 + textLen mod 2);
// comm1.writecommdata(pchar(SendBuf), TextLen div 2 + textLen mod 2);
//ShowMessage(IntToStr(TextLen div 2 + TextLen mod 2));
//SendLen := SendLen + TextLen div 2 + textLen mod 2;
end;