跳到主要内容

WinForm 2种模式控制步进电机

硬件配置

  • 主站: Windows 10 × 1 + Intel i5 × 1
  • 步进驱动器: FASTECH Ezi-SERVO2 EtherCAT 步进驱动器 × 1 + 57 步进电机 × 1

性能指标

  • 控制模式: 位置模式 (CSP) / 速度模式 (CSV)
  • 速度范围: 0 ~ 3000 RPM
  • 位置精度: ±1 脉冲

应用场景

WinForm 界面控制单轴步进电机,支持 CSP 位置模式和 CSV 速度模式切换。后台线程 20Hz 刷新实际位置/速度到 UI,适合调试验证和入门学习。

代码示例

PDO 结构体定义

using System.Runtime.InteropServices;

/// <summary>
/// FASTECH Ezi-SERVO2 步进驱动器输入 PDO
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Stepper_Input
{
public ushort StatusWord; // 0x6041 状态字
public int ActualPosition; // 0x6064 实际位置 (脉冲)
public int ActualVelocity; // 0x606C 实际速度 (脉冲/s)
}

/// <summary>
/// FASTECH Ezi-SERVO2 步进驱动器输出 PDO
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Stepper_Output
{
public ushort ControlWord; // 0x6040 控制字
public sbyte ModesOfOperation; // 0x6060 操作模式: 8=CSP, 9=CSV
public int TargetPosition; // 0x607A 目标位置 (脉冲)
public int TargetVelocity; // 0x60FF 目标速度 (脉冲/s)
}

WinForm 完整控制代码

using System;
using System.Threading;
using System.Windows.Forms;
using DarraEtherCAT_Master;

namespace StepperControl
{
public partial class MainForm : Form
{
DarraEtherCAT master;
bool isRunning = false;

// 从站索引
const int SLAVE_STEPPER = 0;

// 操作模式常量
const sbyte MODE_CSP = 8; // 周期同步位置模式
const sbyte MODE_CSV = 9; // 周期同步速度模式

// 电机参数
const int PULSES_PER_REV = 10000; // 每转脉冲数

public MainForm()
{
InitializeComponent();
}

/// <summary>
/// 连接按钮 - 初始化主站并进入OP状态
/// </summary>
void btnConnect_Click(object sender, EventArgs e)
{
master = new DarraEtherCAT()
.LoadENI("config/stepper.xml")
.Build();

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

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

// 使能驱动器
EnableStepper();

// 启动状态刷新线程
isRunning = true;
new Thread(StatusUpdateLoop) { IsBackground = true }.Start();

btnConnect.Enabled = false;
grpPosition.Enabled = true;
grpVelocity.Enabled = true;
lblStatus.Text = "已连接";
}

/// <summary>
/// 使能步进驱动器 (CIA402 状态机)
/// </summary>
void EnableStepper()
{
ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();

output.ControlWord = 0x0006; // Shutdown
Thread.Sleep(5);
output.ControlWord = 0x0007; // Switch On
Thread.Sleep(5);
output.ControlWord = 0x000F; // Enable Operation
Thread.Sleep(5);
}

// ==================== 位置模式 ====================

/// <summary>
/// 位置模式 - 运行到目标位置
/// </summary>
void btnMoveToPosition_Click(object sender, EventArgs e)
{
int targetPulses = (int)(numTargetPosition.Value * PULSES_PER_REV / 360.0);

ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();
output.ModesOfOperation = MODE_CSP;
output.TargetPosition = targetPulses;
output.ControlWord = 0x001F; // 执行位置指令
}

/// <summary>
/// 位置模式 - 回零
/// </summary>
void btnHome_Click(object sender, EventArgs e)
{
ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();
output.ModesOfOperation = MODE_CSP;
output.TargetPosition = 0;
output.ControlWord = 0x001F;
}

// ==================== 速度模式 ====================

/// <summary>
/// 速度模式 - 启动
/// </summary>
void btnStartVelocity_Click(object sender, EventArgs e)
{
int rpm = (int)numTargetRPM.Value;
int velocityPulses = rpm * PULSES_PER_REV / 60; // 转换为脉冲/s

// 反转
if (chkReverse.Checked)
velocityPulses = -velocityPulses;

ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();
output.ModesOfOperation = MODE_CSV;
output.TargetVelocity = velocityPulses;
output.ControlWord = 0x000F;
}

/// <summary>
/// 速度模式 - 停止
/// </summary>
void btnStopVelocity_Click(object sender, EventArgs e)
{
ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();
output.TargetVelocity = 0;
}

// ==================== 状态刷新 ====================

/// <summary>
/// 后台线程持续刷新电机状态到UI
/// </summary>
void StatusUpdateLoop()
{
// 映射只需获取一次,指向共享PDO内存,循环内直接读取即可
ref var input = ref master.Slaves[SLAVE_STEPPER].InputsMapping<Stepper_Input>();

while (isRunning)
{
double positionDeg = input.ActualPosition * 360.0 / PULSES_PER_REV;
double velocityRPM = input.ActualVelocity * 60.0 / PULSES_PER_REV;

// 更新UI (线程安全)
Invoke(new Action(() =>
{
lblActualPosition.Text = $"{positionDeg:F2}°";
lblActualVelocity.Text = $"{velocityRPM:F1} RPM";
lblStatusWord.Text = $"0x{input.StatusWord:X4}";
}));

Thread.Sleep(50); // 20Hz UI刷新
}
}

/// <summary>
/// 窗体关闭时释放资源
/// </summary>
protected override void OnFormClosing(FormClosingEventArgs e)
{
isRunning = false;
Thread.Sleep(100);

// 停止电机
if (master != null)
{
ref var output = ref master.Slaves[SLAVE_STEPPER].OutputsMapping<Stepper_Output>();
output.TargetVelocity = 0;
output.ControlWord = 0x0006; // Shutdown
Thread.Sleep(10);

master.Dispose();
}

base.OnFormClosing(e);
}
}
}