实用指南:Lazarus结合Lazserial多线程方式的串口采集实操心得

Lazarus和delphi非常类似,只是它的跨平台功能更加诱人,号称是一套源码到处可编译。串口通讯的好处是硬件容易成熟,单片机和各种微处理装置几乎都有这接口,把它转换成RS485配上modbus协议也很常用。

  • 为什么要用多线程

串口通讯速率有限,发送方发出指令后接收方收到后进行一定处理,将数据比特位按比特率回送给发送端。处理串口的时间如果过长,尤其是串口线中断了,那主线程就因停滞而处理变得十分卡顿。除了串口采集外,tcp或udp通讯也适宜放在其它线程中,因为它握手有时耗时太长,而这些时间均由不得软件控制。

  • 在哪里定义线程类

在窗体form类下方接着定义一个线程类即可

type
TMyThreadCOM = class(TThread)
private
procedure ShowTextCOM;
protected
procedure Execute; override;
end;

其中 procedure Execute; override; 是要重写一个我们必须的过程,也就是主线程外的线程要做的任务。procedure ShowTextCOM; 是个私有过程,供Execute过程调用,主要是刷新窗体上的数据或其它表现,由外线程交给主线程协同进行刷新,避免主线程和外线程同时操作界面。如果一个私有过程不够用,那就多加几个私有过程。

  • 定义创建线程类时用的handle变量

var
Form1: TForm1;
MyThreadCOM: TMyThreadCOM;

我加入的变量。就是上面的Form1:TForm1;是我们常见的窗体变量,一般只要新建一个窗体类的project就会有这一行,下面的那行

  • Execute调用的ShowTextCOM过程

就像写普通过程一样写就行,放哪不重要。

procedure TMyThreadCOM.ShowTextCOM;
begin
Form1.Caption := 'Side thread to show';
end;

  • 写Execute过程,在这里我们向串口发送指令

procedure TMyThreadCOM.Execute;

Var
btSend: array[0..7] of Byte;
begin
btSend[0]:= $01;
btSend[1]:= $03;
btSend[2]:= $00;
btSend[3]:= $F1;
btSend[4]:= $00;
btSend[5]:= $01;
btSend[6]:= $D5;
btSend[7]:= $F9;

Form1.LazSerial1.Close;
Form1.LazSerial1.Open;

大家准备好指令,其中btSend的0到5真实指令,6和7是modbus CRC16校验值。关闭串口,再打开串口,类似强制Fresh一次,只是在实践中体会这样做更稳定,没有更多想法。接着往下写...

Form1.LazSerial1.WriteBuffer(btSend, 8);

这个就是我们要发送的完整指令串,指令串发送是按比特位按比特率发送的,接收方收到后会判断处理并返回结果值,这一过程是需要时间的,所以要等待一会,等到LazSerial1.DataAvailable是真的时候。如果长时间等不来呢,那就不等了,200毫秒后自动结束。

for i:= 0 to 20 do
begin
if Form1.LazSerial1.DataAvailable = True then
break else Sleep(10);
end;

通过1秒传送9600个比特位,1000个比特位就应该 (1000/9600)x 1000 = 105 毫秒,所以,能够等上它 150 毫秒。就是通常情况下,串口接收的数据会暂存后一并复制给用户接口,有数就应该是完整的,但实际上并不是这样,而是有了数据就DataAvailable了,此时读数据的话可能就不是完整的数据,所以,还要等一等。等多长时间呢?就是传送比特位需要的时间。比如有100个回送二进制资料,N,8,1方式按10位计算,那就是100x10=1000个比特位,9600比特率那

Sleep(150);

之后读取数据

COMreturnHEXBIN := Form1.LazSerial1.ReadData;

至此,我们就得到了一串进制的材料串。实际采集中,这个二进制串要转换成十六进制数据串,然后进行CRC16校验。

COMreturnHEXCHA := AnsiStr2HexStr(COMreturnHEXBIN);

Synchronize(@ShowTextCOM);

将COMreturnHEXCHA 从左侧截取其长度值减4后的子串,交给CRC16过程校验得到校验码,将这个校验码与串口返送回来的十六进制数据串截取前的右4位校验值进行对比,如果相同就认为是传输无误,如果不同就认为是传送错误。Synchronize(@ShowTextCOM); 这句话是让主线程协调执行私有ShowTextCOM过程,在窗体上显示一些东西(在Delphi中要去掉那个@)。过程就写到这里,需要与实际情况相结合。

  • 创建线程执行Execute

MyThreadCOM := TMyThreadCOM.Create(True); // 创建线程后,暂停suspend
MyThreadCOM.FreeOnTerminate := True; // 线程结束后自动释放线程资源
MyThreadCOM.Start; // 起动线程

这几句话放在一个Timer时钟过程中,比如每500ms执行一次。那Execute过程就会在另外的线中执行,执行完自动释放,下一个500ms到了再次创建,周而复始。如此,线程运行时不影响主程序运行,界面操作丝滑顺畅。

  • 对LazSerial1.ReadData的附加说明

Pascal字符串与普通文本串不同,它在单独的地方记录数据串长度,因此,它的字符串可包括ASCII任意字符,因此,LazSerial1.WriteBuffer也可以用二进制信息的ASCII字符串方式接在一起通过类似LazSerial1.WriteData(Chr(01)+Chr(03)+Chr(00)+Chr(241)+......);的方式替换。发送如此,接收亦如是。ASCII字符串变成ASCII值也不复杂,串本身就是内容,取其某位将其赋给byte变量就是ascii值了。

posted @ 2025-10-25 09:06  yjbjingcha  阅读(2)  评论(0)    收藏  举报