3. 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 → 分辨率与输出格式 → 启动流”的顺序下发,每步之间需延时等待传感器稳定。
完整实现
#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;
}
运行与验证
- 确认总线号:
ls /dev/i2c-*,按实际修改I2C_BUS。 - 编译运行:
gcc i2c_init.c -o i2c_init && sudo ./i2c_init。 - 通过条件:程序打印
sensor id high = 0x56(OV5640 产品 ID 高字节),且无write ... failed报错,最后0x3008回读值为0x02,表示已进入流模式。 - 若读 ID 失败,检查从机地址、总线号与接线;若某寄存器写失败,检查该寄存器是否需在复位前配置。