RK3568驱动指南|第十六篇 SPI-第192章 mcp2515驱动编写:完善write和read函数

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十六篇 SPI_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


  1. 第192章 mcp2515驱动编写:完善write和read函数

在上个章节中对mcp2515的工作模式进行了修改,从配置模式修改为了环回模式,而在本章节将会继续对mcp2515的驱动程序进行完善,加入mcp2515的写函数和读函数,从而实现数据的发送和接收。

192.1 编写mcp2515写函数

MCP2515有三个发送缓冲器,每个发送缓冲器都具有14字节的内存空间,每个发送缓冲器的控制由TXBnCTRL寄存器管理,该寄存器决定了何时发送报文以及发送时的报文状态。该寄存器的具体内容如下所示:

需要通过该寄存器将缓冲器优先级设置为最高,缓冲器优先级由bit1-0两位所决定,当设置为11时,该发送缓冲器具有最高的发送优先级,可以通过以下代码进行设置:

#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址          

 mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03);   //只对该寄存器低两位进行修改,修改值为0x03

发送缓冲器控制寄存器TXBnCTRL为发送缓冲器的第一个字节,接下来的5个字节用来装载标准和扩展标识符以及其他报文仲裁信息。最后的8个字节用于装载等待发送报文的8个可能的数据字节,这13个字节数据由用户空间所传递,且地址是连续的,间隔为一个字节,所以可以通过以下代码进行设置:

char w_kbuf[13] = {0};
int ret;
          

// 从用户空间复制数据到内核缓冲区
ret = copy_from_user(w_kbuf, buf, size);
if (ret) {
    printk("copy_from_user w_kbuf is error\n");
    return -1;
}

// 将数据写入MCP2515寄存器
for (i = 0; i < sizeof(w_kbuf); i++) {
    mcp2515_write_reg(0x31 + i, w_kbuf[i]);
}

数据设置完成之后,需要将TXBnCTRL寄存器的bit3设置为1,从而启动相应缓冲器的报文发送,TXBnCTRL寄存器就是上面修改发送缓冲器优先级的寄存器,具体设置代码如下所示:

 #define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址
mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);   //只对该寄存器bit3进行修改,将bit3设置为1

在报文发送成功后,CANINTF.TXnIF寄存器将会被置1,该寄存器内容如下所示:

可以通过该寄存器来判断报文是否发送成功,由于使用的是缓冲器为0,所以这里要判断的位位bit2,判断完成之后,需要对该寄存器进行手动清零,具体判断代码如下所示:

#define CANINTF 0x2c 

 // 等待发送完成
while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));

// 清除发送完成标志
mcp2515_change_regbit(CANINTF, 0x04, 0x00);

至此,关于mcp2515写函数的相关知识就总结完成了,可以将上面讲解的代码整理成一个完整的函数,具体内容如下所示:

#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址
#define CANINTF 0x2c 

// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
    char w_kbuf[13] = {0};
    int ret;
    int i;

    // 设置TXB0CTRL寄存器的部分位
    mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03);

    // 从用户空间复制数据到内核缓冲区
    ret = copy_from_user(w_kbuf, buf, size);
    if (ret) {
        printk("copy_from_user w_kbuf is error\n");
        return -1;
    }

    // 将数据写入MCP2515寄存器
    for (i = 0; i < sizeof(w_kbuf); i++) {
        mcp2515_write_reg(0x31 + i, w_kbuf[i]);
    }

    // 设置TXB0CTRL寄存器的部分位,启动发送
    mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);
    
    // 等待发送完成
    while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));

    // 清除发送完成标志
    mcp2515_change_regbit(CANINTF, 0x04, 0x00);

    return size;
}

192.2编写mcp2515读函数

在上个小节中编写了mcp2515的写函数,在本小节将编写mcp2515的读函数。

MCP2515 具有两个全接收缓冲器,当数据报文传送至某一接收缓冲器时,与该接收缓冲器对应的CANINTF.RXnIF位将置1,可以通过CANINTF.RXnIF寄存器的值来判断是否接收完成,CANINTF寄存器内容在上一节已经列出,这里不再重复,具体判断代码如下所示:

#define CANINTF 0x2c 

// 等待接收缓冲区满标志位被设置
while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));

然后编写读数据相关的代码,接收缓冲器与发送寄存器相匹配,前5个字节用来装载标准和扩展标识符以及其他报文仲裁信息,最后的8个字节用于装载等待发送报文的8个可能的数据字节,且地址是连续的,间隔为一个字节,接收缓冲器0的标准标识符高位寄存器地址为0x61,所以可以通过以下代码进行设置:

    char r_kbuf[13] = {0};  // 内核缓冲区,用于存储从设备读取的数据
    int i;

    // 从接收缓冲区读取数据到内核缓冲区
    for (i = 0; i < sizeof(r_kbuf); i++) {
        r_kbuf[i] = mcp2515_read_reg(0x61 + i);
    }

数据传送完成之后需要对CANINTF.RXnIF寄存器清零,并且使用copy_to_user传输到用户空间,具体代码如下所示:

 // 清除接收缓冲区满标志位
    mcp2515_change_regbit(CANINTF, 0x01, 0x00);

    // 将内核缓冲区的数据复制到用户缓冲区
    ret = copy_to_user(buf, r_kbuf, size);
    if (ret) {
        printk("copy_to_user r_kbuf is error\n");
        return -1;  // 返回-1表示复制数据失败
    }

至此,关于mcp2515读函数的相关知识就总结完成了,可以将上面讲解的代码整理成一个完整的函数,具体内容如下所示:

#define CANINTF 0x2c 

// 读设备操作函数,从设备读取数据到用户缓冲区
ssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) {
    char r_kbuf[13] = {0};  // 内核缓冲区,用于存储从设备读取的数据
    int i;
    int ret;

    // 等待接收缓冲区满标志位被设置
    while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));

    // 从接收缓冲区读取数据到内核缓冲区
    for (i = 0; i < sizeof(r_kbuf); i++) {
        r_kbuf[i] = mcp2515_read_reg(0x61 + i);
    }

    // 清除接收缓冲区满标志位
    mcp2515_change_regbit(CANINTF, 0x01, 0x00);

    // 将内核缓冲区的数据复制到用户缓冲区
    ret = copy_to_user(buf, r_kbuf, size);
    if (ret) {
        printk("copy_to_user r_kbuf is error\n");
        return -1;  // 返回-1表示复制数据失败
    }

    return 0;  // 返回0表示成功读取数据
}

192.3 实验程序编写

192.3.1 编写驱动程序

本实验驱动对应的网盘路径为:iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\118_mcp2515_06\

本实验将以191章编写完成的驱动程序为基础,添加了本章节完善的mcp2515的读和写函数,编写完成的mcp2515.c代码如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>

#define CNF1 0x2a                    // 寄存器定义
#define CNF2 0x29
#define CNF3 0x28
#define RXB0CTRL 0x60
#define CANINTE 0x2b
#define CANCTRL 0xf                    // CAN控制寄存器

#define TXB0CTRL 0x30   //发送缓冲器控制寄存器地址
#define CANINTF 0x2c 

dev_t dev_num; // 设备号
struct cdev mcp2515_cdev; // 字符设备结构体
struct class *mcp2515_class; // 设备类
struct device *mcp2515_device; // 设备
struct spi_device *spi_dev; // SPI设备指针


// MCP2515芯片复位函数
void mcp2515_reset(void){
    int ret;
    char write_buf[] = {0xc0}; // 复位指令0x11000000即0xc0
    ret = spi_write(spi_dev, write_buf, sizeof(write_buf)); // 发送复位命令
    if(ret < 0){
        printk("spi_write is error\n"); // 打印错误信息
    }
}

// MCP2515读寄存器函数
char mcp2515_read_reg(char reg) {
    char write_buf[] = {0x03, reg};  // SPI写缓冲区写入SPI读指令0x03
    char read_buf;                   // SPI读缓冲区
    int ret;

    ret = spi_write_then_read(spi_dev, write_buf, sizeof(write_buf), &read_buf, sizeof(read_buf));  // 调用SPI写读函数
    if (ret < 0) {
        printk("spi_write_then_read error\n");
        return ret;
    }

    return read_buf;
}

//  MCP2515写寄存器函数
void mcp2515_write_reg(char reg, char value) {
    int ret;
    char write_buf[] = {0x02, reg, value};  // SPI写缓冲区,用于发送写寄存器命令

    ret = spi_write(spi_dev, write_buf, sizeof(write_buf));  // 发送SPI写命令
    if (ret < 0) {
        printk("mcp2515_write_reg error\n");
    }
}

// MCP2515修改寄存器位函数
void mcp2515_change_regbit(char reg, char mask, char value) {
    int ret;
    char write_buf[] = { 0x05, reg, mask, value };  // SPI写缓冲区,用于发送修改寄存器位命令

    ret = spi_write(spi_dev, write_buf, sizeof(write_buf));  // 发送SPI写命令
    if (ret < 0) {
        printk("mcp2515_change_regbit error\n");
    }
}

// 打开设备文件的回调函数
int mcp2515_open(struct inode *inode, struct file *file) {
    return 0; // 返回成功
}


// 读设备操作函数,从设备读取数据到用户缓冲区
ssize_t mcp2515_read(struct file *file, char __user *buf, size_t size, loff_t *offset) {
    char r_kbuf[13] = {0};  // 内核缓冲区,用于存储从设备读取的数据
    int i;
    int ret;

    // 等待接收缓冲区满标志位被设置
    while (!(mcp2515_read_reg(CANINTF) & (1 << 0)));

    // 从接收缓冲区读取数据到内核缓冲区
    for (i = 0; i < sizeof(r_kbuf); i++) {
        r_kbuf[i] = mcp2515_read_reg(0x61 + i);
    }

    // 清除接收缓冲区满标志位
    mcp2515_change_regbit(CANINTF, 0x01, 0x00);

    // 将内核缓冲区的数据复制到用户缓冲区
    ret = copy_to_user(buf, r_kbuf, size);
    if (ret) {
        printk("copy_to_user r_kbuf is error\n");
        return -1;  // 返回-1表示复制数据失败
    }

    return 0;  // 返回0表示成功读取数据
}

// 写设备操作函数
ssize_t mcp2515_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) {
    char w_kbuf[13] = {0};
    int ret;
    int i;

    // 设置TXB0CTRL寄存器的部分位
    mcp2515_change_regbit(TXB0CTRL, 0x03, 0x03);

    // 从用户空间复制数据到内核缓冲区
    ret = copy_from_user(w_kbuf, buf, size);
    if (ret) {
        printk("copy_from_user w_kbuf is error\n");
        return -1;
    }

    // 将数据写入MCP2515寄存器
    for (i = 0; i < sizeof(w_kbuf); i++) {
        mcp2515_write_reg(0x31 + i, w_kbuf[i]);
    }

    // 设置TXB0CTRL寄存器的部分位,启动发送
    mcp2515_change_regbit(TXB0CTRL, 0x08, 0x08);
    
    // 等待发送完成
    while (!(mcp2515_read_reg(CANINTF) & (1 << 2)));

    // 清除发送完成标志
    mcp2515_change_regbit(CANINTF, 0x04, 0x00);

    return size;
}

// 关闭设备文件的回调函数
int mcp2515_release(struct inode *inode, struct file *file) {
    return 0; // 返回成功
}

// 设备文件操作集合
struct file_operations mcp2515_fops = {
    .open = mcp2515_open,
    .read = mcp2515_read,
    .write = mcp2515_write,
    .release = mcp2515_release,
};

// MCP2515设备初始化函数
int mcp2515_probe(struct spi_device *spi) {
    int ret, value;
    printk("This is mcp2515_probe\n");

spi_dev = spi; // 保存SPI设备指针
    // 分配字符设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "mcp2515");
    if (ret < 0) {
        printk("alloc_chrdev_region error\n");
    }

    // 初始化字符设备
    cdev_init(&mcp2515_cdev, &mcp2515_fops);
    mcp2515_cdev.owner = THIS_MODULE;

    // 添加字符设备
    ret = cdev_add(&mcp2515_cdev, dev_num, 1);
    if (ret < 0) {
        printk("cdev_add error\n");
        return -1;
    }

    // 创建设备类
    mcp2515_class = class_create(THIS_MODULE, "spi_to_can");
    if (IS_ERR(mcp2515_class)) {
        printk("mcp2515_class error\n");
        return PTR_ERR(mcp2515_class);
    }

    // 创建设备
    mcp2515_device = device_create(mcp2515_class, NULL, dev_num, NULL, "mcp2515");
    if (IS_ERR(mcp2515_device)) {
        printk("mcp2515_device error\n");
        return PTR_ERR(mcp2515_device);
    }

    mcp2515_reset();             // 复位MCP2515设备
    value = mcp2515_read_reg(0x0e);  // 读取寄存器值
    printk("value is %x\n", value);  // 打印读取的值
	
	mcp2515_write_reg(CNF1, 0x01);  // 写入寄存器配置值
	mcp2515_write_reg(CNF2, 0xb1);
	mcp2515_write_reg(CNF3, 0x05);

	mcp2515_write_reg(RXB0CTRL, 0x60);
	mcp2515_write_reg(CANINTE, 0x05);
	mcp2515_change_regbit(CANCTRL, 0xe0, 0x40);
	
	value = mcp2515_read_reg(0x0e);  // 读取寄存器值
    printk("value is %x\n", value);  // 打印读取的值
	
    return 0; // 返回成功
}

// MCP2515 SPI设备的移除函数
static int mcp2515_remove(struct spi_device *spi) {
    device_destroy(mcp2515_class, dev_num);
    class_destroy(mcp2515_class);
    cdev_del(&mcp2515_cdev);
    unregister_chrdev_region(dev_num, 1);
    return 0;
}

// MCP2515设备匹配表,用于设备树匹配
static const struct of_device_id mcp2515_of_match_table[] = {
    { .compatible = "my-mcp2515" },
    {}
};

// MCP2515设备ID匹配表,用于总线匹配
static const struct spi_device_id mcp2515_id_table[] = {
    { "mcp2515", 0 },
    {}
};

// MCP2515 SPI驱动结构体
static struct spi_driver spi_mcp2515 = {
    .probe = mcp2515_probe, // 探测函数
    .remove = mcp2515_remove, // 移除函数
    .driver = {
        .name = "mcp2515", // 驱动名称
        .owner = THIS_MODULE, // 所属模块
        .of_match_table = mcp2515_of_match_table, // 设备树匹配表
},
    .id_table = mcp2515_id_table, // 设备ID匹配表
};

// 驱动初始化函数
static int __init mcp2515_init(void)
{
    int ret;

    // 注册SPI驱动
    ret = spi_register_driver(&spi_mcp2515);
    if (ret < 0) {
        // 注册失败,打印错误信息
        printk("spi_register_driver error\n");
        return ret;
    }

    return ret;
}

// 驱动退出函数
static void __exit mcp2515_exit(void)
{
    // 注销SPI驱动
    spi_unregister_driver(&spi_mcp2515);
}

module_init(mcp2515_init);
module_exit(mcp2515_exit);

MODULE_LICENSE("GPL");

192.3.2 编写测试APP

本实验测试APP对应的网盘路径为:iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\118_mcp2515_06\

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

// 主函数,程序入口点
int main(int argc, char *argv[]){
    int fd;  // 文件描述符
    int i;   // 循环变量

    // 写缓冲区,包含13个字节的数据,将发送到MCP2515
    char w_buf[13]= {0x66,0x08,0x22,0x33,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
    // 读缓冲区,用于接收从MCP2515读取的数据
    char r_buf[13] = {0};

    // 打开MCP2515设备文件,获取文件描述符
    fd = open("/dev/mcp2515", O_RDWR);
    if(fd < 0){
        // 打开设备文件失败,打印错误信息并返回
        printf("open /dev/mcp2515 error \n");
        return -1;
    }

    // 将写缓冲区的数据写入设备
    write(fd, w_buf, sizeof(w_buf));
    // 从设备读取数据到读缓冲区
    read(fd, r_buf, sizeof(r_buf));
    
    // 打印读缓冲区的数据
    for(i = 0; i < 13; i++){
        printf("r_buf[%d] is %d\n", i, r_buf[i]);
    }
    
    // 关闭设备文件
    close(fd);
    return 0;  // 返回0表示程序正常结束
}

上述测试app代码中第13行表示要发送给mcp2515的13个字节的数据,其中前5个字节用来装载标准和扩展标识符以及其他报文仲裁信息,最后的8个字节用于装载等待发送报文的8个可能的数据字节,第一个字节发送缓冲器标准标识符高位、第三个字节发送缓冲器扩展标识符高位、第四个字节发送缓冲器扩展标识符低位可以随意设置,这里设置的是0x66、0x22、0x33。

第二个字节为发送缓冲器标准标识符低位,该寄存器的具体内容如下所示:

其中bit3代表扩展标识符的使能位,这里需要设置为1进行使能,换算成16进制为0x08。

第5个字节为发送缓冲器数据长度码,该寄存器内容如下所示:

其中bit6需要设置为0,表示发送的报文为数据帧。而要发送的数据为8个字节,所以bit3-bit0需要设置为8,换算成16进制为0x08。

至此,关于前5个字节内容的设置就讲解完成了,而后8个字节为要发送的数据,这里随意取值即可。

192.4 运行测试

192.4.1 编译驱动程序

在上一小节中的mcp2515.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += mcp2505.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放mcp2515.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成ft5x06_driver.ko目标文件,如下图所示:

至此驱动模块就编译成功了。

192.4.2 编译应用程序

首先进行应用程序的编译,因为测试APP是要在开发板上运行的,所以需要aarch64-linux-gnu-gcc来编译,输入以下命令,编译完成以后会生成一个app的可执行程序,如下图所示:

 aarch64-linux-gnu-gcc app.c -o app

然后将编译完成的可执行程序拷贝到开发板上.

192.4.2 运行测试

在进行实验之前,首先要确保开发板烧写的是我们在186.1小节中编译出来的boot.img。开发板启动之后,然后使用以下命令进行驱动模块的加载,如下图所示:

insmod mcp2515.ko

然后使用“./app”运行上一小节中编译的可执行程序,运行结果如下所示:

可以看到可执行程序运行之后会将传输的13位数据依次打印出来,这里打印的是10进制,换算成16进制之后与0x66,0x08,0x22,0x33,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08相匹配,证明编写的mcp2515读函数和写函数可以正常工作。

最后使用以下命令进行驱动模块的卸载,如下图所示:

rmmod mcp2515.ko

由于没有在remove卸载函数中添加打印相关内容,所以使用rmmod命令卸载驱动之后,没有任何打印。

至此,MCP2515读函数和写函数测试实验就完成了。

 

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

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

相关文章

Reid系列论文学习——无人机场景下基于 Transformer 的轻量化行人重识别

今天介绍的一篇论文是针对无人机场景下的行人重识别&#xff0c;论文题目为&#xff1a;"无人机场景下基于 Transformer 的轻量化行人重识别"。该论文针对无人机场景下行人呈现多角度多尺度的特点、以及传统CNN网络在行人重识别任务中受限于感受野和下采样导致的无法…

Git 操作总结

1. 安装、Git 环境配置 1.1 安装 Git 官方版本可以在 Git 官方网站下载&#xff1a;打开 https://git-scm.com/download/win&#xff0c;选择相应版本即可。 Git 安装完成后&#xff0c;可以在开始菜单中看到 Git 的三个启动图标&#xff08;Git Bash、Git CMD、Git GUI&…

关于5G和卫星

手机&#xff0c;已经串联起了我们生活中的一切环节。我们随时随地拿出手机&#xff0c;都能畅快地上网。 这一切是如此地理所当然&#xff0c;以至于我们甚至想不到这样不可思议的问题&#xff1a; 移动通信网络真的无处不在吗&#xff1f; 我们都知道&#xff0c;地球虽叫…

解决在【Tomcat服务器上报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver】

目录 1. 添加驱动依赖&#xff1a;右键导入为库 2. 重新导入工件&#xff0c;注意看lib下有没有mysql的驱动包&#xff0c;没有的话需要导入。 3. 写代码的时候要注意对null值的处理&#xff0c;比如下面的截图&#xff0c;如果只是简单的获取生成的随机数&#xff0c;很有可…

Linux—KVM虚拟化中使用基本命令管理虚拟机(纯实例)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f468;‍&#x1f4bb;Linux高级管理专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年7月2日11点11分 &#x1f004;️文章质量&#xff1a;94分 文章目录 前言 1.查看命令帮助 2.查看KVM 的…

MySQL 如何实现将数据实时同步到 ES ?

引言&#xff1a;在现代应用程序开发中&#xff0c;通常会将数据存储在 MySQL 中&#xff0c;用于事务性处理和数据持久化。而 Elasticsearch&#xff08;ES&#xff09;则是一种专门用于全文搜索和分析的强大工具。将这两者结合使用的一个常见需求是实时将 MySQL 中的数据同步…

Houdini 引用领域及20版本五大重点功能

Houdini是一款三维计算机图形软件&#xff0c;由加拿大Side Effects Software Inc.&#xff08;简称SESI&#xff09;公司开发&#xff0c;SESI公司由Kim Davidson和Greg Hermanovic创建于1987年。Houdini是在Prisms基础上重新开发而来&#xff0c;可运行于Linux, Windows, Mac…

Echarts折线图 自适应窗口大小

实现效果&#xff1a; 代码&#xff1a; <template><div class"echarts"><div class"select-box"><div v-for"(item,index) in trendList":key"index":class"[period item.id?active:,item]"click&…

从涟漪到波浪:资产代币化的变革力量

原文标题&#xff1a;《From ripples to waves: The transformational power of tokenizing assets》撰文&#xff1a;Anutosh Banerjee&#xff0c;Matt Higginson&#xff0c;Julian Sevillano&#xff0c;Matt Higginson编译&#xff1a;Chris&#xff0c;Techub News本文来…

最近,被“AI”狠狠刷屏了......

最近&#xff0c;被“AI”狠狠刷屏了。 作为时下最热门的话题&#xff0c;AI画图、AI配音、AI写文案、AI做视频......AI在最近两年可谓是火遍全球。ChatGPT、Midjourney和SORA等技术不断涌现&#xff0c;不仅深刻改变着我们的生活方式&#xff0c;也推动了AI技术的飞速发展。 …

Linux文件与日志

目录 1. Linux 文件系统 1.1 inode号 1.2 EXT类型文件恢复 1.3 xfs类型文件备份和恢复 2. 日志分析 2.1 日志类型 2.2日志配置文件 2.3 日志分析的重要性 在Linux系统中&#xff0c;文件和日志是管理和维护系统运行所不可或缺的。理解它们的工作原理和如何有效地管理和…

第三天:LINK3D核心原理讲解【第1部分】

第三天:LINK3D核心原理讲解 LINK3D学习笔记 目标 了解LINK3D velodyne64线激光雷达LINK3D质心点提取效果: 分布在车道与墙体的交界处。 课程内容 LINK3D论文精讲LINK3D聚合关键点提取代码讲解LINK3D描述子匹配代码讲解除了ALOAM的线特征、面特征,还有其他点云特征吗,是…

WSL——忘记root密码(Ubuntu)

1、问题描述 Windows下的WSL&#xff08;Ubuntu&#xff09;忘记了root密码&#xff0c;无法使用管理员权限。 2、解决方法 关闭 Ubuntu 窗口。打开 Windows 的 Powershell 或 cmd&#xff0c; 以 root 默认登陆 WSL。 wsl -u root 修改对应用户密码。 # xxx为要修改密码的用…

MySQL—创建查看删除备份恢复数据库

创建数据库 创建数据库 LLF_DB01CREATE DATABASE LLF_DB01删除数据库DROP DATABASE LLF_DB01创建一个使用utf8字符集的数据库并带校对规则的数据库CREATE DATABASE hsp_db03 CHARACTER SET utf8 COLLATE utf8_bin 查看、删除数据库 显示所有的数据库SHOW DATABASES显示数据库…

车牌号查车辆信息在生活中的作用

车牌号查车辆信息在生活中具有多方面的作用&#xff0c;这些作用涵盖了安全、法律合规、便捷性等多个方面。以下是几个主要的作用&#xff1a; 交通安全与事故处理&#xff1a;在交通事故发生后&#xff0c;警方或保险公司可以通过车牌号快速查询到事故车辆的基本信息&#xf…

搭建论坛和mysql数据库安装和php安装

目录 概念 步骤 安装mysql8.0.30 安装php 安装Discuz 概念 搭建论坛的架构&#xff1a; lnmpDISCUZ l 表示linux操作系统 n 表示nginx前端页面的web服务 m 表示 mysql 数据库 用来保存用户和密码以及论坛的相关内容 p 表示php 动态请求转发的中间件 步骤 &#xff…

晨持绪科技:抖音店铺运营思路

在抖音这个充满活力与创意的平台上&#xff0c;店铺运营不仅仅是一种商业行为&#xff0c;它更是一种艺术的展示。如同画家在画布上勾勒出色彩斑斓的画面&#xff0c;抖音店铺的运营者们也在平台上精心策划着每一个细节&#xff0c;以吸引更多的目光和流量。 内容创作。内容是吸…

Vue.js 案例——商品管理

一.需要做出的效果图&#xff1a; 二.实现的步骤 首先&#xff0c;先建一个项目&#xff0c;命名Table&#xff0c;在Table项目中的components里新建一个MyTable.vue文件。 第二步&#xff0c;在原有的 HelloWorld.vue中写入代码。 HelloWorld.vue代码如下&#xff1a; <…

oracle存储结构-----逻辑存储结构(表空间、段、区、块)

文章目录 oracle存储结构图&#xff08;逻辑存储物理存储&#xff09;oracle逻辑存储结构图逻辑存储结构、表空间、段、区、数据块的关系&#xff1a;1、数据 块&#xff08;block&#xff09;---逻辑存储最小单位2、 数据区&#xff08;extent&#xff09;--存储空间分配和回收…

五、框架实战:SSM整合原理和实战-个人版

五、框架实战&#xff1a;SSM整合原理和实战 文章目录 五、框架实战&#xff1a;SSM整合原理和实战一、SSM整合理解1.1 什么是SSM整合&#xff1f;1.2 SSM整合核心问题明确1.2.1 第一问&#xff1a;SSM整合需要几个IoC容器&#xff1f;1.2.2 第二问&#xff1a;每个IoC容器对应…