第八章:MATLAB 与 Simulink 交互
8.1 从 MATLAB 控制 Simulink
8.1.1 sim() 函数运行仿真
sim() 是从 MATLAB 脚本或函数中运行 Simulink 仿真的核心函数。
基本用法:
matlab
% 最简单的用法
sim('mymodel')
% 指定仿真时间
sim('mymodel', 10) % 仿真到 t=10s
sim('mymodel', [0, 10]) % 从 t=0 到 t=10s
% 获取仿真输出
simOut = sim('mymodel');
% 指定参数并获取输出
simOut = sim('mymodel', ...
'StopTime', '10', ...
'Solver', 'ode45', ...
'MaxStep', '0.01');
获取仿真结果:
matlab
% 运行仿真
simOut = sim('mymodel', 'StopTime', '10');
% 获取时间向量
t = simOut.tout;
% 获取输出端口数据(模型中需要有 Outport 模块)
y = simOut.yout{1}.Values.Data;
% 获取 To Workspace 模块的数据
% (模块变量名设为 'output_data')
output = simOut.output_data;
% 获取日志信号数据
logsout = simOut.logsout;
signal1 = logsout.getElement('signal_name');
data = signal1.Values.Data;
time = signal1.Values.Time;
错误处理:
matlab
try
simOut = sim('mymodel', 'StopTime', '10');
disp('仿真成功完成');
catch ME
fprintf('仿真出错:%s\n', ME.message);
fprintf('错误位置:%s\n', ME.identifier);
end
8.1.2 set_param/get_param 修改参数
get_param:获取模型或模块参数
matlab
% 获取模型参数
stopTime = get_param('mymodel', 'StopTime');
solver = get_param('mymodel', 'Solver');
stepSize = get_param('mymodel', 'MaxStep');
% 获取模块参数
gainValue = get_param('mymodel/Gain1', 'Gain');
stepFinal = get_param('mymodel/Step', 'After');
% 获取所有参数(返回结构体)
allParams = get_param('mymodel/Gain1', 'ObjectParameters');
disp(fieldnames(allParams)) % 查看所有可用参数名
set_param:设置模型或模块参数
matlab
% 设置仿真参数
set_param('mymodel', 'StopTime', '20');
set_param('mymodel', 'Solver', 'ode15s');
set_param('mymodel', 'MaxStep', '0.001');
set_param('mymodel', 'RelTol', '1e-6');
% 设置模块参数
set_param('mymodel/Gain1', 'Gain', '5.0');
set_param('mymodel/Step', 'After', '2.0');
set_param('mymodel/Step', 'Time', '1.0');
% 注意:参数值必须是字符串!
% 错误:set_param('mymodel/Gain1', 'Gain', 5.0)
% 正确:set_param('mymodel/Gain1', 'Gain', '5.0')
% set_param('mymodel/Gain1', 'Gain', num2str(5.0))
动态修改参数的技巧:
matlab
% 使用 num2str 转换数值为字符串
Kp = 2.5;
set_param('mymodel/PID/Gain_P', 'Gain', num2str(Kp))
% 使用 sprintf 格式化
set_param('mymodel/Gain1', 'Gain', sprintf('%.4f', Kp))
% 修改向量参数
A = [1 2; 3 4];
set_param('mymodel/Gain1', 'Gain', mat2str(A))
% mat2str([1 2; 3 4]) → '[1 2;3 4]'
8.1.3 批量仿真(参数扫描)
参数扫描是 Simulink 最强大的功能之一,可以自动运行多次仿真。
方法一:循环修改参数
matlab
%% 参数扫描示例:扫描 PID 增益 Kp
% 定义扫描范围
Kp_values = [0.5, 1.0, 2.0, 5.0, 10.0];
results = struct();
for i = 1:length(Kp_values)
Kp = Kp_values(i);
% 更新工作区变量(模型中引用 Kp 变量)
assignin('base', 'Kp', Kp);
% 运行仿真
simOut = sim('pid_demo', 'StopTime', '10');
% 提取结果
t = simOut.tout;
y = simOut.yout{1}.Values.Data;
% 计算性能指标
steady_state = y(end);
error_ss = abs(1 - steady_state); % 稳态误差(参考值=1)
% 计算上升时间(10%~90%)
idx_10 = find(y >= 0.1, 1);
idx_90 = find(y >= 0.9, 1);
if ~isempty(idx_10) && ~isempty(idx_90)
rise_time = t(idx_90) - t(idx_10);
else
rise_time = NaN;
end
% 保存结果
results(i).Kp = Kp;
results(i).steady_state = steady_state;
results(i).error_ss = error_ss;
results(i).rise_time = rise_time;
results(i).t = t;
results(i).y = y;
fprintf('Kp=%.1f: 稳态误差=%.4f, 上升时间=%.3fs\n', ...
Kp, error_ss, rise_time);
end
%% 绘制对比图
figure;
hold on;
colors = lines(length(Kp_values));
for i = 1:length(Kp_values)
plot(results(i).t, results(i).y, ...
'Color', colors(i,:), 'LineWidth', 1.5, ...
'DisplayName', sprintf('Kp=%.1f', results(i).Kp));
end
plot([0 10], [1 1], 'k--', 'LineWidth', 1, 'DisplayName', '参考值');
xlabel('时间 (s)');
ylabel('输出');
title('不同 Kp 值的阶跃响应对比');
legend('Location', 'best');
grid on;
方法二:使用 parsim(并行仿真,更快)
matlab
%% 使用 parsim 并行运行多次仿真
% 创建仿真输入数组
Kp_values = [0.5, 1.0, 2.0, 5.0, 10.0];
n = length(Kp_values);
% 构建 SimulationInput 数组
for i = 1:n
simIn(i) = Simulink.SimulationInput('pid_demo');
simIn(i) = simIn(i).setVariable('Kp', Kp_values(i));
simIn(i) = simIn(i).setModelParameter('StopTime', '10');
end
% 并行运行(需要 Parallel Computing Toolbox)
simOut = parsim(simIn, 'ShowProgress', 'on');
% 或串行运行
% simOut = sim(simIn);
% 提取结果
for i = 1:n
y = simOut(i).yout{1}.Values.Data;
fprintf('Kp=%.1f: 稳态值=%.4f\n', Kp_values(i), y(end));
end
8.2 MATLAB 工作区与 Simulink 数据交换
8.2.1 From Workspace 模块
功能: 从 MATLAB 工作区读取时间序列数据作为仿真输入
位置:Sources → From Workspace
参数配置:
Data:工作区变量名
Sample time:采样时间
Interpolate data:是否插值
Form output after final data value:
Setting to zero:超出数据范围后输出0
Holding final value:保持最后值
Cyclic repetition:循环重复
使用示例:
matlab
%% 准备工作区数据
% 方法1:使用 timeseries 对象(推荐)
t = 0:0.01:10;
u = sin(2*pi*t); % 正弦波输入
input_ts = timeseries(u', t'); % 注意:列向量
assignin('base', 'input_data', input_ts);
% 方法2:使用结构体
input_struct.time = t';
input_struct.signals.values = u';
input_struct.signals.dimensions = 1;
assignin('base', 'input_struct', input_struct);
% 方法3:使用矩阵(第一列为时间,其余列为数据)
input_matrix = [t', u'];
assignin('base', 'input_matrix', input_matrix);
在 Simulink 中配置 From Workspace:
Data = input_data(变量名)
Sample time = 0.01(与数据采样率一致)
Interpolate data = on(允许插值)
8.2.2 To Workspace 模块
功能: 将仿真数据导出到 MATLAB 工作区
位置:Sinks → To Workspace
参数配置:
Variable name:工作区变量名
Limit data points to last:限制保存点数(0=全部)
Decimation:抽取比例(1=全部,2=每隔一个)
Save format:
Timeseries(推荐):保存为 timeseries 对象
Array:保存为矩阵(列=数据,行=时间步)
Structure:保存为结构体
Structure With Time:带时间的结构体
仿真后访问数据:
matlab
% 运行仿真
sim('mymodel');
% 访问 To Workspace 数据(Save format = Timeseries)
t = output_data.Time;
y = output_data.Data;
% 绘图
plot(t, y);
% 访问 To Workspace 数据(Save format = Structure With Time)
t = output_data.time;
y = output_data.signals.values;
% 访问 To Workspace 数据(Save format = Array)
% output_data 是矩阵,每列对应一个时间步
8.2.3 工作区变量作为模块参数
这是 MATLAB 与 Simulink 交互最常用的方式。
基本用法:
matlab
%% 在 MATLAB 中定义参数
Kp = 2.0;
Ki = 0.5;
Kd = 0.1;
tau = 0.1; % 时间常数
wc = 1/tau; % 截止频率
%% 在 Simulink 模块中引用
% Gain 模块:Gain = Kp
% Transfer Fcn:Numerator = [wc], Denominator = [1, wc]
% Step:Final value = setpoint
%% 运行仿真
sim('mymodel');
使用 Model Workspace(模型工作区):
matlab
% 将变量存储在模型工作区(而非全局工作区)
% 好处:变量随模型保存,不依赖外部工作区
% 获取模型工作区
mws = get_param('mymodel', 'ModelWorkspace');
% 添加变量
mws.assignin('Kp', 2.0);
mws.assignin('Ki', 0.5);
% 读取变量
Kp = mws.evalin('Kp');
% 保存模型时,模型工作区变量一起保存
save_system('mymodel');
使用 Data Dictionary(数据字典):
matlab
% 数据字典是更规范的参数管理方式
% 适合大型项目和团队协作
% 创建数据字典
dd = Simulink.data.dictionary.create('myparams.sldd');
% 添加数据
sec = dd.getSection('Design Data');
sec.addEntry('Kp', 2.0);
sec.addEntry('Ki', 0.5);
% 将数据字典关联到模型
set_param('mymodel', 'DataDictionary', 'myparams.sldd');
% 保存数据字典
dd.saveChanges();
8.3 MATLAB Function 模块
8.3.1 MATLAB Function 模块介绍
MATLAB Function 模块允许在 Simulink 中直接编写 MATLAB 代码。
位置:User-Defined Functions → MATLAB Function
特点:
✅ 可以使用 MATLAB 语法编写复杂算法
✅ 支持代码生成(生成 C 代码)
✅ 支持调试(断点、单步执行)
✅ 支持向量/矩阵运算
限制:
❌ 不支持所有 MATLAB 函数(只支持可代码生成的子集)
❌ 不支持动态内存分配(数组大小必须固定)
❌ 不支持全局变量(需要用 persistent 代替)
❌ 不支持 figure、plot 等图形函数
8.3.2 编写 MATLAB Function
双击 MATLAB Function 模块打开编辑器:
matlab
function y = fcn(u)
% 最简单的示例:输入乘以2
y = 2 * u;
end
更复杂的示例:
matlab
function [y1, y2] = process_signal(u, threshold)
% 信号处理函数
% 输入:u - 输入信号,threshold - 阈值
% 输出:y1 - 限幅后的信号,y2 - 超限标志
% 限幅处理
y1 = max(min(u, threshold), -threshold);
% 超限检测
y2 = abs(u) > threshold;
end
使用 persistent 变量(保持状态):
matlab
function y = running_average(u, N)
% 滑动平均滤波器
% u:当前输入
% N:窗口长度
persistent buffer idx;
% 初始化
if isempty(buffer)
buffer = zeros(1, N);
idx = 1;
end
% 更新缓冲区
buffer(idx) = u;
idx = mod(idx, N) + 1;
% 计算平均值
y = mean(buffer);
end
8.3.3 输入输出定义
在 MATLAB Function 编辑器中定义端口:
matlab
function [y1, y2] = my_function(u1, u2, param)
% 函数签名定义了端口:
% 输入端口:u1, u2, param(3个输入)
% 输出端口:y1, y2(2个输出)
y1 = u1 + u2;
y2 = u1 * param;
end
设置端口数据类型和维度:
在 MATLAB Function 编辑器中:
菜单 Edit → Function Properties
或点击工具栏 "Edit Data" 按钮
可以设置每个端口的:
Name:端口名称
Scope:Input/Output/Parameter/Local
Type:数据类型(double/single/int32/...)
Size:维度(1×1 或 [3 1] 等)
Complexity:实数/复数
使用 coder.extrinsic 调用不支持代码生成的函数:
matlab
function y = my_function(u)
% 调用不支持代码生成的函数(仅用于仿真,不生成代码)
coder.extrinsic('disp', 'fprintf');
% 这些调用只在仿真时执行,代码生成时忽略
fprintf('当前输入值:%.4f\n', u);
y = u * 2;
end
8.3.4 使用限制与注意事项
支持的操作:
✅ 基本数学运算(+, -, *, /, ^)
✅ 矩阵运算(*, \, inv, det)
✅ 逻辑运算(&&, ||, ~)
✅ 条件语句(if/elseif/else)
✅ 循环(for/while)
✅ 固定大小数组
✅ 结构体(固定字段)
✅ 大多数数学函数(sin/cos/sqrt/abs/...)
不支持的操作:
❌ 动态数组(大小在运行时改变)
❌ cell 数组
❌ 图形函数(plot/figure/...)
❌ 文件 I/O(fopen/fread/...)
❌ 全局变量(用 persistent 代替)
❌ 递归函数(部分支持)
常见错误:
"Variable 'x' is not fully defined"
→ 所有变量必须在使用前赋值
"Extrinsic function 'xxx' is not supported"
→ 该函数不支持代码生成,用 coder.extrinsic 声明
8.4 S-Function
8.4.1 S-Function 概念
S-Function(System Function)是 Simulink 的高级扩展机制,允许用 MATLAB 或 C 语言编写自定义模块。
S-Function 的用途:
① 实现 Simulink 内置模块无法完成的功能
② 集成已有的 C/C++ 代码到 Simulink
③ 实现复杂的算法(如自定义求解器)
④ 与硬件接口(串口、CAN、以太网等)
S-Function 类型:
Level-1 M S-Function:MATLAB编写,简单但功能有限
Level-2 M S-Function:MATLAB编写,功能完整
C MEX S-Function:C语言编写,性能最高
8.4.2 Level-1 S-Function
Level-1 S-Function 是最简单的形式,适合快速原型。
matlab
function [sys, x0, str, ts] = my_sfunc(t, x, u, flag)
% Level-1 S-Function 模板
%
% 输入参数:
% t - 当前仿真时间
% x - 状态向量
% u - 输入向量
% flag - 调用标志(决定执行哪个功能)
switch flag
case 0 % 初始化
[sys, x0, str, ts] = mdlInitializeSizes();
case 1 % 计算导数(连续状态)
sys = mdlDerivatives(t, x, u);
case 2 % 更新离散状态
sys = mdlUpdate(t, x, u);
case 3 % 计算输出
sys = mdlOutputs(t, x, u);
case 4 % 计算下一个采样时间
sys = mdlGetTimeOfNextVarHit(t, x, u);
case 9 % 终止
sys = mdlTerminate(t, x, u);
otherwise
error(['Unhandled flag = ', num2str(flag)]);
end
%% 子函数定义
function [sys, x0, str, ts] = mdlInitializeSizes()
sizes = simsizes;
sizes.NumContStates = 0; % 连续状态数
sizes.NumDiscStates = 0; % 离散状态数
sizes.NumOutputs = 1; % 输出数
sizes.NumInputs = 1; % 输入数
sizes.DirFeedthrough = 1; % 直接馈通
sizes.NumSampleTimes = 1; % 采样时间数
sys = simsizes(sizes);
x0 = []; % 初始状态
str = [];
ts = [0 0]; % 连续采样时间
function sys = mdlDerivatives(t, x, u)
sys = []; % 无连续状态
function sys = mdlUpdate(t, x, u)
sys = []; % 无离散状态
function sys = mdlOutputs(t, x, u)
sys = u * 2; % 输出 = 输入 × 2
function sys = mdlGetTimeOfNextVarHit(t, x, u)
sys = [];
function sys = mdlTerminate(t, x, u)
sys = [];
8.4.3 Level-2 S-Function
Level-2 S-Function 功能更完整,是推荐的 MATLAB S-Function 格式。
matlab
function my_level2_sfunc(block)
% Level-2 S-Function 模板
setup(block);
%% 设置函数
function setup(block)
% 注册端口数量
block.NumInputPorts = 1;
block.NumOutputPorts = 1;
% 设置端口属性
block.InputPort(1).Dimensions = 1;
block.InputPort(1).DataTypeId = 0; % double
block.InputPort(1).SamplingMode = 'Sample';
block.OutputPort(1).Dimensions = 1;
block.OutputPort(1).DataTypeId = 0; % double
block.OutputPort(1).SamplingMode = 'Sample';
% 设置参数数量
block.NumDialogPrms = 1; % 一个对话框参数
% 设置采样时间
block.SampleTimes = [0.01 0]; % 离散,采样周期0.01s
% 注册回调函数
block.RegBlockMethod('Outputs', @Outputs);
block.RegBlockMethod('Update', @Update);
block.RegBlockMethod('Start', @Start);
block.RegBlockMethod('Terminate', @Terminate);
%% 初始化
function Start(block)
% 初始化工作区(持久数据)
block.Dwork(1).Name = 'prev_output';
block.Dwork(1).Dimensions = 1;
block.Dwork(1).DatatypeID = 0;
block.Dwork(1).Complexity = 'Real';
block.Dwork(1).UsedAsDiscState = true;
%% 计算输出
function Outputs(block)
gain = block.DialogPrm(1).Data; % 获取对话框参数
u = block.InputPort(1).Data;
block.OutputPort(1).Data = gain * u;
%% 更新状态
function Update(block)
block.Dwork(1).Data = block.OutputPort(1).Data;
%% 终止
function Terminate(block)
disp('S-Function 终止');
8.4.4 C MEX S-Function 简介
C MEX S-Function 用 C 语言编写,性能最高,适合计算密集型算法。
c
/* C MEX S-Function 基本结构 */
#define S_FUNCTION_NAME my_c_sfunc
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
/* 初始化 */
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 1); /* 参数数量 */
ssSetNumInputPorts(S, 1);
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortDirectFeedThrough(S, 0, 1);
ssSetNumOutputPorts(S, 1);
ssSetOutputPortWidth(S, 0, 1);
ssSetNumSampleTimes(S, 1);
}
/* 采样时间 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
}
/* 计算输出 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
real_T gain = *mxGetPr(ssGetSFcnParam(S, 0));
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S, 0);
real_T *y = ssGetOutputPortRealSignal(S, 0);
y[0] = gain * (*uPtrs[0]);
}
/* 终止 */
static void mdlTerminate(SimStruct *S) {}
/* 必须包含的宏 */
#ifdef MATLAB_MEX_FILE
#include "simulink.c"
#else
#include "cg_sfun.h"
#endif
编译 C MEX S-Function:
matlab
% 在 MATLAB 命令行编译
mex my_c_sfunc.c
% 编译后生成 .mexw64(Windows)或 .mexa64(Linux)文件
% 在 Simulink 中使用 S-Function 模块,填入函数名
综合实战:MATLAB 脚本自动化仿真分析
matlab
%% 完整的自动化仿真分析脚本
% 功能:对 PID 控制系统进行参数扫描和性能分析
clear; clc; close all;
%% 1. 定义扫描参数
Kp_range = linspace(0.5, 10, 10);
Ki_range = [0, 0.1, 0.5, 1.0];
%% 2. 初始化结果存储
n_Kp = length(Kp_range);
n_Ki = length(Ki_range);
results = zeros(n_Kp, n_Ki, 3); % [稳态误差, 上升时间, 超调量]
%% 3. 参数扫描
fprintf('开始参数扫描...\n');
total = n_Kp * n_Ki;
count = 0;
for i = 1:n_Kp
for j = 1:n_Ki
Kp = Kp_range(i);
Ki = Ki_range(j);
Kd = 0.05;
% 更新工作区变量
assignin('base', 'Kp', Kp);
assignin('base', 'Ki', Ki);
assignin('base', 'Kd', Kd);
% 运行仿真
try
simOut = sim('pid_demo', ...
'StopTime', '20', ...
'SrcWorkspace', 'base');
t = simOut.tout;
y = simOut.yout{1}.Values.Data;
% 计算性能指标
ref = 1.0; % 参考值
% 稳态误差
ss_error = abs(ref - y(end));
% 上升时间(10%~90%)
idx_10 = find(y >= 0.1*ref, 1);
idx_90 = find(y >= 0.9*ref, 1);
if ~isempty(idx_10) && ~isempty(idx_90)
rise_time = t(idx_90) - t(idx_10);
else
rise_time = NaN;
end
% 超调量
overshoot = (max(y) - ref) / ref * 100;
overshoot = max(overshoot, 0); % 不允许负超调
results(i, j, :) = [ss_error, rise_time, overshoot];
catch ME
fprintf('仿真失败 Kp=%.1f, Ki=%.1f: %s\n', ...
Kp, Ki, ME.message);
results(i, j, :) = [NaN, NaN, NaN];
end
count = count + 1;
fprintf('进度:%d/%d (Kp=%.1f, Ki=%.1f)\n', ...
count, total, Kp, Ki);
end
end
%% 4. 结果可视化
figure('Position', [100 100 1200 400]);
subplot(1,3,1);
imagesc(Ki_range, Kp_range, results(:,:,1));
colorbar; colormap(jet);
xlabel('Ki'); ylabel('Kp');
title('稳态误差');
set(gca, 'YDir', 'normal');
subplot(1,3,2);
imagesc(Ki_range, Kp_range, results(:,:,2));
colorbar; colormap(jet);
xlabel('Ki'); ylabel('Kp');
title('上升时间 (s)');
set(gca, 'YDir', 'normal');
subplot(1,3,3);
imagesc(Ki_range, Kp_range, results(:,:,3));
colorbar; colormap(jet);
xlabel('Ki'); ylabel('Kp');
title('超调量 (%)');
set(gca, 'YDir', 'normal');
sgtitle('PID 参数扫描结果');
%% 5. 找最优参数
% 综合评分:稳态误差权重0.4,上升时间权重0.3,超调量权重0.3
score = 0.4 * results(:,:,1) + ...
0.3 * results(:,:,2) / max(results(:,:,2), [], 'all') + ...
0.3 * results(:,:,3) / 100;
[~, idx] = min(score(:));
[i_opt, j_opt] = ind2sub(size(score), idx);
fprintf('\n最优参数:\n');
fprintf(' Kp = %.2f\n', Kp_range(i_opt));
fprintf(' Ki = %.2f\n', Ki_range(j_opt));
fprintf(' 稳态误差 = %.4f\n', results(i_opt, j_opt, 1));
fprintf(' 上升时间 = %.3f s\n', results(i_opt, j_opt, 2));
fprintf(' 超调量 = %.1f%%\n', results(i_opt, j_opt, 3));
%% 6. 保存结果
save('pid_scan_results.mat', 'Kp_range', 'Ki_range', 'results');
fprintf('\n结果已保存到 pid_scan_results.mat\n');
本章小结
本章核心要点:
✅ sim() 函数:从 MATLAB 脚本运行 Simulink 仿真
✅ set_param/get_param:动态修改和读取模型/模块参数
✅ 参数值必须是字符串:使用 num2str() 转换
✅ parsim():并行运行多次仿真,大幅提升效率
✅ From/To Workspace:MATLAB 工作区与 Simulink 双向数据交换
✅ MATLAB Function 模块:在 Simulink 中编写 MATLAB 代码
✅ persistent 变量:在 MATLAB Function 中保持状态
✅ S-Function:高级扩展机制,支持 MATLAB 和 C 语言
✅ 数据字典:规范的参数管理方式,适合大型项目
课后练习
-
编写 MATLAB 脚本,自动运行仿真并绘制结果:
- 加载 RC 电路模型
- 修改 R 值(100Ω, 1kΩ, 10kΩ)
- 每次运行仿真并记录时间常数
- 绘制三条响应曲线对比
-
使用 MATLAB Function 模块实现:
- 一阶低通滤波器(离散形式)
- 输入:原始信号,参数:截止频率
- 输出:滤波后的信号
-
使用 From Workspace 模块:
- 在 MATLAB 中生成一段带噪声的正弦波
- 通过 From Workspace 送入 Simulink
- 用低通滤波器滤波后显示对比
-
编写参数扫描脚本:
- 对一阶系统的时间常数进行扫描(0.1s ~ 2s)
- 记录每个时间常数对应的上升时间
- 验证:上升时间 ≈ 2.2τ(10%~90%定义)