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个步骤,去排查我们哪一步有问题。