资讯动态

Delphi串口通信实战:TComPort 4.14从安装到避坑指南

发布时间:2026/10/14 17:48:49 来源:尧图企业网站定制
简介TComPort 4.14 是一套面向 Delphi 与 C Builder 开发者的串口通信组件库适用于需要快速实现串口收发、数据包解析与终端交互的桌面项目尤其适合使用 Delphi 7 至 XE7 版本、希望减少底层 API 封装工作量的中高级开发者。压缩包共 173 个文件约 1.68MB包含 38 个 dpk 与 8 个 bpk 安装包、12 个 pas 与 16 个 cpp 源码、10 个 dfm 窗体、7 个 hpp 头文件及 7 个 dcu 编译单元另有 res 资源、hlp 帮助、map 映射与 txt 说明等覆盖组件安装、示例工程与文档多个层面。资源内含 TComPort、TComDataPacket、TComComboBox、TComRadioGroup、TComLed、TComTerminal 等组件可分别处理串口连接、数据分包、参数选择、状态指示与终端显示帮助开发者省去重复造轮子的过程。目前已有 671 人学习下载适合需要稳定串口通信方案、对照源码理解组件实现或直接集成到既有工程中的读者参考使用。1. 串口通信这件小事为什么老 Delphi 项目还在用 TComPort 4.14如果你手上维护着一套 Delphi 7 到 XE7 时代的老工控上位机大概率绕不开串口。PLC、称重仪、扫码枪、温控模块、老式 CNC这些设备到今天还在大量出货接口清一色是 RS232 或 RS485 转 USB。TComPort 4.14 就是那个年代 Delphi 圈子里最常用的串口控件包它把 Win32 的串口 API 封装成一组 VCL 组件丢到窗体上设几个属性就能收发数据。为什么不用原生 API 自己写因为重叠 I/O、超时、线程同步、事件驱动这几块手写一遍至少要两三天还容易在长时间运行后出玄学问题。TComPort 4.14 的价值在于它把打开端口、配置波特率、读写超时、OnRxChar 事件回调这些事都做完了你只需要关心协议解析。这篇文章面向的是还在用 Delphi 7 到 XE7 这套工具链、需要快速把串口通信跑起来的工程师我会从安装、最小可运行代码、参数配置、多线程注意点到常见翻车场景一步步拆开讲。2. 把 TComPort 4.14 装进 Delphi 7-XE7包结构、编译顺序与第一个可用工程2.1 先搞清楚你拿到的是什么源码包还是已编译包TComPort 4.14 通常以两种形态出现一种是带.pas源码的完整包里面有CPort.pas、CPortLib.pas、ComPort.pas等单元另一种是已经编译好的.dcu加设计期包.bpl。我一般建议用源码包原因很直接Delphi 7 和 XE7 的 RTL 差异不小已编译的.dcu很可能因为编译器版本不匹配直接报「Unit was compiled with a different version」。源码包虽然要自己编译但至少可控。拿到源码后先看目录结构。典型布局是TComPort/ Source/ CPort.pas CPortLib.pas ComPort.pas CPortSetup.pas Packages/ Delphi7/ CPortD7.dpk DelphiXE7/ CPortXE7.dpk Demos/Source放核心单元Packages放各版本的设计期包Demos是示例工程。不要急着打开.dpk先确认Source路径已经加到 Delphi 的 Library Path 里否则编译包的时候会找不到单元。2.2 编译设计期包顺序错了就是一堆红Delphi 的包分运行期包和设计期包。TComPort 一般只有一个设计期包里面同时包含运行期代码。编译步骤打开对应版本的.dpk比如 Delphi 7 就开CPortD7.dpk。在 Project Manager 里右键先Compile再Install。如果Compile报找不到CPortLib检查Tools Options Environment Options Delphi Options Library里的Library Path是否包含Source目录。Install成功后组件面板会多出一个CPortLib页签里面有TComPort、TComDataPacket、TComLed等组件。这里有个坑Delphi 7 和 XE7 的包不能混用。你在 D7 下编译出的.bpl拿到 XE7 下装必然报错。每个版本用各自的.dpk不要图省事。2.3 最小可运行工程打开 COM1发一条指令收一串数据装好组件后新建一个 VCL Forms Application从组件面板拖一个TComPort到窗体上再放一个TMemo和一个TButton。下面是核心代码// 窗体单元假设窗体类为 TForm1 procedure TForm1.FormCreate(Sender: TObject); begin // 设置端口号COM1 对应 Port : COM1 ComPort1.Port : COM1; // 波特率 9600无校验8 数据位1 停止位 ComPort1.BaudRate : br9600; ComPort1.Parity : paNone; ComPort1.DataBits : db8; ComPort1.StopBits : sb1; // 读写超时单位毫秒 ComPort1.ReadTimeout : 500; ComPort1.WriteTimeout : 500; // 打开端口 try ComPort1.Open; except on E: Exception do Memo1.Lines.Add(打开失败: E.Message); end; end; procedure TForm1.Button1Click(Sender: TObject); var SendBuf: array[0..4] of Byte; begin // 构造一条 Modbus RTU 读保持寄存器指令示例 SendBuf[0] : $01; // 从站地址 SendBuf[1] : $03; // 功能码 SendBuf[2] : $00; // 起始地址高 SendBuf[3] : $00; // 起始地址低 SendBuf[4] : $02; // 寄存器数量 // 实际使用需要加 CRC16这里仅演示发送 ComPort1.Write(SendBuf, SizeOf(SendBuf)); end; procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer); var RecvBuf: array of Byte; begin SetLength(RecvBuf, Count); // 读取收到的数据 ComPort1.Read(RecvBuf[0], Count); // 转成十六进制显示 Memo1.Lines.Add(BytesToHex(RecvBuf)); end;逻辑说明FormCreate里做端口参数初始化Open之后端口才真正被占用。Button1Click里调用Write发送字节数组注意Write的第一个参数是var无类型参数传数组第一个元素即可。OnRxChar是 TComPort 的核心事件当接收缓冲区有数据时触发Count是当前可读字节数。这里必须用Read把数据取走否则缓冲区会一直累积。参数说明BaudRate是枚举类型br9600对应 9600其他常用值有br19200、br38400、br115200。ReadTimeout和WriteTimeout设太小会导致读写失败设太大又会让界面卡顿一般 500 到 1000 毫秒比较稳妥。OnRxChar触发频率和ReadTimeout无关它由驱动层数据到达事件驱动。提示OnRxChar在 VCL 主线程里触发如果你在事件里做耗时解析界面会卡。复杂协议建议在事件里只做数据搬运解析放到工作线程。3. 参数配置与数据收发波特率、超时、缓冲区到底怎么设3.1 波特率和帧格式设备手册写什么你就设什么串口通信最基础也最容易出错的就是帧格式。设备手册上一般会写「9600, 8, N, 1」这种简写对应波特率 9600、8 数据位、无校验、1 停止位。TComPort 里的属性映射关系手册写法TComPort 属性值9600BaudRatebr96008DataBitsdb8NParitypaNone1StopBitssb1无流控FlowControlfcNone如果设备手册写的是「19200, 8, E, 1」那就是br19200、db8、paEven、sb1。校验位设错收到的数据会随机出现位翻转现象是偶尔能通、偶尔全是乱码这种问题排查起来很折磨人。流控这块绝大多数工控设备用的是fcNone。如果设备支持硬件流控会明确写 RTS/CTS这时才设fcHardware。软件流控 XON/XOFF 在工控场景很少见设错了会导致数据被吞。3.2 读写超时设多少取决于你的协议节奏ReadTimeout和WriteTimeout的单位是毫秒。WriteTimeout一般设 500 就够因为串口发送本身很快超时通常意味着硬件流控卡住了。ReadTimeout要看你用什么读法。TComPort 有两种读数据方式一种是在OnRxChar事件里读这种方式下ReadTimeout影响不大因为事件触发时数据已经在了另一种是主动轮询比如在主循环里调Read这时ReadTimeout决定了没数据时Read多久返回。轮询方式我一般设 100 到 200 毫秒太短会空转占 CPU太长响应迟钝。// 主动轮询读法示例 procedure TForm1.Timer1Timer(Sender: TObject); var Buf: array[0..255] of Byte; n: Integer; begin n : ComPort1.Read(Buf, SizeOf(Buf)); if n 0 then Memo1.Lines.Add(BytesToHex(Buf, n)); end;这段代码放在TTimer的OnTimer里定时 100 毫秒触发一次。Read返回实际读到的字节数返回 0 表示超时无数据。注意Read在超时前会阻塞所以定时器间隔要大于ReadTimeout否则会重入。3.3 缓冲区与 OnRxChar 的配合别在事件里做重活TComPort 内部有一个接收缓冲区OnRxChar触发时Count是当前缓冲区里的字节数。如果你在事件里不读走下次触发时Count会累加。我见过有人在OnRxChar里只处理第一个字节剩下的不管结果缓冲区越积越多最后程序内存暴涨。正确做法是在事件里一次性把Count个字节全读出来然后交给协议解析器。如果协议是变长的比如 Modbus RTU 要根据功能码判断长度那就先把数据追加到一个全局缓冲区再在定时器里做帧同步。// 全局缓冲区方式 var GRxBuf: TListByte; procedure TForm1.ComPort1RxChar(Sender: TObject; Count: Integer); var Tmp: array of Byte; i: Integer; begin SetLength(Tmp, Count); ComPort1.Read(Tmp[0], Count); for i : 0 to Count - 1 do GRxBuf.Add(Tmp[i]); // 这里只搬运不解析 end;GRxBuf用TListByte或TBytes动态数组都行。解析逻辑放到Timer里每次从GRxBuf头部找完整帧找到就处理并移除找不到就等下一批数据。这样界面不会卡协议解析也不会因为数据分片而出错。注意OnRxChar里不要调用Application.ProcessMessages也不要弹窗否则可能引发重入和死锁。4. 多线程与跨版本Delphi 7 到 XE7 的兼容性避坑4.1 TComPort 不是线程安全的别在子线程里直接操作TComPort 的组件方法没有做线程同步保护。如果你在工作线程里直接调ComPort1.Write而主线程同时在OnRxChar里读轻则数据错乱重则直接崩溃。常见做法是所有对 TComPort 的调用都通过TThread.Synchronize或TThread.Queue切回主线程。// 在工作线程里安全发送 procedure TWorkerThread.SendData(const Data: TBytes); begin TThread.Queue(nil, procedure begin Form1.ComPort1.Write(Data[0], Length(Data)); end); end;TThread.Queue是异步的不阻塞工作线程Synchronize是同步的会等主线程执行完。发送数据用Queue就行不需要等结果。接收方向OnRxChar本身就在主线程触发所以读数据是安全的但读完之后如果要把数据传给工作线程解析记得用线程安全的队列结构比如TThreadedQueue。4.2 Delphi 7 和 XE7 的字符串与泛型差异Delphi 7 的string是AnsiStringXE7 默认是UnicodeString。TComPort 4.14 的源码里如果用了string来存端口名在 XE7 下编译可能出警告但一般不影响功能。真正要注意的是你自己写的协议解析代码如果协议里有 ASCII 字符串D7 下直接string就行XE7 下要明确用AnsiString或TBytes转。泛型方面D7 不支持TListByte只能用TList存Pointer或者自己封装。XE7 支持泛型但TListByte的装箱拆箱有性能开销高频收发场景建议用动态数组TBytes加手动管理长度。// 跨版本兼容的字节缓冲区 type TByteBuf record Data: array[0..4095] of Byte; Len: Integer; end;用固定大小数组加长度字段D7 和 XE7 都能编译也没有动态内存分配的开销。4096 字节对大多数工控协议够用如果不够就调大。4.3 包安装后的路径问题换台机器就找不到单元TComPort 装好后Library Path里会有Source目录的绝对路径。如果你把工程拷到另一台机器而那台机器没装 TComPort 或者路径不同编译就会报找不到CPort。解决办法有两个一是把Source目录拷到工程目录下用相对路径引用二是在工程里直接添加CPort.pas等文件不用包。我一般推荐第二种尤其是要交付源码的项目。直接把CPort.pas、CPortLib.pas、ComPort.pas这几个文件加到工程里编译时一起编不依赖设计期包。缺点是组件面板上没有可视化组件但串口通信本来就没多少可视化需求代码里动态创建TComPort更灵活。// 动态创建 TComPort不依赖设计期包 var ComPort: TComPort; begin ComPort : TComPort.Create(Self); ComPort.Port : COM3; ComPort.BaudRate : br115200; ComPort.OnRxChar : HandleRxChar; ComPort.Open; end;HandleRxChar是你自己写的方法签名和TComPortRxCharEvent一致。动态创建的好处是端口号可以运行时决定不用在窗体上摆一堆组件。5. 避坑与排查串口通信里那些让人抓狂的常见问题5.1 打开端口报「Access denied」谁占用了 COM 口现象ComPort.Open抛出异常提示拒绝访问。原因通常是另一个程序已经打开了同一个 COM 口或者上一次程序异常退出后句柄没释放。Windows 的串口是独占资源一个口同时只能被一个进程打开。解决先用设备管理器确认端口号没搞错然后检查是否有其他串口调试工具在运行。如果确认没有重启一次机器通常能释放残留句柄。更彻底的办法是在代码里加异常保护确保Close一定被调用try ComPort1.Open; // 业务逻辑 finally if ComPort1.Connected then ComPort1.Close; end;Connected属性判断端口是否处于打开状态比直接调Close安全。5.2 收到数据全是乱码波特率或校验位不匹配现象OnRxChar能触发但收到的字节和预期完全对不上或者偶尔对几个字节。原因九成是波特率、数据位、校验位、停止位这四个参数里有一个和设备不一致。解决对照设备手册逐项核对。如果手册写的是「9600, 8, N, 1」就设br9600、db8、paNone、sb1。如果手册写的是「19200, 8, E, 1」校验位要设paEven。另外注意有些 USB 转串口芯片的波特率有偏差换一个品牌的转换器可能就好了。5.3 OnRxChar 不触发端口没打开或者事件没挂上现象发送正常但接收事件一直不触发。原因可能是Open没成功但被异常吞了或者OnRxChar事件在代码里被覆盖了。解决在Open之后加一句if ComPort1.Connected then Memo1.Lines.Add(已打开)确认端口真的开了。然后检查OnRxChar是否在运行时被重新赋值。设计期挂的事件在动态创建组件时不会自动生效必须手动赋值。5.4 长时间运行后程序卡死缓冲区溢出或线程死锁现象程序跑几个小时或几天后无响应内存占用持续上升。原因通常是OnRxChar里没读走数据缓冲区无限增长或者工作线程和主线程互相等待造成死锁。解决在OnRxChar里确保每次Count个字节都被Read走。如果解析逻辑复杂用全局缓冲区加定时器的方式定时器里做帧同步和清理。线程间通信用TThread.Queue而不是Synchronize避免主线程等子线程、子线程等主线程的环路。5.5 换台电脑就编译不过路径和版本问题现象在开发机上好好的拷到现场机器上编译报错提示找不到单元或版本不匹配。原因通常是Library Path里引用了开发机的绝对路径或者用了已编译的.dcu而现场 Delphi 版本不同。解决把 TComPort 的源码文件直接加到工程里不用设计期包不依赖Library Path。如果必须用包确保每台机器都用对应版本的.dpk重新编译安装。6. 进阶技巧用 TComDataPacket 做帧同步少写一半解析代码TComPort 包里除了TComPort还有一个很实用的组件叫TComDataPacket。它挂在TComPort上帮你做帧同步你告诉它包头的什么特征、包尾是什么、包长多少它就在完整帧到达时才触发OnPacket事件。对于 Modbus RTU 这种靠 3.5 字符间隔分帧的协议TComDataPacket可能不太适用但对于有明确包头包尾的协议比如以$AA开头、$55结尾的自定义帧它能省掉你手写状态机的一大段代码。配置方式// 假设协议格式AA 长度 数据 校验 55 procedure TForm1.FormCreate(Sender: TObject); begin ComPort1.Port : COM1; ComPort1.BaudRate : br9600; ComPort1.Open; ComDataPacket1.ComPort : ComPort1; ComDataPacket1.StartString : #$AA; // 包头 ComDataPacket1.StopString : #$55; // 包尾 ComDataPacket1.OnPacket : HandlePacket; end; procedure TForm1.HandlePacket(Sender: TObject; const Str: string); var Raw: TBytes; begin // Str 是去掉包头包尾后的数据具体行为取决于 IncludeStrings 设置 Raw : BytesOf(Str); Memo1.Lines.Add(收到完整帧长度: IntToStr(Length(Raw))); end;StartString和StopString是string类型在 Delphi 7 下是AnsiStringXE7 下是UnicodeString。如果协议里有大于$7F的字节XE7 下直接赋#$AA可能会有编码问题建议用AnsiString显式转换或者干脆不用TComDataPacket自己写字节级状态机。TComDataPacket还有MaxLength属性防止包头出现后一直等不到包尾导致缓冲区无限增长。设一个合理值比如 256 或 512超长就丢弃当前帧重新找包头。另一个技巧是TComLed组件它就是个指示灯连到TComPort上有数据收发时闪烁。调试阶段放一个在界面上一眼就能看出端口有没有在动比看日志快。我自己的习惯是新项目先用TComPort加TComDataPacket快速跑通确认协议没问题后如果性能要求高或者协议特别复杂再把解析部分抽出来用独立线程加环形缓冲区重写。这样前期开发快后期优化也有明确方向。串口通信这东西硬件层的不确定性太多软件层能简则简少一个环节就少一个翻车点。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑