c#状态机框架了解和学习
作为一名经常和PLC打交道的上位机工程师,经常听到PLC工程师说什么状态机,以及使用状态机的好处,于是乎,我也借助豆老师简单的检索了一下资料。开始之前,要牢记这句话,不要写一堆 if-else,而是把 “当前状态” 单独拎出来管理。
一、状态机定义
有限状态机 FSM
设备任何时刻只处在一个固定工况,收到外部触发信号 / IO 信号 / 指令 / 定时器到,按预设规则从一个状态跳到另一个状态,且禁止非法跳转。
三大核心要素(设备):
- 状态 State:设备当前工况 → 待机、复位中、自动运行、手动、半自动、故障、暂停、下料中、上料中
- 触发 Trigger:启动按钮、IO 感应、气缸到位、伺服到位、报警信号、上位机下发指令、定时器超时
- 流转 Transition:规定好什么状态下收到什么信号,能跳到哪个状态
总结:把设备所有工况拆成固定状态,用规则管住跳转,不用一堆 if-else 乱嵌套。
二、C# 非标设备开发中哪些场景会用到状态机
几乎所有带流程、带模式切换的功能,都能用状态机优化。
1. 整机运行状态(最常用)
设备全局状态枚举:
- 待机 Idle → 复位中 Reset → 就绪 Ready → 自动运行 AutoRun → 手动模式 Manual → 暂停 Pause → 故障 Alarm → 急停 Emergency约束规则:
- 故障状态下禁止启动
- 急停后只能回到待机,不能直接进自动
- 必须先复位完成才能进自动这就是标准状态机,不用状态机,你要写几十行 if 嵌套,后期改逻辑麻烦的要死
2. 单工位 / 单工艺流程状态
比如:上料工位、移栽工位、真空吸附、压合、烘烤、下料
拆分状态:待机 → 取料开始 → 上升到位 → 平移 → 下降到位 → 放料完成 → 回归待机
每一步必须上一步完成才能进下一步,一步错整个流程卡死,用状态机管控步骤流转。
3. 气缸 / 电磁阀 / 伺服单轴动作状态
单轴:空闲、动作中、到位、超时故障
管控:动作中不能重复发指令;超时自动切故障状态。
4. 报警 & 故障处理状态
正常运行 → 轻微报警 → 严重故障 → 确认复位 → 恢复待机
限制:故障未确认,禁止自动启动。
5. 人机界面 HMI 页面状态
页面:初始加载、连接 PLC 中、通信正常、通信断开、参数配置中、配方下载中
6. 串口 / TCP/SECS/GEM 通信状态
半导体设备肯定用到:未连接 → 连接中 → 已连接 → 通信交互中 → 断连 → 重连
SECS(半导体标准协议) 消息收发、会话建立、断线重连全靠状态机管住。
7. 配方 / 流程步骤执行
多工序依次执行:步骤 1→步骤 2→步骤 3→结束,每步等待外部信号再跳转,典型状态机。
三、状态机 和 异步编程、多线程 到底矛不矛盾?
给出结论:完全不矛盾,状态机≠单线程,我最初有这个疑惑是因为PLC的编程方式是单线程编程,从头到尾循环扫描
1.三者定位
- 状态机:管业务逻辑、工况流转、步骤顺序、合法跳转只管「现在是什么状态、下一步能去哪」
- 多线程:管并行干活比如:UI 线程、PLC 通信线程、IO 轮询线程、日志线程、工艺后台线程
- 异步编程 async/await:管不阻塞线程、等待 IO / 等待动作完成比如等待气缸到位、等待伺服定位完成、等待 PLC 回复、等待 SECS 消息
2. 三者关系
- 状态机:作为业务骨架,不管你是多线程还是异步,所有动作都在当前状态规则内执行
- 多线程:开独立线程循环跑状态机逻辑例:一个专用逻辑线程,while 死循环,每帧扫描当前状态、检测触发信号、做状态跳转。
- 异步编程:在某个状态里,用 async/await 等待动作完成,不卡 UI、不卡整个状态机循环
异步是用来等动作,多线程是用来并行,状态机是用来管流程,三层各司其职,不冲突。
3. 会不会冲突?什么时候要注意?
不矛盾,但要注意线程安全:
- 设备当前状态变量 多线程会读写,要加锁 / 原子变量
- 禁止在 UI 线程、通信线程、逻辑线程同时乱改状态
- 状态切换统一收口到一个方法,集中加锁控制
四、PLC 单线程状态机 和 C# 多线程 / 异步 对比
1、核心区别
| 维度 | PLC | C# 上位机 / 非标软件 |
|---|---|---|
| 运行模型 | 单线程循环扫描、串行 | 多线程、Task、async/await、并行 |
| 状态机写法 | 标志位 / SFC/ST 枚举 Switch | Enum + 后台线程循环 + 异步等待 |
| 等待动作 | 靠下一个扫描周期轮询判断到位 | await 异步等待、不阻塞线程 |
| 并发能力 | 弱,只能分时串行 | 强,可以同时跑通信、UI、日志、多工位逻辑 |
2.、通俗类比
- PLC:一个工人,从头走到尾,一圈又一圈,重复执行,啥事都按顺序轮询查一遍(单线程状态机)
- C# 上位机:多个工人分工,一个管 UI、一个管 PLC 通信、一个管工艺逻辑、一个记日志,同时干活,工艺逻辑里还用状态机管流程(多线程 + 状态机 + 异步)
要注意:C# 多线程下状态变量要加锁、防并发乱改状态
五、举例子-非标设备电芯搬运程序
假设我们不用PLC,而使用运动控制卡来做一个锂电设备电芯搬运程序
设计思路
1、电芯本体有二维码,二维码中有追溯信息,反馈该电芯是OK还是NG
2、设备有NG储料盒,储料盒满,设备停止运行,需要手动把储料盒中的电芯拿走,复位并启动
3、搬运过程中安全光栅被触发,机台停止,需要复位并启动
4、搬运和吸取使用轴和气缸实现
先画出流程图,后续直接写代码就行,

通过枚举的状态来控制整个流程,但是要注意,加锁,要不然会线程混乱,代码写的很粗糙,大概懂意思就行
public enum MachineState
{
Idle, // 待机
Resetting, // 复位
Ready, // 就绪
Stop, // 停止
Scanning, // 扫码
Picking, // 吸取
MovingOK, // 搬OK
MovingNG, // 搬NG
AlarmNgFull,// NG满
AlarmGuard // 光栅
}
public partial class MainWindow : Window
{
// 核心状态 + 线程锁
private MachineState _currentState;
private readonly object _stateLock = new object(); // 加锁!解决混乱
private Thread _stateThread;
private bool _isRunning;
// 信号
private bool _ngBoxFull;
private bool _guardTriggered;
public MainWindow()
{
InitializeComponent();
_currentState = MachineState.Idle;
// 常驻线程
_stateThread = new Thread(StateMachineLoop)
{
IsBackground = true,
Priority = ThreadPriority.Normal
};
_stateThread.Start();
UpdateUI();
}
// ===================== 状态机循环 =====================
private void StateMachineLoop()
{
while (true)
{
Thread.Sleep(200);
lock (_stateLock) // 加锁,保证同一时间只执行一个状态
{
try
{
// ============== 最高优先级:停止 ==============
if (_currentState == MachineState.Stop)
{
continue; // 彻底卡住,不往下执行
}
// ============== 第二优先级:安全光栅 ==============
if (_guardTriggered)
{
_currentState = MachineState.AlarmGuard;
Log("安全光栅触发 → 立即停止");
continue;
}
// ============== 未运行 ==============
if (!_isRunning)
{
_currentState = MachineState.Idle;
continue;
}
// ============== 主状态机 ==============
switch (_currentState)
{
case MachineState.Idle:
break;
case MachineState.Resetting:
Log("复位中...");
Thread.Sleep(1500);
_ngBoxFull = false;
_guardTriggered = false;
Log("复位完成 → 待机");
_currentState = MachineState.Idle; // 正确回到Idle
_isRunning = false;
break;
case MachineState.Ready:
if (_ngBoxFull)
{
_currentState = MachineState.AlarmNgFull;
break;
}
Log("等待电池...");
Thread.Sleep(1000);
_currentState = MachineState.Scanning;
break;
case MachineState.Scanning:
Log("扫码中...");
Thread.Sleep(800);
bool ok = new Random().Next(2) == 0;
Log(ok ? "OK" : "NG");
_currentState = MachineState.Picking;
break;
case MachineState.Picking:
Log("吸取电池");
Thread.Sleep(1000);
_currentState = ListLog.Items.Cast<string>().Last().Contains("OK")
? MachineState.MovingOK
: MachineState.MovingNG;
break;
case MachineState.MovingOK:
Log("搬运至传送带");
Thread.Sleep(1200);
_currentState = MachineState.Ready;
break;
case MachineState.MovingNG:
Log("搬运至NG盒");
Thread.Sleep(1200);
_currentState = _ngBoxFull ? MachineState.AlarmNgFull : MachineState.Ready;
break;
case MachineState.AlarmNgFull:
Log("NG盒满 → 停止");
break;
case MachineState.AlarmGuard:
Log("光栅报警 → 停止");
break;
}
}
catch (Exception ex)
{
Log("异常:" + ex.Message);
}
}
UpdateUI();
}
}
// ===================== 按钮 =====================
private void BtnStart_Click(object sender, RoutedEventArgs e)
{
lock (_stateLock)
{
if (_currentState != MachineState.Idle)
{
Log("必须在待机状态启动");
return;
}
Log("启动设备");
_isRunning = true;
_currentState = MachineState.Ready;
UpdateUI();
}
}
private void BtnReset_Click(object sender, RoutedEventArgs e)
{
lock (_stateLock)
{
Log("执行复位");
_isRunning = false;
_guardTriggered = false;
_ngBoxFull = false;
_currentState = MachineState.Idle;
UpdateUI();
}
}
private void BtnEnd_Click(object sender, RoutedEventArgs e)
{
lock (_stateLock)
{
Log("手动停止 → 设备停止");
_isRunning = false;
_currentState = MachineState.Stop; // 真正停止
UpdateUI();
}
}
private void BtnNgFull_Click(object sender, RoutedEventArgs e)
{
lock (_stateLock)
{
_ngBoxFull = true;
}
}
private void BtnGuard_Click(object sender, RoutedEventArgs e)
{
lock (_stateLock)
{
_guardTriggered = !_guardTriggered;
}
}
// ===================== UI =====================
private void UpdateUI()
{
Dispatcher.Invoke(() =>
{
TxtState.Text = _currentState.ToString();
TxtState.Foreground = _currentState.ToString().Contains("Alarm") || _currentState == MachineState.Stop
? Brushes.Red
: _currentState == MachineState.Ready
? Brushes.Green
: Brushes.Blue;
BtnStart.IsEnabled = _currentState == MachineState.Idle;
BtnStart.Background = _currentState == MachineState.Ready ? Brushes.LightGreen : Brushes.LightGray;
BtnEnd.Background = _currentState == MachineState.Stop ? Brushes.Red : Brushes.LightGray;
BtnGuard.Background = _guardTriggered ? Brushes.Red : Brushes.LightGray;
});
}
private void Log(string msg)
{
Dispatcher.Invoke(() =>
{
ListLog.Items.Add($"{DateTime.Now:HH:mm:ss} | {msg}");
});
}
}

更多推荐

所有评论(0)