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