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。

  1. 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},
    ......
    }
    
  2. 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)))
    
  3. 编写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成功了,本文主要描述了三大过程:

  1. Sensor成功上电,并且I2C通信能够正常。(硬件配置,上电时序)
  2. Sensor能够出图。 (初始化成功)
  3. Sensor能够出效果正常的图。 (AE生效)

当我们Porting的过程中遇到问题也可以根据以上3个步骤,去排查我们哪一步有问题。