最近在搞运动控制框架开发,随手撸了个指令表架构的Demo。这个框架最有趣的地方在于程序嵌套执行和硬件抽象设计,咱们边看代码边唠嗑

📅 发布时间:2026/7/12 7:43:38 👁️ 浏览次数:
最近在搞运动控制框架开发,随手撸了个指令表架构的Demo。这个框架最有趣的地方在于程序嵌套执行和硬件抽象设计,咱们边看代码边唠嗑
指令表运动控制软件框架Demo。 1.支持多程序增删改 2.主程序可调用子程序运行 3.工程文件可加载保存另存为 4.含仿真界面窗口 5.目前适配雷赛总线DMC-E3032控制卡为例子其它卡可自主扩展。 开发语言C#程序管理模块用了个骚操作——双层ObservableCollection。主界面列表绑定这个集合增删改直接起飞private ObservableCollectionMotionProgram _programs new(); public ReadOnlyObservableCollectionMotionProgram ProgramList { get; } // 添加程序时的线程安全操作 public void AddProgram(string name) { lock (_syncLock) { if (_programs.Any(p p.Name name)) return; _programs.Add(new MotionProgram(name)); } }这里用ReadOnlyObservableCollection对外暴露只读视图内部操作通过锁对象防止多线程打架。实战中发现不加锁的话界面频繁操作时集合可能被改坏。子程序调用玩的是栈结构。执行引擎里有个调用堆栈碰到CALL指令就把当前上下文压栈private StackProgramContext _callStack new(); void ExecuteProgram(MotionProgram program) { var context new ProgramContext(program); while (context.Pointer program.Instructions.Count) { var instr program.Instructions[context.Pointer]; if(instr.Type InstructionType.CALL) { _callStack.Push(context.Clone()); // 深拷贝当前执行点 context new ProgramContext(GetProgram(instr.TargetName)); continue; } ExecuteSingleInstruction(instr); context.Pointer; } }注意这里的Clone方法是关键得深拷贝当前执行位置和局部变量。之前忘了拷贝局部变量结果多个子程序调用互相污染数据debug到怀疑人生。工程文件持久化用了自定义的二进制格式但序列化过程挺有意思public void SaveProject(string path) { var dto new ProjectDTO { Programs _programs.Select(p p.ToDTO()).ToList(), MainProgramIndex _selectedProgramIndex }; using var stream File.Create(path); var formatter new BinaryFormatter(); formatter.Serialize(stream, dto); }这里故意没用JSON而选择二进制序列化主要是考虑运动轨迹数据量大的时候性能差异明显。不过注意要给所有DTO类打[Serializable]标签不然直接崩给你看。指令表运动控制软件框架Demo。 1.支持多程序增删改 2.主程序可调用子程序运行 3.工程文件可加载保存另存为 4.含仿真界面窗口 5.目前适配雷赛总线DMC-E3032控制卡为例子其它卡可自主扩展。 开发语言C#仿真界面用WPF的Canvas搞了个动态坐标系实时绘制轴位置void UpdateAxisPosition(int axisNo, double position) { Dispatcher.Invoke(() { var ellipse _axisMarks[axisNo]; Canvas.SetLeft(ellipse, position * _pixelPerUnit _originX); Canvas.SetTop(ellipse, -position * _pixelPerUnit _originY); }); }这里有个坑WPF的坐标系Y轴向下所以计算Y坐标时要取反。之前直接套用公式导致运动方向反了被测试同事吐槽像跳机械舞。雷赛控制卡的适配层展示了如何做硬件抽象public class DMC_E3032Controller : IMotionController { public void Connect(string config) { int result DmcBoard.dmc_board_init(0, ref _handle); if (result ! 0) throw new MotionException($初始化失败错误码:{result}); } public void SendCommand(string cmd) { // 雷赛奇葩协议要求命令以\r\n结尾 var formattedCmd cmd.TrimEnd() \r\n; DmcBoard.dmc_command(_handle, formattedCmd, formattedCmd.Length); } }重点是这个SendCommand方法雷赛的协议必须用\r\n结尾刚开始没注意这个细节命令死活不执行。抽象出IMotionController接口后加其他品牌控制卡就方便多了比如写个汇川的适配器实现相同接口就行。框架里还藏了个彩蛋在仿真窗口连续按五次CtrlShift会弹出隐藏的轨迹预测功能。这玩意儿用了个简单的位置外推算法double PredictNextPosition(double current, double speed) { // 假设0.1秒后的位置采样周期为2ms return current speed * 0.1 * (1 - Math.Exp(-5 * 0.1)); }这个指数衰减模型是为了防止预测位置超调实测比线性预测更接近真实电机行为。不过别太依赖这个预测毕竟实际运动有加速度限制。项目里最爽的部分是程序嵌套执行时看着子程序层层调用就像俄罗斯套娃。最崩溃的是有一次递归调用忘记做深度限制直接把调用栈撑爆了现在框架里强制加了最大调用深度32层的限制。