尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

用Python和pysoem在Ubuntu上实现EtherCAT伺服回零与运动控制

发布时间:2026/9/29 1:53:29

资讯中心
01
ARTICLE

用Python和pysoem在Ubuntu上实现EtherCAT伺服回零与运动控制

用Python和pysoem在Ubuntu上实现EtherCAT伺服回零与运动控制
做运动控制这些年我一直觉得 EtherCAT 最大的门槛不在协议本身而在“第一个能动的 Demo”永远隔着一层纱。这层纱来自哪里主站要自己搭、驱动要对着手册抠对象字典、状态机切换错了还不报错电机只是纹丝不动地“装死”。我之前在嵌入式侧用 C/C 写过主站后来一次项目里临时需要在 Ubuntu 22.04 上快速验证一台伺服电机的回零和点位运动实在不想再为环境折腾一周就把目光落在了 pysoem 上。这篇文章就把我最近的完整流程整理出来在 Ubuntu 22.04 上用 Python 3.10 和 pysoem 库把一台支持 CiA402 的 EtherCAT 伺服驱动器拉进周期通信做完电机回零再让它按目标位置转起来。内容偏向“保姆级”环境搭建、扫站配置、PDO 映射、状态机切换、常见排错都会讲到。适合正在被 EtherCAT 从站折腾的嵌入式工程师、刚接触运动控制的同学以及想用 Python 快速做验证的自动化从业者。1. 项目拆解与方案选型1.1 为什么是 Ubuntu 22.04 Python 3.10 pysoem先说结论这个组合不是能跑就行而是“用最小成本把协议链路跑通”的最优解之一。Ubuntu 22.04 LTS 自带 Python 3.10这意味着操作系统装好之后你不需要再去编译一个额外的 Python 版本省掉了很多“版本地狱”的问题。对工业控制类项目来说LTS 的另一个好处是内核和用户态库的更新周期长排错时查资料也容易网上踩坑记录一搜一大把。pysoem 是开源 EtherCAT 主站库 SOEM 的 Python 绑定。SOEM 本身就是轻量级用户态主站代表不需要像 IgH 那样去编译内核模块也不用买商业授权。pysoem 把扫描、配置、周期数据收发这些核心接口封装成了 Python 对象写起来很顺手特别适合快速验证和中小型项目。它的实时性肯定不如裸 C 主站但 EtherCAT 周期控制在 1ms 到 4ms 的范围内Python 完全够用前提是你别在通信循环里做太多重活。1.2 方案对比pysoem 与 IgH、TwinCAT 怎么选我把几个常见方案放在一起比过没有绝对好坏关键是看场景方案运行环境实时性上手难度适合场景IgHLinux 内核模块高高对实时性敏感的生产设备、多轴同步SOEM / pysoem用户态中低快速验证、教学、原型机、中小型设备TwinCATWindows非常高中高端自动化设备、多厂商生态厂商自带库视厂商而定中高中同品牌成套方案调试方便如果项目只是“让一台电机回零并且转起来”用 IgH 需要编译内核模块、配置启动脚本复杂度高出好几个量级。而 TwinCAT 虽然功能强但毕竟要 Windows 环境很多工控主机装的是 Ubuntu。pysoem 适合“先在 Linux 上把逻辑跑通后期再决定是否迁移到 C 或实时主站”的场景。我自己很多原型验证都是这么干的Python 负责写逻辑跑通了再换语言。1.3 硬件准备与选型注意事项软件是软柿子硬件才是老狐狸。这套方案涉及的硬件清单如下一台 Ubuntu 22.04 主机优先选择 Intel 千兆网卡例如 I210、I211 这类工业级网卡。Realtek 网卡虽能用但有时会因为驱动和 VLAN 处理问题出现丢包。支持 CiA402 标准和 EtherCAT 协议的伺服驱动器 伺服电机。国内常见的汇川、台达、松下以及一些国产 EtherCAT 驱动器基本都支持。直连网线或者一台支持巨型帧的工业交换机。不要把 EtherCAT 设备插在办公室普通交换机上容易出问题。24V 控制电源、动力电源、限位开关、急停按钮。这是我强烈建议接入的回零过程中如果限位没接好电机可能直接冲出去。选驱动时我的经验是先确认它有没有内置 EtherCAT 从站协议栈芯片例如 ESC 芯片再看厂商是否提供 ESI 文件。ESI 文件就像驱动的身份证主站靠它识别对象字典和 PDO 默认映射。没有 ESI 文件虽然也能用 pysoem 手动写映射但调试成本会高不少。2. 环境准备把地基打牢2.1 安装 Ubuntu 22.04 与 Python 3.10安装系统这一步不展开太多但有两个细节值得说。第一制作启动盘时建议用 Rufus 或者 balenaEtcher烧录方式选 DD 模式避免出现启动引导不兼容。第二安装分区时如果这台机器要长期跑运动控制建议把/和/home分开日志和虚拟环境放在数据盘重装系统时不会误删代码。系统装好之后确认版本和 Pythonlsb_release -a python3 -V如果输出是 Python 3.10.x那就直接进入下一步。Ubuntu 22.04 自带的 Python 3.10 已经包含 pip为了不污染系统环境我习惯建一个 venvsudo apt update sudo apt install -y python3-venv python3-pip mkdir -p ~/ethercat_demo cd ~/ethercat_demo python3 -m venv venv source venv/bin/activate后面所有操作在这个虚拟环境里做装什么包都不影响系统。2.2 安装 pysoem 和编译依赖pysoem 本质上是 SOEM 的 C 代码加 Python 绑定所以编译工具和头文件要装好sudo apt install -y build-essential cmake python3-dev pip install pysoem装完验证一下python -c import pysoem; print(pysoem.__version__)如果这一步报错绝大多数原因是缺少python3-dev或者build-essential。另外pysoem 有一些接口在 1.x 和 2.x 之间不太一样建议安装后先看下dir(pysoem)确认关键方法是否存在。我在实际项目中遇到过CyclicMaster和Master两个类的区别一个适合周期性运行一个适合手动控制收发节奏下面代码里我统一用Master的经典用法。2.3 权限、网卡配置与实时性调整EtherCAT 主站需要访问原始套接字所以要么用 root 权限运行 Python要么给 Python 可执行文件添加网络原始套接字能力sudo setcap cap_net_rawep $(which python3)我建议直接写成脚本每次运行前检查一下权限。然后修改 NetworkManager别让它抢占 EtherCAT 使用的网卡nmcli dev set eth0 managed no sudo ip link set eth0 up sudo ip addr add 192.168.1.100/24 dev eth0这里的 IP 地址其实不参与 EtherCAT 通信EtherCAT 帧走的是网卡 MAC 层不依赖 IP但给网卡一个静态 IP 可以避免系统网络栈做无谓的响应。实时性方面Ubuntu 默认内核不是硬实时内核但对 1ms 周期控制来说普通内核也够用。需要做的是把 Python 进程绑定到固定 CPU 核心并尽量别在这个核心上跑其他任务。系统启动参数里可以加isolcpus3然后代码里用taskset -c 3 python main.py。如果项目要求更高的实时确定性再安装linux-lowlatency内核甚至编译 PREEMPT_RT。3. EtherCAT 主站初始化与从站扫描3.1 EtherCAT 跑起来的基本流程EtherCAT 主站启动和普通以太网通信完全是两码事。它的状态机严格按照 INIT - PRE-OP - SAFE-OP - OP 这个顺序切换。INIT 阶段主站只做最基础的寻址PRE-OP 阶段可以通过邮箱通信读写 SDOSAFE-OP 阶段输入有效但输出被禁止OP 阶段才开始真正的周期数据交换。把 EtherCAT 通信比作一场快递分拣FMMU现场总线内存管理单元相当于每个从站的分拣规则PDO 映射相当于快递面单上预先填好的字段同步管理器 SM 统一管理每个从站输入输出的缓冲区。主站的工作就是在启动时把规则下发到位然后每个周期发送一组帧从站从帧里取走自己的输出数据把自己的输入数据填进帧里返回。3.2 用 pysoem 完成扫站和初始化先写一段最小初始化代码import pysoem import struct ifname eth0 master pysoem.Master() if master.open(ifname) ! 0: raise RuntimeError(打开网卡失败请检查权限或网卡名) try: slave_count master.config_init() if slave_count 0: print(f发现 {slave_count} 个从站) else: raise RuntimeError(未发现任何从站) for i, slave in enumerate(master.slaves): print(f从站{i}: name{slave.name} man{slave.man:#x} id{slave.id:#x} rev{slave.rev:#x}) master.config_map() master.config_dc() # 依次切到 SAFE_OP 和 OP master.state pysoem.SAFE_OP_STATE master.state pysoem.OP_STATE print(fOP 状态: {master.state:#x}) finally: master.close()config_init()返回从站数量内部会完成初始寻址和状态机推进到 PRE-OP。config_map()会根据从站的默认 PDO 映射或者你自定义的映射计算每个从站需要分配的 FMMU。config_dc()是分布式时钟配置多轴同步时尤其重要单轴用不到也可以调用防止有的从站在 DC 模式下发时序上产生状态错误。3.3 PDO 映射周期数据里到底装什么刚接触 EtherCAT 的时候我花了不少时间才明白 SDO 和 PDO 的区别。SDO 适合非周期数据读写比如配置参数、切换控制模式PDO 则是周期性交换的“快递面单”每个周期主站都把控制字、目标位置打包发出去从站把状态字、实际位置打包送回来。很多驱动默认就有 PDO 映射但默认映射不一定包含你需要的对象。更安全的做法是在config_map()之前手动配置 PDO。pysoem 支持给从站设置回调函数def config_func(slave): if servo not in slave.name.lower(): return # 清空原有 TxPDO/RxPDO 映射 slave.sdo_write(0x1600, 0, 0) slave.sdo_write(0x1A00, 0, 0) # RxPDO控制字、目标位置、目标速度、操作模式 slave.sdo_write(0x1600, 1, 0x60400010) # 控制字16位 slave.sdo_write(0x1600, 2, 0x607A0020) # 目标位置32位 slave.sdo_write(0x1600, 3, 0x60FF0020) # 目标速度32位 slave.sdo_write(0x1600, 4, 0x60600008) # 操作模式8位 slave.sdo_write(0x1600, 0, 4) # TxPDO状态字、实际位置、实际速度、模式显示 slave.sdo_write(0x1A00, 1, 0x60410010) # 状态字16位 slave.sdo_write(0x1A00, 2, 0x60640020) # 实际位置32位 slave.sdo_write(0x1A00, 3, 0x606C0020) # 实际速度32位 slave.sdo_write(0x1A00, 4, 0x60610008) # 模式显示8位 slave.sdo_write(0x1A00, 0, 4) master.config_func config_func master.config_map()映射条目格式是“对象索引低16位 子索引第16到23位 位长度第24到31位”。例如0x60400010表示索引 0x6040、子索引 0x00、数据长度 16 位。不同驱动器可能把 RxPDO 放在 0x1700 或 0x1600 之后的其他索引需要以厂商对象字典为准。映射写完之后master.config_map()会重新计算 PDO 长度后面周期通信时就能直接在slave.output和slave.input字节数组里读写数据。4. 电机回零从“不知道在哪”到“知道原点”4.1 回零的本质与常见回零方式回零的本质是把机械坐标系和编码器坐标系建立映射关系。增量编码器电机上电后控制器的坐标是随机的如果不知道当前机械位置任何绝对位置控制都没有意义。CiA402 协议定义了很多种回零方式通过对象 0x6098 设置。常见方式方式号回零方式0无回零1负限位开关 索引脉冲2正限位开关 索引脉冲11负限位开关12正限位开关33当前位置设为原点34当前位置设为原点并清除位置35当前位置设为原点不移动具体支持哪种以驱动手册为准。我的习惯是首次调试用“当前位置设为原点”方式先验证通信和状态机再切换到“限位开关 索引脉冲”做真实回零。直接上复杂回零方式一旦限位接线反了电机就会朝错误方向一直冲。回零速度通过 0x6099 子索引 1 和子索引 2 设置前者是寻找限位开关速度后者是脱离限位开关后的接近速度。加速度通过 0x609A 设置。4.2 CiA402 状态机与使能流程要让电机动起来必须先走完 CiA402 状态机。核心控制字是 0x6040核心状态字是 0x6041。状态机和家用灯的开关逻辑有点像你不能直接跳到“运行”状态必须一级一级来。控制字值动作0x06Shutdown关断0x07Switch On通电0x0FEnable Operation允许运行0x1FEnable Operation 启动回零0x0B快速停止0x00禁用电压状态字常见位bit0Ready to Switch Onbit1Switched Onbit2Operation Enabledbit3故障bit5快速停止bit6Switch On Disabledbit10目标到达bit12回零完成bit13回零错误切换到“允许运行”的经典顺序是写 0x06、写 0x07、写 0x0F。每次写入后轮询状态字确认对应位已经置位再走下一步。不要一股脑连写否则有些驱动反应不过来。4.3 回零代码实现我用的从站对象是master.slaves[0]假设 PDO 映射里前两个 16 位数据分别是控制字和状态字后面依次是目标位置、实际位置。定义一个周期通信函数def sync(timeout1000): master.send_processdata() master.receive_processdata(timeout)把控制字写入第一个 PDO 数据的位置用struct.pack_intodef set_control_word(value): out bytearray(slave.output) struct.pack_into(H, out, 0, value) slave.output bytes(out) def read_status_word(): data bytes(slave.input) return struct.unpack_from(H, data, 0)[0]使能驱动slave.sdo_write(0x6060, 0, 6) # 切到回零模式 set_control_word(0x06) for _ in range(100): sync() if read_status_word() 0x01: break set_control_word(0x07) for _ in range(100): sync() if read_status_word() 0x03: break set_control_word(0x0F) for _ in range(100): sync() if read_status_word() 0x07: break启动回零slave.sdo_write(0x6098, 0, 35) # 当前位置设为原点先验证逻辑 slave.sdo_write(0x6099, 1, 50000) # 搜索速度单位需查手册 slave.sdo_write(0x6099, 2, 10000) # 接近速度 slave.sdo_write(0x609A, 0, 500) # 回零加速度 set_control_word(0x1F) # 启动回零 while True: sync() status read_status_word() if status (1 12): print(回零完成) break if status (1 13): print(回零失败) break if timeout_condition: break set_control_word(0x0F) # 停止回零启动位这里有个重要细节回零完成标志是状态字的 bit12不是 bit10。很多驱动在回零过程中也会短暂置位 target reached容易误判。另外回零完成后控制字要把 bit4 清掉保持 0x0F否则再次触发回零时驱动可能不响应。5. 位置控制让电机走到目标位置5.1 三种常见位置控制模式怎么选CiA402 定义了多种操作模式位置控制常用的有 PPProfile Position模式 1和 CSPCyclic Synchronous Position模式 8。PP 模式把轨迹规划交给驱动内部完成主站只要设置目标位置、速度、加速度驱动自动走完 S 曲线或梯形曲线简单省心。CSP 模式则要求主站每个周期下发目标位置适合做多轴插补、电子凸轮、轨迹规划这类需要主站统一调度速度曲线的场景。对比项PP 模式CSP 模式轨迹规划驱动器内部完成主站完成多轴同步弱强周期要求低高代码复杂度低中我下面的示例用 CSP 模式因为这样可以看到周期通信的实际效果也方便你后面扩展成多轴同步控制。5.2 CSP 模式配置与代码实现配置操作模式然后写入目标位置slave.sdo_write(0x6060, 0, 8) # CSP 模式 # 确认状态机已经处于允许运行状态 set_control_word(0x06) sync() set_control_word(0x07) sync() set_control_word(0x0F) sync()给目标位置赋值。假设 PDO 映射从偏移 2 开始是目标位置实际位置从偏移 6 开始def set_target_position(pos): out bytearray(slave.output) struct.pack_into(i, out, 2, int(pos)) slave.output bytes(out) def get_actual_position(): data bytes(slave.input) return struct.unpack_from(i, data, 2)[0]启动一个周期循环target 100000 # 单位由驱动和电子齿轮决定 for i in range(500): set_target_position(target) sync() actual get_actual_position() if i % 50 0: print(f目标{target} 实际{actual})这里有个新手最容易踩的坑CSP 模式下控制字要保持 0x0F但如果你下发目标位置之前没有确保从站已经处于 OP 状态数据根本不会通过 PDO 送进驱动。所以正式跑之前一定要检查master.state pysoem.OP_STATE和状态字 bit2。5.3 梯形速度规划与限位保护如果直接从当前位置给一个很大的目标位置即使 CSP 模式也会出现电机猛冲的情况因为主站下发的是“目标位置序列”而不是一次性的阶跃位置。CSP 模式下主站应该自己规划位置曲线。梯形速度规划的实现思路是计算出剩余距离、加速度段、匀速段、减速段的时长然后每个周期累加速度得到目标位置。def trapezoid_target(cur_pos, target, vmax, acc, dt, state): dist target - cur_pos sign 1 if dist 0 else -1 dist abs(dist) # 简化以当前速度为起点做匀加速/减速 v state[v] v_next min(v acc * dt, vmax) # 如果剩余距离不足以减速直接按减速规划 stop_dist v_next * v_next / (2 * acc) if stop_dist dist: v_next max(0.0, v_next - acc * dt) state[v] v_next new_pos cur_pos sign * v_next * dt return new_pos这样可以避免位置阶跃。更严格的规划需要先算三段时间但上面这个“加减速守则”至少能保证曲线平滑。实际项目中我把这个函数放进周期循环每个周期计算新的目标位置再通过set_target_position()下发。限位保护是回零和位置控制里最不能省的一环。硬限位必须接在驱动器急停输入上这是最终防线。软件限位可以通过对象 0x607D 的子索引 1 和 2 设置正负软限位但前提是驱动支持并且已经完成回零。我自己调试时的顺序是先手动低速转动确认方向再设置很小的软限位范围最后才放开到正常工作范围。6. 常见问题与排查技巧实录6.1 打开网卡或扫站失败这是出现频率最高的问题。现象是master.open(ifname)报错或者config_init()返回 0。优先排查三件事权限、网卡名、从站是否上电。权限报错通常是PermissionError用sudo或setcap解决。网卡名可以通过ip link查看别想当然用eth0有次我拿到一台机器网卡叫eno1程序里写死eth0就直接失败。从站没上电的表现最迷惑明明网线连着但扫不到任何从站。这时可以先看驱动的电源指示灯再检查网线两端是否都是 EtherCAT 从站不要用普通路由器或办公交换机串联。6.2 从站进入不了 SAFE-OP 或 OP 状态如果master.state pysoem.SAFE_OP_STATE执行后再读回的状态不是预期的 SAFE_OP问题多半出在配置阶段。常见的几个原因PDO 映射写错长度不对或对象索引不对驱动在检查映射时直接拒绝状态切换。DC 分布式时钟配置失败有的从站对 DC 参数很挑剔。邮箱通信超时SDO 还没来得及完成主站就急着切状态。驱动器急停信号打开、使能信号未接导致驱动内部状态机不允许进入 OP。排查时可以打印从站状态和 AL 状态码for i, slave in enumerate(master.slaves): print(f从站{i}: state{slave.state:#x} al_status{slave.al_status:#x})AL 状态码直接对应厂商手册里的错误表查到原因再针对性处理。我从不用“盲试法”每次都是先看 AL 状态码再动手。6.3 PDO 数据不对、电机乱动电机能转但位置不对绝大部分是字节序和偏移问题。EtherCAT 是小端序但一些厂商的调试软件会显示成大端两边对照很容易看反。我的经验是先读回状态字和驱动器调试面板比对看 16 位值是不是对得上。状态字对了再检查 32 位位置值。如果位置值出现很大的随机数多半是 PDO 偏移算错了。PDO 偏移和映射顺序密切相关。我代码里把控制字放偏移 0状态字放偏移 0目标位置放偏移 2实际位置放偏移 2靠的是映射顺序一致。如果你的驱动器默认映射里除了这些还带了别的对象偏移量就会变。不要背偏移值直接在代码里用注释标明每个偏移对应哪个对象调试时能省大量时间。6.4 周期抖动与实时性不足现象是电机低速时一顿一顿高速时驱动报警或者 EtherCAT 从站偶尔出现同步错误。这种问题在 Python 方案里很常见根源一般是主站周期不稳定时快时慢导致从站看门狗超时。我的优化顺序如下通信循环里避免print()尤其不要每个周期都打印IO 会严重拖慢循环。把周期循环做成固定频率用time.sleep或者忙等待忙到下一个周期起点。给 Python 进程设置实时调度优先级sudo chrt -f 80 python main.py用taskset -c 3 python main.py把进程绑定到独立 CPU 核心。如果还不行下载 linux-lowlatency 内核重启后重新测试。即便做了这些优化Python 方案的周期抖动仍然比 C 大通常只能稳定在 1ms 到 2ms。如果项目要求 500us 周期或者多轴同步精度要求高我建议用 C 版本 SOEM 或直接上商业主站。最后说几句心里话。回零和定位这件事永远是“设备和现场先行”代码只是最后一步。轴装歪了、限位接反了、编码器线松了程序再怎么调都没用。我见过太多人在代码里绕三天最后发现是限位开关常开常闭接错一按急停反而给驱动发了使能信号。所以拿到一套新设备先手动转轴确认方向、限位、原点位置再用最低速度跑一次回零最后才打开自动控制。把安全放在代码之上这比任何奇技淫巧都重要。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。