
各位做机器人、自动化或者嵌入式方向的朋友应该都有一个感受最近几年国内机器人行业的热度一直居高不下但大家讨论最多的不是某一款硬件产品而是一个更底层的问题——我们凭什么在核心技术上占据主动凭什么在全球化竞争中突出重围网上关于这个话题的讨论大多停留在产业分析和宏观趋势真正从开发者视角去拆解技术栈、讲清楚运动学、控制、感知这些底层能力的文章反而很少。这篇文章我想换一个角度不聊宏观叙事只聊技术本身。我们会围绕机器人研发中最核心的几个环节展开核心零部件、运动学建模、轨迹规划、控制算法和感知系统。以“二连杆机械臂的运动学与控制仿真”为主线给出完整可运行的 Python 代码从零搭建一个最小机器人控制仿真环境。无论你是刚接触机器人学的学生还是正在做机器人项目的工程师这篇文章都能帮你建立一条清晰的技术主线。1. 背景与核心概念1.1 机器人“突围”的本质是什么任何一台工业机器人或服务机器人无论外观差异多大拆开来看都离不开三个核心部分执行机构电机、减速器、关节结构负责产生运动。感知系统视觉相机、激光雷达、力传感器、编码器负责感知外界和自身状态。控制系统控制器硬件和算法软件负责把“想做什么”转换为“怎么动”。很多人在讨论机器人产业时喜欢关注整机品牌和销量数据。但从技术研发的角度看真正决定一台机器人性能上限的恰恰是上面三层中的核心零部件控制精度、运动学算法的准确性、动力学模型的匹配程度。这也是为什么许多做机器人的公司最后都会投入大量精力去自研运动控制算法和伺服驱动系统——因为这部分才是真正的技术壁垒也是“突出重围”的底层支撑。1.2 机器人学中的几个核心概念在开始写代码之前先把本文反复用到的几个概念讲清楚。对初学者来说这几个概念是后续所有开发工作的基础。运动学正解Forward Kinematics已知机器人各个关节的角度求末端执行器比如机械臂末端的手爪在空间中的位置和姿态。正解是一个确定性的映射给定一组关节角末端位姿是唯一的。运动学逆解Inverse Kinematics与正解相反已知末端期望的位姿反推各个关节应该转动的角度。逆解通常不唯一存在多组解需要根据关节限位、避障条件选择最优解。轨迹规划Trajectory Planning在已知起点和终点的前提下为每个关节规划一条平滑的角度随时间变化的曲线。好的轨迹规划不仅要让机器人到达目标点还要让运动过程平稳避免冲击和振荡。闭环控制Closed-loop Control实际系统中电机指令角度和实际角度之间存在误差需要通过传感器反馈来实时修正。最常用的方法就是 PID 控制后面我们会用代码实现。工作空间Workspace机械臂末端能够到达的所有空间点的集合。工作空间由机械臂的连杆长度、关节限位和机械结构共同决定。1.3 为什么从二连杆机械臂开始二连杆机械臂是最简单的多关节机器人模型但它已经包含了机器人学中的绝大多数核心问题正解、逆解、工作空间、轨迹规划、动力学、闭环控制。把二连杆模型吃透再扩展到六轴工业机械臂时算法思想完全一致只是矩阵维度和几何复杂度增加。本文的实战环节会选择在 Python 环境下进行因为 Python 具备 NumPy 矩阵运算和 Matplotlib 可视化能力非常适合用来验证算法思路也能直接作为后续产品原型的雏形。2. 环境准备与工具链说明2.1 开发环境本文示例以最常见的 Python 环境为例重点演示算法实现思路具体版本号需要根据你的项目实际情况调整。建议使用以下环境操作系统Windows 10/11、Ubuntu 20.04 及以上均可。Python3.8 或以上版本。NumPy用于矩阵运算。Matplotlib用于绘图和动画可视化。IDE推荐 PyCharm、VS Code 或 Jupyter Notebook看个人习惯。如果你本地还没有安装依赖库可以执行下面的命令pip install numpy matplotlib如果你的网络环境下载较慢可以考虑使用国内镜像源pip install numpy matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 仿真思路说明本文不会直接调用已有的机器人仿真库如 PyBullet、CoppeliaSim而是从零实现二连杆机械臂的运动学、逆解、轨迹规划与 PID 控制。这样做的好处是让你能把每一个公式和代码对应起来真正理解算法内部的运作过程。如果你已经具备一定基础想直接进入更贴近生产的仿真环境可以在完成本文示例后再迁移到 PhysX 或 ODE 等物理引擎上。2.3 准备工作建立全局坐标系在机器人学中我们通常用全局坐标系世界坐标系来描述机器人基座和末端的位置。对二连杆机械臂设定基座位于坐标系原点第一个关节绕 z 轴旋转连杆在同一平面内运动。为了后面复用代码我们先建立一个专门存放几何与数学工具的模块把所有常量、转换函数和基础结构写在一起。# 文件路径robot_sim/config.py 二连杆机械臂的基础配置与常量 # 机械臂连杆长度单位米 L1 1.0 L2 0.8 # 关节角度初始值单位弧度 INIT_THETA1 0.0 INIT_THETA2 0.0 # 控制周期单位秒 DT 0.01 # 仿真总时长单位秒 SIM_TIME 5.03. 核心原理与技术拆解3.1 运动学正解从关节角到末端坐标先推导二连杆机械臂的正解公式。假设第一个关节角度为 θ1第二个关节角度为 θ2那么第二个关节相对于基座的坐标为x1 L1 * cos(θ1)y1 L1 * sin(θ1)末端执行器相对于第二个关节的局部坐标为 L2 * cos(θ1 θ2) 和 L2 * sin(θ1 θ2)。因此末端在世界坐标系中的位置为x L1 * cos(θ1) L2 * cos(θ1 θ2)y L1 * sin(θ1) L2 * sin(θ1 θ2)这里特别要注意θ1 θ2 表示第二个连杆的绝对角度而不是相对角度。很多初学机器人的同学会在这一步犯错导致计算出的末端位置与仿真不一致。下面把正解写成函数# 文件路径robot_sim/kinematics.py import numpy as np from config import L1, L2 def forward_kinematics(theta1, theta2): 二连杆机械臂运动学正解 参数 theta1: 关节1角度弧度 theta2: 关节2角度弧度 返回 (x, y): 末端执行器在全局坐标系中的位置 x L1 * np.cos(theta1) L2 * np.cos(theta1 theta2) y L1 * np.sin(theta1) L2 * np.sin(theta1 theta2) return x, y用一组具体角度验证一下。当 θ10θ20 时机械臂完全展开末端位置应为 (L1 L2, 0) (1.8, 0)。运行下面代码即可验证from kinematics import forward_kinematics x, y forward_kinematics(0.0, 0.0) print(f末端位置: x{x:.4f}, y{y:.4f})预期输出末端位置: x1.8000, y0.00003.2 运动学逆解从期望位置到关节角逆解是轨迹规划和实际控制中最常用的功能。对平面二连杆机械臂可以用几何法直接推导。已知末端期望位置 (x, y)首先计算末端到基座的距离 dd sqrt(x² y²)如果 d L1 L2说明目标点在工作空间之外无解。如果 d |L1 - L2|目标点在工作空间内部但离基座太近同样达不到。这是逆解的第一步判断。当 d 在可达范围内时根据余弦定理可求关节2角度cos(θ2) (x² y² - L1² - L2²) / (2 * L1 * L2)由于 cos 函数是偶函数θ2 可以取正负两个值θ2 arccos(cosθ2) 对应“肘部向下”姿态θ2 -arccos(cosθ2) 对应“肘部向上”姿态关节1的角度可以通过几何关系求取。设末端在全局坐标系中的极角为 φ atan2(y, x)第二个关节到末端连线与第一个连杆之间的夹角为 β则θ1 φ - atan2(L2 * sin(θ2), L1 L2 * cos(θ2))完整代码如下# 文件路径robot_sim/inverse_kinematics.py import numpy as np from config import L1, L2 def inverse_kinematics(x, y, elbow_downTrue): 二连杆机械臂运动学逆解几何法 参数 x, y: 末端目标位置 elbow_down: True 表示肘部向下姿态False 表示肘部向上姿态 返回 (theta1, theta2): 两组可能的关节角度 d np.sqrt(x * x y * y) # 工作空间判断 if d L1 L2 or d abs(L1 - L2): raise ValueError(f目标点 ({x:.3f}, {y:.3f}) 超出机械臂可达范围) # 计算关节2角度余弦值 cos_theta2 (x * x y * y - L1 * L1 - L2 * L2) / (2.0 * L1 * L2) # 数值截断避免浮点误差导致 arccos 越界 cos_theta2 np.clip(cos_theta2, -1.0, 1.0) if elbow_down: theta2 np.arccos(cos_theta2) else: theta2 -np.arccos(cos_theta2) theta1 np.arctan2(y, x) - np.arctan2(L2 * np.sin(theta2), L1 L2 * np.cos(theta2)) return theta1, theta2这里需要留意的点是np.clip。由于浮点数精度问题计算出的 cosθ2 有时会是 1.0000000002 这样的值直接送入 arccos 会产生 NaN。工程上必须做一次数值截断。3.3 轨迹规划让机械臂平滑运动逆解解决的是“目标点对应什么角度”但机械臂要从当前角度运动到目标角度中间经过什么样的角度序列由轨迹规划决定。最简单的做法是直接在关节空间做线性插值但线性插值在起点和终点处速度突变会导致机械臂冲击。更常用的做法是使用梯形速度规划或 S 型曲线。这里实现一个比较成熟的五次多项式插值它保证角位移、角速度、角加速度在起点和终点都连续。五次多项式角度公式为θ(t) a0 a1·t a2·t² a3·t³ a4·t⁴ a5·t⁵给定起点角度 θ0、终点角度 θf、运动时间 T并假设起点终点速度、加速度均为 0可解得a0 θ0a1 0a2 0a3 10 * (θf - θ0) / T³a4 -15 * (θf - θ0) / T⁴a5 6 * (θf - θ0) / T⁵代码实现# 文件路径robot_sim/trajectory.py import numpy as np def quintic_trajectory(theta0, thetaf, T, dt): 五次多项式轨迹规划 参数 theta0: 起始角度 thetaf: 目标角度 T: 运动总时长秒 dt: 插值时间间隔秒 返回 times: 时间序列 positions: 角度序列 velocities: 角速度序列 # 求解多项式系数 a0 theta0 a1 0.0 a2 0.0 a3 10.0 * (thetaf - theta0) / (T ** 3) a4 -15.0 * (thetaf - theta0) / (T ** 4) a5 6.0 * (thetaf - theta0) / (T ** 5) times np.arange(0.0, T dt, dt) positions [] velocities [] for t in times: theta a0 a1 * t a2 * t ** 2 a3 * t ** 3 a4 * t ** 4 a5 * t ** 5 omega a1 2 * a2 * t 3 * a3 * t ** 2 4 * a4 * t ** 3 5 * a5 * t ** 4 positions.append(theta) velocities.append(omega) return times, np.array(positions), np.array(velocities)3.4 闭环控制PID 在关节控制中的作用轨迹规划给出的是期望角度序列但实际电机执行时会有误差。比如负载变化、摩擦、惯性等都会导致实际角度偏离期望角度。闭环控制的任务就是根据偏差实时调整控制量。PID 控制器的输出为u(t) Kp·e(t) Ki·∫e(τ)dτ Kd·de(t)/dt其中 e(t) 是当前误差期望角度减实际角度。工程中常用的离散 PID 形式为# 文件路径robot_sim/pid_controller.py class PIDController: def __init__(self, kp, ki, kd, dt, max_outputNone): self.kp kp self.ki ki self.kd kd self.dt dt self.max_output max_output self.integral 0.0 self.prev_error 0.0 def reset(self): self.integral 0.0 self.prev_error 0.0 def update(self, reference, measurement): 计算控制量 参数 reference: 期望值 measurement: 实际测量值 返回 控制量 u error reference - measurement self.integral error * self.dt derivative (error - self.prev_error) / self.dt output self.kp * error self.ki * self.integral self.kd * derivative self.prev_error error if self.max_output is not None: output max(-self.max_output, min(self.max_output, output)) return output在仿真里我们可以用一个简单的一阶惯性环节来模拟电机响应。假设关节实际角加速度与控制量成正比并受到粘性阻尼影响α (u - b·ω) / J其中 J 是转动惯量b 是阻尼系数。每次控制周期内我们用欧拉法更新角速度和角度。4. 完整实战案例二连杆机械臂点到点运动控制下面把以上几块内容组合成一套完整的仿真程序。程序会完成以下任务设定机械臂末端目标点。通过逆解计算目标关节角。通过五次多项式生成关节轨迹。用 PID 闭环控制跟踪轨迹。实时绘制机械臂姿态和末端轨迹。4.1 创建项目结构为了保持代码清晰我们按照下面的目录结构组织文件robot_sim/ ├── config.py # 基础配置 ├── kinematics.py # 运动学正解 ├── inverse_kinematics.py # 运动学逆解 ├── trajectory.py # 轨迹规划 ├── pid_controller.py # PID 控制器 ├── simulate.py # 主仿真入口 └── visualize.py # 可视化模块4.2 编写可视化模块可视化模块负责把机械臂的姿态绘制出来同时记录末端轨迹。这里使用 Matplotlib 的交互模式方便实时更新。# 文件路径robot_sim/visualize.py import matplotlib.pyplot as plt import numpy as np from config import L1, L2 class RobotVisualizer: def __init__(self): plt.ion() self.fig, self.ax plt.subplots(figsize(8, 8)) self.ax.set_xlim(-2.2, 2.2) self.ax.set_ylim(-2.2, 2.2) self.ax.set_aspect(equal) self.ax.grid(True) self.arm_line, self.ax.plot([], [], o-, lw4, markersize8, colorsteelblue) self.traj_line, self.ax.plot([], [], --, lw1, colororange, alpha0.8) self.target_point, self.ax.plot([], [], rx, markersize12) self.traj_x [] self.traj_y [] def update(self, x0, y0, x1, y1, x_end, y_end, target_x, target_y): 更新画面 self.arm_line.set_data([x0, x1, x_end], [y0, y1, y_end]) self.target_point.set_data([target_x], [target_y]) self.traj_x.append(x_end) self.traj_y.append(y_end) self.traj_line.set_data(self.traj_x, self.traj_y) self.ax.set_title( f2-Link Robot Arm | End: ({x_end:.3f}, {y_end:.3f}) ) plt.pause(0.01)4.3 编写主仿真脚本主仿真脚本将各个模块串起来。核心循环如下在当前时刻读取规划轨迹上的期望角度。用 PID 输出控制量。用简化动力学模型更新关节速度与角度。用正解计算末端位置并可视化。# 文件路径robot_sim/simulate.py import numpy as np from config import L1, L2, DT, SIM_TIME from kinematics import forward_kinematics from inverse_kinematics import inverse_kinematics from trajectory import quintic_trajectory from pid_controller import PIDController from visualize import RobotVisualizer def simulate_joint(theta_meas, omega_meas, u, b0.5, J1.0): 简化动力学模型一阶惯性 粘性阻尼 参数 theta_meas: 当前角度 omega_meas: 当前角速度 u: 控制力矩 b: 阻尼系数 J: 转动惯量 返回 新的角速度、新的角度 alpha (u - b * omega_meas) / J omega_new omega_meas alpha * DT theta_new theta_meas omega_new * DT return omega_new, theta_new def main(): # 1. 设定目标点 target_x 1.2 target_y 0.9 # 2. 逆解得到目标关节角 theta1_target, theta2_target inverse_kinematics(target_x, target_y, elbow_downTrue) print(f目标关节角: theta1{theta1_target:.3f}, theta2{theta2_target:.3f}) # 3. 初始关节角 theta1_meas 0.0 theta2_meas 0.0 omega1_meas 0.0 omega2_meas 0.0 # 4. 五次多项式轨迹 T 3.0 times, theta1_refs, _ quintic_trajectory(theta1_meas, theta1_target, T, DT) _, theta2_refs, _ quintic_trajectory(theta2_meas, theta2_target, T, DT) # 5. 创建 PID 控制器 pid1 PIDController(kp8.0, ki0.5, kd1.0, dtDT, max_output20.0) pid2 PIDController(kp8.0, ki0.5, kd1.0, dtDT, max_output20.0) # 6. 初始化可视化 visualizer RobotVisualizer() x0, y0 0.0, 0.0 # 7. 主控制循环 for i in range(len(times)): theta1_ref theta1_refs[i] theta2_ref theta2_refs[i] # 计算控制量 u1 pid1.update(theta1_ref, theta1_meas) u2 pid2.update(theta2_ref, theta2_meas) # 更新动力学模型 omega1_meas, theta1_meas simulate_joint(theta1_meas, omega1_meas, u1) omega2_meas, theta2_meas simulate_joint(theta2_meas, omega2_meas, u2) # 正解计算末端位置 j2_x L1 * np.cos(theta1_meas) j2_y L1 * np.sin(theta1_meas) end_x, end_y forward_kinematics(theta1_meas, theta2_meas) # 可视化 visualizer.update(x0, y0, j2_x, j2_y, end_x, end_y, target_x, target_y) visualizer.traj_line.set_data(visualizer.traj_x, visualizer.traj_y) plt.ioff() plt.show() if __name__ __main__: main()4.4 运行与结果说明在项目根目录执行python simulate.py如果一切正常你会看到画面中有一个蓝色关节臂从初始水平姿态开始运动。机械臂末端沿橙色虚线轨迹逐步逼近红色叉号标记的目标点。标题实时显示末端坐标数值逐渐收敛到 (1.2, 0.9) 附近。从控制角度看机械臂的运动分为两个阶段前 3 秒五次多项式轨迹给出平滑的期望角度PID 控制器进行跟踪。3 秒后期望角度保持目标值PID 继续消除残余误差末端位置微调后稳定。4.5 参数调节实验为了加深理解可以手动修改 PID 参数观察不同参数下的表现修改内容现象原因Kp 调大响应变快但可能出现超调或抖动比例增益过大导致系统趋于不稳定Ki 调大稳态误差减小但容易积分饱和积分项累积过快控制量饱和Kd 调大阻尼增强超调减小微分项对误差变化率敏感抑制振荡增大 T运动更平稳但到位更慢轨迹时间拉长角速度峰值下降建议你先保持默认参数跑通再分别把 Kp 从 8 改成 30、把 Kd 从 1 改成 0观察末端轨迹和机械臂姿态的变化。这个过程能帮助你直观理解 PID 三个参数的作用。5. 核心零部件在控制链路中的作用上面这个仿真虽然简单但它完整复现了机器人控制链路中“规划—控制—反馈”的闭环。在实际的工业机器人中实时控制周期通常在 1ms 到 8ms 之间远高于仿真的 10ms。并且真实系统里还有两个关键的硬件环节在起作用。5.1 减速器提升力矩和精度工业机器人关节中电机输出的转速很高、力矩很小不能直接驱动负载。减速器的作用是把电机的转速降低、力矩放大。常见的减速器包括 RV 减速器和谐波减速器。RV 减速器刚性大、承载能力强适合大负载关节谐波减速器体积小、精度高适合小负载关节。控制链路中减速器直接影响末端位置精度和关节回差。仿真中我们没有模拟回差但真实系统必须考虑。5.2 伺服电机与驱动器执行规划指令伺服电机接收控制器发送的电流指令通过编码器实时反馈转子位置和速度。驱动器的响应带宽决定了关节跟踪高速轨迹的能力。控制系统中PID 参数需要根据伺服驱动器的整定结果进行调整。很多国产机器人系统的突围正是从电机和驱动器的自研匹配开始的——只有掌握电机和驱动器的底层协议和参数才能在算法层面做更深层的优化。5.3 控制器与实时系统机器人控制器是运动控制算法的运行载体。大多数工业机器人控制器基于实时操作系统RTOS保证控制循环的时间确定性。如果你希望把 Python 仿真迁移到真实机器人上需要注意控制链路的实时性。常用的替代方案是使用 C 编写底层控制把 Python 用于离线规划和仿真验证。6. 从二连杆到六轴扩展思路与常见坑点6.1 扩展思路二连杆模型与六轴工业机械臂的差别主要体现在维度和几何复杂程度上但技术思路是一致的正解改为使用 D-H 参数法或 Modified D-H 参数法建立齐次变换矩阵。逆解从几何法改为解析法或数值迭代法。工作空间从二维平面扩展为三维球体空间。轨迹规划从关节空间扩展到笛卡尔空间直线、圆弧插补。如果你已经掌握了本文的二连杆实现下一步可以尝试把上述代码封装成矩阵形式引入齐次变换矩阵T R * t为扩展到三维空间做准备。6.2 常见问题与排查思路在写机器人仿真和实际调试过程中下面几个问题非常常见整理成表格供你参考问题现象常见原因解决思路逆解计算出现 NaN目标点超出工作空间或浮点误差越界检查目标距离使用np.clip截断末端无法到达目标点轨迹时间太短控制量饱和增大轨迹时间 T或调整 PID 参数关节角度跳变逆解多解选择不稳定固定肘部姿态参数增加关节限位判断仿真抖动严重Kp 过大或 Kd 过小减小 Kp增大 Kd降低控制周期末端轨迹出现尖角轨迹规划未考虑加速度连续使用五次多项式代替直线插值真实机器人与仿真偏差大未考虑摩擦、回差、柔性对减速器回差和关节柔性做补偿7. 最佳实践与工程建议做完这个仿真项目后有几个工程经验值得沉淀下来。7.1 版本与依赖管理机器人项目涉及大量算法库和硬件 SDK依赖管理非常重要。建议使用虚拟环境或 conda 环境隔离项目依赖并且把 requirements.txt 或 environment.yml 一并提交到代码仓库。对于工业项目Python 版本升级要谨慎优先锁定长期维护版本。conda create -n robot_sim python3.8 numpy matplotlib conda activate robot_sim7.2 代码结构与可维护性本文的代码虽然简单但已经按“配置—运动学—轨迹—控制—可视化”做了模块拆分。真实项目中更要注意这一点算法模块和硬件驱动模块要彻底分离。所有参数通过配置文件注入不要写死在业务逻辑里。使用日志记录每个控制周期的参考值、实际值和控制量方便事后分析。日志示例import logging logging.basicConfig(filenamecontrol_log.csv, levellogging.INFO, format%(asctime)s,%(message)s)7.3 控制参数整定要分步进行很多同学第一次调机器人时喜欢同时调多个 PID 参数结果系统出现振荡后根本不知道是哪个参数引起的。正确做法是先把 Ki 和 Kd 置零只调 Kp找到系统开始振荡的临界值。取临界值的一半作为 Kp。加入 Kd抑制超调。最后加入少量 Ki消除稳态误差。这就是经典的 Ziegler-Nichols 整定思路尤其在二连杆这类多关节耦合系统中非常有效。7.4 安全边界极其重要从仿真走向真实设备前必须考虑以下安全边界关节限位在控制循环中检测角度是否超出机械限位。速度限制对规划速度做限幅防止异常指令导致飞车。触碰检测通过电流或力矩传感器检测异常碰撞立即停机或切换柔顺控制。急停逻辑接入硬件急停回路软件层面的停机只能作为辅助。这些安全逻辑在仿真中不会体现但在实际设备调试时是最高优先级需求。7.5 算法验证要使用仿真先行在做真实机械臂调试前先花时间在仿真环境里验证算法能节省大量现场排错时间。即使到了真机阶段也可以用“仿真模式—半实物仿真—真机空载—真机带载”这样逐级推进的方式把风险控制在最小范围。8. 总结与下一阶段学习建议这篇文章从产业竞争中最常被讨论的“核心能力”话题出发落到机器人研发最基础也最关键的技术链路运动学正解、逆解、轨迹规划、PID 闭环控制。我们用二连杆机械臂作为最小模型完整实现了一套可控可扩展的仿真系统。你掌握了正解公式的推导与编码、逆解的几何法实现、五次多项式轨迹规划以及 PID 控制在关节伺服系统中的应用思路。把这个仿真跑通后对机器人的底层工作方式就有了基本体感。接下来你可以根据自己的方向继续深入如果你对运动规划感兴趣可以学习 RRT快速扩展随机树和 A* 算法在避开障碍物中的应用。如果你对控制算法感兴趣可以尝试用模型预测控制MPC替代 PID提升跟踪精度。如果你对感知系统感兴趣可以学习相机标定、手眼标定以及视觉伺服控制。如果你对整机产品落地感兴趣可以了解 EtherCAT 总线协议、实时控制系统的搭建和机器人操作系统如 ROS 2的应用。如果动手实践过程中遇到卡住的地方建议回到仿真中逐步排查先检查逆解给出的关节角是否合理再检查轨迹是否平滑最后检查控制参数是否收敛。技术突围没有捷径但长期坚持把基础链路做扎实自然会和只停留在组装层面的差距越拉越大。