Linux驱动开发——PCI设备驱动

目录

一、 PCI协议简介

二、PCI和PCI-e

三、Linux PCI驱动

四、 PCI设备驱动实例

五、 总线类设备驱动开发习题



一、 PCI协议简介


        PCI (Peripheral Component Interconnect,外设部件互联) 局部总线是由Intel 公司联合其他几家公司一起开发的一种总线标准,最初是为了替代 ISA 之类的总线,用于解决当时的图形化界面显示器的带宽问题。相比于 ISA 总线,它最大的特点是高带宽、突发传输和即插即用(热插拔)。在 PCI 3.0 的规范中,PCI 局部总线的时钟速率有 33MHZ、66MHz 和133MHz 三种标准速率,支持的数据位宽有 32 位和 64 位两种。所以最低的数据传输率为33MHz x 32bit = 132MB/s,即每秒 132M 字节,这完全满足当时的图形显卡的要求。突发传输是指其地址总线和数据总线复用,在传输开始时先发地址,然后再连续传输若干个字节的数据,这样做的好处是可以减少芯片的管脚,并且一个传输周期可以完成若干个字节的传输。即插即用和前面谈到的 USB 类似,总线上的设备存放有配置信息,在初始化的过程中,主机会主动获取这些信息,从而分配其所需要的资源,这会在后面做更详细的介绍。随着 PCI 局部总线的发展,其应用的领域也越来越广泛,现在 PC 中独立的网卡、声卡、数据采集卡等使用的都是 PCI 局部总线。后来又推出了串行的标准,PCI-Express,其传输速率相当高,在 PCI-Express 3.0 规范中,其传输率可以达到 8GT/s即每秒 8G 次传输。因为使用的广泛性,在某些嵌入式系统中也使用了 PCI或 PCI-Express局部总线。下面简单介绍一下 PCI3.0 规范中驱动开发者需要关心的内容,下图是 PCI系统的连接框图(引自 PCI3.0 规范)。


        处理器(Processor)通过 Host/PCI桥(Bridge)连接到了 0号 PCI局部总线(PCI LocalBus #0),在这条局部总线上,有声卡 (Audio)、动态视频(Motion Video)、图形显卡(Graphics)、网卡(LAN)和 SCSI控制器等。通过 PCI-to-PCI Bridge,又扩展出了1号PCI局部总线(PCILocal Bus #1),在这条总线上又接入了其他 PCI功能设备。另外,还有PCI-ISA 桥,可以将 PCI 总线转换为传统的ISA 总线。

        PCI局部总线也是主从结构,在 PCI的规范中主设备叫发起者(Initiator),从设备叫目标(Target),传输由主设备发起,从设备进行响应。一个 PCI 设备都要实现目标的功能,但也可以实现发起者的功能,也就是说,一个设备既可以在某一时刻做主设备,也可以在另一个时刻做从设备。并且一条总线上允许有多个主设备,由仲裁器来决定哪个主设备可以获得总线的控制权。下面我们仅讨论 PCI的从设备。
        PCI 定义了三个物理地址空间,包括内存地址空间、I/O 地址空间和配置地址空间。

        其中配置地址空间是必需的,这个地址空间用于对设备的硬件进行配置。为了更好地理解这三个地址空间的访问,我们先来看看 PCI 的典型写传输时序图,如下图所示(引自PCI3.0 规范)。


        当发起者要对目标进行写操作时,会先将 FRAME 拉低,在之后的第一个时钟周期AD 总线上是发送地址,C/BE 是总线的命令,用于确定一个更具体的写操作,DEVSEI是被选中的目标发出的确认信号。在之后的若干个周期,AD 总线上是要写入的数据,C/BE 上是字节使能,用于确定哪个字节是有效的。IRDY 和 TRDY 分别是发起者和目标的准备信号,当任一个无效时,都会自动插入等待周期。在最后一个数据周期,FRAME无效,但传输最终完成是在 FRAME 无效后 IRDY 也无效的时刻。PCI 的读传输操作和写基本类似,只是数据的方向相反。上面涉及的总线命令如下图所示(引自 PCI3.0 规范)。


        I/O 读、I/O 写、内存读、内存写、配置读和配置写即我们前面提到的三个物理地址空间的读写。我们首先来看配置空间是如何寻址的,地址结构如下图所示。


        PCI规范定义了两种类型的配置空间地址,Type 0 用于选择总线上的一个设备,Type1 用于将请求传递给另一条总线。地址中的各个字段的含义如下。
        Bus Number:8 位总线地址,在 256条PCI 局部总线中选择一条。
        Device Number:5 位设备地址,在一条总线上的 32 个物理设备中选择一个
        Function Number:3 位功能地址,在一个物理设备上的8个功能中选择一个功能,也就是说,PCI 设备和 USB 设备类似,一个物理设备可以有多个功能,从而实现多个逻辑设备。
        Register Number:用于选择配置空间中的一个 32 位寄存器。

        在 PCI规范中,对配置空间的各寄存器都有具体的定义,整个配置空间有 64 个字节我们并不需要关心配置空间中每个寄存器的含义,下面列出最主要的一些寄存器(其他寄存器的定义及地址请参见 PCI规范)。


        Vendor ID:16 位,硬件厂商ID。
        Device ID:16 位,设备 ID。
        Class Code: 24 位,外设所属的类别,如大容量存储设备控制器类、网络控制器类显示控制器类等。为 0表示不属于某一具体的类。
        Subsystem Vendor ID: 16 位,子系统厂商 ID。
        Subsystem ID:16 位,子系统ID。

        Base Address Registers: 32 位,在计算机启动的过程中,会检查所有的 PCI 设备,其中一个重要的操作就是要获取其使用的内存空间和 I/O 空间的大小,然后给每一个空间分配一个基址,这个基址就是存放在基址寄存器中的。配置空间中共有 6 个这样的基址寄存器,在 Linux 驱动中简称 bar。
        上面的 ID 和 Class 用于匹配驱动程序,基址则用于驱动进行资源获取和映射操作,后面会进行更详细的描述。有了基址寄存器后,对内存空间和 IO 空间的访问问题也就迎刃而解了,因为我们只需要发出相应的内存地址或I/O地址就可以访问对应的空间了。
 

 

二、PCI和PCI-e

        PCI和PCIe都是计算机中用于连接设备的接口标准,但它们之间存在一些重要区别。

        PCI(Peripheral Component Interconnect)是一种早期的计算机总线标准,它被设计用于连接各种高速外围设备,如显卡、声卡、网卡等。PCI总线是一种共享总线,这意味着所有设备共享相同的带宽。因此,当多个设备同时尝试使用总线时,可能会出现性能下降或冲突。

        相比之下,PCIe(Peripheral Component Interconnect Express)是一种更现代的计算机总线标准,也被广泛应用于连接各种高速设备。与PCI不同,PCIe是一种点对点互连协议,这意味着每个设备都有自己的专用连接,不会与其他设备共享带宽。这使得PCIe在性能上大大超越PCI,特别是在高带宽应用和多设备环境中。

        总的来说,PCIe在性能、灵活性和扩展性方面都优于PCI,这也是为什么现在的计算机和设备大多采用PCIe接口的原因。但需要注意的是,由于PCIe的复杂性和成本较高,在一些低带宽和低成本的应用中,PCI仍然可能被使用。

        PCI-e在软件层面是兼容PCI的,PCI是并行传输,PCI-e是串行点对点传输。

三、Linux PCI驱动


        下面我们还是只讨论 PCI 从设备。PCI 设备在内中用 struct pci_dev 结构来表示,该结构的成员非常多,在此就不一一列出了,可以参见内核源码中的 include/linux/pci.h 文件。在里面会发现我们前面提到的 ID、类等成员,还有设备所使用的IRQ 线。设备的 ID 还有一个 struct pci_device_id 结构,驱动中通常会定义这样一个数组,来表示可以支持的设备列表,和前面的 USB 设备列表类似。和 PCI 设备结构相关的主要函数和宏如下。

int pci_enable_device(struct pci_dev *dev);
void pci_disable_device(struct pci_dev *dev);
pci_resource_start(dev, bar)
pci_resource_end(dev, bar)
pci_resource_flags(dev, bar)
pci_resource_len(dev,bar)
int pci_request_regions (struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);


pci_enable_device: 使能 PCI设备,在操作 PCI设备之前必须先使能设备

pci_disable_device:禁止PCI设备。
pci_resource_start: 获取 dev 中第 bar 个基址寄存器中记录的资源起始地址.

pci_resource_end:获取 dev 中第 bar 个基址寄存器中记录的资源结束地址。
pci_resource_flags:取 dev 中第 bar 个基址寄存器中记录的资源标志,是内存资源还是 IO 资源。
pci_resource_len:获取 dev 中第 bar 个基址寄存器中记录的资源大小。
pci_request_regions: 申请 PCI 设备 pdev 内的内存资源和I/0 资源,取名为res_name.

pci_release_regions: 释放 PCI设备 pdev 内的内存资源和 IO 资源。

        内核中用 struct pci_driver 结构来表示 PCI 设备驱动,相关的主要函数宏如下


 

void pci_unregister_driver(struct pci_driver *dev);
pci_register_driver(driver)
void pci_set_drvdata(struct pci_dev *pdev, void *data);
void *pci_get_drvdata(struct pci_dev *pdev);


pci_register_driver: 注册 PCI 设备驱动 driver
pci_unregister_driver: 注销 PCI 设备驱动 dev。
pci_set_drvdata:保存 data 指针到PCI设备pdev 中。
pci_get_drvdata: 从 PCI 设备 pdev 中获取保存的指针

PCI设备的配置空间访问的主要函数如下。

int pci_read_config_byte(const struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(const struct pci_dev *dev, int where, u16 *val);
int pei_read_config_dword(const struct pci_dev *dev, int where, u32 *val);
int pci_write_config_byte(const struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(const struct pci_dev *dev, int where, u16 val);
int pei_write_config_dword(const struct pci_dev *dev, int where, u32 val);


上面的函数分别实现了对配置空间的字节、字 (16 位) 和双字(32 位) 的读写操作。


四、 PCI设备驱动实例


这里使用的 PCI 设备是南京沁恒公司的 CH368EVT 评估板,该评估板使用了一片该公司设计的 CH368 PCI-Express 接口芯片,虽然是 PCI-Express 协议,但是在驱动上两者可以兼容,只是 PCI-Express 速率更高,能够支持更多的功能。选用该评估板的原因是其价格低廉,完全国产,也能够全面验证三个空间的读写操作。
        使用L1~L4 这 4个 LED 显示 I/O 数据端口 D3~DO 位的状态。灯亮代表 1,灯灭代表0。
CH368 的配置空间定义如下图所示。


         厂商 ID 和设备 ID 是我们比较关心的内容,驱动的设备列表中的 ID 要和这里的致。第一个基址寄存器是 I/O 地址空间的基址,有 232 个字节,定义如下图所示。另外,CH368 的内存空间有 32KB。

该设备的 Linux 驱动代码如下,为了尽量突出 PCI驱动的核心,并没有加入并发控制相关的代码。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/uaccess.h>#include "ch368.h"#define CH368_MAJOR	256
#define CH368_MINOR	11
#define CH368_DEV_NAME	"ch368"struct ch368_dev {void __iomem *io_addr;void __iomem *mem_addr;unsigned long io_len;unsigned long mem_len;struct pci_dev *pdev;struct cdev cdev;dev_t dev;
};static unsigned int minor = CH368_MINOR;static int ch368_open(struct inode *inode, struct file *filp)
{struct ch368_dev *ch368;ch368 = container_of(inode->i_cdev, struct ch368_dev, cdev);filp->private_data = ch368;return 0;
}static int ch368_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t ch368_read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops)
{int ret;struct ch368_dev *ch368 = filp->private_data;count = count > ch368->mem_len ? ch368->mem_len : count;ret = copy_to_user(buf, ch368->mem_addr, count);return count - ret;
}static ssize_t ch368_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops)
{int ret;struct ch368_dev *ch368 = filp->private_data;count = count > ch368->mem_len ? ch368->mem_len : count;ret = copy_from_user(ch368->mem_addr, buf, count);return count - ret;
}static long ch368_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{union addr_data ad;struct ch368_dev *ch368 = filp->private_data;if (_IOC_TYPE(cmd) != CH368_MAGIC)return -ENOTTY;if (copy_from_user(&ad, (union addr_data __user *)arg, sizeof(union addr_data)))return -EFAULT;switch (cmd) {case CH368_RD_CFG:if (ad.addr > 0x3F)return -ENOTTY;pci_read_config_byte(ch368->pdev, ad.addr, &ad.data);if (copy_to_user((union addr_data __user *)arg, &ad, sizeof(union addr_data)))return -EFAULT;break;case CH368_WR_CFG:if (ad.addr > 0x3F)return -ENOTTY;pci_write_config_byte(ch368->pdev, ad.addr, ad.data);break;case CH368_RD_IO:ad.data = ioread8(ch368->io_addr + ad.addr);if (copy_to_user((union addr_data __user *)arg, &ad, sizeof(union addr_data)))return -EFAULT;break;case CH368_WR_IO:iowrite8(ad.data, ch368->io_addr + ad.addr);break;default:return -ENOTTY;}return 0;
}static struct file_operations ch368_ops = {.owner = THIS_MODULE,.open = ch368_open,.release = ch368_release,.read = ch368_read,.write = ch368_write,.unlocked_ioctl = ch368_ioctl,
};static int ch368_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{int ret;unsigned long io_start;unsigned long io_end;unsigned long io_flags;unsigned long io_len;void __iomem *io_addr = NULL;unsigned long mem_start;unsigned long mem_end;unsigned long mem_flags;unsigned long mem_len;void __iomem *mem_addr = NULL;struct ch368_dev *ch368;ret = pci_enable_device(pdev);if(ret)goto enable_err;io_start  = pci_resource_start(pdev, 0);io_end    = pci_resource_end(pdev, 0);io_flags  = pci_resource_flags(pdev, 0);io_len    = pci_resource_len(pdev, 0);mem_start = pci_resource_start(pdev, 1);mem_end   = pci_resource_end(pdev, 1);mem_flags = pci_resource_flags(pdev, 1);mem_len   = pci_resource_len(pdev, 1);if (!(io_flags & IORESOURCE_IO) || !(mem_flags & IORESOURCE_MEM)) {ret = -ENODEV;goto res_err;}ret = pci_request_regions(pdev, "ch368");if (ret)goto res_err;io_addr = ioport_map(io_start, io_len);if (io_addr == NULL) {ret = -EIO;goto ioport_map_err;}mem_addr = ioremap(mem_start, mem_len);if (mem_addr == NULL) {ret = -EIO;goto ioremap_err;}ch368 = kzalloc(sizeof(struct ch368_dev), GFP_KERNEL);if (!ch368) {ret = -ENOMEM;goto mem_err;}pci_set_drvdata(pdev, ch368);ch368->io_addr = io_addr;ch368->mem_addr = mem_addr;ch368->io_len = io_len;ch368->mem_len = mem_len;ch368->pdev = pdev;ch368->dev = MKDEV(CH368_MAJOR, minor++);ret = register_chrdev_region (ch368->dev, 1, CH368_DEV_NAME);if (ret < 0)goto region_err;cdev_init(&ch368->cdev, &ch368_ops);ch368->cdev.owner = THIS_MODULE;ret = cdev_add(&ch368->cdev, ch368->dev, 1); if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(ch368->dev, 1);
region_err:kfree(ch368);
mem_err:iounmap(mem_addr);
ioremap_err:ioport_unmap(io_addr);
ioport_map_err:pci_release_regions(pdev);
res_err:pci_disable_device(pdev);
enable_err:return ret;
}static void ch368_remove(struct pci_dev *pdev)
{struct ch368_dev *ch368 = pci_get_drvdata(pdev);cdev_del(&ch368->cdev);unregister_chrdev_region(ch368->dev, 1);iounmap(ch368->mem_addr);ioport_unmap(ch368->io_addr);kfree(ch368);pci_release_regions(pdev);pci_disable_device(pdev);
}static struct pci_device_id ch368_id_table[] =	
{{0x1C00, 0x5834, 0x1C00, 0x5834, 0, 0, 0},{0,}
};
MODULE_DEVICE_TABLE(pci, ch368_id_table);static struct pci_driver ch368_driver = {.name = "ch368",.id_table = ch368_id_table,.probe = ch368_probe,.remove = ch368_remove,
};module_pci_driver(ch368_driver);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("CH368 driver");
#ifndef _CH368_H
#define _CH368_Hunion addr_data {unsigned char addr;unsigned char data;
};#define CH368_MAGIC   'c'#define CH368_RD_CFG	_IOWR(CH368_MAGIC, 0, union addr_data)
#define CH368_WR_CFG	_IOWR(CH368_MAGIC, 1, union addr_data)
#define CH368_RD_IO	_IOWR(CH368_MAGIC, 2, union addr_data)
#define CH368_WR_IO	_IOWR(CH368_MAGIC, 3, union addr_data)#endif


        代码第 19 行至第 27 行是设备结构的定义,包含了保存映射之后的 IO 地址和内存地址的 io_addr 和 mem_addr 指针成员、保存 IO 地址空间大小和内存地址空间大小的io_len 和 mem_len 成员、保存 PCI 设备结构的 pdev 指针成员。该 PCI设备实现为一个字符设备,所以有 cdev 和 dev 成员。
        代码第 224 行至第 242 行是 PCI驱动结构的定义、注册和注销。ch368_id_table 是该驱动支持的设备列表,其中的 ID 号要和上图中的 ID 号一致。
        当有匹配的 PCI设备被检测到后,ch368_probe 函数自动被调用。代码第 134 行首先使能了 PCI 设备,代码第 138 行至第 146 行分别获取了 IO 和内存的物理地址、标志和长度信息。代码第 148 行至第 151 行判断了获取的标志内的资源类型信息,如果不和预期的相同,则设备探测失败。代码第 153 行至第 167 行申请了 PCI 设备所声明的资源,然后进行了映射,获得了对应的虚拟地址。代码第 169 行至第 180 行分配了 struct ch368_dev 结构的内存空间,并对各成员进行了相应的初始化,还使用 pci_set_drvdata函
 

        总线类设备驱动微将该结构地址保存在了 PCI 设备结构之中,方便之后从 PCI 设备结构中获得该地址该函数之后的代码是字符设备相关的注册操作。ch368 remove 做的工作和 h368 probe第亿函数相反。


        ch368_open 和 ch368_release 没有做太多的工作,ch368_read 和 ch368_write 则是针对内存空间的读和写,因为在这片内存空间没有对应外接的设备,所以没有实际意义。比较实际的操作是在 ch368_ioctl 中,CH368_RD_CFG 命今用来读取配置空间的数据,CH368_WR_CFG 命令用于向配置空间写入数据。CH368_RD_IO和CH368_WR_IO则分别是对 I/0 空间进行读和写。union addr_data 用于传送地址和返回数据,这和ADC 驱动的例子是类似的。
        应用层的测试代码如下
 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "ch368.h"int main(int argc, char *argv[])
{int i;int fd;int ret;union addr_data ad;unsigned char id[4];fd = open("/dev/ch368", O_RDWR);if (fd == -1)goto fail;for (i = 0; i < sizeof(id); i++) {ad.addr = i;if (ioctl(fd, CH368_RD_CFG, &ad))goto fail;id[i] = ad.data;}printf("VID: 0x%02x%02x, DID: 0x%02x%02x\n", id[1], id[0], id[3], id[2]);i = 0;ad.addr = 0;while (1) {ad.data = i++;if (ioctl(fd, CH368_WR_IO, &ad))goto fail;i %= 15;sleep(1);}
fail:perror("pci test");exit(EXIT_FAILURE);
}


        上面的代码在打开设备后先读取了配置空间的前 4 个字节,根据 PCI 规范,这4字节刚好是厂商ID 和设备ID。接下来在 while 循环中对 I/0 空间的第一个字节依次写入了0~15,这样 PCI 设备上的 4个 LED 灯就会按照此规律被点亮。前面说过,4个LED反映了写入 I/ O空间的数据的低 4 位的状态,数据位为 1 对应的灯被点亮,数据位为0对应的灯熄灭。
        使用下面的命令进行编译和测试。需要说明的是,需要有一台安装了 Linux 系统的物理机,并且物理机上要有对应的 PCIE 插槽才能插入该设备并进行测试。


五、 总线类设备驱动开发习题


1.I2C 总线协议规定是由 ( ) 来进行应答的。
[A] 数据发送者
[B] 数据接收者


2.I2C 总线协议规定所有访问都是由 ( )来发起的
[B] 从设备
[A] 主设备


3.SPI是 ( )总线。
[B] 异步
[A] 同步


4.SPI总线是 ( )的

[B] 半双工
[A] 单工

[C] 全双功


5.SPI 总线是 ( )的。

[A] 单主
[B] 多主


6.USB 的传输类型分为 (
[B] 等时传输
[A] 控制传输
[D] 块传输
[C] 中断传输
 


7.USB 的接口是由多个 ( ) 组成的。
[A] 配置
[B] 管道

[C] 端点


8.PCI的配置空间包括 ( ) 信息。
[B] 设备ID
[A] 厂商ID
[D] 地址空间大小
[C] 基地址

 

答案:B         A         A         A         C        ABCD        C        ABCD

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

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

相关文章

前端开发引入element plus与windi css

背景 前端开发有很多流行框架&#xff0c;像React 、angular、vue等等&#xff0c;本文主要讲vue 给新手用的教程&#xff0c;其实官网已经写的很清楚&#xff0c;这里再啰嗦只是为了给新手提供一个更加简单明了的参考手册。 一、打开element plus官网选则如图所示模块安装命令…

Nginx缓存基础

1 nginx缓存的流程 客户端需要访问服务器的数据时&#xff0c;如果都直接向服务器发送请求&#xff0c;服务器接收过多的请求&#xff0c;压力会比较大&#xff0c;也比较耗时&#xff1b;而如果在nginx缓存一定的数据&#xff0c;使客户端向基于nginx的代理服务器发送请求&…

华为L410上制作内网镜像模板02

原文链接&#xff1a;华为L410上制作离线安装软件模板02 hello&#xff0c;大家好啊&#xff0c;今天给大家带来第二篇在内网搭建Apache服务器&#xff0c;用于安装完内网操作系统后&#xff0c;在第一次开机时候&#xff0c;为系统安装软件的文章&#xff0c;今天给大家介绍在…

Linux之基础开发工具gdb调试器的使用(三)

文章目录 一、Linux调试器-gdb使用1、安装gdb2、背景3、Debug和release4、区分Debug和release 二、Linux调试器-gdb命令演示1、显示指定行之后的代码&#xff08;自动记录最后一条指令&#xff09;2、断点1、打印断点2、查看断点3、删除断点4、使能&#xff08;禁用/开启&#…

StartUML的基本使用

文章目录 简介和安装创建包创建类视图时序图 简介和安装 最近在学习一个项目的时候用到了StartUML来构造项目的类图和时序图 虽然vs2019有类视图&#xff0c;但是也不是很清晰&#xff0c;并没有生成uml图&#xff0c;但是宇宙最智能的IDE IDEA有生成uml图的功能 下面就简单介…

Flowable 外部表单

内置表单需要在每个节点中去配置&#xff0c;当如果多个节点使用同一套表单属性就要配置多次比较麻烦&#xff0c;修改的时候也要修改多次&#xff0c;外部表单可以定义一次&#xff0c;然后其它节点都去引用同一个表单属性。 外部表单需要定义一个.form后缀的文件。 外部表单…

关于值传递和引用传递的问题记录

目录 1. 问题概述 1.1 测试 1.2 结果 2. ArrayList和Arrays.ArrayList 1. 问题概述 最近忙着写论文很久没更新了&#xff0c;趁现在有时间简单记录一下最近遇到的一个坑。 对于Java中的List<>类型的对象&#xff0c;按我以前理解是引用传递&#xff0c;但有一点要注…

Excel中使用数据验证、OFFSET实现自动更新式下拉选项

在excel工作簿中&#xff0c;有两个Sheet工作表。 Sheet1&#xff1a; Sheet2&#xff08;数据源表&#xff09;&#xff1a; 要实现Sheet1中的“班级”内容&#xff0c;从数据源Sheet2中获取并形成下拉选项&#xff0c;且Sheet2中“班级”内容更新后&#xff0c;Sheet1中“班…

SMART PLC MODBUSTCP速度测试

SMART PLC MODBUSTCP通信详细介绍请参看下面文章链接: S7-200SMART PLC ModbusTCP通信(多服务器多从站轮询)_matlab sumilink 多个modbustcp读写_RXXW_Dor的博客-CSDN博客文章浏览阅读6.4k次,点赞5次,收藏10次。MBUS_CLIENT作为MODBUS TCP客户端通过S7-200 SMART CPU上的…

ARM寄存器及功能介绍/R0-R15寄存器

1、ARM 寄存器组介绍 ARM 处理器一般共有 37 个寄存器&#xff0c;其中包括&#xff1a; &#xff08;1&#xff09; 31 个通用寄存器&#xff0c;包括 PC&#xff08;程序计数器&#xff09;在内&#xff0c;都是 32 位的寄存器。 &#xff08;2&#xff09; 6 个状态寄存器…

【中间件篇-Redis缓存数据库02】Redis高级特性和应用(慢查询、Pipeline、事务、Lua)

Redis高级特性和应用(慢查询、Pipeline、事务、Lua) Redis的慢查询 许多存储系统&#xff08;例如 MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间&#xff0c;当超过预设阀值,就将这条命令的相关…

spring boot 中@Value读取中文配置时乱码

1.spring boot 读取application.properties 该文件是iso8859编码 如果是直接写中文 读取时会乱码 显示成?? 必须得转ascii码才能正常显示 其他方法测试也不行 Value("${apig.order.tiaokong.qianzi}") private String apigOrderTiaokongQianzi;

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错 当插入GPIB-USB设备时&#xff0c;看到了NI MAX中列出该设备&#xff0c;但却显示了黄色警告指示&#xff0c;并且指出Windows没有与您的设备相关的驱动程序。 解决方案 需要安装能兼容的NI-488.2驱动程序。 通过交叉参考以下有…

链表的实现(文末附完整代码)

链表的概念及结构 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的 我们在上一篇文章所学习的顺序表是连续存储的 例如&#xff1a; 顺序表就好比火车上的一排座位&#xff0c;是连续的 而链表就好比是火车…

xlua游戏热更新(lua访问C#)

CS.UnityEngine静态方法访问unity虚拟机 创建游戏物体 CS.UnityEngine.GameObject(new by lua);静态属性 CS.UnityEngine.GameObject(new by lua); -- 创建 local camera CS.UnityEngine.GameObject.Find(Main Camera); --查找 camera.name Renamed by Lua;访问组件 loca…

Swift--量值与基本数据类型

系列文章目录 第一章: Swift–量值与基本数据类型 文章目录 系列文章目录前言对学习过程做一个记录 变量和常量命名规范注释 元祖类型可选类型拆包 typealias 前言 对学习过程做一个记录 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 变量和常量 …

分享一下关于“vcruntime140_1.dll丢失的5种解决方法

今天我来给大家分享一下关于“vcruntime140_1.dll丢失的5种修复方法”的分享。首先&#xff0c;我们来了解一下vcruntime140_1.dll丢失的原因。 病毒感染&#xff1a;病毒或恶意软件可能损坏或删除vcruntime140_1.dll文件。 系统更新或软件安装&#xff1a;在进行系统更新或安…

vue+iView实现下载zip文件导出多个excel表格

1&#xff0c;需求&#xff1a;在vue项目中&#xff0c;实现分月份导出多个Excel表格。 点击导出&#xff0c;下载zip文件&#xff0c;解压出多张表数据。 2&#xff0c;关键代码&#xff1a; <Button class"export button-style button-space" click"ex…

【Bug】当用opencv库的imread()函数读取图像,用matplotlib库的plt.imshow()函数显示图像时,图像色彩出现偏差问题的解决方法

一&#xff0c;问题描述 我们在利用opencv的imread读取本地图像&#xff0c;进行一系列处理&#xff0c;但是发现用matplotlib库的imshow&#xff08;&#xff09;函数显示的时候出现色彩改变&#xff0c;比如图像偏黄&#xff0c;偏红&#xff0c;偏蓝等等&#xff0c;但是对…

智慧城市建设解决方案分享【完整】

文章目录 第1章 前言第2章 智慧城市建设的背景2.1 智慧城市的发展现状2.2 智慧城市的发展趋势 第3章 智慧城市“十二五”规划要点3.1 国民经济和社会发展“十二五”规划要点3.2 “十二五”信息化发展规划要点 第4章 大数据&#xff1a;智慧城市的智慧引擎4.1 大数据技术—智慧城…