PYNQ 框架 - OV5640驱动 + Linux 驱动分析

news2024/11/30 17:43:08

目录

1. 简介

1.1 博文要点

1.2 V4L2

2. 极简 Char 驱动

2.1 源码

2.2 Makefile

2.3 加载驱动

2.4 设备文件

2.5 测试驱动程序

2.6 卸载驱动程序

2.7 自动创建设备文件

2.8 日志等级

3. 极简 V4L2 驱动

3.1 源码

3.2 Makefile

3.3 设备节点类型

3.4 测试 V4L2

3.5 Probe / 直接注册

4. OV5640 驱动

4.1 查找驱动.ko

4.2 主要功能分析

4.3 V4L2 API

4.4 IIC 相关函数

4.4.1 ov5640_write_reg

4.4.2 ov5640_read_reg

4.5 时钟计算

4.5.1 时钟树

4.5.2 Cal Sys_Div_clk

4.5.3 Get SCLK

4.5.4 ov5640_set_mipi_pclk

4.6 曝光控制

4.6.1 自动曝光

4.6.2 获取曝光值

4.6.3 设置自动曝光

4.6.4 自动曝光阈值

4.6.5 设定手动曝光值

4.7 增益控制

4.7.1 获取当前配置

4.7.2 设置自动增益

4.8 Light Frequency

4.8.1 获取 Light Freq

4.8.2 设置自动/手动 

 4.8.3 Band Filter

4.9 Night Mode

4.10 获取 VTS/HTS

4.11 Virtual Channel

5. 总结


1. 简介

1.1 博文要点

  • 分析一个极简的 Char 驱动
  • 分享实时查看日志文件的方法
  • 分析一个极简的 V4L2 驱动
  • 分析 OV5640 的 V4L2 驱动
  • 提取 OV5640 V4L2 驱动代码并转换为 PYNQ 框架代码

1.2 V4L2

V4L2 (Video for Linux 2) 是 Linux 内核中用于视频设备驱动的框架。它为应用层提供统一的接口,并支持各种复杂硬件的灵活扩展。V4L2 主要用于实时视频捕捉,支持许多 USB 摄像头、电视调谐器等设备。

  • V4L2 框架包括以下几个主要模块:
  • v4l2-core:核心模块,处理设备实例数据。
  • media framework:媒体框架,集成了 V4L2 设备节点和子设备。
  • videobuf:视频缓冲区管理模块。

这个框架使得开发者可以更容易地为 Linux 系统添加视频支持,简化了设备实例、子设备和视频节点的设置。

2. 极简 Char 驱动

2.1 源码

// simple_char_driver.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "simple_char_device"
#define BUFFER_SIZE 1024

static char buffer[BUFFER_SIZE];
static int major_number;

// 打开设备
static int device_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "simple_char_device opened\n");
    return 0;
}

// 关闭设备
static int device_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "simple_char_device closed\n");
    return 0;
}

// 读取设备
static ssize_t device_read(struct file *file, char __user *user_buffer, size_t len, loff_t *offset) {
    int bytes_read = 0;

    if (*offset >= BUFFER_SIZE) {
        return 0; // EOF
    }

    while (len && (*offset < BUFFER_SIZE)) {
        put_user(buffer[*offset], user_buffer++);
        len--;
        (*offset)++;
        bytes_read++;
    }

    printk(KERN_INFO "Read %d bytes from simple_char_device\n", bytes_read);
    return bytes_read;
}

// 写入设备
static ssize_t device_write(struct file *file, const char __user *user_buffer, size_t len, loff_t *offset) {
    int i;

    if (len > BUFFER_SIZE) {
        len = BUFFER_SIZE; // 限制写入长度
    }

    for (i = 0; i < len; i++) {
        get_user(buffer[i], user_buffer + i);
    }

    printk(KERN_INFO "Wrote %zu bytes to simple_char_device\n", len);
    return len;
}

// 文件操作结构体
static struct file_operations fops = {
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

// 模块加载
static int __init simple_char_driver_init(void) {
    major_number = register_chrdev(0, DEVICE_NAME, &fops);
    if (major_number < 0) {
        printk(KERN_ALERT "Failed to register character device\n");
        return major_number;
    }
    printk(KERN_INFO "simple_char_device registered with major number %d\n", major_number);
    return 0;
}

// 模块卸载
static void __exit simple_char_driver_exit(void) {
    unregister_chrdev(major_number, DEVICE_NAME);
    printk(KERN_INFO "simple_char_device unregistered\n");
}

module_init(simple_char_driver_init);
module_exit(simple_char_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
MODULE_VERSION("0.1");

2.2 Makefile

# Makefile
obj-m += simple_char_driver.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

注意:Makefile 中,命令行前面必须使用 制表符(Tab),不能是空格,否则会报错 missing separator。

清理生成的文件:make clean。

执行 make 命令,终端输出如下:

make -C /lib/modules/5.15.0-1037-xilinx-zynqmp/build M=/home/ubuntu/simple_driver modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'
  CC [M]  /home/ubuntu/simple_driver/simple_char_driver.o
  MODPOST /home/ubuntu/simple_driver/Module.symvers
  CC [M]  /home/ubuntu/simple_driver/simple_char_driver.mod.o
  LD [M]  /home/ubuntu/simple_driver/simple_char_driver.ko
  BTF [M] /home/ubuntu/simple_driver/simple_char_driver.ko
Skipping BTF generation for /home/ubuntu/simple_driver/simple_char_driver.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'

解析输出:

1)make -C /lib/modules/5.15.0-1037-xilinx-zynqmp/build M=/home/ubuntu/simple_driver modules

  • 这个命令告诉 make 在指定的路径 /lib/modules/5.15.0-1037-xilinx-zynqmp/build 中查找 Linux 内核的构建文件。
  • M=/home/ubuntu/simple_driver 指定了你的模块源代码所在的目录。
  • modules 是目标,表示要编译内核模块。

2)make[1]: Entering directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'

  • 这行表示 make 进入了 Linux 内核头文件的目录,这个目录包含了构建内核模块所需的所有文件。

3)CC [M]  /home/ubuntu/simple_driver/simple_char_driver.o

  • CC 表示编译器正在编译一个源文件。
  • [M] 表示这是一个模块(而不是内核的内置部分)。
  • /home/ubuntu/simple_driver/simple_char_driver.o 是生成的目标文件的路径(即编译后的对象文件)。

4)MODPOST /home/ubuntu/simple_driver/Module.symvers

  • MODPOST 是一个步骤,负责生成模块符号版本信息,并将其写入 Module.symvers 文件。这个文件包含了模块间的符号信息,帮助内核在加载模块时解决符号依赖。

5)CC [M]  /home/ubuntu/simple_driver/simple_char_driver.mod.o

  • 这是另一个编译步骤,生成模块的描述文件(.mod.o),这个文件包含了模块的元数据。

6)LD [M]  /home/ubuntu/simple_driver/simple_char_driver.ko

  • LD 表示链接器正在将编译的对象文件链接成最终的内核模块文件(.ko 文件)。
  • /home/ubuntu/simple_driver/simple_char_driver.ko 是生成的内核模块的路径。

7)BTF [M] /home/ubuntu/simple_driver/simple_char_driver.ko

  • BTF 表示生成 BPF 类型格式(BPF Type Format)信息,这是一种为 BPF 程序提供类型信息的格式。
  • 这行表示正在为生成的模块创建 BTF 信息。

8)Skipping BTF generation for /home/ubuntu/simple_driver/simple_char_driver.ko due to unavailability of vmlinux

  • 这行表示由于找不到 vmlinux 文件,因此跳过了 BTF 的生成。vmlinux 是内核的完整可执行映像,通常在内核构建时生成。
  • 尽管这条消息提示 BTF 信息没有生成,但这并不影响模块的正常使用。

9)make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'

  • 这行表示 make 完成了在内核源代码目录中的操作,并即将返回到原来的目录。

2.3 加载驱动

1)加载驱动程序:

sudo insmod simple_char_driver.ko

2)查看是否加载成功:

lsmod | grep simple_char_driver
---
simple_char_driver     20480  0

3) 查看内核日志,获取主设备号:

加载驱动程序后,使用 dmesg 命令查看内核日志,查找主设备号的相关信息:

sudo dmesg | tail
---
...
[ 3112.860151] simple_char_device registered with major number 507

507 就是主设备号。

2.4 设备文件

1)创建 nod

使用 mknod 命令手动创建一个设备文件:

sudo mknod /dev/simple_char_device c <major_number> 0

2)确认设备文件: 

ll /dev/simple_char_device
---
crw-r--r-- 1 root root 507, 0 Nov 13 10:51 /dev/simple_char_device

3)设置设备文件权限:

确保设备文件具有适当的权限,以便用户或进程可以访问它。 

sudo chmod 666 /dev/simple_char_device

2.5 测试驱动程序

可以使用 echo 和 cat 命令来测试读写:

echo "Hello, World!" > /dev/simple_char_device
cat /dev/simple_char_device

2.6 卸载驱动程序

sudo rmmod simple_char_driver
  • 设备驱动程序:是内核的一部分,负责处理与硬件或虚拟设备的交互。当你使用 rmmod 命令卸载驱动程序时,你是在告诉内核停止使用该驱动程序,并释放与该驱动程序相关的资源和内核模块。
  • 设备文件:是一个特殊的文件,通常位于 /dev 目录下,用于提供用户空间程序通过文件系统接口与设备驱动程序交互的方式。这些文件是由系统管理员或通过udev系统自动创建的,并不会因为卸载驱动程序而自动删除。

因此,即使卸载了驱动程序,/dev/simple_char_device 文件仍然存在,因为它只是一个文件系统上的节点。如果你尝试对其进行读写操作,操作将失败,因为没有相应的驱动程序来处理这些请求。

2.7 自动创建设备文件

步骤 2.4 设备文件 中,为手动创建设备文件。

通过在驱动程序中实现 udev 规则或编写额外的代码来自动创建设备文件。

这涉及到内核编程的更高级部分,如使用device_create和class_create函数。这些函数可以在设备注册时自动创建设备文件,而无需步骤一的手动创建。例如:

static struct class*  simple_char_class  = NULL;
static struct device* simple_char_device = NULL;

// 在初始化函数中
simple_char_class = class_create(THIS_MODULE, "simple_char");
if (IS_ERR(simple_char_class)) { return PTR_ERR(simple_char_class); }

simple_char_device = device_create(simple_char_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME);
if (IS_ERR(simple_char_device)) {
    class_destroy(simple_char_class);
    return PTR_ERR(simple_char_device);
}

// 在退出函数中
device_destroy(simple_char_class, MKDEV(major_number, 0));
class_destroy(simple_char_class);

2.8 日志等级

1)控制日志级别:

#define KERN_EMERG      "<0>"  /* 系统不可用 */
#define KERN_ALERT      "<1>"  /* 需要立即采取行动 */
#define KERN_CRIT       "<2>"  /* 临界条件 */
#define KERN_ERR        "<3>"  /* 错误条件 */
#define KERN_WARNING    "<4>"  /* 警告条件 */
#define KERN_NOTICE     "<5>"  /* 正常但重要的条件 */
#define KERN_INFO       "<6>"  /* 信息性消息 */
#define KERN_DEBUG      "<7>"  /* 调试级消息 */

2)显示当前日志级别:

cat /proc/sys/kernel/printk

3)修改显示级别:

sudo sh -c 'echo "8 4 1 7" > /proc/sys/kernel/printk'

这里使用 sudo sh -c 是因为直接使用 sudo echo "6 4 1 7" > /proc/sys/kernel/printk 无法成功,因为重定向操作 (>) 不会被 sudo 影响,所以还是以普通用户权限执行。使用 sh -c 允许整个子shell命令字符串以超级用户权限执行,包括重定向操作。

4)实时查看日志文件

sudo tail -f /var/log/kern.log

or

sudo dmesg -w

可以新建一个终端,然后执行实时查看日志,另一个终端开发驱动,这样可以实时得到结果。 

5)查看终端编号

ubuntu@kria: w
---

11:39:04 up 39 min,  2 users,  load average: 0.07, 0.08, 0.11

USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
ubuntu   tty1     -                11:04   32:40   0.54s  0.26s -bash
ubuntu   pts/1    192.168.101.225  11:14    0.00s  0.69s  0.01s w

tty(Teletypewriter):指的是物理终端设备,例如直接连接到计算机的键盘和显示器。

pts(Pseudo Terminal Slave):指的是伪终端设备,通常用于远程登录会话,例如通过SSH连接的会话。

3. 极简 V4L2 驱动

3.1 源码

这个驱动程序创建了一个虚拟的视频设备,实现了基本的打开、关闭、查询功能,但是省略了帧捕捉和缓冲区管理的复杂部分。为了完整实现一个功能性的 V4L2 驱动,需要进一步处理缓冲区管理、帧的生成或捕捉等。涉及到更复杂的内核编程技术,如 DMA (直接内存访问) 和中断处理等。 

//simple_v4l2_driver.c
#include <linux/module.h>
#include <linux/videodev2.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>

#define VIDEO_DEVICE_NAME "simple_v4l2_device"

static struct v4l2_device v4l2_dev;
static struct video_device vdev;

static int v4l2_open(struct file *file)
{
    printk(KERN_INFO "simple V4L2 device opened\n");
    return 0;
}

static int v4l2_close(struct file *file)
{
    printk(KERN_INFO "simple V4L2 device closed\n");
    return 0;
}

static int v4l2_mmap(struct file *file, struct vm_area_struct *vma)
{
    // For simplicity, mmap handling is not implemented here.
    return -EINVAL;
}

static ssize_t v4l2_read(struct file *file, char __user *buffer, size_t size, loff_t *offset)
{
    // For simplicity, read handling is not implemented here.
    return -EINVAL;
}

static const struct v4l2_file_operations v4l2_fops = {
    .owner = THIS_MODULE,
    .open = v4l2_open,
    .release = v4l2_close,
    .mmap = v4l2_mmap,
    .read = v4l2_read,
};

static int v4l2_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
{
    strscpy(cap->driver, "simple_v4l2_driver", sizeof(cap->driver));
    strscpy(cap->card, "simple V4L2 device", sizeof(cap->card));
    strscpy(cap->bus_info, "platform:simple_v4l2", sizeof(cap->bus_info));
    cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; // report capabilities
    cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
    return 0;
}

static const struct v4l2_ioctl_ops v4l2_ioctl_ops = {
    .vidioc_querycap = v4l2_querycap,
};

static int __init v4l2_driver_init(void)
{
    int ret;

    //init v4l2 name, version
    strlcpy(v4l2_dev.name, "sv", sizeof("sv"));
    ret = v4l2_device_register(NULL, &v4l2_dev);
    if (ret)
        return ret;

    //setup video
    strlcpy(vdev.name, VIDEO_DEVICE_NAME, sizeof(VIDEO_DEVICE_NAME));
    vdev.v4l2_dev = &v4l2_dev;
    vdev.fops = &v4l2_fops;
    vdev.ioctl_ops = &v4l2_ioctl_ops;
    vdev.release = video_device_release_empty;

    ret = video_register_device(&vdev, VFL_TYPE_SUBDEV, -1);
    if (ret < 0) {
        v4l2_device_unregister(&v4l2_dev);
        return ret;
    }

    printk(KERN_INFO "simple V4L2 driver loaded\n");
    return 0;
}

static void __exit v4l2_driver_exit(void)
{
    video_unregister_device(&vdev);
    v4l2_device_unregister(&v4l2_dev);
    printk(KERN_INFO "simple V4L2 driver unloaded\n");
}

module_init(v4l2_driver_init);
module_exit(v4l2_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Simple V4L2 Driver");
MODULE_VERSION("0.1");

3.2 Makefile

obj-m += simple_v4l2_driver.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

install:
	insmod simple_v4l2_driver.ko

remove:
	rmmod simple_v4l2_driver

编译成功后:

make
---
make -C /lib/modules/5.15.0-1037-xilinx-zynqmp/build M=/home/ubuntu/simple_v4l2_driver modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'
  CC [M]  /home/ubuntu/simple_v4l2_driver/simple_v4l2_driver.o
  MODPOST /home/ubuntu/simple_v4l2_driver/Module.symvers
  CC [M]  /home/ubuntu/simple_v4l2_driver/simple_v4l2_driver.mod.o
  LD [M]  /home/ubuntu/simple_v4l2_driver/simple_v4l2_driver.ko
  BTF [M] /home/ubuntu/simple_v4l2_driver/simple_v4l2_driver.ko
Skipping BTF generation for /home/ubuntu/simple_v4l2_driver/simple_v4l2_driver.ko due to unavailability of vmlinux
make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-1037-xilinx-zynqmp'

3.3 设备节点类型

1)VFL_TYPE_VIDEO(视频输入/输出设备)

这类设备主要用于视频捕捉和视频输出。例如,摄像头、电视卡等。它们可以用来录制视频或者将视频信号输出到其他设备。

2)VFL_TYPE_VBI(垂直消隐数据)

VBI(Vertical Blanking Interval)是电视信号中的一部分,用于传输非图像信息,如闭路字幕和电视文本(Teletext)。这类设备用于解码或编码这些信息。

3)VFL_TYPE_RADIO(无线电调谐器)

这类设备用于接收和处理无线电信号,例如FM/AM广播接收器。用户可以通过这些设备收听广播。

4)VFL_TYPE_SUBDEV(V4L2子设备)

子设备通常是指不独立于主设备存在的组件,例如摄像头模块中的图像传感器或镜头控制器。它们通常作为更大系统的一部分,由主设备控制。

5)VFL_TYPE_SDR(软件定义无线电调谐器)

6)VFL_TYPE_TOUCH(触摸传感器)

3.4 测试 V4L2

1)加载/卸载驱动模块:

  • 使用 insmod 命令加载编译好的内核模块:
sudo make install
sudo make remove
  • 确认模块已加载:
lsmod | grep simple_v4l2_driver
---
simple_v4l2_driver     16384  0

2)创建设备文件:

  • 创建一个设备节点(设备文件)。
sudo mknod /dev/video0 c <major_number> 0
sudo chmod 666 /dev/video0
ls -l /dev | grep video
---
crw-rw-rw- 1 root root 92, 0 Nov 14 08:44 /dev/video0
  • V4L2 的主设备号为81:
cat /proc/devices
------
...
81 video4linux
...

3)使用 v4l2-ctl 工具测试:

  • 安装 v4l2-ctl 工具(如果尚未安装):
sudo apt install v4l-utils
  • 使用 v4l2-ctl 工具查询设备信息:
v4l2-ctl --all
---
VIDIOC_SUBDEV_QUERYCAP: failed: Inappropriate ioctl for device
Driver Info:
        Driver version   : 0.0.0
        Capabilities     : 0x00000000
  • 你应该能看到设备的详细信息,包括驱动名称、卡名称、总线信息等。

3.5 Probe / 直接注册

1)probe 方法

通常被称为设备探测方法。它用于在设备驱动程序中检测和初始化设备。当设备与驱动程序匹配时,内核会调用 probe 方法来执行设备的初始化工作。

适用于更复杂的设备,通常依赖于设备树平台代码来匹配设备和驱动程序。

2)直接注册方法

直接注册方法适用于简单设备,直接在 module_init 和 module_exit 中进行设备的注册和注销。

驱动程序直接在 module_init 和 module_exit 函数中注册和注销设备。

4. OV5640 驱动

4.1 查找驱动.ko

检查 Ubuntu 系统中是否包含特定的驱动(即使驱动没有被加载),应该查看系统中的可用内核模块。

find /lib/modules/$(uname -r) -type f -name "*ov5640*.ko"
---
/lib/modules/5.15.0-1037-xilinx-zynqmp/kernel/drivers/media/i2c/ov5640.ko

查看模块信息:

modinfo /lib/modules/5.15.0-1037-xilinx-zynqmp/kernel/drivers/media/i2c/ov5640.ko
---
filename:       /lib/modules/5.15.0-1037-xilinx-zynqmp/kernel/drivers/media/i2c/ov5640.ko
license:        GPL
description:    OV5640 MIPI Camera Subdev Driver
srcversion:     E8CCF4859B81AD94897A18F
alias:          i2c:ov5640
alias:          of:N*T*Covti,ov5640C*
alias:          of:N*T*Covti,ov5640
depends:
intree:         Y
name:           ov5640
vermagic:       5.15.0-1037-xilinx-zynqmp SMP mod_unload modversions aarch64
sig_id:         PKCS#7
signer:         Build time autogenerated kernel key
sig_key:        1E:C8:8A:35:C8:65:47:6C:7B:10:E0:2A:AB:7C:2C:A9:A2:FC:52:4A
sig_hashalgo:   sha512
signature:      ...
parm:           virtual_channel:MIPI CSI-2 virtual channel (0..3), default 0 (uint)

4.2 主要功能分析

1)枚举类型

定义了一些枚举类型,如 `ov5640_mode_id`、`ov5640_frame_rate` 和 `ov5640_pixel_rate_id`,用于表示不同的图像模式、帧率和像素率。

2)数据结构

  • ov5640_pixfmt: 定义了像素格式的结构体,包括格式代码、色彩空间、每个像素的字节数等。
  • ov5640_dev: 定义了传感器设备的主要结构体,包含了 I2C 客户端、V4L2 子设备、媒体 pad、时钟、GPIO、调节器、锁、格式、模式、流状态等信息。
  • reg_value: 用于表示寄存器地址、值、掩码和延迟的结构体。

3)功能实现

  • 初始化和配置: 包括初始化传感器、设置寄存器、配置时钟等。函数如 `ov5640_init_slave_id`、`ov5640_write_reg`、`ov5640_read_reg` 等用于与硬件进行交互。
  • 控制流: 通过 `v4l2_ctrl` 相关的函数实现对传感器的控制,如曝光、增益、白平衡等。`ov5640_set_ctrl_*` 函数用于设置各种控制属性。
  • 流控制: 通过 `ov5640_set_stream` 函数控制视频流的开启和关闭。
  • 格式和分辨率设置: 通过 `ov5640_set_fmt` 和 `ov5640_try_fmt_internal` 函数设置和尝试不同的图像格式和分辨率。

4)驱动的挂载和卸载

  • ov5640_probe: 驱动的探测函数,初始化设备,获取 GPIO 和时钟,设置控制器。
  • ov5640_remove: 驱动的卸载函数,释放资源,关闭设备。

5)设备树和 I2C 驱动

  • 支持通过设备树配置(`of_device_id`)和 I2C 设备(`i2c_device_id`)进行驱动注册和识别。

6) 运行时电源管理

  • 使用 `pm_runtime` API 实现设备的电源管理,确保在不使用时可以降低功耗。

4.3 V4L2 API

1)V4L2 子设备操作

  • ov5640_get_fmt: 获取当前的格式设置。此函数可以被用户空间应用程序用来查询当前的图像格式。
  • ov5640_set_fmt: 设置图像格式。用户空间可以通过此函数请求更改图像的格式。
  • ov5640_get_selection: 获取选择区域的信息,通常用于获取裁剪区域或原生尺寸。
  • ov5640_set_frame_interval: 设置帧间隔(即帧率)。
  • ov5640_get_frame_interval: 获取当前的帧间隔设置。
  • ov5640_enum_frame_size: 枚举支持的帧大小,可以让用户空间查询传感器支持的分辨率。
  • ov5640_enum_frame_interval: 枚举支持的帧间隔,允许用户空间查询每个分辨率下支持的帧率。
  • ov5640_enum_mbus_code: 枚举支持的媒体总线代码,允许用户空间查询支持的像素格式。

2)控制操作

  • ov5640_set_ctrl: 设置各种控制参数(如曝光、增益、白平衡等)。这些控制通常是通过 V4L2 控制接口暴露的。
  • ov5640_g_volatile_ctrl: 获取可变控制值,如当前增益和曝光值。这可以用于监视传感器状态。

3)流控制

  • ov5640_s_stream: 启动或停止视频流。用户空间应用程序可以通过此函数控制数据流的开始和结束。

4)设备管理

  • ov5640_probe: 驱动的探测函数,虽然通常不直接暴露给用户空间,但它是驱动初始化的一部分,确保设备正确设置。
  • ov5640_remove: 驱动的移除函数,用于释放资源,通常在设备卸载时调用。

5)运行时电源管理

  • ov5640_sensor_suspend 和 ov5640_sensor_resume : 用于控制设备的电源状态,通常在设备进入休眠或唤醒时调用。

4.4 IIC 相关函数

4.4.1 ov5640_write_reg

static int ov5640_write_reg(struct ov5640_dev *sensor, u16 reg, u8 val)
{
	struct i2c_client *client = sensor->i2c_client;
	struct i2c_msg msg;
	u8 buf[3];
	int ret;

	buf[0] = reg >> 8;
	buf[1] = reg & 0xff;
	buf[2] = val;

	msg.addr = client->addr;
	msg.flags = client->flags;
	msg.buf = buf;
	msg.len = sizeof(buf);

	ret = i2c_transfer(client->adapter, &msg, 1);
	if (ret < 0) {
		dev_err(&client->dev, "%s: error: reg=%x, val=%x\n",
			__func__, reg, val);
		return ret;
	}

	return 0;
}

4.4.2 ov5640_read_reg

static int ov5640_read_reg(struct ov5640_dev *sensor, u16 reg, u8 *val)
{
	struct i2c_client *client = sensor->i2c_client;
	struct i2c_msg msg[2];
	u8 buf[2];
	int ret;

	buf[0] = reg >> 8;
	buf[1] = reg & 0xff;

	msg[0].addr = client->addr;
	msg[0].flags = client->flags;
	msg[0].buf = buf;
	msg[0].len = sizeof(buf);

	msg[1].addr = client->addr;
	msg[1].flags = client->flags | I2C_M_RD;
	msg[1].buf = buf;
	msg[1].len = 1;

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret < 0) {
		dev_err(&client->dev, "%s: error: reg=%x\n",
			__func__, reg);
		return ret;
	}

	*val = buf[0];
	return 0;
}

4.5 时钟计算

4.5.1 时钟树

4.5.2 Cal Sys_Div_clk

def ov5640_compute_sys_clk(pll_mult, sysdiv):
    '''
                   +----------------+        +----------------+            +----------------+
     xclk          | PRE_DIV0       |        | Mult (4~252)   |  PLL1_clk  | Sys divider    | sys_div_clk
     +-------+-----> 3037[3:0]      +--------> 3036[7:0]      +------------> 3035[7:4]      +-------------->
     12MHz   |     | 3 (fixed)      |        | pll_mult       |            | sysdiv         |
             |     +----------------+        +----------------+            +----------+-----+
    '''
    xclk_freq = 12_000_000
    OV5640_PLL_PREDIV = 3

    PLL1_clk = xclk_freq // OV5640_PLL_PREDIV * pll_mult # PLL1 CLK Out

    # PLL1输出不能超过 1GHz
    if PLL1_clk // 1_000_000 > 1_000:
        return 0

    sys_div_clk = PLL1_clk // sysdiv # Sys divider clk out

    return sys_div_clk

def ov5640_calc_sys_clk(sys_div_clk):
    best_sys_div_clk = 2**32 - 1
    best_sysdiv = 1
    best_mult   = 1

    for _sysdiv in range(1, 16 + 1):
        for _pll_mult in range(4, 252 + 1):

            if _pll_mult > 127 and (_pll_mult % 2 != 0):
                continue # 如果 PLL 乘数超过 127 且为奇数,则跳过内循环,_pll_mult自增一

            _sys_div_clk = ov5640_compute_sys_clk(_pll_mult, _sysdiv)

            if _sys_div_clk == 0:
                break # 如果达到 PLL1 输出的最大允许值,则跳过外循环,sysdiv自增一

            # 更倾向于选择高于期望时钟率的值,即使这意味着精度较低
            if _sys_div_clk < sys_div_clk:
                continue

            if abs(sys_div_clk - _sys_div_clk) < abs(sys_div_clk - best_sys_div_clk):
                best_sys_div_clk = _sys_div_clk
                best_sysdiv      = _sysdiv
                best_mult        = _pll_mult

            if _sys_div_clk == sys_div_clk:
                break

    return best_sysdiv, best_mult, best_sys_div_clk

#test
#sysdiv, pll_mult, best_sys_div_clk = ov5640_calc_sys_clk(48_000_000) # 期望的时钟率
#print(f"best_sysdiv: {sysdiv}, best_pll_mult: {pll_mult}, Sys divider clk: {best_sys_div_clk}")

4.5.3 Get SCLK

def ov5640_get_SCLK():
    '''
              +----------------+        +------------------+         +---------------------+
XVCLK         | PRE_DIV0       |        | Mult (4~252)     |         | Sys divider (0=16)  |
+-------------> 3037[3:0]=0001 +--------> 3036[7:0]=0x38   +---------> 3035[7:4]=0001      +
12MHz         | / 1            | 12MHz  | * 56             | 672MHz  | / 1                 |
              +----------------+        +------------------+         +----------+----------+
                                                                                |
                                                                                |
                                                                                |
                                                                     +----------v----------+
                                                                     | PLL R divider       |
                                                                     | 3037[4]=1 (+1)      |
                                                                     | / 2                 |
                                                                     +----------+----------+
                                                                                |
                                                                                |
                                                                                |
                                                                     +----------v----------+        +---------------------+
                                                                     | BIT div (MIPI 8/10) |        | SCLK divider        | SCLK
                                                                     | 3034[3:0]=0x8)      +--------> 3108[1:0]=01 (2^)   +------->
                                                                     | / 2                 |        | / 2                 | 84MHz
                                                                     +----------+----------+        +---------------------+
    '''
    # Calculate sysclk
    xvclk = 12_000_000
    sclk_rdiv_map = [1, 2, 4, 8]
    bit_div2x = 1

    temp1 = read_cam_dat(0x3034, 1) # OV5640_REG_SC_PLL_CTRL0
    temp2 = temp1[0] & 0x0f
    if temp2 == 8 or temp2 == 10:
        bit_div2x = temp2 // 2

    temp1 = read_cam_dat(0x3035, 1) # OV5640_REG_SC_PLL_CTRL1
    sysdiv = temp1[0] >> 4
    if sysdiv == 0: # 0x3035[7:4]=0x0, sysdiv=16
        sysdiv = 16

    temp1 = read_cam_dat(0x3036, 1) # OV5640_REG_SC_PLL_CTRL2
    multiplier = temp1[0]

    temp1 = read_cam_dat(0x3037, 1) # OV5640_REG_SC_PLL_CTRL3
    prediv = temp1[0] & 0x0f
    pll_rdiv = ((temp1[0] >> 4) & 0x01) + 1

    temp1 = read_cam_dat(0x3108, 1) # OV5640_REG_SYS_ROOT_DIVIDER
    temp2 = temp1[0] & 0x03
    sclk_rdiv = sclk_rdiv_map[temp2]

    PLL1_clk = xvclk * multiplier // prediv
    _SCLK = PLL1_clk // sysdiv // pll_rdiv * 2 // bit_div2x // sclk_rdiv

    return _SCLK

SCLK = ov5640_get_SCLK()
print(f"SCLK: {SCLK} Hz")

4.5.4 ov5640_set_mipi_pclk

ov5640_csi2_link_freqs = [
    992000000, 888000000, 768000000, 744000000, 672000000, 672000000, #  0 - 5
    592000000, 592000000, 576000000, 576000000, 496000000, 496000000, #  6 - 11
    384000000, 384000000, 384000000, 336000000, 296000000, 288000000, # 12 - 17
    248000000, 192000000, 192000000, 192000000, 96000000,             # 18 - 22
]

def ov5640_set_mipi_pclk():

    # Use the link frequency calculated in ov5640_update_pixel_rate()
    link_freq = ov5640_csi2_link_freqs[13]

    if link_freq > 490_000_000:
        mipi_div = 1
    else:
        mipi_div = 2 # 0x0305[3:0]

    sysclk = link_freq * mipi_div
    prediv, mult, sysdiv = ov5640_calc_sys_clk(sysclk)

    root_div   = 1    # PLL_ROOT_DIV = 2, 0x3037[4], fixed
    bit_div    = 0x08 # 0x3034[3:0], 0x08 = 8bit-mode, 0x0A = 10bit-mode
    pclk_div   = 0    # PCLK_ROOT_DIV = 1, 0x3108[5:4], fixed
    sclk_div   = 1    # SCLK root divider = 2, 0x3108[1:0], fixed
    sclk2x_div = 0    # sclk2x root divider = 1, 0x3108[1:0], fixed

    num_lanes = 2
    sample_rate = (link_freq * mipi_div * num_lanes * 2) // 16
    pclk_period = 2000000000 // sample_rate # Period of pixel clock, 0x4837[7:0]

    ov5640_mod_reg(0x3034, 0x0f, 0x08)                      # OV5640_REG_SC_PLL_CTRL0 = 0x3034
    ov5640_mod_reg(0x3035, 0xff, (sysdiv << 4) | mipi_div)  # OV5640_REG_SC_PLL_CTRL1 = 0x3035
    ov5640_mod_reg(0x3036, 0xff, mult)                      # OV5640_REG_SC_PLL_CTRL2 = 0x3036
    ov5640_mod_reg(0x3037, 0x1f, (root_div << 4) | prediv)  # OV5640_REG_SC_PLL_CTRL3 = 0x3037
    ov5640_mod_reg(0x3108, 0x3f, (pclk_div << 4) | (sclk2x_div << 2) | sclk_div) # OV5640_REG_SYS_ROOT_DIVIDER = 0x3108
    write_cam_dat(0x4837, pclk_period)                      # OV5640_REG_PCLK_PERIOD  = 0x4837

    print(f"link_freq: {link_freq}, PLL1 out: {sysclk}, sample_rate: {sample_rate}")

ov5640_set_mipi_pclk()

4.6 曝光控制

4.6.1 自动曝光

基于平均值的自动曝光控制(AEC)系统通过使用一系列特定寄存器来调整图像的亮度和曝光。这些寄存器包括:

  • 寄存器(0x3A0F)和(0x3A10):分别设置图像亮度的高阈值和低阈值。
  • 寄存器(0x3A1B)和(0x3A1E):控制图像从稳定状态到不稳定状态的亮度阈值。

当图像的平均亮度(由寄存器 0x56A1 测量)处于特定的阈值范围内时,AEC 会维持当前的曝光和增益。如果平均亮度超出这些阈值,AEC 将自动调整曝光和增益,以使亮度回到设定的范围内。

AEC系统提供手动和自动两种速度调整模式:

  • 手动模式允许用户选择正常或快速调整曝光。
  • 自动模式下,系统会根据目标亮度和当前亮度之间的差异自动调整步进速度。

此外,还有两个寄存器(0x3A11)和(0x3A1F)用于在手动模式下快速调整 AEC 的范围。这使得 AEC 能够根据实际情况灵活调整,以实现图像的最佳曝光。

这个基于平均值的 AEC 系统通过一系列精细的控制和自动调整机制,确保图像在不同亮度条件下保持最佳曝光,从而达到稳定和高质量的图像输出。

在自动模式下,AEC 将根据目标值和当前值之间的差异自动计算所需的调整步骤。因此,对于这种模式来说,外部控制区域是无意义的。

4.6.2 获取曝光值

def get_exposure():

    val = read_cam_dat(0x56A1, 1)
    print(f"Image luminance average value AVG Readout: {val[0]}")

    val = read_cam_dat(0x3500, 3)
    exp = (val[0] & 0x0F)<<16 | val[1]<<8 | val[2]
    exp = exp >> 4

    temp = read_cam_dat(0x3503, 1)
    if temp[0] & 0x01: # 0x3503[0]=1
        print(f"Manual Exposure Mode: {exp}")
    else: # 0x3503[0]=0
        temp = read_cam_dat(0x350C, 2)
        aec_vts = temp[0]<<8 | temp[1]

        print(f"Auto Exposure Mode: {exp}")
        print(f"AEC VTS Output: {aec_vts}")

get_exposure()

---
Image luminance average value AVG Readout: 110
Auto Exposure Mode: 1770
AEC VTS Output: 0

4.6.3 设置自动曝光

def set_autoexposure(aec):
    ov5640_mod_reg(0x3503, 0x01, 1 if aec else 0x00);

set_autoexposure(1) # 0=Enable AEC; 1=Disable AEC

4.6.4 自动曝光阈值

def ov5640_set_ae_target(target):

    ae_low  = target * 23 // 25  # 0.92
    ae_high = target * 27 // 25  # 1.08

    fast_high = ae_high << 1
    if (fast_high > 255):
        fast_high = 255

    fast_low = ae_low >> 1

    write_cam_dat(0x3A0F, ae_high)
    write_cam_dat(0x3A10, ae_low)
    write_cam_dat(0x3A1B, ae_high)
    write_cam_dat(0x3A1E, ae_low)
    write_cam_dat(0x3A11, fast_high)
    write_cam_dat(0x3A1F, fast_low)

ov5640_set_ae_target(52) # in ov5640.c @line-3873

4.6.5 设定手动曝光值

def set_exposure(exp):
    exp = exp << 4
    write_cam_dat(0x3500, (exp>>16)&0x0F)
    write_cam_dat(0x3501, (exp>>8 )&0xFF)
    write_cam_dat(0x3502,  exp     &0xF0)

set_exposure(200)

4.7 增益控制

4.7.1 获取当前配置

def get_gain():
    
    val  = read_cam_dat(0x350A, 2)
    gain = val[0]*256 + val[1]
    gain &= 0x3ff
    
    temp = read_cam_dat(0x3503, 1)
    
    if temp[0] & 0x02:
        print(f"Manual Gain Mode: {gain}") # 0x3503[1]=1
    else:
        print(f"Auto Gain Mode: {gain}")   # 0x3503[1]=0

get_gain()

---
Auto Gain Mode: 19

4.7.2 设置自动增益

def set_autogain(on):
    ov5640_mod_reg(0x3503, 0x02, 0x02 if on else 0x00)

set_autogain(1) # 0=Enable AGC, 1=Disable AGC

4.8 Light Frequency

4.8.1 获取 Light Freq

def ov5640_get_light_freq():
    # 获取带宽滤波值
    val = read_cam_dat(0x3C01, 1) # 0x3C01[7], 0=Auto, 1=Manual
    AM = val[0] & 0x80

    if AM:
        print("Banding filter: Manual Mode")

        val = read_cam_dat(0x3C00, 1)
        light_freq = 50 if (val[0] & 0x04) else 60
    else:
        print("Banding filter: Auto Mode")

        val = read_cam_dat(0x3C0C, 1)
        light_freq = 50 if (val[0] & 0x01) else 60

    return light_freq

ov5640_get_light_freq()

---
Banding filter: Auto Mode
50

4.8.2 设置自动/手动 

def ov5640_set_ctrl_light_am(AM):

    ov5640_mod_reg(0x3C01, 0x80, AM << 7) # AM=0, Auto; AM=1, Manual
    ov5640_mod_reg(0x3A00, 0x20, AM << 5)

def ov5640_set_ctrl_light_freq(LINE_FREQ):
    
    ov5640_mod_reg(0x3C00, 0x04, (1<<2) if LINE_FREQ else 0) # LINE_FREQ=1,50Hz; LINE_FREQ=0,60Hz

ov5640_set_ctrl_light_am(0)  # AM=0, Auto; AM=1, Manual
ov5640_set_ctrl_light_freq(1) # 0=60Hz; 1=50Hz;

 4.8.3 Band Filter

val = read_cam_dat(0x3A00, 1) # Bit[5]: Band function enable

print(f"Band function enable: {'Yes' if (val[0] & 0x20) else 'No'}")

4.9 Night Mode

val = read_cam_dat(0x3A00, 1) # Bit[2]: Night Mode

print(f"Night Mode: {'Yes' if (val[0] & 0x04) else 'No'}")

4.10 获取 VTS/HTS

val = read_cam_dat(0x380E, 2) # Total vertical size
vts = val[0]*256 + val[1]

val = read_cam_dat(0x380C, 2) # Total vertical size
hts = val[0]*256 + val[1]

print(f"Total Size: {hts} x {vts}")

---
Total Size: 1896 x 984

4.11 Virtual Channel

def get_virtual_channel():
    temp = read_cam_dat(0x4814, 1)
    channel = temp[0] >> 6; # 保留第6和第7位
    
    return channel

def set_virtual_channel(channel):
    if (channel > 3):
        print("Wrong virtual_channel parameter, expected (0..3).")
        return 0

    temp = read_cam_dat(0x4814, 1)
    temp = temp[0] & (~(3 << 6)) # 清除第6和第7位
    temp |= (channel << 6)       # 设置第6和第7位
    
    write_cam_dat(0x4814, temp)

    # 返回修改后的值
    return temp

vc = get_virtual_channel()
print(f"Virtual Channel: {vc}")

5. 总结

OV5640 驱动非常复杂,通过一系列博文分享其调试过程,暂时告一段落,继续研究 KV260 在 AI 方向的应用。

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

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

相关文章

微信小程序Webview与H5通信

背景 近期有个微信小程序需要用到web-view嵌套H5的场景&#xff0c;该应用场景需要小程序中频繁传递数据到H5进行渲染&#xff0c;且需要保证页面不刷新。 由于微信小程序与H5之间的通信限制比较大&#xff0c;显然无法满足于我的业务场景 探索 由于微信小程序与webview的环境是…

【maven-4】IDEA 配置本地 Maven 及如何使用 Maven 创建 Java 工程

IntelliJ IDEA&#xff08;以下简称 IDEA&#xff09;是一款功能强大的集成开发环境&#xff0c;广泛应用于 Java 开发。下面将详细介绍如何在 IDEA 中配置本地 Maven&#xff0c;并创建一个 Maven Java 工程&#xff0c;快速上手并高效使用 Maven 进行 Java 开发。 1. Maven …

交通流量预测:基于交通流量数据建立模型

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

【故障处理系列--移动云云盘根目录在线扩容】

移动云云盘根目录扩容 **目的&#xff1a;**测试harbor仓库服务器的根目录能否在线扩容 1、移动云控制台系统盘扩容 这里以我自己的虚拟机演示 2、查看分区的文件类型 3、安装growpart工具 rootgitlab-cli:~# apt install cloud-guest-utils -y rootgitlab-cli:~# apt ins…

不可分割的整体—系统思考的微妙法则

不可分割的整体——系统思考的微妙法则 作为企业领导者&#xff0c;我们经常需要做出决策&#xff0c;但有时候&#xff0c;我们会忽略一个事实&#xff1a;每个决策都不是孤立的&#xff0c;它背后都是一个复杂系统的一部分。 无论是市场动态、团队协作&#xff0c;还是产品…

内核模块里获取当前进程和父进程的cmdline的方法及注意事项,涉及父子进程管理,和rcu的初步介绍

一、背景 在编写内核态系统监控代码时&#xff0c;有时候为了调试的便捷性&#xff0c;不仅要拿到异常事件有关的线程id&#xff0c;进程id和父进程id&#xff0c;还需要拿到当前进程和父进程的comm和cmdline。主要有几下几个原因&#xff1a; 1&#xff09;单纯的pid或者tgi…

京东OPPO定制版 12.1.0 | 安装包只有4.7M,无短视频,适合轻度使用

底部四个标签&#xff0c;没有短视频&#xff0c;可以正常登录。安装包体积很小&#xff0c;功能基本可用&#xff0c;特别适合追求简洁界面和轻度使用的用户。 大小&#xff1a;4.7M 下载地址&#xff1a; 百度网盘&#xff1a;https://pan.baidu.com/s/1lD0o1y9X3s4hRiz-8F…

AOSP的同步问题

repo sync同步时提示出错: error: .repo/manifests/: contains uncommitted changesRepo command failed due to the following UpdateManifestError errors: contains uncommitted changes解决方法&#xff1a; 1、cd 进入.repo/manifests cd .repo/manifests2、执行如下三…

Redis【1】- 如何阅读Redis 源码

1 Redis 的简介 Redis 实际上是简称&#xff0c;全称为 Remote Dictionary Server (远程字典服务器)&#xff0c;由 Salvatore Sanfilippo 写的高性能 key-value 存储系统&#xff0c;其完全开源免费&#xff0c;遵守 BSD 协议。Redis 与其他 key-value 缓存产品&#xff08;如…

鸿蒙修饰符

文章目录 一、引言1.1 什么是修饰符1.2 修饰符在鸿蒙开发中的重要性1.3 修饰符的作用机制 二、UI装饰类修饰符2.1 Styles修饰符2.1.1 基本概念和使用场景2.1.2 使用示例2.1.3 最佳实践 2.2 Extend修饰符2.2.1 基本概念2.2.2 使用示例2.2.3 Extend vs Styles 对比2.2.4 使用建议…

nginx安装和负载均衡

1. nginx安装 &#xff08;1&#xff09;安装依赖项&#xff1a; yum -y install gcc gcc-c make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel&#xff08;2&#xff09;下载Nginx源代码&#xff1a; http://nginx.org/en/download.html https://nginx.o…

部署 Prometheus

实验环境 IP地址服务192.168.88.10Prometheus服务端, Consul, Grafana, Node-Exporter192.168.88.77MySQL, Node-Exporter192.168.88.30Nginx&#xff0c;Node-Exporter 一、Prometheus Server 端安装和相关配置 【Prometheus1.sh】 &#xff08;1&#xff09;上传 prometh…

通过深度点图表示的隐式场实现肺树结构的高效解剖标注文献速递-生成式模型与transformer在医学影像中的应用

Title 题目 Efficient anatomical labeling of pulmonary tree structures via deeppoint-graph representation-based implicit fields 通过深度点图表示的隐式场实现肺树结构的高效解剖标注 01 文献速递介绍 近年来&#xff0c;肺部疾病&#xff08;Decramer等&#xff…

# 22_ Python基础到实战一飞冲天(二)-python基础(二十二)--名片管理系统案例:cards_tools.py文件

22_ Python基础到实战一飞冲天&#xff08;二&#xff09;-python基础&#xff08;二十二&#xff09;–名片管理系统案例&#xff1a;cards_tools.py文件 一、框架搭建-09-准备名片操作函数修改主文件中函数调用 1、名片管理系统 案例&#xff1a;框架搭建 — cards_tools.p…

Python 和 Pyecharts 对Taptap相关数据可视化分析

结果展示&#xff1a; 数据来源&#xff1a; Python爬取TapTap 热门游戏信息并存储到数据库&#xff08;详细版&#xff09; 目录 结果展示&#xff1a; 数据来源&#xff1a; Python爬取TapTap 热门游戏信息并存储到数据库&#xff08;详细版 一、引言 二、准备工作 三、…

泷羽sec-shell (3)脚本参数传递与数学运算

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

重塑视频新语言,让每一帧都焕发新生——Video-Retalking,开启数字人沉浸式交流新纪元!

模型简介 Video-Retalking 模型是一种基于深度学习的视频再谈话技术&#xff0c;它通过分析视频中的音频和图像信息&#xff0c;实现视频角色口型、表情乃至肢体动作的精准控制与合成。这一技术的实现依赖于强大的技术架构和核心算法&#xff0c;特别是生成对抗网络&#xff0…

Mybatis Plus 增删改查方法(一、增)

先定义一个简单的测试表&#xff0c;执行脚本如下&#xff1a; create table user(id bigint primary key auto_increment,name varchar(255) not null,age int not null default 0 check (age > 0) ); 根据Spingbootmybatisplus的结构根据表自行构建结构&#xff0c;大致…

依赖倒置原则:Java实践篇

在软件开发的世界里&#xff0c;设计原则如同指南针&#xff0c;指引着我们构建更加健壮、可维护和可扩展的系统。其中&#xff0c;依赖倒置原则&#xff08;Dependency Inversion Principle&#xff0c;DIP&#xff09;是面向对象设计&#xff08;OOD&#xff09;中的一个重要…

【MySQL】库和表的基本操作

目录 库 库的增删查改 字符集与校验集 库的备份与恢复 表 表的创建和删除 用不同的存储引擎创建表的区别 查看表 修改表 添加删除属性 修改改变属性 上篇博客我们讲了数据库的基本理解&#xff0c;对数据库有了一个大致的概念&#xff0c;下面我们来介绍一下库和表的…