【IgH EtherCAT】 基于IgH EtherCAT主站的Linux内核模块,实现了一个最小化的EtherCAT测试环境

这是一个基于IgH EtherCAT主站的Linux内核模块,实现了一个最小化的EtherCAT测试环境。主要功能和特点包括:

核心功能:

  1. EtherCAT主站管理

     - 创建和管理EtherCAT主站实例

  2. 从站设备支持

     - 支持Beckhoff的EL3152(模拟输入)、EL4102(模拟输出)、EL2004(数字输出)模块

  3. 实时周期任务

     - 100Hz的实时数据交换循环

  4. 过程数据管理

     - PDO(过程数据对象)的注册和处理

  5. 状态监控

     - 主站、域和从站配置状态的实时监控

技术架构:

  • 定时器驱动

     - 使用Linux内核定时器实现精确的周期性任务

  • 回调机制

     - 通过发送和接收回调函数实现异步数据处理

  • 内存管理

     - 支持内部和外部过程数据内存管理

  • 线程安全

     - 使用自旋锁保护关键代码段

可选功能:

  • PDO配置

     - 可配置的过程数据对象映射

  • SDO访问

     - 服务数据对象的读写操作

  • VoE访问

     - 以太网供应商特定数据的处理

  • 外部内存

     - 自定义内存分配管理

数据流程:

每个10ms周期内执行:接收数据 → 处理数据 → 状态检查 → 计算输出 → 写入数据 → 发送数据,实现了完整的EtherCAT实时通信循环。

这个程序为EtherCAT应用开发提供了一个简洁而完整的参考实现。

这个 mini.c 文件是一个功能全面且高度可配置的 Linux 内核模块,是 IgH EtherCAT Master 项目中一个核心的、教科书式的示例。它演示了如何在内核空间实现一个完整的、软实时的 EtherCAT 控制器,并通过宏开关展示了多种基本和高级功能。

核心架构与功能:

  1. 内核模块形态:这是一个标准的 Linux 内核模块,通过 insmod 加载,rmmod 卸载。所有逻辑都在内核空间执行。

  2. 软实时周期驱动 - 内核定时器

  • 与 tty.c 示例类似,它使用标准的 Linux 内核定时器 (struct timer_list) 作为周期性任务的驱动源。

  • 通过在 cyclic_task 回调函数末尾重新武装定时器的方式,实现 100 Hz 的周期性调用。

  • 这提供的是软实时保证,其精度和抖动受限于内核时钟节拍(HZ)和系统负载。

  • 并发保护 - 自旋锁

    • 与使用信号量的 tty.c 示例不同,此模块使用了自旋锁 (spinlock_t) 来保护对共享 EtherCAT 主站资源的访问。

    • 自旋锁是一种更轻量级的锁,适用于中断上下文(如定时器回调函数)和多核处理器环境。当锁被占用时,尝试获取锁的 CPU 会“自旋”等待,而不是像信号量那样让任务睡眠。这在锁持有时间极短的情况下效率更高。

  • 高度可配置的特性 (通过宏)

    • 当为 1 时,模块会调用 ecrt_domain_size() 计算出域所需的总内存大小,然后通过 kmalloc() 自己分配这块内存,并用 ecrt_domain_external_memory() 将其注册给主站。

    • 当为 0 时,模块会调用 ecrt_domain_data() 来获取由 IgH 主站库内部管理的过程数据内存指针。

    • CONFIGURE_PDOS

      : 控制是否执行详细的 PDO 配置。关闭它可能意味着依赖从站的默认 PDO 映射。

    • EXTERNAL_MEMORY

      : 这是一个重要的特性演示。

    • SDO_ACCESS

      : 演示了如何创建和使用 SDO 请求对象 (ec_sdo_request_t) 来异步读写从站对象字典中的非过程数据。read_sdo() 函数展示了处理 SDO 请求状态机的典型方法。

    • VOE_ACCESS

      : 演示了如何通过 VoE (Vendor specific profile over EtherCAT) 协议与支持该协议的从站进行通信,这通常用于处理更复杂的、厂商特定的配置和数据。

  • 生命周期管理

    • 加载 (insmod)

      : 执行 init_mini_module。此函数完成所有配置,包括根据宏开关选择性地配置 PDO、创建 SDO/VoE 请求、分配外部内存等,最后激活主站并启动内核定时器。

    • 运行

      : 内核定时器周期性地触发 cyclic_task,执行 EtherCAT I/O、应用逻辑(闪烁),并根据宏开关执行 SDO/VoE 的周期性读取。

    • 卸载 (rmmod)

      : 执行 cleanup_mini_module。安全地停止定时器,释放可能由模块分配的外部内存,最后释放主站资源。

    总结mini.c 是一个功能丰富的软实时内核空间 EtherCAT 控制器模板。它不仅展示了基本的周期性 I/O 控制,还通过可配置的宏开关,系统地演示了 IgH EtherCAT Master 库提供的多种高级功能,如不同的 PDO 配置策略、内存管理模式以及重要的非周期性通信机制(SDO 和 VoE)。对于学习和理解 IgH Master 在内核空间中的各种用法,这是一个极具价值的参考实现。

    #include <linux/version.h> // 包含 Linux 内核版本信息头文件,用于条件编译#include <linux/module.h> // 包含 Linux 内核模块编程所需的核心头文件#include <linux/timer.h> // 包含内核定时器(jiffies, timer_list)相关的头文件#include <linux/interrupt.h> // 包含中断处理相关的头文件#include <linux/err.h> // 包含内核错误处理相关的头文件,如 IS_ERR#include <linux/slab.h> // 包含内核内存分配器(slab allocator)的头文件,如 kmalloc, kfree#include <linux/spinlock.h> // 包含自旋锁相关的头文件,用于中断上下文安全的锁定#include "../../include/ecrt.h" // EtherCAT realtime interface // 包含 IgH EtherCAT 主站的实时接口头文件/****************************************************************************/ // 分隔注释// Module parameters // 区域注释:模块参数#define FREQUENCY 100 // 宏定义:周期性任务的频率为 100 Hz// Optional features // 区域注释:可选特性#define CONFIGURE_PDOS  1 // 宏定义:启用 PDO 配置代码块#define EL3152_ALT_PDOS 0 // 宏定义:为 EL3152 从站选择备用 PDO 配置 (0 表示不使用)#define EXTERNAL_MEMORY 1 // 宏定义:启用外部内存管理模式#define SDO_ACCESS      0 // 宏定义:启用 SDO 访问示例 (0 表示不启用)#define VOE_ACCESS      0 // 宏定义:启用 VoE 访问示例 (0 表示不启用)#define PFX "ec_mini: " // 宏定义:内核日志消息的前缀,方便调试/****************************************************************************/ // 分隔注释// EtherCAT // 区域注释:EtherCAT 相关全局变量static ec_master_t *master = NULL; // 声明一个静态的 EtherCAT 主站对象指针static ec_master_state_t master_state = {}; // 声明一个静态的主站状态结构体变量static spinlock_t master_spinlock; // 声明一个自旋锁,用于保护对主站对象的并发访问(中断安全)static ec_domain_t *domain1 = NULL; // 声明一个静态的 EtherCAT 过程数据域指针static ec_domain_state_t domain1_state = {}; // 声明一个静态的域状态结构体变量static ec_slave_config_t *sc_ana_in = NULL; // 声明一个静态的从站配置对象指针,用于模拟量输入从站static ec_slave_config_state_t sc_ana_in_state = {}; // 声明一个静态的从站配置状态结构体变量// Timer // 区域注释:定时器static struct timer_list timer; // 声明一个内核定时器结构体变量/****************************************************************************/ // 分隔注释// process data // 区域注释:过程数据static uint8_t *domain1_pd; // process data memory // 声明一个指向过程数据内存区域的指针#define AnaInSlavePos  0, 2 // 宏定义:模拟量输入从站的位置#define AnaOutSlavePos 0, 1 // 宏定义:模拟量输出从站的位置#define DigOutSlavePos 0, 3 // 宏定义:数字量输出从站的位置#define Beckhoff_EL2004 0x00000002, 0x07D43052 // 宏定义:倍福 EL2004 的厂商/产品ID#define Beckhoff_EL3152 0x00000002, 0x0c503052 // 宏定义:倍福 EL3152 的厂商/产品ID#define Beckhoff_EL4102 0x00000002, 0x10063052 // 宏定义:倍福 EL4102 的厂商/产品ID// offsets for PDO entries // 注释:PDO条目的偏移量static unsigned int off_ana_in; // 静态无符号整型,存储模拟量输入值的偏移量static unsigned int off_ana_out; // 静态无符号整型,存储模拟量输出的偏移量static unsigned int off_dig_out; // 静态无符号整型,存储数字量输出的偏移量static const ec_pdo_entry_reg_t domain1_regs[] = { // 定义一个静态常量数组,用于注册需要映射到域的PDO条目#if EL3152_ALT_PDOS // 如果启用 EL3152 备用 PDO 配置    {AnaInSlavePos,  Beckhoff_EL3152, 0x6401, 1, &off_ana_in}, // 注册备用 PDO#else // 否则    {AnaInSlavePos,  Beckhoff_EL3152, 0x3101, 2, &off_ana_in}, // 注册标准 PDO (值)#endif // 结束 #if    {AnaOutSlavePos, Beckhoff_EL4102, 0x3001, 1, &off_ana_out}, // 注册 EL4102 的输出 PDO    {DigOutSlavePos, Beckhoff_EL2004, 0x3001, 1, &off_dig_out}, // 注册 EL2004 的输出 PDO    {} // 数组结束标志};static unsigned int counter = 0; // 静态无符号整型,用作通用计数器static unsigned int blink = 0; // 静态无符号整型,用作闪烁标志/****************************************************************************/ // 分隔注释#if CONFIGURE_PDOS // 如果定义了 CONFIGURE_PDOS 宏 (启用 PDO 配置)// Analog in -------------------------- // 区域注释:模拟量输入从站(EL3152)的配置数据static ec_pdo_entry_info_t el3152_pdo_entries[] = { // 定义 EL3152 的 PDO 条目信息    {0x3101, 1,  8}, // channel 1 status (通道1 状态,8位)    {0x3101, 2, 16}, // channel 1 value (通道1 值,16位)    {0x3102, 1,  8}, // channel 2 status (通道2 状态,8位)    {0x3102, 2, 16}, // channel 2 value (通道2 值,16位)    {0x6401, 1, 16}, // channel 1 value (alt.) (备用通道1 值,16位)    {0x6401, 2, 16}  // channel 2 value (alt.) (备用通道2 值,16位)};#if EL3152_ALT_PDOS // 如果启用 EL3152 备用 PDO 配置static ec_pdo_info_t el3152_pdos[] = { // 定义备用的 PDO 信息    {0x1A10, 2, el3152_pdo_entries + 4}, // 仅包含两个备用值条目};static ec_sync_info_t el3152_syncs[] = { // 定义备用的同步管理器配置    {2, EC_DIR_OUTPUT}, // SM2    {3, EC_DIR_INPUT, 1, el3152_pdos}, // SM3 关联1个PDO    {0xff} // 结束标志};#else // 否则,使用标准配置static ec_pdo_info_t el3152_pdos[] = { // 定义标准的 PDO 信息    {0x1A00, 2, el3152_pdo_entries}, // TxPDO 0x1A00 (状态+值)    {0x1A01, 2, el3152_pdo_entries + 2}  // TxPDO 0x1A01 (状态+值)};static ec_sync_info_t el3152_syncs[] = { // 定义标准的同步管理器配置    {2, EC_DIR_OUTPUT}, // SM2    {3, EC_DIR_INPUT, 1, el3152_pdos}, // SM3 关联1个PDO (此处可能应为2个,或者只用第一个)    {0xff} // 结束标志};#endif // 结束 #if// Analog out ------------------------- // 区域注释:模拟量输出从站(EL4102)的配置数据static ec_pdo_entry_info_t el4102_pdo_entries[] = { // 定义 EL4102 的 PDO 条目信息    {0x3001, 1, 16}, // channel 1 value (通道1 值,16位)    {0x3002, 1, 16}, // channel 2 value (通道2 值,16位)};static ec_pdo_info_t el4102_pdos[] = { // 定义 EL4102 的 PDO 信息    {0x1600, 1, el4102_pdo_entries}, // RxPDO 0x1600    {0x1601, 1, el4102_pdo_entries + 1} // RxPDO 0x1601};static ec_sync_info_t el4102_syncs[] = { // 定义 EL4102 的同步管理器配置    {2, EC_DIR_OUTPUT, 2, el4102_pdos}, // SM2 是输出SM,关联2个PDO    {3, EC_DIR_INPUT}, // SM3 是空的输入SM    {0xff} // 结束标志};// Digital out ------------------------ // 区域注释:数字量输出从站(EL2004)的配置数据static ec_pdo_entry_info_t el2004_channels[] = { // 定义 EL2004 的 PDO 条目信息    {0x3001, 1, 1}, // Value 1    {0x3001, 2, 1}, // Value 2    {0x3001, 3, 1}, // Value 3    {0x3001, 4, 1}  // Value 4};static ec_pdo_info_t el2004_pdos[] = { // 定义 EL2004 的 PDO 信息    {0x1600, 1, &el2004_channels[0]}, // RxPDO 0x1600    {0x1601, 1, &el2004_channels[1]}, // RxPDO 0x1601    {0x1602, 1, &el2004_channels[2]}, // RxPDO 0x1602    {0x1603, 1, &el2004_channels[3]}  // RxPDO 0x1603};static ec_sync_info_t el2004_syncs[] = { // 定义 EL2004 的同步管理器配置    {0, EC_DIR_OUTPUT, 4, el2004_pdos}, // SM0 是输出SM,关联4个PDO    {1, EC_DIR_INPUT}, // SM1 是空的输入SM    {0xff} // 结束标志};#endif // 结束 #if CONFIGURE_PDOS/****************************************************************************/ // 分隔注释#if SDO_ACCESS // 如果启用 SDO 访问static ec_sdo_request_t *sdo; // 声明一个 SDO 请求对象指针#endif // 结束 #if#if VOE_ACCESS // 如果启用 VoE 访问static ec_voe_handler_t *voe; // 声明一个 VoE 处理器指针#endif // 结束 #if/****************************************************************************/ // 分隔注释// 函数原型声明void check_domain1_state(void); // 检查域状态void check_master_state(void); // 检查主站状态void check_slave_config_states(void); // 检查从站配置状态#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) // 根据内核版本声明定时器回调函数void cyclic_task(struct timer_list *); // 新版本内核#elsevoid cyclic_task(unsigned long); // 老版本内核#endifvoid send_callback(void *); // 发送回调函数void receive_callback(void *); // 接收回调函数int init_mini_module(void); // 模块初始化函数void cleanup_mini_module(void); // 模块退出函数/****************************************************************************/ // 分隔注释void check_domain1_state(void) // 定义检查域状态的函数{    ec_domain_state_t ds; // 声明一个局部的域状态结构体变量    spin_lock(&master_spinlock); // 获取自旋锁(保护共享资源)    ecrt_domain_state(domain1, &ds); // 调用 ecrt API 获取 domain1 的当前状态    spin_unlock(&master_spinlock); // 释放自旋锁    if (ds.working_counter != domain1_state.working_counter) // 比较当前工作计数器(WC)与上次记录的WC        printk(KERN_INFO PFX "Domain1: WC %u.\n", ds.working_counter); // 如果不一致,打印新的WC值    if (ds.wc_state != domain1_state.wc_state) // 比较当前工作计数器状态与上次记录的状态        printk(KERN_INFO PFX "Domain1: State %u.\n", ds.wc_state); // 如果不一致,打印新的WC状态    domain1_state = ds; // 将当前状态赋值给全局变量,用于下次比较}/****************************************************************************/ // 分隔注释void check_master_state(void) // 定义检查主站状态的函数{    ec_master_state_t ms; // 声明一个局部的主站状态结构体变量    spin_lock(&master_spinlock); // 获取自旋锁    ecrt_master_state(master, &ms); // 调用 ecrt API 获取主站的当前状态    spin_unlock(&master_spinlock); // 释放自旋锁    if (ms.slaves_responding != master_state.slaves_responding) // 比较当前响应的从站数量        printk(KERN_INFO PFX "%u slave(s).\n", ms.slaves_responding); // 如果不一致,打印新的从站数量    if (ms.al_states != master_state.al_states) // 比较当前应用层(AL)状态        printk(KERN_INFO PFX "AL states: 0x%02X.\n", ms.al_states); // 如果不一致,以十六进制格式打印新的AL状态    if (ms.link_up != master_state.link_up) // 比较当前链路连接状态        printk(KERN_INFO PFX "Link is %s.\n", ms.link_up ? "up" : "down"); // 如果不一致,打印链路状态    master_state = ms; // 将当前状态赋值给全局变量,用于下次比较}/****************************************************************************/ // 分隔注释void check_slave_config_states(void) // 定义检查从站配置状态的函数{    ec_slave_config_state_t s; // 声明一个局部的从站配置状态结构体变量    spin_lock(&master_spinlock); // 获取自旋锁    ecrt_slave_config_state(sc_ana_in, &s); // 调用 ecrt API 获取模拟量输入从站的当前配置状态    spin_unlock(&master_spinlock); // 释放自旋锁    if (s.al_state != sc_ana_in_state.al_state) // 比较当前应用层状态        printk(KERN_INFO PFX "AnaIn: State 0x%02X.\n", s.al_state); // 如果不一致,打印新的AL状态    if (s.online != sc_ana_in_state.online) // 比较当前在线状态        printk(KERN_INFO PFX "AnaIn: %s.\n", s.online ? "online" : "offline"); // 如果不一致,打印在线/离线状态    if (s.operational != sc_ana_in_state.operational) // 比较当前操作状态        printk(KERN_INFO PFX "AnaIn: %soperational.\n", // 如果不一致,打印是否进入操作状态                s.operational ? "" : "Not ");    sc_ana_in_state = s; // 将当前状态赋值给全局变量,用于下次比较}/****************************************************************************/ // 分隔注释#if SDO_ACCESS // 如果启用 SDO 访问void read_sdo(void) // 定义读取 SDO 的函数{    switch (ecrt_sdo_request_state(sdo)) { // 检查 SDO 请求的状态        case EC_REQUEST_UNUSED: // request was not used yet // 状态:未使用            ecrt_sdo_request_read(sdo); // trigger first read // 触发第一次读取            break; // 结束 switch        case EC_REQUEST_BUSY: // 状态:忙碌            printk(KERN_INFO PFX "Still busy...\n"); // 打印忙碌信息            break; // 结束 switch        case EC_REQUEST_SUCCESS: // 状态:成功            printk(KERN_INFO PFX "SDO value: 0x%04X\n", // 打印读取到的 SDO 值                    EC_READ_U16(ecrt_sdo_request_data(sdo))); // 从 SDO 请求缓冲区读取16位数据            ecrt_sdo_request_read(sdo); // trigger next read // 触发下一次读取            break; // 结束 switch        case EC_REQUEST_ERROR: // 状态:错误            printk(KERN_INFO PFX "Failed to read SDO!\n"); // 打印错误信息            ecrt_sdo_request_read(sdo); // retry reading // 尝试重新读取            break; // 结束 switch    }}#endif // 结束 #if/****************************************************************************/ // 分隔注释#if VOE_ACCESS // 如果启用 VoE 访问void read_voe(void) // 定义读取 VoE 的函数{    switch (ecrt_voe_handler_execute(voe)) { // 执行 VoE 处理器并检查状态        case EC_REQUEST_UNUSED: // 状态:未使用            ecrt_voe_handler_read(voe); // trigger first read // 触发第一次读取            break; // 结束 switch        case EC_REQUEST_BUSY: // 状态:忙碌            printk(KERN_INFO PFX "VoE read still busy...\n"); // 打印忙碌信息            break; // 结束 switch        case EC_REQUEST_SUCCESS: // 状态:成功            printk(KERN_INFO PFX "VoE received.\n"); // 打印成功信息            // get data via ecrt_voe_handler_data(voe) // 注释:可以通过 ecrt_voe_handler_data(voe) 获取数据            ecrt_voe_handler_read(voe); // trigger next read // 触发下一次读取            break; // 结束 switch        case EC_REQUEST_ERROR: // 状态:错误            printk(KERN_INFO PFX "Failed to read VoE data!\n"); // 打印错误信息            ecrt_voe_handler_read(voe); // retry reading // 尝试重新读取            break; // 结束 switch    }}#endif // 结束 #if/****************************************************************************/ // 分隔注释#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) // 根据内核版本定义定时器回调函数void cyclic_task(struct timer_list *t) // 新版本内核#elsevoid cyclic_task(unsigned long data) // 老版本内核#endif{    // receive process data // 注释:接收过程数据    spin_lock(&master_spinlock); // 获取自旋锁    ecrt_master_receive(master); // 从网络接口接收数据帧    ecrt_domain_process(domain1); // 处理域的数据    spin_unlock(&master_spinlock); // 释放自旋锁    // check process data state (optional) // 注释:检查过程数据状态(可选)    check_domain1_state(); // 调用函数检查并打印域的状态变化    if (counter) { // 如果计数器不为0        counter--; // 计数器减1    } else { // do this at 1 Hz // 如果计数器为0 (即每秒执行一次)        counter = FREQUENCY; // 重置计数器为频率值 (100)        // calculate new process data // 注释:计算新的过程数据        blink = !blink; // 对 blink 变量取反,实现闪烁逻辑        // check for master state (optional) // 注释:检查主站状态(可选)        check_master_state(); // 调用函数检查并打印主站的状态变化        // check for islave configuration state(s) (optional) // 注释:检查从站配置状态(可选)        check_slave_config_states(); // 调用函数检查并打印特定从站的状态变化#if SDO_ACCESS // 如果启用 SDO 访问        // read process data SDO // 注释:读取过程数据 SDO        read_sdo(); // 调用读取 SDO 的函数#endif // 结束 #if#if VOE_ACCESS // 如果启用 VoE 访问        read_voe(); // 调用读取 VoE 的函数#endif // 结束 #if    }    // write process data // 注释:写入过程数据    EC_WRITE_U8(domain1_pd + off_dig_out, blink ? 0x06 : 0x09); // 将数据写入过程数据区,控制数字量输出 (输出 0b0110 或 0b1001)    // send process data // 注释:发送过程数据    spin_lock(&master_spinlock); // 获取自旋锁    ecrt_domain_queue(domain1); // 将要发送的域数据放入发送队列    ecrt_master_send(master); // 将数据帧发送到 EtherCAT 总线    spin_unlock(&master_spinlock); // 释放自旋锁    // restart timer // 注释:重启定时器    timer.expires += HZ / FREQUENCY; // 计算下一个超时时间点    add_timer(&timer); // 将定时器重新加入内核的定时器列表}/****************************************************************************/ // 分隔注释void send_callback(void *cb_data) // 定义一个发送回调函数{    ec_master_t *m = (ec_master_t *) cb_data; // 将 void* 指针转换为 master 指针    spin_lock_bh(&master_spinlock); // 获取自旋锁(禁止 bottom half)    ecrt_master_send_ext(m); // 调用扩展的发送函数    spin_unlock_bh(&master_spinlock); // 释放自旋锁(允许 bottom half)}/****************************************************************************/ // 分隔注释void receive_callback(void *cb_data) // 定义一个接收回调函数{    ec_master_t *m = (ec_master_t *) cb_data; // 将 void* 指针转换为 master 指针    spin_lock_bh(&master_spinlock); // 获取自旋锁(禁止 bottom half)    ecrt_master_receive(m); // 调用接收函数    spin_unlock_bh(&master_spinlock); // 释放自旋锁(允许 bottom half)}/****************************************************************************/ // 分隔注释int __init init_mini_module(void) // 内核模块的初始化函数{    int ret = -1; // 声明并初始化返回值#if CONFIGURE_PDOS // 如果启用 PDO 配置    ec_slave_config_t *sc; // 声明一个从站配置对象指针#endif // 结束 #if#if EXTERNAL_MEMORY // 如果启用外部内存    unsigned int size; // 声明一个无符号整型,用于存储域大小#endif // 结束 #if    printk(KERN_INFO PFX "Starting...\n"); // 在内核日志中打印启动信息    master = ecrt_request_master(0); // 请求索引为0的 EtherCAT 主站实例    if (!master) { // 检查主站请求是否成功        ret = -EBUSY; // 设置返回值为设备忙        printk(KERN_ERR PFX "Requesting master 0 failed.\n"); // 打印错误信息        goto out_return; // 跳转到返回处    }    spin_lock_init(&master_spinlock); // 初始化自旋锁    ecrt_master_callbacks(master, send_callback, receive_callback, master); // 注册发送和接收的回调函数    printk(KERN_INFO PFX "Registering domain...\n"); // 打印信息    if (!(domain1 = ecrt_master_create_domain(master))) { // 在主站上创建一个过程数据域        printk(KERN_ERR PFX "Domain creation failed!\n"); // 如果失败,打印错误        goto out_release_master; // 跳转到释放主站处    }    if (!(sc_ana_in = ecrt_master_slave_config( // 为模拟量输入从站创建配置                    master, AnaInSlavePos, Beckhoff_EL3152))) {        printk(KERN_ERR PFX "Failed to get slave configuration.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }#if CONFIGURE_PDOS // 如果启用 PDO 配置    printk(KERN_INFO PFX "Configuring PDOs...\n"); // 打印信息    if (ecrt_slave_config_pdos(sc_ana_in, EC_END, el3152_syncs)) { // 为 EL3152 配置 PDO        printk(KERN_ERR PFX "Failed to configure PDOs.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }    if (!(sc = ecrt_master_slave_config( // 为模拟量输出从站创建配置                    master, AnaOutSlavePos, Beckhoff_EL4102))) {        printk(KERN_ERR PFX "Failed to get slave configuration.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }    if (ecrt_slave_config_pdos(sc, EC_END, el4102_syncs)) { // 为 EL4102 配置 PDO        printk(KERN_ERR PFX "Failed to configure PDOs.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }    if (!(sc = ecrt_master_slave_config( // 为数字量输出从站创建配置                    master, DigOutSlavePos, Beckhoff_EL2004))) {        printk(KERN_ERR PFX "Failed to get slave configuration.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }    if (ecrt_slave_config_pdos(sc, EC_END, el2004_syncs)) { // 为 EL2004 配置 PDO        printk(KERN_ERR PFX "Failed to configure PDOs.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }#endif // 结束 #if#if SDO_ACCESS // 如果启用 SDO 访问    printk(KERN_INFO PFX "Creating SDO requests...\n"); // 打印信息    if (!(sdo = ecrt_slave_config_create_sdo_request(sc_ana_in, 0x3102, 2, 2))) { // 创建一个 SDO 请求对象        printk(KERN_ERR PFX "Failed to create SDO request.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }    ecrt_sdo_request_timeout(sdo, 500); // ms // 设置 SDO 请求的超时时间为 500 毫秒#endif // 结束 #if#if VOE_ACCESS // 如果启用 VoE 访问    printk(KERN_INFO PFX "Creating VoE handlers...\n"); // 打印信息    if (!(voe = ecrt_slave_config_create_voe_handler(sc_ana_in, 1000))) { // 创建一个 VoE 处理器        printk(KERN_ERR PFX "Failed to create VoE handler.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }#endif // 结束 #if    printk(KERN_INFO PFX "Registering PDO entries...\n"); // 打印信息    if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) { // 将定义的 PDO 注册列表注册到域        printk(KERN_ERR PFX "PDO entry registration failed!\n"); // 如果注册失败,打印错误        goto out_release_master; // 跳转到释放主站处    }#if EXTERNAL_MEMORY // 如果启用外部内存    if ((size = ecrt_domain_size(domain1))) { // 获取域所需的总内存大小        if (!(domain1_pd = (uint8_t *) kmalloc(size, GFP_KERNEL))) { // 使用 kmalloc 分配内存            printk(KERN_ERR PFX "Failed to allocate %u bytes of process data" // 如果失败,打印错误                    " memory!\n", size);            goto out_release_master; // 跳转到释放主站处        }        ecrt_domain_external_memory(domain1, domain1_pd); // 将分配的内存注册给域    }#endif // 结束 #if    printk(KERN_INFO PFX "Activating master...\n"); // 打印信息    if (ecrt_master_activate(master)) { // 激活主站,开始总线通信        printk(KERN_ERR PFX "Failed to activate master!\n"); // 如果激活失败,打印错误#if EXTERNAL_MEMORY // 如果启用了外部内存        goto out_free_process_data; // 跳转到释放过程数据处#else // 否则        goto out_release_master; // 跳转到释放主站处#endif // 结束 #if    }#if !EXTERNAL_MEMORY // 如果没有启用外部内存    // Get internal process data for domain // 注释:获取域的内部过程数据    domain1_pd = ecrt_domain_data(domain1); // 从主站库获取内部管理的内存指针#endif // 结束 #if    printk(KERN_INFO PFX "Starting cyclic sample thread.\n"); // 打印信息#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0) // 检查内核版本    timer_setup(&timer, cyclic_task, 0); // 使用新的 timer_setup 宏初始化定时器#else // 老版本内核    init_timer(&timer); // 使用旧的 init_timer 函数初始化    timer.function = cyclic_task; // 设置定时器的回调函数#endif // 结束 #if    timer.expires = jiffies + 10; // 设置第一次超时时间    add_timer(&timer); // 将定时器加入内核的定时器列表    printk(KERN_INFO PFX "Started.\n"); // 打印启动成功信息    return 0; // 返回成功#if EXTERNAL_MEMORY // 如果启用外部内存out_free_process_data: // 清理标签:释放过程数据    kfree(domain1_pd); // 释放分配的过程数据内存#endif // 结束 #ifout_release_master: // 清理标签:释放主站    printk(KERN_ERR PFX "Releasing master...\n"); // 打印信息    ecrt_release_master(master); // 释放主站资源out_return: // 清理标签:返回    printk(KERN_ERR PFX "Failed to load. Aborting.\n"); // 打印加载失败信息    return ret; // 返回错误码}/****************************************************************************/ // 分隔注释void __exit cleanup_mini_module(void) // 内核模块的退出函数{    printk(KERN_INFO PFX "Stopping...\n"); // 在内核日志中打印停止信息#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0) // 检查内核版本 (这行可能是笔误,应为 4.15.0 左右的某个版本,但 del_timer_sync 长期存在)    timer_delete_sync(&timer); // 同步地删除定时器 (新接口名)#else    del_timer_sync(&timer); // 同步地删除定时器 (旧接口名)#endif#if EXTERNAL_MEMORY // 如果启用外部内存    kfree(domain1_pd); // 释放过程数据内存#endif // 结束 #if    printk(KERN_INFO PFX "Releasing master...\n"); // 打印信息    ecrt_release_master(master); // 释放主站资源    printk(KERN_INFO PFX "Unloading.\n"); // 打印卸载完成信息}/****************************************************************************/ // 分隔注释MODULE_LICENSE("GPL"); // 宏:声明模块的许可证为 GPLMODULE_AUTHOR("Florian Pose <fp@igh.de>"); // 宏:声明模块的作者MODULE_DESCRIPTION("EtherCAT minimal test environment"); // 宏:声明模块的描述module_init(init_mini_module); // 宏:将 init_mini_module 函数注册为模块的初始化函数module_exit(cleanup_mini_module); // 宏:将 cleanup_mini_module 函数注册为模块的退出函数/****************************************************************************/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值