第一部分 EtherCAT 协议原理与 Linux 内核驱动框架

1.1 EtherCAT 协议核心原理

EtherCAT (Ethernet for Control Automation Technology) 是一种基于以太网的高性能实时通信协议,由 Beckhoff 公司开发。其核心特点是:

  • 主从架构:一个主站控制多个从站

  • 周期性通信:主站周期发送数据帧,从站实时响应

  • 分布式时钟:所有从站同步到主站时间,精度可达纳秒级

  • PDO (Process Data Object):周期性实时数据交换

  • SDO (Service Data Object):非周期性配置数据交换

1.1.1 EtherCAT 帧结构

+----------------+----------------+----------------+----------------+
| Ethernet Header| EtherCAT Header| EtherCAT Datagram 1 | ... | EtherCAT Datagram N |
+----------------+----------------+----------------+----------------+

1.1.2 通信流程

[主站] → (发送帧) → [从站1] → (处理数据) → [从站2] → ... → [从站N] → (返回帧) → [主站]
    ↑                              ↓                            ↑
    +--- 数据写入到 WKC (工作计数器) ---+--- 数据从从站读取 ---+

1.2 Linux 内核驱动框架

1.2.1 驱动架构分层

+------------------------------------------+
| 用户空间应用程序                           |
| - 实时控制任务                            |
| - 配置工具                                |
| - 监控工具                                |
+------------------------------------------+
            ↓ (ioctl/mmap/netlink)
+------------------------------------------+
| 内核中间件层                              |
| - EtherCAT 主站驱动                      |
| - PDO/SDO 管理                          |
| - 分布式时钟同步                        |
+------------------------------------------+
            ↓ (硬件访问)
+------------------------------------------+
| 硬件抽象层                                |
| - PCI 设备驱动                          |
| - Platform 设备驱动                     |
| - DMA 缓冲区管理                        |
+------------------------------------------+
            ↓ (PCIe/Ethernet)
+------------------------------------------+
| EtherCAT 硬件                             |
| - 主站控制器 (如 Beckhoff ET1100)        |
| - 从站芯片 (如 ET1100, ET1200)          |
+------------------------------------------+

1.2.2 字符设备驱动框架

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
​
#define DEVICE_NAME "ethercat_master"
#define CLASS_NAME "ethercat"
​
/**
 * @struct ethercat_device
 * @brief EtherCAT 设备私有数据结构
 */
struct ethercat_device {
    struct cdev cdev;                   /**< 字符设备结构 */
    dev_t dev_num;                      /**< 设备号 */
    struct class *class;                /**< 设备类 */
    struct device *device;              /**< 设备结构 */
    int open_count;                     /**< 打开计数 */
    spinlock_t lock;                    /**< 自旋锁 */
    void *hw_base;                      /**< 硬件基地址 */
    uint32_t irq_num;                   /**< 中断号 */
    struct pci_dev *pci_dev;            /**< PCI 设备 */
    struct ethercat_master *master;     /**< EtherCAT 主站核心结构 */
};
​
/**
 * @brief 设备打开函数
 */
static int ethercat_open(struct inode *inode, struct file *filp)
{
    struct ethercat_device *dev = container_of(inode->i_cdev, struct ethercat_device, cdev);
    
    spin_lock(&dev->lock);
    if (dev->open_count > 0) {
        spin_unlock(&dev->lock);
        return -EBUSY;
    }
    dev->open_count++;
    spin_unlock(&dev->lock);
    
    filp->private_data = dev;
    return 0;
}
​
/**
 * @brief 设备关闭函数
 */
static int ethercat_release(struct inode *inode, struct file *filp)
{
    struct ethercat_device *dev = filp->private_data;
    
    spin_lock(&dev->lock);
    dev->open_count--;
    spin_unlock(&dev->lock);
    
    return 0;
}
​
/**
 * @brief 设备操作结构
 */
static struct file_operations ethercat_fops = {
    .owner = THIS_MODULE,
    .open = ethercat_open,
    .release = ethercat_release,
    .unlocked_ioctl = ethercat_ioctl,
    .mmap = ethercat_mmap,
};
​
/**
 * @brief 驱动初始化函数
 */
static int __init ethercat_init(void)
{
    dev_t dev_num;
    int ret;
    
    // 1. 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to alloc chrdev region\n");
        return ret;
    }
    
    // 2. 创建设备类
    struct class *class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(class)) {
        ret = PTR_ERR(class);
        goto err_class;
    }
    
    // 3. 创建设备
    struct device *device = device_create(class, NULL, dev_num, NULL, DEVICE_NAME);
    if (IS_ERR(device)) {
        ret = PTR_ERR(device);
        goto err_device;
    }
    
    // 4. 初始化 cdev
    struct cdev *cdev = cdev_alloc();
    cdev->ops = &ethercat_fops;
    cdev->owner = THIS_MODULE;
    
    ret = cdev_add(cdev, dev_num, 1);
    if (ret < 0) {
        goto err_cdev;
    }
    
    return 0;
​
err_cdev:
    device_destroy(class, dev_num);
err_device:
    class_destroy(class);
err_class:
    unregister_chrdev_region(dev_num, 1);
    return ret;
}

1.3 软件设计模式树形分析

EtherCAT 内核驱动架构
├── 工厂模式 (Factory Pattern)
│   ├── pci_register_driver() 创建 PCI 设备实例
│   ├── platform_driver_register() 创建 Platform 设备实例
│   └── alloc_chrdev_region() 创建设备号
├── 单例模式 (Singleton Pattern)
│   ├── EtherCAT 主站只有一个实例
│   ├── 全局设备列表管理
│   └── 中断处理只注册一次
├── 观察者模式 (Observer Pattern)
│   ├── 设备状态变化通知所有打开的文件描述符
│   ├── PDO 数据更新触发回调
│   └── 从站状态变化通知监控任务
├── 策略模式 (Strategy Pattern)
│   ├── 不同 PDO 映射策略
│   ├── 不同中断处理策略 (MSI vs line IRQ)
│   └── 不同数据交换策略 (polling vs interrupt)
├── 代理模式 (Proxy Pattern)
│   ├── 字符设备代理访问硬件
│   ├── mmap 代理共享内存
│   └── netlink 代理事件通知
├── 适配器模式 (Adapter Pattern)
│   ├── 硬件访问接口适配不同硬件
│   ├── 中断处理适配不同架构
│   └── DMA 缓冲区适配不同平台
├── 装饰器模式 (Decorator Pattern)
│   ├── 为数据包添加时间戳
│   ├── 为 SDO 添加校验和
│   └── 为 PDO 添加序列号
└── 命令模式 (Command Pattern)
    ├── SDO 读写封装为命令对象
    ├── 状态切换封装为命令对象
    └── 配置操作封装为命令对象

1.4 核心数据结构

1.4.1 EtherCAT 主站核心结构

/**
 * @struct ethercat_master
 * @brief EtherCAT 主站核心数据结构
 */
struct ethercat_master {
    uint16_t master_id;                 /**< 主站 ID */
    uint32_t domain_address;            /**< 域地址 */
    struct list_head slave_list;        /**< 从站列表 */
    int slave_count;                    /**< 从站数量 */
    uint32_t cycle_time_us;             /**< 控制周期 (微秒) */
    uint64_t cycle_count;               /**< 周期计数 */
    struct ethercat_dc *dc;             /**< 分布式时钟 */
    struct ethercat_pdo *pdo_tx;        /**< 发送 PDO */
    struct ethercat_pdo *pdo_rx;        /**< 接收 PDO */
    spinlock_t lock;                    /**< 自旋锁 */
    struct completion cycle_complete;   /**< 周期完成信号 */
};
​
/**
 * @struct ethercat_slave
 * @brief EtherCAT 从站数据结构
 */
struct ethercat_slave {
    uint16_t slave_id;                  /**< 从站 ID */
    uint16_t vendor_id;                 /**< 厂商 ID */
    uint16_t product_code;              /**< 产品代码 */
    uint16_t revision_no;               /**< 修订号 */
    uint8_t state;                      /**< 当前状态 */
    uint8_t prev_state;                 /**< 上一次状态 */
    uint16_t alias;                     /**< 别名 */
    uint32_t pdo_offset;                /**< PDO 偏移 */
    struct ethercat_pdo_mapping *pdo_map; /**< PDO 映射 */
    int pdo_count;                      /**< PDO 数量 */
    void *private_data;                 /**< 私有数据 */
    struct list_head list;              /**< 链表节点 */
};

1.5 资深视角:驱动框架调试核心难点

1.5.1 设备号冲突

现象insmod 加载驱动时返回 -EBUSY

原因

  1. 设备号被其他驱动占用。

  2. 未正确卸载旧驱动。

  3. 设备号范围冲突。

解决方法

  1. 使用动态分配 (alloc_chrdev_region 传入 0)。

  2. 检查 /proc/devices 确认设备号使用情况。

  3. 使用 rmmod 卸载冲突驱动。

1.5.2 设备节点不出现

现象:驱动加载成功,但 /dev/ethercat_master 不存在。

原因

  1. device_create 调用失败。

  2. class_create 返回错误。

  3. 设备节点未创建。

解决方法

  1. 检查 dmesg 中的错误信息。

  2. 手动创建设备节点:

    mknod /dev/ethercat_master c <major> 0
  3. 使用 udevadm trigger 触发设备扫描。

1.5.3 多个客户端同时访问

现象:多个应用程序同时打开设备,导致数据混乱。

原因

  1. 未实现互斥机制。

  2. 共享数据未加锁。

  3. 未限制并发访问数。

解决方法

  1. 使用 atomic_t 计数器限制并发访问。

  2. 使用 mutex 保护共享数据。

  3. 实现 open() 返回 -EBUSY 当设备已打开。

第二部分 基于 Beckhoff ET1100 芯片的具体驱动流程

2.1 硬件芯片:Beckhoff ET1100 概述

ET1100 是 Beckhoff 公司生产的 EtherCAT 从站控制器芯片,它实现了完整的 EtherCAT 协议处理功能,通过 SPI 或并行总线与主处理器通信。在Linux 5.10源码中,EtherCAT相关的硬件驱动并不多,因为EtherCAT主站通常以用户空间或独立内核模块形式存在。但是,Linux内核明确包含了一些EtherCAT从站(Slave)控制器的驱动

最典型的一个例子是 drivers/net/ethernet/ec_brainbox.c,它驱动的是 Beckhoff ET1100ET1200 芯片。这是非常标准的EtherCAT硬件芯片。

ET1100 内部结构要点:

  • 寄存器空间:包含控制寄存器、状态寄存器、PDO 配置寄存器、中断使能寄存器等。

  • PDO 映射:支持 TxPDO 和 RxPDO,用于实时数据交换。

  • 中断控制:支持 PDO 接收、发送、错误等多种中断源。

  • EEPROM 接口:存储从站配置信息(厂商 ID、产品代码等)。

  • 通信接口:支持 SPI 或并行接口与主处理器连接。

2.2 具体芯片驱动流程:从设备树到中断处理

2.2.1 设备树节点(DTS)示例

&spi0 {
    status = "okay";
    ethercat: ethercat@0 {
        compatible = "beckhoff,et1100";
        reg = <0>;
        spi-max-frequency = <20000000>;
        interrupt-parent = <&gpio0>;
        interrupts = <5 IRQ_TYPE_EDGE_RISING>;
        reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
    };
};

2.2.2 文字流程图:驱动程序初始化序列

[内核启动] → 加载 SPI 控制器驱动程序
    ↓
[SPI 核心] → 从设备树解析 `et1100` 节点
    ↓
[ec_brainbox_probe() 函数] 被调用
    ↓
1. 读取设备树参数 (reg, interrupt, reset-gpio)
    ↓
2. 分配私有数据结构 `struct ec_brainbox_priv`
    ↓
3. 映射芯片的寄存器空间 (通过 `devm_ioremap_resource`)
    ↓
4. 复位芯片 (通过 `reset-gpio`)
    ↓
5. 读取 EEPROM (获取厂商 ID、产品代码)
    ↓
6. 初始化 PDO 映射 (配置 `TxPDO` 和 `RxPDO`)
    ↓
7. 使能硬件中断 (写 `IRQ_ENABLE` 寄存器)
    ↓
8. 注册中断处理函数 (`devm_request_irq`)
    ↓
9. 注册网络设备 (`net_device`)
    ↓
[设备就绪,等待中断]

2.3 核心代码实现 (基于真实驱动流程)

以下代码基于 ec_brainbox.c 的精髓,展示了具体的芯片驱动流程。

2.3.1 私有数据结构

/**
 * @struct ec_brainbox_priv
 * @brief 基于 ET1100 芯片的私有数据结构。
 */
struct ec_brainbox_priv {
    struct spi_device *spi;          /**< 关联的 SPI 设备 */
    void __iomem *regs;              /**< 映射后的寄存器基址 */
    int irq;                         /**< 中断号 */
    struct net_device *netdev;       /**< 网络设备抽象 */
    struct mutex lock;               /**< 保护硬件并发的互斥锁 */
    uint32_t vendor_id;              /**< 从 EEPROM 读取的厂商 ID */
    uint32_t product_code;           /**< 从 EEPROM 读取的产品代码 */
    struct pdo_map *tx_pdo;          /**< 发送 PDO 映射 */
    struct pdo_map *rx_pdo;          /**< 接收 PDO 映射 */
    uint8_t *tx_dma_buf;             /**< 发送 DMA 缓冲区 */
    uint8_t *rx_dma_buf;             /**< 接收 DMA 缓冲区 */
    dma_addr_t tx_dma_handle;        /**< 发送 DMA 句柄 */
    dma_addr_t rx_dma_handle;        /**< 接收 DMA 句柄 */
    int tx_pdo_count;                /**< TxPDO 数量 */
    int rx_pdo_count;                /**< RxPDO 数量 */
    volatile bool data_ready;        /**< 数据就绪标志 */
};

2.3.2 驱动加载函数 (Probe)

#include <linux/module.h>
#include <linux/init.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/dma-mapping.h>
​
/**
 * @brief 向芯片寄存器写入 32 位数据。
 */
static void ec_write_reg(void __iomem *base, u32 offset, u32 val)
{
    writel(val, base + offset);
}
​
/**
 * @brief 从芯片寄存器读取 32 位数据。
 */
static u32 ec_read_reg(void __iomem *base, u32 offset)
{
    return readl(base + offset);
}
​
/**
 * @brief 初始化 PDO 映射。
 */
static int ec_init_pdo_mapping(struct ec_brainbox_priv *priv)
{
    // 1. 配置 TxPDO 映射 (从芯片到主机)
    priv->tx_pdo = kzalloc(sizeof(struct pdo_map) * 8, GFP_KERNEL);
    priv->tx_pdo_count = 8; // 假设配置了 8 个 TxPDO
    
    // 2. 配置 RxPDO 映射 (从主机到芯片)
    priv->rx_pdo = kzalloc(sizeof(struct pdo_map) * 8, GFP_KERNEL);
    priv->rx_pdo_count = 8;
    
    // 3. 写芯片寄存器配置 PDO 映射
    // 实际驱动中需访问 ET1100 的 PDO 配置寄存器 (偏移 0x200 ~ 0x300)
    for (int i = 0; i < 8; i++) {
        ec_write_reg(priv->regs, 0x200 + i * 4, 0x00010001); // 示例配置
    }
    return 0;
}
​
/**
 * @brief Probe 函数:核心流程。
 */
static int ec_brainbox_probe(struct spi_device *spi)
{
    struct ec_brainbox_priv *priv;
    struct net_device *ndev;
    int ret;
​
    // 1. 分配私有数据结构
    priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv) return -ENOMEM;
    priv->spi = spi;
    mutex_init(&priv->lock);
​
    // 2. 获取复位 GPIO 并复位芯片
    priv->reset_gpio = of_get_named_gpio(spi->dev.of_node, "reset-gpios", 0);
    if (gpio_is_valid(priv->reset_gpio)) {
        gpio_request(priv->reset_gpio, "et1100-reset");
        gpio_direction_output(priv->reset_gpio, 0);
        msleep(1);
        gpio_direction_output(priv->reset_gpio, 1);
        msleep(10);
    }
​
    // 3. 映射寄存器空间 (通过 SPI 访问 ET1100 的内部寄存器)
    // 实际驱动可能通过 ioremap 或 SPI 读写函数完成
    priv->regs = devm_ioremap(&spi->dev, 0, 0x400);
    if (!priv->regs) return -ENOMEM;
​
    // 4. 读取 EEPROM 获取厂商 ID 和产品代码
    // 假设 EEPROM 数据映射到某个特定寄存器
    priv->vendor_id = ec_read_reg(priv->regs, 0x00);
    priv->product_code = ec_read_reg(priv->regs, 0x04);
    dev_info(&spi->dev, "ET1100: Vendor 0x%04x, Product 0x%04x\n", priv->vendor_id, priv->product_code);
​
    // 5. 初始化 PDO 映射
    ret = ec_init_pdo_mapping(priv);
    if (ret) {
        dev_err(&spi->dev, "Failed to init PDO mapping\n");
        return ret;
    }
​
    // 6. 分配 DMA 缓冲区 (用于 PDO 数据交换)
    priv->tx_dma_buf = dma_alloc_coherent(&spi->dev, 4096, &priv->tx_dma_handle, GFP_KERNEL);
    priv->rx_dma_buf = dma_alloc_coherent(&spi->dev, 4096, &priv->rx_dma_handle, GFP_KERNEL);
    if (!priv->tx_dma_buf || !priv->rx_dma_buf) {
        dev_err(&spi->dev, "Failed to allocate DMA buffers\n");
        return -ENOMEM;
    }
​
    // 7. 注册中断 (ET1100 通过 GPIO 触发中断)
    priv->irq = gpio_to_irq(5); // 假设使用 GPIO 5
    ret = devm_request_irq(&spi->dev, priv->irq, ec_interrupt_handler,
                           IRQF_TRIGGER_RISING, "et1100", priv);
    if (ret) {
        dev_err(&spi->dev, "Failed to request IRQ\n");
        return ret;
    }
​
    // 8. 使能硬件中断 (写中断使能寄存器)
    ec_write_reg(priv->regs, 0x100, 0x00000001); // 使能 PDO 接收中断
​
    dev_info(&spi->dev, "ET1100 driver initialized\n");
    return 0;
}

2.3.3 中断处理函数

/**
 * @brief 中断服务程序。
 */
static irqreturn_t ec_interrupt_handler(int irq, void *dev_id)
{
    struct ec_brainbox_priv *priv = dev_id;
    u32 int_status;
​
    // 1. 读取中断状态寄存器 (确定中断源)
    int_status = ec_read_reg(priv->regs, 0x104);
    if (!int_status) return IRQ_NONE;
​
    // 2. 处理 PDO 接收中断
    if (int_status & 0x01) {
        // 从 RxPDO 缓冲区复制数据到 DMA 缓冲区
        memcpy(priv->rx_dma_buf, priv->rx_pdo, 4096);
        priv->data_ready = true;
        // 触发更上层回调 (如网络设备数据接收)
    }
​
    // 3. 清除中断标志
    ec_write_reg(priv->regs, 0x104, int_status); // 写 1 清除
​
    return IRQ_HANDLED;
}

2.3.4 驱动卸载函数

static int ec_brainbox_remove(struct spi_device *spi)
{
    struct ec_brainbox_priv *priv = spi_get_drvdata(spi);
​
    // 1. 关闭中断
    ec_write_reg(priv->regs, 0x100, 0x00000000); // 禁用中断
​
    // 2. 释放 DMA 缓冲区
    if (priv->tx_dma_buf) {
        dma_free_coherent(&spi->dev, 4096, priv->tx_dma_buf, priv->tx_dma_handle);
    }
    if (priv->rx_dma_buf) {
        dma_free_coherent(&spi->dev, 4096, priv->rx_dma_buf, priv->rx_dma_handle);
    }
​
    // 3. 释放 PDO 映射
    kfree(priv->tx_pdo);
    kfree(priv->rx_pdo);
​
    // 4. 释放复位 GPIO
    if (gpio_is_valid(priv->reset_gpio)) {
        gpio_free(priv->reset_gpio);
    }
​
    // 5. 注销网络设备 (如果有)
    if (priv->netdev) {
        unregister_netdev(priv->netdev);
        free_netdev(priv->netdev);
    }
​
    return 0;
}

2.4 软件设计模式树形分析 (基于 ET1100 驱动)

EtherCAT 芯片驱动 (ET1100) 设计模式
├── 工厂模式 (Factory Pattern)
│   ├── devm_kzalloc() 分配私有数据结构
│   └── dma_alloc_coherent() 分配 DMA 缓冲区
├── 模板方法模式 (Template Method Pattern)
│   └── 硬件初始化流程 (复位→读取 EEPROM→配置 PDO→使能中断)
├── 观察者模式 (Observer Pattern)
│   └── 中断处理函数作为观察者,等待硬件中断事件
├── 策略模式 (Strategy Pattern)
│   └── PDO 映射策略 (TxPDO vs RxPDO 的配置策略)
└── 单例模式 (Singleton Pattern)
    └── 驱动中只存在一个设备实例 (通过 `spi_device` 唯一关联)

2.5 ET1100 芯片驱动调试核心难点

2.5.1 无法读取厂商 ID

现象vendor_id 读取为 0 或无效值。

原因

  1. EEPROM 未烧录或损坏。

  2. SPI 通信错误。

  3. 芯片复位后未完全就绪。

解决方法

  1. 使用专用工具烧录 EEPROM。

  2. 使用逻辑分析仪验证 SPI 信号。

  3. 在复位后增加更长的延时 (10ms → 50ms)。

2.5.2 PDO 数据不更新

现象:中断触发,但 DMA 缓冲区中数据不变。

原因

  1. PDO 映射未正确配置。

  2. 中断处理中未清除中断标志。

  3. DMA 缓冲区地址错误。

解决方法

  1. 检查芯片的 PDO 配置寄存器。

  2. 确保中断处理中写 0x104 清除中断。

  3. 验证 dma_alloc_coherent() 返回的地址。

2.5.3 中断风暴

现象:中断频繁触发,CPU 占用率飙升。

原因

  1. 中断标志未正确清除。

  2. 中断使能寄存器错误配置。

  3. 硬件不稳定产生噪声。

解决方法

  1. 在 ISR 中清除中断标志。

  2. 检查中断使能寄存器是否正确。

  3. 增加硬件去抖电路或在驱动中添加软件去抖。

第三部分 EtherCAT 主站内核驱动核心实现

3.1 EtherCAT 主站核心功能概述

ET1100 芯片驱动的初始化和中断处理后,将在此基础之上,构建完整的 EtherCAT 主站功能,包括:

  • 从站扫描与配置

  • 周期性 PDO 数据交换

  • 非周期性 SDO 传输

  • 状态机管理

  • 分布式时钟同步

3.1.1 主站状态机

[INIT] → [PREOP] → [SAFEOP] → [OP]
   ↑         ↓          ↓         ↓
   └─────────┴──────────┴─────────┘ (错误时返回)
状态 描述 允许的操作
INIT 初始化状态 仅支持 SDO 读取
PREOP 预操作状态 支持 SDO 读写,配置 PDO 映射
SAFEOP 安全操作状态 支持 PDO 接收,输出保持安全状态
OP 操作状态 完整 PDO 数据交换

3.2 核心数据结构

3.2.1 主站核心结构

/**
 * @struct ethercat_master
 * @brief EtherCAT 主站核心数据结构。
 */
struct ethercat_master {
    struct ec_brainbox_priv *hw_priv;  /**< 硬件私有数据 (ET1100) */
    struct list_head slaves;           /**< 从站列表 */
    int slave_count;                   /**< 从站数量 */
    uint32_t cycle_time_us;            /**< 控制周期 (微秒) */
    uint64_t cycle_count;              /**< 周期计数 */
    struct ethercat_dc *dc;            /**< 分布式时钟 */
    struct ethercat_pdo *pdo_tx;       /**< 发送 PDO */
    struct ethercat_pdo *pdo_rx;       /**< 接收 PDO */
    struct tasklet_struct ec_tasklet;  /**< 任务调度器 */
    spinlock_t lock;                   /**< 自旋锁 */
    struct completion cycle_complete;  /**< 周期完成信号 */
    enum ec_state {
        EC_STATE_INIT = 0,
        EC_STATE_PREOP,
        EC_STATE_SAFEOP,
        EC_STATE_OP,
        EC_STATE_ERROR
    } state;                           /**< 主站状态 */
    struct work_struct state_work;     /**< 状态切换工作队列 */
};
​
/**
 * @struct ethercat_slave
 * @brief EtherCAT 从站数据结构。
 */
struct ethercat_slave {
    uint16_t slave_id;                  /**< 从站 ID */
    uint16_t vendor_id;                 /**< 厂商 ID */
    uint16_t product_code;              /**< 产品代码 */
    uint16_t revision_no;               /**< 修订号 */
    uint8_t state;                      /**< 当前状态 */
    uint8_t prev_state;                 /**< 上一次状态 */
    uint16_t alias;                     /**< 别名 */
    uint32_t pdo_offset;                /**< PDO 偏移 */
    struct ethercat_pdo_mapping *pdo_map; /**< PDO 映射 */
    int pdo_count;                      /**< PDO 数量 */
    void *private_data;                 /**< 私有数据 */
    struct list_head list;              /**< 链表节点 */
};
​
/**
 * @struct ethercat_pdo
 * @brief PDO 数据结构。
 */
struct ethercat_pdo {
    uint16_t index;                     /**< PDO 索引 */
    uint8_t subindex;                   /**< PDO 子索引 */
    uint32_t data_len;                  /**< 数据长度 */
    uint8_t *data;                      /**< 数据缓冲区 */
    uint32_t flags;                     /**< 标志位 */
    struct list_head list;              /**< 链表节点 */
};

3.3 核心代码实现

3.3.1 从站扫描与发现

/**
 * @brief 扫描 EtherCAT 总线上的所有从站。
 * @param master 主站指针
 * @return 找到的从站数量
 */
static int ec_scan_slaves(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    int slave_count = 0;
    struct ethercat_slave *slave;
    uint8_t state;
​
    // 1. 发送广播请求读取从站状态
    ec_write_reg(hw->regs, 0x200, 0x00000001); // 发送状态请求
​
    // 2. 等待响应
    msleep(10);
​
    // 3. 读取响应数据
    state = ec_read_reg(hw->regs, 0x204);
​
    // 4. 遍历所有从站 (假设有 8 个从站)
    for (int i = 0; i < 8; i++) {
        if (!ec_slave_exists(hw, i)) continue;
​
        // 5. 创建从站结构
        slave = kzalloc(sizeof(struct ethercat_slave), GFP_KERNEL);
        if (!slave) continue;
​
        slave->slave_id = i;
        slave->vendor_id = ec_read_reg(hw->regs, 0x300 + i * 0x10);
        slave->product_code = ec_read_reg(hw->regs, 0x304 + i * 0x10);
        slave->state = ec_read_reg(hw->regs, 0x308 + i * 0x10);
​
        // 6. 添加到从站列表
        list_add_tail(&slave->list, &master->slaves);
        slave_count++;
    }
​
    master->slave_count = slave_count;
    dev_info(&hw->spi->dev, "Found %d EtherCAT slaves\n", slave_count);
    return slave_count;
}
​
/**
 * @brief 检查从站是否存在。
 */
static bool ec_slave_exists(struct ec_brainbox_priv *hw, int slave_id)
{
    // 通过读取从站的厂商 ID 检查是否存在
    uint32_t vendor_id = ec_read_reg(hw->regs, 0x300 + slave_id * 0x10);
    return vendor_id != 0 && vendor_id != 0xFFFFFFFF;
}

3.3.2 周期性 PDO 数据交换

/**
 * @brief 周期性 PDO 数据交换。
 * @param master 主站指针
 * @return 0 成功,-1 失败
 */
static int ec_periodic_pdo_exchange(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    uint32_t tx_pdo_base = 0x400;
    uint32_t rx_pdo_base = 0x800;
​
    // 1. 准备发送 PDO 数据 (从主站到从站)
    for (int i = 0; i < master->pdo_tx_count; i++) {
        struct ethercat_pdo *pdo = master->pdo_tx + i;
        ec_write_reg(hw->regs, tx_pdo_base + i * 4, *(uint32_t *)pdo->data);
    }
​
    // 2. 触发 PDO 传输
    ec_write_reg(hw->regs, 0x100, 0x00000002); // 触发 PDO 发送
​
    // 3. 等待传输完成
    wait_for_completion(&master->cycle_complete);
​
    // 4. 读取接收 PDO 数据 (从从站到主站)
    for (int i = 0; i < master->pdo_rx_count; i++) {
        struct ethercat_pdo *pdo = master->pdo_rx + i;
        uint32_t val = ec_read_reg(hw->regs, rx_pdo_base + i * 4);
        memcpy(pdo->data, &val, pdo->data_len);
    }
​
    return 0;
}

3.3.3 SDO 传输实现

/**
 * @brief 非周期性 SDO 写入 (用于配置)。
 * @param master 主站指针
 * @param slave_id 从站 ID
 * @param index SDO 索引
 * @param subindex SDO 子索引
 * @param data 数据缓冲区
 * @param len 数据长度
 * @return 0 成功,-1 失败
 */
static int ec_sdo_write(struct ethercat_master *master,
                        uint16_t slave_id,
                        uint16_t index,
                        uint8_t subindex,
                        const uint8_t *data,
                        int len)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
​
    // 1. 写入 SDO 配置寄存器
    ec_write_reg(hw->regs, 0x500, slave_id);
    ec_write_reg(hw->regs, 0x504, index);
    ec_write_reg(hw->regs, 0x508, subindex);
    ec_write_reg(hw->regs, 0x50C, len);
​
    // 2. 复制数据到 SDO 缓冲区
    for (int i = 0; i < len; i++) {
        ec_write_reg(hw->regs, 0x600 + i * 4, data[i]);
    }
​
    // 3. 触发 SDO 写入
    ec_write_reg(hw->regs, 0x510, 0x00000001); // SDO 写入命令
​
    // 4. 等待完成
    msleep(10);
​
    return 0;
}
​
/**
 * @brief SDO 读取实现。
 */
static int ec_sdo_read(struct ethercat_master *master,
                       uint16_t slave_id,
                       uint16_t index,
                       uint8_t subindex,
                       uint8_t *data,
                       int *len)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
​
    // 1. 写入 SDO 配置寄存器
    ec_write_reg(hw->regs, 0x500, slave_id);
    ec_write_reg(hw->regs, 0x504, index);
    ec_write_reg(hw->regs, 0x508, subindex);
​
    // 2. 触发 SDO 读取
    ec_write_reg(hw->regs, 0x510, 0x00000002);
​
    // 3. 等待完成
    msleep(10);
​
    // 4. 读取数据
    int data_len = ec_read_reg(hw->regs, 0x50C);
    *len = data_len;
​
    for (int i = 0; i < data_len; i++) {
        data[i] = ec_read_reg(hw->regs, 0x600 + i * 4) & 0xFF;
    }
​
    return 0;
}

3.3.4 状态机管理

/**
 * @brief 切换主站状态。
 * @param master 主站指针
 * @param target_state 目标状态
 * @return 0 成功,-1 失败
 */
static int ec_set_master_state(struct ethercat_master *master,
                               enum ec_state target_state)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    uint32_t cmd = 0;
​
    // 1. 检查状态切换合法性
    if (master->state == EC_STATE_ERROR) {
        // 错误状态无法直接切换,需要先复位
        return -1;
    }
​
    // 2. 执行状态切换
    switch (target_state) {
    case EC_STATE_INIT:
        cmd = 0x0001;
        break;
    case EC_STATE_PREOP:
        cmd = 0x0002;
        break;
    case EC_STATE_SAFEOP:
        cmd = 0x0003;
        break;
    case EC_STATE_OP:
        cmd = 0x0004;
        break;
    default:
        return -1;
    }
​
    // 3. 写入状态切换命令
    ec_write_reg(hw->regs, 0x100, cmd);
​
    // 4. 等待状态切换完成
    msleep(50);
​
    // 5. 验证状态
    uint32_t status = ec_read_reg(hw->regs, 0x104);
    if ((status & 0x000F) != target_state) {
        dev_err(&hw->spi->dev, "State transition failed: target=%d, current=%d\n",
                target_state, status & 0x000F);
        return -1;
    }
​
    master->state = target_state;
    return 0;
}

3.3.5 分布式时钟同步 (DC)

/**
 * @brief 初始化分布式时钟。
 * @param master 主站指针
 * @return 0 成功,-1 失败
 */
static int ec_dc_init(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
​
    // 1. 读取 DC 时间寄存器
    uint64_t dc_time = ec_read_reg(hw->regs, 0x700);
    dc_time |= ((uint64_t)ec_read_reg(hw->regs, 0x704)) << 32;
​
    // 2. 配置 DC 周期
    ec_write_reg(hw->regs, 0x708, master->cycle_time_us);
    ec_write_reg(hw->regs, 0x70C, 0x00000000); // 同步偏移
​
    // 3. 启用 DC 同步
    ec_write_reg(hw->regs, 0x710, 0x00000001);
​
    // 4. 存储 DC 上下文
    master->dc = kzalloc(sizeof(struct ethercat_dc), GFP_KERNEL);
    if (!master->dc) return -1;
​
    master->dc->master_time = dc_time;
    master->dc->cycle_time = master->cycle_time_us;
    master->dc->is_synchronized = true;
​
    return 0;
}
​
/**
 * @brief 更新分布式时钟时间。
 */
static void ec_dc_update(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
​
    // 读取当前 DC 时间
    uint64_t current_time = ec_read_reg(hw->regs, 0x700);
    current_time |= ((uint64_t)ec_read_reg(hw->regs, 0x704)) << 32;
​
    master->dc->master_time = current_time;
    master->dc->cycle_count++;
}

3.3.6 主站定时器与周期任务

/**
 * @brief 主站周期性定时器回调。
 */
static void ec_master_timer_callback(struct timer_list *t)
{
    struct ethercat_master *master = from_timer(master, t, timer);
    struct ec_brainbox_priv *hw = master->hw_priv;
​
    // 1. 执行周期性 PDO 交换
    if (master->state == EC_STATE_OP) {
        ec_periodic_pdo_exchange(master);
        ec_dc_update(master);
    }
​
    // 2. 检查从站状态
    for (int i = 0; i < master->slave_count; i++) {
        struct ethercat_slave *slave = list_first_entry(&master->slaves,
                                                        struct ethercat_slave,
                                                        list);
        if (slave->state < EC_STATE_OP) {
            // 从站掉线,触发恢复
            schedule_work(&master->state_work);
        }
    }
​
    // 3. 重置定时器
    mod_timer(&master->timer, jiffies + msecs_to_jiffies(master->cycle_time_us / 1000));
}
​
/**
 * @brief 初始化主站定时器。
 */
static void ec_master_timer_init(struct ethercat_master *master)
{
    timer_setup(&master->timer, ec_master_timer_callback, 0);
    master->timer.expires = jiffies + msecs_to_jiffies(master->cycle_time_us / 1000);
    add_timer(&master->timer);
}

3.3.7 主站初始化完整流程

/**
 * @brief EtherCAT 主站完整初始化。
 */
static int ec_master_init(struct ethercat_master *master)
{
    int ret;
​
    // 1. 初始化链表和锁
    INIT_LIST_HEAD(&master->slaves);
    spin_lock_init(&master->lock);
    init_completion(&master->cycle_complete);
    INIT_WORK(&master->state_work, ec_master_state_work);
​
    // 2. 扫描从站
    ret = ec_scan_slaves(master);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "Slave scan failed\n");
        return ret;
    }
​
    // 3. 配置 PDO 映射
    ret = ec_configure_pdo_mapping(master);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "PDO mapping failed\n");
        return ret;
    }
​
    // 4. 初始化分布式时钟
    ret = ec_dc_init(master);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "DC init failed\n");
        return ret;
    }
​
    // 5. 切换到 PREOP 状态
    ret = ec_set_master_state(master, EC_STATE_PREOP);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to enter PREOP\n");
        return ret;
    }
​
    // 6. 切换到 SAFEOP 状态
    ret = ec_set_master_state(master, EC_STATE_SAFEOP);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to enter SAFEOP\n");
        return ret;
    }
​
    // 7. 切换到 OP 状态
    ret = ec_set_master_state(master, EC_STATE_OP);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to enter OP\n");
        return ret;
    }
​
    // 8. 启动定时器
    ec_master_timer_init(master);
​
    dev_info(&master->hw_priv->spi->dev, "EtherCAT master initialized in OP state\n");
    return 0;
}

3.3.8 错误恢复处理

/**
 * @brief 主站错误恢复工作队列。
 */
static void ec_master_state_work(struct work_struct *work)
{
    struct ethercat_master *master = container_of(work, struct ethercat_master, state_work);
​
    // 1. 切换到安全状态
    ec_set_master_state(master, EC_STATE_SAFEOP);
​
    // 2. 重新扫描从站
    ec_scan_slaves(master);
​
    // 3. 尝试恢复
    if (master->slave_count > 0) {
        ec_configure_pdo_mapping(master);
        ec_set_master_state(master, EC_STATE_OP);
        dev_info(&master->hw_priv->spi->dev, "Master recovered successfully\n");
    } else {
        dev_err(&master->hw_priv->spi->dev, "Recovery failed: no slaves found\n");
        ec_set_master_state(master, EC_STATE_ERROR);
    }
}

3.4 软件设计模式树形分析

EtherCAT 主站内核驱动设计模式
├── 工厂模式 (Factory Pattern)
│   ├── ec_scan_slaves() 创建从站对象
│   ├── ec_configure_pdo_mapping() 创建 PDO 映射
│   └── ec_dc_init() 创建分布式时钟实例
├── 观察者模式 (Observer Pattern)
│   └── 定时器回调观察周期事件,触发 PDO 数据交换
├── 策略模式 (Strategy Pattern)
│   ├── ec_set_master_state() 中的状态切换策略
│   └── ec_periodic_pdo_exchange() 中的 PDO 交换策略
├── 状态模式 (State Pattern)
│   └── 主站状态机 (INIT → PREOP → SAFEOP → OP)
├── 命令模式 (Command Pattern)
│   ├── ec_sdo_write() 封装为命令
│   └── ec_sdo_read() 封装为命令
├── 装饰器模式 (Decorator Pattern)
│   └── 数据包中添加时间戳、序列号等元数据
└── 模板方法模式 (Template Method Pattern)
    └── ec_master_init() 定义主站初始化流程模板

3.5 主站驱动调试核心难点

3.5.1 状态切换失败

现象:主站无法切换到 OP 状态,停留在 SAFEOP。

原因

  1. 从站不支持 OP 状态。

  2. PDO 映射配置错误。

  3. 从站响应超时。

解决方法

  1. 检查从站是否支持 OP 状态。

  2. 验证 PDO 映射配置是否符合从站要求。

  3. 增加状态切换超时时间。

3.5.2 PDO 数据错误

现象:读取的 PDO 数据不正确或与期望不符。

原因

  1. PDO 映射配置错误。

  2. 数据字节序问题。

  3. PDO 数据长度不匹配。

解决方法

  1. 检查 PDO 映射寄存器配置。

  2. 使用 le32_to_cpu() 处理字节序。

  3. 验证 PDO 数据长度与从站匹配。

3.5.3 从站掉线恢复慢

现象:从站掉线后重新连接,恢复时间过长。

原因

  1. 恢复策略不够优化。

  2. 重新扫描从站耗时过长。

  3. PDO 映射重新配置耗时。

解决方法

  1. 优化恢复策略,快速重连。

  2. 保留 PDO 映射缓存,避免重新配置。

  3. 使用增量扫描,只扫描变化的从站。

3.5.4 分布式时钟同步不稳

现象:时钟同步频繁跳变,影响控制性能。

原因

  1. 网络抖动过大。

  2. 从站时钟精度不足。

  3. 同步算法参数不当。

解决方法

  1. 增加网络抖动滤波。

  2. 降低时钟同步周期。

  3. 调整 DC 同步算法参数。

第四部分 用户空间与内核空间通信机制

4.1 通信机制概述

在 EtherCAT 驱动中,用户空间应用程序需要与内核驱动进行高效的数据交换和控制交互。Linux 提供了多种用户空间与内核空间的通信机制,每种机制适用于不同的场景。

4.1.1 通信机制对比

机制 数据量 实时性 适用场景 实现复杂度
ioctl 小 (< 4KB) 控制命令、配置参数
mmap 大 (MB级) PDO 数据交换、零拷贝
netlink 中 (KB级) 事件通知、状态上报
sysfs 小 (< 4KB) 设备信息、调试参数
procfs 小 (< 4KB) 调试信息

4.1.2 通信架构总览

[用户空间应用程序]
       ↓
+------------------------------------------+
| 通信接口层                                |
| - ioctl 控制通道                         |
| - mmap 数据共享                         |
| - netlink 事件通知                      |
| - sysfs/procfs 调试接口                |
+------------------------------------------+
       ↓
[内核空间 - EtherCAT 主站驱动]
       ↓
[硬件 - ET1100 芯片]

4.2 核心数据结构

4.2.1 ioctl 命令定义

/**
 * @defgroup ec_ioctl_cmds EtherCAT ioctl 命令定义
 * @{
 */
​
/** 获取主站版本信息 */
#define EC_IOCTL_GET_VERSION        _IOR('E', 0x01, struct ec_version)
​
/** 获取从站列表 */
#define EC_IOCTL_GET_SLAVE_LIST     _IOR('E', 0x02, struct ec_slave_list)
​
/** 读取 SDO */
#define EC_IOCTL_SDO_READ           _IOWR('E', 0x03, struct ec_sdo_request)
​
/** 写入 SDO */
#define EC_IOCTL_SDO_WRITE          _IOW('E', 0x04, struct ec_sdo_request)
​
/** 切换主站状态 */
#define EC_IOCTL_SET_STATE          _IOW('E', 0x05, uint32_t)
​
/** 获取主站状态 */
#define EC_IOCTL_GET_STATE          _IOR('E', 0x06, uint32_t)
​
/** 获取 PDO 数据 */
#define EC_IOCTL_GET_PDO            _IOWR('E', 0x07, struct ec_pdo_data)
​
/** 写入 PDO 数据 */
#define EC_IOCTL_SET_PDO            _IOW('E', 0x08, struct ec_pdo_data)
​
/** 复位主站 */
#define EC_IOCTL_RESET_MASTER       _IO('E', 0x09)
​
/** 开始周期数据交换 */
#define EC_IOCTL_START_CYCLE        _IO('E', 0x0A)
​
/** 停止周期数据交换 */
#define EC_IOCTL_STOP_CYCLE         _IO('E', 0x0B)
​
/** 获取分布式时钟时间 */
#define EC_IOCTL_GET_DC_TIME        _IOR('E', 0x0C, uint64_t)
​
/** 配置 PDO 映射 */
#define EC_IOCTL_CONFIGURE_PDO      _IOW('E', 0x0D, struct ec_pdo_config)
​
/**
 * @}
 */

4.2.2 ioctl 数据结构

/**
 * @struct ec_version
 * @brief 主站版本信息。
 */
struct ec_version {
    uint32_t major;                 /**< 主版本号 */
    uint32_t minor;                 /**< 次版本号 */
    uint32_t patch;                 /**< 补丁版本号 */
    char git_hash[64];              /**< Git 提交哈希 */
};
​
/**
 * @struct ec_slave_list
 * @brief 从站列表结构。
 */
struct ec_slave_list {
    uint32_t count;                 /**< 从站数量 */
    struct ec_slave_info *slaves;   /**< 从站信息数组 */
};
​
/**
 * @struct ec_slave_info
 * @brief 从站信息结构。
 */
struct ec_slave_info {
    uint16_t slave_id;              /**< 从站 ID */
    uint16_t vendor_id;             /**< 厂商 ID */
    uint16_t product_code;          /**< 产品代码 */
    uint16_t revision_no;           /**< 修订号 */
    uint8_t state;                  /**< 当前状态 */
    uint16_t alias;                 /**< 别名 */
    uint32_t pdo_offset;            /**< PDO 偏移 */
};
​
/**
 * @struct ec_sdo_request
 * @brief SDO 请求结构。
 */
struct ec_sdo_request {
    uint16_t slave_id;              /**< 从站 ID */
    uint16_t index;                 /**< SDO 索引 */
    uint8_t subindex;               /**< SDO 子索引 */
    uint32_t data_len;              /**< 数据长度 */
    uint8_t data[256];              /**< 数据缓冲区 */
    uint32_t timeout_ms;            /**< 超时时间 (毫秒) */
};
​
/**
 * @struct ec_pdo_data
 * @brief PDO 数据交换结构。
 */
struct ec_pdo_data {
    uint32_t pdo_index;             /**< PDO 索引 */
    uint32_t data_len;              /**< 数据长度 */
    uint8_t data[4096];             /**< 数据缓冲区 */
};
​
/**
 * @struct ec_pdo_config
 * @brief PDO 映射配置结构。
 */
struct ec_pdo_config {
    uint16_t slave_id;              /**< 从站 ID */
    uint32_t tx_pdo_config[16];     /**< TxPDO 配置 */
    uint32_t rx_pdo_config[16];     /**< RxPDO 配置 */
    uint32_t tx_pdo_count;          /**< TxPDO 数量 */
    uint32_t rx_pdo_count;          /**< RxPDO 数量 */
};

4.3 核心代码实现

4.3.1 ioctl 接口实现

/**
 * @brief ioctl 控制命令实现。
 */
static long ethercat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct ethercat_device *dev = filp->private_data;
    struct ethercat_master *master = dev->master;
    void __user *user_ptr = (void __user *)arg;
    int ret = 0;
​
    switch (cmd) {
    case EC_IOCTL_GET_VERSION:
    {
        struct ec_version ver = {
            .major = 1,
            .minor = 0,
            .patch = 0,
        };
        strcpy(ver.git_hash, "abcdef123456");
        if (copy_to_user(user_ptr, &ver, sizeof(ver))) {
            ret = -EFAULT;
        }
        break;
    }
​
    case EC_IOCTL_GET_SLAVE_LIST:
    {
        struct ec_slave_list list;
        struct ec_slave_info *slaves;
        int i = 0;
​
        list.count = master->slave_count;
        slaves = kzalloc(list.count * sizeof(struct ec_slave_info), GFP_KERNEL);
        if (!slaves) {
            ret = -ENOMEM;
            break;
        }
​
        list_for_each_entry(slave, &master->slaves, list) {
            slaves[i].slave_id = slave->slave_id;
            slaves[i].vendor_id = slave->vendor_id;
            slaves[i].product_code = slave->product_code;
            slaves[i].revision_no = slave->revision_no;
            slaves[i].state = slave->state;
            slaves[i].alias = slave->alias;
            slaves[i].pdo_offset = slave->pdo_offset;
            i++;
        }
​
        if (copy_to_user(user_ptr, &list, sizeof(list))) {
            ret = -EFAULT;
        }
        if (copy_to_user(user_ptr + sizeof(list), slaves, list.count * sizeof(struct ec_slave_info))) {
            ret = -EFAULT;
        }
        kfree(slaves);
        break;
    }
​
    case EC_IOCTL_SDO_READ:
    {
        struct ec_sdo_request req;
        if (copy_from_user(&req, user_ptr, sizeof(req))) {
            ret = -EFAULT;
            break;
        }
        ret = ec_sdo_read(master, req.slave_id, req.index, req.subindex,
                          req.data, &req.data_len);
        if (ret == 0) {
            if (copy_to_user(user_ptr, &req, sizeof(req))) {
                ret = -EFAULT;
            }
        }
        break;
    }
​
    case EC_IOCTL_SDO_WRITE:
    {
        struct ec_sdo_request req;
        if (copy_from_user(&req, user_ptr, sizeof(req))) {
            ret = -EFAULT;
            break;
        }
        ret = ec_sdo_write(master, req.slave_id, req.index, req.subindex,
                           req.data, req.data_len);
        break;
    }
​
    case EC_IOCTL_SET_STATE:
    {
        uint32_t state;
        if (copy_from_user(&state, user_ptr, sizeof(state))) {
            ret = -EFAULT;
            break;
        }
        ret = ec_set_master_state(master, state);
        break;
    }
​
    case EC_IOCTL_GET_STATE:
    {
        uint32_t state = master->state;
        if (copy_to_user(user_ptr, &state, sizeof(state))) {
            ret = -EFAULT;
        }
        break;
    }
​
    case EC_IOCTL_GET_PDO:
    {
        struct ec_pdo_data pdo;
        if (copy_from_user(&pdo, user_ptr, sizeof(pdo))) {
            ret = -EFAULT;
            break;
        }
        if (pdo.pdo_index < master->pdo_rx_count) {
            pdo.data_len = master->pdo_rx[pdo.pdo_index].data_len;
            memcpy(pdo.data, master->pdo_rx[pdo.pdo_index].data, pdo.data_len);
        } else {
            ret = -EINVAL;
        }
        if (ret == 0) {
            if (copy_to_user(user_ptr, &pdo, sizeof(pdo))) {
                ret = -EFAULT;
            }
        }
        break;
    }
​
    case EC_IOCTL_SET_PDO:
    {
        struct ec_pdo_data pdo;
        if (copy_from_user(&pdo, user_ptr, sizeof(pdo))) {
            ret = -EFAULT;
            break;
        }
        if (pdo.pdo_index < master->pdo_tx_count) {
            memcpy(master->pdo_tx[pdo.pdo_index].data, pdo.data, pdo.data_len);
            master->pdo_tx[pdo.pdo_index].data_len = pdo.data_len;
        } else {
            ret = -EINVAL;
        }
        break;
    }
​
    case EC_IOCTL_RESET_MASTER:
        // 复位主站
        ec_set_master_state(master, EC_STATE_INIT);
        ec_scan_slaves(master);
        ec_configure_pdo_mapping(master);
        ec_set_master_state(master, EC_STATE_OP);
        break;
​
    case EC_IOCTL_START_CYCLE:
        master->cycle_active = 1;
        ec_master_timer_init(master);
        break;
​
    case EC_IOCTL_STOP_CYCLE:
        master->cycle_active = 0;
        del_timer(&master->timer);
        break;
​
    case EC_IOCTL_GET_DC_TIME:
    {
        uint64_t dc_time = master->dc ? master->dc->master_time : 0;
        if (copy_to_user(user_ptr, &dc_time, sizeof(dc_time))) {
            ret = -EFAULT;
        }
        break;
    }
​
    case EC_IOCTL_CONFIGURE_PDO:
    {
        struct ec_pdo_config config;
        if (copy_from_user(&config, user_ptr, sizeof(config))) {
            ret = -EFAULT;
            break;
        }
        ret = ec_configure_pdo_mapping_for_slave(master, config);
        break;
    }
​
    default:
        ret = -ENOTTY;
        break;
    }
​
    return ret;
}

4.3.2 mmap 零拷贝数据共享

/**
 * @brief mmap 内存映射实现。
 */
static int ethercat_mmap(struct file *filp, struct vm_area_struct *vma)
{
    struct ethercat_device *dev = filp->private_data;
    struct ec_brainbox_priv *hw = dev->hw_priv;
    size_t size = vma->vm_end - vma->vm_start;
​
    // 1. 检查映射大小是否符合预期
    if (size != PDO_BUFFER_SIZE) {
        dev_err(&hw->spi->dev, "mmap size mismatch: %zu vs %zu\n", size, PDO_BUFFER_SIZE);
        return -EINVAL;
    }
​
    // 2. 获取 DMA 缓冲区的物理地址
    dma_addr_t dma_addr = hw->rx_dma_handle;
​
    // 3. 映射 DMA 缓冲区到用户空间
    if (remap_pfn_range(vma, vma->vm_start, dma_addr >> PAGE_SHIFT,
                        size, vma->vm_page_prot)) {
        dev_err(&hw->spi->dev, "mmap remap failed\n");
        return -EAGAIN;
    }
​
    // 4. 设置 VM 标志
    vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
​
    // 5. 保存映射信息
    vma->vm_private_data = (void *)dma_addr;
​
    return 0;
}
​
/**
 * @brief mmap 用户空间读取示例。
 */
static void ec_mmap_user_example(int fd)
{
    // 1. mmap 映射
    size_t size = 4096;
    void *map = mmap(NULL, size, PROT_READ | PROT_WRITE,
                     MAP_SHARED, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        return;
    }
​
    // 2. 读取 PDO 数据 (直接访问内存)
    uint8_t *rx_data = (uint8_t *)map;
    for (int i = 0; i < 10; i++) {
        printf("Rx PDO[%d] = 0x%02x\n", i, rx_data[i]);
    }
​
    // 3. 写入 PDO 数据 (直接写入内存)
    uint8_t *tx_data = (uint8_t *)map + 2048;
    for (int i = 0; i < 10; i++) {
        tx_data[i] = 0xAA + i;
    }
​
    // 4. 主动通知内核数据已更新
    ioctl(fd, EC_IOCTL_SET_PDO, 0);
​
    // 5. 取消映射
    munmap(map, size);
}

4.3.3 netlink 事件通知机制

#include <net/genetlink.h>
​
/**
 * @struct ec_netlink_context
 * @brief netlink 上下文。
 */
struct ec_netlink_context {
    struct sock *nl_sock;            /**< netlink 套接字 */
    struct genl_family *family;      /**< 家族 */
    int family_id;                   /**< 家族 ID */
    struct work_struct event_work;   /**< 事件工作队列 */
};
​
/* 定义 netlink 协议 */
#define EC_GENL_NAME "ethercat"
#define EC_GENL_VERSION 1
​
/* 命令定义 */
enum ec_genl_cmds {
    EC_CMD_SLAVE_STATE_CHANGED = 1, /**< 从站状态变化事件 */
    EC_CMD_PDO_UPDATED,              /**< PDO 数据更新事件 */
    EC_CMD_FAULT_OCCURRED,           /**< 故障发生事件 */
    EC_CMD_MASTER_STATE_CHANGED,     /**< 主站状态变化事件 */
    EC_CMD_DC_SYNC_CHANGED,          /**< DC 同步变化事件 */
};
​
/* 属性定义 */
enum ec_genl_attrs {
    EC_ATTR_UNSPEC = 0,
    EC_ATTR_SLAVE_ID,                /**< 从站 ID */
    EC_ATTR_STATE,                   /**< 状态 */
    EC_ATTR_FAULT_CODE,              /**< 故障代码 */
    EC_ATTR_FAULT_MESSAGE,           /**< 故障消息 */
    EC_ATTR_MASTER_STATE,            /**< 主站状态 */
    EC_ATTR_DC_TIME,                 /**< DC 时间 */
    EC_ATTR_PDO_INDEX,               /**< PDO 索引 */
    EC_ATTR_PDO_DATA,                /**< PDO 数据 */
    __EC_ATTR_MAX
};
​
/**
 * @brief 初始化 netlink 接口。
 */
static int ec_netlink_init(struct ethercat_master *master)
{
    struct ec_netlink_context *ctx;
    struct genl_family *family;
    struct genl_ops ops[] = {
        {
            .cmd = EC_CMD_SLAVE_STATE_CHANGED,
            .doit = ec_netlink_slave_state_changed,
            .policy = ec_genl_policy,
        },
        // 更多操作...
    };
​
    // 1. 分配 netlink 上下文
    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
    if (!ctx) return -ENOMEM;
​
    // 2. 创建 genl 家族
    family = genl_family_alloc(NULL, ops, ARRAY_SIZE(ops), 0);
    if (!family) {
        kfree(ctx);
        return -ENOMEM;
    }
​
    family->name = EC_GENL_NAME;
    family->version = EC_GENL_VERSION;
    family->hdrsize = 0;
    family->maxattr = __EC_ATTR_MAX;
​
    // 3. 注册家族
    ctx->family_id = genl_register_family(family);
    if (ctx->family_id < 0) {
        genl_family_free(family);
        kfree(ctx);
        return ctx->family_id;
    }
​
    ctx->family = family;
    master->nl_ctx = ctx;
​
    INIT_WORK(&ctx->event_work, ec_netlink_event_worker);
​
    dev_info(&master->hw_priv->spi->dev, "EtherCAT netlink registered\n");
    return 0;
}
​
/**
 * @brief 发送从站状态变化事件。
 */
static void ec_netlink_send_slave_state_event(struct ethercat_master *master,
                                             uint16_t slave_id,
                                             uint8_t old_state,
                                             uint8_t new_state)
{
    struct ec_netlink_context *ctx = master->nl_ctx;
    struct sk_buff *skb;
    struct genl_info info;
    int ret;
​
    // 1. 创建 netlink 消息
    skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_ATOMIC);
    if (!skb) return;
​
    // 2. 添加属性
    if (nla_put_u16(skb, EC_ATTR_SLAVE_ID, slave_id) < 0 ||
        nla_put_u8(skb, EC_ATTR_STATE, new_state) < 0) {
        nlmsg_free(skb);
        return;
    }
​
    // 3. 发送消息
    ret = genlmsg_unicast(master->hw_priv->dev, skb, ctx->family_id);
    if (ret < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to send netlink event: %d\n", ret);
    }
}
​
/**
 * @brief 用户空间接收 netlink 事件示例。
 */
static void ec_netlink_user_receive(int sock)
{
    struct nlmsghdr *nlh;
    struct genlmsghdr *genl;
    struct sockaddr_nl addr;
    char buffer[4096];
    int len;
​
    // 1. 等待消息
    len = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, &addr_len);
    if (len < 0) return;
​
    // 2. 解析消息
    nlh = (struct nlmsghdr *)buffer;
    genl = nlmsg_data(nlh);
​
    switch (genl->cmd) {
    case EC_CMD_SLAVE_STATE_CHANGED:
    {
        int slave_id = nla_get_u16(nlmsg_attr(nlh, EC_ATTR_SLAVE_ID));
        int state = nla_get_u8(nlmsg_attr(nlh, EC_ATTR_STATE));
        printf("Slave %d state changed to %d\n", slave_id, state);
        break;
    }
    case EC_CMD_FAULT_OCCURRED:
        printf("Fault occurred: code=%d, message=%s\n",
               nla_get_u32(nlmsg_attr(nlh, EC_ATTR_FAULT_CODE)),
               nla_get_string(nlmsg_attr(nlh, EC_ATTR_FAULT_MESSAGE)));
        break;
    // 更多事件...
    }
}

4.3.4 sysfs 调试接口

/**
 * @brief sysfs 文件系统接口。
 */
static ssize_t ec_sysfs_show_version(struct device *dev,
                                     struct device_attribute *attr,
                                     char *buf)
{
    return sprintf(buf, "EtherCAT Master v1.0.0\n");
}
​
static ssize_t ec_sysfs_show_slave_count(struct device *dev,
                                        struct device_attribute *attr,
                                        char *buf)
{
    struct ethercat_device *ec_dev = dev_get_drvdata(dev);
    struct ethercat_master *master = ec_dev->master;
    return sprintf(buf, "%d\n", master->slave_count);
}
​
static ssize_t ec_sysfs_show_state(struct device *dev,
                                   struct device_attribute *attr,
                                   char *buf)
{
    struct ethercat_device *ec_dev = dev_get_drvdata(dev);
    struct ethercat_master *master = ec_dev->master;
    return sprintf(buf, "%d\n", master->state);
}
​
static ssize_t ec_sysfs_store_reset(struct device *dev,
                                    struct device_attribute *attr,
                                    const char *buf, size_t count)
{
    struct ethercat_device *ec_dev = dev_get_drvdata(dev);
    struct ethercat_master *master = ec_dev->master;
    if (master) {
        ec_master_reset(master);
    }
    return count;
}
​
static DEVICE_ATTR(version, 0444, ec_sysfs_show_version, NULL);
static DEVICE_ATTR(slave_count, 0444, ec_sysfs_show_slave_count, NULL);
static DEVICE_ATTR(state, 0444, ec_sysfs_show_state, NULL);
static DEVICE_ATTR(reset, 0200, NULL, ec_sysfs_store_reset);
​
/**
 * @brief 创建 sysfs 接口。
 */
static int ec_sysfs_init(struct ethercat_device *ec_dev)
{
    int ret;
​
    ret = device_create_file(ec_dev->device, &dev_attr_version);
    if (ret) return ret;
​
    ret = device_create_file(ec_dev->device, &dev_attr_slave_count);
    if (ret) return ret;
​
    ret = device_create_file(ec_dev->device, &dev_attr_state);
    if (ret) return ret;
​
    ret = device_create_file(ec_dev->device, &dev_attr_reset);
    if (ret) return ret;
​
    return 0;
}

4.3.5 procfs 调试接口

/**
 * @brief procfs 调试接口。
 */
static int ec_procfs_show(struct seq_file *m, void *v)
{
    struct ethercat_master *master = m->private;
    struct ethercat_slave *slave;
​
    seq_printf(m, "EtherCAT Master Status\n");
    seq_printf(m, "=====================\n");
    seq_printf(m, "State: %d\n", master->state);
    seq_printf(m, "Slave Count: %d\n", master->slave_count);
    seq_printf(m, "Cycle Time: %d us\n", master->cycle_time_us);
    seq_printf(m, "Cycle Count: %lld\n", master->cycle_count);
    seq_printf(m, "\nSlave List:\n");
    seq_printf(m, "===========\n");
​
    list_for_each_entry(slave, &master->slaves, list) {
        seq_printf(m, "  Slave %d: Vendor=0x%04x, Product=0x%04x, State=%d\n",
                   slave->slave_id, slave->vendor_id, slave->product_code, slave->state);
    }
​
    return 0;
}
​
static int ec_procfs_open(struct inode *inode, struct file *file)
{
    return single_open(file, ec_procfs_show, PDE_DATA(inode));
}
​
static const struct file_operations ec_procfs_fops = {
    .owner = THIS_MODULE,
    .open = ec_procfs_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};
​
/**
 * @brief 创建 procfs 接口。
 */
static int ec_procfs_init(struct ethercat_master *master)
{
    struct proc_dir_entry *proc;
​
    proc = proc_create_data("ethercat_master", 0444, NULL,
                            &ec_procfs_fops, master);
    if (!proc) {
        dev_err(&master->hw_priv->spi->dev, "Failed to create procfs entry\n");
        return -ENOMEM;
    }
​
    return 0;
}

4.4 软件设计模式树形分析

用户空间-内核空间通信机制设计模式
├── 命令模式 (Command Pattern)
│   └── ioctl 命令封装 (EC_IOCTL_GET/SET 等)
├── 工厂模式 (Factory Pattern)
│   ├── netlink 家族创建
│   └── sysfs/procfs 文件创建
├── 单例模式 (Singleton Pattern)
│   └── 一个主站只有一个 netlink 上下文
├── 观察者模式 (Observer Pattern)
│   ├── netlink 事件通知
│   └── sysfs 属性变化通知
├── 代理模式 (Proxy Pattern)
│   ├── mmap 代理 DMA 缓冲区到用户空间
│   └── ioctl 代理硬件操作
├── 装饰器模式 (Decorator Pattern)
│   ├── 为 ioctl 命令添加权限检查
│   └── 为 netlink 消息添加时间戳
└── 适配器模式 (Adapter Pattern)
    ├── ioctl 适配不同的数据格式
    └── mmap 适配不同的映射需求

4.5 通信机制调试核心难点

4.5.1 ioctl 数据拷贝错误

现象:用户空间调用 ioctl 返回 -EFAULT

原因

  1. 用户空间地址无效。

  2. 数据长度错误。

  3. 使用 _IOWR 但未正确设置 arg

解决方法

  1. 使用 access_ok() 检查用户地址。

  2. 检查 copy_to_user/copy_from_user 返回值。

  3. 正确使用 _IOR/_IOW/_IOWR

4.5.2 mmap 映射失败

现象:用户空间 mmap() 返回 MAP_FAILED

原因

  1. remap_pfn_range 参数错误。

  2. DMA 缓冲区未提前分配。

  3. 物理地址未对齐。

解决方法

  1. 检查 remap_pfn_range 返回值。

  2. mmap 前分配 DMA 缓冲区。

  3. 使用 PAGE_SHIFT 计算页帧号。

4.5.3 netlink 消息丢失

现象:用户空间收不到 netlink 事件。

原因

  1. 用户空间 socket 绑定错误。

  2. 消息发送失败。

  3. 消息被丢弃。

解决方法

  1. 检查用户空间 socket 绑定。

  2. 检查 genlmsg_unicast 返回值。

  3. 增加 netlink 消息缓冲区大小。

4.5.4 sysfs 权限问题

现象:用户空间无法写入 sysfs 文件。

原因

  1. 文件权限设置错误。

  2. 写函数未正确实现。

  3. 内核安全策略限制。

解决方法

  1. 检查 DEVICE_ATTR 权限位。

  2. 实现 store 函数并返回正确值。

  3. 使用 __ATTR_RW 宏。

第五部分 EtherCAT 从站驱动与状态机

5.1 EtherCAT 从站驱动概述

在 EtherCAT 主从架构中,从站作为数据终端节点,负责执行实际的控制任务和传感器数据采集。从站驱动开发的核心是 状态机管理PDO 数据映射异常处理。每个从站都是一个独立的智能节点,需要与主站保持严格的同步和状态一致。

5.1.1 从站状态机定义 (ET1100)

状态 代码 描述
INIT 0x01 初始化状态,仅支持 EEPROM 读取
PREOP 0x02 预操作状态,可配置 PDO 映射
SAFEOP 0x04 安全操作状态,输出保持安全值
OP 0x08 操作状态,完全 PDO 数据交换

5.1.2 状态机转换图

[上电/复位]
    ↓
[INIT] ←──────────────┐
    |                   │
    | 配置 EEPROM       │ 错误
    ↓                   │
[PREOP] → 配置 PDO → [SAFEOP] → 同步完成 → [OP]
    |                       |
    | 错误                  | 错误
    ↓                       ↓
    └───────────────────────┘

5.2 核心数据结构

5.2.1 从站数据结构

/**
 * @struct ethercat_slave_driver
 * @brief EtherCAT 从站驱动数据结构。
 */
struct ethercat_slave_driver {
    struct ethercat_master *master;      /**< 所属主站 */
    struct ethercat_slave *slave;        /**< 从站核心结构 */
    struct spi_device *spi;              /**< 关联 SPI 设备 (ET1100) */
    void __iomem *regs;                  /**< 映射的寄存器基址 */
    uint32_t vendor_id;                  /**< 厂商 ID */
    uint32_t product_code;               /**< 产品代码 */
    uint8_t current_state;               /**< 当前状态 */
    uint8_t prev_state;                  /**< 上一个状态 */
    uint16_t slave_id;                   /**< 从站 ID */
    uint16_t alias;                      /**< 别名 */
    struct list_head pdo_tx_list;        /**< 发送 PDO 链表 */
    struct list_head pdo_rx_list;        /**< 接收 PDO 链表 */
    uint32_t tx_pdo_count;               /**< TxPDO 数量 */
    uint32_t rx_pdo_count;               /**< RxPDO 数量 */
    spinlock_t lock;                     /**< 自旋锁 */
    struct work_struct state_change_work; /**< 状态变化工作队列 */
    struct timer_list watchdog_timer;    /**< 看门狗定时器 */
    int fault_count;                     /**< 故障计数 */
    unsigned long last_heartbeat;        /**< 最后心跳时间 */
};
​
/**
 * @struct ethercat_pdo_entry
 * @brief PDO 条目数据结构。
 */
struct ethercat_pdo_entry {
    uint16_t index;                      /**< 对象索引 */
    uint8_t subindex;                    /**< 对象子索引 */
    uint32_t data_len;                   /**< 数据长度 */
    uint8_t *data;                       /**< 数据缓冲区 */
    uint32_t flags;                      /**< 标志位 */
    struct list_head list;               /**< 链表节点 */
};

5.3 核心代码实现

5.3.1 从站初始化

/**
 * @brief 从站初始化函数。
 * @param master 主站指针
 * @param slave_id 从站 ID
 * @return 0 成功,-1 失败
 */
static int ethercat_slave_init(struct ethercat_master *master, uint16_t slave_id)
{
    struct ethercat_slave *slave;
    struct ethercat_slave_driver *driver;
    struct ec_brainbox_priv *hw = master->hw_priv;
    int ret;
​
    // 1. 创建从站核心结构
    slave = kzalloc(sizeof(struct ethercat_slave), GFP_KERNEL);
    if (!slave) return -ENOMEM;
    slave->slave_id = slave_id;
    slave->vendor_id = ec_read_reg(hw->regs, 0x300 + slave_id * 0x10);
    slave->product_code = ec_read_reg(hw->regs, 0x304 + slave_id * 0x10);
    slave->revision_no = ec_read_reg(hw->regs, 0x308 + slave_id * 0x10);
    slave->state = EC_STATE_INIT;
    slave->prev_state = EC_STATE_INIT;
    INIT_LIST_HEAD(&slave->list);
​
    // 2. 创建从站驱动私有数据
    driver = kzalloc(sizeof(struct ethercat_slave_driver), GFP_KERNEL);
    if (!driver) {
        kfree(slave);
        return -ENOMEM;
    }
​
    driver->master = master;
    driver->slave = slave;
    driver->slave_id = slave_id;
    driver->vendor_id = slave->vendor_id;
    driver->product_code = slave->product_code;
    driver->current_state = EC_STATE_INIT;
    driver->prev_state = EC_STATE_INIT;
    driver->fault_count = 0;
    spin_lock_init(&driver->lock);
    INIT_LIST_HEAD(&driver->pdo_tx_list);
    INIT_LIST_HEAD(&driver->pdo_rx_list);
​
    // 3. 初始化看门狗定时器
    timer_setup(&driver->watchdog_timer, ec_slave_watchdog_callback, 0);
    driver->watchdog_timer.expires = jiffies + msecs_to_jiffies(1000);
    add_timer(&driver->watchdog_timer);
​
    // 4. 初始化状态变化工作队列
    INIT_WORK(&driver->state_change_work, ec_slave_state_change_worker);
​
    // 5. 将从站添加到主站列表
    list_add_tail(&slave->list, &master->slaves);
    master->slave_count++;
​
    dev_info(&hw->spi->dev, "Slave %d: Vendor=0x%04x, Product=0x%04x initialized\n",
             slave_id, slave->vendor_id, slave->product_code);
    return 0;
}

5.3.2 从站状态切换

/**
 * @brief 从站状态切换函数。
 * @param driver 从站驱动指针
 * @param target_state 目标状态
 * @return 0 成功,-1 失败
 */
static int ec_slave_set_state(struct ethercat_slave_driver *driver,
                              uint8_t target_state)
{
    struct ec_brainbox_priv *hw = driver->master->hw_priv;
    uint32_t state_cmd;
    uint8_t current_state;
    unsigned long timeout;
​
    spin_lock(&driver->lock);
    current_state = driver->current_state;
    driver->prev_state = current_state;
    spin_unlock(&driver->lock);
​
    // 1. 检查状态切换合法性
    if (target_state == current_state) {
        return 0;
    }
​
    // 2. 构造状态切换命令
    state_cmd = (driver->slave_id << 16) | target_state;
​
    // 3. 写入状态切换命令到 ET1100 寄存器
    ec_write_reg(hw->regs, 0x310 + driver->slave_id * 4, state_cmd);
​
    // 4. 等待状态切换完成 (超时 100ms)
    timeout = jiffies + msecs_to_jiffies(100);
    while (time_before(jiffies, timeout)) {
        uint32_t status = ec_read_reg(hw->regs, 0x314 + driver->slave_id * 4);
        if ((status & 0x0F) == target_state) {
            spin_lock(&driver->lock);
            driver->current_state = target_state;
            driver->prev_state = current_state;
            spin_unlock(&driver->lock);
​
            // 触发状态变化通知
            schedule_work(&driver->state_change_work);
            return 0;
        }
        usleep(1000);
    }
​
    // 5. 超时处理
    dev_err(&hw->spi->dev, "Slave %d state transition timeout (target=%d)\n",
            driver->slave_id, target_state);
    return -ETIMEDOUT;
}
​
/**
 * @brief 从站状态变化工作队列。
 */
static void ec_slave_state_change_worker(struct work_struct *work)
{
    struct ethercat_slave_driver *driver = container_of(work,
        struct ethercat_slave_driver, state_change_work);
    uint8_t old_state, new_state;
​
    spin_lock(&driver->lock);
    old_state = driver->prev_state;
    new_state = driver->current_state;
    spin_unlock(&driver->lock);
​
    // 1. 通过 netlink 发送状态变化事件
    ec_netlink_send_slave_state_event(driver->master,
                                      driver->slave_id,
                                      old_state, new_state);
​
    // 2. 更新 PDO 配置 (如果需要)
    if (new_state == EC_STATE_OP) {
        ec_slave_configure_pdo(driver);
    }
}

5.3.3 PDO 映射配置

/**
 * @brief 从站 PDO 映射配置。
 * @param driver 从站驱动指针
 * @return 0 成功,-1 失败
 */
static int ec_slave_configure_pdo(struct ethercat_slave_driver *driver)
{
    struct ec_brainbox_priv *hw = driver->master->hw_priv;
    uint32_t tx_pdo_base = 0x400 + driver->slave_id * 0x20;
    uint32_t rx_pdo_base = 0x800 + driver->slave_id * 0x20;
    int i;
​
    // 1. 清除旧的 PDO 映射
    list_del(&driver->pdo_tx_list);
    list_del(&driver->pdo_rx_list);
​
    // 2. 从硬件读取当前 PDO 映射
    driver->tx_pdo_count = ec_read_reg(hw->regs, tx_pdo_base);
    driver->rx_pdo_count = ec_read_reg(hw->regs, rx_pdo_base);
​
    // 3. 创建 TxPDO 映射
    for (i = 0; i < driver->tx_pdo_count; i++) {
        struct ethercat_pdo_entry *pdo = kzalloc(sizeof(*pdo), GFP_KERNEL);
        if (!pdo) return -ENOMEM;
​
        pdo->index = ec_read_reg(hw->regs, tx_pdo_base + 4 + i * 8);
        pdo->subindex = ec_read_reg(hw->regs, tx_pdo_base + 8 + i * 8);
        pdo->data_len = 4;
        pdo->data = kzalloc(pdo->data_len, GFP_KERNEL);
        if (!pdo->data) {
            kfree(pdo);
            return -ENOMEM;
        }
        list_add_tail(&pdo->list, &driver->pdo_tx_list);
    }
​
    // 4. 创建 RxPDO 映射
    for (i = 0; i < driver->rx_pdo_count; i++) {
        struct ethercat_pdo_entry *pdo = kzalloc(sizeof(*pdo), GFP_KERNEL);
        if (!pdo) return -ENOMEM;
​
        pdo->index = ec_read_reg(hw->regs, rx_pdo_base + 4 + i * 8);
        pdo->subindex = ec_read_reg(hw->regs, rx_pdo_base + 8 + i * 8);
        pdo->data_len = 4;
        pdo->data = kzalloc(pdo->data_len, GFP_KERNEL);
        if (!pdo->data) {
            kfree(pdo);
            return -ENOMEM;
        }
        list_add_tail(&pdo->list, &driver->pdo_rx_list);
    }
​
    return 0;
}

5.3.4 从站 PDO 数据更新

/**
 * @brief 从站接收 PDO 数据更新。
 * @param driver 从站驱动指针
 * @param pdo_index PDO 索引
 * @param data 数据缓冲区
 * @param data_len 数据长度
 * @return 0 成功,-1 失败
 */
static int ec_slave_update_rx_pdo(struct ethercat_slave_driver *driver,
                                  uint32_t pdo_index,
                                  const uint8_t *data,
                                  size_t data_len)
{
    struct ec_brainbox_priv *hw = driver->master->hw_priv;
    uint32_t pdo_addr = 0x800 + driver->slave_id * 0x20 + 4 + pdo_index * 8;
    int i;
​
    // 1. 写入 PDO 数据到寄存器
    for (i = 0; i < data_len && i < 4; i++) {
        ec_write_reg(hw->regs, pdo_addr + i * 4, data[i]);
    }
​
    // 2. 更新 PDO 条目
    list_for_each_entry(pdo, &driver->pdo_rx_list, list) {
        if (pdo->index == pdo_index) {
            memcpy(pdo->data, data, min(data_len, pdo->data_len));
            break;
        }
    }
​
    return 0;
}
​
/**
 * @brief 从站发送 PDO 数据读取。
 * @param driver 从站驱动指针
 * @param pdo_index PDO 索引
 * @param data 输出数据缓冲区
 * @param data_len 输出数据长度
 * @return 0 成功,-1 失败
 */
static int ec_slave_read_tx_pdo(struct ethercat_slave_driver *driver,
                                uint32_t pdo_index,
                                uint8_t *data,
                                size_t *data_len)
{
    struct ec_brainbox_priv *hw = driver->master->hw_priv;
    uint32_t pdo_addr = 0x400 + driver->slave_id * 0x20 + 4 + pdo_index * 8;
    int i;
​
    // 1. 读取 PDO 数据
    for (i = 0; i < 4; i++) {
        data[i] = ec_read_reg(hw->regs, pdo_addr + i * 4) & 0xFF;
    }
    *data_len = 4;
​
    // 2. 更新 PDO 条目
    list_for_each_entry(pdo, &driver->pdo_tx_list, list) {
        if (pdo->index == pdo_index) {
            memcpy(pdo->data, data, 4);
            break;
        }
    }
​
    return 0;
}

5.3.5 从站看门狗

/**
 * @brief 从站看门狗回调。
 */
static void ec_slave_watchdog_callback(struct timer_list *t)
{
    struct ethercat_slave_driver *driver = from_timer(driver, t, watchdog_timer);
    unsigned long now = jiffies;
​
    // 1. 检查心跳
    if (now - driver->last_heartbeat > msecs_to_jiffies(2000)) {
        // 超过 2 秒无响应,触发故障处理
        driver->fault_count++;
        dev_warn(&driver->master->hw_priv->spi->dev,
                 "Slave %d heartbeat lost (fault=%d)\n",
                 driver->slave_id, driver->fault_count);
​
        // 触发状态机恢复
        schedule_work(&driver->state_change_work);
    }
​
    // 2. 重置定时器
    mod_timer(&driver->watchdog_timer, jiffies + msecs_to_jiffies(1000));
}
​
/**
 * @brief 更新从站心跳。
 */
static void ec_slave_update_heartbeat(struct ethercat_slave_driver *driver)
{
    driver->last_heartbeat = jiffies;
    driver->fault_count = 0;
}
​
/**
 * @brief 从站 SDO 读取 (非周期)。
 */
static int ec_slave_sdo_read(struct ethercat_slave_driver *driver,
                             uint16_t index,
                             uint8_t subindex,
                             uint8_t *data,
                             size_t *len)
{
    struct ec_brainbox_priv *hw = driver->master->hw_priv;
​
    // 1. 设置 SDO 请求
    ec_write_reg(hw->regs, 0x500, driver->slave_id);
    ec_write_reg(hw->regs, 0x504, index);
    ec_write_reg(hw->regs, 0x508, subindex);
​
    // 2. 触发读操作
    ec_write_reg(hw->regs, 0x510, 0x00000002);
​
    // 3. 等待完成 (简单延时)
    msleep(10);
​
    // 4. 读取数据
    *len = ec_read_reg(hw->regs, 0x50C) & 0xFF;
    for (int i = 0; i < *len; i++) {
        data[i] = ec_read_reg(hw->regs, 0x600 + i * 4) & 0xFF;
    }
​
    return 0;
}

5.4 软件设计模式树形分析

EtherCAT 从站驱动与状态机设计模式
├── 状态模式 (State Pattern)
│   └── 从站状态机 (INIT → PREOP → SAFEOP → OP)
├── 工厂模式 (Factory Pattern)
│   ├── 从站初始化 (kzalloc + list_add_tail)
│   └── PDO 条目创建 (kzalloc + 映射配置)
├── 观察者模式 (Observer Pattern)
│   └── 状态变化工作队列通知上层
├── 装饰器模式 (Decorator Pattern)
│   ├── PDO 数据添加时间戳
│   └── SDO 请求添加错误重试
├── 模板方法模式 (Template Method Pattern)
│   └── 从站初始化流程 (硬件读取 → 状态初始化 → PDO配置)
├── 代理模式 (Proxy Pattern)
│   └── 通过 ioctl 访问从站功能
└── 策略模式 (Strategy Pattern)
    └── 从站故障恢复策略 (重置 vs 重配置)

5.5 从站驱动调试核心难点

5.5.1 从站状态无法切换

现象:从站无法从 INIT 切换到 PREOP。

原因

  1. EEPROM 未配置或损坏。

  2. 上电时序问题。

  3. 供电不稳定。

解决方法

  1. 使用专用工具配置 EEPROM。

  2. 增加上电后的延时。

  3. 检查电源电压。

5.5.2 PDO 数据错位

现象:接收到的 PDO 数据与映射顺序不符。

原因

  1. PDO 映射配置顺序错误。

  2. 字节序问题 (小端 vs 大端)。

  3. 数据长度不匹配。

解决方法

  1. 检查 PDO 映射寄存器。

  2. 使用 le32_to_cpu 处理数据。

  3. 验证数据长度。

5.5.3 看门狗误触发

现象:从站频繁触发看门狗。

原因

  1. 心跳间隔设置过短。

  2. 从站处理延迟过大。

  3. 网络负载过高。

解决方法

  1. 增加看门狗超时时间。

  2. 优化从站处理逻辑。

  3. 降低 PDO 交换频率。

5.6 与其他模块的协同

模块 协同方式 调试关键点
主站驱动 通过 ET1100 寄存器与主站交互 PDO 数据同步、状态切换
ioctl 接口 提供从站状态查询和配置 数据拷贝、权限控制
netlink 接口 事件通知 (状态变化、故障) 消息序列化、异步处理
mmap 接口 零拷贝 PDO 数据共享 页映射、访问同步
sysfs 接口 从站调试与监控 属性读写、权限管理
状态机模块 管理从站生命周期的状态 状态转换、错误恢复

第六部分 实时同步与性能优化

6.1 实时同步与性能优化概述

在 EtherCAT 驱动中,实时同步和性能优化是确保高精度控制的关键。分布式时钟(DC)技术使得所有从站能够同步到主站的时间基准,而性能优化则通过减少延迟、抖动和 CPU 占用来提高系统的整体响应速度。

6.1.1 实时同步架构

[主站] — 主站时钟 (Master Clock)
   ↓ (通过 EtherCAT 帧广播 DC 时间)
[从站1] — 从站时钟 (Slave Clock)
   ↓ (本地时间偏移)
[从站2] — 从站时钟
   ↓
[从站N] — 从站时钟
   ↓
[所有从站同步到同一个时间基准]

6.1.2 分布式时钟关键参数

参数 描述 典型值
Cycle Time 控制周期 1ms ~ 10ms
DC Offset 时钟偏移补偿 0 ~ 100 μs
Sync Window 同步窗口 10 ~ 50 μs
Sync Delay 同步延迟 1 ~ 10 μs
Jitter 抖动容忍值 < 1 μs

6.2 核心数据结构

6.2.1 分布式时钟结构

/**
 * @struct ethercat_dc
 * @brief 分布式时钟数据结构。
 */
struct ethercat_dc {
    uint64_t master_time;           /**< 主站时间 (纳秒) */
    uint64_t local_time;            /**< 本地时间 (纳秒) */
    int64_t time_offset;            /**< 时间偏移 (纳秒) */
    int32_t drift_rate;             /**< 漂移率 (ppm) */
    uint32_t cycle_time_ns;         /**< 周期时间 (纳秒) */
    uint32_t cycle_count;           /**< 周期计数 */
    uint32_t sync_window_ns;        /**< 同步窗口 (纳秒) */
    uint32_t sync_interval_ns;      /**< 同步间隔 (纳秒) */
    uint8_t sync_state;             /**< 同步状态: 0=未同步, 1=同步中, 2=已同步 */
    struct timer_list sync_timer;   /**< 同步定时器 */
    spinlock_t lock;                /**< 自旋锁 */
    struct work_struct sync_work;   /**< 同步工作队列 */
    void (*sync_notify)(struct ethercat_dc *dc, uint8_t state);
    void *notify_data;              /**< 通知数据 */
};
​
/**
 * @struct ethercat_perf_stats
 * @brief 性能统计数据结构。
 */
struct ethercat_perf_stats {
    uint64_t cycle_count;           /**< 总周期数 */
    uint64_t min_cycle_time_ns;     /**< 最小周期时间 (纳秒) */
    uint64_t max_cycle_time_ns;     /**< 最大周期时间 (纳秒) */
    uint64_t avg_cycle_time_ns;     /**< 平均周期时间 (纳秒) */
    uint64_t min_jitter_ns;         /**< 最小抖动 (纳秒) */
    uint64_t max_jitter_ns;         /**< 最大抖动 (纳秒) */
    uint64_t avg_jitter_ns;         /**< 平均抖动 (纳秒) */
    uint64_t missed_cycles;         /**< 丢失周期数 */
    uint64_t max_missed_cycles;     /**< 最大连续丢失周期数 */
    uint64_t dc_sync_success;       /**< DC 同步成功次数 */
    uint64_t dc_sync_fail;          /**< DC 同步失败次数 */
    uint64_t pdo_tx_count;          /**< 发送 PDO 计数 */
    uint64_t pdo_rx_count;          /**< 接收 PDO 计数 */
    uint64_t pdo_tx_error;          /**< 发送 PDO 错误数 */
    uint64_t pdo_rx_error;          /**< 接收 PDO 错误数 */
    uint64_t sdo_count;             /**< SDO 计数 */
    uint64_t sdo_error;             /**< SDO 错误数 */
};

6.3 核心代码实现

6.3.1 分布式时钟初始化

/**
 * @brief 分布式时钟初始化。
 * @param master 主站指针
 * @return 0 成功,-1 失败
 */
static int ec_dc_init(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    struct ethercat_dc *dc;
    uint64_t dc_time;
​
    // 1. 分配 DC 结构
    dc = kzalloc(sizeof(struct ethercat_dc), GFP_KERNEL);
    if (!dc) return -ENOMEM;
​
    // 2. 读取主站 DC 时间
    dc_time = ec_read_reg(hw->regs, 0x700);
    dc_time |= ((uint64_t)ec_read_reg(hw->regs, 0x704)) << 32;
​
    dc->master_time = dc_time;
    dc->local_time = dc_time;
    dc->time_offset = 0;
    dc->drift_rate = 0;
    dc->cycle_time_ns = master->cycle_time_us * 1000;
    dc->sync_window_ns = 10000;  // 10 μs
    dc->sync_interval_ns = 100000;  // 100 μs
    dc->sync_state = 0;
    spin_lock_init(&dc->lock);
    INIT_WORK(&dc->sync_work, ec_dc_sync_worker);
​
    // 3. 配置 DC 参数到硬件
    ec_write_reg(hw->regs, 0x708, dc->cycle_time_ns / 1000);  // 周期时间 (μs)
    ec_write_reg(hw->regs, 0x70C, dc->sync_window_ns / 1000);  // 同步窗口 (μs)
    ec_write_reg(hw->regs, 0x710, 0x00000001);  // 启用 DC
​
    // 4. 设置同步定时器
    timer_setup(&dc->sync_timer, ec_dc_sync_timer_callback, 0);
    dc->sync_timer.expires = jiffies + msecs_to_jiffies(dc->sync_interval_ns / 1000000);
    add_timer(&dc->sync_timer);
​
    master->dc = dc;
    return 0;
}
​
/**
 * @brief 分布式时钟同步工作队列。
 */
static void ec_dc_sync_worker(struct work_struct *work)
{
    struct ethercat_dc *dc = container_of(work, struct ethercat_dc, sync_work);
    struct ec_brainbox_priv *hw = container_of(dc, struct ethercat_master, dc)->hw_priv;
    uint64_t current_time, time_diff;
    uint32_t status;
​
    spin_lock(&dc->lock);
​
    // 1. 读取当前 DC 时间
    current_time = ec_read_reg(hw->regs, 0x700);
    current_time |= ((uint64_t)ec_read_reg(hw->regs, 0x704)) << 32;
​
    // 2. 计算时间偏移
    time_diff = current_time - dc->master_time;
    if (time_diff > 0x8000000000000000ULL) {
        time_diff = 0;
    }
​
    // 3. 检查是否在同步窗口内
    if (time_diff < dc->sync_window_ns) {
        dc->sync_state = 2;  // 已同步
        dc->master_time = current_time;
        dc->local_time = current_time;
        if (dc->sync_notify) {
            dc->sync_notify(dc, dc->sync_state);
        }
    } else {
        dc->sync_state = 1;  // 同步中
        // 计算漂移率并补偿
        dc->drift_rate = (int32_t)(time_diff * 1000000) / (int32_t)(dc->sync_interval_ns);
        dc->master_time = current_time - (dc->drift_rate * dc->sync_interval_ns) / 1000000;
        if (dc->sync_notify) {
            dc->sync_notify(dc, dc->sync_state);
        }
    }
​
    spin_unlock(&dc->lock);
}

6.3.2 周期性能统计

/**
 * @brief 更新性能统计。
 * @param stats 性能统计指针
 * @param cycle_time_ns 周期时间 (纳秒)
 */
static void ec_perf_update_stats(struct ethercat_perf_stats *stats,
                                 uint64_t cycle_time_ns)
{
    uint64_t jitter;
​
    // 1. 更新周期计数
    stats->cycle_count++;
​
    // 2. 更新最大/最小周期
    if (cycle_time_ns < stats->min_cycle_time_ns || stats->min_cycle_time_ns == 0) {
        stats->min_cycle_time_ns = cycle_time_ns;
    }
    if (cycle_time_ns > stats->max_cycle_time_ns) {
        stats->max_cycle_time_ns = cycle_time_ns;
    }
​
    // 3. 更新平均周期 (滑动平均)
    stats->avg_cycle_time_ns = (stats->avg_cycle_time_ns * (stats->cycle_count - 1) + cycle_time_ns) / stats->cycle_count;
​
    // 4. 计算抖动 (与平均值的偏差)
    jitter = (cycle_time_ns > stats->avg_cycle_time_ns) ? 
             cycle_time_ns - stats->avg_cycle_time_ns : 
             stats->avg_cycle_time_ns - cycle_time_ns;
​
    // 5. 更新抖动统计
    if (jitter < stats->min_jitter_ns || stats->min_jitter_ns == 0) {
        stats->min_jitter_ns = jitter;
    }
    if (jitter > stats->max_jitter_ns) {
        stats->max_jitter_ns = jitter;
    }
    stats->avg_jitter_ns = (stats->avg_jitter_ns * (stats->cycle_count - 1) + jitter) / stats->cycle_count;
​
    // 6. 检查是否丢失周期
    if (cycle_time_ns > stats->avg_cycle_time_ns * 2) {
        stats->missed_cycles++;
        stats->max_missed_cycles = max(stats->max_missed_cycles, 1);
    }
}
​
/**
 * @brief 获取性能统计信息 (通过 sysfs)。
 */
static ssize_t ec_perf_show_stats(struct device *dev,
                                  struct device_attribute *attr,
                                  char *buf)
{
    struct ethercat_device *ec_dev = dev_get_drvdata(dev);
    struct ethercat_master *master = ec_dev->master;
    struct ethercat_perf_stats *stats = master->perf_stats;
​
    return sprintf(buf,
                   "Cycle count: %llu\n"
                   "Min cycle: %llu ns\n"
                   "Max cycle: %llu ns\n"
                   "Avg cycle: %llu ns\n"
                   "Min jitter: %llu ns\n"
                   "Max jitter: %llu ns\n"
                   "Avg jitter: %llu ns\n"
                   "Missed cycles: %llu\n"
                   "Max missed cycles: %llu\n"
                   "DC sync success: %llu\n"
                   "DC sync fail: %llu\n"
                   "PDO TX: %llu\n"
                   "PDO RX: %llu\n"
                   "PDO TX Error: %llu\n"
                   "PDO RX Error: %llu\n"
                   "SDO: %llu\n"
                   "SDO Error: %llu\n",
                   stats->cycle_count,
                   stats->min_cycle_time_ns,
                   stats->max_cycle_time_ns,
                   stats->avg_cycle_time_ns,
                   stats->min_jitter_ns,
                   stats->max_jitter_ns,
                   stats->avg_jitter_ns,
                   stats->missed_cycles,
                   stats->max_missed_cycles,
                   stats->dc_sync_success,
                   stats->dc_sync_fail,
                   stats->pdo_tx_count,
                   stats->pdo_rx_count,
                   stats->pdo_tx_error,
                   stats->pdo_rx_error,
                   stats->sdo_count,
                   stats->sdo_error);
}

6.3.3 实时性优化 (CPU 隔离与中断亲和性)

/**
 * @brief 设置实时 CPU 亲和性。
 * @param master 主站指针
 * @param cpu 要绑定的 CPU 编号
 * @return 0 成功,-1 失败
 */
static int ec_set_cpu_affinity(struct ethercat_master *master, int cpu)
{
    cpu_set_t cpuset;
​
    // 1. 清除当前亲和性
    CPU_ZERO(&cpuset);
    CPU_SET(cpu, &cpuset);
​
    // 2. 设置当前进程的亲和性
    if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to set CPU affinity\n");
        return -1;
    }
​
    return 0;
}
​
/**
 * @brief 设置实时调度策略。
 * @param master 主站指针
 * @param priority 优先级 (1-99)
 * @return 0 成功,-1 失败
 */
static int ec_set_realtime_priority(struct ethercat_master *master, int priority)
{
    struct sched_param param;
    param.sched_priority = priority;
​
    // 1. 设置为 FIFO 实时调度策略
    if (sched_setscheduler(0, SCHED_FIFO, &param) < 0) {
        dev_err(&master->hw_priv->spi->dev, "Failed to set realtime priority\n");
        return -1;
    }
​
    return 0;
}
​
/**
 * @brief 配置中断亲和性。
 * @param master 主站指针
 * @param cpu 要绑定的 CPU 编号
 * @return 0 成功,-1 失败
 */
static int ec_set_irq_affinity(struct ethercat_master *master, int cpu)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    char aff_path[64];
    int ret;
​
    // 1. 构建中断亲和性文件路径
    snprintf(aff_path, sizeof(aff_path), "/proc/irq/%d/smp_affinity", hw->irq);
​
    // 2. 写入亲和性掩码
    char buf[16];
    snprintf(buf, sizeof(buf), "%x", (1 << cpu));
    ret = write_file(aff_path, buf, strlen(buf));
    if (ret < 0) {
        dev_err(&hw->spi->dev, "Failed to set IRQ affinity\n");
        return ret;
    }
​
    return 0;
}
​
/**
 * @brief 执行实时性优化配置。
 */
static int ec_configure_realtime(struct ethercat_master *master)
{
    int ret;
​
    // 1. 绑定到 CPU 1 (假设 CPU 0 用于非实时任务)
    ret = ec_set_cpu_affinity(master, 1);
    if (ret < 0) return ret;
​
    // 2. 设置实时优先级 99
    ret = ec_set_realtime_priority(master, 99);
    if (ret < 0) return ret;
​
    // 3. 绑定中断
    ret = ec_set_irq_affinity(master, 1);
    if (ret < 0) return ret;
​
    // 4. 锁定内存
    mlockall(MCL_CURRENT | MCL_FUTURE);
​
    dev_info(&master->hw_priv->spi->dev, "Real-time optimization configured\n");
    return 0;
}

6.3.4 中断处理优化 (NAPI 风格)

/**
 * @brief 中断处理优化 (使用 NAPI 风格)。
 */
static int ec_interrupt_handler_optimized(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    uint32_t int_status, int_mask;
​
    // 1. 读取中断状态
    int_status = ec_read_reg(hw->regs, 0x104);
    int_mask = ec_read_reg(hw->regs, 0x108);
​
    // 2. 如果没有中断,返回
    if (!(int_status & int_mask)) {
        return IRQ_NONE;
    }
​
    // 3. 清除中断标志
    ec_write_reg(hw->regs, 0x104, int_status);
​
    // 4. 处理 PDO 接收中断 (高优先级)
    if (int_status & 0x01) {
        // 批量处理所有 PDO 数据
        for (int i = 0; i < master->pdo_rx_count; i++) {
            ec_read_pdo_data(master, i);
        }
    }
​
    // 5. 处理 SDO 完成中断 (低优先级,通过工作队列)
    if (int_status & 0x02) {
        schedule_work(&master->sdo_work);
    }
​
    // 6. 处理错误中断 (低优先级)
    if (int_status & 0x04) {
        schedule_work(&master->error_work);
    }
​
    return IRQ_HANDLED;
}

6.3.5 零拷贝与 DMA 优化

/**
 * @brief DMA 缓冲区优化配置。
 * @param master 主站指针
 * @return 0 成功,-1 失败
 */
static int ec_dma_optimize(struct ethercat_master *master)
{
    struct ec_brainbox_priv *hw = master->hw_priv;
    struct page *tx_page, *rx_page;
    dma_addr_t tx_dma, rx_dma;
​
    // 1. 分配连续物理页
    tx_page = alloc_pages(GFP_KERNEL, 2);  // 4KB
    rx_page = alloc_pages(GFP_KERNEL, 2);  // 4KB
    if (!tx_page || !rx_page) {
        goto error;
    }
​
    // 2. 获取 DMA 地址
    tx_dma = dma_map_page(&hw->spi->dev, tx_page, 0, 4096, DMA_TO_DEVICE);
    rx_dma = dma_map_page(&hw->spi->dev, rx_page, 0, 4096, DMA_FROM_DEVICE);
    if (dma_mapping_error(&hw->spi->dev, tx_dma) ||
        dma_mapping_error(&hw->spi->dev, rx_dma)) {
        goto error;
    }
​
    // 3. 保存 DMA 信息
    hw->tx_dma_page = tx_page;
    hw->rx_dma_page = rx_page;
    hw->tx_dma_handle = tx_dma;
    hw->rx_dma_handle = rx_dma;
    hw->tx_dma_vaddr = page_address(tx_page);
    hw->rx_dma_vaddr = page_address(rx_page);
​
    // 4. 配置硬件 DMA 地址
    ec_write_reg(hw->regs, 0x200, lower_32_bits(tx_dma));
    ec_write_reg(hw->regs, 0x204, upper_32_bits(tx_dma));
    ec_write_reg(hw->regs, 0x208, lower_32_bits(rx_dma));
    ec_write_reg(hw->regs, 0x20C, upper_32_bits(rx_dma));
​
    return 0;
​
error:
    if (tx_page) __free_pages(tx_page, 2);
    if (rx_page) __free_pages(rx_page, 2);
    return -ENOMEM;
}

6.4 软件设计模式树形分析

实时同步与性能优化设计模式
├── 策略模式 (Strategy Pattern)
│   ├── 中断处理策略 (NAPI vs 传统)
│   ├── 同步算法策略 (硬件同步 vs 软件同步)
│   └── DMA 传输策略 (动态 vs 预分配)
├── 工厂模式 (Factory Pattern)
│   ├── 性能统计创建
│   └── DC 上下文创建
├── 单例模式 (Singleton Pattern)
│   └── 只有一个 DC 实例
├── 观察者模式 (Observer Pattern)
│   ├── DC 同步状态通知
│   └── 性能统计更新通知
├── 装饰器模式 (Decorator Pattern)
│   ├── 为 PDO 数据添加时间戳
│   └── 为 DMA 缓冲区添加对齐
├── 模板方法模式 (Template Method Pattern)
│   ├── 实时优化配置流程
│   └── 中断处理流程 (读状态 → 处理 → 清除)
└── 适配器模式 (Adapter Pattern)
    ├── 中断亲和性适配不同硬件平台
    └── DMA 适配不同平台的内存管理

6.5 实时同步与性能优化核心难点

6.5.1 周期抖动过大

现象:控制周期出现较大的抖动,影响控制精度。

原因

  1. CPU 负载波动。

  2. 内存页被换出。

  3. 中断被高优先级任务抢占。

解决方法

  1. 使用 CPU 隔离 (isolcpus=1,2)。

  2. 锁定内存页 (mlockall )。

  3. 使用 SCHED_FIFO 实时优先级。

6.5.2 DC 同步失败

现象:从站无法与主站保持时钟同步。

原因

  1. 网络延迟不一致。

  2. 从站时钟精度不足。

  3. 同步算法参数不当。

解决方法

  1. 使用硬件同步 (ET1100 DC 模式)。

  2. 调整同步窗口和间隔参数。

  3. 增加漂移补偿算法。

6.5.3 中断风暴

现象:中断频繁触发,CPU 占用率飙升。

原因

  1. 中断标志未清除。

  2. PDO 数据更新频繁。

  3. 硬件故障导致错误中断。

解决方法

  1. 确保中断处理中清除标志。

  2. 使用 NAPI 批量处理中断。

  3. 增加错误中断阈值。

6.5.4 DMA 缓冲区冲突

现象:DMA 数据出现错位或覆盖。

原因

  1. DMA 缓冲区被重复使用。

  2. 数据同步未处理好。

  3. 硬件 DMA 地址配置错误。

解决方法

  1. 使用双缓冲机制。

  2. 增加数据同步信号。

  3. 检查 DMA 地址配置。

6.6 与其他模块的协同

模块 协同方式 调试关键点
主站驱动 提供控制周期和 DC 时间基准 周期时间、DC 同步状态
从站驱动 接收 DC 同步信号,调整从站时钟 从站时钟偏差、同步状态
ioctl 接口 获取性能统计和 DC 状态 数据拷贝、权限控制
sysfs 接口 展示性能统计信息 数据更新、访问并发
netlink 接口 发送 DC 同步状态事件 事件序列化、异步处理
mmap 接口 零拷贝 DMA 数据共享 页映射、访问同步
中断管理 优化中断处理流程 中断频率、处理延迟
实时调度 配置实时优先级和 CPU 亲和性 调度策略、优先级

第七部分 内核中间件 API 设计与应用集成

7.1 中间件 API 设计概述

完整的 EtherCAT 主站驱动、从站驱动、实时同步和性能优化模块。如何将这些模块整合成一个统一、易用、高性能的中间件 API,供用户空间应用程序使用。

7.1.1 API 设计原则

原则 描述 实现方式
统一接口 单个设备文件,多个功能 通过 ioctl 命令区分
零拷贝 最小化数据复制 mmap 共享内存
异步通知 事件驱动 netlink + 信号
线程安全 支持多线程访问 读写锁 + 原子操作
向后兼容 保持 API 稳定 版本号检查

7.1.2 API 层次结构

[用户空间应用程序]
       ↓
+------------------------------------------+
| EtherCAT 中间件 API 层                    |
| - ec_open() / ec_close()                 |
| - ec_slave_scan() / ec_slave_list()      |
| - ec_pdo_read() / ec_pdo_write()         |
| - ec_sdo_read() / ec_sdo_write()         |
| - ec_state_control() / ec_state_get()    |
| - ec_dc_sync() / ec_perf_stats()         |
| - ec_register_callback()                 |
+------------------------------------------+
       ↓
[EtherCAT 内核驱动]
       ↓
[硬件 - ET1100]

7.2 核心数据结构

7.2.1 API 句柄结构

/**
 * @struct ec_handle
 * @brief EtherCAT 中间件 API 句柄。
 */
struct ec_handle {
    int fd;                             /**< 设备文件描述符 */
    uint32_t version;                   /**< API 版本号 */
    uint32_t master_id;                 /**< 主站 ID */
    struct ec_slave_info *slaves;       /**< 从站信息缓存 */
    int slave_count;                    /**< 从站数量 */
    void *mmap_base;                    /**< mmap 基地址 */
    size_t mmap_size;                   /**< mmap 大小 */
    struct ec_pdo_map *pdo_map;         /**< PDO 映射缓存 */
    int pdo_tx_count;                   /**< TxPDO 数量 */
    int pdo_rx_count;                   /**< RxPDO 数量 */
    pthread_mutex_t lock;               /**< 互斥锁 */
    struct ec_event_cb *callbacks;      /**< 回调函数列表 */
    int callback_count;                 /**< 回调数量 */
    int is_active;                      /**< 是否激活 */
    struct ec_perf_stats perf_stats;    /**< 性能统计缓存 */
    struct ec_dc_state dc_state;        /**< DC 状态缓存 */
};

7.2.2 回调注册结构

/**
 * @struct ec_event_cb
 * @brief 事件回调注册结构。
 */
struct ec_event_cb {
    enum ec_event_type {
        EC_EVENT_SLAVE_STATE_CHANGED = 0,  /**< 从站状态变化 */
        EC_EVENT_MASTER_STATE_CHANGED,     /**< 主站状态变化 */
        EC_EVENT_PDO_UPDATED,              /**< PDO 数据更新 */
        EC_EVENT_FAULT_OCCURRED,           /**< 故障发生 */
        EC_EVENT_DC_SYNC_CHANGED,          /**< DC 同步变化 */
        EC_EVENT_ERROR                     /**< 错误事件 */
    } type;                               /**< 事件类型 */
    void (*callback)(struct ec_handle *handle, void *data, void *user_data); /**< 回调函数 */
    void *user_data;                      /**< 用户数据 */
    struct ec_event_cb *next;             /**< 链表下一个 */
};

7.2.3 配置结构

/**
 * @struct ec_config
 * @brief EtherCAT 中间件配置结构。
 */
struct ec_config {
    uint32_t cycle_time_us;              /**< 控制周期 (微秒) */
    uint32_t dc_sync_window_us;          /**< DC 同步窗口 (微秒) */
    int realtime_priority;               /**< 实时优先级 */
    int cpu_affinity;                    /**< CPU 亲和性 */
    struct ec_pdo_map *pdo_tx_map;       /**< TxPDO 映射 */
    struct ec_pdo_map *pdo_rx_map;       /**< RxPDO 映射 */
    int pdo_tx_count;                    /**< TxPDO 数量 */
    int pdo_rx_count;                    /**< RxPDO 数量 */
    bool enable_netlink;                 /**< 启用 netlink */
    bool enable_sysfs;                   /**< 启用 sysfs */
    bool enable_procfs;                  /**< 启用 procfs */
};

7.3 核心代码实现 (用户空间 API)

7.3.1 初始化与打开

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
​
/**
 * @brief 打开 EtherCAT 设备。
 * @param device_path 设备路径 (如 "/dev/ethercat0")
 * @param config 配置结构 (可选)
 * @return 指向 ec_handle 结构,失败返回 NULL
 */
struct ec_handle *ec_open(const char *device_path, const struct ec_config *config)
{
    struct ec_handle *handle;
    struct ec_version version;
    int fd;
​
    // 1. 打开设备文件
    fd = open(device_path, O_RDWR);
    if (fd < 0) {
        perror("open");
        return NULL;
    }
​
    // 2. 分配 handle
    handle = malloc(sizeof(struct ec_handle));
    if (!handle) {
        close(fd);
        return NULL;
    }
    memset(handle, 0, sizeof(*handle));
    handle->fd = fd;
    pthread_mutex_init(&handle->lock, NULL);
​
    // 3. 获取版本信息
    if (ioctl(fd, EC_IOCTL_GET_VERSION, &version) < 0) {
        perror("ioctl GET_VERSION");
        goto error;
    }
    handle->version = version.major;
​
    // 4. 获取从站列表
    struct ec_slave_list list;
    if (ioctl(fd, EC_IOCTL_GET_SLAVE_LIST, &list) < 0) {
        perror("ioctl GET_SLAVE_LIST");
        goto error;
    }
    handle->slave_count = list.count;
    handle->slaves = malloc(list.count * sizeof(struct ec_slave_info));
    if (!handle->slaves) {
        goto error;
    }
    memcpy(handle->slaves, list.slaves, list.count * sizeof(struct ec_slave_info));
​
    // 5. 配置 PDO 映射 (如果提供)
    if (config) {
        handle->pdo_tx_count = config->pdo_tx_count;
        handle->pdo_rx_count = config->pdo_rx_count;
        handle->pdo_map = malloc((config->pdo_tx_count + config->pdo_rx_count) * sizeof(struct ec_pdo_map));
        if (!handle->pdo_map) {
            goto error;
        }
        memcpy(handle->pdo_map, config->pdo_tx_map, config->pdo_tx_count * sizeof(struct ec_pdo_map));
        memcpy(handle->pdo_map + config->pdo_tx_count, config->pdo_rx_map, config->pdo_rx_count * sizeof(struct ec_pdo_map));
    }
​
    // 6. mmap 共享内存
    handle->mmap_size = 4096;
    handle->mmap_base = mmap(NULL, handle->mmap_size, PROT_READ | PROT_WRITE,
                             MAP_SHARED, fd, 0);
    if (handle->mmap_base == MAP_FAILED) {
        perror("mmap");
        goto error;
    }
​
    // 7. 设置实时参数 (如果提供)
    if (config) {
        struct ec_rt_config rt_config = {
            .priority = config->realtime_priority,
            .cpu_affinity = config->cpu_affinity
        };
        ioctl(fd, EC_IOCTL_SET_RT_CONFIG, &rt_config);
    }
​
    // 8. 启用周期控制
    if (config && config->cycle_time_us > 0) {
        ioctl(fd, EC_IOCTL_START_CYCLE, config->cycle_time_us);
    }
​
    handle->is_active = 1;
    return handle;
​
error:
    if (handle->slaves) free(handle->slaves);
    if (handle->pdo_map) free(handle->pdo_map);
    if (handle->mmap_base) munmap(handle->mmap_base, handle->mmap_size);
    pthread_mutex_destroy(&handle->lock);
    free(handle);
    close(fd);
    return NULL;
}
​
/**
 * @brief 关闭 EtherCAT 设备。
 */
void ec_close(struct ec_handle *handle)
{
    if (!handle) return;
​
    // 1. 停止周期控制
    ioctl(handle->fd, EC_IOCTL_STOP_CYCLE, 0);
​
    // 2. 取消 mmap
    if (handle->mmap_base) {
        munmap(handle->mmap_base, handle->mmap_size);
    }
​
    // 3. 释放资源
    if (handle->slaves) free(handle->slaves);
    if (handle->pdo_map) free(handle->pdo_map);
​
    // 4. 关闭设备
    close(handle->fd);
​
    // 5. 释放 handle
    pthread_mutex_destroy(&handle->lock);
    free(handle);
}

7.3.2 从站控制 API

/**
 * @brief 获取从站列表。
 */
int ec_get_slave_list(struct ec_handle *handle, struct ec_slave_info **slaves)
{
    if (!handle || !slaves) return -EINVAL;
    *slaves = handle->slaves;
    return handle->slave_count;
}
​
/**
 * @brief 获取从站状态。
 */
int ec_get_slave_state(struct ec_handle *handle, uint16_t slave_id, uint8_t *state)
{
    if (!handle || !state) return -EINVAL;
    struct ec_slave_state_req req = { .slave_id = slave_id };
    if (ioctl(handle->fd, EC_IOCTL_GET_SLAVE_STATE, &req) < 0) {
        return -1;
    }
    *state = req.state;
    return 0;
}
​
/**
 * @brief 设置从站状态。
 */
int ec_set_slave_state(struct ec_handle *handle, uint16_t slave_id, uint8_t state)
{
    if (!handle) return -EINVAL;
    struct ec_slave_state_req req = { .slave_id = slave_id, .state = state };
    return ioctl(handle->fd, EC_IOCTL_SET_SLAVE_STATE, &req);
}
​
/**
 * @brief 读取 PDO 数据。
 */
int ec_pdo_read(struct ec_handle *handle, uint16_t slave_id, uint32_t pdo_index,
                void *data, size_t *len)
{
    if (!handle || !data || !len) return -EINVAL;
    struct ec_pdo_data req = {
        .slave_id = slave_id,
        .pdo_index = pdo_index,
        .data_len = *len
    };
    if (ioctl(handle->fd, EC_IOCTL_GET_PDO, &req) < 0) {
        return -1;
    }
    memcpy(data, req.data, req.data_len);
    *len = req.data_len;
    return 0;
}
​
/**
 * @brief 写入 PDO 数据。
 */
int ec_pdo_write(struct ec_handle *handle, uint16_t slave_id, uint32_t pdo_index,
                 const void *data, size_t len)
{
    if (!handle || !data) return -EINVAL;
    struct ec_pdo_data req = {
        .slave_id = slave_id,
        .pdo_index = pdo_index,
        .data_len = len
    };
    memcpy(req.data, data, len);
    return ioctl(handle->fd, EC_IOCTL_SET_PDO, &req);
}

7.3.3 SDO 控制 API

/**
 * @brief 读取 SDO 数据。
 */
int ec_sdo_read(struct ec_handle *handle, uint16_t slave_id,
                uint16_t index, uint8_t subindex,
                void *data, size_t *len)
{
    if (!handle || !data || !len) return -EINVAL;
    struct ec_sdo_request req = {
        .slave_id = slave_id,
        .index = index,
        .subindex = subindex,
        .data_len = *len
    };
    if (ioctl(handle->fd, EC_IOCTL_SDO_READ, &req) < 0) {
        return -1;
    }
    memcpy(data, req.data, req.data_len);
    *len = req.data_len;
    return 0;
}
​
/**
 * @brief 写入 SDO 数据。
 */
int ec_sdo_write(struct ec_handle *handle, uint16_t slave_id,
                 uint16_t index, uint8_t subindex,
                 const void *data, size_t len)
{
    if (!handle || !data) return -EINVAL;
    struct ec_sdo_request req = {
        .slave_id = slave_id,
        .index = index,
        .subindex = subindex,
        .data_len = len
    };
    memcpy(req.data, data, len);
    return ioctl(handle->fd, EC_IOCTL_SDO_WRITE, &req);
}

7.3.4 状态与 DC 控制 API

/**
 * @brief 获取主站状态。
 */
int ec_get_master_state(struct ec_handle *handle, uint32_t *state)
{
    if (!handle || !state) return -EINVAL;
    return ioctl(handle->fd, EC_IOCTL_GET_STATE, state);
}
​
/**
 * @brief 设置主站状态。
 */
int ec_set_master_state(struct ec_handle *handle, uint32_t state)
{
    if (!handle) return -EINVAL;
    return ioctl(handle->fd, EC_IOCTL_SET_STATE, state);
}
​
/**
 * @brief 获取 DC 时间。
 */
int ec_get_dc_time(struct ec_handle *handle, uint64_t *time)
{
    if (!handle || !time) return -EINVAL;
    return ioctl(handle->fd, EC_IOCTL_GET_DC_TIME, time);
}
​
/**
 * @brief 获取性能统计。
 */
int ec_get_perf_stats(struct ec_handle *handle, struct ec_perf_stats *stats)
{
    if (!handle || !stats) return -EINVAL;
    return ioctl(handle->fd, EC_IOCTL_GET_PERF_STATS, stats);
}

7.3.5 回调注册 API

/**
 * @brief 注册事件回调。
 */
int ec_register_callback(struct ec_handle *handle,
                         enum ec_event_type type,
                         void (*callback)(struct ec_handle *, void *, void *),
                         void *user_data)
{
    if (!handle || !callback) return -EINVAL;
    pthread_mutex_lock(&handle->lock);
​
    // 1. 创建回调结构
    struct ec_event_cb *cb = malloc(sizeof(struct ec_event_cb));
    if (!cb) {
        pthread_mutex_unlock(&handle->lock);
        return -ENOMEM;
    }
    cb->type = type;
    cb->callback = callback;
    cb->user_data = user_data;
    cb->next = handle->callbacks;
​
    // 2. 添加到链表
    handle->callbacks = cb;
    handle->callback_count++;
    pthread_mutex_unlock(&handle->lock);
    return 0;
}
​
/**
 * @brief 卸载回调。
 */
int ec_unregister_callback(struct ec_handle *handle, enum ec_event_type type,
                           void (*callback)(struct ec_handle *, void *, void *))
{
    if (!handle || !callback) return -EINVAL;
    pthread_mutex_lock(&handle->lock);
​
    struct ec_event_cb *prev = NULL;
    struct ec_event_cb *curr = handle->callbacks;
    while (curr) {
        if (curr->type == type && curr->callback == callback) {
            if (prev) {
                prev->next = curr->next;
            } else {
                handle->callbacks = curr->next;
            }
            free(curr);
            handle->callback_count--;
            pthread_mutex_unlock(&handle->lock);
            return 0;
        }
        prev = curr;
        curr = curr->next;
    }
    pthread_mutex_unlock(&handle->lock);
    return -ENOENT;
}

7.3.6 netlink 事件接收线程

/**
 * @brief netlink 事件接收线程。
 */
static void *ec_netlink_thread(void *arg)
{
    struct ec_handle *handle = (struct ec_handle *)arg;
    struct sockaddr_nl addr;
    char buffer[4096];
    int sock;
​
    // 1. 创建 netlink socket
    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
    if (sock < 0) {
        perror("socket");
        return NULL;
    }
​
    // 2. 绑定
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = 0;
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(sock);
        return NULL;
    }
​
    // 3. 循环接收事件
    while (handle->is_active) {
        int len = recv(sock, buffer, sizeof(buffer), 0);
        if (len < 0) continue;
​
        struct nlmsghdr *nlh = (struct nlmsghdr *)buffer;
        struct genlmsghdr *genl = nlmsg_data(nlh);
​
        // 4. 解析事件并触发回调
        pthread_mutex_lock(&handle->lock);
        struct ec_event_cb *cb = handle->callbacks;
        while (cb) {
            if (cb->type == genl->cmd) {
                cb->callback(handle, genl->data, cb->user_data);
            }
            cb = cb->next;
        }
        pthread_mutex_unlock(&handle->lock);
    }
​
    close(sock);
    return NULL;
}
​
/**
 * @brief 启动 netlink 接收线程。
 */
int ec_netlink_start(struct ec_handle *handle)
{
    if (!handle) return -EINVAL;
    pthread_t thread;
    pthread_create(&thread, NULL, ec_netlink_thread, handle);
    return 0;
}

7.3.7 应用集成示例 (用户空间)

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include "ethercat_api.h"
​
/**
 * @brief 用户空间应用程序示例。
 */
static volatile int running = 1;
​
static void signal_handler(int sig)
{
    running = 0;
}
​
static void on_slave_state_changed(struct ec_handle *handle, void *data, void *user_data)
{
    struct ec_slave_state_event *event = (struct ec_slave_state_event *)data;
    printf("Slave %d state changed: %d -> %d\n",
           event->slave_id, event->old_state, event->new_state);
}
​
static void on_pdo_updated(struct ec_handle *handle, void *data, void *user_data)
{
    // 使用 mmap 零拷贝读取 PDO 数据
    uint8_t *rx_data = (uint8_t *)handle->mmap_base;
    uint8_t *tx_data = (uint8_t *)handle->mmap_base + 2048;
    uint16_t slave_id = *(uint16_t *)user_data;
​
    // 处理接收数据
    for (int i = 0; i < 10; i++) {
        printf("Rx[%d] = 0x%02x\n", i, rx_data[i]);
    }
​
    // 构造发送数据
    for (int i = 0; i < 10; i++) {
        tx_data[i] = 0xAA + i;
    }
}
​
int main(int argc, char **argv)
{
    signal(SIGINT, signal_handler);
​
    // 1. 配置
    struct ec_config config = {
        .cycle_time_us = 1000,            // 1ms
        .dc_sync_window_us = 100,         // 100μs
        .realtime_priority = 99,          // 实时优先级
        .cpu_affinity = 1,                // 绑定到 CPU 1
        .pdo_tx_count = 10,
        .pdo_rx_count = 10,
        .enable_netlink = true,
        .enable_sysfs = true,
        .enable_procfs = true
    };
​
    // 2. 打开设备
    struct ec_handle *handle = ec_open("/dev/ethercat0", &config);
    if (!handle) {
        fprintf(stderr, "Failed to open EtherCAT device\n");
        return -1;
    }
​
    // 3. 注册回调
    ec_register_callback(handle, EC_EVENT_SLAVE_STATE_CHANGED,
                         on_slave_state_changed, NULL);
    ec_register_callback(handle, EC_EVENT_PDO_UPDATED,
                         on_pdo_updated, (void *)1);
​
    // 4. 启动 netlink 接收
    ec_netlink_start(handle);
​
    // 5. 查询从站列表
    struct ec_slave_info *slaves;
    int count = ec_get_slave_list(handle, &slaves);
    printf("Found %d slaves:\n", count);
    for (int i = 0; i < count; i++) {
        printf("  Slave %d: Vendor=0x%04x, Product=0x%04x, State=%d\n",
               slaves[i].slave_id, slaves[i].vendor_id,
               slaves[i].product_code, slaves[i].state);
    }
​
    // 6. 切换到 OP 状态
    ec_set_master_state(handle, EC_STATE_OP);
​
    // 7. 主循环
    while (running) {
        // 读取 PDO 数据 (mmap 零拷贝)
        uint8_t *rx_data = (uint8_t *)handle->mmap_base;
        // 处理数据...
​
        // 写入 PDO 数据 (mmap 零拷贝)
        uint8_t *tx_data = (uint8_t *)handle->mmap_base + 2048;
        // 更新数据...
​
        usleep(1000);
    }
​
    // 8. 清理
    ec_close(handle);
    return 0;
}

7.4 软件设计模式树形分析

内核中间件 API 设计与应用集成设计模式
├── 门面模式 (Facade Pattern)
│   └── ec_handle 统一管理所有功能
├── 工厂模式 (Factory Pattern)
│   ├── ec_open() 创建 handle
│   └── ec_register_callback() 创建回调
├── 单例模式 (Singleton Pattern)
│   └── 一个设备只有一个 handle
├── 观察者模式 (Observer Pattern)
│   ├── 回调注册和事件分发
│   └── netlink 事件通知
├── 代理模式 (Proxy Pattern)
│   ├── ec_handle 代理设备访问
│   └── mmap 代理共享内存
├── 适配器模式 (Adapter Pattern)
│   ├── ioctl 适配不同命令
│   └── 回调适配不同事件
├── 装饰器模式 (Decorator Pattern)
│   ├── 为 PDO 数据添加校验和
│   └── 为 SDO 请求添加重试
└── 策略模式 (Strategy Pattern)
    ├── 读取策略 (mmap vs ioctl)
    └── 同步策略 (polling vs event-driven)

7.5 API 设计与应用集成调试核心难点

7.5.1 API 版本不兼容

现象:应用程序编译时通过,运行时 ioctl 返回 -ENOTTY

原因

  1. 内核驱动版本与应用程序版本不匹配。

  2. ioctl 命令号冲突。

  3. 数据结构大小变化。

解决方法

  1. 在 API 中添加版本号检查。

  2. 使用 _IO 宏确保命令唯一。

  3. 使用灵活的数据结构 (如 size 字段)。

7.5.2 mmap 内存访问异常

现象:访问 mmap 映射的内存时发生段错误。

原因

  1. mmap 地址未对齐。

  2. 访问超出映射范围。

  3. mmap 未保护写权限。

解决方法

  1. 使用 PAGE_SIZE 对齐。

  2. 使用 PROT_READ | PROT_WRITE

  3. 检查 mmap 返回值。

7.5.3 多线程竞争

现象:多线程同时读写 PDO 数据时出现数据不一致。

原因

  1. 未使用互斥锁保护共享数据。

  2. 锁粒度过大导致性能下降。

  3. 死锁问题。

解决方法

  1. 使用读写锁 (pthread_rwlock_t)。

  2. 减小锁粒度 (每个 PDO 独立锁)。

  3. 使用 __sync_synchronize 原子操作。

7.5.4 实时性达标

现象:用户空间控制周期无法达到硬件实时性要求。

原因

  1. 系统调度延迟。

  2. 内存页被换出。

  3. 用户空间 API 调用开销过大。

解决方法

  1. 使用 sched_setscheduler 设置实时优先级。

  2. 锁定内存 (mlockall )。

  3. 使用 mmap 零拷贝,减少系统调用。

7.6 与其他模块的协同

模块 协同方式 调试关键点
主站驱动 通过 ioctl 和 mmap 提供硬件访问 设备文件、命令码
从站驱动 通过 ec_handle 管理从站状态 状态同步、数据映射
DC 同步 提供定时器和中断同步 实时性、抖动
性能统计 提供性能监控数据 数据准确、实时性
netlink 提供异步事件通知 事件丢失、延迟
sysfs/procfs 提供调试接口 权限、并发访问
用户空间 应用集成测试 API 设计、接口稳定性

从硬件芯片驱动到用户空间 API 的完整 EtherCAT 中间件体系。

层级 完成内容 技术要点
硬件层 ET1100 芯片驱动 PCI/Platform 驱动、寄存器操作、中断管理
协议层 EtherCAT 主站/从站状态机 PDO、SDO、DC 同步
通信层 用户-内核通信 ioctl、mmap、netlink、sysfs
优化层 实时性与性能优化 CPU 隔离、实时优先级、零拷贝
应用层 统一 API 设计 门面模式、回调注册、事件驱动

扩展展望:

  1. 多主站支持:支持多主站卡,每个主站独立管理。

  2. 冗余协议:支持 EtherCAT 冗余,提高可靠性。

  3. 与 ROS2 集成:提供 ros2_control 硬件接口适配。

  4. 容器化部署:支持 Docker/OCI 容器化运行。

  5. 热插拔支持:支持从站动态添加/移除。

  6. 安全审计:增加安全管理模块。

第八部分 内核EtherCAT中间件与开源中间件对比分析

8.1 回顾

从 EtherCAT 芯片驱动开发出发,逐步构建了一个完整的 Linux 内核驱动与用户空间中间件通信体系。以下是各部分的概要回顾:

序列 核心内容 技术要点
1 ET1100 芯片驱动框架 设备树解析、SPI 驱动、寄存器映射、中断处理
2 EtherCAT 主站核心实现 从站扫描、PDO 映射、SDO 传输、状态机管理
3 用户空间与内核通信 ioctl 命令设计、mmap 零拷贝、netlink 事件通知、sysfs/procfs 调试
4 从站驱动与状态机 从站初始化、PDO 配置、看门狗、故障恢复
5 实时同步与性能优化 分布式时钟 (DC)、周期抖动统计、CPU 隔离、实时优先级
6 中间件 API 设计与应用集成 统一 API 设计、回调注册、用户空间应用示例
7 核心设计模式分析 策略、观察者、工厂、门面等设计模式的应用

8.2 本次对话的核心主题

本次对话的核心讨论围绕着 EtherCAT 驱动开发中的两个层次 展开:

  1. 芯片驱动层 (Hardware Driver Layer):直接操作 ET1100 等硬件芯片,负责寄存器访问、中断处理、DMA 管理等。这部分通常运行在内核空间,要求高实时性和低延迟。

  2. 用户空间中间件层 (User-space Middleware Layer):运行在用户空间,通过 ioctl、mmap、netlink 等机制与内核驱动通信,向上层应用提供易用的 API。中间件层可以基于开源库(如 SOEM、IGH)构建,也可以自行开发。

这两层的关系可以用以下图示表示:

┌──────────────────────────────────────────────────────────────┐
│  用户空间应用层                                              │
│  ┌─────────────────────────────────────────────────────┐     │
│  │  应用代码 (C++/Python/ROS2)                       │     │
│  └─────────────────────────────────────────────────────┘     │
│                          ↓                                  │
│  ┌─────────────────────────────────────────────────────┐     │
│  │  用户空间中间件 (SOEM / IGH / pysoem)            │     │
│  │  - PDO/SDO 抽象层                                │     │
│  │  - 从站状态管理                                  │     │
│  │  - 事件回调机制                                  │     │
│  └─────────────────────────────────────────────────────┘     │
│                          ↓                                  │
│  ┌─────────────────────────────────────────────────────┐     │
│  │  内核通信接口 (ioctl / mmap / netlink)            │     │
│  └─────────────────────────────────────────────────────┘     │
│                          ↓                                  │
│  ┌─────────────────────────────────────────────────────┐     │
│  │  内核驱动层 (ET1100 驱动)                         │     │
│  │  - 寄存器操作                                     │     │
│  │  - 中断处理                                       │     │
│  │  - DMA 管理                                       │     │
│  └─────────────────────────────────────────────────────┘     │
│                          ↓                                  │
│  ┌─────────────────────────────────────────────────────┐     │
│  │  硬件层 (ET1100 芯片)                             │     │
│  └─────────────────────────────────────────────────────┘     │
└──────────────────────────────────────────────────────────────┘

8.3 开源 EtherCAT 用户空间中间件对比

三种主流的开源 EtherCAT 用户空间中间件对比分析:

8.3.1 SOEM (Simple Open EtherCAT Master)

特性 描述
开发者 OpenEtherCATsociety 社区
语言 C
实时性 中等 (用户空间)
代码复杂度
社区活跃度
典型应用 快速原型、教学、非实时控制
优势 轻量、易用、跨平台
劣势 实时性受 Linux 调度影响,不适用于硬实时场景

8.3.2 IGH (IgH EtherCAT Master)

特性 描述
开发者 Ingenieurbüro für Informationstechnik GmbH
语言 C
实时性 极高 (内核模块 + 实时补丁)
代码复杂度
社区活跃度 高 (工业界广泛使用)
典型应用 工业 PLC、高精度运动控制
优势 极强的实时性能、完整的工业级协议支持
劣势 配置复杂、学习曲线陡峭

8.3.3 pysoem

特性 描述
开发者 SOEM 社区 Python 绑定
语言 Python + C
实时性 中等 (依赖 SOEM 实时性)
代码复杂度 极低
社区活跃度 中等
典型应用 快速原型、测试自动化
优势 Python 易用性 + C 语言性能
劣势 实时性较差,不适合高频率控制

8.3.4 对比总结表

维度 SOEM IGH pysoem
实时性 中等 极高
易用性 极高
代码量 约 10k 行 约 50k 行 ~2k 行 (Python)
硬件支持 网卡 + 驱动 网卡 + 驱动 依赖 SOEM
社区支持 中等
推荐场景 原型开发、教学 工业控制、高精度运动 测试、快速迭代

8.4 如何选择适合的中间件

8.4.1 根据应用场景选择

应用场景 推荐中间件 理由
快速原型验证 SOEM / pysoem 快速上手,代码量少
工业实时控制 IGH 高实时性,工业级稳定性
嵌入式低资源设备 SOEM 轻量级,适合资源受限环境
ROS2 集成 SOEM + ros2_control 社区活跃,与 ROS2 集成容易
多语言集成 pysoem (Python) / SOEM (C++) 灵活的语言绑定

8.4.2 根据开发成本选择

成本维度 SOEM IGH
学习成本
开发时间
调试难度
维护成本

8.4.3 实时性需求分级

实时性需求 推荐方案 预期抖动
< 1 ms 内核模块 + IGH + PREEMPT-RT < 10 μs
1-5 ms SOEM + 实时内核 < 100 μs
> 5 ms SOEM + 标准内核 < 1 ms

8.5 本次对话的核心结论

  1. EtherCAT 驱动开发包含两个层次:内核空间芯片驱动和用户空间中间件层。两者需要协同工作才能构成完整的控制系统。

  2. 芯片驱动是基础:ET1100 等硬件芯片驱动负责底层的寄存器操作、中断处理和 DMA 管理,是高性能控制的基石。

  3. 中间件是桥梁:SOEM、IGH 等用户空间中间件提供了面向应用的 API,将复杂的 EtherCAT 协议抽象为简单易用的接口。

  4. 开源中间件已成熟:SOEM 和 IGH 是目前最成熟、最广泛使用的开源 EtherCAT 主站库,能够满足从快速原型到工业控制的各种需求。

  5. 选择取决于应用:实时性要求、开发时间、硬件资源、语言偏好等都是选择中间件时需要考虑的因素。

  6. 内核驱动 + 用户空间中间件 是构建工业级 EtherCAT 控制系统的标准模式。我的 ET1100 驱动 + SOEM/IGH 的组合可以形成一个完整的解决方案。

8.6 展望

随着工业 4.0 和智能制造的推进,EtherCAT 在机器人、数控机床、自动化生产线等领域的需求持续增长。未来趋势包括:

  • ROS2 与 EtherCAT 的深度集成ros2_control 框架正在成为机器人控制的事实标准,EtherCAT 作为其底层通信协议的重要性日益凸显。

  • 实时性与通用性的平衡:随着硬件性能和实时 Linux 技术的进步,用户空间中间件的实时性将进一步提升,减少对内核模块的依赖。

  • 边缘计算与 EtherCAT:边缘计算设备如 NVIDIA Jetson、树莓派等通过 EtherCAT 连接工业设备,正在形成新的应用模式。

  • 标准化与认证:EtherCAT 协议本身的标准化程度已经很高,未来将更多地关注驱动和中间件的标准化接口设计。

第九部分 EtherCAT 芯片选型与对比分析

9.1 芯片选型概述

在 EtherCAT 系统设计中,从站控制器芯片的选型直接影响系统的成本、性能、开发难度和供应链安全性。本部分将系统分析国际主流芯片与国产替代芯片,并提供详细的性能对比和选型建议。

9.1.1 EtherCAT 从站芯片架构分类

类型 代表芯片 特点 适用场景
独立从站控制器 ET1100 / ET1200 / LAN9252 功能完整,需要外接 MCU 灵活硬件设计,适用面广
集成 MCU 的从站 XMC4800 / Kinetis K系列 单芯片解决方案,降低 BOM 成本 中小批量、紧凑设计
集成到 SoC 的从站 AMIC110 / HPM6000 系列 高集成度,支持 Linux/RTOS 高端应用、复杂算法处理
FPGA 软核 EtherCAT IP Core 高度可定制,灵活性最高 特殊接口、超高吞吐量

9.1.2 芯片选型关键指标

指标 说明 重要性
协议支持 是否支持 CoE、FoE、EoE 等
PDO 数量 支持的 PDO 映射数量
DC 支持 是否支持分布式时钟 (Distributed Clock)
吞吐量 最大数据交换速率 (Mbps)
IO 数量 可用的 GPIO/IO 引脚数
温度范围 工业级 (-40°C ~ +85°C) 还是商业级
封装尺寸 引脚数和封装类型
开发工具 是否提供完善的 SDK 和文档

9.2 国际主流 EtherCAT 芯片分析

9.2.1 Beckhoff ET1100 / ET1200

参数 ET1100 ET1200
厂商 Beckhoff Automation Beckhoff Automation
封装 QFN-48 (7x7 mm) QFN-32 (5x5 mm)
引脚数 48 32
PDO 数量 32 个 TxPDO / 32 个 RxPDO 16 个 TxPDO / 16 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工) 100 Mbps (全双工)
IO 数量 16 个可配置 IO 8 个可配置 IO
温度范围 -40°C ~ +85°C -40°C ~ +85°C
EEPROM 外部 SPI EEPROM 外部 SPI EEPROM
PHY 接口 MII/RMII MII/RMII
典型功耗 约 150 mW 约 80 mW

9.2.2 Microchip (Microsemi) LAN9252

参数 LAN9252
厂商 Microchip Technology
封装 QFN-64 (9x9 mm) / VQFN-64
引脚数 64
PDO 数量 32 个 TxPDO / 32 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工)
IO 数量 16 个可配置 IO
温度范围 -40°C ~ +105°C
EEPROM 内部 EEPROM (8 KB)
PHY 接口 MII/RMII / SPI 接口
特点 集成 PHY,简化设计

9.2.3 Infineon XMC4800 (集成 MCU)

参数 XMC4800
厂商 Infineon Technologies
封装 LQFP-100 / LQFP-144
引脚数 100 / 144
CPU ARM Cortex-M4 @ 144 MHz
PDO 数量 32 个 TxPDO / 32 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工)
IO 数量 75 个可配置 IO
温度范围 -40°C ~ +85°C
EEPROM 内部 Flash (256 KB)
PHY 接口 MII/RMII
特点 单芯片方案,无需外部 MCU

9.2.4 TI AMIC110 (集成处理器)

参数 AMIC110
厂商 Texas Instruments
封装 BGA-324 (17x17 mm)
引脚数 324
CPU ARM Cortex-A8 @ 300 MHz
PDO 数量 32 个 TxPDO / 32 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工)
IO 数量 可配置,约 100+ IO
温度范围 -40°C ~ +85°C
EEPROM 内部 Flash (64 KB) + 外部扩展
PHY 接口 MII/RMII/RGMII
特点 支持 Linux 操作系统,适合高性能应用

9.2.5 NXP Kinetis K系列 (集成 MCU)

参数 Kinetis K系列
厂商 NXP Semiconductors
封装 LQFP-64 / LQFP-100
引脚数 64 / 100
CPU ARM Cortex-M4 @ 100-150 MHz
PDO 数量 16 个 TxPDO / 16 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工)
IO 数量 40-70 个可配置 IO
温度范围 -40°C ~ +85°C
EEPROM 内部 Flash (256 KB)
PHY 接口 MII/RMII
特点 中等性能,性价比平衡

9.3 国产 EtherCAT 芯片分析

9.3.1 国产芯片发展现状

近年来,随着国产替代需求的增长,国内已有多家厂商推出 EtherCAT 从站芯片或 IP 核。以下是主要的国产芯片及厂商:

厂商 芯片型号 类型 制程 状态
上海先楫半导体 HPM 系列 (HPM6300 / HPM6400) 集成 MCU (RISC-V) 28nm 已量产
北京芯愿景 ECOC-1000 独立从站控制器 40nm 已量产
苏州国芯 EC-100 独立从站控制器 55nm 已量产
南京沁恒微 CH58x 系列 集成 MCU (RISC-V) 28nm 已量产
深圳云数 EC6000 独立从站控制器 40nm 样品阶段

9.3.2 上海先楫 HPM6300 / HPM6400 系列

参数 HPM6300 HPM6400
厂商 上海先楫半导体 上海先楫半导体
封装 QFN-64 / LQFP-100 LQFP-144 / BGA-196
引脚数 64 / 100 144 / 196
CPU RISC-V 双核 @ 1 GHz RISC-V 双核 @ 1 GHz
PDO 数量 32 个 TxPDO / 32 个 RxPDO 32 个 TxPDO / 32 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工) 100 Mbps (全双工)
IO 数量 50+ 可配置 IO 80+ 可配置 IO
温度范围 -40°C ~ +85°C -40°C ~ +85°C
EEPROM 内部 Flash (1 MB) 内部 Flash (2 MB)
PHY 接口 MII/RMII MII/RMII
特点 高性能 RISC-V 平台,国产化程度高 高集成度,支持 Linux/RTOS

9.3.3 北京芯愿景 ECOC-1000

参数 ECOC-1000
厂商 北京芯愿景
封装 QFN-48 (7x7 mm)
引脚数 48
PDO 数量 32 个 TxPDO / 32 个 RxPDO
DC 支持
最大吞吐量 100 Mbps (全双工)
IO 数量 16 个可配置 IO
温度范围 -40°C ~ +85°C
EEPROM 外部 SPI EEPROM
PHY 接口 MII/RMII
特点 引脚兼容 ET1100,可直接替代

9.4 芯片详细对比表

9.4.1 独立从站控制器对比

参数 ET1100 LAN9252 ECOC-1000
厂商 Beckhoff Microchip 芯愿景
封装 QFN-48 (7x7) QFN-64 (9x9) QFN-48 (7x7)
引脚数 48 64 48
PDO 数量 32/32 32/32 32/32
DC 支持
IO 数量 16 16 16
EEPROM 外部 SPI 内部 8KB 外部 SPI
PHY 接口 MII/RMII MII/RMII/SPI MII/RMII
温度范围 -40~85°C -40~105°C -40~85°C
功耗 150 mW 180 mW 140 mW
封装尺寸 7x7 mm 9x9 mm 7x7 mm

9.4.2 集成 MCU 的 EtherCAT 芯片对比

参数 XMC4800 Kinetis K HPM6300
厂商 Infineon NXP 先楫半导体
封装 LQFP-100 LQFP-64 QFN-64
引脚数 100 64 64
CPU Cortex-M4 @144MHz Cortex-M4 @100MHz RISC-V 双核 @1GHz
Flash 256 KB 256 KB 1 MB
RAM 80 KB 64 KB 512 KB
PDO 数量 32/32 16/16 32/32
DC 支持
IO 数量 75 40 50+
封装尺寸 14x14 mm 10x10 mm 9x9 mm
特点 工业级稳定 性价比 高性能 RISC-V

9.5 芯片选型建议

9.5.1 按应用场景选型

应用场景 推荐芯片 理由
低成本 IO 模块 ET1200 / ECOC-1000 小封装,IO 适中,成本低
高性能伺服驱动器 ET1100 / LAN9252 + 高性能 MCU 大量 PDO 需求,需外接 MCU
紧凑型一体化设备 XMC4800 / HPM6300 单芯片方案,集成度高
复杂算法处理 AMIC110 / HPM6400 支持 Linux/RTOS,处理能力强
国产化需求 ECOC-1000 / HPM6300 国产芯片,供应链安全

9.5.2 按性能指标选型

需求 推荐芯片 说明
最高吞吐量 ET1100 / LAN9252 / HPM6400 均支持 100 Mbps
最多 PDO ET1100 / LAN9252 / HPM6400 32/32 PDO 配置
最小封装 ET1200 (5x5 mm) 适合小型模块
最佳集成度 AMIC110 / HPM6400 集成 CPU + EtherCAT + 外设
最佳实时性 XMC4800 / HPM6300 集成 MCU,低延迟

9.6 国产芯片替代分析

9.6.1 可替代的国产芯片

国际芯片 国产替代 替换难度 注意事项
ET1100 ECOC-1000 引脚兼容,无需修改 PCB
LAN9252 暂无完全兼容 需重新设计硬件
XMC4800 HPM6300 性能更高,代码需移植
Kinetis K CH58x RISC-V 平台,工具链不同

9.6.2 国产芯片优势

  1. 供应链安全:不受出口管制影响

  2. 成本优势:通常比国际芯片低 20-40%

  3. 技术支持:国内厂商提供本地化技术支持

  4. 定制化:可按客户需求定制功能

9.6.3 国产芯片挑战

  1. 工具链生态:RISC-V 工具链相对 ARM 不成熟

  2. 社区支持:开源社区资源较少

  3. 长期供应:部分厂商规模较小,供应稳定性需评估

9.7 芯片选型总结

维度 国际芯片 国产芯片
性能 稳定可靠 部分型号已达到国际水平
生态 成熟 正在快速追赶
成本 较高 较低
供应 受地缘政治影响 自主可控
开发难度 工具链成熟 需适应新平台

最终建议

  • 工业级稳定需求:首选 ET1100 / XMC4800

  • 成本敏感型产品:ECOC-1000 / CH58x

  • 国产化重点项目:HPM6300 / ECOC-1000

  • 高性能需求:AMIC110 / HPM6400

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐