Sstar平台Sensor驱动介绍
1. 基本介绍¶
source code位置:
vendor/sigmastar/alkaid/sdk/driver/SensorDriver/
编译方式:
export PATH=android-ndk-r25b/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export CROSS_COMPILE=aarch64-linux-android33-
export ARCH=arm
make clean;make -j8;
驱动生成目录:
vendor/sigmastar/alkaid/sdk/driver/SensorDriver/drv/src/
2. 环境准备¶
开始Porting之前,请先确保Sensor的接线没有问题。根据原理图,参考MIPI SENSOR配置参考文档配置好sensor的lane swap、clk、pdn、rst,将sensor接到板卡对应的位置,确保硬件环境连接畅通。
3. Sensor Driver Porting¶
Sensor driver 实际上被Sensor Interface模块管理,register向Sensor Interface提供sensor信息,包括但不限于sensor的基础图像参数、数据接口参数、控制接口参数、如何上下电、出流、修改FPS,这些信息都需要用于Sensor driver填充结构体或实现部分相应的回调函数,最后注册到Sensor Interface模块。
按照以下的参数与接口说明进行配置,就可以完成SensorDriver的porting工作。
3.1 SensorDriver基本参数说明¶
3.1.1 Sensor图像基本参数¶
基本的出流需要实现LinearEntry的handle注册,基本上都是对 ms_cus_sensor 结构体进行构造,该数据结构原型的关键部分如下:
/* Location: sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef struct __ms_cus_sensor{ ....... char model_id[32]; /**< Please fill the sensor modle id string here .*/ // i2c app_i2c_cfg i2c_cfg; /**< Sensor i2c setting */ // polarity CUS_CLK_POL reset_POLARITY; /** < Sensor REST pin polarity */ CUS_CLK_POL pwdn_POLARITY; /** < Sensor Power Down pin polarity */ // sensor data enum list*/ CUS_CAMSENSOR_ISPTYPE isp_type; /** < Always use ISP_EXT */ CUS_SEN_BAYER bayer_id; /** < Sensor bayer raw pixel order */ CUS_SENIF_BUS sif_bus; /** < Select sensor interface */ CUS_DATAPRECISION data_prec; /** < Raw data bits */ CUS_CAMSENSOR_ORIT orient; /** < Sensor mirror/flip */ cus_camsensor_res_list video_res_supported; /** < Resolution list */ InterfaceAttr_u interface_attr; //sensor calibration CUS_MCLK_FREQ mclk; /** < Sensor master clock frequency */ ...... }ms_cus_sensor;
配置方法如下表:
Parm | Type | value | desc |
---|---|---|---|
model_id | char [31] | string | Sensor名字,任意字符串 |
isp_type | enum | ISP_SOC、ISP_EXT | 只支持ISP_EXT |
bayer_id | enum | CUS_BAYER_RG、CUS_BAYER_GR、CUS_BAYER_BG、CUS_BAYER_GB | Sensor的bayer排列格式 |
sif_bus | enum | CUS_SENIF_BUS_PARL、CUS_SENIF_BUS_MIPI、CUS_SENIF_BUS_BT601、CUS_SENIF_BUS_BT656、CUS_SENIF_BUS_BT1120、CUS_SENIF_BUS_LVDS | Sensor 的数据接口类型 |
data_prec | enum | CUS_DATAPRECISION_8、CUS_DATAPRECISION_10、CUS_DATAPRECISION_16、CUS_DATAPRECISION_12、CUS_DATAPRECISION_14 | 像素位宽支持:8b/10b/12b/14b/16b |
orient | enum | CUS_ORIT_M0F0、CUS_ORIT_M1F0、CUS_ORIT_M0F1、CUS_ORIT_M1F1 | 图像水平和垂直翻转设定,M:Mirror 水平翻转,F:Flip 垂直翻转 |
以SC850SL为例:
以上参数皆可灵活设置,根据实际需求,以Sensor vendor 确定拿到的init table配置为准。
3.1.2 上下电参数¶
Sensor上下电需要按照如下参数来控制:
Parm | Type | value | desc |
---|---|---|---|
mclk | enum | CUS_CMU_CLK_27MHZ、CUS_CMU_CLK_21P6MHZ、CUS_CMU_CLK_12MHZ、CUS_CMU_CLK_5P4MHZ、CUS_CMU_CLK_36MHZ、CUS_CMU_CLK_54MHZ、CUS_CMU_CLK_43P2MHZ、CUS_CMU_CLK_61P7MHZ、CUS_CMU_CLK_72MHZ、CUS_CMU_CLK_48MHZ、CUS_CMU_CLK_24MHZ | mclk频率,按Sensor需求选择 |
pwdn_POLARITY | enum | CUS_CLK_POL_POS、CUS_CLK_POL_NEG | Sensor Power Down Pin 的有效电平 |
reset_POLARITY | enum | CUS_CLK_POL_POS、CUS_CLK_POL_NEG | Sensor Reset Pin 的有效电平 |
- mclk为soc提供给sensor的工作时钟,对于sensor而言,在sensor data sheet中可能被称为 INCK/EXTCLK 等,容易混淆,需要仔细甄别
- pwdn与reset 实际上是用于控制两个GPIO的高低电平,但是并不是所有的sensor上下电都需要两根pin来控制,部分Sensor只需要一根pin就可以实现上电操作
以SC850SL为例:
-
确认XSHUTDN接到哪一个SOC的哪一个PAD,是pwdn还是rst
-
设置相应的引脚 POLARITY 为 CUS_CLK_POL_POS
3.1.3 I2C接口参数¶
大部分的Sensor都是用I2C或兼容I2C的接口进行控制,I2C 接口的参数由app_i2c_cfg结构体定义:
/* sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef struct _app_i2c_cfg{ ISP_I2C_MODE mode; //!< I2C_NORMAL_MODE only ISP_I2C_FMT fmt; //!< I2C data format u32 speed; //!< I2C clock in Hz u16 address; //!< Sensor slave address , bit[7~1] are available, bit[0] user don't care u16 reserved; }__attribute__((packed, aligned(1))) app_i2c_cfg;
Parm | Type | value | desc |
---|---|---|---|
mode | enum | I2C_LEGACY_MODE、I2C_NORMAL_MODE | 只使用 I2C_NORMAL_MODE |
fmt | enum | I2C_FMT_A8D8、I2C_FMT_A16D8、I2C_FMT_A8D16、I2C_FMT_A16D16 | I2C_FMT_AxDy : x bit Address, y bit Data |
speed | uint | 60000~320000 (P5) | I2C 通信速率,单位(Hz) |
address | uint | 0x00~0xFF | Sensor I2C slave address,8bit寻址地址,读写位任意 |
以下图提供的信息为例:
- fmt: I2C_FMT_A16D8
- speed: 咨询sensor vendor建议值
- address:0x60/0x61
3.1.4 数据接口参数¶
数据接口的信息主要由InterfaceAttr_u联合体定义,使用联合体是为了兼容不同的接口类型,这里我们只看MIPI接口:
/* sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef union { ...... //MIPI sensor struct { u32 mipi_lane_num; CUS_SEN_INPUT_FORMAT mipi_data_format; CUS_SENSOR_YUV_ORDER mipi_yuv_order; CUS_CSI_VC_HS_MODE mipi_hsync_mode; CUS_HDR_MODE mipi_hdr_mode; u32 mipi_hdr_virtual_channel_num; } attr_mipi; ...... } InterfaceAttr_u;
Parm | Type | value | desc |
---|---|---|---|
mipi_lane_num | uint | ½/4 | Sensor 输出的 Data Lane数量 |
mipi_data_format | enum | CUS_SEN_INPUT_FORMAT_YUV422、CUS_SEN_INPUT_FORMAT_RGB | 根据格式选择YUV或RGB。YUV422:仅支持YUV4228b,RGB:支持RAW8/10/12/14/16 |
mipi_yuv_order | enum | CUS_SENSOR_YUV_ORDER_CY、CUS_SENSOR_YUV_ORDER_YC | YUV排列方式 |
mipi_hsync_mode | enum | PACKET_HEADER_EDGE1、PACKET_HEADER_EDGE2、PACKET_HEADER_EDGE3、PACKET_FOOTER_EDGE | 只使用 PACKET_FOOTER_EDGE |
mipi_hdr_mode | enum | CUS_HDR_MODE_NONE、CUS_HDR_MODE_SONY_DOL、CUS_HDR_MODE_DCG、CUS_HDR_MODE_COMP_VS、 CUS_HDR_MODE_VC | HDR 种类,Linear mode 选 NONE |
mipi_hdr_virtual_channel_num | uint | 0/½/3 | CSI通道数量,Linear mode 写 0,HDR才会用到其他通道 |
3.1.5 分辨率信息¶
Sensor支持的所有的分辨率信息列表由cus_camsensor_res_list和cus_camsensor_res定义 ,结构体原型如下:
/* sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef struct _cus_camsensor_res_list { u32 num_res; /**< number of sensor resolution in list */ u32 ulcur_res; /**< current sensor resolution index */ cus_camsensor_res res[31]; /**< resolution list max size is 31 */ } __attribute__((packed, aligned(4))) cus_camsensor_res_list; typedef struct _cus_camsensor_res{ u32 width; /**< Sensor out image width */ u32 height; /**< Sensor out image height */ u32 max_fps; /**< Sensor support max fps */ u32 min_fps; /**< Sensor support min fps */ u32 crop_start_x; /**< crop x if need */ u32 crop_start_y; /**< crop y if need */ u32 nOutputWidth; /**< crop output width if need */ u32 nOutputHeight; /**< crop output height if need */ char strResDesc[32]; /**< resolution info string */ } __attribute__((packed, aligned(4))) cus_camsensor_res;
3.2 SensorDriver基础接口¶
从MI SENSOR层的API来看,典型的 Sensor 出流调用流程如下:
->MI_SNR_SetPlaneMode //设置 HDR mode ->MI_SNR_SetRes //选择resolution ->MI_SNR_Enable //使能sensor出流
该过程中会调用Sensor Driver 注册到 Sensor Interface的接口,这些接口需要作为回调实现,回调接口也在ms_cus_sensor的结构体中被定义,出流所需的关键接口原型如下:
/* sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef struct __ms_cus_sensor{ ...... int (*pCus_sensor_poweron)(struct __ms_cus_sensor* handle, u32 idx); int (*pCus_sensor_poweroff)(struct __ms_cus_sensor* handle, u32 idx); int (*pCus_sensor_GetVideoResNum)(struct __ms_cus_sensor* handle, u32 *ulres_num); int (*pCus_sensor_SetVideoRes)(struct __ms_cus_sensor* handle, u32 res_id); int (*pCus_sensor_GetCurVideoRes)(struct __ms_cus_sensor* handle, u32 *cur_idx, cus_camsensor_res **res); int (*pCus_sensor_GetVideoRes)(struct __ms_cus_sensor* handle, u32 res_idx, cus_camsensor_res **res); int (*pCus_sensor_init)(struct __ms_cus_sensor* handle); int (*pCus_sensor_release)(struct __ms_cus_sensor* handle); ...... }ms_cus_sensor;
3.2.1 Power ON/OFF¶
实现 Power on/off,需要针对每个Sensor Data sheet中的上下电时序图,进行porting。
以 SC850SL 为例:
除了上电时序,还需要一些额外的I/O与clock的操作,这些操作的 API 都由Sensor Interface 提供,用到的接口原型如下:
/* sdk\driver\SensorDriver\drv\pub\drv_ms_cus_sensor.h */ typedef struct __ISensorIfAPI { ...... int (*PowerOff)(u32 idx, CUS_CLK_POL pol); int (*Reset)(u32 idx, CUS_CLK_POL pol); int (*MCLK)(u32 idx, u8 bONOFF, CUS_MCLK_FREQ mclk); int (*SetIOPad)(u32 idx, CUS_SENIF_BUS ulSnrType, u32 ulSnrPadCfg); int (*SetCSI_Clk)(u32 idx, CUS_CSI_CLK clk); int (*SetCSI_Lane)(u32 idx, u16 num_lan, u8 bon_off); int (*SetCSI_LongPacketType)(u32 idx, u16 ctl_cfg0_15, u16 ctl_cfg16_31, u16 ctl_cfg32_47); ...... }ISensorIfAPI;
代码示例:
static int pCus_poweron(ms_cus_sensor *handle, u32 idx) { ISensorIfAPI *sensor_if = handle->sensor_if_api; SENSOR_DMSG("[%s] ", __FUNCTION__); //Sensor Disable sensor_if->PowerOff(idx, !handle->pwdn_POLARITY); // 将reset pin 拉到无效电平 sensor_if->Reset(idx, !handle->reset_POLARITY); // 将reset pin 拉到无效电平 SENSOR_USLEEP(1000); // MIPI CSI config sensor_if->SetIOPad(idx, handle->sif_bus, handle->interface_attr.attr_mipi.mipi_lane_num); // 接口I/O配置 sensor_if->SetCSI_Clk(idx, CUS_CSI_CLK_288M); // CSI clock 频率配置 sensor_if->SetCSI_Lane(idx, handle->interface_attr.attr_mipi.mipi_lane_num, 1); // CSI Lane 数量配置 sensor_if->SetCSI_LongPacketType(idx, 0, 0x1C00, 0); // CSI 长包数据类型配置 //Sensor power on sequence sensor_if->MCLK(idx, 1, handle->mclk); // 开启mclk输出 SENSOR_USLEEP(2000); sensor_if->Reset(idx, handle->reset_POLARITY); // 将reset pin 拉到触发电平 SENSOR_USLEEP(1000); sensor_if->PowerOff(idx, handle->pwdn_POLARITY); // 将pwdn pin 拉到触发电平 SENSOR_MSLEEP(5); return SUCCESS; } static int pCus_poweroff(ms_cus_sensor *handle, u32 idx) { ISensorIfAPI *sensor_if = handle->sensor_if_api; SENSOR_DMSG("[%s] power low\n", __FUNCTION__); sensor_if->PowerOff(idx, !handle->pwdn_POLARITY); sensor_if->Reset(idx, !handle->reset_POLARITY); SENSOR_USLEEP(1000); sensor_if->SetCSI_Clk(idx, CUS_CSI_CLK_DISABLE); sensor_if->MCLK(idx, 0, handle->mclk); handle->orient = CUS_ORIT_M0F0; return SUCCESS; }
3.2.2 Resolution Configuration¶
主要实现获取和设置Resolution的功能:
/* 获取支持的resolution的数量 */ static int pCus_GetVideoResNum( ms_cus_sensor *handle, u32 *ulres_num) { *ulres_num = handle->video_res_supported.num_res; return SUCCESS; } /* 获取某一个resolution的信息 */ static int pCus_GetVideoRes(ms_cus_sensor *handle, u32 res_idx, cus_camsensor_res **res) { u32 num_res = handle->video_res_supported.num_res; if (res_idx >= num_res) { return FAIL; } *res = &handle->video_res_supported.res[res_idx]; return SUCCESS; } /* 获取当前正在使用的resolution的信息 */ static int pCus_GetCurVideoRes(ms_cus_sensor *handle, u32 *cur_idx, cus_camsensor_res **res) { u32 num_res = handle->video_res_supported.num_res; *cur_idx = handle->video_res_supported.ulcur_res; if (*cur_idx >= num_res) { return FAIL; } *res = &handle->video_res_supported.res[*cur_idx]; return SUCCESS; } /* 设置要使用的resolution index */ static int pCus_SetVideoRes(ms_cus_sensor *handle, u32 res_idx) { u32 num_res = handle->video_res_supported.num_res; if (res_idx >= num_res) { return FAIL; } handle->video_res_supported.ulcur_res = res_idx; return SUCCESS; }
3.2.3 Sensor Init/Deinit¶
Sensor Init主要用于实现Sensor的初始化,按照既定的目标出流,这里就需要用到我们所找厂商提供的init table,即一组I2C配置,典型的操作是:将 table 转换为 I2C_ARRAY,然后再通过I2C的相关读写接口将这些配置写入sensor。
-
Init table 转换
I2C_ARRAY已经在\sdk\driver\SensorDriver\drv\pub\sensor_i2c_api.h中定义,结构体原型与转换的示例如下:
typedef struct _I2C_ARRAY{ u16 reg; /**< Register address.*/ u16 data; /**< Data.*/ } I2C_ARRAY; /* i2c init table 0x3280,0x00, 0x3281,0x00, 0x3301,0x3c, ...... */ const I2C_ARRAY Sensor_init_table_8M30fps[] ={ {0x3280, 0x00}, {0x3281,0x00}, {0x3301,0x3c}, ...... }
-
I2C 读写接口
如下的宏可以直接用于I2C读写:
#define SensorReg_Read(_reg,_data) (handle->i2c_bus->i2c_rx(handle->i2c_bus, &(handle->i2c_cfg),_reg,_data)) #define SensorReg_Write(_reg,_data) (handle->i2c_bus->i2c_tx(handle->i2c_bus, &(handle->i2c_cfg),_reg,_data)) #define SensorRegArrayW(_reg,_len) (handle->i2c_bus->i2c_array_tx(handle->i2c_bus, &(handle->i2c_cfg),(_reg),(_len))) #define SensorRegArrayR(_reg,_len) (handle->i2c_bus->i2c_array_rx(handle->i2c_bus, &(handle->i2c_cfg),(_reg),(_len)))
-
编写Init funciton
准备好init table的转换和I2C的读写接口就可以实现Sensor Init/deinit 了,示例代码如下:
static int pCus_init_linear_8M30fps(ms_cus_sensor *handle) { int i,cnt; for(i=0;i< ARRAY_SIZE(Sensor_init_table_8M30fps);i++) { if(Sensor_init_table_8M30fps[i].reg==0xffff) { SENSOR_MSLEEP(Sensor_init_table_8M30fps[i].data); } else { cnt = 0; while(SensorReg_Write(Sensor_init_table_8M30fps[i].reg, Sensor_init_table_8M30fps[i].data) != SUCCESS) { cnt++; SENSOR_DMSG("Sensor_init_table -> Retry %d...\n",cnt); if(cnt>=10) { SENSOR_DMSG("[%s:%d]Sensor init fail!!\n", __FUNCTION__, __LINE__); return FAIL; } SENSOR_MSLEEP(10); } } } return SUCCESS; } // Deinit一般在没有明确需求的情况下,可以直接写空 static int cus_camsensor_release_handle(ms_cus_sensor *handle) { return SUCCESS; }
3.2.4 SensorDriver 回调注册¶
当我们完成了上述的参数配置以及功能函数以后,把这些函数全部注册到 ms_cus_sensor 结构体的回调接口上,Sensor就可以准备出流了,注册流程一般放在init handle中。
示例如下:
int cus_camsensor_init_handle(ms_cus_sensor* drv_handle) { { ...... handle->pCus_sensor_poweron = pCus_poweron ; handle->pCus_sensor_poweroff = pCus_poweroff; handle->pCus_sensor_release = cus_camsensor_release_handle; handle->pCus_sensor_init = pCus_init_linear_8M30fps; handle->pCus_sensor_GetVideoResNum = pCus_GetVideoResNum; handle->pCus_sensor_GetVideoRes = pCus_GetVideoRes; handle->pCus_sensor_GetCurVideoRes = pCus_GetCurVideoRes; handle->pCus_sensor_SetVideoRes = pCus_SetVideoRes; ...... }
完成注册后可以直接跑任意demo出流,但是此时可能会因为init table的曝光和gain的配置比较低,画面偏暗,属于正常现象,需要把AE porting完才能实现亮度的自动调节。
3.3 SensorDriver基础调节¶
3.3.1 FPS调节¶
Sensor FPS 计算原理:
FPS(Frame per seconed), 帧率指单位时间内完全读出的图像帧数,单位为fps,一个sensor的resolution为1080P@30FPS,即每秒钟可以吐出30张1920x1080 pixel的图像数据。也可以说Sensor 每隔 1s/30=33.333ms 就需要发出一张,Sensor是没有时间概念的,计时的基本单位参考Pixel clock的周期,相关时间一般由下列几个参数决定:
- PCLK(Pixel clock):控制像素输出的时钟,即pixel采样时钟,一个clk采集一个像素点,单位:MHz。表示每个单位时间内(每秒)采样的pixel数量。
- HTS(Horizontal total size):一行的长度,即每行包含多少个pixel,单位:pixel。
- VTS(Vertical total size):一帧的长度,即每一帧由多少行,单位:line。
了解上述这3个参数后,再根据如下公式:
一个pixel采样周期为:pixel_time = 1s/PCLK 一行所花费的时间为:line_period(H_time) = HTS * pixel_time 一帧所花费的时间为:V_time = VTS * H_time Sensor的输出帧率为:FPS = 1s/V_time
可得到如下计算公式:
从上面公式可以看出,想要增加FPS,可以减小VTS/HTS,或加快Pclk的频率来实现,但是这些参数肯定不可能无限调整,其上下限需要和sensor 厂商确认,只能在合理的范围内调。通常来说都采取只控制 VTS 的方式来降低或者提高FPS,少数情况需要调整HTS,一般不调整PCLK。
Sensor FPS 调节方式:
通常只使用调节VTS来控制帧率,如此,我们需要的公式就变成: FPS = 1s/ V_time = 1s/ VTS * H_time
假设我们拿到的init table 出流的配置为30fps,那可以得到 V_time = 1s/30fps = 33.333ms
VTS需要先查阅DataSheet,找到控制帧长的寄存器,以SC850SL为例,寄存器描述如下:
确定寄存器位置为 0x320E[6:0]/0x320F,此处不要直接使用默认值0x08CA作为VTS来计算,应该确认init table中是否有对该寄存器配置,以init table中的值为准,没有写则以data sheet的default值为准。
这里我们假设 VTS=0x08CA=2250,则 H_time = V_time / VTS = 33.333ms / 2250 = 14815 ns
FPS的计算公式可更新为 FPS = 1s/(VTS*14815ns) 即,VTS = 1s/(FPS*14815ns)
寄存器地址 | reg_addr | {0x320e[6:0], 0x320f} |
---|---|---|
帧率上限 | fps_max | 30 |
帧率下限 | fps_min | 0 |
每行时间 | line_period(H_time) | 14815ns |
初始帧长 | init_vts | 2250 |
根据上述的计算公式,给定想要的FPS,可以算出VTS的值来写入Sensor寄存器,由此实现FPS的控制计算,示例代码如下:
const static I2C_ARRAY vts_reg[] = { {0x320e, 0x08}, {0x320f, 0xCA}, }; u32 Preview_line_period = 14815; u32 vts = 2250; u32 preview_fps = 30; static int pCus_SetFPS(ms_cus_sensor *handle, u32 fps) { u32 max_fps = handle->video_res_supported.res[handle->video_res_supported.ulcur_res].max_fps; //需要在init handle中填充 u32 min_fps = handle->video_res_supported.res[handle->video_res_supported.ulcur_res].min_fps; //在init handle中有写就可以直接使用 if(fps>=min_fps && fps <= max_fps){ vts = 1000000000/(fps*Preview_line_period); }else if((fps >= (min_fps*1000)) && (fps <= (max_fps*1000))){ // 1000x 的参数进行判断 vts = 1000000000/(fps*Preview_line_period/1000); // 使用u32类型在某些情况下有可能计算过程中溢出,可改用u64 }else{ SENSOR_DMSG("[%s] FPS %d out of range.\n",__FUNCTION__,fps); return FAIL; } vts_reg[0].data = (vts >> 8) & 0x00ff; vts_reg[1].data = (vts >> 0) & 0x00ff; SensorReg_Write(vts_reg[0].reg, vts_reg[0].data); SensorReg_Write(vts_reg[1].reg, vts_reg[1].data); return SUCCESS; } static int pCus_GetFPS(ms_cus_sensor *handle) { 32 vts = (vts_reg[0].data << 8) | (vts_reg[1].data << 0); if (preview_fps >= 1000) preview_fps = 1000000000000ULL/(vts*Preview_line_period); else preview_fps = 1000000000/(vts*Preview_line_period); return preview_fps; }
实现后注册到Sensor Interface模块:
int cus_camsensor_init_handle(ms_cus_sensor* drv_handle) { { ...... handle->pCus_sensor_GetFPS = pCus_GetFPS; handle->pCus_sensor_SetFPS = pCus_SetFPS; ...... }
3.3.2 AEStatusNotify机制¶
上面的示例代码和仓库里的driver稍有不同,前面的示例代码中,往往是计算出需要下给寄存器的值,就直接通过I2C接口下给Sensor,大部分情况下这种做法不会有太大问题,但是在部分Sensor中或者一些特殊的场景下,Sensor会对timing有要求,例如:
- 必须在Frame end到下一个Frame start中的blanking才能生效
- ISP希望所有对Sensor出流的改动能在同一帧生效
所以就需要一个机制来满足上述的timing需求,在实现这个功能之前,先改造一下之前的FSP的func,用一个drity变量来替代直接write的动作:
bool reg_dirty = false; static int pCus_SetFPS(ms_cus_sensor *handle, u32 fps) { ...... vts_reg[0].data = (vts >> 8) & 0x00ff; vts_reg[1].data = (vts >> 0) & 0x00ff; //SensorReg_Write(vts_reg[0].reg, vts_reg[0].data); //SensorReg_Write(vts_reg[1].reg, vts_reg[1].data); reg_dirty = true; ...... return SUCCESS; }
改完后再实现以下函数:
static int pCus_AEStatusNotify(ms_cus_sensor *handle, CUS_CAMSENSOR_AE_STATUS_NOTIFY status) { switch(status) { case CUS_FRAME_INACTIVE: break; case CUS_FRAME_ACTIVE: if(reg_dirty) { SensorRegArrayW((I2C_ARRAY*)vts_reg, ARRAY_SIZE(vts_reg)); reg_dirty = false; } break; default : break; } return SUCCESS; }
该接口的参数CUS_CAMSENSOR_AE_STATUS_NOTIFY只用到了其中一种类型,两种方式的区别如下,可根据需求自行选择:
- CUS_FRAME_ACTIVE:User主动触发,调用之后会立刻下寄存器
- CUS_FRAME_INACTIVE:由Frame end中断触发,保证下寄存器的动作在当前Frame的VBlanking期间。
3.4 AE调节¶
AE(Auto Exposure)自动曝光,AE 模块实现的功能是根据自动测光系统获得当前图像的曝光量,再自动配置镜头光圈、sensor 快门及增益来获得最佳的图像质量。
AE自动曝光 其实并不单指对于Sensor的曝光时间这个参数的调节,可以简单理解为是对整个画面的亮度的调节。
3.4.1 曝光/快门 调节¶
曝光(exposure)调节 也叫快门(shutter)调节,快门的开关决定了曝光时间的长短,首先需要确认Sensor的调节能力,查阅Sensor的Data sheet,找的曝光调节的章节,以SC850SL为例:
重要的参数如下:
寄存器地址 | reg_addr | {0x3e00[3:0], 0x3e01[0:7], 0x3e02[7:4] } |
---|---|---|
曝光上限 | expo_max | VTS - 4 ({0x320e, 0x320f}) |
曝光下限 | expo_min | 0 |
曝光步长 | expo_step | 1 |
曝光生效延迟时间 | expo_delay | 2 |
在init handle中填入下面的参数:
...... handle->ae_gain_delay = 2; //gain_delay handle->ae_shutter_delay = 2; //expo_delay handle->ae_gain_ctrl_num = 1; // linear mode always set 1 handle->ae_shutter_ctrl_num = 1; // linear mode always set 1 ......
曝光时间的计算实际上和FPS类似,一般来说单位都是行,也是由HTS决定,和FPS计算中的 H_time/line_period 是一致的,曝光时间的计算公式如下:
有了如上的参数与公式,就可以着手实现如下的示例代码:
u32 Preview_MIN_FPS = 3; u32 Preview_line_period = 14815; //初始值参考init table,保持一致 I2C_ARRAY expo_reg[] = { // max expo line vts-4! {0x3e00, 0x00}, // [3:0] expo [20:17] {0x3e01, 0x30}, // [7:0 ]expo[16:8] {0x3e02, 0x10}, // [7:4] expo[3:0] }; static int sc850sl_GetShutterInfo(struct __ms_cus_sensor* handle,CUS_SHUTTER_INFO *info) { info->max = 1000000000/Preview_MIN_FPS - 4*Preview_line_period; info->min = Preview_line_period; info->step = Preview_line_period; return SUCCESS; } static int pCus_SetAEUSecs(ms_cus_sensor *handle, u32 us) { u32 lines = 0; lines = (1000*us)/Preview_line_period; // Preview_line_period in ns if(lines<=1) lines=1; if (lines > vts-4) { lines = vts - 4; } lines = lines<<4; expo_reg[0].data = (lines>>16) & 0x0f; expo_reg[1].data = (lines>>8) & 0xff; expo_reg[2].data = (lines>>0) & 0xf0; reg_dirty = true; return SUCCESS; } static int pCus_GetAEUSecs(ms_cus_sensor *handle, u32 *us) { int rc=0; u32 lines = 0; lines |= (u32)(expo_reg[0].data&0x0f)<<16; lines |= (u32)(expo_reg[1].data&0xff)<<8; lines |= (u32)(expo_reg[2].data&0xf0)<<0; lines >>= 4; *us = (lines*Preview_line_period)/1000; //return us return rc; } //回调注册 ...... handle->pCus_sensor_GetAEUSecs = pCus_GetAEUSecs; handle->pCus_sensor_SetAEUSecs = pCus_SetAEUSecs; handle->pCus_sensor_GetShutterInfo = sc850sl_GetShutterInfo; ...... //AEnotify生效 static int pCus_AEStatusNotify(ms_cus_sensor *handle, CUS_CAMSENSOR_AE_STATUS_NOTIFY status) { ...... if(params->reg_dirty) { SensorRegArrayW(expo_reg, ARRAY_SIZE(expo_reg)); SensorRegArrayW(vts_reg, ARRAY_SIZE(vts_reg)); params->reg_dirty = false; } ...... }
3.4.2 增益调节¶
-
模拟增益 (Analog gain)
在ADC转换之前对模拟信号进行放大,模拟增益在放大信号的同时,带来的噪声只会一次引入
-
数字增益(Digital gain)
对ADC转换后的数字信号进行放大,数字增益在放大信号的同时,带来的噪声会多次级联引入,相较于模拟增益,数字信号还包含了量化噪声,会读出电路噪声
Tips: 各个厂商的sensor增益调节的方法存在差异,同一个厂商不同型号的计算方法也存在区别,有的厂商甚至不写在Data sheet中,比如GC(galaxy core),OV(omnivison),需要咨询厂商的FAE提供计算公式。
参考SC850SL的Data sheet,获取gain的规格参数,如下:
增益的总的倍数计算:total_gain = analog_gain * digital_gain = 49.6*8 = 396.8
倍。模拟增益和数字增益可以配合使用,一般来说优先调节模拟增益,如果拉到最大还是无法满足,再调节数字增益,当然也可以单独只使用模拟增益或数字增益其中的一种。
然后再找到调节方法,如下:
SC850SL的调节的方法为:
-
analog_gain使用两组寄存器控制,0x3e08:粗调(Coarse_Again),0x3e09:细调(Fine_Again)
-
digtal_gain只有一个寄存器,0x3e06,但其精度不高
通过SC850SL的 analog_gain table,这里我们先假设,只需要用到3.125倍的Again(analog_gain)来进行练习:
如果找到 fine_again_reg 和 gain_value的计算公式,就能根据需要的gain值,算出需要下到寄存器的值。
这里我们就得到了对SC850SL的gain计算而言在[1, 3.125) 在范围内的一个比较通用的公式:
由此实现如下的示例代码:
I2C_ARRAY gain_reg[] = { {0x3e06, 0x00}, {0x3e08, 0x03}, //coarse_Again_reg {0x3e09, 0x40}, //fine_Again_reg }; #define SENSOR_MAXGAIN 3200 \\3.125*1024 // Code中使用的gain都是实际倍数的1024倍 static int pCus_GetAEMinMaxGain(ms_cus_sensor *handle, u32 *min, u32 *max){ *min = 1024; *max = SENSOR_MAXGAIN; return SUCCESS; } static int pCus_SetAEGain(ms_cus_sensor *handle, u32 gain){ u32 Coarse_gain,Coarse_gain_reg,Fine_gain_reg; if (gain <= 1024) { gain = 1024; } else if (gain > SENSOR_MAXGAIN) { gain = SENSOR_MAXGAIN; } if (gain < 2048) // start again 2 * 1024 { Coarse_gain = 1; Coarse_gain_reg = 0x03; } else if (gain < 3200) // 3.125 * 1024 { Coarse_gain = 2; Coarse_gain_reg = 0x07; } Fine_gain_reg = gain*64/(Coarse_gain*1024); gain_reg[2].data = Fine_gain_reg & 0xFF; gain_reg[1].data = Coarse_gain_reg & 0xFF; gain_reg[0].data = 0x00; //不使用Dgain params->reg_dirty = true; return SUCCESS; } static int pCus_GetAEGain(ms_cus_sensor *handle, u32* gain) { u32 Coarse_gain; u32 Fine_gain_reg = gain_reg[2].data; if(gain_reg[1].data == 0x3) Coarse_gain = 1; else if(gain_reg[1].data == 0x7) Coarse_gain = 2; *gain = (Fine_gain_reg*Coarse_gain*1024)/64; return SUCCESS; } //回调注册 ...... handle->pCus_sensor_GetAEGain = pCus_GetAEGain; handle->pCus_sensor_SetAEGain = pCus_SetAEGain; handle->pCus_sensor_GetAEMinMaxGain = pCus_GetAEMinMaxGain; ...... static int pCus_AEStatusNotify(ms_cus_sensor *handle, CUS_CAMSENSOR_AE_STATUS_NOTIFY status) { ...... if(params->reg_dirty) { SensorRegArrayW(expo_reg, ARRAY_SIZE(expo_reg)); SensorRegArrayW(gain_reg, ARRAY_SIZE(gain_reg)); SensorRegArrayW(vts_reg, ARRAY_SIZE(vts_reg)); params->reg_dirty = false; } ...... }
4. 总结¶
当我们完成章节2的环境准备和章节3的配置调节后,就可以参照章节1的编译方式,将SensorDriver编译出来,并用相关demo确认SensorDriver是否Porting成功了,本文主要描述了三大过程:
- Sensor成功上电,并且I2C通信能够正常。(硬件配置,上电时序)
- Sensor能够出图。 (初始化成功)
- Sensor能够出效果正常的图。 (AE生效)
当我们Porting的过程中遇到问题也可以根据以上3个步骤,去排查我们哪一步有问题。