
csharp
using System;
using System.Windows.Forms;
namespace c_顺序控制demo
{
public partial class Form1 : Form
{
// =================================================================
// 核心变量定义:模拟 PLC 内部寄存器
// =================================================================
// 定义流程状态枚举,增强可读性(等同于 PLC 的步进状态)
private enum Step
{
Idle = 0,
StartInfeed = 10, // 进料步
Processing = 20, // 加工步
Outfeed = 30, // 出料步
Complete = 40 // 周期完成
}
private Step _currentStep = Step.Idle; // 当前运行步
private bool _isPaused = false; // 暂停标志位 (类似中间继电器)
private int _processCounter = 0; // 模拟计时器累积值
public Form1()
{
InitializeComponent();
InitializeCustomSettings();
}
private void InitializeCustomSettings()
{
// 初始化定时器:模拟 PLC 扫描周期 100ms
processTimer.Interval = 100;
processTimer.Enabled = false;
// 绑定定时器事件(如果在设计器里绑过了,这里可以删掉)
processTimer.Tick += ProcessTimer_Tick;
lblStatus.Text = "系统就绪,请点击启动";
}
// =================================================================
// 主扫描逻辑:相当于 PLC 的主程序循环
// =================================================================
private void ProcessTimer_Tick(object sender, EventArgs e)
{
// 顶尖架构首要规则:优先判断"允许运行"条件
if (_isPaused) return;
// 使用 Case 语句实现严格的步进控制
switch (_currentStep)
{
case Step.Idle:
UpdateStatus("停止中");
break;
case Step.StartInfeed:
UpdateStatus("步骤 10: 正在进料...");
if (WaitCycles(20)) // 模拟等待 2 秒 (20 * 100ms)
{
_currentStep = Step.Processing;
}
break;
case Step.Processing:
UpdateStatus("步骤 20: 核心加工中...");
if (WaitCycles(30)) // 模拟等待 3 秒
{
_currentStep = Step.Outfeed;
}
break;
case Step.Outfeed:
UpdateStatus("步骤 30: 正在出料...");
if (WaitCycles(15)) // 模拟等待 1.5 秒
{
_currentStep = Step.Complete;
}
break;
case Step.Complete:
UpdateStatus("步骤 40: 单次循环完成,准备重启");
_currentStep = Step.StartInfeed; // 自动跳转回第一步,实现不停循环
break;
default:
_currentStep = Step.Idle;
break;
}
}
// =================================================================
// 功能块封装:模拟 PLC 中的 TON 计时器
// =================================================================
private bool WaitCycles(int target)
{
_processCounter++;
if (_processCounter >= target)
{
_processCounter = 0; // 计数器清零,准备下一步
return true;
}
return false;
}
// =================================================================
// UI 事件处理:按钮控制
// =================================================================
private void btnStart_Click(object sender, EventArgs e)
{
_currentStep = Step.StartInfeed;
_processCounter = 0;
_isPaused = false;
processTimer.Start();
UpdateStatus("流程启动");
}
private void btnPause_Click(object sender, EventArgs e)
{
// 仅置位暂停标志,不停止 Timer,模拟控制系统的"暂停/挂起"
_isPaused = true;
UpdateStatus($"已暂停在: {_currentStep}");
}
private void btnResume_Click(object sender, EventArgs e)
{
// 复位暂停标志,逻辑从断点自动继续
_isPaused = false;
UpdateStatus("恢复运行...");
}
private void UpdateStatus(string msg)
{
// 实时反馈到界面
lblStatus.Text = $"状态: {msg} | 计数: {_processCounter}";
}
}
}
//------------------总结如下-------------------------
为什么说这是"顶尖"的写法?
-
业务逻辑与定时器控制分离
很多初学者会在暂停时调用 Timer.Stop()。高手不这么做。 在工业控制思维中,主 CPU(定时器)应该一直运行,通过一个"允许位"(如 _isPaused)来控制业务逻辑是否执行。这样可以方便地在暂停期间监控传感器状态或处理 UI 更新。
-
枚举(Enum)优于数字
使用 Step.Processing 代替数字 20。当你的程序达到几千行时,你不需要去查文档才知道数字代表什么,代码本身就是文档。
-
模拟 PLC 计数器
我写了一个 WaitCycles 方法。这模仿了 PLC 里的 TON(接通延时定时器)。在 switch 结构中,只有当计数达标时才切换状态,这保证了状态切换的严谨性,不会因为 UI 的卡顿导致逻辑跳步。
-
易于实现"断点续传"
因为我们在暂停时没有重置 _currentStep 和 _processCounter,所以按下"继续"按钮时,程序会精准地从刚才那一秒、那个步骤继续往下跑,完美符合产线逻辑。