在ov5640芯片手册中看到这样一句话:
The OV5640 supports both a digital video parallel port and a serial MIPI port.
所以ov5640既支持数字并口视频传输,同样支持mipi接口规范。
摄像头插入到开发板上面的时候,如果有匹配的驱动程序,就会调用到probe函数,先从probe函数来分析。
(一)probe函数
1.1 获取设备ID
在probe函数中,首先通过几个of类函数来获取pwn-gpios,rst-gpios等的值。
然后就是设置sensor_data结构体ov5640_data。每个sensor_data结构体都代表一个具体的设备,来看看这个结构体:
struct sensor_data {
const struct ov5642_platform_data *platform_data;
struct v4l2_int_device *v4l2_int_device;
struct i2c_client *i2c_client;
struct v4l2_pix_format pix;
struct v4l2_captureparm streamcap;
bool on; //设备是否上电
/* control settings */
int brightness;
int hue;
int contrast;
int saturation;
int red;
int green;
int blue;
int ae_mode;
u32 mclk; //mclk时钟
u8 mclk_source; //mclk时钟源
struct clk *sensor_clk;
int csi;
void (*io_init)(void); //初始化函数
};
然后就是填充这个结构体,重点是i2c_client的填充,需要根据填充的这个client来找到对应的设备。那么怎么确定找到的设备就是我们想要的呢?就是通过读设备的设备ID。可以看到在probe函数中通过:
retval= ov5640_read_reg(OV5640_CHIP_ID_HIGH_BYTE, &chip_id_high); 和
retval= ov5640_read_reg(OV5640_CHIP_ID_LOW_BYTE, &chip_id_low);
来分别读取ov5640设备ID的高字节和低字节。我们可以看到,在ov5640_mipi.c中是这样定义的:
#define OV5640_CHIP_ID_HIGH_BYTE 0x300A
#define OV5640_CHIP_ID_LOW_BYTE 0x300B
我们将这两个地址去ov5640的芯片手册中搜索可以发现,
这两个地址就是ov5640设备的ID所在的地址,通过这个设备ID就能确定我们找到的设备。
1.2 ov5640_power_on函数
ov5640_power_on(dev);
ov5640_power_on(dev);
static int ov5640_power_on(struct device *dev)
{
int ret = 0;
io_regulator = devm_regulator_get(dev, "DOVDD");
if (!IS_ERR(io_regulator)) {
regulator_set_voltage(io_regulator,
OV5640_VOLTAGE_DIGITAL_IO,
OV5640_VOLTAGE_DIGITAL_IO);
ret = regulator_enable(io_regulator);
if (ret) {
pr_err("%s:io set voltage error\n", __func__);
return ret;
} else {
dev_dbg(dev,
"%s:io set voltage ok\n", __func__);
}
} else {
pr_err("%s: cannot get io voltage error\n", __func__);
io_regulator = NULL;
}
core_regulator = devm_regulator_get(dev, "DVDD");
if (!IS_ERR(core_regulator)) {
regulator_set_voltage(core_regulator,
OV5640_VOLTAGE_DIGITAL_CORE,
OV5640_VOLTAGE_DIGITAL_CORE);
ret = regulator_enable(core_regulator);
if (ret) {
pr_err("%s:core set voltage error\n", __func__);
return ret;
} else {
dev_dbg(dev,
"%s:core set voltage ok\n", __func__);
}
} else {
core_regulator = NULL;
pr_err("%s: cannot get core voltage error\n", __func__);
}
analog_regulator = devm_regulator_get(dev, "AVDD");
if (!IS_ERR(analog_regulator)) {
regulator_set_voltage(analog_regulator,
OV5640_VOLTAGE_ANALOG,
OV5640_VOLTAGE_ANALOG);
ret = regulator_enable(analog_regulator);
if (ret) {
pr_err("%s:analog set voltage error\n",
__func__);
return ret;
} else {
dev_dbg(dev,
"%s:analog set voltage ok\n", __func__);
}
} else {
analog_regulator = NULL;
pr_err("%s: cannot get analog voltage error\n", __func__);
}
return ret;
}
从上面的程序中可以看出来,它设置了三个regulator,中文翻译为”稳定器“,内核中有关于这个的模块的驱动框架,关于这个驱动框架的分析可以查看:
http://www.wowotech.net/pm_subsystem/regulator_framework_overview.html
我们这里只分析与我们相关的东西。
通过regulator_set_voltage函数来为这几个regulator设置电压,分别设置为OV5640_VOLTAGE_DIGITAL_IO,OV5640_VOLTAGE_DIGITAL_CORE和OV5640_VOLTAGE_ANALOG。
#define OV5640_VOLTAGE_ANALOG 2800000
#define OV5640_VOLTAGE_DIGITAL_CORE 1500000
#define OV5640_VOLTAGE_DIGITAL_IO 1800000
同时,在dts文件中,定义了它的电压为多少,
ov564x_mipi: ov564x_mipi@3c { /* i2c2 driver */
compatible = "ovti,ov564x_mipi";
reg = <0x3c>;
clocks = <&clks 201>;
clock-names = "csi_mclk";
DOVDD-supply = <&vgen4_reg>; /* 1.8v */
AVDD-supply = <&vgen3_reg>; /* 2.8v, rev C board is VGEN3
rev B board is VGEN5 */
DVDD-supply = <&vgen2_reg>; /* 1.5v*/
pwn-gpios = <&gpio1 19 1>; /* active low: SD1_CLK */
rst-gpios = <&gpio1 20 0>; /* active high: SD1_DAT2 */
csi_id = <1>;
mclk = <24000000>;
mclk_source = <0>;
};
可以看出来,它们设置的一致。那么在dts文件中为啥这么设置呢?肯定是根据ov5640的芯片手册中设置的:
1.3 ov5640_reset函数
ov5640_reset();
这个函数用来重置摄像头,如下所示:
static void ov5640_reset(void)
{
/* camera reset */
gpio_set_value(rst_gpio, 1);
/* camera power dowmn */
gpio_set_value(pwn_gpio, 1);
msleep(5);
gpio_set_value(pwn_gpio, 0);
msleep(5);
gpio_set_value(rst_gpio, 0);
msleep(1);
gpio_set_value(rst_gpio, 1);
msleep(5);
gpio_set_value(pwn_gpio, 1);
}
这里面的rst_gpio和pwn_gpio是在probe函数中通过of类函数获取的,既然是通过of类函数获得的,那么在dts文件中肯定有对应的设置:
ov564x_mipi: ov564x_mipi@3c { /* i2c2 driver */
compatible = "ovti,ov564x_mipi";
reg = <0x3c>;
clocks = <&clks 201>;
clock-names = "csi_mclk";
DOVDD-supply = <&vgen4_reg>; /* 1.8v */
AVDD-supply = <&vgen3_reg>; /* 2.8v, rev C board is VGEN3
rev B board is VGEN5 */
DVDD-supply = <&vgen2_reg>; /* 1.5v*/
pwn-gpios = <&gpio1 19 1>; /* active low: SD1_CLK */
rst-gpios = <&gpio1 20 0>; /* active high: SD1_DAT2 */
csi_id = <1>;
mclk = <24000000>;
mclk_source = <0>;
};
这个函数操作的是gpio1的19和20位,关于这两位引脚的意义还需要继续深入。它一共包含3个参数,第一个参数表示gpio1,第二个参数是否表示gpio1的哪一位??第三个参数表示默认值。比如说pwn-gpios的默认值是1,说明置0的时候是上电,置1的时候是关电。
同时,重置摄像头的时候,ov5640_reset()函数设置pwn-gpios和rst-gpios的写入顺序是否是固定的?
1.3 ov5640_standby函数
ov5640_standby(0);
static void ov5640_standby(s32 enable)
{
if (enable)
gpio_set_value(pwn_gpio, 1);
else
gpio_set_value(pwn_gpio, 0);
msleep(2);
}
这个函数就是根据函数的传入参数来向pwn_gpio寄存器写值,向pwn_gpio寄存器写1表示关电,写0代表关电。至于向寄存器中写1代表上电还是关电,这个需要查看对应寄存器在dts文件中写进去的值。
1.4
先上电,通过ov5640_read_reg函数来获取摄像头的设备ID以后,再次使用ov5640_standby(1);来关电。
之后就是通过ov5640_int_device.priv= &ov5640_data;来将ov5640_int_device结构体的priv指向设置好的sensor_data结构体,然后通过
retval= v4l2_int_device_register(&ov5640_int_device);来将ov5640_int_device作为一个slave设备注册到v4l2框架中,在这个函数中,会将slave设备添加到int_list链表中,尝试使用v4l2_int_device_try_attach_all函数来匹配master设备。
(二)在probe函数执行完毕以后,就可以操作这个摄像头了,之后我们继续按照mxc_v4l2_capture.c这个应用程序的执行过程来完善ov5640_mipi的一些操作。首先是open函数。
2.1 vidioc_int_g_ifparm函数
在open函数中,首先调用vidioc_int_g_ifparm(cam->sensor,&ifparm);函数来从slave设备中获取ifparm的信息,最终会调用到ov5640_mipi.c的ioctl_g_ifparm函数。先来看open函数中,它传入了两个参数:cam->sensor,ifparm;其实在ov5640_mipi.c中,它并没有从cam->sensor里面获取ifparm的信息来填充到ifparm中,而是在ioctl_g_ifparm函数中直接为ifparm中的各个成员变量赋值。主要是为ifparm.u.bt656成员赋值,包括clock_curr,mode,clock_min,clock_max等等。
2.2 vidioc_int_g_fmt_cap函数
在这个函数中,就直接用f->fmt.pix= sensor->pix;来将sensor_data里面的pix结构体赋给了cam_fmt里面的fmt.pix结构体。
2.3 vidioc_int_s_power函数
vidioc_int_s_power(cam->sensor, 1);
static int ioctl_s_power(struct v4l2_int_device *s, int on)
{
struct sensor_data *sensor = s->priv;
if (on && !sensor->on) {
if (io_regulator)
if (regulator_enable(io_regulator) != 0)
return -EIO;
if (core_regulator)
if (regulator_enable(core_regulator) != 0)
return -EIO;
if (gpo_regulator)
if (regulator_enable(gpo_regulator) != 0)
return -EIO;
if (analog_regulator)
if (regulator_enable(analog_regulator) != 0)
return -EIO;
/* Make sure power on */
ov5640_standby(0);
} else if (!on && sensor->on) {
if (analog_regulator)
regulator_disable(analog_regulator);
if (core_regulator)
regulator_disable(core_regulator);
if (io_regulator)
regulator_disable(io_regulator);
if (gpo_regulator)
regulator_disable(gpo_regulator);
ov5640_standby(1);
}
sensor->on = on;
return 0;
}
在这个函数中,会根据vidioc_int_s_power函数传入的第二个参数的值on来决定是否将设备上电。1表示上电,0表示关电。这里面的io_regulator,core_regulator和analog_regulator是在ov5640_power_on函数中获取到的,gpo_regulator应该没有设置。然后需要注意的一点是:在ioctl_s_power函数中的第二个参数如果为1的话代表上电,为0的话代表关电。但是在ov5640_standby函数中,如果为0代表上电,为1代表关电。所以,在if(on &&!sensor->on)判断语句中,如果想要确定摄像头是上电的话,需要使用ov5640_standby(0);。这一点比较拗口。
2.4 vidioc_int_dev_init函数
vidioc_int_dev_init(cam->sensor);
static int ioctl_dev_init(struct v4l2_int_device *s)
{
struct sensor_data *sensor = s->priv;
u32 tgt_xclk; /* target xclk */
u32 tgt_fps; /* target frames per secound */
int ret;
enum ov5640_frame_rate frame_rate;
void *mipi_csi2_info;
ov5640_data.on = true;
/* mclk */
tgt_xclk = ov5640_data.mclk;
tgt_xclk = min(tgt_xclk, (u32)OV5640_XCLK_MAX);
tgt_xclk = max(tgt_xclk, (u32)OV5640_XCLK_MIN);
ov5640_data.mclk = tgt_xclk;
pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000);
/* Default camera frame rate is set in probe */
tgt_fps = sensor->streamcap.timeperframe.denominator /
sensor->streamcap.timeperframe.numerator;
pr_debug(" tft_fps is %d.\n", tgt_fps);
if (tgt_fps == 15)
frame_rate = ov5640_15_fps;
else if (tgt_fps == 30)
frame_rate = ov5640_30_fps;
else
return -EINVAL; /* Only support 15fps or 30fps now. */
mipi_csi2_info = mipi_csi2_get_info();
/* enable mipi csi2 */
if (mipi_csi2_info)
mipi_csi2_enable(mipi_csi2_info);
else {
printk(KERN_ERR "%s() in %s: Fail to get mipi_csi2_info!\n",
__func__, __FILE__);
return -EPERM;
}
ret = ov5640_init_mode(frame_rate, ov5640_mode_INIT, ov5640_mode_INIT);
return ret;
}
2.4.1
在这个函数中,首先通过tgt_xclk= ov5640_data.mclk;来获取到mclk的值。这个ov5640_data.mclk是在probe函数中设置的。然后对tgt_xclk与摄像头允许的最大值最小值进行判断,
#define OV5640_XCLK_MIN 6000000
#define OV5640_XCLK_MAX 24000000
使得tgt_xclk位于这个区间内。
然后计算tgt_fps的值,它需要的两个值同样是在probe函数中设置的。
根据tgt_fps的值来设置frame_rate的值,这个frame_rate用在后面的ov5640_init_mode函数中。
然后通过mipi_csi2_get_info函数来获取到mxc_mipi_csi2.c文件中的gmipi_csi2结构体,这个结构体是一个全局变量。获取到这个结构体以后通过mipi_csi2_enable函数来使能。具体操作是设置mipi_csi2_info结构体里面的mipi_en位为true,然后通过clk_prepare_enable函数来使能mipi_csi2_info结构体里面的cfg_clk和dphy_clk。
之后就是调用ov5640_init_mode函数了。
2.4.2 ov5640_init_mode函数
这个函数在ioctl_s_parm函数和ioctl_dev_init函数中调用,这个函数用来设置ov5640的模式,有以下几种模式:
enum ov5640_mode {
ov5640_mode_MIN = 0,
ov5640_mode_VGA_640_480 = 0,
ov5640_mode_QVGA_320_240 = 1,
ov5640_mode_NTSC_720_480 = 2,
ov5640_mode_PAL_720_576 = 3,
ov5640_mode_720P_1280_720 = 4,
ov5640_mode_1080P_1920_1080 = 5,
ov5640_mode_QSXGA_2592_1944 = 6,
ov5640_mode_QCIF_176_144 = 7,
ov5640_mode_XGA_1024_768 = 8,
ov5640_mode_MAX = 8,
ov5640_mode_INIT = 0xff, /*only for sensor init*/
};
在mxc_v4l2_capture.c这个应用程序中,可以通过-m选项来指定使用哪种模式,然后将-m后面指定的模式保存在g_capture_mode中,然后通过parm.parm.capture.capturemode= g_capture_mode;再调用ioctl(fd_v4l,VIDIOC_S_PARM, &parm)函数,最终就会调用这个ov5640_init_mode函数来修改ov5640的模式。
在ioctl_dev_init函数中已经设置了frame_rate,然后设置ov5640_init_mode函数其他两个参数,第二个参数表示新设置的mode,第三个参数表示原来的mode。但是需要注意的是在mxc_v4l2_capture.c中的mxc_v4l_open函数,它调用ov5640_init_mode来初始化摄像头,这时候,摄像头的初始mode和新mode都没有指定,上面说了,它们是在VIDIOC_S_PARMioctl中指定的,这时候就需要指定ov5640_init_mode的第二个和第三个参数都为ov5640_mode_INIT。
static int ov5640_init_mode(enum ov5640_frame_rate frame_rate,
enum ov5640_mode mode, enum ov5640_mode orig_mode)
{
struct reg_value *pModeSetting = NULL;
s32 ArySize = 0;
int retval = 0;
void *mipi_csi2_info;
u32 mipi_reg, msec_wait4stable = 0;
enum ov5640_downsize_mode dn_mode, orig_dn_mode;
if ((mode > ov5640_mode_MAX || mode < ov5640_mode_MIN)
&& (mode != ov5640_mode_INIT)) {
pr_err("Wrong ov5640 mode detected!\n");
return -1;
}
/*对mode进行判断,根据上面列举的enumov5640_mode,它就是判断传入的mode是否合法。*/
mipi_csi2_info = mipi_csi2_get_info(); //获取 mipi_csi2_info
/* initial mipi dphy */
if (!mipi_csi2_info) {
printk(KERN_ERR "%s() in %s: Fail to get mipi_csi2_info!\n",
__func__, __FILE__);
return -1;
}
/*判断mipi_csi2_info是否获取成功*/
if (!mipi_csi2_get_status(mipi_csi2_info))
mipi_csi2_enable(mipi_csi2_info);
if (!mipi_csi2_get_status(mipi_csi2_info)) {
pr_err("Can not enable mipi csi2 driver!\n");
return -1;
}
/*首先判断mipi_csi2_info中mipi_en是否置位,这一位表示是否使能了mipi摄像头。如果没有使能的话,就调用mipi_csi2_enable函数来使能mipi_csi2_info里面的cfg_clk和dphy_clk,然后将mipi_en置位为true。设置好以后继续调用mipi_csi2_get_status函数来确定是否使能成功。*/
mipi_csi2_set_lanes(mipi_csi2_info);
/*在这个函数中将mipi_csi2_info里面的lanes的值写到MIPI_CSI2_N_LANES寄存器中,
而这个mipi_csi2_info里面的lanes的值是什么时候获取到的呢?在mxc_mipi_csi2.c文件中的mipi_csi2_probe函数中,通过of_property_read_u32函数来保存到mipi_csi2_info中的。
*/
/*Only reset MIPI CSI2 HW at sensor initialize*/
if (mode == ov5640_mode_INIT)
mipi_csi2_reset(mipi_csi2_info);
/*如果是mode等于ov5640_mode_INIT,就表示是在ioctl_dev_init函数中调用的,就调用mipi_csi2_reset函数来初始化MIPI_CSI2相关的寄存器。这个函数在mxc_mipi_csi2.c文件中定义.
int mipi_csi2_reset(struct mipi_csi2_info *info)
{
_mipi_csi2_lock(info);
mipi_csi2_write(info, 0x0, MIPI_CSI2_PHY_SHUTDOWNZ);
mipi_csi2_write(info, 0x0, MIPI_CSI2_DPHY_RSTZ);
mipi_csi2_write(info, 0x0, MIPI_CSI2_CSI2_RESETN);
mipi_csi2_write(info, 0x00000001, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL1);
mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0x00010044, MIPI_CSI2_PHY_TST_CTRL1);
mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0x00000014, MIPI_CSI2_PHY_TST_CTRL1);
mipi_csi2_write(info, 0x00000002, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0x00000000, MIPI_CSI2_PHY_TST_CTRL0);
mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_PHY_SHUTDOWNZ);
mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_DPHY_RSTZ);
mipi_csi2_write(info, 0xffffffff, MIPI_CSI2_CSI2_RESETN);
_mipi_csi2_unlock(info);
return 0;
}
EXPORT_SYMBOL(mipi_csi2_reset);
这个函数中反复向MIPI_CSI2_PHY_TST_CTRL0和MIPI_CSI2_PHY_TST_CTRL1寄存器中写不同的值,到底有什么目的??明天来了把值写进去仔细看看。*/
if (ov5640_data.pix.pixelformat == V4L2_PIX_FMT_UYVY)
mipi_csi2_set_datatype(mipi_csi2_info, MIPI_DT_YUV422);
else if (ov5640_data.pix.pixelformat == V4L2_PIX_FMT_RGB565)
mipi_csi2_set_datatype(mipi_csi2_info, MIPI_DT_RGB565);
else
pr_err("currently this sensor format can not be supported!\n");
/*根据ov5640_data.pix.pixelformat的值来设置mipi_csi2_info里面的datatype的值。通过mipi_csi2_set_datatype函数来设置。这个ov5640_data.pix.pixelformat的值是在ov5640_probe函数中设置的。从这两个判断语句中可以推断出来,在这个驱动中,指定摄像头只支持V4L2_PIX_FMT_UYVY和V4L2_PIX_FMT_RGB565两种格式。*/
/*看下面的代码前,需要对enumov5640_downsize_mode进行一个初步的了解:
/* image size under 1280 * 960 are SUBSAMPLING
* image size upper 1280 * 960 are SCALING
*/
enum ov5640_downsize_mode {
SUBSAMPLING,
SCALING,
};
这个enum的注释写的很清楚了,当imagesize大于1280* 960时,downsize_mode为SCALING,当imagesize小于1280* 960时,downsize_mode为SUBSAMPLING。下面的代码主要是根据新mode的ov5640_downsize_mode类型和初始mode的ov5640_downsize_mode类型来决定使用哪个函数来切换mode模式。*/
dn_mode = ov5640_mode_info_data[frame_rate][mode].dn_mode;
orig_dn_mode = ov5640_mode_info_data[frame_rate][orig_mode].dn_mode;
/*在这里有一个二维数组ov5640_mode_info_data:
staticstruct ov5640_mode_info ov5640_mode_info_data[2][ov5640_mode_MAX +1],再来看这个函数中,它会根据frame_rate和mode两个下标来找到对应的元素。关于这个frame_rate,它是enumov5640_frame_rate类型的,
enum ov5640_frame_rate {
ov5640_15_fps,
ov5640_30_fps
};
这两个值默认为0和1.关于enum的默认值的分析可以看:《C语言enum枚举类型解析》
http://blog.csdn.net/skyflying2012/article/details/22736633
*/
if (mode == ov5640_mode_INIT) {
pModeSetting = ov5640_init_setting_30fps_VGA;
ArySize = ARRAY_SIZE(ov5640_init_setting_30fps_VGA);
ov5640_data.pix.width = 640;
ov5640_data.pix.height = 480;
retval = ov5640_download_firmware(pModeSetting, ArySize);
if (retval < 0)
goto err;
pModeSetting = ov5640_setting_30fps_VGA_640_480;
ArySize = ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480);
retval = ov5640_download_firmware(pModeSetting, ArySize);
}
/*当mode为ov5640_mode_INIT时,代表第一次初始化摄像头设备,就直接设置pModeSetting和ArySize的值,然后调用ov5640_download_firmware函数来初始化摄像头。在这个文件中,可以看出来ov5640_init_setting_30fps_VGA数组是一堆寄存器的地址和值的组合,通过这个函数来设置摄像头内部寄存器的值,而不是设置开发板上面的控制寄存器。这些值可以对照ov5640摄像头的芯片手册来查看。但是不理解的是,在这里设置了两次,分别为ov5640_init_setting_30fps_VGA和ov5640_setting_30fps_VGA_640_480。*/
else if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) ||
(dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) {
/* change between subsampling and scaling
* go through exposure calucation */
retval = ov5640_change_mode_exposure_calc(frame_rate, mode);
}
/*因为dn_mode和orig_dn_mode都是enumov5640_downsize_mode类型的,它们都只有两种值,所以当两者不同时,就通过ov5640_change_mode_exposure_calc函数来改变摄像头的mode。*/
else {
/* change inside subsampling or scaling
* download firmware directly */
retval = ov5640_change_mode_direct(frame_rate, mode);
}
/*当dn_mode和orig_dn_mode这两者相同时,就直接调用ov5640_change_mode_direct函数改变mode就行了。关于这三个函数的分析,在分析完这个函数后分析。*/
if (retval < 0)
goto err;
/*总之,当调用ov5640_init_mode函数的时候,比如在ioctl_dev_init函数中调用,它就表示第一次使用摄像头,ov5640_init_mode函数的mode模式都为ov5640_mode_INIT,然后就会在ov5640_init_mode函数中调用ov5640_download_firmware函数来设置摄像头上面的寄存器。如果是在VIDIOC_S_PARMioctl调用的时候,这时候,就可能修改ov5640_init_mode函数里面的mode模式,这时候就需要根据mode模式里面的ov5640_downsize_mode来判断是否改变了,如果改变了的话,就会调用ov5640_change_mode_exposure_calc函数来设置摄像头上面的寄存器,如果没有改变的话,就直接调用ov5640_change_mode_direct设置摄像头上面的寄存器即可。*/
OV5640_set_AE_target(AE_Target);
/*关于这一块的讲解查看ov5640芯片手册的《4.5AEC/AGC algorithms》这一节,主要是设置自动曝光控制(AutoExposure Control,AEC)和自动增益控制(AutoGain Control,AGC)。
函数如下所示:
static int OV5640_set_AE_target(int target)
{
/* stable in high */
int fast_high, fast_low;
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;
ov5640_write_reg(0x3a0f, AE_high);
ov5640_write_reg(0x3a10, AE_low);
ov5640_write_reg(0x3a1b, AE_high);
ov5640_write_reg(0x3a1e, AE_low);
ov5640_write_reg(0x3a11, fast_high);
ov5640_write_reg(0x3a1f, fast_low);
return 0;
}
这个计算过程是摄像头相关的算法,在芯片手册中详细介绍了。
ov5640芯片上面的0x3a0f存储的是曝光时间的最大值,0x3a10存储的是曝光时间的最小值。通过这个函数可以看出来它的计算过程。
0x3a1b存储的是图像从稳定状态切换到不稳定状态时曝光时间的最大值,0x3a1e存储的是图像从稳定状态切换到不稳定状态时曝光时间的最小值。
这时候需要理解另外一个寄存器:0x56a1,这个寄存器中保存的是目标图像的亮度平均值,这个寄存器是一个只读寄存器。
当0x56a1寄存器的值不在{0x3a1e,0x3a1b}这个区间之内时,AEC就调整它们,并且使他们位于{0x3a10,0x3a0f}这个区间。所以这个{0x3a1e,0x3a1b}这个区间称为稳定状态区间。
当0x56a1寄存器的值位于{0x3a1e,0x3a1b}这个区间的时候,就是指图像处于稳定状态。反之,则称为不稳定状态。
上面的讲解是AEC处于auto状态时,AEC就会去自动调节这些参数,同时,这个AEC支持manual模式,关于这个模式的选择,是通过操作0x3503寄存器来完成的,我们在后面的OV5640_turn_on_AE_AG()函数分析中再具体分析。
当AEC处于manual模式的时候,还分为normal和fast选择。normal就是支持手工一点一点地调节曝光量,fast快速的调节曝光量。究竟有多快速呢。。。{0x3a1f,0x3a11}这个区间是fast情况下的曝光区间,当0x56a1寄存器里面的值小于0x3a1f时,AEC就直接将0x56a1寄存器里面的值乘2;当0x56a1寄存器里面的值大于0x3a11时,AEC就直接将0x56a1寄存器里面的值除以2。
*/
OV5640_get_light_freq();
/*这个函数如下所示,这个函数的目的是获得ov5640的频闪,关于摄像头频闪的讲解,可以查看《Camera图像处理原理分析-抗噪变焦 频闪 等 》
http://blog.csdn.net/colorant/article/details/1913334:
static int OV5640_get_light_freq(void)
{
/* get banding filter value */
int temp, temp1, light_freq = 0;
u8 tmp;
temp = ov5640_read_reg(0x3c01, &tmp);
if (temp & 0x80) {
/* manual */
temp1 = ov5640_read_reg(0x3c00, &tmp);
if (temp1 & 0x04) {
/* 50Hz */
light_freq = 50;
} else {
/* 60Hz */
light_freq = 60;
}
} else {
/* auto */
temp1 = ov5640_read_reg(0x3c0c, &tmp);
if (temp1 & 0x01) {
/* 50Hz */
light_freq = 50;
} else {
/* 60Hz */
/* 这里是不是一个bug,应该写上:light_freq = 60; */
}
}
return light_freq;
}
对比程序和芯片手册,可以看出来,首先会根据0x3c01的bit[7]来判断是auto还是manual模式,
如果为auto模式的话,就会去读取0x3c0c寄存器的bit[0],为1的话就是50Hz的频闪,为0的话就是60Hz的频闪。
如果为manual模式的话,就会去读取0x3c00寄存器的bit[2],为1的话就是50Hz的频闪,为0的话就是60Hz的频闪。
*/
OV5640_set_bandingfilter();
/*这个函数是设置摄像头的工频干扰,CMOS是行曝光,也就是在每行曝光时间决定了画面的亮度,举例:一个50HZ的光源,电压曲线为正弦曲线,那能量曲线定性分析可以认为是取了绝对值的电压曲线。那就是能量做1/100秒的周期变化。那就要求曝光的时间必须是1/100秒的整数倍。如果没有把曝光时间调整到1/100秒的整数倍,就有可能会有每行的曝光值不一样,造成同一个image上有水波纹现象。CCD是整帧同时曝光,所以,工频干扰表现的就是图像有轻微的闪烁。产生的原理与CMOS sensor的原理相似。
static void OV5640_set_bandingfilter(void)
{
int prev_VTS;
int band_step60, max_band60, band_step50, max_band50;
/* read preview PCLK */
prev_sysclk = OV5640_get_sysclk();
/* read preview HTS */
prev_HTS = OV5640_get_HTS();
/* read preview VTS */
prev_VTS = OV5640_get_VTS();
/* calculate banding filter */
/* 60Hz */
band_step60 = prev_sysclk * 100/prev_HTS * 100/120;
ov5640_write_reg(0x3a0a, (band_step60 >> 8));
ov5640_write_reg(0x3a0b, (band_step60 & 0xff));
max_band60 = (int)((prev_VTS-4)/band_step60);
ov5640_write_reg(0x3a0d, max_band60);
/* 50Hz */
band_step50 = prev_sysclk * 100/prev_HTS;
ov5640_write_reg(0x3a08, (band_step50 >> 8));
ov5640_write_reg(0x3a09, (band_step50 & 0xff));
max_band50 = (int)((prev_VTS-4)/band_step50);
ov5640_write_reg(0x3a0e, max_band50);
}
关于这个函数,它首先通过OV5640_get_sysclk函数来获取系统的时钟,然后OV5640_get_HTS和OV5640_get_VTS函数分别获取ov5640的Horizontaltotalsize 和verticaltotal size,对于50Hz和60Hz,有不同的计算方式,但是这个计算方法看半天都没有理解。。。
*/
ov5640_set_virtual_channel(ov5640_data.csi);
/*这个函数设置虚拟通道,如下所示:
static void ov5640_set_virtual_channel(int channel)
{
u8 channel_id;
ov5640_read_reg(0x4814, &channel_id);
channel_id &= ~(3 << 6);
ov5640_write_reg(0x4814, channel_id | (channel << 6));
}
但是在ov5640的芯片手册中,这几位显示的是DEBUGMODE,如下所示
*/
/* add delay to wait for sensor stable */
if (mode == ov5640_mode_QSXGA_2592_1944) {
/* dump the first two frames: 1/7.5*2
* the frame rate of QSXGA is 7.5fps */
msec_wait4stable = 267;
} else if (frame_rate == ov5640_15_fps) {
/* dump the first nine frames: 1/15*9 */
msec_wait4stable = 600;
} else if (frame_rate == ov5640_30_fps) {
/* dump the first nine frames: 1/30*9 */
msec_wait4stable = 300;
}
msleep(msec_wait4stable);
/*根据不同的模式来选择等待sensor稳定的时间。*/
if (mipi_csi2_info) {
unsigned int i;
i = 0;
/* wait for mipi sensor ready */
mipi_reg = mipi_csi2_dphy_status(mipi_csi2_info);
while ((mipi_reg == 0x200) && (i < 10)) {
mipi_reg = mipi_csi2_dphy_status(mipi_csi2_info);
i++;
msleep(10);
}
if (i >= 10) {
pr_err("mipi csi2 can not receive sensor clk!\n");
return -1;
}
/* mipi_csi2_dphy_status函数就是去读取MIPI_CSI2_PHY_STATE寄存器的值,然后保存在mipi_reg变量中。这个寄存器如下所示:
可以看出来,这个寄存器的bit[9]表示sensor的clocklane处于什么样的状态。注意:这一位是activelow,所以置0时表示使能。再来看这一段代码,它会一直等待这一位置0,一直等待10×10ms,如果过了这一段时间,MIPI_CSI2_PHY_STATE寄存器的bit[9]还保持为1的状态时,就打印出“mipicsi2 can not receive sensor clk!”这句话报错。
*/
i = 0;
/* wait for mipi stable */
mipi_reg = mipi_csi2_get_error1(mipi_csi2_info);
while ((mipi_reg != 0x0) && (i < 10)) {
mipi_reg = mipi_csi2_get_error1(mipi_csi2_info);
i++;
msleep(10);
}
if (i >= 10) {
pr_err("mipi csi2 can not reveive data correctly!\n");
return -1;
}
}
err:
return retval;
}
/*这一段代码就是通过mipi_csi2_get_error1函数来读取MIPI_CSI2_ERR1寄存器的值。这个寄存器表示MIPI控制寄存器中是否有错误发生,如果都没有错误的话,这个寄存器里面的值都应该是0.关于这个寄存器中每一位的含义就不分析了。*/
至此,ov5640_init_mode函数就大致分析完毕了,也就代表ioctl_dev_init函数分析完毕。这个ioctl_dev_init函数是mxc_v4l2_open函数中的最后一个函数。
对于应用程序中调用的其他ioctl函数,大致过程都是相似的,不会涉及到芯片上面寄存器的设置,大部分都是与ov5640_probe函数中sensor_data结构体相关,就不再分析他们了。
下面分析ov5640_init_mode函数中没有分析的三个函数:
ov5640_download_firmware
ov5640_change_mode_exposure_calc
ov5640_change_mode_direct
这三个函数是关于芯片设置最重要的函数。在ov5640_init_mode函数中,会根据第二个参数和第三个参数的不同来选择执行哪一路过程。当mode== ov5640_mode_INIT时,会直接执行ov5640_download_firmware函数来设置ov5640摄像头。
先来看ov5640_download_firmware函数:
/* download ov5640 settings to sensor through i2c */
static int ov5640_download_firmware(struct reg_value *pModeSetting, s32 ArySize)
{
register u32 Delay_ms = 0;
register u16 RegAddr = 0;
register u8 Mask = 0;
register u8 Val = 0;
u8 RegVal = 0;
int i, retval = 0;
for (i = 0; i < ArySize; ++i, ++pModeSetting) {
Delay_ms = pModeSetting->u32Delay_ms;
RegAddr = pModeSetting->u16RegAddr;
Val = pModeSetting->u8Val;
Mask = pModeSetting->u8Mask;
if (Mask) {
retval = ov5640_read_reg(RegAddr, &RegVal);
if (retval < 0)
goto err;
RegVal &= ~(u8)Mask;
Val &= Mask;
Val |= RegVal;
}
retval = ov5640_write_reg(RegAddr, Val);
if (retval < 0)
goto err;
if (Delay_ms)
msleep(Delay_ms);
}
err:
return retval;
}
先看这个函数的注释:通过I2C总线下载设置到ov5640摄像头中。意思就是我们在驱动中写好ov5640摄像头上面寄存器的配置以后,通过这个函数设置进去。
再来看这个函数,这个函数设置的核心是reg_value结构体,这个结构体如下所示:
struct reg_value {
u16 u16RegAddr;
u8 u8Val;
u8 u8Mask;
u32 u32Delay_ms;
};
它就包含了所要设置一个寄存器所需要的所有元素:地址,值,掩码,延迟时间。以ov5640_init_setting_30fps_VGA[]为例:
static struct reg_value ov5640_init_setting_30fps_VGA[] = {
{0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0},
{0x3103, 0x03, 0, 0}, {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0},
{0x3034, 0x18, 0, 0}, {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0},
。。。。。。。。。。。。。。。。。。
};
通过这个函数,就能够把这个结构体数组里面的值都设置到ov5640中去。而这个结构体数组中寄存器的值怎么来的就是关键了,理论上可以参考芯片手册一位一位地设置,但是厂家应该会提供这个初始化数组的。
下面来看第二个函数ov5640_change_mode_exposure_calc:
/* sensor changes between scaling and subsampling
* go through exposure calcualtion
*/
static int ov5640_change_mode_exposure_calc(enum ov5640_frame_rate frame_rate,
enum ov5640_mode mode)
{
struct reg_value *pModeSetting = NULL;
s32 ArySize = 0;
u8 average;
int prev_shutter, prev_gain16;
int cap_shutter, cap_gain16;
int cap_sysclk, cap_HTS, cap_VTS;
int light_freq, cap_bandfilt, cap_maxband;
long cap_gain16_shutter;
int retval = 0;
/* check if the input mode and frame rate is valid */
pModeSetting =
ov5640_mode_info_data[frame_rate][mode].init_data_ptr;
ArySize =
ov5640_mode_info_data[frame_rate][mode].init_data_size;
ov5640_data.pix.width =
ov5640_mode_info_data[frame_rate][mode].width;
ov5640_data.pix.height =
ov5640_mode_info_data[frame_rate][mode].height;
if (ov5640_data.pix.width == 0 || ov5640_data.pix.height == 0 ||
pModeSetting == NULL || ArySize == 0)
return -EINVAL;
/* auto focus */
/* OV5640_auto_focus();//if no af function, just skip it */
/* turn off AE/AG */
OV5640_turn_on_AE_AG(0);
/*这个函数的意思是根据传入的参数的值来决定打开还是关闭autoAE/AG。关于这个的寄存器地址是0x3503,可以看出来,想要关闭auto模式的话,只需要将bit[1:0]设置为0x03即可,打开auto的话,将bit[1:0]清空即可。OV5640_turn_on_AE_AG这个函数正是这么做的。
static void OV5640_turn_on_AE_AG(int enable)
{
u8 ae_ag_ctrl;
ov5640_read_reg(0x3503, &ae_ag_ctrl);
if (enable) {
/* turn on auto AE/AG */
ae_ag_ctrl = ae_ag_ctrl & ~(0x03);
} else {
/* turn off AE/AG */
ae_ag_ctrl = ae_ag_ctrl | 0x03;
}
ov5640_write_reg(0x3503, ae_ag_ctrl);
}
*/
/* read preview shutter */
prev_shutter = OV5640_get_shutter();
if ((binning_on()) && (mode != ov5640_mode_720P_1280_720)
&& (mode != ov5640_mode_1080P_1920_1080))
prev_shutter *= 2;
/*这个OV5640_get_shutter()函数就是去读取ov5640芯片上面的0x3500~0x3502地址里面的值,然后根据图示构造出Exposure的值,返回保存到prev_shutter中。在这里,shutter就是曝光时间的意思。
在这里有一个函数:binning_on():
static bool binning_on(void)
{
u8 temp;
ov5640_read_reg(0x3821, &temp);
temp &= 0xfe;
if (temp)
return true;
else
return false;
}
关于binning的概念和理解,可以查看《sensor的skippingand binning 模式》
http://blog.csdn.net/sloan6/article/details/8242713
在ov5640的芯片手册中,可以看到,与这个概念有关的寄存器位于0x3821的bit[0],但是个人感觉这个函数写的有问题,只涉及0x3821的bit[0]位,如果只打算比较bit[0]而不改变其他位的话,应该:
if(temp & 0x01),根本不需要temp&= 0xfe操作,经过这一个操作的话,假如其他位有不为0的,那么这个temp的值就不为0.
*/
/* read preview gain */
prev_gain16 = OV5640_get_gain16();
/*看看OV5640_get_gain16()这个函数:
static int OV5640_get_gain16(void)
{
/* read gain, 16 = 1x */
int gain16;
u8 temp;
gain16 = ov5640_read_reg(0x350a, &temp) & 0x03;
gain16 = (gain16<<8) + ov5640_read_reg(0x350b, &temp);
return gain16;
}
从这个函数中很显然就能猜出来这个previewgain保存在0x350a和0x350b这两个寄存器中,看看芯片手册:
最终这个函数读取这两个寄存器的值,然后保存在prev_gain16变量中。
*/
/* get average */
ov5640_read_reg(0x56a1, &average);
/*这里直接使用ov5640_read_reg函数来读取0x56a1的值保存在&average中:
*/
/* turn off night mode for capture */
OV5640_set_night_mode();
/*这个函数如下所示,这个函数与上面的binning_on()函数做比较的话,这个函数的设置是正确的,因为这个函数只想设置0x3a00的bit[2]为0,在不改变其他位的基础上面,通过mode&= 0xfb的形式是最好的。
static void OV5640_set_night_mode(void)
{
/* read HTS from register settings */
u8 mode;
ov5640_read_reg(0x3a00, &mode);
mode &= 0xfb;
ov5640_write_reg(0x3a00, mode);
}
*/
/* turn off overlay */
/* ov5640_write_reg(0x3022, 0x06);//if no af function, just skip it */
OV5640_stream_off();
/* Write capture setting */
retval = ov5640_download_firmware(pModeSetting, ArySize);
if (retval < 0)
goto err;
/*最终这个函数里面也是调用ov5640_download_firmware函数来将从ov5640_mode_info_data数组中获取到的ov5640中寄存器的值写到对应的寄存器中。*/
/* read capture VTS */
cap_VTS = OV5640_get_VTS();
cap_HTS = OV5640_get_HTS();
cap_sysclk = OV5640_get_sysclk();
/*先来看前两个函数,读取寄存器的值来获取verticalzise和horizontalsize的值。寄存器如下所示:
这个OV5640_get_sysclk()函数有点复杂,以后再分析。
*/
/* calculate capture banding filter */
light_freq = OV5640_get_light_freq();
if (light_freq == 60) {
/* 60Hz */
cap_bandfilt = cap_sysclk * 100 / cap_HTS * 100 / 120;
} else {
/* 50Hz */
cap_bandfilt = cap_sysclk * 100 / cap_HTS;
}
cap_maxband = (int)((cap_VTS - 4)/cap_bandfilt);
/* calculate capture shutter/gain16 */
if (average > AE_low && average < AE_high) {
/* in stable range */
cap_gain16_shutter =
prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk
* prev_HTS/cap_HTS * AE_Target / average;
} else {
cap_gain16_shutter =
prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk
* prev_HTS/cap_HTS;
}
/* gain to shutter */
if (cap_gain16_shutter < (cap_bandfilt * 16)) {
/* shutter < 1/100 */
cap_shutter = cap_gain16_shutter/16;
if (cap_shutter < 1)
cap_shutter = 1;
cap_gain16 = cap_gain16_shutter/cap_shutter;
if (cap_gain16 < 16)
cap_gain16 = 16;
} else {
if (cap_gain16_shutter >
(cap_bandfilt * cap_maxband * 16)) {
/* exposure reach max */
cap_shutter = cap_bandfilt * cap_maxband;
cap_gain16 = cap_gain16_shutter / cap_shutter;
} else {
/* 1/100 < (cap_shutter = n/100) =< max */
cap_shutter =
((int) (cap_gain16_shutter/16 / cap_bandfilt))
*cap_bandfilt;
cap_gain16 = cap_gain16_shutter / cap_shutter;
}
}
/* write capture gain */
OV5640_set_gain16(cap_gain16);
/* write capture shutter */
if (cap_shutter > (cap_VTS - 4)) {
cap_VTS = cap_shutter + 4;
OV5640_set_VTS(cap_VTS);
}
OV5640_set_shutter(cap_shutter);
OV5640_stream_on();
err:
return retval;
}
/*后面这些计算应该都是摄像头中定义的,有点看不懂,以后再分析吧*/
最后看看ov5640_change_mode_direct函数:
static int ov5640_change_mode_direct(enum ov5640_frame_rate frame_rate,
enum ov5640_mode mode)
{
struct reg_value *pModeSetting = NULL;
s32 ArySize = 0;
int retval = 0;
/* check if the input mode and frame rate is valid */
pModeSetting =
ov5640_mode_info_data[frame_rate][mode].init_data_ptr;
ArySize =
ov5640_mode_info_data[frame_rate][mode].init_data_size;
ov5640_data.pix.width =
ov5640_mode_info_data[frame_rate][mode].width;
ov5640_data.pix.height =
ov5640_mode_info_data[frame_rate][mode].height;
if (ov5640_data.pix.width == 0 || ov5640_data.pix.height == 0 ||
pModeSetting == NULL || ArySize == 0)
return -EINVAL;
/* turn off AE/AG */
OV5640_turn_on_AE_AG(0);
OV5640_stream_off();
/* Write capture setting */
retval = ov5640_download_firmware(pModeSetting, ArySize);
if (retval < 0)
goto err;
OV5640_stream_on();
OV5640_turn_on_AE_AG(1);
err:
return retval;
}
经过上一个函数的分析,这个函数看起来就相当简单了。
3.OV5640_get_sysclk 函数分析
static int OV5640_get_sysclk(void)
{
/* calculate sysclk */
int xvclk = ov5640_data.mclk / 10000;
int temp1, temp2;
int Multiplier, PreDiv, VCO, SysDiv, Pll_rdiv;
int Bit_div2x = 1, sclk_rdiv, sysclk;
u8 temp;
/*ov5640_data.mclk的值是在ov5640_probe函数中同dts文件中读取的,为24000000.*/
int sclk_rdiv_map[] = {1, 2, 4, 8};
temp1 = ov5640_read_reg(0x3034, &temp);
temp2 = temp1 & 0x0f;
if (temp2 == 8 || temp2 == 10)
Bit_div2x = temp2 / 2;
/*读取0x3034寄存器的值,取低4位除以2后保存在Bit_div2x中。从芯片手册中可以看出来,0x3034的低4位是MIPIbit mode,那么为什么还要除以2呢?在《ov5640_PLL_diagram.jpg》图片的左下角有这样一句话:note6:MIPISCLK= (4 or 5) * PCLK if 2 lanes;= (8 or 10) * PCLK if 1lane。再看Bit_div2x在PLLdiagram中对应的模块是BITdivider,而现在使用的是2lanes模式,所以需要除以2.
*/
temp1 = ov5640_read_reg(0x3035, &temp);
SysDiv = temp1>>4;
if (SysDiv == 0)
SysDiv = 16;
/*读取0x3035寄存器的高4位保存在SysDiv中,看芯片手册中的解释是系统时钟分频器。在《ov5640_PLL_diagram.jpg》图片中对应SYSdivider0模块。
*/
temp1 = ov5640_read_reg(0x3036, &temp);
Multiplier = temp1;
/*读取0x3036寄存器的值保存在Multiplier中,看芯片手册中的解释是PLL倍频器。在《ov5640_PLL_diagram.jpg》图片中对应multiplier模块。
*/
temp1 = ov5640_read_reg(0x3037, &temp);
PreDiv = temp1 & 0x0f;
Pll_rdiv = ((temp1 >> 4) & 0x01) + 1;
/*读取0x3037寄存器的低4位保存在PreDiv中,读取0x3037寄存器的bit[4]保存在Pll_rdiv中。
PreDiv在《ov5640_PLL_diagram.jpg》图片中对应pre-divider模块,Pll_rdiv在《ov5640_PLL_diagram.jpg》图片中对应PLLR divider模块,因为上图中的解释是:如果bit[4]为1的话,就分成2份,所以在代码中将从bit[4]中读取出来的值加1。*/
temp1 = ov5640_read_reg(0x3108, &temp);
temp2 = temp1 & 0x03;
sclk_rdiv = sclk_rdiv_map[temp2];
/*读取0x3108寄存器的低2位保存在temp2中,然后将这个temp2作为下标来从这个数组里面取值:
intsclk_rdiv_map[] = {1, 2, 4, 8};
为什么要从这个数组里面取值呢?看芯片手册中,SCLK为pll_clk的1/2,1/4, 1/8。没法用两位来表示8,所以选择这种方式来选取值。
*/
VCO = xvclk * Multiplier / PreDiv;
/*这个VCO是压控振荡器(VoltageControlledOscillator),是PLL(锁相环)的组成部分,在PLL中,一般是先分频,然后再经过VCO增频,这里先计算的是VCO的输出。在《ov5640_PLL_diagram.jpg》中就是PLL1后的输出。*/
sysclk = VCO / SysDiv / Pll_rdiv * 2 / Bit_div2x / sclk_rdiv;
/*剩下的就是计算过程,根据图《ov5640_PLL_diagram.jpg》就可以写出这个计算过程。*/
return sysclk;
}
《ov5640_PLL_diagram.jpg》是我们网上看到的一个有关ov5640的时钟控制图,我下载的芯片手册都没有这个图,所以一直对这个函数不理解,通过这个图,这个函数的设置过程就很清楚了,《ov5640_PLL_diagram.jpg》图片如下所示:
经过上面的步骤就可以得出OV5640的系统时钟参数。
参考:
《CMOSSensor的调试经验分享》
http://blog.csdn.net/yapingmcu/article/details/37817727
原文:https://blog.csdn.net/yanbixing123/article/details/52299519