• 第4季4:图像sensor的驱动源码解析


    以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。 

    一、sensor驱动源码的框架

    mpp定义了一整套sensor驱动的实现和封装,这里以ar0130型号的sensor为例进行说明。

    1、sensor层驱动

    (1)sensor层驱动位于mpp/component/isp/sensor/ar0130目录,包括:Makefile、ar0130_cmos.c、ar0130_sensor_ctl.c文件。

    (2)ar0130_cmos.c文件定义了一个回调函数sensor_register_callback和一些上层函数,体现了sensor驱动框架中的最上层,或者说功能层。

    (3)ar0130_sensor_ctl.c文件定义了一些与底层硬件相关的寄存器值配置函数。这些函数间接调用了ar0130_sensor_ctl.c文件中的sensor_write_register函数,这个函数用来给寄存器配置数值。注意,这个文件并不是sensor驱动框架的最底层,最底层的应该是I2C驱动,因为函数sensor_write_register里面的sensor_i2c_init函数打开I2C设备文件,然后利用ioctl函数来写寄存器的值,也就是说,最后还是需要调用I2C驱动来完成寄存器数值的配置的。一言以蔽之,sensor内部有若干寄存器,可以通过I2C接口来读取。

    2、底层I2C驱动

    (1)I2C的驱动源码,位于内核源码的driver/i2c目录,提供了I2C层面的物理层操作接口。

    (2)这层驱动只与Hi3518e芯片设置有关(主要与I2C控制器有关),海思SDK中已经写好,我们一般不会去改动这层驱动源码。

    (3)这层驱动主要体现在I2C设备文件“/dev/i2c-0”。

    3、驱动调用关系

    (1)我们来分析一下mpp/component/isp/sensor/ar0130/Makefile文件。

    1. # 忽略部分代码
    2. # 寻找mpp/component/isp/sensor/ar0130目录下以.c结尾的文件
    3. SRCS = $(wildcard ./*.c)
    4. # 将这些.c文件换成后缀为.o的文件
    5. OBJS = $(SRCS:%.c=%.o)
    6. # 这个是啥意思?
    7. # 为了不污染源码目录,新建了一个目录./obj/,将来.o文件会放在./obj目录里?
    8. OBJS := $(OBJS:./%=obj/%)
    9. # TARGETLIB=/mpp/component/isp/lib/libsns_ar0130.a
    10. TARGETLIB := $(LIBPATH)/libsns_ar0130.a
    11. # TARGETLIB_SO=/mpp/component/isp/lib/libsns_ar0130.a
    12. TARGETLIB_SO := $(LIBPATH)/libsns_ar0130.so
    13. # 利用这些
    14. # 制作/mpp/component/isp/lib/libsns_ar0130.a静态链接库文件
    15. # 制作/mpp/component/isp/lib/libsns_ar0130.so动态链接库文件
    16. all:$(TARGETLIB)
    17. $(TARGETLIB):$(OBJS)
    18. @($(AR) $(ARFLAGS) $(TARGETLIB) $(OBJS))
    19. @($(CC) $(ARFLAGS_SO) $(TARGETLIB_SO) $(OBJS))

    (2)分析可知,在该Makefile文件所在目录下执行make后,在mpp/component/isp/目录下生成了lib目录,该目录中有两个文件:libsns_ar0130.a、libsns_ar0130.so文件,它们分别是静态链接库文件、动态链接库文件。

    1. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/sensor/ar0130# ls
    2. ar0130_cmos.c ar0130_sensor_ctl.c Makefile obj
    3. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/sensor/ar0130# cd ../..
    4. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp# ls
    5. 3a defog firmware include iniparser lib Makefile sensor
    6. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp# cd lib/
    7. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/lib# ls
    8. libsns_ar0130.a libsns_ar0130.so
    9. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/lib#

    (3)在mpp/component/isp/Makefile中,将上面生成的libsns_ar0130.a、libsns_ar0130.so文件复制到了mpp/lib目录下。

    1. # 省略部分代码
    2. ISP_KO := ./ko
    3. ISP_LIB := ./lib
    4. .PHONY:clean all rel
    5. all:
    6. @mkdir -p $(REL_KO); cp $(ISP_KO)/*.ko $(REL_KO) -rf
    7. @mkdir -p $(REL_LIB); cp $(ISP_LIB)/*.a $(REL_LIB) -rf; cp $(ISP_LIB)/*.so $(REL_LIB) -rf

    其中的REL_KO、REL_LIB在mpp/Makefile.param文件中定义如下:

    1. export REL_INC := $(REL_DIR)/include
    2. export REL_LIB := $(REL_DIR)/lib
    3. export REL_KO := $(REL_DIR)/ko
    4. # REL_INC = ……/mpp/include
    5. # REL_LIB = ……/mpp/lib
    6. # REL_KO = ……/mpp/ko

    (4)分析与总结

    ar0130的sensor层驱动代码以库的形式存在(对应着mpp/component/isp/lib目录中的libsns_ar0130.a、libsns_ar0130.so文件,最后被复制到mpp/lib目录中),这说明sensor层驱动不属于内核源码,而属于应用层的内容,或者说应用层的驱动(见博客I2C子系统详解1——I2C总线设备的驱动框架的描述)。在对应用程序进行编译链接时,这些库会以静态或者动态的方式链接进去。

    在博文第一季7:海思的根文件系统的概览与制作中,我们把mpp/lib/目录中的文件拷贝到了板载系统的/usr/lib目录中。其实拷贝动态链接库就好,因为如果是静态链接库,在虚拟机中编译生成可执行文件时,可执行文件就已经包括静态链接库了,因此没必要把静态链接库也拷贝到板载系统上。

    二、sensor驱动源码的解析

    1、sample_venc.c调用sensor驱动的流程

    利用SI软件建立mpp工程,然后从sample_venc.c入手,分析调用sensor驱动的流程。

    1. SAMPLE_VENC_1080P_CLASSIC
    2. SAMPLE_COMM_VI_StartVi
    3. SAMPLE_COMM_VI_StartIspAndVi
    4. SAMPLE_COMM_ISP_Init
    5. sensor_register_callback//位于ar0130_cmos.c文件文件

    (1)从上面的分析可知,应用层sample_venc.c中调用了sensor_register_callback函数,通过这个函数去操作sensor硬件。这个函数位于ar0130_cmos.c文件中,而ar0130_cmos.c文件将来会被编译成库的形式被调用,而不是驱动的形式。即ar0130_cmos.c文件还是属于应用层的,但因为有驱动的性质,所以称之为“应用层驱动”(见I2C子系统详解1——I2C总线设备的驱动框架)。

    (2)但“应用层驱动”最终需要调用内核驱动来操控硬件。比如sensor_register_callback函数流程中的sensor_i2c_init函数,它打开了一个I2C设备文件“/dev/i2c-0”(这个设备文件对应着内核中的I2C驱动相关的内容,它是I2C驱动的表征),然后利用ioctl函数给寄存器写入数值。至于数值怎样写入寄存器的,涉及到I2C驱动的细节内容(比如怎样发送数据),而sensor驱动层是不包含这些细节内容的,它只是操作I2C设备文件“/dev/i2c-0”(从这角度来看,sensor驱动层是应用层)。

    1. sensor_register_callback //位于ar0130_cmos.c文件文件
    2. cmos_init_sensor_exp_function //位于ar0130_cmos.c文件文件
    3. sensor_init //位于ar0130_sensor_ctl.c文件
    4. sensor_init_720p_30fps //位于ar0130_sensor_ctl.c文件
    5. sensor_write_register //位于ar0130_sensor_ctl.c文件
    6. sensor_i2c_init //位于ar0130_sensor_ctl.c文件
    7. ioctl
    1. int sensor_i2c_init(void)
    2. {
    3. if(g_fd >= 0)
    4. {
    5. return 0;
    6. }
    7. #ifdef HI_GPIO_I2C
    8. int ret;
    9. g_fd = open("/dev/gpioi2c_ex", 0);
    10. if(g_fd < 0)
    11. {
    12. printf("Open gpioi2c_ex error!\n");
    13. return -1;
    14. }
    15. #else
    16. int ret;
    17. g_fd = open("/dev/i2c-0", O_RDWR);
    18. if(g_fd < 0)
    19. {
    20. printf("Open /dev/i2c-0 error!\n");
    21. return -1;
    22. }
    23. ret = ioctl(g_fd, I2C_SLAVE_FORCE, sensor_i2c_addr);
    24. if (ret < 0)
    25. {
    26. printf("CMD_SET_DEV error!\n");
    27. return ret;
    28. }
    29. #endif
    30. return 0;
    31. }

    2、sensor_register_callback函数的分析

    (1)该函数内容如下,由代码结构可知有ISP、AE、AWB三部分,我们重点关注ISP部分。

    1. int sensor_register_callback(void)
    2. {
    3. ISP_DEV IspDev = 0;
    4. HI_S32 s32Ret;
    5. ALG_LIB_S stLib;
    6. ISP_SENSOR_REGISTER_S stIspRegister;
    7. AE_SENSOR_REGISTER_S stAeRegister;
    8. AWB_SENSOR_REGISTER_S stAwbRegister;
    9. cmos_init_sensor_exp_function(&stIspRegister.stSnsExp);
    10. s32Ret = HI_MPI_ISP_SensorRegCallBack(IspDev, AR0130_ID, &stIspRegister);
    11. if (s32Ret)
    12. {
    13. printf("sensor register callback function failed!\n");
    14. return s32Ret;
    15. }
    16. stLib.s32Id = 0;
    17. strncpy(stLib.acLibName, HI_AE_LIB_NAME, sizeof(HI_AE_LIB_NAME));
    18. cmos_init_ae_exp_function(&stAeRegister.stSnsExp);
    19. s32Ret = HI_MPI_AE_SensorRegCallBack(IspDev, &stLib, AR0130_ID, &stAeRegister);
    20. if (s32Ret)
    21. {
    22. printf("sensor register callback function to ae lib failed!\n");
    23. return s32Ret;
    24. }
    25. stLib.s32Id = 0;
    26. strncpy(stLib.acLibName, HI_AWB_LIB_NAME, sizeof(HI_AWB_LIB_NAME));
    27. cmos_init_awb_exp_function(&stAwbRegister.stSnsExp);
    28. s32Ret = HI_MPI_AWB_SensorRegCallBack(IspDev, &stLib, AR0130_ID, &stAwbRegister);
    29. if (s32Ret)
    30. {
    31. printf("sensor register callback function to awb lib failed!\n");
    32. return s32Ret;
    33. }
    34. return 0;
    35. }

    (2)函数HI_MPI_ISP_SensorRegCallBack(IspDev, AR0130_ID, &stIspRegister),主要用来绑定sensor和Hi3518e的ISP模块。示意图如下,其中ISP表示Hi3518e中的ISP模块,该模块位于VI模块里。

    该函数的参数1,IspDev=0,是因为Hi3518e中只有一个VI模块,所以编号为0。

    该函数的参数2,AR0130_ID=130,这是sensor AR0130在mpp中的唯一编号。

    重点关注参数3,它表示ISP模块可以对sensor进行哪些操作。该参数是结构体变量,结构体的成员是一些函数指针,如下所示。

    1. typedef struct hiISP_SENSOR_EXP_FUNC_S
    2. {
    3.     HI_VOID(*pfn_cmos_sensor_init)(HI_VOID);
    4.     HI_VOID(*pfn_cmos_sensor_exit)(HI_VOID);
    5.     HI_VOID(*pfn_cmos_sensor_global_init)(HI_VOID);
    6.     HI_S32(*pfn_cmos_set_image_mode)(ISP_CMOS_SENSOR_IMAGE_MODE_S *pstSensorImageMode);
    7.     HI_VOID(*pfn_cmos_set_wdr_mode)(HI_U8 u8Mode);
    8.     
    9.     /* the algs get data which is associated with sensor, except 3a */
    10.     HI_U32(*pfn_cmos_get_isp_default)(ISP_CMOS_DEFAULT_S *pstDef);
    11.     HI_U32(*pfn_cmos_get_isp_black_level)(ISP_CMOS_BLACK_LEVEL_S *pstBlackLevel);
    12.     HI_U32(*pfn_cmos_get_sns_reg_info)(ISP_SNS_REGS_INFO_S *pstSnsRegsInfo);
    13.     /* the function of sensor set pixel detect */
    14.     HI_VOID(*pfn_cmos_set_pixel_detect)(HI_BOOL bEnable);
    15. } ISP_SENSOR_EXP_FUNC_S;

    这些函数指针指向哪些函数?或者说什么时候被填充的?

    如下所示,在cmos_init_sensor_exp_function(&stIspRegister.stSnsExp)函数中完成了上述函数指针的填充工作。其中sensor_init等实体函数,位于ar0130_sensor_ctl.c文件中,它们是sensor驱动要提供的函数。

    1. HI_S32 cmos_init_sensor_exp_function(ISP_SENSOR_EXP_FUNC_S *pstSensorExpFunc)
    2. {
    3. memset(pstSensorExpFunc, 0, sizeof(ISP_SENSOR_EXP_FUNC_S));
    4. pstSensorExpFunc->pfn_cmos_sensor_init = sensor_init;
    5. pstSensorExpFunc->pfn_cmos_sensor_exit = sensor_exit;
    6. pstSensorExpFunc->pfn_cmos_sensor_global_init = sensor_global_init;
    7. pstSensorExpFunc->pfn_cmos_set_image_mode = cmos_set_image_mode;
    8. pstSensorExpFunc->pfn_cmos_set_wdr_mode = cmos_set_wdr_mode;
    9. pstSensorExpFunc->pfn_cmos_get_isp_default = cmos_get_isp_default;
    10. pstSensorExpFunc->pfn_cmos_get_isp_black_level = cmos_get_isp_black_level;
    11. pstSensorExpFunc->pfn_cmos_set_pixel_detect = cmos_set_pixel_detect;
    12. pstSensorExpFunc->pfn_cmos_get_sns_reg_info = cmos_get_sns_regs_info;
    13. return 0;
    14. }

    (3)上面讨论的是ISP,用来初始化sensor中与ISP(图像采集等)相关的操作;而代码中的AE(自动曝光)用来初始化sensor中与AE相关的操作,AWB(自动白平衡)用来初始化sensor中与AWB相关的操作。其实应该还有自动对焦AF,但AR0130没有这个功能,所以这里没有。因为AE、AWB的内容结构和ISP类似,这里不再赘述。

    (4)完成ISP、AE、AWB这三组操作之后,sensor和Hi3518e的对接就基本完成了。代码中其他的内容,基本就是上面的函数指针要指向的实体函数,这也是我们写sensor驱动的人要编写的部分(一般厂商工程师会提供一些较深入的数据与内容)。

    (5)关于HI_MPI_ISP_SensorRegCallBack(IspDev, AR0130_ID, &stIspRegister)函数的用法,可以参考海思SDK中Hi3518E V200R001C01SPC030\01.software\board\document_cn目录下的文档《ISP_3A开发指南》。

  • 相关阅读:
    DHCPsnooping 配置实验(1)
    量化交易中的资金管理模型分享
    在JavaScript中利用代码来实现ATM的效果,具有存钱,取钱,查看余额,退出功能
    PyCharm配置Anaconda PyQt5开发环境
    一些关于运筹学和机器学习之间协同作用的思考
    给饿了么Radio 单选框添加点击事件
    【第三天】C++类和对象进阶指南:从堆区空间操作到友元的深度掌握
    ROS入门21讲笔记
    马斯克嘲讽元宇宙:谁会整天戴着头显设备?
    docker运行javaWeb服务,操作文件异常
  • 原文地址:https://blog.csdn.net/oqqHuTu12345678/article/details/128132400