☰
日置电阻测试仪上位机源码实战:C#串口采集与判定系统
2026/10/1 22:17:27 网站建设 项目流程

简介:面向电气测量与自动化测试开发者的XCS电阻测试软件完整源码包,聚焦C#与日置电阻测试仪的集成控制,完整覆盖串口连接、SCPI命令生成与发送、回显解析、量程切换、阻值换算、断线重连、异常返回码判断等自动化测试闭环,适合正在搭建仪器上位机或学习硬件通信的工程师与学习者。包内共165个文件,主体为30个C#源码文件,配套55个txt文档、24个resources及12个resx界面资源,另有exe/dll/config等可运行与配置内容,整体体积约1.07MB;目录按工程结构组织,能快速定位窗体界面、控制层、业务逻辑与数据库模块。已有393人学习下载。通过源码可直接掌握基于SerialPort类的RS-232通信、按SCPI协议设置量程与读取电阻值的完整写法;同时可理解Windows窗体布局、事件驱动流程、测试结果计算以及SQLite/SQL Server存储等配套实现,并可在现有框架上扩展批量测试、实时曲线与异常报警。对需要二次开发日置电阻仪或学习C#硬件通信的开发者,这份源码在协议对接、界面解耦及数据持久化方面均有清晰示范。

1. 电阻测试软件源码不是让你抄,是让你少走一轮弯路

产线上的日置电阻测试仪,比如 RM3545 这类微电阻计,手测有多麻烦,干过 QC 的都懂:探针夹好,等读数定下来,肉眼比上下限,再手抄进 Excel。一个班次几百个样品,抄错行是常有的事。XCS 电阻测试软件源代码这个项目名经常出现在工控源码分享站里,它不是日置官方出的软件,而是一个流传很广的 C# 上位机方案:用 C# 写串口采集程序,把日置仪表的电阻读数自动收上来,做上下限判定,存成 CSV/Excel,再按批次做统计。适合刚接产线自动化需求、想直接改源码加扫码枪或加 MES 接口的 C# 开发者。这篇就按落地顺序拆:通信方案怎么选、串口怎么读、判定和台账怎么写、最容易翻车的地方在哪。

2. 先把通信方案定死:日置电阻测试仪的数据到底怎么进 PC

2.1 日置电阻测试仪常见的三种对外接口:RS-232C、USB 和 GP-IB

先说结论:产线上改造起来最省事、长期运行最不容易出问题的,是 RS-232C 串口。日置的微电阻计和台式万用表基本都带这个口,DB9 公头,拿一根交叉串口线就能接电脑。USB 口在日置这边大多不是「真 USB 口」,而是内部转成虚拟串口,装完官方 VCP 驱动后在设备管理器里照样显示为 COM 口,程序里按串口处理就行。GP-IB 是老仪器台架才需要的东西,PC 端要插 IEEE-488 板卡,再通过厂商 DLL 或 NI-VISA 访问,C# 源码一旦走这条路,代码量立刻大一个量级。

接口硬件成本参数/速率典型场景程序访问方式
RS-232C一根交叉线9600/19200,8N1单台仪表改造SerialPort 直接读写
USB(VCP)官方驱动同串口笔记本/新工控机先识别 COM 号再 SerialPort
GP-IB板卡+线缆IEEE-488 总线多台仪表组成机架厂商 DLL / NI-VISA

选型时要多问一句现场:这台仪表是固定放在测试台上,还是会被借走?我见过不少项目把 RS-232C 换成 USB,理由是笔记本没有串口,结果换来的是 USB 转出的 COM 号每次都变,程序里写死的 COM4 到了下次开机变成 COM7,工人又不敢自己改,只能等维护的人过来。RS-232C 接法虽然老,但识别机制最简单:插上就是那个口,不会漂。所以只要现场有工控机或台式机带原生串口,我一般会优先建议保留 RS-232C。

2.2 协议比想象中碎:先翻协议手册再写第一行代码

很多第一次搞仪表上位机的人有个错觉,以为所有仪器都遵守同一套 SCPI 标准命令,拿过来就能跑。实际情况是:日置的通信命令整体上接近 SCPI 风格,但每个型号都有自己的命令集和返回格式,RM3545 和 RM3544 之间就有差别,同一型号不同固件版本的返回也可能多一个空格、多一个单位后缀。XCS 这套源码如果作者写的目标型号和你的不一样,直接编译是跑不起来的,第一步永远是核对命令。

常见做法是先用串口助手做一次人工会话验证。把仪表切到 REMOTE/通信模式,串口助手那边设好波特率,手动发一条触发测量命令,比如:MEAS:RES?(具体命令以你手上这台机器的手册为准),看返回是+1.2340E-02这种科学计数法,还是12.3400mΩ这种带单位文本。这一步能把后面所有解析代码的方向定下来:按指数解析,还是按单位后缀分支解析,是完全不同的两种写法。

协议手册里值得拍下来贴在显示器边上的内容有这么几项:测量触发命令、读取结果命令、量程设置命令、触发源设置(连续触发还是单次触发)、返回数据的结束符。日置很多型号默认命令结尾要回车换行,你发的命令不带结束符,仪表就永远不回答,这是新手最先碰到的「看起来全是玄学」的问题之一。

2.3 为什么这类上位机源码几乎都用 C# 写

XCS 标题里明确写着 c#,这其实代表了这个方向的主流选择。做产线数据采集,C# 的 WinForms 或 WPF 有天然优势:System.IO.Ports 里的 SerialPort 开箱即用,收数据和刷新界面的事件模型是现成的,写一个「收到一行数据 → 解析 → 上屏」的循环比大多数语言都顺手。其次,产线程序最终要交出去的是一份能双击运行的 exe,C# 做完直接拷到工控机上就能跑,不像 Python 那套要先配解释器和依赖包。

再往后看集成,C# 的优势更明显:接 MES 走 TCP 有 TcpListener/TcpClient,接报表用 NPOI/OpenXML 生成 xlsx,接扫码枪本质也是串口或 HID 输入,接 PLC 用 Modbus 库或者直接读写网口。一个采集程序从单机版长成产线系统,C# 不需要换语言。相比之下,用 Python 做快速验证确实快,pyserial 十行代码就能读个值,但做成给车间用的正式工具,界面、异常处理、日志、权限这些都得补,工程量反而更大。LabVIEW 在仪器圈也好用,可授权成本和改程序的灵活度都拼不过 C# 源码。

还有一点容易被忽视:车间里的老工控机。这类源码能跑起来的设备,很多还装在 Windows 7 Embedded 或老 Windows 10 上,.NET Framework 4.x 是兼容性最好的目标框架。如果拿到的是 .NET 6/8 源码,编译出来在老系统上大概率缺运行时,要么装环境,要么把代码降级到 .NET Framework 重新编译。这个判断做在项目开始时,能省最后集成阶段的一整轮折腾。

3. 读数的核心循环:串口怎么打开、命令怎么发、结果怎么上屏

3.1 串口枚举与打开:先解决「连不上」而不是「读不到」

写串口程序第一步不是读数据,而是可靠地把端口打开。下面这段是 XCS 这类源码里最常见的一段,我做过的小采集程序也基本长这样:

private SerialPort _port; public bool TryOpenCom(string portName, int baudRate) { try { _port = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One) { ReadTimeout = 2000, // 超时短一点,失败能快速感知 WriteTimeout = 2000 }; _port.Open(); // Open 成功即独占端口 _port.DiscardInBuffer(); _port.DiscardOutBuffer(); // 清掉历史残留,避免解析到旧数据 return _port.IsOpen; } catch (Exception ex) { lastError = ex.Message; // “端口被占用”多半是在这里抛出来 return false; } } // 枚举可用串口,填进 ComboBox string[] ports = SerialPort.GetPortNames();

日置微电阻计的串口默认参数常见是 9600 或 19200,8 数据位、无校验、1 停止位,也就是 8N1。如果你的仪表之前被人改过通信参数,用默认值打开后发命令没有任何响应,先回仪表面板把通信设置恢复出厂再试。ReadTimeout 不建议设成 5000ms 这种大值,自动量程下测大电阻本身要几百毫秒,2000ms 足够;设太大反而会让「仪表没接好」这类故障拖很久才暴露。

有个易忽略的地方:DiscardInBuffer 要在 Open 之后调,不能之前。打开串口时驱动会先把缓冲里的旧字节带出来,不清掉的话,程序刚启动读到的第一行可能是一段上次会话的残留,解析出来就是 NaN 或者一个莫名其妙的负数。

如果现场用的是 USB 虚拟串口,COM 号会漂移,这时需要在枚举列表里做自动过滤。常见做法是用 WMI 查设备描述,把带 HIOKI 字样的设备挑出来:

ManagementObjectSearcher searcher = new ManagementObjectSearcher( "SELECT DeviceID FROM Win32_PnPEntity WHERE Name LIKE '%HIOKI%'");

这条查询拿到的是 COM 号对应的设备实例,再解析出端口名填到界面下拉框。界面默认选中它,而不是让操作员去猜哪个口是对的。

3.2 发命令、读返回值:字符串解析决定数据对不对

打开端口之后的核心操作是「发一条命令,等一行返回」。最直接的做法是同步 ReadLine,但同步读会把 UI 线程卡死,所以现在新写的代码更推荐用 ReadLineAsync:

private async Task<double?> ReadResistanceAsync(CancellationToken ct) { try { _port.DiscardInBuffer(); _port.WriteLine(":MEAS:RES?"); // 具体命令以这台仪表的通信手册为准 string line = await _port.ReadLineAsync(); line = line.Trim().Replace("\r", "").Replace("\n", ""); if (string.IsNullOrEmpty(line)) return null; return ParseHiokiResistance(line); // 把文本转成电阻值,单位统一成欧姆 } catch (TimeoutException) { return null; // 超时统一返回 null,上层决定重试 } }

这里最值得展开的是解析函数。日置仪表返回的可能是+1.2340E-02这种科学计数法,也可能是12.3400mΩ这种带单位后缀的文本。前者直接double.Parse(line, CultureInfo.InvariantCulture)就能拿到以欧姆为单位的值;后者要先截取数字部分,再根据 mΩ、μΩ 后缀按 1e-3、1e-6 换算。解析时一定要用 InvariantCulture,否则换个系统区域设置,小数点在程序里就变成了逗号,一条好好的代码换个机器就翻车。

另一个细节是返回行尾可能带\r\n也可能只带\n,取决于仪表的结束符设置。Trim() 之后还要判断字符串是不是「空命令回显」——有些仪表会把收到的命令原样回发一遍,再接一行结果。这时候只取最后一次完整行才是真正要的测量值。老项目里没有 ReadLineAsync 时会用 DataReceived 事件配合 StringBuilder 攒行,攒到换行符再切出一整条,本质上就是手动处理字符串截断与拼接,逻辑比 Async 版本要绕一些。

3.3 采集循环放后台,UI 只做刷新

XCS 这类源码的界面通常长这样:一个「开始测试」按钮,一个 DataGridView,一个进度条,一个日志框。循环结构本身不复杂,难在别让界面卡死:

private async void btnStart_Click(object sender, EventArgs e) { using var cts = new CancellationTokenSource(); List<double> results = new List<double>(); // 集合按需增长,比固定数组好处理 for (int i = 0; i < totalCount; i++) { double? val = await ReadResistanceAsync(cts.Token); if (val == null) { logBox.AppendText($"第 {i + 1} 次超时,自动重试...\r\n"); i--; // 重试计数:本次不算完成 continue; } results.Add(val.Value); dataGrid.Rows.Add(i + 1, val.Value.ToString("F6"), Judge(val.Value)); progressBar.Value = (int)((i + 1.0) / totalCount * 100); } }

关键点在 async/await 这条链上:ReadLineAsync 挂起时不占 UI 线程,所以 await 之后的 UI 更新可以直接写,不需要手动 Invoke。C# 的委托和事件在这个模型里退到了后台,但如果你拿到的源码是老的 DataReceived 事件版本,那就要记住「事件回调线程里不能直接碰控件」,必须用 BeginInvoke 把刷新动作扔回 UI 线程。这也是很多老源码在 Windows 10 上「偶尔假死」的根因:不是采集问题,是跨线程访问控件被 .NET 拦了。

循环里加超时重试是必要的,但重试要有上限。无限重试会让程序卡在一个坏样品上,后面的料全停着;建议连续 N 次超时(比如 5 次)就停下来弹窗,让操作员检查探针,比程序自己死等要靠谱得多。进度条按「完成数/总数」算,不要按时间算,这样操作员看到 87% 就知道还剩多少个样品。显示位数按仪表实际分辨率定,F6 只是例子,微电阻计测到 μΩ 级时 F6 还不够用。

注意:连续超时重试必须带计数上限,否则一个坏样品能卡住整条线。

4. 判定、台账和统计:一台电阻测试软件的真正价值在 UI 之外

4.1 上下限判定:先统一单位,再做比较

读到的电阻值单位不统一,判定代码第一件要做的事是全部换算成欧姆,然后再跟规格上下限比较。有些需求给的是绝对值上下限,比如「0.998Ω 到 1.002Ω」;有些给的是百分比容差,比如「1Ω ±0.2%」。XCS 这套源码里判定部分通常是一个独立类,好处是换产品型号时不用改采集代码:

public sealed class ResistanceSpec { public string PartNo { get; set; } // 产品型号 public double Nominal { get; set; } // 标称值,单位 Ω public double AbsTol { get; set; } // 绝对容差,如 0.002 public double PctTol { get; set; } // 百分比容差,如 0.2(%) public bool IsPass(double valueOhm) { if (AbsTol > 0) return Math.Abs(valueOhm - Nominal) <= AbsTol; return Math.Abs(valueOhm - Nominal) / Nominal * 100.0 <= PctTol; } }

判定结果不能只在屏幕上显示,产线上更常见的诉求是 NG 时自动报警:界面颜色变红、蜂鸣器响、或者通过 IO 卡给 PLC 一个 NG 信号。颜色和蜂鸣在 C# 里几分钟就能做,PLC 信号要看现场有没有预留点位,没有的话先做软件报警把流程跑起来,不要因为硬件没到位卡住整个项目。

4.2 数据落盘:CSV 兜底,Excel 给报告

测量数据必须边测边写,不能等一批测完再一次写。车间断电、程序崩溃都说不准,一次写整个 List 的做法会在异常时把一整批数据全丢了。CSV 是落盘兜底的最好格式,Excel 只是给人看的报告。写 CSV 用 UTF-8 with BOM,否则记事本打开中文全乱码:

private void AppendCsv(string path, string partNo, string sn, double valueOhm, bool pass) { string line = string.Format("{0},{1},{2:F6},{3},{4:yyyy-MM-dd HH:mm:ss}", partNo, sn, valueOhm, pass ? "PASS" : "FAIL", DateTime.Now); File.AppendAllText(path, line + Environment.NewLine, new UTF8Encoding(true)); }

文件名建议带批次信息,比如RES_20250614_A2.csv,这样按日期和台架号就能把文件定位出来。XCS 源码里如果只有「按启动时间命名一个文件」的逻辑,通常要改成「按产品型号+批量号命名」,否则多批次混在一个文件里,后续做追溯要自己写筛选,很麻烦。

Excel 报告用 NPOI 生成比较省事,xlsx 和旧版 xls 都支持,不用在工控机上装 Office。报告不用太复杂:一个参数头(产品型号、数量、判定结果)、一列测量值、一个按序列号的明细表就够。复杂图表在企业里反而没人看,车间最常用的是「这一批的合格率是多少」。

如果扫码枪已经接上,SN 这个字段别只放在列表里,CSV 第一列固定写 SN,这样后续任何工具都能按 SN 排序或筛选。操作员姓名、班次建议放在启动界面让人选,别从 Windows 登录名读——车间好几台机器共用一个账号,读出来没意义。每条数据带上时间戳到秒,做产线分析时能跟其他设备对齐时序。

4.3 批次统计:平均值、极差、不合格率要算对口径

统计逻辑看着简单,实际最容易在口径上出错。默认情况下,统计范围应该是「本次启动后测过的全部样品」,但程序里如果有人手动点了「跳过」或「重测」,合格率分母会乱。我的习惯是:数据流只从判定模块进统计模块,跳过和重测不进统计,这样报表口径永远一致。

统计项怎么算注意点
平均值总电阻值 / 样品数单位统一成 Ω 后再算,别拿 mΩ 混着除
极差最大值 - 最小值直接反映本批一致性,比方差直观
不合格率NG 数 / 总样品数分母必须是「全部实测样品」,不是「测完没跳过的」
CPK(USL-均值)/(3σ) 等公式少于 25 个数据算出来的 CPK 没有参考意义

CPK 这个词在电子厂很响,但很多人不清楚算法前提:样本量不够时,σ 的估计值波动很大,CPK 会给你一个「看起来精确、实际不可信」的数。至少攒 25 个样品再开算,报表里把样本数标出来,避免后续和质量部门扯皮。标准差计算用 double 累加没问题,但要注意用「样本标准差」公式,分母是 n-1,不是 n,这个细节在我见到过的源码里错了一半以上。

5. 避坑:日置仪表 + C# 串口常见的五个翻车点,现象、原因、解决

5.1 端口能打开,命令发出去仪表理都不理

现象:串口打开成功,WriteLine 也不报错,但永远收不到返回。

原因:最常见是仪表没切到 REMOTE/通信模式,面板上还停在手动测量;其次是命令文本不对,或命令结尾缺结束符。

解决:先把仪表面板切到远程/通信设置,确认主界面有 REMOTE 之类字样;再用串口助手手动发一条命令验证,排除程序问题。注意结束符,常见顺序是命令文本加\r\n,很多 C# 新手只发文本忘了换行,仪表一直等着,表现就是「毫无反应」。这个问题排查起来最费时间,因为程序本身没有异常,看起来一切都对,就是没数据。

5.2 读数大部分对,隔一会儿错一位或者乱码

现象:连续测 50 个,有一两个值差了 10 倍,或者出现#、?字符。

原因:自动量程切换瞬间仪表返回格式变化;或者 USB 转串口的驱动缓冲区时序偶发问题;或者解析时用了系统区域设置,小数点是逗号。

解决:解析统一用 InvariantCulture;对返回的字符串先 Trim 再判断是不是空;量程锁定在固定档位可以彻底解决「量程切换导致位数变化」的问题,代价是测量范围受限。如果乱码集中在 USB 转串口上,换一根驱动更成熟的转串口线,或者直接换原生串口试。量程开关在代码里要留配置项,不要在源码里写死,否则换产品规格又要翻代码。

5.3 点开始测试后窗口假死,拖都拖不动

现象:点按钮后界面卡住,过一会才恢复,严重时直接被系统提示「未响应」。

原因:读取串口的同步调用或 Thread.Sleep 写在了 UI 线程里。

解决:改成 async/await + ReadLineAsync;如果是老项目用 DataReceived 事件,回调里绝对不能直接碰控件,用 BeginInvoke。改完之后还要注意,取消按钮要能触发 CancellationToken,别让线程一直在等超时。这个改动不复杂,但要把循环里所有的 Thread.Sleep 都删干净,否则只改一处,界面照样卡。

5.4 P/Invoke 调原生 DLL 崩出 access violation c0000005

现象:源码里通过 DllImport 调用仪器厂商或第三方原生 DLL,运行一段时间后崩溃,崩在调用处。

原因:最常见是委托被垃圾回收,原生 DLL 持有了一个已被回收的回调函数指针;其次是结构体布局没有按 C 的排列声明。

解决:把回调委托声明为静态字段,或者用 GCHandle.Alloc 把它固定住,防止 GC 收走;结构体用 [StructLayout(LayoutKind.Sequential)] 显式声明,字段顺序和 C 头文件逐一对齐。这个错误在 C# 调用 C++ 库的场景里非常典型,源码里如果已经出现 c0000005,多半不是偶发,而是生命周期写错了,不是重试能绕过去的。

5.5 USB 虚拟串口的 COM 号会漂移,写死的 COM4 第二天就失效

现象:昨天还能连,今天开机报「找不到端口」,设备管理器里串口号从 COM4 变成了 COM9。

原因:USB 枚举顺序变化,Windows 分配给虚拟串口设备的 COM 号跟着变。

解决:不要写死 COM 号,程序启动时枚举一遍所有串口,通过设备管理器里查到的型号描述自动匹配。匹配方式可以用 WMI 查询 Win32_PnPEntity 里含 HIOKI 字样的设备,拿到对应 COM 号再打开。如果驱动装得不全,设备显示的是「USB 串行设备」不是 HIOKI 字样,那就让维护人员先重装驱动,别在代码里做一堆猜端口的逻辑来兜底。

6. 再往上走一步:给采集站接上 PLC、扫码枪和 MES

一个单机采集程序在车间里通常只活得过第一个月,后面一定会被要求「把这个数据传到系统」。常见接法有三种:

要接的东西常见协议C# 里怎么实现源码要改的位置
PLC 输出 NG 信号IO 卡 / Modbus TCPNModbus 库读写线圈判定结果处加输出
扫码枪绑定 SN串口 HID / 键盘仿真串口读或全局键盘钩子采集循环前先读 SN
MES 上传数据HTTP JSON / TCPHttpClient 或 TcpListenerCSV 写入后异步推送

我通常会先把扫码枪接上,因为 SN 是追溯的主键,没有 SN 的测量数据价值打对折。扫码枪如果是键盘仿真模式,界面里放一个只读的输入框抢到焦点就能拿到 SN;如果是串口模式,就按前面串口那套读。拿到 SN 之后,CSV 第一列写它,判定和统计维持原样,改动最小。

改 MES 推送时有一个教训:不要在采集线程里同步发 HTTP,网络一慢整个测量节奏就卡住。正确做法是生产者-消费者队列,采集线程只往队列里放,后台线程批量推送,推失败先落本地磁盘,下次启动补传。

我后来做这类程序的习惯是:所有 COM 号、仪表型号、产品规格全部放配置文件,绝不明文写死在代码里;凡是能自动识别的绝不让人手填。这样每换一个台架只需要改配置,不用重新编译。一个采集程序交到车间,可靠运行半年没被叫过去改代码,才算真正做完。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询