简介:面向光伏系统仿真初学者与电气工程方向研究者的实操素材,聚焦利用Simulink复现光伏电池在不同温度与光照强度下的UI、PU特性曲线。通过参数化设置光照与温度输入,可观察最大功率点漂移及效率变化规律,为光伏建模与性能评估提供直观依据。压缩包共2个文件,包含1个m脚本与1个slx仿真模型,整体约24KB,轻量易跑通,脚本负责参数设定与绘图,模型承担光伏阵列与测量环节。已有3123人学习下载,说明该示例在课程实验与入门仿真中具备较高参考价值。读者可据此掌握光伏电池模型参数配置、环境变量扫描、数据记录与曲线绘制流程,并在此基础上开展新型材料假设分析或特定地区发电性能预测,是衔接理论公式与Simulink实操的简洁范例。
1. 先给结论:光伏的 U-I 曲线不是一条线,而是温度与辐照度共同撑开的曲面
很多人第一次在 Simulink 里画光伏 U-I 和 P-U 特性曲线,会把辐照度和温度当成两个“标注变量”:曲线画完,贴个 legend 就算结束。真正跑过 PV MATLAB 参数扫描的人会得到一个反直觉结论:同一块组件在 1000 W/m²、25℃ 下的最大功率点,和 800 W/m²、55℃ 下的最大功率点,不仅数值不同,连曲线拐点的“形状”都会变。温度主要改写开路电压和暗电流,辐照度主要改写光生电流;前者让 P-U 峰值左移,后者让峰值上抬。标题里的 UI 指端电压 U 与电流 I 的关系,PU 指功率 P 与端电压 U 的关系,不是用户界面。搞懂这两条曲线背后的单二极管方程和数值求解过程,才能判断仿真结果到底是“算对了”,还是“只是画出来了”。
2. 单二极管方程:U-I 和 P-U 曲线到底由哪几个物理量决定
光伏电池的工程模型大多从单二极管等效电路出发。它把一个光伏单元看成光生电流源、并联二极管、串联电阻和并联电阻的组合。光生电流随辐照度变化,二极管承担暗电流,串联电阻消耗一部分端电压,并联电阻则代表漏电通道。把这几个量写进一个方程,U-I 曲线的基本形状就定了;再乘上端电压得到 P-U 曲线,最大功率点的位置也就跟着定了。Simulink 里无论怎么搭模块,最后都要落到这个隐式方程上,否则曲线只能靠手动调参“凑形状”。
单二极管模型的电流关系可以写成:
$$ I = I_{ph} - I_0\left[\exp\left(\frac{U + I R_s}{n N_s V_t}\right)-1\right] - \frac{U + I R_s}{R_{sh}} $$
其中 $U$ 是端电压,$I$ 是输出电流,$I_{ph}$ 是光生电流,$I_0$ 是暗饱和电流,$n$ 是理想因子,$N_s$ 是串联电池数,$R_s$ 是串联电阻,$R_{sh}$ 是并联电阻,$V_t=kT/q$ 是热电压。注意 $I$ 同时出现在等式左边和指数项里,所以这是一个隐式方程,不能简单地把 $U$ 代进去直接算出一个 $I$。这个“隐式”是后面数值求解和避坑章节的根。
| 参数 | 物理含义 | 示意量级 | 对曲线的主要影响 |
|---|---|---|---|
| $I_{ph}$ | 光生电流 | 随 G 变化,A 级 | 决定短路电流附近的高度 |
| $I_0$ | 暗饱和电流 | 极小,A 级以下 | 决定开路电压和温度灵敏度 |
| $n$ | 理想因子 | 1 到 2 之间 | 影响指数项陡峭程度 |
| $R_s$ | 串联电阻 | 毫欧到欧姆级 | 增大时填充因子下降 |
| $R_{sh}$ | 并联电阻 | 百欧到千欧级 | 减小时低压段漏电明显 |
| $N_s$ | 串联电池数 | 整数 | 按比例抬高端电压 |
2.1 光生电流与辐照度:为什么 Isc 近似线性而 Voc 只是对数上升
光生电流和辐照度的关系最直观:辐照度越强,单位时间打到 PN 结上的光子越多,能被分离的电子空穴对越多,所以 $I_{ph}$ 近似按 $G/G_{ref}$ 线性缩放。工程上常写成 $I_{ph}=(G/G_{ref})[I_{sc,ref}+\alpha_{Isc}(T-T_{ref})]$。这里 $I_{sc,ref}$ 是参考条件下的短路电流,$\alpha_{Isc}$ 是短路电流温度系数,通常是一个很小的正值。温度升高时,半导体带隙略微变窄,短波响应增强,短路电流会轻微上升,但幅度远没有辐照度的影响大。
短路电流近似等于光生电流,是因为短路时端电压 $U=0$,二极管上的电压主要来自 $I R_s$,在 $R_s$ 很小时这个压降很小,二极管电流可以忽略。也正因为如此,固定温度下把 $G$ 从 200 W/m² 扫到 1000 W/m²,U-I 曲线在纵轴附近的点会近似按比例抬高。很多仿真图里 Isc 不随 G 线性变化,通常不是物理模型的问题,而是 $I_{ph}$ 没乘 $G/G_{ref}$,或者并联电阻在低辐照度下把一部分电流吃掉了。
开路电压的行为不一样。开路时 $I=0$,忽略 $R_{sh}$ 后可以近似得到 $V_{oc}\approx nN_sV_t\ln(I_{ph}/I_0+1)$。辐照度增加时 $I_{ph}$ 线性增加,但 $\ln$ 把这种增长压成了对数关系,所以 Voc 随 G 上升得越来越慢。低辐照度下,$I_{ph}$ 和 $I_0$ 的比值变小,Voc 下降更明显。这个对数关系是判断曲线是否合理的一个硬指标:如果辐照度翻倍,Voc 也跟着近似翻倍,那多半是公式里把对数项写成了线性项。
2.2 暗电流与温度:温度升高时 Voc 和 Pmax 为什么反而下降
温度对光伏曲线的影响,核心在暗饱和电流 $I_0$。$I_0$ 随温度近似按指数规律增大,常用形式是 $I_0=I_{0,ref}(T/T_{ref})^3\exp[(qE_g/(nk))(1/T_{ref}-1/T)]$。温度升高时,指数项里的 $(1/T_{ref}-1/T)$ 为正,$I_0$ 快速增大。虽然热电压 $V_t=kT/q$ 也随温度线性增大,但指数增长远快于线性增长,所以开路电压整体下降。这个符号很容易写反:把 $1/T-1/T_{ref}$ 写进去,温度升高时 $I_0$ 反而减小,曲线就会表现出“温度越高 Voc 越高”的错误趋势。
固定辐照度时,温度升高会让 U-I 曲线整体向左偏移,短路电流略微上抬,开路电压明显下降,填充因子也会变差。P-U 曲线的峰值因此向左下方移动,最大功率下降。工程上常说的“光伏组件有负功率温度系数”,说的就是这件事。仿真时如果只改温度、不改 $I_0$ 的温度项,P-U 峰值可能几乎不动,看起来像“温度只影响开路点”,这不符合单二极管模型的基本行为。
这里还有一个容易混淆的点:$T$ 必须用开尔文。把摄氏度直接代进 $V_t=kT/q$,热电压会小得离谱,指数项变得极陡,牛顿迭代也更容易发散。常见做法是在模型入口统一把温度转换成 $T_K=T_C+273.15$,掩码变量名直接写成T_K,避免后面的人再猜单位。这个命名习惯看起来小事,实际能省掉很多“曲线形状不对”的返工。
2.3 串联电阻、并联电阻与填充因子:曲线拐点不是装饰
串联电阻 $R_s$ 对 U-I 曲线的影响集中在靠近开路电压的拐弯处。电流流过 $R_s$ 会产生压降,端电压 $U$ 不等于结电压,所以曲线在高压段会提前“弯下去”。$R_s$ 越大,填充因子 $FF=P_{max}/(I_{sc}V_{oc})$ 越低,P-U 峰值也越靠左。很多教学模型为了图省事把 $R_s$ 设成 0,曲线看起来更“方”,但和真实组件的拐点差距明显。判断 $R_s$ 是否合理,可以看 U-I 曲线在最大功率点附近的斜率,而不是只看短路点和开路点。
并联电阻 $R_{sh}$ 代表漏电,影响主要在低电压段。$R_{sh}$ 减小时,即使端电压不高,也会有一股电流从并联通道溜走,导致 U-I 曲线在纵轴附近下塌,短路电流看起来变小。低辐照度下这种漏电影响更明显,因为光生电流本身不大,漏电流占比上升。仿真里如果发现 G=200 W/m² 的曲线比 G=400 W/m² 的曲线“塌”得不成比例,先检查 $R_{sh}$ 是不是设得太小,或者是不是把组件级的 $R_{sh}$ 和单电池级的 $R_{sh}$ 混用了。
填充因子是连接 U-I 和 P-U 的桥梁。$FF$ 下降意味着同样 $I_{sc}$ 和 $V_{oc}$ 下能取到的最大功率变小,P-U 峰值变矮。实际调参时,$R_s$ 和 $R_{sh}$ 都能改变 FF,但它们的着力点不同:$R_s$ 主要压高压段,$R_{sh}$ 主要抬低压段。看曲线时先定位“哪一段不对”,再决定动哪个电阻,比一上来就全局最小二乘拟合更可控。
2.4 从 U-I 到 P-U:最大功率点的数学条件
P-U 曲线不是另算一套模型,而是把每个工作点的 $P=UI$ 画出来。最大功率点满足 $dP/dU=I+U(dI/dU)=0$,也就是 $dI/dU=-I/U$。在 U-I 图上,这一点对应曲线切线的斜率等于负的“当前电流除以当前电压”。这句话听起来抽象,实际可以用来做自检:在 P-U 峰值对应的 $U_{mpp}$ 处,U-I 曲线的切线应该和从原点连到该点的直线关于某个方向对称,数值上就是 $dI/dU$ 和 $-I/U$ 很接近。
很多“P-U 峰值和 U-I 拐点对不上”的问题,根源是 P 用的不是同一个工作点的 I。比如电压扫描到某个点,牛顿迭代没收敛,返回了上一次的电流,或者把 mA 和 A 混用,P 的峰值就会漂。还有一种是坐标轴范围不一致:U-I 图画的是 0 到 Voc,P-U 图只画了 0 到 0.8Voc,峰值被截断在边缘。自检时最好同时输出 $U_{mpp}$、$I_{mpp}$、$P_{max}$ 和迭代收敛标志,别只看图。
从设计角度看,单二极管模型已经把温度和辐照度的主要趋势抓住了,但它不是万能的。双二极管模型能更好地描述复合电流,热斑和旁路二极管需要额外的电路拓扑,老化、光谱变化和入射角修正也不在这个方程里。教学和参数扫描用单二极管足够透明;工程验收如果要求高精度,就要在模型边界上做取舍,这一点放到最后一章展开。
3. Simulink 参数化建模:把温度和辐照度做成可扫描的输入而不是硬编码
在 Simulink 里画光伏曲线,最容易犯的错不是方程写错,而是把 G 和 T 写死在模块里。写死之后每换一个工况就要手动改一次参数,图省事的人会复制一堆模型,最后连哪条曲线对应哪个温度都说不清。更稳的做法是把辐照度、温度和电压扫描做成输入,把单二极管方程封装成一个可复用的 MATLAB Function,再用脚本批量扫工况。这样模型只保留一套核心逻辑,曲线多寡由外层脚本控制,参数扫描和绘图才能分开维护。
3.1 模块拓扑:电压扫描、单二极管函数和记录端口怎么接
一个教学用的最小拓扑可以这样组织:用Ramp或Signal Builder产生端电压 $U$,从 0 扫到略低于预估开路电压;用两个Constant模块给出辐照度G和温度T_K;把这三个量送进MATLAB Function,内部用牛顿迭代解出电流I,同时输出P=U*I;最后用To Workspace或Signal Logging记录 U、I、P。这个拓扑里没有把二极管方程拆成 Simulink 方块再反馈回来,所以不会形成代数环,迭代在函数内部完成。
如果非要用 Simulink 基本模块搭指数项和反馈,方程里的 $I$ 会从输出端绕回输入端,形成代数环。Simulink 会提示代数环,或者用一个很小的步长反复迭代,仿真速度掉得厉害。教学演示可以展示这个环,但实际扫描不建议这么搭。把隐式求解收进MATLAB Function,模型边界清晰:外面看到的是“给 U、G、T,出 I、P”的黑盒,里面怎么迭代不影响外层拓扑。这个取舍和第 4 章的数值求解是同一件事的两面。
电压扫描的上限不要直接设成数据表里的 Voc。单二极管方程在开路点附近指数项非常敏感,$U$ 稍微超过真实 $V_{oc}$,电流会变成负值,牛顿法也容易跑飞。常用做法是先估一个 $V_{oc,est}$,扫描到 $0.98V_{oc,est}$ 或 $0.99V_{oc,est}$,画图时再把开路点单独补上。这个细节不影响曲线主体,但能显著减少 NaN 和发散点。
3.2 掩码变量与 SimulationInput:参数扫描不污染基础工作区
参数少的时候,很多人用assignin('base', 'G', ...)在循环里改变量,然后sim一把。这个做法在单次调试时很快,但多层循环加并行时会翻车:基础工作区是全局的,两个并行任务同时改变量,后跑的会覆盖先跑的。更稳的方式是用Simulink.SimulationInput,把每个工况的变量绑定到一次仿真任务上。模型里用G、T_K作为输入变量,脚本里用setVariable注入,基础工作区保持干净。
掩码变量也要注意作用域。把参数封装到 masked subsystem 后,掩码参数会覆盖模型工作区里的同名变量。有人改了基础工作区的pv.Rs,发现曲线没变,就是因为掩码里还留着一个旧值。排查时先看模型属性里的“变量搜索顺序”,再确认掩码参数是不是“Evaluate”或者“Tunable”。如果参数是结构体,建议在模型工作区里定义一份默认值,脚本用SimulationInput.setVariable('pv', pv)整体替换,避免只改半个结构体导致字段不一致。
变量命名也有讲究。G和T太短,容易和 MATLAB 内置函数或循环变量冲突;T尤其危险,因为转置运算符也是',虽然不完全一样,但读代码时容易混。用G_irr、T_K、pv这类名字,单位写在名字里,后面的人一眼能看懂。模型入口处再放一个T_C到T_K的转换,曲线图和参数表都用开尔文,减少“这个温度到底是摄氏还是开尔文”的争论。
3.3 数据记录:logsout、timeseries 与多层循环的存储结构
Simulink 默认的logsout是一个Dataset,每个信号是一个timeseries或timetable。单次仿真时直接out.logsout.get('I').Values.Data就能取到数据;多层循环时,如果把每次结果都塞进同一个变量,后一次会覆盖前一次,最后图上只剩最后一组工况。常见做法是预分配一个结构体数组,每个元素存G、T_C、U、I、P、ok,循环里用索引赋值。这样图例、颜色和工况能一一对应,排查时也知道哪条线是谁。
To Workspace的保存格式也要统一。选Timeseries时,数据在out.I.Data里;选Array时,可能只剩最后一帧;选Structure with time时,字段名又不一样。团队协作时最好在模型里固定一种格式,并在脚本里写一个小的提取函数,把不同格式统一成U、I两列。不要在图里直接写plot(out.I),因为out.I可能是 timeseries 对象,坐标轴会变成时间轴,曲线看起来完全不对。
记录端口还要注意采样时间。电压扫描用Ramp时,如果求解器步长太大,曲线会显得很粗糙,最大功率点附近尤其明显。可以在Ramp后面加一个Rate Transition,或者把仿真步长设成固定步长,步长取扫描总时间的千分之一左右。步长太小会拖慢仿真,太大又丢拐点,这个平衡靠试跑几次确定。P-U 峰值对电压分辨率敏感,建议至少保证峰值附近有几十个采样点。
3.4 一个可复现的扫描脚本
下面这段脚本用SimulationInput批量扫辐照度和温度,模型名用虚构的pv_curve_model,实际使用时替换成自己的模型名。脚本只负责注入变量和收集结果,单二极管方程在模型里的MATLAB Function中实现。
% 批量扫描辐照度与温度,模型名按实际替换 G_list = [200 400 600 800 1000]; % W/m2 T_C_list = [15 25 45 65]; % 摄氏度 nCase = numel(G_list) * numel(T_C_list); simIn(1:nCase) = Simulink.SimulationInput('pv_curve_model'); idx = 0; for iG = 1:numel(G_list) for iT = 1:numel(T_C_list) idx = idx + 1; simIn(idx) = simIn(idx).setVariable('G_irr', G_list(iG)); simIn(idx) = simIn(idx).setVariable('T_K', T_C_list(iT) + 273.15); end end out = sim(simIn); % 返回 SimulationOutput 数组 % 预分配结果结构体,避免循环覆盖 res = struct('G', [], 'T_C', [], 'U', [], 'I', [], 'P', [], 'ok', []); for k = 1:nCase U = out(k).logsout.get('U').Values.Data; I = out(k).logsout.get('I').Values.Data; res(k).G = G_list(floor((k-1)/numel(T_C_list)) + 1); res(k).T_C = T_C_list(mod(k-1, numel(T_C_list)) + 1); res(k).U = U; res(k).I = I; res(k).P = U .* I; res(k).ok = true; % 实际可接迭代收敛标志 end这段脚本的关键点不在语法,而在“变量绑定”和“结果预分配”。setVariable把每个工况的G_irr和T_K绑定到对应的SimulationInput,并行运行时不会互相踩变量;res用结构体数组存每条曲线的完整数据,后面画图、算峰值、做对比都从res取,不再依赖工作区里临时生成的ans或out。ok字段现在先写死为true,实际模型里可以把牛顿迭代的收敛标志一起记录进来,第 5 章会用到它。
如果模型里的MATLAB Function需要结构体参数pv,可以再加一行simIn(idx).setVariable('pv', pv)。结构体字段在扫描过程中保持不变,只改G_irr和T_K,这样参数辨识和工况扫描就分开了。注意Simulink.SimulationInput数组要预分配,循环里不要用simIn(end+1)动态增长,工况多时性能差异很明显。
4. 数值求解:为什么 U-I 方程不能直接“算一个 I 填一个点”
单二极管方程把 $I$ 放在等式两边,还塞进了指数项和 $R_s$ 的乘积里。给定端电压 $U$,要求输出电流 $I$,本质上是解一个非线性方程 $f(I)=0$。如果忽略 $R_s$ 和 $R_{sh}$,可以把方程整理成 $I=I_{ph}-I_0[\exp(U/(nN_sV_t))-1]$,确实能直接算;但这样得到的曲线在高压段会偏乐观,填充因子也不对。想保留 $R_s$ 和 $R_{sh}$,就要接受隐式求解,牛顿法是教学模型里最常用的一种。
4.1 隐式方程:为什么不能把 U 直接塞进指数项
把方程移项得到 $f(I)=I-I_{ph}+I_0[\exp((U+IR_s)/(nN_sV_t))-1]+(U+IR_s)/R_{sh}=0$。这里 $I$ 同时出现在 $f(I)$ 和指数项里,所以不能像显式函数那样“给 U 返回 I”。一个朴素做法是做定点迭代:先猜一个 $I$,代进右边算新的 $I$,反复迭代。这个方法在短路点附近收敛很快,但接近开路点时指数项很陡,定点迭代可能振荡甚至发散。牛顿法用一阶导数修正步长,收敛更快,但需要处理初值、阻尼和溢出。
导数也不复杂。对 $f(I)$ 求导得到 $f'(I)=1+I_0\exp(x)R_s/(nN_sV_t)+R_s/R_{sh}$,其中 $x=(U+IR_s)/(nN_sV_t)$。牛顿更新是 $I_{new}=I-f(I)/f'(I)$。指数项在 $x$ 较大时增长极快,直接算exp(x)可能溢出成Inf,后续f/f'变成NaN。教学代码里常用exp(min(x, 50))做保护,但更好的做法是限制电压扫描范围,并在迭代失败时返回上一次的可行值,而不是硬算到发散。
注意:隐式求解的收敛标志要一路传到绘图脚本,不要只返回一个电流数组。没有收敛标志,后面无法判断 P-U 峰值是不是被坏点带偏。
4.2 牛顿迭代的实现:初值、阻尼和溢出保护
下面这个MATLAB Function版本把单二极管方程和牛顿迭代放在一起。输入是端电压U、辐照度G、温度T_K和参数结构体pv,输出电流I、功率P、收敛标志ok和迭代次数iter。代码里的参数名和前面公式对应,pv.Eg用 eV,pv.I0_ref是参考温度下的暗饱和电流。
function [I, P, ok, iter] = pvOneDiode(U, G, T_K, pv) %#codegen q = 1.602176634e-19; k = 1.380649e-23; Vt = k * T_K / q; % 光生电流:辐照度线性缩放,温度做小正系数修正 Iph = (G / pv.G_ref) * (pv.Isc_ref + pv.alpha_Isc * (T_K - pv.T_ref)); % 暗饱和电流:温度指数项决定 Voc 的温度趋势 I0 = pv.I0_ref * (T_K / pv.T_ref)^3 * ... exp((q * pv.Eg / (pv.n * k)) * (1 / pv.T_ref - 1 / T_K)); I = Iph; % 初值取光生电流,短路点附近很稳 ok = false; maxStep = 0.5 * max(Iph, 1e-3); for iter = 1:80 x = (U + I * pv.Rs) / (pv.n * pv.Ns * Vt); ex = exp(min(x, 50)); % 防止指数溢出 f = I - Iph + I0 * (ex - 1) + (U + I * pv.Rs) / pv.Rsh; df = 1 + I0 * ex * pv.Rs / (pv.n * pv.Ns * Vt) + pv.Rs / pv.Rsh; dI = f / df; if ~isfinite(dI) break; end dI = sign(dI) * min(abs(dI), maxStep); % 阻尼,防大步长跑飞 I = I - dI; if abs(dI) < 1e-9 ok = true; break; end end P = U * I; end代码里Iph的括号不能少:温度修正只作用在参考短路电流上,外面再乘辐照度比例。I0的指数项里(1/T_ref - 1/T_K)的符号决定了温度升高时暗电流增大,如果写成相反的顺序,Voc 的温度趋势就会反。maxStep是一个简单的阻尼上限,取光生电流的一半,防止牛顿步在指数陡峭区跨得太大。exp(min(x,50))是工程保护,x 真到 50 时指数已经很大,正常扫描不应该走到这里;如果经常触发,说明电压扫描上限或初值有问题。
P = U * I放在函数末尾,保证功率和电流来自同一个工作点。ok和iter通过输出端口记录到logsout,后面画图时只保留ok=true的点,P-U 峰值就不会被未收敛的坏点带偏。这个函数可以单独用 MATLAB 测试,也可以放进 Simulink 的MATLAB Function模块,模块输入端口按U、G、T_K、pv的顺序接好即可。
4.3 工作点扫描与负载线:两种求 U-I 曲线的思路
电压扫描是最直观的方法:给一系列端电压,解出对应电流,连起来就是 U-I 曲线。它的优点是横轴均匀,P-U 峰值附近容易加密采样;缺点是接近开路点时方程很敏感,电压稍微超过 Voc 就解出负电流。教学模型里通常把扫描范围卡在 $0.98V_{oc}$ 附近,开路点用近似公式补一个点,或者把最后一个点标成空心,提醒读者这里不是严格解。
另一种思路是负载线法:把光伏电池看成一个非线性电源,外面接一个可变负载电阻 $R_L$,工作点满足 $U=I R_L$ 和 $I=f(U)$。每换一个 $R_L$,电路自然落在某条负载线和 U-I 曲线的交点上。Simulink 的电气网络更接近这种思路,用受控源和电阻搭出电路后,求解器直接找交点,不需要手动扫描电压。负载线法的好处是物理意义清楚,坏处是 $R_L$ 从很小变到很大时,工作点在 U-I 曲线上的分布不均匀,最大功率点附近可能需要加密。
两种方法得到的曲线应该一致,差异主要在数值误差和采样密度。如果电压扫描和负载线法画出的 P-U 峰值差很多,先检查负载线法里的电阻单位是不是欧姆,再检查电压扫描是否在峰值附近采样太稀。负载线法还有一个好处:它能自然处理“电压源直接并联电流源”的拓扑,不会因为理想电压源和理想电流源冲突而报错。教学演示可以先用电网拓扑建立直觉,再用电压扫描批量出图。
4.4 绘图与自检:P-U 峰值、dP/dU 和单位
绘图本身不难,难的是图上的每个点都能追溯。下面这段后处理代码从res结构体里取一组工况,画 U-I 和 P-U 两个子图,并在 P-U 图上标出最大功率点。实际使用时把k换成要看的工况索引,或者在外面套一层循环画多条曲线。
k = 1; % 工况索引,按实际替换 U = res(k).U; I = res(k).I; P = res(k).P; [Pmax, idx] = max(P); Umpp = U(idx); Impp = I(idx); figure; tiledlayout(1, 2); nexttile; plot(U, I, 'LineWidth', 1.2); xlabel('端电压 U / V'); ylabel('电流 I / A'); title(sprintf('U-I, G=%d W/m^2, T=%d C', res(k).G, res(k).T_C)); grid on; nexttile; plot(U, P, 'LineWidth', 1.2); hold on; plot(Umpp, Pmax, 'o', 'MarkerSize', 6); xlabel('端电压 U / V'); ylabel('功率 P / W'); title('P-U'); grid on;这段代码里P = res(k).P必须来自和U、I同一组数据,不能重新用U.*I之外的电流算。max(P)返回峰值和索引,Umpp、Impp用来在 U-I 图上对照拐点。画多条曲线时,图例不要用legend('曲线1','曲线2')硬写,最好用sprintf把 G 和 T 拼进去,避免图例和工况错位。坐标轴标签写清单位,U 用 V,I 用 A,P 用 W;如果数据里电流是 mA,要么先除以 1000,要么在标签上写 mA,别让读图的人猜。
自检可以加一条数值检查:在Umpp附近用差分算 $dP/dU$,看它是否从正变负。还可以检查 $P_{max}$ 是否小于 $I_{sc}V_{oc}$,如果大于,说明电流或电压有一个单位错了。U-I 曲线的短路点电流应该接近 $I_{sc}$,开路点电压应该接近 $V_{oc}$,这两个端点对不上,后面的峰值也不用看了。把这些检查写成脚本里的几行断言,比肉眼盯图可靠。
5. 避坑排查:曲线发散、峰值错位和变量不生效的常见现场
光伏曲线仿真的大部分时间不是花在写方程上,而是花在“图为什么不对”上。下面几条是参数扫描里出现频率很高的现场,每条按现象、原因、解决三段拆开。它们的共同点是:曲线看起来像那么回事,但数值趋势或端点对不上,不查到底就会把错误模型带进后续分析。
5.1 扫描到开路电压附近曲线发散
现象:U-I 曲线在高压段突然翘起或掉到负值,P-U 曲线出现一个不合理的尖峰,控制台里偶尔出现NaN或Inf。把电压扫描范围缩小一点,曲线又正常了。
原因:电压扫描上限超过了真实开路电压,单二极管方程的指数项在 $U+IR_s$ 超过 $nN_sV_t$ 的几十倍后增长极快,exp溢出;牛顿迭代的初值仍然取 $I_{ph}$,在高压段离真解太远,阻尼不够时一步跨到负电流区。另一个常见原因是exp(min(x,50))这种保护把指数截断了,函数值和导数值不再匹配,迭代在截断区来回振荡。
解决:先估开路电压,扫描到 $0.98V_{oc,est}$,开路点单独补;在牛顿法里加阻尼上限,发现dI非有限就退出并返回ok=false;绘图时过滤掉未收敛点,不要用NaN去连线。如果必须在开路点附近取点,可以把初值改成上一次收敛的电流,或者用 Lambert W 函数做解析求解。教学模型不必追求开路点的严格数值,但要把“这个点不是解出来的”标清楚。
5.2 温度升高后开路电压反而变大
现象:固定辐照度,把温度从 25℃ 调到 65℃,U-I 曲线的开路点向右移,P-U 峰值也跟着右移,和“温度升高功率下降”的常识相反。短路电流可能只是略微变化,所以问题不容易第一眼发现。
原因:暗饱和电流 $I_0$ 的温度指数项符号写反了。正确形式里温度升高时 $I_0$ 增大,Voc 下降;如果写成 $(1/T-1/T_{ref})$,温度升高时指数项为负,$I_0$ 反而减小,Voc 就上去了。另一个可能是温度单位没转换,直接把摄氏度代进 $V_t=kT/q$,$V_t$ 小得离谱,温度趋势被数值误差掩盖。
解决:在模型入口统一用T_K = T_C + 273.15,变量名写T_K;检查I0公式的指数项是不是(1/T_ref - 1/T_K);用一组固定 G 跑 T=15、25、45、65 四个点,画 Voc 随 T 的曲线,正常应该单调下降。如果还不对,把I0在参考温度下的值打印出来,看它随温度是增还是减,别只看最终曲线。
5.3 改辐照度后短路电流几乎不变
现象:把 G 从 200 W/m² 改到 1000 W/m²,U-I 曲线在纵轴附近的高度几乎一样,只有开路电压稍微变了一点。图例里的 G 明明改了,曲线却像没生效。
原因:Iph公式里漏了G/G_ref比例,或者G_irr变量没有真正传进模型。前者是方程写错,后者是作用域问题:掩码参数、模型工作区和基础工作区各有一份同名变量,SimulationInput改的是其中一份,模型读的是另一份。还有一种情况是辐照度单位用成了 kW/m²,数值从 1000 变成 1,曲线当然不动。
解决:先在MATLAB Function里打印Iph或把它输出到logsout,确认它随 G 变化;检查Iph=(G/G_ref)*(...)的括号和乘除顺序;用SimulationInput.setVariable注入,不要用assignin在并行循环里改变量;模型里加一个显示G_irr的Display模块,跑一次看读数。低辐照度下还要检查 $R_{sh}$ 是否把电流吃掉太多,G=200 时 Isc 不线性有可能是漏电,不一定是比例项漏了。
5.4 P-U 峰值和 U-I 拐点对不上
现象:P-U 图上的峰值点对应的电压,和 U-I 图上切线斜率为 $-I
本文还有配套的精品资源,点击获取