3. MIPI摄像头I2C寄存器配置与初始化序列实现

MIPI摄像头I2C寄存器配置与初始化序列实现 学习卡片

任务目标

通过 Linux I2C 设备节点(/dev/i2c-N)对 MIPI 摄像头传感器寄存器进行读写,按上电时序执行初始化序列,使传感器进入可输出 MIPI 数据流的工作状态。本案例以 OV5640 为例,核心是封装寄存器读写原语并组织初始化序列。

环境与依赖

  • Linux 平台,内核已加载 i2c-dev 模块,存在 /dev/i2c-N 节点。
  • 传感器 I2C 从机地址(OV5640 为 0x3C,7 位地址)。
  • 编译:gcc i2c_init.c -o i2c_init。

寄存器读写与初始化序列

OV5640 使用 16 位寄存器地址、8 位数据。读写通过 ioctl(I2C_RDWR) 组合消息完成:写寄存器时先发 2 字节地址再发 1 字节数据;读寄存器时先写地址,再发起读。初始化序列按“软复位 → 时钟/PLL → 分辨率与输出格式 → 启动流”的顺序下发,每步之间需延时等待传感器稳定。

应用层 i2c_init i2c-dev /dev/i2c-N I2C 控制器 SCL / SDA OV5640 0x3C 1. 打开设备 2. 写寄存器 3. 读寄存器 4. 启动流

完整实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

#define I2C_BUS   "/dev/i2c-1"
#define SENSOR_ADDR 0x3C

static int fd = -1;

/* 写 16 位寄存器地址 + 8 位数据 */
static int reg_write(uint16_t reg, uint8_t val)
{
    uint8_t buf[3] = { reg >> 8, reg & 0xFF, val };
    struct i2c_msg msg = { .addr = SENSOR_ADDR, .flags = 0,
                           .len = 3, .buf = buf };
    struct i2c_rdwr_ioctl_data data = { .msgs = &msg, .nmsgs = 1 };
    return ioctl(fd, I2C_RDWR, &data) < 0 ? -1 : 0;
}

/* 读 16 位寄存器地址的 8 位数据 */
static int reg_read(uint16_t reg, uint8_t *val)
{
    uint8_t addr[2] = { reg >> 8, reg & 0xFF };
    struct i2c_msg msgs[2] = {
        { .addr = SENSOR_ADDR, .flags = 0, .len = 2, .buf = addr },
        { .addr = SENSOR_ADDR, .flags = I2C_M_RD, .len = 1, .buf = val },
    };
    struct i2c_rdwr_ioctl_data data = { .msgs = msgs, .nmsgs = 2 };
    return ioctl(fd, I2C_RDWR, &data) < 0 ? -1 : 0;
}

/* 初始化序列:软复位 -> 时钟/PLL -> 输出格式 -> 启动流 */
static const struct { uint16_t reg; uint8_t val; } init_seq[] = {
    { 0x3103, 0x11 },  /* 系统时钟来自 PLL */
    { 0x3008, 0x82 },  /* 软复位 */
    { 0x3008, 0x42 },  /* 退出复位 */
    { 0x3100, 0x00 },  /* 时钟使能 */
    { 0x3035, 0x11 },  /* PLL 控制 */
    { 0x3036, 0x46 },  /* PLL 倍频 */
    { 0x3C07, 0x08 },  /* 预增益 */
    { 0x4300, 0x30 },  /* 输出格式 YUV422 */
    { 0x3820, 0x46 },  /* 时序控制 */
    { 0x3821, 0x00 },  /* 时序控制 */
    { 0x3008, 0x02 },  /* 启动数据流 */
};

int main(void)
{
    fd = open(I2C_BUS, O_RDWR);
    if (fd < 0) { perror("open i2c"); return 1; }

    /* 校验传感器在线:读产品 ID 高字节 */
    uint8_t id = 0;
    if (reg_read(0x300A, &id) < 0) {
        perror("read id"); close(fd); return 1;
    }
    printf("sensor id high = 0x%02X\n", id);

    for (size_t i = 0; i < sizeof(init_seq)/sizeof(init_seq[0]); i++) {
        if (reg_write(init_seq[i].reg, init_seq[i].val) < 0) {
            fprintf(stderr, "write 0x%04X failed\n", init_seq[i].reg);
            close(fd); return 1;
        }
        usleep(2000);  /* 每步等待传感器稳定 */
    }

    /* 回读启动寄存器确认已进入流模式 */
    uint8_t mode = 0;
    reg_read(0x3008, &mode);
    printf("stream mode reg 0x3008 = 0x%02X\n", mode);

    close(fd);
    return 0;
}

运行与验证

  1. 确认总线号:ls /dev/i2c-*,按实际修改 I2C_BUS。
  2. 编译运行:gcc i2c_init.c -o i2c_init && sudo ./i2c_init。
  3. 通过条件:程序打印 sensor id high = 0x56(OV5640 产品 ID 高字节),且无 write ... failed 报错,最后 0x3008 回读值为 0x02,表示已进入流模式。
  4. 若读 ID 失败,检查从机地址、总线号与接线;若某寄存器写失败,检查该寄存器是否需在复位前配置。