4. 配置帧率

任务说明

本案例目标:通过修改Sensor寄存器配置,将输出帧率设置为30fps,并通过测量VSYNC信号周期或读取帧计数器来验证实际帧率。以OV5640 Sensor为例(I²C地址0x3C),完成寄存器配置与帧率测量闭环。

环境与依赖

  • 硬件:OV5640 Sensor模组、I²C控制器(主控端)、逻辑分析仪或示波器(用于测量VSYNC)
  • 软件:Linux内核(含I²C设备驱动)、i2c-tools工具包
  • 驱动接口:/dev/i2c-N 设备节点,使用 ioctl 进行I²C读写

帧率配置原理

OV5640的帧率由PLL配置、行/帧消隐参数共同决定。30fps@1080p(1920×1080)的推荐寄存器配置如下表所示。核心思路:通过设置 0x380C(HTS高字节)和 0x380D(HTS低字节)控制一行总像素数,通过 0x380E(VTS高字节)和 0x380F(VTS低字节)控制一帧总行数。帧率 = 像素时钟 / (HTS × VTS)。

寄存器地址值说明
0x380C0x0AHTS[11:8]
0x380D0x50HTS[7:0](总2640像素)
0x380E0x07VTS[11:8]
0x380F0xD0VTS[7:0](总2000行)

当像素时钟为96MHz时,帧率 = 96,000,000 / (2640 × 2000) ≈ 18.18fps。要达到30fps,需将VTS调整为:96,000,000 / (2640 × 30) ≈ 1212行,即VTS = 0x04BC。

关键完整代码

以下程序通过I²C写入VTS寄存器将帧率配置为30fps,并读取帧计数器寄存器(0x380A/0x380B为VSYNC中断后的帧计数)来测量实际帧率。测量原理:在1秒时间内统计VSYNC中断次数。

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

#define I2C_BUS "/dev/i2c-2"
#define OV5640_ADDR 0x3C
#define REG_VTS_H 0x380E
#define REG_VTS_L 0x380F
#define REG_FRAME_CNT 0x380A  // 帧计数器(每帧+1)

static int i2c_fd;

int i2c_write_reg(uint8_t reg, uint8_t val) {
    uint8_t buf[2] = {reg, val};
    if (write(i2c_fd, buf, 2) != 2) {
        perror("I2C write failed");
        return -1;
    }
    return 0;
}

int i2c_read_reg(uint8_t reg, uint8_t *val) {
    if (write(i2c_fd, &reg, 1) != 1) {
        perror("I2C write reg failed");
        return -1;
    }
    if (read(i2c_fd, val, 1) != 1) {
        perror("I2C read failed");
        return -1;
    }
    return 0;
}

void set_30fps(void) {
    // VTS = 0x04BC (1212行),配合HTS=2640,像素时钟96MHz → 30fps
    i2c_write_reg(REG_VTS_H, 0x04);
    i2c_write_reg(REG_VTS_L, 0xBC);
    printf("VTS set to 0x04BC (1212 lines)\n");
}

float measure_fps(int duration_sec) {
    uint8_t cnt_h, cnt_l;
    uint16_t start_cnt, end_cnt;
    int elapsed = 0;
    time_t start_time, now;

    // 读取起始帧计数
    i2c_read_reg(REG_FRAME_CNT, &cnt_h);
    i2c_read_reg(REG_FRAME_CNT + 1, &cnt_l);
    start_cnt = (cnt_h << 8) | cnt_l;

    time(&start_time);
    while (elapsed < duration_sec) {
        usleep(100000);  // 100ms轮询
        time(&now);
        elapsed = (int)(now - start_time);
    }

    // 读取结束帧计数
    i2c_read_reg(REG_FRAME_CNT, &cnt_h);
    i2c_read_reg(REG_FRAME_CNT + 1, &cnt_l);
    end_cnt = (cnt_h << 8) | cnt_l;

    // 处理计数器回绕(16位)
    uint16_t diff = (end_cnt >= start_cnt) ?
                    (end_cnt - start_cnt) :
                    (65535 - start_cnt + end_cnt + 1);
    return (float)diff / duration_sec;
}

int main(void) {
    i2c_fd = open(I2C_BUS, O_RDWR);
    if (i2c_fd < 0) {
        perror("Failed to open I2C bus");
        return 1;
    }
    if (ioctl(i2c_fd, I2C_SLAVE, OV5640_ADDR) < 0) {
        perror("Failed to set I2C slave address");
        close(i2c_fd);
        return 1;
    }

    set_30fps();
    sleep(1);  // 等待配置生效

    float fps = measure_fps(2);  // 测量2秒
    printf("Measured frame rate: %.2f fps\n", fps);

    close(i2c_fd);
    return 0;
}

编译与运行

# 编译
gcc -o ov5640_fps ov5640_fps.c

# 运行(需要root权限访问I²C设备)
sudo ./ov5640_fps

验证方法与判定标准

  • 寄存器验证:运行后使用 i2cget -y 2 0x3C 0x380E 和 i2cget -y 2 0x3C 0x380F 确认VTS值分别为0x04和0xBC。
  • 帧率验证:程序输出 Measured frame rate 应在29.0~31.0fps范围内(考虑计数器读取延迟和时钟精度,允许±1fps误差)。
  • 外部测量(可选):用示波器或逻辑分析仪测量VSYNC引脚(OV5640的VSYNC输出)脉冲频率,应接近30Hz。

预期输出(基于代码逻辑和给定配置):VTS set to 0x04BC (1212 lines) 后,测量结果约为 30.00 fps。实际值可能因Sensor时钟偏差略有浮动。

常见问题

  • 帧率偏低:确认像素时钟是否为96MHz。若Sensor使用外部24MHz晶振且PLL配置不同,需按实际像素时钟重新计算VTS值。
  • I²C通信失败:检查总线号(/dev/i2c-2)是否正确,可用 i2cdetect -y 2 扫描确认设备地址。
  • 帧计数器不递增:确认Sensor已退出待机模式(寄存器0x3008 = 0x02),且正在输出有效视频流。