I2C总线驱动——ap3216c光感传感器从寄存器手册开始入手的实战版(附思维导图)

文章目录

  • 1.I2C驱动框架简介
    • 1.1 I2C总线驱动(适配器驱动)
      • 1.1.1 重要结构体
      • 1.1.2 重要函数
    • 1.2 I2C设备驱动
      • 1.2.1 重要结构体
      • 1.2.2 重要函数
    • 1.3 I2C设备和驱动匹配过程
  • 2.I2C设备驱动编写
    • 2.1 确认原理图引脚及pinctrl子系统引脚配置信息
    • 2.2 确认设备树I2C节点信息
    • 2.3 编写主体框架代码
    • 2.4 实现module_init、exit
    • 2.5 实现probe、remove函数
    • 2.6 实现open、close、read等操作函数
    • 2.7 测试
  • 3.i2c通信时序
  • 4.思维导图

最近公司项目用到I2C陀螺仪,之前也学习过I2C子系统这块,但是稍微淡忘了些,所以特地来补一下这块,顺便整理成博客。
自己画的思维导图:
image.png
关于I2C通信时序,可用直接看第三章节中的链接

1.I2C驱动框架简介

IIC驱动框架,也是一个标准的platform驱动,其中分为I2C总线驱动和I2C设备驱动
I2C总线驱动:( 适配器驱动 )一般是原厂维护,主要是提供读写等API
I2C设备驱动:是针对具体I2C设备所编写的驱动

IIC总线驱动也遵循驱动、设备、总线的匹配规则,设备和驱动匹配成功后,probe函数便会执行,probe函数在i2c_driver中,我们主要需要实现的也是driver这部分。
其中i2c_device_match完成IIC总线的设备和驱动匹配。
i2c_client:描述设备信息
i2c_driver:描述驱动信息
i2c_adapter:I2C总线驱动

这里借用一张其他地方看到的图,描述的比较清晰
image.png

1.1 I2C总线驱动(适配器驱动)

i2c总线驱动一般由原厂完成,主要工作是初始化i2c_adapter结构体,设置i2c_algorithm中的master_xfer函数(i2c适配器传输函数,该函数完成IIC通信),再通过i2c_add_adapter向系统注册i2c_adapter结构体。

1.1.1 重要结构体

i2c_adapter:i2c适配器

struct i2c_adapter {
    struct module *owner;
    unsigned int class;       /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* the algorithm to access the bus */
    void *algo_data;

    /* data fields that are valid for all devices   */
    struct rt_mutex bus_lock;

    int timeout;            /* in jiffies */
    int retries;
    struct device dev;      /* the adapter device */

    int nr;
    char name[48];
    struct completion dev_released;

    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;

    struct i2c_bus_recovery_info *bus_recovery_info;
    const struct i2c_adapter_quirks *quirks;
};

ii2c_algorithm:总线访问算法

struct i2c_algorithm {
	/* If an adapter algorithm can't do I2C-level access, set master_xfer
	   to NULL. If an adapter algorithm can do SMBus access, set
	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
	   using common I2C messages */
	/* master_xfer should return the number of messages successfully
	   processed, or a negative value on error */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* To determine what the adapter supports */
	u32 (*functionality) (struct i2c_adapter *);

};

functionality:返回I2C适配器支持什么样的通信协议

1.1.2 重要函数

/// 向Linux内核添加i2c适配器
/// 返回值0成功,负值失败
int i2c_add_adapter(struct i2c_adapter *adapter)	/// 动态总线号
int i2c_add_numbered_adapter(struct i2c_adapter *adap) /// 静态总线号
/// 删除Linux内核适配器
void i2c_del_adapter(struct i2c_adapter * adap)

1.2 I2C设备驱动

1.2.1 重要结构体

i2c_client:描述设备信息,每检测到一个i2c设备,分配一个i2c_client

struct i2c_client {
	unsigned short flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	char name[I2C_NAME_SIZE];
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct device dev;		/* the device structure		*/
	int irq;			/* irq issued by device		*/
	struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

i2c_driver:描述驱动信息,重点处理的结构体,其中包括probe函数,remove函数等

struct i2c_driver {
	unsigned int class;
	/* Notifies the driver that a new bus has appeared. You should avoid
	 * using this, it will be removed in a near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);
	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);
	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);
	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
	struct device_driver driver;
	const struct i2c_device_id *id_table;
	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;
};

i2c_msg:I2C传参结构体,用于i2c_transfer传输数据
device_driver:其中包含设备树匹配方式of_match_table(compatible属性在其中),以及acpi_match_table
id_table:未使用设备树的设备匹配ID表

1.2.2 重要函数

#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)
/// 注册i2c driver,当已经注册过时,需要先进行del
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/// 注销i2c_driver
void i2c_del_driver(struct i2c_driver *driver)
/// 发送I2C数据
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)   

adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。

其中i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,i2c驱动具有中间大两边小的特点。中间你无论是哪个厂家,无论你使用什么算法,最终提供给驱动开发人员的接口一定是i2c_transfer()接口,向下使用的文件一定是i2c-algo-bit.c文件中的函数产生波形

1.3 I2C设备和驱动匹配过程

I2C设备和驱动的匹配过程由I2C总线来完成,drivers/i2c/i2c-core.c、i2c_bus_type是i2c总线的核心部分,调用其中的i2c_device_match函数完成匹配。

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	if (!client)
		return 0;

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);
	/* match on an id table if there is one */
	if (driver->id_table)
		return i2c_match_id(driver->id_table, client) != NULL;

	return 0;
}

依旧是匹配三部曲:设备树compatible属性匹配->acpi形式匹配->无设备树的id_table name属性方式匹配

2.I2C设备驱动编写

现在主流基本都会使用设备树来管理,如果不使用设备树的话,则是借助i2c_board_info结构体来描述I2C设备信息,当使用设备树则按照下面的步骤,一步步完成一个I2C设备驱动编写/适配!

2.1 确认原理图引脚及pinctrl子系统引脚配置信息

这里我使用的I2C设备是AP3216C,一款光感sensor,原理图如下。
image.png
image.png
其中sensor使用的是I2C1_SCL以及I2C1_SDA,所以先确定这两个引脚的引脚复用,也就是pinctrl子系统配置是否正确。

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>; ///< pinctrl设备节点为pinctrl_i2c1
	status = "okay";
    ..... //省略
}

在imx6ull-14x14-emmc-4.3-480x272-c.dts设备树文件下搜索后能看到引脚复用信息如下,已经复用为I2C1_SCL以及I2C1_SDA,所以不用修改。而如果这里的引脚复用未配置,则需要进行修改,需根据自己的实际情况。

pinctrl_i2c1: i2c1grp {
    fsl,pins = <
        MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
        MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
    >;
};

2.2 确认设备树I2C节点信息

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";
        codec: wm8960@1a {
                compatible = "wlf,wm8960";
                reg = <0x1a>;
                clocks = <&clks IMX6UL_CLK_SAI2>;
                clock-names = "mclk";
                wlf,shared-lrclk;
        };
	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
		status = "disabled";
	};
	ap3216c@1e {
		compatible = "alientek,ap3216c";
		reg = <0x1e>;
	};
};

添加ap3216c节点信息,ap3216c@1e是节点名字,0e/1e这种则是I2C器件地址,compatibel属性为ap3216c,用这个属性进行设备和驱动的匹配,reg属性也是I2C器件地址,时钟频率为100KHz。
如果设备树编写的没问题,在替换完dtb文件到开发板后,能cat到相应的属性。这里我是从虚拟机拷贝dtb文件到板子的emmc上,具体操作需要根据情况而定。
scp imx6ull-14x14-emmc-4.3-480x272-c.dtb root@169.254.113.91:/run/media/mmcblk1p1

root@ATK-IMX6U:/run/media/mmcblk1p1# cat /sys/bus/i2c/devices/0-001e/name
ap3216c

其中001e是设备树中器件的节点地址,name为ap3216c

2.3 编写主体框架代码

主要是实现module_init、module_exit、probe函数、remove函数、以及file_operations操作函数等,这个几乎所有驱动框架是通用的

2.4 实现module_init、exit

在module_init时,进行i2c_add_driver,exit时,则是进行i2c_del_driver的操作

/// 传统方式匹配
static const struct i2c_device_id ap3216c_id[] = {
    {"alientek,ap3216c", 0}, 
    {}
};

/// 设备树方式匹配
static const struct of_device_id ap3216c_of_match[] = {
    {.compatible = "alientek,ap3216c"}
};

static struct i2c_driver ap3216c_driver = {
    .probe = ap3216c_probe,
    .remove = ap3216c_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "ap3216c",
        .of_match_table = ap3216c_of_match,
    },
    .id_table = ap3216c_id,
};

static int __init ap3216c_init(void)
{
    int ret = 0;

    printk("ap3216c driver init begin\n");
    ret = i2c_add_driver(&ap3216c_driver);
    printk("ap3216c driver init done, ret:%d\n", ret);

    return ret;
}

static int __exit ap3216c_exit(void)
{
    printk("ap3216c driver exit begin\n"); 
    i2c_del_driver(&ap3216c_driver);
    printk("ap3216c driver exit done\n"); 

    return 0;
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xuzhangxin");

2.5 实现probe、remove函数

probe中进行申请设备号,cdev_init、cdev_add、class_create、device_create等

static struct file_operations ap3216c_fops = {
    .owner = THIS_MODULE,
    .open = ap3216c_open,
    .release = ap3216c_close,
    .read = ap3216c_read,
};

int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    printk("ap3216c driver probe begin\n");

    ///1.申请设备号
    if (ap3216c_dev.major) {
        ap3216c_dev.devid = MKDEV(ap3216c_dev.major, 0);
        register_chrdev_region(ap3216c_dev.devid, 1, AP3216C_NAME);
    } else {
        alloc_chrdev_region(&ap3216c_dev.devid, 0, 1, AP3216C_NAME);
        ap3216c_dev.major = MAJOR(ap3216c_dev.devid);
    }

    ///2.cdev_init、cdev_add
    cdev_init(&ap3216c_dev.m_cdev, &ap3216c_fops);
    cdev_add(&ap3216c_dev.m_cdev, ap3216c_dev.devid, 1);

    ///3.class_create
    ap3216c_dev.m_class = class_create(THIS_MODULE, AP3216C_NAME);
    if (IS_ERR(ap3216c_dev.m_class)) {
        return PTR_ERR(ap3216c_dev.m_class);
    }

    ///4.device_create    
    ap3216c_dev.m_dev = device_create(ap3216c_dev.m_class, NULL, ap3216c_dev.devid, NULL, AP3216C_NAME);
    if (IS_ERR(ap3216c_dev.m_dev)) {
        return PTR_ERR(ap3216c_dev.m_dev);
    }

    ap3216c_dev.client = client;

    printk("ap3216c driver probe done\n");
    return 0;
}

int ap3216c_remove(struct i2c_client *client)
{
    printk("ap3216c driver remove begin\n");
    ///cdev_del
    cdev_del(&ap3216c_dev.m_cdev);

    ///注销设备号
    unregister_chrdev(ap3216c_dev.devid, AP3216C_NAME);

    ///device_destroy
    device_destroy(ap3216c_dev.m_class, ap3216c_dev.devid);
    
    ///class_desroy
    class_destroy(ap3216c_dev.m_class);
    
    printk("ap3216c driver remove done\n");
    return 0;
}

2.6 实现open、close、read等操作函数

从这边开始的话,就要去看器件相应的寄存器手册了,这里我将整个的思路都尽可能写清楚,强化自己也方便大家,不需要了解这么详细的话也可以直接看下面的源码。

1)实现写i2c数据接口
寄存器手册中,有描述该器件I2C 写数据的协议。
首先是发送1bit开始信号+7bit的slave address(从设备地址),
此时从设备回1 bitACK,主设备继续发送8bit register address(寄存器地址),每收到一个字节数据,从设备拉低数据线产生一个应答,最后主设备再发送停止位
image.png
根据这里的i2c时序,可以先封装出一个i2c_write_reg的函数,用于设置System Configuration寄存器

static int ap3216c_write_reg(struct ap3216c_dev_t *dev, u8 reg, u8 *data, u16 len)
{
    int ret = 0;
    struct i2c_client* client = (struct i2c_client*)dev->client;
    struct i2c_msg msg;

    msg.addr = client->addr;            ///< 设备地址
    msg.flags = 0;                      ///< 写数据
    msg.buf[0] = reg;                   ///< 操作寄存器地址
    memcpy(&msg.buf[1], data, len);     ///< 写寄存器的数据
    msg.len = len + 1 ;                 ///< 1byte寄存器地址+data的长度    
    return i2c_transfer(client->adapter, &msg, 1);
}

2)实现读i2c数据接口
1bit开始信号+7bit的slave address(从设备地址)+1bit写数据位,此时从设备拉低数据线,给一个应答位,再发送8it的reg addr寄存器地址,从设备应答,主设备再次发送1bit的开始信号+7bit slave address+1bit读数据位+8bit寄存器地址,最后主设备作为接收方,收到最后一个字节数据后,主设备拉高SDA发送一个NACK信号,通知发送端结束数据发送,最后发送一个停止信号,完成数据的读取
image.png

static int ap3216c_read_reg(struct ap3216c_dev_t *dev, u8 reg, u8 *data, u16 len)
{
    int ret = 0;
    struct i2c_client* client = (struct i2c_client*)dev->client;
    struct i2c_msg msg[2];

    msg[0].addr = client->addr;             ///< 设备地址
    msg[0].flags = 0;                       ///< 写数据
    msg[0].buf = &reg;                    ///< 操作寄存器地址
    msg[0].len = 1 ;                        ///< 1byte寄存器地址 

    msg[1].addr = client->addr;            ///< 设备地址
    msg[1].flags = I2C_M_RD;               ///< 读数据
    msg[1].buf = data;                     ///< 读取数据缓冲区
    msg[1].len = len;                      ///< 读取数据长度 

    return i2c_transfer(client->adapter, &msg, 2);
}

3)实现fops->open接口,接口中配置设备工作模式
主要看我们需要采集哪些数据,来决定配置的模式
首先找到System Configuration寄存器,这个寄存器用于配置器件的工作模式,bit0-bit2有效,bit3-bit7无效image.png
image.png
image.png
关于它的不同工作模式,我找到的这篇博客:https://developer.aliyun.com/article/1083178
直接选用ALS+PS+IR 模式,同时对光强度及接近程度测量,也就是把mode配置为011
另外根据寄存器手册中的说明,设置完SW reset后,需要等待10ms,再设置真正的工作模式
image.png
所以最终open的代码如下:

static int ap3216c_open(struct inode *node, struct file *file)
{
    u8 value = 0x00;
    printk("ap3216c driver open\n");
    file->private_data = (void *)&ap3216c_dev; ///< 设置私有数据
    
    
    ap3216c_write_reg(file->private_data, AP3216C_SYSTEMCONG, &value, 1);  ///< set sw reset
    mdelay(20); ///< 最少等待10ms
    value = 0x03;
    ap3216c_write_reg(file->private_data, AP3216C_SYSTEMCONG, &value, 1);  ///< 设置为ALS and PS+IR模式

    printk("ap3216c driver open success\n");
    return 0;
}

4)实现fpos->close接口

static int ap3216c_close(struct inode *node, struct file *file)
{
    u8 value = 0x00;
    printk("ap3216c driver close\n");
    file->private_data = (void *)&ap3216c_dev; ///< 设置私有数据
    
    
    ap3216c_write_reg(file->private_data, AP3216C_SYSTEMCONG, &value, 1);  ///< set sw reset
    
    printk("ap3216c driver close success\n");
    return 0;
}

5)实现fops->read接口
读以下几个寄存器,获取IR、ALS、PS寄存器的值,其中若IR Data Low寄存器中bit7的值为1,则代表IR数据无效,否则代表数据有效。
image.png
image.png
image.png
image.png
所以这里读出IR Data Low寄存器的值val & 0x80,判断最高位是否为1,为1则IR寄存器的数据无效,为0则数据有效。PS寄存器同样也有这种机制,判断方法是一样的

static ssize_t ap3216c_read(struct file *file, char __user *buf, size_t cnt, loff_t *off)
{
    u8 data[3] = {0};
    u8 read_buf[AP3216C_DATA_REG_NUM] = {0};
    u8 i = 0;
    int ret = 0;
    printk("ap3216c driver read\n");

    file->private_data = (void *)&ap3216c_dev; ///< 设置私有数据

    /// 循环将IR、ALS、PS中的寄存器数据读取
    for (i = 0; i < AP3216C_DATA_REG_NUM; i++) {
        ret = ap3216c_read_reg(file->private_data, AP3216C_IRDATALOW + i, read_buf, 6);
        printk("ap3216c driver read ret:%d, reg:%x, value:%d\n", ret, AP3216C_IRDATALOW + i, read_buf[i]);
    }

    if (read_buf[0] & 0x80) {
        data[0] = 0;
        printk("ap3216c driver read ir data error\n");
    } else {
        data[0] = (read_buf[1] << 2) | read_buf[0];
        printk("ap3216c driver read ir data success, data:%x\n", data[0]);
    }

    data[1] = (read_buf[3] << 8) | read_buf[2];
    printk("ap3216c driver read als data success, data:%x\n", data[1]);


    if (read_buf[4] & 0x80) {
        data[2] = 0;
        printk("ap3216c driver read ps data error\n");
    } else {
        data[2] = ((read_buf[5] & 0x3f) << 4) | (read_buf[4] & 0x0f);
        printk("ap3216c driver read ir data success, data:%x\n", data[0]);
    }

    printk("ap3216c driver read als data success, data:%x\n", data[1]);

    if (copy_to_user(buf, data, sizeof(data) / sizeof(data[0])) != 0) {
        return -1;
    }


    printk("ap3216c driver read sucess, ret:%d\n", ret);
    return ret;
}

2.7 测试

这里写了一个测试代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"



int main(int argc, char **argv)
{
    char r_buf[3] = {0};
    int count = 100;
    int fd = open("/dev/ap3216c", O_RDWR);
    if (fd < 0)
    {
        printf("open ap3216c failed\n");
        return -1;
    }
    printf("open ap3216c success\n");
    while (count--) {
        read(fd, r_buf, sizeof(r_buf) / sizeof(r_buf[0]));
        printf("read ap3216c ir:%d\n", r_buf[0]);
        printf("read ap3216c als:%d\n", r_buf[1]);
        printf("read ap3216c ps:%d\n", r_buf[2]);
        usleep(200 * 1000);
    }
    close(fd);


    return 0;
}
insmod i2c_ap3216.ko
./test

image.png

3.i2c通信时序

这里暂时自己还没有写相关博客,可以参考下其他人写的
I2C通信时序

4.思维导图

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

华为数通企业面试笔试实验题

1. 笔试题 1.1 实验拓扑 1.2 实验要求 公司A为小型销售公司,需要实现基本上网功能,蓝色部分为外网线,提供DHCP服务 DnsServer:114.114.114.114 帮助网管排查某一台计算机在某一台交换机的某个端口 2. 操作步骤 配置路由器相关的LAN侧接口IP地址 配置DHCP项,要求有PC1与PC2…

个人学习算法总结的基础crud与算法思想数据结构解释

建议都从简单的crud入手,结合生活理解了结构与操作在去进阶更难的东西,做事有规划有步骤有时间限制这样比较好进步 跳转阅读

xxe漏洞学习

一、什么是xxe漏洞 XXE就是XML外部实体注入&#xff0c;当允许引用外部实体时&#xff0c; XML数据在传输中有可能会被不法分子被修改&#xff0c;如果服务器执行被恶意插入的代码&#xff0c;就可以实现攻击的目的攻击者可以通过构造恶意内容&#xff0c;就可能导致任意文件读…

Java图形用户界面设计的布局管理器

LayoutManager布局管理器 前言一、布局管理器的背景简介 二、FlowLayout构造方法参数说明代码演示AWTSwing 三、BorderLayout布局管理器注意点构造方法代码演示AWT示例一示例二 Swing 四、GridLayout简介构造方法代码示例AWTSwing 五、GridBagLayoutGridBagConstraints APIGrid…

时间同步概念及常见的时间同步协议NTP PTP

一、前言 前面几篇文章介绍了Linux中的各种各样的时间、时钟源以及时间维护的方式&#xff0c;其中在timekeeper等数据结构中&#xff0c;我们当时略过了NTP相关的字段&#xff0c;为了补充这一段内容&#xff0c;从本篇开始会介绍时间同步的基本概念、及常见的时间同步协议&am…

0617_QT3

练习&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//去掉头部this->setWindowFlag(Qt::FramelessWindowHint);//去掉空白部分this->setA…

YOLOv10项目-服务器上运行

1、前言 2、运行YOLOv10代码流程&#xff08;超详细&#xff09; &#xff08;3&#xff09;根据下面步骤安装&#xff1a; &#xff08;4&#xff09;数据集和其他配置 &#xff08;5&#xff09;测试训练&#xff08;很详细&#xff09; 1、前言 由于一些事情&#xff0…

java文件传输小工具 java17+springboot3+thymeleaf

背景 在和同事工作中经常需要传输文件&#xff0c;但是公网传输太慢&#xff0c;业务方不是计算机专业直接用命令行沟通麻烦。 本小工具通过页面可视化方便用户使用&#xff0c;端口9090&#xff0c;启动默认展示当前登陆本机用户的桌面。 代码开源&#xff1a; https://git…

制作翻页电子版画册攻略:轻松掌握数字创作技巧

​随着科技的飞速发展&#xff0c;数字创作已经成为了一种流行的创作方式。如今&#xff0c;越来越多的人选择将纸质画册转化为翻页电子版画册&#xff0c;以便更好地展示和传播自己的作品。你也想掌握这项技能&#xff0c;但却苦于不知从何入手&#xff1f;接下来教你制作翻页…

Redis-数据结构-跳表详解

Redis概述 Redis-数据结构-跳表详解 跳表&#xff08;Skip List&#xff09;是一种基于并联的链表结构&#xff0c;用于在有序元素序列中快速查找元素的数据结构。 Redis 中广泛使用跳表来实现有序集合&#xff08;Sorted Set&#xff09;这一数据结构。 1.跳表的基本概念和…

怎么做成的文件二维码?扫阅览文件的制作方法

现在用二维码来分享或者查看文件是一种很常用的方式&#xff0c;比如常见的文件内容有简历、资料、作品、压缩包等等。通过将文件生成二维码能够在提升文件传输速度的同时还有利于用户体验的提升&#xff0c;那么如何制作可以长期提供文件预览或者下载的二维码呢&#xff1f; …

python之Bible快速检索器

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! python之Bible快速检索器 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; 助力快…

Python基础用法 之 转义字符

将两个字符进⾏转义 表示⼀个特殊的字符 \n ---> 换⾏&#xff0c;回⻋ \t ---> 制表符, tab键 注意&#xff1a; print( end\n)&#xff1a; print() 函数中默认有⼀个 end\n, 所以,每个 print 结束之后, 都会输出⼀ 个换行。 未完待续。

大腾智能正式入驻华为云

5月30日&#xff0c;大腾智能正式入驻华为云云商店。作为一家基于云原生的国产工业软件与数字化协同平台&#xff0c;大腾智能专注于推动企业数字化转型与升级&#xff0c;为企业提供一系列专业、高效的云原生数字化软件及方案。 华为云云商店&#xff0c;作为业界标杆&#xf…

眼动研究实验设计方法

摘要 本文对基于实验室的眼动实验设计进行了总体回顾&#xff0c;并侧重于回顾实验程序和方法&#xff0c;从而为眼动追踪实验提供一个框架或背景。本文内容涵盖了基本的实验设计&#xff0c;这与实验心理学课本没有太大的区别&#xff0c;其中析因设计在眼动追踪研究中特别受…

MinIO安装、与SpringBoot整合、常见方法

系统学习&#xff1a;若依框架&#xff08;整合了MinIO&#xff09;介绍 | RuoYi MinIO MinIO是一个高性能的对象存储系统&#xff0c;专为大规模数据存储、管理和访问而设计。以下是关于MinIO的详细解析&#xff1a; 1. 基本概念 定义&#xff1a;MinIO是一个基于Amazon S3…

DY-34/60C电压继电器 带板前底座 约瑟JOSEF

系列型号&#xff1a; DY-32电压继电器&#xff1b;DY-36电压继电器&#xff1b; DY-33电压继电器&#xff1b;DY-37电压继电器&#xff1b; DY-34电压继电器&#xff1b;DY-38电压继电器&#xff1b; DY-31电压继电器&#xff1b;DY-35电压继电器&#xff1b; DY-32/60C电…

定时器介绍之8253芯片

目录 定时器简介 8253功能介绍 组成 工作原理 相关引脚 启动方法 计数方式 实现 读取计数值 定时器简介 8253功能介绍 内部结构 相关引脚 计数器组成 工作原理 启动方法 计数方式 初始化&#xff1a;写入控制字——>写入计数初值 实现 计数长度选择&#xff1a…

1832javaERP管理系统之实践教学管理Myeclipse开发mysql数据库servlet结构java编程计算机网页项目

一、源码特点 java erp管理系统之实践教学管理是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了servlet设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Mye…

7.无代码爬虫八爪鱼采集器软件——采集规则/项目的创建与网址输入

接上篇 6.零代码网页爬虫软件基础实操——下载与安装八爪鱼采集器 八爪鱼免费爬虫软件下载&#xff1a; 八爪鱼采集器下载 小白数据采集神器​​https://affiliate.bazhuayu.com/retrieve 直接复制粘贴要采集的网站在这里就可以进入采集规则的设计器 自定义任务 通过这个功能…