跳到主要内容

老化力学测试设备

硬件配置

  • 主站: Linux × 1 + Intel i5 × 1
  • 伺服驱动: 汇川 SV660N 1.5kW (EtherCAT, CiA 402) × 1
  • 伺服电机: 1.5kW 带抱闸 × 1
  • 直线模组: 精密滚珠丝杠 (导程 5mm, 行程 150mm, 推力 8kN) × 1
  • 力传感器: S型拉压 5kN (精度 0.02% F.S.) × 1
  • 称重模块: Beckhoff EL3356-0010 (24位) × 1
  • 位移传感器: LVDT ±25mm (0-10V 调理输出) × 1
  • 模拟量输入: Beckhoff EL3164 (16位) × 1
设计说明

单轴电动疲劳测试机:伺服 + 丝杠往复加载,S型传感器串联测力,LVDT 实测试件变形。 PDO 125μs (8kHz),应用层通过 PDO 映射读写。 整线成本约为市场同规格产品的 1/5。

成本分析

类别型号/规格参考单价 (¥)数量小计 (¥)
伺服驱动器汇川 SV660N 1.5kW EtherCAT2,50012,500
伺服电机1.5kW 带抱闸1,20011,200
滚珠丝杠模组导程5mm 行程150mm 8kN3,50013,500
S型力传感器5kN 0.02% F.S.2,00012,000
称重模块EL3356-0010 (24位)2,50012,500
LVDT 位移传感器±25mm 0-10V 调理输出1,80011,800
模拟量输入EL3164 (16位 4通道)8001800
工控机Intel i5 级别3,00013,000
整线参考总计≈ ¥17,300

核心部件参考价 (RMB),不含机架、夹具、线缆、配电等。

性能指标

  • 最大推力: ±5 kN (拉/压)
  • 行程: ±25 mm
  • 疲劳频率: 1-5 Hz (正弦波)
  • 力分辨率: 24 位 (EL3356, ±0.001% F.S.)
  • 位移分辨率: 16 位 (EL3164, 0.0008mm)
  • PDO 周期: 125μs (8kHz, DC 同步)

应用场景

汽车零部件低频疲劳寿命测试。 伺服 + 丝杠往复加载,S型传感器精密测力,LVDT 实测试件变形。 正弦波位移控制,实时追踪每循环峰值力检测刚度退化 (力幅下降 > 20% 判定失效)。 适用于橡胶衬套、密封圈、弹簧等部件的万次疲劳循环测试。

代码说明

系统架构

关键设计:

  1. 正弦波位移控制 — Stopwatch 实时时钟生成正弦轨迹,写入 PDO 映射
  2. 力-位移双限制 — 超力/超位移停机,保护试件
  3. 刚度退化检测 — 每周期追踪峰值力,下降超阈值判定失效
  4. 生产者-消费者 — 控制线程入队,记录线程批量落盘
定制服务

本案例中的疲劳测试控制算法 (正弦波轨迹生成、刚度退化检测、S-N 曲线分析等) 及力传感器标定、LVDT 信号调理等底层时序逻辑为附加定制服务, 不包含在 EtherCAT 主站软件授权中。

我们是专业的工业自动化技术公司,拥有多家行业专用设备制造商合作者。 除软件授权外,同样提供整线方案设计、硬件选型、设备采购及技术支持等一站式服务,欢迎咨询合作。

代码示例

PDO 结构体定义

using System.Runtime.InteropServices;

/// <summary>
/// 汇川 SV660N 伺服驱动器输入 PDO (TxPDO, CiA 402)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SV660N_Input
{
public ushort StatusWord; // 0x6041 状态字
public int ActualPosition; // 0x6064 实际位置 (编码器脉冲)
public int ActualVelocity; // 0x606C 实际速度 (pulse/s)
public short ActualTorque; // 0x6077 实际转矩 (0.1% 额定)
}

/// <summary>
/// 汇川 SV660N 伺服驱动器输出 PDO (RxPDO, CiA 402 CSP 模式)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SV660N_Output
{
public ushort ControlWord; // 0x6040 控制字
public sbyte ModesOfOperation; // 0x6060 操作模式 (8=CSP)
public int TargetPosition; // 0x607A 目标位置 (编码器脉冲)
}

/// <summary>
/// EL3356-0010 称重模块输入 PDO (24位)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EL3356_Input
{
public ushort Status; // 状态字
public int Value; // 称重值 (24位有效)
}

/// <summary>
/// EL3164 模拟量输入 PDO (使用通道1采集 LVDT)
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EL3164_Input
{
public ushort Ch1_Status;
public short Ch1_Value; // LVDT (16位, 0-10V)
public ushort Ch2_Status;
public short Ch2_Value;
public ushort Ch3_Status;
public short Ch3_Value;
public ushort Ch4_Status;
public short Ch4_Value;
}

/// <summary>
/// 测试数据点
/// </summary>
public class TestDataPoint
{
public long SampleIndex { get; set; }
public double Time { get; set; } // 时间 (秒)
public double TargetDisplacement { get; set; } // 目标位移 (mm)
public double ActualDisplacement { get; set; } // 实测位移 (mm)
public double Force { get; set; } // 力 (N)
public int CycleCount { get; set; } // 当前循环数
}

控制代码

using System;
using System.Diagnostics;
using System.Threading;
using System.Collections.Concurrent;
using System.IO;
using DarraEtherCAT_Master;

/// <summary>
/// 测试参数
/// </summary>
public class FatigueTestParams
{
public double Amplitude { get; set; } // 位移幅值 (mm)
public double Frequency { get; set; } // 疲劳频率 (Hz)
public double MaxForce { get; set; } // 力上限 (N), 超限停机
public int MaxCycles { get; set; } // 最大循环次数
public double FailureThreshold { get; set; } // 刚度退化阈值 (0.8 = 降至80%)
}

class FatigueTestController
{
// 从站索引 (EtherCAT 菊花链拓扑)
const int SLAVE_SERVO = 0; // 汇川 SV660N 伺服驱动器
const int SLAVE_LOADCELL = 1; // EL3356 称重模块
const int SLAVE_LVDT = 2; // EL3164 模拟量输入 (LVDT)

// CiA 402 常量
const ushort CW_SHUTDOWN = 0x0006;
const ushort CW_SWITCH_ON = 0x0007;
const ushort CW_ENABLE_OPERATION = 0x000F;
const ushort CW_QUICK_STOP = 0x0002;
const ushort SW_FAULT = 0x0008;
const sbyte MODE_CSP = 8; // Cyclic Synchronous Position

// ============ 可配置参数 ============

/// <summary> 编码器脉冲/mm (电机编码器分辨率 ÷ 丝杠导程) </summary>
public static double PulsesPerMm { get; set; } = 100000.0 / 5.0; // 编码器/导程5mm

/// <summary> EL3356 力值换算: 原始值 → N (需根据传感器标定) </summary>
public static double ForceScale { get; set; } = 5000.0 / (1 << 23); // 5000N / 24位

/// <summary> EL3164 位移换算: 原始值 → mm (0-10V 对应 LVDT 0-50mm) </summary>
public static double LvdtScale { get; set; } = 50.0 / 32767.0;

/// <summary> LVDT 零位偏移 (mm) </summary>
public static double LvdtOffset { get; set; } = -25.0; // ±25mm 中点偏移

/// <summary> 测试参数 </summary>
public static FatigueTestParams TestParams { get; set; } = new FatigueTestParams
{
Amplitude = 5.0, // ±5mm
Frequency = 2.0, // 2Hz
MaxForce = 4500.0, // 4500N (传感器量程90%)
MaxCycles = 10000,
FailureThreshold = 0.8 // 刚度退化至80%判定失效
};

// 数据缓冲区
static ConcurrentQueue<TestDataPoint> dataBuffer = new ConcurrentQueue<TestDataPoint>();
static volatile bool isRunning = true;

static void Main(string[] args)
{
var master = new DarraEtherCAT()
.LoadENI("config/fatigue_test.xml")
.Build();

if (master == null)
{
Console.WriteLine("主站初始化失败!");
return;
}

try
{
Console.WriteLine($"发现 {master.Slaves.Count} 个从站");

var (success, msg) = master.SetState(EcState.OP);
if (!success)
{
Console.WriteLine($"无法进入 OP 状态: {msg}");
return;
}

// 使能伺服 (CiA 402 状态机)
EnableServo(master);

Console.WriteLine("老化力学测试设备启动!");
Console.WriteLine($"幅值: ±{TestParams.Amplitude}mm, " +
$"频率: {TestParams.Frequency}Hz, " +
$"目标循环: {TestParams.MaxCycles}");

// 启动数据记录线程
var logThread = new Thread(() => DataLogLoop("fatigue_data.csv"));
logThread.IsBackground = true;
logThread.Start();

// 执行疲劳测试
RunFatigueTest(master, TestParams);
}
finally
{
isRunning = false;
master.Dispose();
}
}

/// <summary>
/// CiA 402 状态机使能
/// </summary>
static void EnableServo(DarraEtherCAT master)
{
ref var servoOut = ref master.Slaves[SLAVE_SERVO].OutputsMapping<SV660N_Output>();
servoOut.ModesOfOperation = MODE_CSP;

ushort[] sequence = { CW_SHUTDOWN, CW_SWITCH_ON, CW_ENABLE_OPERATION };
foreach (var cmd in sequence)
{
servoOut.ControlWord = cmd;
Thread.Sleep(50);
}

ref var servoIn = ref master.Slaves[SLAVE_SERVO].InputsMapping<SV660N_Input>();
if ((servoIn.StatusWord & SW_FAULT) != 0)
{
Console.WriteLine($"警告: 伺服故障 (StatusWord=0x{servoIn.StatusWord:X4})");
return;
}

// 记录当前位置作为起始点
servoOut.TargetPosition = servoIn.ActualPosition;
Console.WriteLine("伺服已使能 (CSP 模式)");
}

/// <summary>
/// 疲劳测试主循环
/// </summary>
static void RunFatigueTest(DarraEtherCAT master, FatigueTestParams param)
{
// PDO 映射
ref var servoIn = ref master.Slaves[SLAVE_SERVO].InputsMapping<SV660N_Input>();
ref var servoOut = ref master.Slaves[SLAVE_SERVO].OutputsMapping<SV660N_Output>();
ref var forceIn = ref master.Slaves[SLAVE_LOADCELL].InputsMapping<EL3356_Input>();
ref var lvdtIn = ref master.Slaves[SLAVE_LVDT].InputsMapping<EL3164_Input>();

int homePosition = servoIn.ActualPosition;
long sampleIndex = 0;
int cycleCount = 0;
double prevTarget = 0;
double peakForce = 0;
double initialPeakForce = 0;
bool initialCalibrated = false;
DateTime lastDisplayTime = DateTime.Now;
var stopwatch = Stopwatch.StartNew();

Console.WriteLine("疲劳测试开始...\n");

while (isRunning && cycleCount < param.MaxCycles)
{
double t = stopwatch.Elapsed.TotalSeconds;

// 1. 正弦波位移 → PDO 映射
double targetDisp = param.Amplitude * Math.Sin(2 * Math.PI * param.Frequency * t);
servoOut.TargetPosition = homePosition + (int)(targetDisp * PulsesPerMm);

// 2. 读取传感器 (PDO 映射)
double currentForce = forceIn.Value * ForceScale;
double currentDisp = lvdtIn.Ch1_Value * LvdtScale + LvdtOffset;

// 3. 周期计数 (过零点上升沿)
if (prevTarget < 0 && targetDisp >= 0)
{
if (!initialCalibrated && cycleCount >= 3)
{
initialPeakForce = peakForce;
initialCalibrated = true;
Console.WriteLine($"初始刚度标定: 峰值力 = {initialPeakForce:F1}N");
}

if (initialCalibrated && peakForce < initialPeakForce * param.FailureThreshold)
{
double ratio = peakForce / initialPeakForce * 100;
Console.WriteLine($"\n[试件失效] 刚度退化至 {ratio:F1}%, " +
$"循环数: {cycleCount}");
break;
}

cycleCount++;
peakForce = 0;
}
prevTarget = targetDisp;

if (Math.Abs(currentForce) > peakForce)
peakForce = Math.Abs(currentForce);

// 4. 记录数据
dataBuffer.Enqueue(new TestDataPoint
{
SampleIndex = sampleIndex++,
Time = t,
TargetDisplacement = targetDisp,
ActualDisplacement = currentDisp,
Force = currentForce,
CycleCount = cycleCount
});

// 5. 安全检查
if (Math.Abs(currentForce) > param.MaxForce)
{
Console.WriteLine($"\n[安全停止] 力超限: " +
$"{currentForce:F1}N > {param.MaxForce:F1}N");
EmergencyStop(master, homePosition);
return;
}

// 6. 状态显示
if ((DateTime.Now - lastDisplayTime).TotalSeconds >= 1)
{
string stiff = initialCalibrated
? $", 刚度: {peakForce / initialPeakForce * 100:F1}%"
: "";
Console.WriteLine(
$"循环: {cycleCount}/{param.MaxCycles}, " +
$"力: {currentForce:F1}N, " +
$"位移: {currentDisp:F3}mm{stiff}");
lastDisplayTime = DateTime.Now;
}

Thread.Sleep(1);
}

// 测试完成, 回到原点
servoOut.TargetPosition = homePosition;
Console.WriteLine($"\n测试完成! 循环: {cycleCount}, " +
$"采样: {sampleIndex}");
}

/// <summary>
/// 紧急停止: 快速停止 + 回原点
/// </summary>
static void EmergencyStop(DarraEtherCAT master, int homePosition)
{
ref var servoOut = ref master.Slaves[SLAVE_SERVO].OutputsMapping<SV660N_Output>();
servoOut.ControlWord = CW_QUICK_STOP;
Thread.Sleep(500);

// 恢复使能, 缓慢回原点
servoOut.ControlWord = CW_ENABLE_OPERATION;
Thread.Sleep(50);
servoOut.TargetPosition = homePosition;

Console.WriteLine("[紧急停止] 已停止并回原点");
}

/// <summary>
/// 数据记录线程 (低频批量写入)
/// </summary>
static void DataLogLoop(string filename)
{
using (var writer = new StreamWriter(filename))
{
writer.WriteLine("Index,Time_s,Target_mm," +
"Actual_mm,Force_N,Cycle");

while (isRunning)
{
while (dataBuffer.TryDequeue(out var d))
{
writer.WriteLine(
$"{d.SampleIndex},{d.Time:F3}," +
$"{d.TargetDisplacement:F4}," +
$"{d.ActualDisplacement:F4}," +
$"{d.Force:F2},{d.CycleCount}");
}

writer.Flush();
Thread.Sleep(100);
}
}
}
}