简介:这是一个matlab源表驱动程序,用于keithley2400源表的数据采集和控制。面向科研、高校实验室和电子产品制造商中使用matlab与源表联调的工程师与研究人员,解决精确电性能参数测量与自动化测试流程构建需求。压缩包共25个文件,以21个m脚本/函数文件为主,附带说明文档、许可证及配置文件,整体仅33KB,结构紧凑。已有91人学习下载。该驱动程序不仅支持设置电压/电流范围、测试点数和采样速率,还能实时读取测量数据并在matlab中绘图、拟合和统计分析;同时包含错误处理与安全保护逻辑,并提供测试脚本与验证函数,便于用户二次开发。对于需要搭建自动化测试系统或提升源表控制效率的读者,具备直接参考和复用价值。
1. 用 MATLAB 调 Keithley 2400,先想清楚通信层
Keithley 2400 是一台源表(SourceMeter),既能输出电压电流,又能测量回读,这在半导体器件 I-V 特性测试中几乎是标配。MATLAB 控制它最常见的路径是 SCPI 命令加 GPIB/USB 串口,而不是依赖官方那套重量级测试软件。matlab-keithley-jv 驱动包走的就是这条路:把底层的 con_a_b、con_on_off、check_port 这类函数拆开,让使用者自己组合出电压扫描、电流监测、数据回读的逻辑。包内还带了一个专门做参数校验的 Common 目录,这对源表驱动很关键,因为 2400 最高输出 210V、10A,一个越界命令就可能烧器件。这个包适合两类人:一是高校实验室里想快速搭起可重复自动测试流程的科研人员,二是做器件可靠性测试的工程师,希望把源表每一步操作都显式写进脚本,而不是被黑盒挡住。
2. 驱动包的基石:连接、端口探测与仪器对象封装
2.1 先拆开压缩包,看函数分工
解压之后,顶层除了 README.md、LICENSE、.gitignore,还有一个 CommonDevice/Keithley 目录和 Common 目录。CommonDevice/Keithley 里主要放针对 2400 的设备定义,Common 目录里就是驱动核心函数。它们的关系很简单:Common 里的函数不关心具体是 2400 还是 2450,只负责连接、校验、错误读取这类通用工作;设备相关配置放在 CommonDevice/Keithley 里。
matlab-keithley-jv/ ├── README.md ├── LICENSE ├── .gitignore ├── classKeithley2400_testscript.m ├── CommonDevice/ │ └── Keithley/ ├── Common/ │ ├── check_port.m │ ├── con_a_b.m │ ├── con_on_off.m │ ├── check_boolean.m │ ├── check_string.m │ ├── check_value.m │ ├── validationFcn.m │ └── getErrorMessage.m这些文件的分工可以列成一张表,方便你判断改哪些、不该动哪些。这张表也是后续读代码时的索引,遇到问题先查对应函数,而不是满目录乱翻。
| 函数 | 作用 | 什么时候要动它 |
|---|---|---|
| check_port.m | 探测 GPIB 控制器或串口是否被占用 | 换电脑后连不上设备时 |
| con_a_b.m | 建立 MATLAB 到源表的连接对象 | 改变通信方式(GPIB 换 USB)时 |
| con_on_off.m | 打开或关闭源表输出 | 一般不用改,直接调用 |
| check_value.m | 检查数值参数是否越界 | 自定义量程时需要同步修改 |
| check_boolean.m | 检查逻辑开关参数 | 基本不用改 |
| check_string.m | 检查字符串枚举参数 | 要加入新功能代码时 |
| validationFcn.m | 参数校验的统一入口 | 需要把校验合并到批量测试中 |
| getErrorMessage.m | 读取仪器错误队列并格式化 | 报错信息不够用时改这里 |
这套设计的核心思想是:凡是会发给 2400 的参数,都先经过一层校验。源表不是普通仪器,输出一个超出量程的电压可能就是一次事故,所以校验函数在驱动包里的地位和通信函数一样高。很多从 GitHub 上拉下来的驱动包只关注"怎么发命令",而这个包额外花了一半篇幅做校验,用意就在这里。
2.2 连接建立:GPIB、串口、VISA 到底怎么选
2400 前面板通常有 GPIB 和 RS-232 接口,后期型号还支持 USB。MATLAB 里对应三种打开方式:老版gpib对象、serial对象、以及更通用的visa对象。我一般优先用 VISA,因为如果实验室同时有 NI 和 Keysight 的 VISA 驱动,visa可以统一管理地址,不用关心底层是 GPIB 还是 USB。
% 方式一:VISA 连接(常用,地址由 NI MAX 或 Keysight Connection Expert 提供) k = visa('ni', 'USB0::0x05E6::0x2400::202026::0::INSTR'); fopen(k); % 方式二:老式 GPIB 卡,地址 24 % k = gpib('ni', 0, 24); % fopen(k); % 发一个 ID 查询,验证连接和设备型号 fprintf(k, '*IDN?'); idn = fscanf(k); disp(idn);visa的对象创建参数 'ni' 表示使用 NI-VISA 驱动库,后面那串是 VISA 资源地址,每个设备都不同。如果你用串口,直接创建serialport对象更快,但要注意 2400 的串口默认波特率是 9600,数据位 8,停止位 1,无校验,设置错了会收到乱码。执行fprintf(k, '*IDN?')时,MATLAB 会自动在命令末尾追加 LF 符,2400 的 SCPI 解析器接受这种换行。
这里有个新手容易踩的坑:直接用fopen失败。常见原因是之前连接没释放。先执行instrfind查看残留对象,再用delete(instrfindall)清掉,包里的 check_port.m 做的就是这类检查。它会把上一次异常退出留下的通信对象清理干净,避免新对象创建时报 "Object is busy"。
2.3 用 con_a_b 和 con_on_off 串起安全启动流程
连接和输出控制应该写成独立函数,而不是散落在每个测试脚本里。包里的 con_a_b.m 抽象了连接过程,con_on_off.m 管理输出继电器的开关。这里给出我习惯的写法和调用逻辑。
function [obj, ok] = con_a_b(addr, mode) % 建立到 Keithley 2400 的连接 % addr —— GPIB 地址(数字) 或 VISA 完整地址(字符串) % mode —— 'gpib' 或 'visa',默认 gpib if nargin < 2 mode = 'gpib'; end ok = false; switch mode case 'gpib' obj = gpib('ni', 0, addr); otherwise obj = visa('ni', addr); end try fopen(obj); idn = query(obj, '*IDN?'); if contains(idn, 'MODEL 2400') ok = true; end catch ME warning('连接失败: %s', ME.message); obj = []; end end这段代码把连接过程包成一个函数,返回可用对象和布尔状态。query内部执行先写后读,比手动fprintf加fscanf更不容易卡住。contains(idn, 'MODEL 2400')的作用是顺手验证设备型号,避免你把 GPIB 地址误接到别的仪器上。.
连接建立后,输出控制要遵循一个铁律:测试结束或者脚本崩溃,都必须执行:OUTP OFF。con_on_off.m 就负责这个动作。
function con_on_off(obj, state) % 打开/关闭源表输出,并回读状态确认 switch state case 'on' fprintf(obj, ':OUTP ON'); case 'off' fprintf(obj, ':OUTP OFF'); end status = query(obj, ':OUTP?'); disp(['OUTP = ', strtrim(status)]); end注意:OUTP?返回1或0,不是'ON'/'OFF',所以调试时不要直接拿字符串比对。这里的回读机制是为了确认命令已生效,因为 2400 在本地锁定时会拒绝远程输出指令,那种情况下回读值会告诉你实际输出状态没变。
3. 参数校验与状态控制:写好安全的源表驱动函数
3.1 为什么要单独一层参数校验
很多人在网上找的 MATLAB 源表控制代码都是直接fprintf(obj, ':SOUR:VOLT %.2f', V),这在小电压实验里没问题,但拿到 2400 上就有隐患。2400 的电压量程是 200mV 到 200V,电流量程是 1uA 到 1A(高功率档到 10A 但需要特殊接线)。如果脚本里算出来的电压因为单位转换错误变成了 2100V,命令会发到仪器端才报错,而此时设备可能已经在错误状态下跳闸。更严重的情况是保护限值(compliance)设得太大,器件被过流击穿却没有任何警示。
所以这个驱动包在 Common 目录里放了一组校验函数:check_value、check_boolean、check_string,以及一个总入口 validationFcn。它们做的事可以这么理解。
function check_value(value, range, name) % 检查数值 value 是否落在 range 区间内 % range = [min, max] if ~isscalar(value) || ~isnumeric(value) error('%s 必须是一个标量数值', name); end if value < range(1) || value > range(2) error('%s 超出允许范围 [%g, %g],当前值: %g', ... name, range(1), range(2), value); end end比如设置电压前,我会调用check_value(V_set, [0, 210], '输出电压'),把单位错误在发指令前就拦下来。isscalar是为了防止某个计算步骤返回了向量,导致命令行拼接出错。范围上下限要按实际测试需求收紧,不一定用满量程。
check_boolean.m 用来处理像是:OUTP ON这样的开关参数,防止有人传进来'1'或'true'这类跟 SCPI 不兼容的写法。check_string.m 则是枚举检查,比如':SENS:FUNC'只允许'CURR'或'VOLT',传了'current'就直接在本地报错,而不是浪费一次仪器通信。
3.2 封装 set_voltage + measure_current
校验只是前置,真正的测量是发命令、等读数、取数据三步。下面这个函数是我通常放在驱动包里的核心测量接口,它把电压设置、电流保护、量程选择、一次读取打包在一起。
function [V_meas, I_meas] = k2400_measure(obj, V_set, compliance) % 设置输出电压,等待稳定后读取电流 % 输入: % V_set 输出电压设定值,单位 V % compliance 电流保护限值,单位 A % 输出: % V_meas 回读电压(实际输出电压) % I_meas 回读电流 fprintf(obj, ':SOUR:VOLT:LEV %.6f', V_set); fprintf(obj, ':SOUR:VOLT:RANG 20'); fprintf(obj, ':SENS:CURR:PROT %.6f', compliance); fprintf(obj, ':SENS:FUNC "CURR"'); fprintf(obj, ':FORM:ELEM CURR,VOLT'); data = sscanf(query(obj, ':READ?'), '%f,%f'); I_meas = data(1); V_meas = data(2); end这段代码的关键在于:FORM:ELEM CURR,VOLT,它决定:READ?返回两个值的顺序。不设置这一行,默认可能只返回主测量量,那就拿不到同时回读的电压了。:SENS:CURR:PROT是电流 compliance,设置成 10mA 表示输出一旦超过这个电流,2400 会立刻进入保护模式,不再继续加压。
调用时注意:compliance 一定不要设得过松。比如测二极管正向特性,设定 100mA 保护其实是靠被测回路自己的电阻在限流;测纳米线或者薄膜电容,建议从 1mA 开始试,确认安全再逐渐放宽。
3.3 用 testscript 结构把安全序列固定下来
包里的 classKeithley2400_testscript.m 就是这类流程的模板。把它拆开看,结构是连接、校验、测量、关闭四段。
obj = con_a_b(24, 'gpib'); try check_value(3, [0, 10], '测试电压'); [V_meas, I_meas] = k2400_measure(obj, 3, 0.01); fprintf('V = %.6f V, I = %.6f A\n', V_meas, I_meas); catch ME getErrorMessage(obj); rethrow(ME); finally con_on_off(obj, 'off'); fclose(obj); end注意 finally 里的顺序:先关输出,再关对象。如果反过来,源表可能保持输出状态,下一次接上设备时直接带电,这对某些敏感器件是致命的。getErrorMessage(obj)放在 catch 里,是为了在 MATLAB 抛出异常前先把仪器端错误队列榨干,拿到真正的失败原因。没有这个调用,你看到的可能就是一条笼统的 "Unexpected error"。
4. 自动化 I-V 扫描:时序、采样与实时绘图
4.1 用循环扫描而不是内置 sweep 的原因
2400 本身有内置扫描引擎(:SOUR:VOLT:STAR、:SOUR:VOLT:STOP、:SOUR:VOLT:STEP加:TRIG:COUN),能在仪器内部完成整条 I-V 曲线。但实际在 MATLAB 里,我几乎总是用循环一个点一个点地扫,而不是直接触发内置 sweep。原因有三个:一是内置 sweep 的数据会存在仪器缓冲里,完成后再一次性传回 MATLAB,中间无法实时判断是否已经发生击穿,也没法提前终止;二是循环方式可以把每个点的源延迟独立控制,遇到异常点可以快速回退重测;三是后续要接优化循环或者把结果实时画出来,循环方式更自然。
obj = con_a_b(24, 'gpib'); V_start = -5; V_stop = 5; V_step = 0.2; V_list = V_start:V_step:V_stop; I_list = zeros(size(V_list)); fprintf(obj, ':SOUR:FUNC VOLT'); fprintf(obj, ':SOUR:VOLT:RANG 20'); fprintf(obj, ':SENS:FUNC "CURR"'); fprintf(obj, ':SENS:CURR:PROT 0.01'); con_on_off(obj, 'on'); for n = 1:numel(V_list) fprintf(obj, ':SOUR:VOLT:LEV %.6f', V_list(n)); pause(0.05); I_list(n) = str2double(query(obj, ':MEAS:CURR?')); if abs(I_list(n)) >= 0.01 warning('达到保护限值,扫描中止于 %.2f V', V_list(n)); break; end end con_on_off(obj, 'off');这里的pause(0.05)是留给源表输出建立稳定状态的时间。更严格的做法是设置:SOUR:DEL 0.05,让源表自己保证延迟;不过:SOUR:DEL在循环中同样每次生效。每次查询:MEAS:CURR?都会阻塞到测量完成,所以实际点间隔不是严格 50ms,而是 50ms 加一次测量周期,测量周期由 NPLC 决定。
4.2 NPLC 与采样速率的取舍
Keithley 2400 里的 NPLC 是 Power Line Cycle 数,1 个 NPLC 等于一个工频周期,50Hz 供电下就是 20ms。NPLC 越大,积分时间越长,噪声越低,但速度越慢。这个参数对 I-V 曲线质量的影响非常大,很多人扫描出来的曲线毛刺多是直接把 NPLC 设到了 0.01。
| NPLC | 50Hz 下积分时间 | 噪声特性 | 适合场景 |
|---|---|---|---|
| 10 | 200ms | 最低 | 弱电流测量、电容漏电 |
| 1 | 20ms | 好 | 常规 I-V 曲线 |
| 0.1 | 2ms | 尚可 | 初步快速扫描 |
| 0.01 | 0.2ms | 高 | 只看趋势,不作数值依据 |
把 NPLC 写进脚本是这个驱动包很容易扩展的一个点。只要在扫描前加一句fprintf(obj, ':SENS:CURR:NPLC 1'),就能统一控制积分时间。注意,NPLC 是针对测量通道的参数,如果你用:READ?而不是:MEAS?,这个设置在两种命令下都有效。
4.3 扫描结果的实时绘图与数据落盘
循环扫描的好处之一是可以边扫边画点,这样长时间测试时不需要等全部跑完才知道曲线长什么样。简单画法是这样的。
figure('Name', 'I-V Curve'); for n = 1:numel(V_list) plot(V_list(1:n), I_list(1:n), 'b.-'); xlabel('Source Voltage (V)'); ylabel('Source Current (A)'); grid on; drawnow; enddrawnow会让 MATLAB 在每次循环里刷新图形窗口,否则图像只在循环结束后更新。对于几百个点的扫描,这个延时可以忽略,但对 0.1NPLC 下的快速扫描会有明显影响,所以我通常保留drawnow并用V_list(1:n)只重画已采集的数据段。
扫描完成后,数据落盘和参数提取是下一步。用writetable可以直接生成 CSV,方便 Origin 或 Excel 继续处理。
T = table(V_list', I_list', 'VariableNames', {'Voltage_V', 'Current_A'}); writetable(T, 'iv_data.csv'); % 线性区电阻估算:斜率倒数即电阻 idx = V_list > -1 & V_list < 1; p = polyfit(V_list(idx), I_list(idx), 1); R_est = 1 / p(1); fprintf('Estimated resistance: %.3f ohm\n', R_est);这段代码最后一段做了线性拟合,polyfit返回[斜率, 截距],斜率是电流对电压的导数,也就是电导,所以电阻是它的倒数。对于二极管或太阳能电池这类非线性器件,线性拟合只能在很小的某个电压窗口内成立,实际使用时要根据器件类型选择拟合区间。
5. 进阶:错误捕获、SCPI 排错与二次封装技巧
5.1 用 getErrorMessage 抠出仪器端细节
Keithley 2400 的 SCPI 错误队列用:SYST:ERR?逐条读取。这个驱动包里的 getErrorMessage.m 就是循环读队列的函数。
function msg = getErrorMessage(obj) msg = ''; while true s = query(obj, ':SYST:ERR?'); if contains(s, 'No error') break; end msg = [msg, strtrim(s), newline]; end if isempty(msg) msg = 'No error'; end end调用位置放在catch ME之后、rethrow之前。例如-113 Undefined header表示 SCPI 路径拼写错误,-410 Query interrupted表示命令未完成就发查询。这些只有读仪器队列才看得到,比单纯看 MATLAB 异常信息有用得多。
5.2 连接故障速查
下表是几种高频故障的定位方向,按先检查连接、再检查命令的顺序排查。
| 现象 | 原因 | 处理 |
|---|---|---|
fopen超时 | GPIB 地址错、板卡未初始化 | instrhwinfo('gpib'),核对地址 |
| 返回乱码 | 串口参数不一致 | 设 9600-8-N-1 |
| 查询卡住 | 命令没以 LF 结尾 | 用fprintf不用fwrite |
报-113 | SCPI 拼写错误 | 发:SYST:ERR?看明细 |
| 读值 NaN | 输出未开启或量程冲突 | 先:OUTP ON |
排错时最有效的一条命令是query(obj, ':SYST:ERR?')。每次报错先读一遍,大多数问题能当场定位。
5.3 把驱动封装成 handle 类,做二次开发
Common 目录里的函数是底层工具,真正面向业务的是设备类。把包内函数组装成一个 classdef,可以让测试脚本只关心测量逻辑。
classdef Keithley2400 < handle properties (Access = private) Instrument end methods function obj = Keithley2400(addr, mode) obj.Instrument = con_a_b(addr, mode); if isempty(obj.Instrument) error('连接失败'); end end function delete(obj) con_on_off(obj.Instrument, 'off'); fclose(obj.Instrument); end function I = measureCurrent(obj, V) [~, I] = k2400_measure(obj.Instrument, V, 0.1); end end end做成类之后,扫描、限流、报错处理都变成方法,脚本里只需k.measureCurrent(v)。实际项目中还可以在类里加串口重连、GPIB 地址扫描、批量队列测试这些功能,那就是在驱动层之上扩展出一套设备管理层了。
本文还有配套的精品资源,点击获取