Linux驱动学习—I2C总线

news2025/4/25 13:02:33

1、应用层实现I2C通信

1.1 I2C简介

I2C是很常见的一种总线协议,I2C是NXP公司设计的,I2C使用两条线在主控制器和从机之间进行数据通信。一条是SCL(串行时钟线),另外一条是SDA(串行数据线),因为I2C这两条数据线是开漏输出的,所以需要接上拉电阻,总线空闲的时候SCL和SDA处于高电平。I2C总线标准模式下速度可以达到100Kb/s,快速模式下可以达到400kb/s。如果大家玩过51单片机,肯定对模拟I2C时序这个操作并不陌生,但是在Linux上,还需要我们继续来模拟I2C的时序吗,答案是不需要的,cpu会自带I2C控制器,有了这个I2C控制器后,就不需要模拟时序了,只需要关心怎么把数据写到寄存器和怎么从寄存器读数据即可,具体的时序都是由I2C控制器来帮我们自动完成。

1.2 如何查看板子系统上有几个i2c?

Linux把I2C控制器抽象成一个i2c_adapter,我们只要来分配这个i2c_adapter,就可以得到一个I2C控制器。我们可以先来体验一下,在Linux上操作I2C是多模任意,先来看一下系统里面都有哪些I2C的节点,在开发板串口输入:

ls /dev/i2c-*

查看i2c节点:

Linux有一个非常重要的概念是一切皆文件,那么我们能不能在应用层通过open这些节点来操作I2C来跟外设I2C通信的芯片进行数据交互呢?当然是可以的,我们来以前看一下,这里我们以7寸RGB屏幕上的触摸芯片FT5X06为例。

通过原理图来确定FT5X06使用的是哪个I2C,通过下面的截图我们可以得到在开发板上,触摸芯片FT5X06使用的是I2C2,对应的节点是dev下面的i2c-1。那么跟触摸芯片FT5X06进行通信,是不是操作dev下的i2c-1这个节点就可以了?

1.3 数据包的结构体是i2c_rdwr_ioctl_data及i2c_msg

怎么在应用层操作I2C呢,应用层操作I2C是以数据包进行交流的,所有我们在应用层就要进行封包的操作。数据包对应的结构体是i2c_rdwr_ioctl_data,这个结构体在include\uapi\linux\i2c-dev.h下面,定义如下:

struct i2c_rdwr_ioctl_data {
    struct i2c_msg __user *msgs;    /* pointers to i2c_msgs 要发送的数据包的指针*/
    __u32 nmsgs;            /* number of i2c_msgs发送数据包的个数 */
};

再来看一下i2c_msg结构体的定义,这个结构体是定义在include\uapi\linux\i2c.h下面,定义如下:

struct i2c_msg {
    __u16 addr; /* slave addres 从机地址*/
    __u16 flags ;/*读写标志位,为1表示为读,反之为0,则为写*/
#define I2C_M_RD        0x0001  /* read data, from slave to master */
                    /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP      0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
    __u16 len;      /* msg length 为buf的大小,单位是字节*/
    __u8 *buf;      /* pointer to msg data  当flags为1是,buf是要接受的数据,当flags为0,就是要发送的数据*/
};

1.4 应用程序编写

那么怎么设计程序呢,首先要看一下触摸芯片的数据手册:

了解相关的寄存器后,就可以开始写程序了

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/inpuit.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#incldue <sys/ioctl.h>
​
int fd;
​
int i2c_read_data(unsigned int slave_addr, unsigned char reg_addr)
{
    unsigned char data;
    struct i2c_rdwr_ioctl_data i2c_read_lcd;
    struct i2c_msg msg[2] = {
        [0] = {//第一个数据包先写要操作的寄存器的地址
            .addr = slave_addr,
            .flags = 0,
            .buf = &reg_addr,
            .len = sizeof(reg_addr)
        },
        [1] = {//第二个数据包再读这个寄存器的数据
            .addr = slave_addr,
            .flags = 1,
            .buf = &data,
            .len = sizeof(data)
        },
    };
    
    i2c_read_lcd.msgs = msg;
    i2c_read_lcd.nmsgs = 2;
    
    ret = ioctl(fd, I2C_RDWR, &i2c_read_lcd);
    if (ret < 0) {
        perror("ioctl errror is:");
        return ret;
    }
    
    return data;
}
​
int main(int argc, char *argv[])
{
    int TD_STATUS;
    fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) {
        perror("open error");
        return fd;
    }
    while (1) {
        TD_STATUS = i2c_read_data(0x38,0x02);
        printf("TD_STATUS value is %d\n", TD_STATUS);
        sleep(1);
    }
    return 0;
}

编译并在开发板上运行这个app:

2、I2C总线实现clien设备

2.1 Linux I2C驱动框架简介

Linux中的I2C也是按照平台总线模型设计的,既然也是按照平台总线模型设计的,是不是也分为一个device和一个driver呢?但是I2C这里的device不叫device,也叫client。platform是虚拟出来的一条总线,目的是未来实现总线、设备、驱动框架。对于I2C而言,不需要虚拟出一条总线,直接使用I2C总线即可。

同样,这里先从非设备树开始,先看一下再没有设备树之前怎么实现的I2C的device部分,也就是client部分。然后再学习有了设备树之后,我们的client是怎么编写的,按照Linux的发展路径来学习。

在没有使用设备树之前,我们使用的是i2c_board_info这个结构体来描述一个I2C设备的, i2c_board_info 这个结构体如下,在include/linux/i2c.h:

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];
    unsigned short  flags;
    unsigned short  addr;
    void        *platform_data;
    struct dev_archdata *archdata;
    struct device_node *of_node;
    struct fwnode_handle *fwnode;
    int     irq;
};

在上面的这个结构体中,type和addr这两个成员变量是必须要设置的,一个是I2C设备的名字,这个名字就是用来进行匹配用的,一个是I2C设备的器件地址。也可以使用宏,在include/linux/i2c.h::

#define I2C_BOARD_INFO(dev_type, dev_addr) \
    .type = dev_type, .addr = (dev_addr)

可以看出,I2C_BOARD_INFO宏其实就是设置i2c_board_info的 type 和 addr这两个成员变量。

2.2 I2C核心提供的具体硬件无关的API函数

I2C设备和驱动的匹配过程是由I2C核心来完成的,在Linux源码的drivers/i2c/i2c-core.c就是I2C的核心部分,I2C核心提供了一些与具体硬件无关的函数,如下:

2.2.1 i2c_get_adapter函数

作用:获得一个I2C适配器。

struct i2c_adapter *i2c_get_adapter(int nr);
参数:
nr:要获得的哪个I2C适配器的编号。
返回值:失败返回NULL。
2.2.2 i2c_put_adapter函数

作用:释放I2C适配器。

void i2c_put_adapter(struct i2c_adapter *adap);
参数:
adap:要释放I2C适配器。
返回值:失败返回NULL。
2.2.3 i2c_new_device函数

作用:把I2C适配器和I2C器件关联起来。

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info);
参数:
adap:I2C适配器。
info:i2c_board_info的指针。
返回值:失败返回NULL。
2.2.4 i2c_unregister_device函数

作用:注销一个client。

void i2c_unregister_device(struct i2c_client *client);
client:i2c_client的指针。

2.3 如果使用设备树要怎么描述硬件信息呢?

在使用设备树以后,就不用这么复杂了,使用给设备树的时候只要在对应的I2C节点下创建相应设备的节点即可,比如我想添加一个触摸芯片FT5X06的设备,我就可以在对应的I2C的节点下这样写:

注意:这里使用的是10.1寸的触摸芯片gt911,4.3寸触摸芯片是tsc2007.其他都是ft5426。

查看对应设备树节点:

注意:我们使用的是I2C2,上图设备树节点看是1-0038,为啥是1呢,因为平台上的是I2C2,是从1开始计数的,而这里是从0开始计数的。

2.4 不用设备树的方法怎么描述硬件信息

下面演示不用设备树的方法进行实验,

<1> 首先先要去掉设备树上的节点:

<2> 再make menuconfig把相关i2c驱动注释掉:

注释掉之后编译内核源码,并烧写至系统,会发现看不到设备节点了

现在运行编译脚本就不会把这个设备驱动编译进去了

<3> 最后编写实现client驱动源码,并加载到系统上,会发现可以重新看到有对应的设备节点了
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
//分配一个I2C适配器指针
struct i2c_adapter *i2c_ada;
​
//分配一个i2c_client指针
struct i2c_client *i2c_client;
​
//支持的I2C的设备列表
struct ic_board_info ft5x06_info[] = {
    //每一项都代表一个I2C设备,这句话的意思就是说这个设备的名字代表ft5x06_test,器件地址是0x38
    {I2C_BOARD_INFO("ft5x06_test", 0x38)},
};
​
static int ft5x06_client_init(void)
{
    //调用 i2c_get_adapter ,获得一个I2C总线,因为ft5x06是挂载动力I2C2上,所以这个参数就是1,所以这句代码的意思就是把这个触摸触摸芯片挂载到i2c2上.
    i2c_ada = i2c_get_adapter(1);
    //把I2C适配器和I2C器件关联起来 
    i2c_new_device(i2c_ada, ft5x06_info);
    //释放I2C控制器
    i2c_put_adapter(i2c_ada);
    printk("This is ft5x06_client_init\n");
    return0;
}
​
static void ft5x06_client_exit(void)
{
    i2c_unsigned_device(i2c_client);
    printk("This is ft5x06_client_exit\n");
}
​
module_init(ft5x06_client_init);
module_exit(ft5x06_client_exit);
MODULE_LICENSE("GPL");

挂载驱动,可以看到有对应的设备节点了:

3、I2C总线实现driver驱动

上面实现了client部分,然后我们再来看driver部分。不管是使用设备树还是非设备树,driver部分就比较复杂了。和注册一个杂项设备或者是字符设备的套路一样,也是要先顶一个i2c_driver的结构体,然后再对他进行初始化,下面先看一下这个结构体的定义,如下图所示:

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").
     * For the SMBus Host Notify protocol, the data corresponds to the
     * 16-bit payload data reported by the slave device acting as master.
     */
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
              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_driver注册进内核,注册进内核我们使用的是i2c_add_driver。

<1>i2c_add_driver函数宏

作用:注册一个i2c驱动。

#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)
参数:
driver: struct i2c_driver的指针。
返回值:失败返回负值。
<2>i2c_del_driver函数

作用:删除一个i2c驱动。

extern void i2c_del_driver(struct i2c_driver *);
参数:driver: struct i2c_driver的指针。
返回值:失败返回负值。
<3>驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {
    {"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {
    {.compatible = "edt,edt-ft5306", 0},
    {.compatible = "edt,edt-ft5x06", 0},
    {.compatible = "edt,edt-ft5406", 0},
};
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{
    printk("This is ft5x06_probe\n");
    //注册一个杂项设备 或 注册一个字符设备
    return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}
static struct i2c_driver ft5x06_driver = {
    .driver = {
        .owner = YHIS_MODULE,
        .name = "ft5x06_test",
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{
    int ret;
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    printk("This is ft5x06_driver_init\n");
    return 0;
}
​
static void ft5x06_driver_exit(void)
{
    i2c_del_driver(&ft5x06_driver);
    printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

makefile:

编译驱动,加载驱动前需要取消设备树相关内容注释并烧写到板子上:

4、驱动程序实现I2C通信

在上面程序的基础上修改:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
​
static const struct i2c_device_id ft5x06_id_ts[] = {
    {"xxx",0},
};
​
static const struct of_device_id ft5x06_id[]  = {
    {.compatible = "edt,edt-ft5306", 0},
    {.compatible = "edt,edt-ft5x06", 0},
    {.compatible = "edt,edt-ft5406", 0},
};
​
static struct i2c_client *ft5x06_client;
static int ft5x06_read_reg(u8 reg_addr);
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len);
​
static void ft5x06_read_reg(u8 reg_addr)
{
    u8 data;
    struct i2c_msg msgs[] {
        //第一个数据包,写
        [0] = {
            .addr  = ft5x06_client->addr,
            .flags = 0,
            .len = sizeof(reg_addr),
            .buf = &reg_addr,
        },
        //第二个数据包,读
        [1] = {
            .addr  = ft5x06_client->addr,
            .flags = 0,
            .len =  sizeof(data),
            .buf = &data,
        },
    };
    i2c_transfer(ft5x06_client->adapter, msgs, 2);
    return data;
}
​
static void ft5x06_write_reg(u8 reg_addr, u8 data, u8 len)
{
    u8 buff[256];
    struct i2c_msg msgs[] = {
        [0] = {
            .addr  = ft5x06_client->addr,
            .flags = 0,
            .len = len+1,
            .buf = buff,
        }
    };
    buff[0] = reg_addr;
    memcpy(&buff[1], &data, len);
    i2c_transfer(ft5x06_client->adapter, msgs, 1);
}
​
int ft5x06_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id)
{
    printk("This is ft5x06_probe\n");
    ft5x06_client = client;//因为我们再别的函数里面用到这个client,所以我们要把他复制出来。
    //往地址为0x80的寄存器里面写入0x4b
    ft5x06_write_reg(0x80, 0x4b, 1);
    //读取寄存器地址为0x80的数据
    ret = ft5x06_read_reg(0x80);
    printk("ret is %#x\n",ret);
    return 0;
}
​
int ft5x06_remove(struct i2c_client *i2c_client)
{
    return 0;
}
static struct i2c_driver ft5x06_driver = {
    .driver = {
        .owner = YHIS_MODULE,
        .name = "ft5x06_test",
        .of_match_table = ft5x06_id,
    },
    .probe = ft5x06_probe,
    .remove = ft5x06_remove,
    .id_table = ft5x06_pid_ts
};
​
static int ft5x06_driver_init(void)
{
    int ret;
    ret = i2c_add_driver(&ft5x06_driver);
    if (ret < 0) {
        printk("i2c_add_driver is error\n");
        return ret;
    }
    printk("This is ft5x06_driver_init\n");
    return 0;
}
​
static void ft5x06_driver_exit(void)
{
    i2c_del_driver(&ft5x06_driver);
    printk("This is ft5x06_driver_exit\n");
}
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);
MODULE_LICENSE("GPL");

编译加载驱动:

改成0x02:

重新编译加载驱动:这个是没有放手指头的

放一个手指头在屏幕上,重新加载驱动:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何使用创建时间给文件重命名,简单的批量操作教程

在处理大量文件时&#xff0c;有时要按照规则对文件重命名&#xff0c;根据文件的创建时间来重命名。那如何批量操作呢&#xff1f;现在一起来看云炫文件管理器如何用文件的创建时间来批量重命名。 按创建时间重命名文件的前后对比图。 用创建时间批量给文件重命名的步骤&…

定时器问题(vue的问题)

我在a页面写一个定时&#xff0c;让他每秒钟打印一个1&#xff0c;然后跳转到b页面&#xff0c;此时可以看到&#xff0c;定时器依然在执行。这样是非常消耗性能的。如下图所示&#xff1a; 解决方法1 首先我在data函数里面进行定义定时器名称&#xff1a; data() {return {t…

六、新建窗体时,几种窗体的区别

新建窗体时&#xff0c;会有几种类型的选项&#xff0c;很多同学不明白其中的意思&#xff0c;我们在本章节中详细介绍一下几种窗体的区别。 窗体的类型分以下几种 Dialog with Buttons Bottom 带按钮的对话框&#xff0c;按钮在底部 Dialog with Buttons Right 带按钮的对话框…

【Java SE语法篇】8.面向对象三大特征——封装、继承和多态

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ 文章目录 1. 封装1.1 封装的概念1.2 为什么封装1.3 封装的实现…

Vue基知识四

本文对前边几章所学习的内容&#xff0c;以案例的形式做一个总结 一 TodoList案例 即待办事项案例&#xff0c;效果如下 1.1 组件化编码流程&#xff08;通用&#xff09; 这是编码时的通用流程&#xff0c;以后也可以按照这个流程来写代码&#xff08;熟悉后这个流程的顺…

Flutter开发进阶之动画

Flutter开发进阶之动画 在Flutter中&#xff0c;动画是至关重要的一个部分&#xff0c;它能够为应用程序提供更加丰富和生动的用户体验&#xff0c;Flutter中的动画系统是UI框架的核心功能之一&#xff0c;也是开发者学习Flutter框架的重要部分&#xff0c;由于动画原理在所有…

Python基本语法与变量的相关介绍

python基本语法与变量 python语句的缩进 Python代码块使用缩进对齐表示代码逻辑&#xff0c;Python每段代码块缩进的空白数量可以任意&#xff0c;但要确保同段代码块语句必须包含相同的缩进空白数量。建议在代码块的每个缩进层次使用单个制表符或两个空格或四个空格 , 切记不…

GPU云服务器使用教程、运行YOLOV5项目并连接到本地VSCode(Pycharm)

编程如画&#xff0c;我是panda&#xff01; 之前已经教过大家如何在自己的电脑中配置Pytorch深度学习环境&#xff0c;但是有些小伙伴没有英伟达的GPU&#xff0c;所以用CPU的话训练模型会比较慢&#xff0c;所以这次出一期使用GPU云服务器的教程。 码字不易&#xff0c;如果对…

金南瓜SECS/GEM发送event、VID

金南瓜SECS/GEM发送事件&#xff08;CEID&#xff09;很简单&#xff0c;只需一步就完成。 最重要是简单易懂&#xff0c;任何人一看就上手。无需懂得内部逻辑&#xff0c;以及一大堆的导入问题。 代码如下 C#的代码&#xff1a; // 扫码成功 private void buttonReadBarco…

一个简易的PHP论坛系统

一个简易的PHP论坛系统 php课程设计&#xff0c;毕业设计 预览 技术 bootstrap 4.x jquery css php mysql 5.7 目录结构 登录 管理员 admin/123456 测试用户 user1/123456 更多文章和源码获取查看

51-13 多模态论文串讲—BEiT v3 论文精读

BEIT-3的核心思想是将图像建模为一种语言&#xff0c;这样我们就可以对图像、文本以及图像-文本对进行统一的mask modeling。Multi-way transformer模型可以有效地完成不同的视觉和视觉语言任务&#xff0c;使其成为通用建模的一个有效选择。 同时&#xff0c;本文也对多模态大…

深入 Move 生态,探秘铭文热潮背后的思考

Move 语言是 Meta&#xff08;Facebook&#xff09;在 2018 年开发的新一代智能合约编程语言。回顾过去的一年&#xff0c;Aptos 与 Sui 主网上线&#xff0c;为整个 Web3 开启了下一个十亿用户服务的新征程。Rooch、Initia、MoveMent 等多条使用 Move 语言的区块链网络涌现&am…

边缘计算的舞台,挑战与机遇相伴

目录 前言 边缘计算保卫战&#xff1a;数据宝藏的隐藏与探索 稳如磐石&#xff1a;保障你的边缘计算宝藏安稳运行&#xff01; 打破时间的桎梏&#xff1a;为边缘计算注入超快速度与实时表演 边缘计算&#xff1a;应对多样性和异构性的酷炫策略大揭秘 边缘计算&#xff1a;释…

MySQL面试题 | 06.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Spring Boot - JaCoCo Code Coverage

文章目录 概述如何集成pom添加插件Code Demo排除不相关的类CI/CD中使用完整POM 概述 JaCoCo&#xff08;Java Code Coverage&#xff09;是一个开源的Java代码覆盖率工具&#xff0c;它主要用于评估Java程序的测试完整性。通过跟踪测试过程中执行的代码&#xff0c;JaCoCo能够…

【Python数据可视化】matplotlib之绘制常用图形:折线图、柱状图(条形图)、饼图和直方图

文章传送门 Python 数据可视化matplotlib之绘制常用图形&#xff1a;折线图、柱状图&#xff08;条形图&#xff09;、饼图和直方图matplotlib之设置坐标&#xff1a;添加坐标轴名字、设置坐标范围、设置主次刻度、坐标轴文字旋转并标出坐标值matplotlib之增加图形内容&#x…

读元宇宙改变一切笔记07_硬件与互操作性(上)

1. 元宇宙的头号入口 1.1. 元宇宙最令人兴奋的地方在于&#xff0c;我们可以借此开发用来访问、渲染和操纵它的新设备 1.1.1. App Newton于1993年发布&#xff0c;是世界上第一款掌上电脑 1.2. 功能超强大又轻巧的AR和沉浸式VR头显 1.2.1.…

AI智能创作软件,颠覆你的写作体验

你是否想过&#xff0c;有一天&#xff0c;文字创作不再受限于人的思维和表达能力&#xff1f;AI智能文章创作就是这样一个神奇的存在。它运用先进的自然语言处理技术&#xff0c;通过对大量数据的深度学习&#xff0c;根据需要自动生成文章。你只需输入关键词或主题&#xff0…

【天龙八部】攻略day6

关键字&#xff1a; 灵武、寻宝要求、雁门 1】灵武选择 西凉枫林&#xff0c;锦带&#xff0c;短匕 白溪湖&#xff0c;明镜&#xff0c;双刺 竹海&#xff0c;玉钩&#xff0c;锁甲 2】楼兰寻宝需求 等级80级&#xff0c;40级前6本心法 3】雁门奖励 简单35*4元佑碎金 普…

机器学习扩散模型简介

一、说明 扩散模型的迅速崛起是过去几年机器学习领域最大的发展之一。在这本易于理解的指南中了解您需要了解的有关扩散模型的所有信息。 扩散模型是生成模型&#xff0c;在过去几年中越来越受欢迎&#xff0c;这是有充分理由的。仅在 2020 年代发布的几篇开创性论文就向世界…