Sensor_Porting

REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 04/17/2025

    1. 环境准备

    开始Porting之前,请先确保Sensor的接线没有问题,MIPI Sensor接线可以参考这篇文档 SENSOR 使用参考,在不同CHIP上基本的原理也相同

    首先需要确认需求,包含Sensor的分辨率,帧率,像素位宽,MIPI Lane数量,mclk 频率,将需求给到厂商,并让其提供数据手册与初始化表。

    1.1 Sensor Initialize table

    init table 实际上就是一组I2C配置,通过I2C写到sensor后,按照既定的目标出流。

    I2C A16D8 init table

        ......
        0x3280,0x00,
        0x3281,0x00,
        0x3301,0x3c,
        0x3304,0x30,
        0x3306,0xe8,
        0x3308,0x10,
        0x3309,0x70,
        0x330a,0x01,
        0x330b,0xe0,
        0x330d,0x10,
        0x3314,0x92,
        0x331e,0x29,
        ......
    

    2. Sensor Driver Porting

    2.1 Sensor Driver 基本参数

    Sensor driver实际上是被Sensor Interface模块管理,register即向Sensor Interface提供sensor信息,包括但不限于sensor的基础图像参数,数据接口参数,控制接口参数,如何上下电,出流,修改FPS等,这些都需要Sensor driver填充结构体或实现部分相应的回调函数,注册到Sensor Interface模块。

    C:\442db8aa18b9cbcb709d4e0114b4cabf

    2.1.1 Sensor module register

    在linux中每个Sensordriver都是一个单独的module,SDK中实现了两个宏,可以同时将driver编译成linux module,并将driver注册到sensor interface

    MACRO SENSOR_DRV_ENTRY_IMPL_BEGIN_EX( Name )
    描述 该宏中定义了Sensor注册到Sensor Interface所需要的module参数,一般放在driver源文件头部
    参数
    in Name string 任意字符串,sensor命名
    返回
    MACRO SENSOR_DRV_ENTRY_IMPL_END_EX( Name, LinearEntry, HdrSefEntry, HdrLefEntry, PrivateDataType )
    描述 该宏中定义了Sensor注册到Sensor Interface所需要的module参数,一般放在driver源文件末尾
    参数
    in Name string 任意字符串,sensor命名
    in LinearEntry func Linear mode init 函数
    in HdrSefEntry func HDR mode SEF init 函数
    in HdrLefEntry func HDR mode LEF init 函数
    in PrivateDataType func Sensor私有数据
    返回
    • 两个宏中使用的Name保持一致

    2.1.2 Sensor 图像基本参数

    基本的出流需要实现 LinearEntry的handle进行注册,基本上都是对 ss_cus_sensor 这个结构体进行构造,该数据结构原型的关键部分如下:

        /* Location : sdk\driver\SensorDriver\drv\pub\drv_ss_cus_sensor.h */
    
        typedef struct __ss_cus_sensor{
    
        .......
    
        char strSensorStreamName[32];        /**< Please fill the sensor modle id string here then libcamera user can read model_id by using cameraGetSensorModelID() */
        .*/
    
        // i2c
        app_i2c_cfg i2c_cfg;                 /**< Sensor i2c setting */
    
        // sensor data enum list*/
        CUS_SEN_BAYER           bayer_id;    /** < Sensor bayer raw pixel order */
        CUS_SEN_RGBIR           RGBIR_id;    /** < Sensor bayer raw pixel order */
        CUS_SENIF_BUS           sif_bus;     /** < Select sensor interface */
        CUS_DATAPRECISION       data_prec;   /** < Raw data bits */
    
        cus_camsensor_res_list  video_res_supported; /** < Resolution list */
        InterfaceAttr_u         interface_attr;
    
        //sensor calibration
        int           mclk;        /** < Sensor master clock frequency */
    
        ......
    
        }ss_cus_sensor;
    

    需要配置的sensor图像基本参数

    Param Type value desc
    model_id char[31] string Sensor名字,任意字符串
    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
    像素位宽
    port:8b/10b/12b/14b/16b

    以某一型号sensor为例

    C:\6b1dba225c98304ab3137080a7253f94

    这些参数都是灵活的,需要根据实际需求并与Sensor vendor 确定拿到的init table配置为准

    2.1.3 上下电参数

    Sensor上下电需要如下参数来控制

    Param Type value desc
    mclk enum CUS_CMU_CLK_27MHZ
    CUS_CMU_CLK_21P6MHZ
    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需求选择,不同chip支持的mclk存在差异,可以参考SENSOR PADMUX使用参考 3. 功能描述
    • mclk为soc提供给sensor的工作时钟,对于sensor而言,在sensor data sheet中可能被称为 INCK/EXTCLK 等,容易混淆,需要仔细甄别

    • pwdn与reset实际上是用于控制两个GPIO的高低电平,但是并不是所有的sensor上下电都需要两根pin来控制,部分Sensor只需要一根pin就可以实现上电操作

    以某一型号sensor为例

    • 确认XSHUTDN接到哪一个SOC的哪一个PAD,是pwdn还是rst

    • 将相应的引脚设置为触发电平

      C:\3b20d085c5dbbdf4f2ee1fa968b61cf1

    2.1.4 I2C接口参数

    大部分的Sensor都是用I2C或兼容I2C的接口进行控制,I2C接口的参数由app_i2c_cfg这个结构体定义

    /*  sdk\driver\SensorDriver\drv\pub\drv_ss_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;
    
    Param 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 数据格式,按Sensor要求选择
    I2C_FMT_AxDy : x bit Address, y bit Data
    speed uint 60000~320000 I2C 通信速率,单位(Hz)
    address uint 0x00~0xFF Sensor I2Caddress,8bit寻址地址,读写位为0或1均可

    以下图提供的信息为例

    C:\4f0cc93eddd213484076d8626d5e162c

    • fmt:  I2C_FMT_A16D8

    • speed: 咨询sensor vendor建议值

    • address:0x60/0x61

    2.1.5 MIPI 数据接口参数

    数据接口的信息主要是InterfaceAttr_u这个联合体定义,使用联合体是为了兼容不同的接口类型,这里我们只看MIPI接口的

    /*  sdk\driver\SensorDriver\drv\pub\drv_ss_cus_sensor.h */
    typedef union {
    ......
        //MIPI sensor
        struct {
            u8 mipi_lane_num;
            CUS_SEN_INPUT_FORMAT mipi_data_format;      /**< 0: YUV 422 format. 1: RGB pattern. */
            CUS_SENSOR_YUV_ORDER mipi_yuv_order;        /**< YUYV or UYVY*/
            CUS_HDR_MODE mipi_hdr_mode;
            CUS_HDR_FUSION_TYPE mipi_hdr_fusion_type;
            u8 mipi_hdr_virtual_channel_num;
            u32 mipi_user_def;
        } attr_mipi;
    ......
    }  InterfaceAttr_u;
    
    Param Type value desc
    mipi_lane_num uint 1 / 2 / 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_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
    CUS_HDR_MODE_VC
    ....... 
    HDR 种类
    Linear mode 选 NONE
    mipi_hdr_virtual_channel_num uint 0 / 1 / 2 / 3 CSI channel number ,Linear mode 写 0,HDR才会用到其他channel

    示例:

    handle->interface_attr.attr_mipi.mipi_lane_num = 4;
    handle->interface_attr.attr_mipi.mipi_data_format = CUS_SEN_INPUT_FORMAT_RGB;
    handle->interface_attr.attr_mipi.mipi_hdr_mode = CUS_HDR_MODE_VC; //hdr type setting
    handle->interface_attr.attr_mipi.mipi_hdr_virtual_channel_num = 1; //virtual chnannel can be 0~3
    
    • 注意:对于其他接口类型的sensor,如DVP/bt656等接口,不需要去填充这个结构体

    2.1.6 分辨率信息

    Sensor所支持的所有的分辨率信息列表由cus_camsensor_res_list和cus_camsensor_res定义,结构体原型如下:

    /*  sdk\driver\SensorDriver\drv\pub\drv_ss_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{
        u16 u16width;          /**< Image crop width */
        u16 u16height;         /**< Image crop height */
        u16 u16max_fps;        /**< Max fps in this resolution */
        u16 u16min_fps;        /**< Min fps in this resolution*/
        u16 u16crop_start_x;   /**< Image crop width start position*/
        u16 u16crop_start_y;   /**< Image crop height start position*/
        u16 u16OutputWidth;   /**< Sensor actual output width */
        u16 u16OutputHeight;  /**< Sensor actual output height */
        u16 u16MinFrameLengthLine;    // in Line
        u16 u16RowTime;
        char strResDesc[32];
    } __attribute__((packed, aligned(4))) cus_camsensor_res;
    

    2.1.7 AE信息

    Sensor Driver内用于存储和设置AE信息的结构体原型如下:

    typedef struct
    {
        u8 u8AEGainDelay;                /**< How many frame delay from writing AE gain to take effect*/
        u8 u8AEShutterDelay;             /**< How many frame delay from writing AE shutter to take effect*/
        u8 u8AEGainCtrlNum;              /**HDR gain share or not,1:share 2:separate*/
        u8 u8AEShutterCtrlNum;           /**HDR shutter share or not,1:share 2:separate*/
        u32 u32PixelSize;                /**in nano meter*/
        u32 u32AEShutter_length;         /**< struct size */
        u32 u32AEShutter_max;            /**< maximun shutter in us*/
        u32 u32AEShutter_min;            /**< minimum shutter in us*/
        u32 u32AEShutter_step;           /**< shutter in step us*/
        u32 u32AEGain_max;
        u32 u32AEGain_min;
    } __attribute__((packed, aligned(4))) CUS_SENSOR_AE_INFO_t;
    

    2.2 Sensor Stream on

    从MI SENSOR层的API来看,典型的 Sensor 出流调用流程如下:

    ->MI_SNR_SetPlaneMode //设置 HDR mode
    ->MI_SNR_SetRes        //选择resolution
    ->MI_SNR_Enable        //使能sensor出流
    

    该过程中会调用Sensor Driver 注册到 Sensor Interface的接口,这些接口需要作为回调实现,回调接口也在ss_cus_sensor的结构体中被定义,出流所需的关键接口原型如下

    /*  sdk\driver\SensorDriver\drv\pub\drv_ss_cus_sensor.h */
    typedef struct __ss_cus_sensor{
    ......
    int (*pCus_sensor_poweron)(struct __ss_cus_sensor* handle, u32 idx);
    int (*pCus_sensor_poweroff)(struct __ss_cus_sensor* handle, u32 idx);
    int (*pCus_sensor_GetVideoResNum)(struct __ss_cus_sensor* handle, u32 *ulres_num);
    int (*pCus_sensor_SetVideoRes)(struct __ss_cus_sensor* handle, u32 res_id);
    int (*pCus_sensor_GetCurVideoRes)(struct __ss_cus_sensor* handle, u32 *cur_idx, cus_camsensor_res **res);
    int (*pCus_sensor_GetVideoRes)(struct __ss_cus_sensor* handle, u32 res_idx, cus_camsensor_res **res);
    int (*pCus_sensor_init)(struct __ss_cus_sensor* handle);
    int (*pCus_sensor_release)(struct __ss_cus_sensor* handle);
    ......
    }ss_cus_sensor;
    

    2.2.1 Power ON/OFF

    实现 Power on/off,需要针对每个Sensor Datasheet中的上下电时序图,进行porting

    以某一型号sensor为例

    C:\da94bfe96ab6bb5efda9699549abd44f

    C:\8fc626ac465df94300269faea5d93b71

    除了上电时序,还需要一些额外的I/O与clock的操作,这些操作的 API都由Sensor Interface 提供的,用到的接口原型如下:

    /*  sdk\driver\SensorDriver\drv\pub\drv_ss_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);            /**MIPI sensor used*/
        int (*SetCSI_Lane)(u32 idx, u16 num_lan, u8 bon_off);   /**MIPI sensor used*/
        int (*SetCSI_LongPacketType)(u32 idx, u16 ctl_cfg0_15, u16 ctl_cfg16_31, u16 ctl_cfg32_47); /**MIPI sensor used*/
    ......
    }ISensorIfAPI;
    
    • PowerOff: 控制power down引脚的电压

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      pol enum CUS_CLK_POL_POS
      CUS_CLK_POL_NEG
      CUS_CLK_POL_POS:高有效
      CUS_CLK_POL_NEG:低有效

      示例代码:

      sensor_if->PowerOff(idx,CUS_CLK_POL_POS); // 将pdn pin拉高
      
    • Reset: 控制reset引脚的电压

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      pol enum CUS_CLK_POL_POS
      CUS_CLK_POL_NEG
      CUS_CLK_POL_POS:高有效
      CUS_CLK_POL_NEG:低有效

      示例代码:

      sensor_if->Reset(idx, CUS_CLK_POL_NEG); // 将rst pin拉高
      
    • MCLK: 配置sensor mclk

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      bONOFF unsigned char unsigned char 大于0表示开启
      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
      CUS_CMU_CLK_37P125MHZ
      CUS_CMU_CLK_LPLL_DIV1
      CUS_CMU_CLK_LPLL_DIV2
      CUS_CMU_CLK_LPLL_DIV4
      CUS_CMU_CLK_LPLL_DIV8
      选择mclk频率

      示例代码:

      sensor_if->MCLK(idx, 1, CUS_CMU_CLK_27MHZ);   // 设置MCLK为27M,并使能
      
    • SetIOPad: 配置sensor padmux mode

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      ulSnrType 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
      CUS_SENIF_BUS_MAX
      sensor接口类型
      ulSnrPadCfg unsigned int unsigned int mipi lane number

      示例代码:

      sensor_if->SetIOPad(idx, CUS_SENIF_BUS_MIPI, 4); //配置sensor为MIPI mode,4对data lane
      
    • SetCSI_Clk: 设置MIPI RX MAC CLK

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      clk enum CUS_CSI_CLK_DISABLE
      CUS_CSI_CLK_432M
      CUS_CSI_CLK_384M
      CUS_CSI_CLK_320M
      CUS_CSI_CLK_288M
      CUS_CSI_CLK_240M
      CUS_CSI_CLK_216M
      CUS_CSI_CLK_172M
      CUS_CSI_CLK_144M
      MIPI RX MAC时钟频率,选择的CSI CLK应满足这样的关系:MAC CLK > MIPI Data Rate(单位:bps)/8x1.5

      示例代码:

      sensor_if->SetCSI_Clk(idx, CUS_CSI_CLK_288M);// 设置MIPI RX MAC CLk为288M
      
    • SetCSI_Lane: 设置MIPI RX Data Lane 数量

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      num_lan unsigned short unsigned short data lane 数量
      bon_off unsigned char unsigned char 是否使能

      示例代码:

      sensor_if->SetCSI_Lane(idx, 4, 1); // 4对Data Lane
      
    • SetCSI_LongPacketType: 设置MIPI 长包类型的enable

      Param Type value desc
      idx unsigned int unsigned int sensor pad id
      ctl_cfg0_15 unsigned short unsigned short long packet type[0:15]
      ctl_cfg16_31 unsigned short unsigned short long packet type[16:31]
      ctl_cfg32_47 unsigned short unsigned short long packet type[32:47]

      支持的long packet data type如下图,支持同时选择多个bit。

      示例代码:

      sensor_if->SetCSI_LongPacketType(idx, 0, 0x1C00, 0);// [26:28] 接收raw8/raw10/raw12
      

    典型Msensor上电flow

    • MIPI sensor

      示例代码

      static int pCus_poweron(ss_cus_sensor *handle, u32 idx)
      {
          ISensorIfAPI *sensor_if = handle->sensor_if_api;
          SENSOR_DMSG("[%s] ", __FUNCTION__);
      
          //Sensor Disable
          sensor_if->PowerOff(idx, CUS_CLK_POL_NEG); // 将pdn pin 拉到无效电平
          sensor_if->Reset(idx, CUS_CLK_POL_NEG); // 将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 Data 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, CUS_CLK_POL_POS); // 将reset pin 拉到触发电平
          SENSOR_USLEEP(1000);
          sensor_if->PowerOff(idx, CUS_CLK_POL_POS); // 将pwdn pin  拉到触发电平
          SENSOR_MSLEEP(5);
          return SUCCESS;
      }
      
    • DVP/bt656/bt1120 sensor

      非MIPI sensor和MIPI sensor相比上电时不用做CSI相关配置,示例代码如下:

      static int pCus_poweron(ss_cus_sensor *handle, u32 idx)
      {
          ISensorIfAPI *sensor_if = handle->sensor_if_api;
          SENSOR_DMSG("[%s] ", __FUNCTION__);
      
          //Sensor Disable
          sensor_if->PowerOff(idx, CUS_CLK_POL_NEG); // 将pdn pin 拉到无效电平
          sensor_if->Reset(idx, CUS_CLK_POL_NEG); // 将reset pin 拉到无效电平
          SENSOR_USLEEP(1000);
      
          //Sensor power on sequence
          sensor_if->MCLK(idx, 1, handle->mclk);  // 开启mclk输出
          SENSOR_USLEEP(2000);
          sensor_if->Reset(idx, CUS_CLK_POL_POS); // 将reset pin 拉到触发电平
          SENSOR_USLEEP(1000);
          sensor_if->PowerOff(idx, CUS_CLK_POL_POS);  // 将pwdn pin 拉到触发电平
          SENSOR_MSLEEP(5);
          return SUCCESS;
      }
      

    典型下电flow

    • MIPI sensor

      下电示例代码如下:

      static int pCus_poweroff(ss_cus_sensor *handle, u32 idx)
      {
          ISensorIfAPI *sensor_if = handle->sensor_if_api;
          SENSOR_DMSG("[%s] power low\n", __FUNCTION__);
          sensor_if->PowerOff(idx, CUS_CLK_POL_NEG);
          sensor_if->Reset(idx, CUS_CLK_POL_NEG);
          SENSOR_USLEEP(1000);
          sensor_if->SetCSI_Clk(idx, CUS_CSI_CLK_DISABLE);
          sensor_if->MCLK(idx, 0, handle->mclk);
          return SUCCESS;
      }
      
    • DVP/bt656/bt1120 sensor

      非MIPI sensor和MIPI sensor相比下电时不用disable CSI clk,示例代码如下:

      static int pCus_poweroff(ss_cus_sensor *handle, u32 idx)
      {
          ISensorIfAPI *sensor_if = handle->sensor_if_api;
          SENSOR_DMSG("[%s] power low\n", __FUNCTION__);
          sensor_if->PowerOff(idx, CUS_CLK_POL_NEG);
          sensor_if->Reset(idx, CUS_CLK_POL_NEG);
          SENSOR_USLEEP(1000);
          sensor_if->MCLK(idx, 0, handle->mclk);
          return SUCCESS;
      }
      

    2.2.2 ResolutionConfigration

    主要是实现获取和设置Resolution的功能,逻辑都比较简单

    /* 获取支持的Resolution的数量 */
    static int pCus_GetVideoResNum( ss_cus_sensor *handle, u32 *ulres_num)
    {
        *ulres_num = handle->video_res_supported.num_res;
        return SUCCESS;
    }
    /* 获取某一个Resolution的信息 */
    static int pCus_GetVideoRes(ss_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(ss_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(ss_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;
    }
    

    2.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(ss_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(ss_cus_sensor *handle)
    {
        return SUCCESS;
    }
    

    2.2.4 SensorDriver 回调注册

    当你完成了上述的码code,恭喜你,就差最后一步,把这些函数在一股脑注册到ss_cus_sensor结构体中的回调接口上,Sensor就可以准备出流了,注册流程一般放在init_handle中,示例如下

    int cus_camsensor_init_handle(ss_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出流,但是此时可能会因为inittable的曝光和gain的配置比较低,画面偏暗,属于正常现象,需要把AE porting完才能实现亮度的自动调节

    2.3 Sensor基础调节

    2.3.1 Sensor FPS 计算原理

    FPS(Frame per seconed), 帧率指单位时间内完全读出的图像帧数,单位为fps,一个sensor的resolution为1080P@30FPS即每秒钟可以吐出30张1920x1080pixel的图像数据

    也可以说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

    2.3.2 FPS 调节

    通常只使用调节VTS来控制帧率,如此,我们需要的公式就变成: FPS = 1s/V_time = 1s/ VTS * H_time

    假设我们拿到的init table 出流的配置为30fps,那可以得到 V_time = 1s/30fps = 33.333ms

    VTS需要先查阅DataSheet,找到控制帧长的寄存器,以某一型号sensor为例,寄存器描述如下:

    确定寄存器位置为0x320E[6:0]/0x320F,此处不要直接使用默认值0x08CA作为VTS来计算,应该是确认init table中是否有对该寄存器配置,以init table中的值为准,没有写则以datasheet的default值为准

    这里我们假设 VTS=0x08CA=2250,则 H_time = V_time / VTS = 33.333ms / 2250 = 14815 ns

    FPS的计算公式可更新为 FPS = 1s/(VTS*14815ns)  ,即VTS = 1s/(FPS*14815ns)

    根据上述的计算公式,给定想要的FPS,可以算出VTS的值来写入Sensor寄存器整理所需的所有参数与公式

    寄存器地址 reg_addr  {0x320e[6:0], 0x320f}
    帧率上限 fpx_max 30
    帧率下限 fps_min 0
    每行时间 line_period(H_time) 14815ns
    初始帧长 init_vts 2250

    VTS 计算公式为

    就可以实现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(ss_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(ss_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(ss_cus_sensor* drv_handle) {
    {
    ......
        handle->pCus_sensor_GetFPS          = pCus_GetFPS;
        handle->pCus_sensor_SetFPS          = pCus_SetFPS;
    ......
    }
    

    2.3.3 Orien 调节

    Orien调节指的是对sensor输出进行水平(mirror)或垂直(flip)翻转

    某一型号sensor Datasheet相关寄存器描述如下

    C:\9b3b606717a4210b175b0fa928320ab3

    const static I2C_ARRAY mirror_reg[] =
    {
        {0x3221, 0x00}, // mirror[2:1], flip[6:5]
    };
    
    static int pCus_SetOrien(ss_cus_sensor *handle, CUS_CAMSENSOR_ORIT orit)
    {
        switch(orit) {
            case CUS_ORIT_M0F0:
                mirror_reg[0].data = 0x00;
            break;
            case CUS_ORIT_M1F0:
                mirror_reg[0].data = 0x06;
            break;
            case CUS_ORIT_M0F1:
                mirror_reg[0].data = 0x60;
            break;
            case CUS_ORIT_M1F1:
                mirror_reg[0].data = 0x66;
                break;
            default :
                break;
        }
        SensorReg_Write(mirror_reg[0].reg, mirror_reg[0].data);
        return SUCCESS;
    }
    
    static int pCus_GetOrien(ss_cus_sensor *handle, CUS_CAMSENSOR_ORIT *orit) {
        char sen_data;
        sen_data = mirror_reg[0].data;
        SENSOR_DMSG("[%s] mirror:%x\r\n", __FUNCTION__, sen_data & 0x66);
        switch(sen_data & 0x66)
        {
            case 0x00:
                *orit = CUS_ORIT_M0F0;
                break;
            case 0x06:
                *orit = CUS_ORIT_M1F0;
                break;
            case 0x60:
                *orit = CUS_ORIT_M0F1;
                break;
            case 0x66:
                *orit = CUS_ORIT_M1F1;
                break;
        }
        return SUCCESS;
    }
    
    //记得在init handle中注册
    ......
        handle->pCus_sensor_GetOrien          = pCus_GetOrien;
        handle->pCus_sensor_SetOrien          = pCus_SetOrien;
    ......
    

    2.3.4 AEStatusNotify机制

    前面的示例代码中,往往是计算出需要下给寄存器的值,就直接通过I2C接口下给Sensor,大部分情况下这种做法不会有太大问题,但是在部分Sensor中或者一些特殊的场景下,Sensor会对timing有要求,例如

    • 必须在Frame end到下一个Frame start中的blanking中下才能生效

    • ISP希望所有对Sensor出流的改动能在同一帧生效

    所以就需要一个机制可以来满足上述的timing需求,在实现这个功能之前,先改造一下之前的FSP和orien的func,用一个dirty变量来替代直接write的动作

    bool reg_dirty = false;
    bool orient_dirty = false;
    
    static int pCus_SetFPS(ss_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_SetOrien(ss_cus_sensor *handle, CUS_CAMSENSOR_ORIT orit)
    {
    ......
        //SensorReg_Write(mirror_reg[0].reg, mirror_reg[0].data);
        orient_dirty = true;
        return SUCCESS;
    }
    

    改完之后再实现如下的示例代码

    static int pCus_AEStatusNotify(ss_cus_sensor *handle, u32 idx, CUS_CAMSENSOR_AE_STATUS_NOTIFY status)
    {
        switch(status)
        {
            case CUS_FRAME_INACTIVE:
                break;
            case CUS_FRAME_ACTIVE:
                if(orient_dirty)
                {
                    SensorRegArrayW((I2C_ARRAY*)params->tMirror_reg, ARRAY_SIZE(mirror_reg));
                    orient_dirty = false;
                }
                if(reg_dirty)
                {
                    SensorRegArrayW((I2C_ARRAY*)vts_reg, ARRAY_SIZE(vts_reg));
                    reg_dirty = false;
                }
                break;
            default :
                break;
        }
        return SUCCESS;
    }
    

    如果使用者想让orien与fps的改动在同一个frame生效,可以按以下的流程进行调用

    即可保证这些参数能在同一个frame生效,当Sensor有更多参数时也同理,只要最后统一到AEnotify中写sensor寄存器就可以做到

    该接口的参数CUS_CAMSENSOR_AE_STATUS_NOTIFY 这里只用到了其中一种类型,两种方式的区别如下,可根据需求自行选择

    typedef enum {
        CUS_FRAME_INACTIVE = 0, /**由Frame start中断触发*/
        CUS_FRAME_ACTIVE = 1,   /**由Frame end中断触发,保证下寄存器的动作在当前Frame的VBlanking期间*/
    } CUS_CAMSENSOR_AE_STATUS_NOTIFY;
    

    2.4 AE 调节

    2.4.1 自动曝光基本原理

    AE(Auto Exposure)自动曝光,AE模块实现的功能是根据自动测光系统获得当前图像的曝光量,再自动配置镜头光圈、sensor快门及增益来获得最佳的图像质量

    简单的示意图如下

    Sensor送出的每一张图经过一定规则的数理统计得到AE算法需要的入参,AE算法根据当前的统计值,以及Sensor所提供的调节能力(每个Sensor的调整幅度和范围都不一样),得出需要更新给Sensor的参数,最终由Sensor Driver提供的接口来写入给Sensor

    tips:AE自动曝光其实并不单指对于Sensor的曝光时间这个参数的调节,可以简单理解为是对整个画面的亮度的调节

    2.4.2 曝光/快门 调节

    曝光(exposure)调节也叫快门(shutter)调节,快门的开关决定了曝光时间的长短,首先需要确认Sensor的调节能力,查阅Sensor的Datasheet,找的曝光调节的章节,以某一型号sensor为例,其曝光相关寄存器如下:

    重要的参数如下

    寄存器地址 reg_addr  {0x3e00[3:0], 0x3e01[0:7], 0x3e02[7:4]}
    曝光上限 expo_max VTS - 4  (
    曝光下限 expo_min 0
    曝光步长 expo_step 1
    曝光生效延迟时间 expo_delay 2

    在init handle中填入下面的参数

    handle->sensor_ae_info_cfg.u8AEShutterDelay                   = 2;
    handle->sensor_ae_info_cfg.u8AEShutterCtrlNum                 = 1;
    
    /*增益设置同理*/
    handle->sensor_ae_info_cfg.u8AEGainDelay                      = SENSOR_GAIN_DELAY_FRAME_COUNT;
    handle->sensor_ae_info_cfg.u8AEGainCtrlNum                    = SENSOR_GAIN_CTRL_NUM;
    

    曝光时间的计算实际上和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 pCus_GetShutterInfo(ss_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(ss_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(ss_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  = pCus_GetShutterInfo;
    ......
    
    //AEnotify生效
    static int pCus_AEStatusNotify(ss_cus_sensor *handle, u32 idx, 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;
            }
    ......
    }
    

    2.4.3 曝光与FPS的相互影响

    假如有这样一个需求,FPS要有30,曝光时间40ms

    FPS为30意味着,sensor需要每33.3ms读出一张图像,曝光时间的理论上限即为33.3ms,但一般不可能到这个值,要给图像处理留出时间所以上面的那种需求是不合理的,也是不可能的

    前面推导出的曝光上上限公式如下

    C:\b465e44256bd1940d240effe97c81d60

    可以看到曝光实际上会受到FPS的影响,FPS越小,曝光时间就能调整得越长

    某些低亮度场景下,可以牺牲帧率来提高画面亮度

    需要注意的是,不同Sensor的行为之间会有差异,有如下两种

    • 仅调整曝光行数就可以降低帧率,当 expo_lines > VTS  时 , VTS = expo_lines + X

    • Sensor内部会限制expo_lines的最大值为 VTS - X,曝光行数调得再大也没用,必须同时调整VTS和expo_lines

    tips:X 的值如果data sheet未说明,请找sensor vendor确认,此处该型号sensor的X=4

    2.4.4 增益调节

    增益(gain)调节与信号系统中的概念一致,对信号进行放大,在Sensor中,最终表现为影响图像的整体亮度,通常的Sensor都支持两种调节功能

    C:\0df99bd81041ce244ce682f7f28d95da

    • 模拟增益 (Analog gain)

      在ADC转换之前对模拟信号进行放大,模拟增益在放大信号的同时,带来的噪声只会一次引入

    • 数字增益(Digital gain)

      对ADC转换后的数字信号进行放大,数字增益在放大信号的同时,带来的噪声会多次级联引入,相较于模拟增益,数字信号还包含了量化噪声,读出电路噪声

    一般来说模拟增益的效果优于数字增益的,各个厂商的sensor增益调节的方法存在差异,同一个厂商不同型号的计算方法也存在区别,有的厂商甚至不写在Data sheet中,比如GC(galaxy core),OV(omnivison),需要问厂商的FAE要计算公式。

    分段式粗细调节

    参考某一型号sensor的Data sheet,获取gain的规格参数,如下

    增益的总的倍数计算:total_gain = analog_gain * digital_gain = 49.6*8 =  396.8 倍

    模拟增益和数字增益可以配合使用,一般来说优先调节模拟增益,如果拉到最大还是无法满足,再调节数字增益,当然也可以单独只使用模拟增益或数字增益其中的一种,再找到调节方法,如下

    C:\549e29cf052da5ce141331bb3d359d58

    该sensor的调节方法为

    • analog_gain使用两组寄存器控制,0x3e08:粗调(Coarse_Again),0x3e09:细调(Fine_Again)

    • digtal_gain只有一个寄存器,0x3e06,但其精度不高

    此外该sensor还提供了一份 analog_gain table

    最简单的办法是,把上表做成一个数组,通过查表来得到最接近的近似值,但是这样这个表太大了,自己手动创建会花费极大的精力。

    一般来说大多数厂家会提供一个计算公式,该公式包含了目标gain和寄存器间的映射关系,通过该等式可以计算出需要下的寄存器数值。若厂家并未提供计算公式,我们也可以通过找规律的方式自己得出gain和reg_value间的关系式。

    对于上表,当 1\<= gain_value \< 2 时,coarse_again_reg固定为0x3,coarse_again固定为1,fine_again_reg取值为 0x40~0x7F(64~127), reg_step=1/64。 fine_again_reg每变化1bit,gain的变化幅度为(gain_max-gain_min)*reg_step,记作reg_unit = (gain_max-gain_min)*reg_step =  (2-1)*1/64=0.015625

    当 2\<= gain_value \< 3.125 时,coarse_again_reg固定为0x7,coarse_again固定为2,fine_again_reg取值为 0x40~0x63(64~99),reg_step=1/36。fine_again_reg每变化1bit,reg_unit = (3.125-2)*1/36=0.03125

    当gain_value在其他的取值范围内时同理,也可找到类似的规律,我们的目标就是找到 fine_again_reg 和gain_value的计算公式,就能根据需要的gain值,算出需要下到寄存器的值了

    这里我们就得到了该型号sensor在[1, 3.125)范围内的一个比较通用的gain与gain_value的计算公式

    就可以实现如下的示例代码

    I2C_ARRAY gain_reg[] = {
        {0x3e06, 0x00},
        {0x3e08, 0x03}, //coarse_Again_reg
        {0x3e09, 0x40}, //fine_Again_reg
    };
    #define SENSOR_MAXGAIN 3200 \\3.125*1024
    
    static int pCus_SetAEGain(ss_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(ss_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->sensor_ae_info_cfg.u32AEGain_min        = 1024;
        handle->sensor_ae_info_cfg.u32AEGain_max        = SENSOR_MAXGAIN;
    ......
    
    static int pCus_AEStatusNotify(ss_cus_sensor *handle, u32 idx, 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;
            }
    ......
    }
    

    3. Sensor Driver验证

    3.1 出图验证

    可以使用任意的串流demo,跑RTSP出流,使用VLC或PotPlayer预览图像

    3.2 FPS/Orien 控制验证

    echo cmd param > /dev/sensorif

    支持的命令与参数如下:

    cmd param description example
    enable  [pad] pad:Sensor注册的PAD ID 使能sensor echo enable 0 >/dev/sensorif
    disable  [pad] pad:Sensor注册的PAD ID 关闭sensor echo disable 0 >/dev/sensorif
    planemode [pad] [HDR mode] pad:Sensor注册的PAD ID
    HDR mode:是否HDR
    设置sensor的HDR模式 echo planemode 0 1 >/dev/sensorif
    set_res  [pad] [nres] pad:Sensor注册的PAD ID
    nres:resolution的编号
    设置sensor输出的resolution echo set_res  0 1 >/dev/sensorif
    fps  [pad] [fpsx1000] pad:Sensor注册的PAD ID
    fpsx1000:fps帧数的1000倍
    设置sensor输出的fps echo fps 0 30000 >/dev/sensorif
    mf  [pad] [mirror] [flip] pad:Sensor注册的PAD ID
    mirror:是否水平翻转
    flip:是否垂直翻转
    设置sensor输出的rotation echo mf 0 1 1 >/dev/sensorif
    shutter  [pad] [us] pad:Sensor注册的PAD ID
    us:曝光时间,单位us
    设置sensor曝光时间 echo shutter  0 30000 >/dev/sensorif
    gain  [pad] [gainx1024] pad:Sensor注册的PAD ID
    gainx1024:gain的1024倍
    设置sensor 增益 echo gain 0 2048 >/dev/sensorif
    notify  [pad] [notify_type] pad:Sensor注册的PAD ID
    notify_type:1 frame start触发,0在frame end触发
    执行AEnotify,主动触发或者frame end时notify echo notify 0 1 >/dev/sensorif

    出流成功后,通过以上命令来调整Sensor

    3.3 AE验证

    AE影响的是画面亮度,想要判断AE是否工作,最简单的检查手段如下:

    • RTSP出流时,遮黑sensor镜头,此时如果AE在工作就会将画面亮度拉得很高,再迅速将遮黑物移除,观察画面亮度是否经过过曝,轻微闪烁,亮度稳定

    更详细的检查需要使用IQtool来连接,查看gain/exposure的值变化来判断

    4. FAQ

    Q1:如何确定VIF有收到数据

    查看 VIF的图像统计和中断统计信息,请参考MI VIF API 章节5.PROCFS介绍

    Q2:出图测试需要注意哪些内容

    1. 翻转/镜像

    验证方法:

    1.1 根据3.1跑RTSP出流,使用VLC或PotPlayer预览图像,输入3.2 所述的ut指令要可以看到画面实现翻转或者镜像效果。

    1.2 在vif-isp frame mode下进行翻转,需要注意mi_vif proc节点下,没有发生double vsync,有一些sensor在翻转时可能会出现数据异常,此时可以调用SetSkipFrame接口跳过N(N=1,2,..)帧进行规避。

    1. 曝光/增益

    验证方法:

    2.1 根据3.3所述进行AE验证时,串流到isp时,此时3a会去调节曝光,ut指令调节效果就不明显了,此时可以使用iq tool,将ae调成manul mode模式,手动调节画面观看效果。对于自动曝光效果验证,可以打开do ae的log或者连接iq tool,进行镜头遮黑-挪开-遮黑循环操作观看AE参数的变化,预期时ae参数变化明显,且趋于稳定,并且画面不会出现过黑和过曝现象;

    2.2 最直接且明显的方法是在driver的AeStatusNotify function内加log打印当前写到sensor的寄存器值。此外还可以通过i2c读回ae相关寄存器的值确定driver的写操作确实生效。

    2.3 对于细节的曝光和增益异常(例如增益线性度异常),就需要对比function与datasheet描述是否一致,是否出现参数写死、写错问题,必要时请咨询sensor厂商。

    Q3:对gain/shutter/mirror/flip等寄存器下,如何选择notify时机

    对大多数sensor来讲,它们的寄存器都是延迟生效,即在下一个frame start才真正生效。所以一般建议driver在AeStatusNotify中的CUS_FRAME_ACTIVE统一去写这些寄存器,这样可以使的sensor相关的改动全在同一帧生效。但也可能存在一些sensor,它的部分寄存器是写完后立即生效的,那就只能在vblanking区间写即driver需要在CUS_FRAME_INACTIVE内去写。

    Q4:sensor在做mirror/flip时画面花屏或撕裂

    部分sensor存在做mirror/flip时出现画面花屏或撕裂的情况,这个时候我们可以使用SetSkipFrame函数跳过坏帧,该函数的使用需要在写寄存器前。或者如果sensor支持group hold寄存器,可以加上group hold寄存器设定,保证多个寄存器设定同时生效,对于HDR可以保证长短曝同步生效。

    使用SetSkipFrame示例代码:

    static int pCus_AEStatusNotify(struct __ss_cus_sensor*handle,u32 idx, CUS_CAMSENSOR_AE_STATUS_NOTIFY status)
    {
        xxx_params *params = (xxx_params *)handle->private_data;
        ISensorIfAPI *sensor_if = handle->sensor_if_api;
    
        switch(status)
        {
            case CUS_FRAME_INACTIVE:
                break;
            case CUS_FRAME_ACTIVE:
               if(params->orient_dirty){
                    sensor_if->SetSkipFrame(idx, params->expo.fps, 1u);
                    SensorRegArrayW((I2C_ARRAY*)params->tMirror_reg, ARRAY_SIZE(mirror_reg));
                    params->orient_dirty = false;
                }
                break;
            default :
                break;
        }
        return SUCCESS;
    }
    

    使用group hold示例代码:

    static int pCus_AEStatusNotify(struct __ss_cus_sensor*handle,u32 idx, CUS_CAMSENSOR_AE_STATUS_NOTIFY status)
    {
        xxx_params *params = (xxx_params *)handle->private_data;
        ISensorIfAPI *sensor_if = handle->sensor_if_api;
    
        switch(status)
        {
            case CUS_FRAME_INACTIVE:
                break;
            case CUS_FRAME_ACTIVE:
                if(params->dirty || params->orien_dirty) {
                    if(params->dirty) {
                        SensorReg_Write(0x3001,1); // Global hold on
                        SensorRegArrayW((I2C_ARRAY*)params->tExpo_reg, ARRAY_SIZE(expo_reg));
                        SensorRegArrayW((I2C_ARRAY*)params->tGain_reg, ARRAY_SIZE(gain_reg));
                        SensorRegArrayW((I2C_ARRAY*)params->tVts_reg, ARRAY_SIZE(vts_reg));
                        SensorReg_Write(0x3001,0); // Global hold off
                        params->dirty = false;
                    }
                }
                break;
            default :
                break;
        }
        return SUCCESS;
    }