3、C#基础回顾:变量与数据类型、流程控制、面向对象基础、委托与事件
各位同学,咱们直接进入正题。做IPC运动控制,说白了就是用代码指挥机器动起来。C#是我们的指挥语言,基础不牢,后面写控制逻辑时你会很痛苦。我见过太多人,上来就写运动控制代码,结果连变量类型都搞混,导致电机跑飞了还找不到原因。
这一章,我带大家快速过一遍C#的核心基础。别嫌啰嗦,这些都是我踩过的坑换来的经验。
3.1 变量与数据类型——选对类型,少出Bug
变量就是存数据的盒子。但盒子有大有小,装的东西也不一样。选错了,轻则浪费内存,重则数据溢出,电机直接给你来个急停。
值类型 vs 引用类型
这是C#里最基础的分水岭。值类型存的是数据本身,引用类型存的是数据的地址。我刚开始做项目时,有一次把结构体当类用,结果赋值时数据没复制过去,位置反馈一直不对。排查了一下午,才发现是类型搞混了。
| 类别 | 常见类型 | 存储位置 | 赋值行为 |
|---|---|---|---|
| 值类型 | int, float, double, bool, struct | 栈 | 复制值 |
| 引用类型 | string, class, array, delegate | 堆 | 复制引用 |
避坑指南:运动控制中,位置、速度这些关键数据,我建议用double类型。float精度不够,跑长距离时累积误差会让你崩溃。我曾经用float存累计位置,跑了10万脉冲后,误差达到了0.5个脉冲——这在精密定位中是不可接受的。
// 正确的数据类型选择
double targetPosition = 10000.5; // 目标位置,单位:脉冲
double currentSpeed = 500.0; // 当前速度,单位:脉冲/秒
bool isHomeCompleted = false; // 回零完成标志
string axisName = "X轴"; // 轴名称,引用类型
3.2 流程控制——让机器按你的节奏走
流程控制就是告诉机器:什么情况下做什么事。运动控制里,最常用的就是条件判断和循环。
if-else 的实战用法
我写运动控制程序时,if-else用得最多。比如判断限位开关是否触发、检查是否到达目标位置。这里有个小技巧:把最可能发生的情况放在最前面,能提高执行效率。
// 实际项目中的限位判断
if (currentPosition >= positiveLimit)
{
// 正向限位触发,立即停止
StopAxis(axisId);
Console.WriteLine("正向限位触发,轴已停止");
}
else if (currentPosition <= negativeLimit)
{
// 负向限位触发
StopAxis(axisId);
Console.WriteLine("负向限位触发,轴已停止");
}
else
{
// 正常范围内,继续运动
ContinueMove(axisId, targetSpeed);
}
我的习惯:在运动控制中,尽量少用switch-case。因为轴的状态变化很快,if-else的短路特性可以更快地跳出判断。而且,if-else的可读性更好,调试时一眼就能看出逻辑。
循环——别让机器死循环
做点位运动时,经常需要循环等待某个条件满足。比如等待轴到达目标位置。但千万要注意:一定要加超时退出机制。我曾经犯过这个错,程序一直卡在while循环里,机器都撞到硬限位了还没退出。
// 带超时的等待循环
int timeout = 5000; // 超时时间5秒
int elapsed = 0;
bool isArrived = false;
while (elapsed < timeout)
{
if (CheckPositionReached(axisId, targetPosition))
{
isArrived = true;
break;
}
Thread.Sleep(10); // 每10ms检查一次
elapsed += 10;
}
if (!isArrived)
{
// 超时处理,紧急停止
EmergencyStop(axisId);
Console.WriteLine("轴运动超时,已紧急停止");
}
3.3 面向对象基础——把轴当成对象来管
面向对象编程,说白了就是把现实世界的东西抽象成代码里的对象。在运动控制里,每个轴就是一个对象。它有属性(当前位置、速度)、有方法(启动、停止)、有事件(到达位置、报警)。
封装——把轴的数据藏起来
我习惯把轴的所有参数封装成一个类。外部代码只能通过公开的方法来操作轴,不能直接修改内部数据。这样能防止误操作。
public class Axis
{
// 私有字段,外部不能直接访问
private double _currentPosition;
private double _currentSpeed;
private bool _isEnabled;
// 公开属性,只读
public double CurrentPosition => _currentPosition;
public bool IsEnabled => _isEnabled;
// 公开方法,控制轴的行为
public void MoveTo(double targetPosition, double speed)
{
if (!_isEnabled)
{
Console.WriteLine("轴未使能,无法运动");
return;
}
// 执行运动逻辑
Console.WriteLine($"轴正在移动到 {targetPosition},速度 {speed}");
}
public void Enable()
{
_isEnabled = true;
Console.WriteLine("轴已使能");
}
public void Disable()
{
_isEnabled = false;
Console.WriteLine("轴已去使能");
}
}
注意:千万不要把轴的关键参数设为public。我见过有人直接把_currentPosition设为public,结果在别的地方不小心改了它,导致位置反馈和实际位置不一致,机器直接撞机。封装不是形式,是保护。
继承——复用通用的轴逻辑
如果项目中有多种类型的轴(伺服轴、步进轴、虚拟轴),可以把公共逻辑写在基类里,子类继承并扩展。
// 基类:通用轴
public class BaseAxis
{
public virtual void Start()
{
Console.WriteLine("轴启动");
}
public virtual void Stop()
{
Console.WriteLine("轴停止");
}
}
// 子类:伺服轴
public class ServoAxis : BaseAxis
{
public override void Start()
{
base.Start(); // 调用基类方法
Console.WriteLine("伺服轴已使能");
}
public void SetTorqueLimit(double limit)
{
Console.WriteLine($"设置扭矩限制为 {limit}");
}
}
3.4 委托与事件——让轴主动通知你
委托,你可以把它理解成一个方法的指针。事件,则是基于委托的一种发布-订阅机制。在运动控制里,这个太有用了。
为什么需要委托?
你想想看,如果轴到达目标位置后,你需要执行一系列操作:停止其他轴、记录日志、更新UI。如果把这些逻辑都写在轴类里,那轴类就太臃肿了。用委托,你可以把「到达位置后要做什么」这个行为,作为参数传进去。
// 定义委托
public delegate void PositionReachedHandler(double position);
public class AxisWithEvent
{
// 声明事件
public event PositionReachedHandler OnPositionReached;
private double _currentPosition;
public void MoveTo(double target)
{
// 模拟运动
_currentPosition = target;
Console.WriteLine($"轴到达位置 {target}");
// 触发事件
OnPositionReached?.Invoke(_currentPosition);
}
}
// 使用事件
AxisWithEvent axis = new AxisWithEvent();
axis.OnPositionReached += (pos) =>
{
Console.WriteLine($"轴已到达 {pos},执行后续操作");
// 这里可以写停止其他轴、记录日志等逻辑
};
axis.MoveTo(1000.0); // 运动到1000,自动触发事件
我的经验:在IPC运动控制中,事件机制是解耦的关键。轴只管运动,到达位置后通知外部。外部程序想做什么就做什么,互不干扰。我曾经用事件机制做了一个多轴联动系统,每个轴到达位置后触发事件,主控程序根据事件顺序决定下一步动作。代码清晰,调试方便。
委托的另一种用法——回调函数
有时候,运动控制卡提供的API是异步的。你发一个运动指令,它不会立即返回结果。这时候就需要回调函数。委托就是实现回调的最佳方式。
// 异步运动回调
public void AsyncMoveTo(double target, Action callback)
{
// 模拟异步操作
Task.Run(() =>
{
Thread.Sleep(1000); // 模拟运动耗时
bool success = true;
callback(success); // 运动完成后回调
});
}
// 调用
AsyncMoveTo(5000.0, (result) =>
{
if (result)
{
Console.WriteLine("异步运动完成");
}
else
{
Console.WriteLine("异步运动失败");
}
});
好了,C#基础回顾就到这里。这些内容看起来简单,但都是运动控制编程的基石。下一章,我们会把这些知识用到实际的IPC运动控制卡编程中。到时候你会发现,基础扎实了,写控制代码就像搭积木一样顺畅。