这是一个基于IgH EtherCAT主站的Linux内核模块,实现了一个最小化的EtherCAT测试环境。主要功能和特点包括:
核心功能:
- EtherCAT主站管理
- 创建和管理EtherCAT主站实例
- 从站设备支持
- 支持Beckhoff的EL3152(模拟输入)、EL4102(模拟输出)、EL2004(数字输出)模块
- 实时周期任务
- 100Hz的实时数据交换循环
- 过程数据管理
- PDO(过程数据对象)的注册和处理
- 状态监控
- 主站、域和从站配置状态的实时监控
技术架构:
- 定时器驱动
- 使用Linux内核定时器实现精确的周期性任务
- 回调机制
- 通过发送和接收回调函数实现异步数据处理
- 内存管理
- 支持内部和外部过程数据内存管理
- 线程安全
- 使用自旋锁保护关键代码段
可选功能:
- PDO配置
- 可配置的过程数据对象映射
- SDO访问
- 服务数据对象的读写操作
- VoE访问
- 以太网供应商特定数据的处理
- 外部内存
- 自定义内存分配管理
数据流程:
每个10ms周期内执行:接收数据 → 处理数据 → 状态检查 → 计算输出 → 写入数据 → 发送数据,实现了完整的EtherCAT实时通信循环。
这个程序为EtherCAT应用开发提供了一个简洁而完整的参考实现。
这个 mini.c
文件是一个功能全面且高度可配置的 Linux 内核模块,是 IgH EtherCAT Master 项目中一个核心的、教科书式的示例。它演示了如何在内核空间实现一个完整的、软实时的 EtherCAT 控制器,并通过宏开关展示了多种基本和高级功能。
核心架构与功能:
内核模块形态:这是一个标准的 Linux 内核模块,通过
insmod
加载,rmmod
卸载。所有逻辑都在内核空间执行。软实时周期驱动 - 内核定时器:
与
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 函数注册为模块的退出函数/****************************************************************************/
- 加载 (