SVG图展示了系统的分层架构:

  • RTAI实时层

    :包含RT_TASK、信号量和定时器

  • EtherCAT Master层

    :主站、域、从站配置和PDO映射

  • EtherCAT网络层

    :与实际硬件设备(EL3162模拟输入、EL2004数字输出)通信

关键特点:

  1. 实时性

    :使用RTAI确保2000Hz的精确定时

  2. 同步保护

    :通过信号量保护EtherCAT操作

  3. 状态监控

    :定期检查主站、域和从站状态

  4. 双向数据流

    :接收模拟输入数据,发送数字输出控制信号

这个系统典型用于工业自动化场景,需要高精度的实时控制和可靠的现场总线通信

这个序列图详细描绘了从模块加载到卸载的整个生命周期中,Linux 内核、RTAI 子系统、驱动逻辑以及 EtherCAT 主站之间的动态交互。它特别突出了 RTAI 任务的创建、周期性执行以及与信号量的交互。

时序图展示了EtherCAT RTAI程序的完整执行流程:

  • 模块初始化阶段

    :请求主站、创建域、配置从站、注册PDO等

  • 循环执行阶段

    :以2000Hz频率进行实时数据交换

  • 模块卸载阶段

    :清理资源

这段使用了 RTAI (Real-Time Application Interface) 的 IgH EtherCAT Master 示例代码。这是一个在内核空间运行的硬实时应用,通过 RTAI 提供的任务调度和定时器来实现高精度的周期性控制。

// Linux // 区域注释:Linux 内核头文件#include <linux/module.h> // 包含 Linux 内核模块编程所需的核心头文件#include <linux/err.h> // 包含内核错误处理相关的头文件// RTAI // 区域注释:RTAI 头文件#include <rtai_sched.h> // 包含 RTAI 调度器相关的头文件,如任务创建、周期性设置#include <rtai_sem.h> // 包含 RTAI 信号量相关的头文件,用于同步// EtherCAT // 区域注释:EtherCAT 头文件#include "../../include/ecrt.h" // 包含 IgH EtherCAT 主站的实时接口头文件/****************************************************************************/ // 分隔注释// Module parameters // 区域注释:模块参数#define FREQUENCY 2000 // task frequency in Hz // 宏定义:任务频率为 2000 Hz#define INHIBIT_TIME 20 // 宏定义:抑制时间为 20 (单位可能是微秒,用于回调函数)#define TIMERTICKS (1000000000 / FREQUENCY) // 宏定义:每个周期的纳秒数// Optional features (comment to disable) // 区域注释:可选特性 (注释掉以禁用)#define CONFIGURE_PDOS // 宏定义:启用 PDO 配置代码块#define PFX "ec_rtai_sample: " // 宏定义:内核日志消息的前缀,方便调试/****************************************************************************/ // 分隔注释// EtherCAT // 区域注释:EtherCAT 相关全局变量static ec_master_t *master = NULL; // 声明一个静态的 EtherCAT 主站对象指针static ec_master_state_t master_state = {}; // 声明一个静态的主站状态结构体变量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 = {}; // 声明一个静态的从站配置状态结构体变量// RTAI // 区域注释:RTAI 相关全局变量static RT_TASK task; // 声明一个 RTAI 任务结构体变量static SEM master_sem; // 声明一个 RTAI 信号量结构体变量static cycles_t t_last_cycle = 0, t_critical; // 声明 RTAI 的时间戳变量 (CPU周期数),用于高精度时间测量/****************************************************************************/ // 分隔注释// process data // 区域注释:过程数据static uint8_t *domain1_pd; // process data memory // 声明一个指向过程数据内存区域的指针#define AnaInSlavePos  0, 3 // 宏定义:模拟量输入从站的位置#define DigOutSlavePos 0, 2 // 宏定义:数字量输出从站的位置#define Beckhoff_EL2004 0x00000002, 0x07D43052 // 宏定义:倍福 EL2004 的厂商/产品ID#define Beckhoff_EL3162 0x00000002, 0x0C5A3052 // 宏定义:倍福 EL3162 的厂商/产品IDstatic unsigned int off_ana_in; // offsets for PDO entries // 静态无符号整型,存储模拟量输入值的偏移量static unsigned int off_dig_out; // 静态无符号整型,存储数字量输出的偏移量const static ec_pdo_entry_reg_t domain1_regs[] = { // 定义一个静态常量数组,用于注册需要映射到域的PDO条目    {AnaInSlavePos,  Beckhoff_EL3162, 0x3101, 2, &off_ana_in}, // 注册 EL3162 的值 PDO    {DigOutSlavePos, Beckhoff_EL2004, 0x3001, 1, &off_dig_out}, // 注册 EL2004 的输出 PDO    {} // 数组结束标志};static unsigned int counter = 0; // 静态无符号整型,用作通用计数器static unsigned int blink = 0; // 静态无符号整型,用作闪烁标志/****************************************************************************/ // 分隔注释#ifdef CONFIGURE_PDOS // 如果定义了 CONFIGURE_PDOS 宏// 以下是为从站进行 SII (从站信息接口) 覆盖配置所需的数据结构static ec_pdo_entry_info_t el3162_channel1[] = { // 定义 EL3162 通道1的 PDO 条目信息    {0x3101, 1,  8}, // status (状态,8位)    {0x3101, 2, 16}  // value (值,16位)};static ec_pdo_entry_info_t el3162_channel2[] = { // 定义 EL3162 通道2的 PDO 条目信息    {0x3102, 1,  8}, // status (状态,8位)    {0x3102, 2, 16}  // value (值,16位)};static ec_pdo_info_t el3162_pdos[] = { // 定义 EL3162 的 PDO 信息 (将条目分组)    {0x1A00, 2, el3162_channel1}, // TxPDO 0x1A00    {0x1A01, 2, el3162_channel2}  // TxPDO 0x1A01};static ec_sync_info_t el3162_syncs[] = { // 定义 EL3162 的同步管理器配置    {2, EC_DIR_OUTPUT}, // SM2 是一个空的输出 SM    {3, EC_DIR_INPUT, 2, el3162_pdos}, // SM3 是输入 SM,关联2个 PDO    {0xff} // 结束标志};static ec_pdo_entry_info_t el2004_channels[] = { // 定义 EL2004 的 PDO 条目信息    {0x3001, 1, 1}, // Value 1 (值1,1位)    {0x3001, 2, 1}, // Value 2 (值2,1位)    {0x3001, 3, 1}, // Value 3 (值3,1位)    {0x3001, 4, 1}  // Value 4 (值4,1位)};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 // 结束 #ifdef/****************************************************************************/ // 分隔注释void check_domain1_state(void) // 定义一个函数,用于检查并打印域的状态变化{    ec_domain_state_t ds; // 声明一个局部的域状态结构体变量    rt_sem_wait(&master_sem); // 等待(获取)RTAI信号量    ecrt_domain_state(domain1, &ds); // 调用 ecrt API,获取 domain1 的当前状态    rt_sem_signal(&master_sem); // 发送(释放)RTAI信号量    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; // 声明一个局部的主站状态结构体变量    rt_sem_wait(&master_sem); // 等待 RTAI 信号量    ecrt_master_state(master, &ms); // 调用 ecrt API,获取主站的当前状态    rt_sem_signal(&master_sem); // 释放 RTAI 信号量    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"); // 如果不一致,打印链路是 "up" 还是 "down"    master_state = ms; // 将当前状态赋值给全局变量,用于下次比较}/****************************************************************************/ // 分隔注释void check_slave_config_states(void) // 定义一个函数,用于检查并打印特定从站的配置状态变化{    ec_slave_config_state_t s; // 声明一个局部的从站配置状态结构体变量    rt_sem_wait(&master_sem); // 等待 RTAI 信号量    ecrt_slave_config_state(sc_ana_in, &s); // 调用 ecrt API,获取模拟量输入从站的当前配置状态    rt_sem_signal(&master_sem); // 释放 RTAI 信号量    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 "); // 根据 operational 的值打印 "operational" 或 "Not operational"    sc_ana_in_state = s; // 将当前状态赋值给全局变量,用于下次比较}/****************************************************************************/ // 分隔注释void run(long data) // RTAI 实时任务的主体函数{    while (1) { // 进入一个无限循环        t_last_cycle = get_cycles(); // 使用 RTAI 函数获取当前 CPU 周期数,记录周期开始时间        // receive process data // 注释:接收过程数据        rt_sem_wait(&master_sem); // 获取信号量        ecrt_master_receive(master); // 从网络接口接收数据帧        ecrt_domain_process(domain1); // 处理域的数据        rt_sem_signal(&master_sem); // 释放信号量        // check process data state (optional) // 注释:检查过程数据状态(可选)        check_domain1_state(); // 调用函数检查并打印域的状态变化        if (counter) { // 如果计数器不为0            counter--; // 计数器减1        } else { // do this at 1 Hz // 如果计数器为0 (即每秒执行一次)            counter = FREQUENCY; // 重置计数器为频率值 (2000)            // 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(); // 调用函数检查并打印特定从站的状态变化        }        // write process data // 注释:写入过程数据        EC_WRITE_U8(domain1_pd + off_dig_out, blink ? 0x06 : 0x09); // 将数据写入过程数据区,控制数字量输出 (输出 0b0110 或 0b1001)        rt_sem_wait(&master_sem); // 获取信号量        ecrt_domain_queue(domain1); // 将要发送的域数据放入发送队列        ecrt_master_send(master); // 将数据帧发送到 EtherCAT 总线        rt_sem_signal(&master_sem); // 释放信号量        rt_task_wait_period(); // RTAI 函数:使当前任务睡眠,直到下一个周期性调度点    }}/****************************************************************************/ // 分隔注释void send_callback(void *cb_data) // 定义一个发送回调函数 (用于外部事件驱动模式){    ec_master_t *m = (ec_master_t *) cb_data; // 将 void* 指针转换为 master 指针    // too close to the next real time cycle: deny access... // 注释:离下一个实时周期太近:拒绝访问...    if (get_cycles() - t_last_cycle <= t_critical) { // 如果当前时间与上个周期开始时间的差值小于临界值        rt_sem_wait(&master_sem); // 获取信号量        ecrt_master_send_ext(m); // 调用扩展的发送函数        rt_sem_signal(&master_sem); // 释放信号量    }}/****************************************************************************/ // 分隔注释void receive_callback(void *cb_data) // 定义一个接收回调函数 (用于外部事件驱动模式){    ec_master_t *m = (ec_master_t *) cb_data; // 将 void* 指针转换为 master 指针    // too close to the next real time cycle: deny access... // 注释:离下一个实时周期太近:拒绝访问...    if (get_cycles() - t_last_cycle <= t_critical) { // 如果当前时间与上个周期开始时间的差值小于临界值        rt_sem_wait(&master_sem); // 获取信号量        ecrt_master_receive(m); // 调用接收函数        rt_sem_signal(&master_sem); // 释放信号量    }}/****************************************************************************/ // 分隔注释int __init init_mod(void) // 内核模块的初始化函数{    int ret = -1; // 声明并初始化返回值    RTIME tick_period, requested_ticks, now; // 声明 RTAI 的时间变量#ifdef CONFIGURE_PDOS // 如果定义了 CONFIGURE_PDOS 宏    ec_slave_config_t *sc; // 声明一个从站配置对象指针#endif // 结束 #ifdef    printk(KERN_INFO PFX "Starting...\n"); // 在内核日志中打印启动信息    rt_sem_init(&master_sem, 1); // 初始化 RTAI 信号量,初始值为1    // 计算一个临界时间值,用于回调函数中判断是否离下一个周期太近    t_critical = cpu_khz * 1000 / FREQUENCY - cpu_khz * INHIBIT_TIME / 1000;    master = ecrt_request_master(0); // 请求索引为0的 EtherCAT 主站实例    if (!master) { // 检查主站请求是否成功        ret = -EBUSY; // 设置返回值为设备忙        printk(KERN_ERR PFX "Requesting master 0 failed!\n"); // 打印错误信息        goto out_return; // 跳转到返回处    }    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_EL3162))) {        printk(KERN_ERR PFX "Failed to get slave configuration.\n"); // 打印错误        goto out_release_master; // 跳转到释放主站处    }#ifdef CONFIGURE_PDOS // 如果定义了 CONFIGURE_PDOS 宏    printk(KERN_INFO PFX "Configuring PDOs...\n"); // 打印信息    if (ecrt_slave_config_pdos(sc_ana_in, EC_END, el3162_syncs)) { // 为 EL3162 配置 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 // 结束 #ifdef    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; // 跳转到释放主站处    }    printk(KERN_INFO PFX "Activating master...\n"); // 打印信息    if (ecrt_master_activate(master)) { // 激活主站,开始总线通信        printk(KERN_ERR PFX "Failed to activate master!\n"); // 如果激活失败,打印错误        goto out_release_master; // 跳转到释放主站处    }    // Get internal process data for domain // 注释:获取域的内部过程数据    domain1_pd = ecrt_domain_data(domain1); // 获取域的过程数据内存区指针    printk(KERN_INFO PFX "Starting cyclic sample thread...\n"); // 打印信息    requested_ticks = nano2count(TIMERTICKS); // 将周期纳秒数转换为 RTAI 的时钟节拍数    tick_period = start_rt_timer(requested_ticks); // 启动 RTAI 的实时定时器,并获取实际的节拍周期    printk(KERN_INFO PFX "RT timer started with %i/%i ticks.\n", // 打印定时器信息           (int) tick_period, (int) requested_ticks);    // 初始化 RTAI 任务    if (rt_task_init(&task, run, 0, 2000, 0, 1, NULL)) { // 参数:任务结构体, 函数, 参数, 栈大小, 优先级, 使用FPU, 信号        printk(KERN_ERR PFX "Failed to init RTAI task!\n"); // 如果失败,打印错误        goto out_stop_timer; // 跳转到停止定时器处    }    now = rt_get_time(); // 获取当前 RTAI 时间    // 将任务设置为周期性任务    if (rt_task_make_periodic(&task, now + tick_period, tick_period)) { // 参数:任务, 首次启动时间, 周期        printk(KERN_ERR PFX "Failed to run RTAI task!\n"); // 如果失败,打印错误        goto out_stop_task; // 跳转到删除任务处    }    printk(KERN_INFO PFX "Initialized.\n"); // 打印初始化成功信息    return 0; // 返回成功 out_stop_task: // 清理标签:删除任务    rt_task_delete(&task); // 删除 RTAI 任务 out_stop_timer: // 清理标签:停止定时器    stop_rt_timer(); // 停止 RTAI 实时定时器 out_release_master: // 清理标签:释放主站    printk(KERN_ERR PFX "Releasing master...\n"); // 打印信息    ecrt_release_master(master); // 释放主站资源 out_return: // 清理标签:返回    rt_sem_delete(&master_sem); // 删除 RTAI 信号量    printk(KERN_ERR PFX "Failed to load. Aborting.\n"); // 打印加载失败信息    return ret; // 返回错误码}/****************************************************************************/ // 分隔注释void __exit cleanup_mod(void) // 内核模块的退出函数{    printk(KERN_INFO PFX "Stopping...\n"); // 在内核日志中打印停止信息    rt_task_delete(&task); // 删除 RTAI 任务    stop_rt_timer(); // 停止 RTAI 实时定时器    ecrt_release_master(master); // 释放主站资源    rt_sem_delete(&master_sem); // 删除 RTAI 信号量    printk(KERN_INFO PFX "Unloading.\n"); // 打印卸载完成信息}/****************************************************************************/ // 分隔注释MODULE_LICENSE("GPL"); // 宏:声明模块的许可证为 GPLMODULE_AUTHOR("Florian Pose <fp@igh.de>"); // 宏:声明模块的作者MODULE_DESCRIPTION("EtherCAT RTAI sample module"); // 宏:声明模块的描述module_init(init_mod); // 宏:将 init_mod 函数注册为模块的初始化函数module_exit(cleanup_mod); // 宏:将 cleanup_mod 函数注册为模块的退出函数/****************************************************************************/

这是一个在 Linux 内核空间运行的、基于 RTAI (Real-Time Application Interface) 的硬实时 EtherCAT 主站示例模块。它展示了如何利用 RTAI 提供的实时任务和调度机制来构建一个高精度、确定性的工业控制应用。

核心架构与功能:

  1. 内核模块形态:与 tty.c 示例类似,这是一个通过 insmod 加载、rmmod 卸载的完整内核模块,其生命周期由 module_init 和 module_exit 函数管理。

  2. 硬实时环境 (RTAI):此示例的核心是 RTAI。它不依赖 Linux 自身的调度器或定时器,而是使用 RTAI 提供的、运行在 Linux 内核之下的实时微内核服务。

  • RTAI 任务

    :通过 rt_task_init 创建一个名为 task 的 RTAI 实时任务,并指定其执行函数为 run

  • RTAI 调度与定时

    :通过 start_rt_timer 启动 RTAI 的高精度实时定时器,并通过 rt_task_make_periodic 将 task 设置为由该定时器驱动的周期性任务。周期的精确性由 RTAI 内核保证,抖动(jitter)非常小。

  • 周期性执行

    run 函数中的 rt_task_wait_period() 会精确地阻塞任务,直到下一个由 RTAI 定时器确定的调度点到来。

  • 并发保护 - RTAI 信号量

    • 为了保护对共享 EtherCAT 主站资源的访问,代码使用了 RTAI 提供的信号量 (rt_sem_initrt_sem_waitrt_sem_signal)。

    • 在 run 函数和两个回调函数中,所有对 ecrt_* 函数的调用都被信号量加锁和解锁,确保了在 RTAI 的多任务环境下的数据一致性和原子性。

  • 周期性任务 (run)

    • 这是 RTAI 实时任务的主体,在一个无限循环中运行。

    • 每个周期开始时,它执行标准的 EtherCAT I/O 流程:接收、处理、应用逻辑、发送。

    • 应用逻辑

      :包含一个简单的 1Hz 闪烁逻辑,控制一个数字量输出模块。

    • 状态监控

      :以较低频率检查并打印主站、域和从站的状态。

    • 高精度时间戳

      :使用 get_cycles() 获取 CPU 周期数,用于高精度的时间测量,这在 send/receive_callback 中用于防止与实时周期冲突。

  • 可选的回调机制

    • 代码中注册了 send_callback 和 receive_callback,这通常用于外部事件驱动的模式(例如,由网卡中断直接触发数据收发)。

    • 在这些回调函数中,通过比较当前 CPU 周期数与上一个实时周期开始的时间戳,来判断是否离下一个实时周期太近。如果太近,就不执行操作,这是为了避免外部事件干扰到由 RTAI 定时器驱动的主实时循环的确定性。

  • 生命周期管理

    • 加载 (insmod)

      : 执行 init_mod。完成 EtherCAT 配置、初始化 RTAI 信号量、启动 RTAI 实时定时器,并创建和启动 RTAI 周期性任务。

    • 运行

      : RTAI 调度器周期性地唤醒 run 任务,执行硬实时控制循环。

    • 卸载 (rmmod)

      : 执行 cleanup_mod。安全地删除 RTAI 任务,停止 RTAI 定时器,释放 EtherCAT 主站资源,并删除 RTAI 信号量。

    总结:此代码是一个经典的硬实时内核空间 EtherCAT 控制器范例。它完全绕开了标准 Linux 的调度和定时机制,将所有实时关键操作都交由 RTAI 微内核处理,从而获得了微秒级的、确定性的性能。这是在需要极高实时性保证的工业自动化和机器人控制等领域中常见的架构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92945.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92945.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/92945.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7款热门智能电视文件管理器横向评测

7款智能电视文件管理器横向评测 在智能电视和电视盒子日益普及的今天&#xff0c;一款好用的文件管理器能让您的数字生活更加便捷。本文为您评测了7款广受欢迎的TV版文件管理器&#xff0c;助您找到最适合自己的工具。 1. ES文件浏览器TV版 ES文件浏览器是一款广受欢迎的多功能…

Python 类元编程(导入时和运行时比较)

导入时和运行时比较 为了正确地做元编程&#xff0c;你必须知道 Python 解释器什么时候计算各个代码 块。Python 程序员会区分“导入时”和“运行时”&#xff0c;不过这两个术语没有严 格的定义&#xff0c;而且二者之间存在着灰色地带。在导入时&#xff0c;解释器会从上到 下…

[git diff] 对比检查变更 | 提交前复审 | 版本回退

git diff git diff 是 Git 版本控制系统中用于比较文件差异的核心命令&#xff0c;可以显示工作目录、暂存区&#xff08;Index&#xff09;和仓库历史之间的变化。 通过对比不同版本或状态的文件内容&#xff0c;帮助开发者理解代码变更。 比较工作目录与暂存区 运行以下命令查…

【数据可视化-85】海底捞门店数据分析与可视化:Python + pyecharts打造炫酷暗黑主题大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

物联网之小白调试网关设备

小伙伴们&#xff0c;你们好呀&#xff01;我是老寇&#xff01;跟我一起学习调试网关设备 相信搞过物联网的朋友&#xff0c;对网关设备非常熟悉&#xff0c;本人以小白的视角&#xff0c;手把手教你调试网关设备&#xff01; 工作中使用的是Ubuntu操作系统&#xff0c;因此&a…

Node.js特训专栏-实战进阶:22. Docker容器化部署

🔥 欢迎来到 Node.js 实战专栏!在这里,每一行代码都是解锁高性能应用的钥匙,让我们一起开启 Node.js 的奇妙开发之旅! Node.js 特训专栏主页 专栏内容规划详情 我将从Docker容器化部署的基础概念入手,介绍Node.js应用容器化的步骤,包括创建Dockerfile、构建镜像、运行…

eclipse嵌入式编译速度慢

eclipse 嵌入式 编译 速度慢 同一个项目&#xff0c;eclipse编译速度越来越慢&#xff0c;一开始几秒钟编译完&#xff0c;后面要10分钟。只需要将以下两个程序卸载重新安装即可。

编译Android版本可用的高版本iproute2

背景&#xff1a; Android自带的iproute2 太老&#xff0c;很多指令格式不支持 直接基于Android源码&#xff0c;替换源码下iproute2 代码编译新版&#xff0c;报错太多&#xff0c;于是改用Android NDK工具编译 环境&#xff1a; android-ndk-r25c-linux.zip 下载链接&am…

JavaScript的fetch函数的用法

基本语法fetch函数用于发起网络请求&#xff0c;返回一个Promise对象。基本语法如下&#xff1a;fetch(url, options).then(response > response.json()).then(data > console.log(data)).catch(error > console.error(Error:, error));GET请求发起一个简单的GET请求&…

Json和XML文件相互转化

目录 一.XML转Json文件 示例&#xff1a;将XML转换为JSON 依赖准备 Java代码示例 代码详细讲解 二.Json转XML文件 示例&#xff1a;将JSON转换为XML 依赖准备 Java代码示例 代码详细讲解 关键代码解析 将JSON转换为XML 写入文件 示例输入与输出 三.具有相同功能的…

Python科学计算与可视化领域工具TVTK、Mayavi、Mlab、Traits(附视频教程)

概述 TVTK、Mayavi、Mlab 和 Traits 都是 Python 科学计算与可视化领域中紧密相关的工具&#xff0c;它们常被结合使用来处理和展示三维数据。视频教程&#xff1a;https://pan.quark.cn/s/f73e875225ca 1. TVTK TVTK&#xff08;Traits-based Visualization Toolkit&#xff0…

SQL INSERT INTO SELECT 详解

SQL INSERT INTO SELECT 详解 引言 SQL(Structured Query Language)是数据库操作的基础语言,广泛用于各种关系型数据库管理系统中。在SQL中,INSERT INTO SELECT 是一个强大的功能,它允许用户从一个表中选取数据,并直接将这些数据插入到另一个表中。本文将详细讲解 SQL …

python速成学习路线

第一部分&#xff1a;核心基础&#xff08;语法与工具&#xff09; 目标&#xff1a;掌握 Python 的基本语法规则、数据处理方式和开发工具 核心内容&#xff1a; 环境搭建 安装Python 3.x版本&#xff08;推荐3.10&#xff09;配置开发工具&#xff08;如PyCharm、VS Code或…

自然语言处理的实际应用

在这个信息爆炸的时代&#xff0c;我们每天都在与文字、语音打交道 —— 发送消息、查询信息、使用智能助手…… 这些看似平常的互动背后&#xff0c;都离不开一项关键技术的支撑&#xff1a;自然语言处理&#xff08;NLP&#xff09;。作为人工智能的重要分支&#xff0c;NLP …

Docker实战:为项目打造即开即用的宝塔LNMP环境

Docker实战&#xff1a;为项目打造即开即用的宝塔LNMP环境背景一、准备基础镜像二、启动配置容器&#xff08;关键步骤&#xff09;三、容器内环境配置&#xff08;逐步执行&#xff09;1. 基础环境搭建2. 安装Systemd&#xff08;宝塔依赖&#xff09;3. 安装宝塔面板&#xf…

.net\c#web、小程序、安卓开发之基于asp.net家用汽车销售管理系统的设计与实现

.net\c#web、小程序、安卓开发之基于asp.net家用汽车销售管理系统的设计与实现

药房智能盘库系统:基于CV与时间序列预测的库存革命

> 在医疗资源日益紧张的今天,**全国78%的药房仍依赖人工盘库**,平均每100家药房每年因库存问题损失超50万元。当计算机视觉遇见时间序列预测,一场药房库存管理的智能化革命正在悄然发生。 --- ### 一、传统药房库存的三大痛点与破局思路 #### 致命痛点分析 1. **人工…

【互动屏幕】解析双屏联动在数字展厅中的应用与价值

双屏联动 https://www.bmcyzs.com/ 作为现代展厅设计中的重要技术手段&#xff0c;通过两块或多块屏幕的协同工作&#xff0c;实现了信息的动态展示与交互体验的提升。在展厅环境中&#xff0c;双屏联动软件能够将触摸屏与大屏幕无缝连接&#xff0c;使观众通过简单的操作即可控…

clickhouse基础概念及集群部署

一. 简述&#xff1a; ClickHouse 是一款高性能列式存储数据库&#xff0c;专为海量数据的实时分析场景设计。它以极致的查询速度、高效的存储利用率和强大的并行处理能力著称&#xff0c;广泛应用于日志分析、用户行为分析、业务监控等大数据分析领域。1. 核心特性&#xff1a…

低版本 IntelliJ IDEA 使用高版本 JDK 语言特性的问题

现实问题&#xff1a; 目前最新的 IntelliJ IDEA 已经不支持在 Win7 环境上安装了&#xff0c;如果企业内开发环境仍然是 Win7&#xff0c;就会导致很多问题。 比如当前 IDEA 版本为 2023.1&#xff0c;最大支持 JDK17&#xff0c;如何正常使用 JDK21 的新特性呢&#xff1f;比…