以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
一、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文件。
# 忽略部分代码 # 寻找mpp/component/isp/sensor/ar0130目录下以.c结尾的文件 SRCS = $(wildcard ./*.c) # 将这些.c文件换成后缀为.o的文件 OBJS = $(SRCS:%.c=%.o) # 这个是啥意思? # 为了不污染源码目录,新建了一个目录./obj/,将来.o文件会放在./obj目录里? OBJS := $(OBJS:./%=obj/%) # TARGETLIB=/mpp/component/isp/lib/libsns_ar0130.a TARGETLIB := $(LIBPATH)/libsns_ar0130.a # TARGETLIB_SO=/mpp/component/isp/lib/libsns_ar0130.a TARGETLIB_SO := $(LIBPATH)/libsns_ar0130.so # 利用这些 # 制作/mpp/component/isp/lib/libsns_ar0130.a静态链接库文件 # 制作/mpp/component/isp/lib/libsns_ar0130.so动态链接库文件 all:$(TARGETLIB) $(TARGETLIB):$(OBJS) @($(AR) $(ARFLAGS) $(TARGETLIB) $(OBJS)) @($(CC) $(ARFLAGS_SO) $(TARGETLIB_SO) $(OBJS))
(2)分析可知,在该Makefile文件所在目录下执行make后,在mpp/component/isp/目录下生成了lib目录,该目录中有两个文件:libsns_ar0130.a、libsns_ar0130.so文件,它们分别是静态链接库文件、动态链接库文件。
root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/sensor/ar0130# ls ar0130_cmos.c ar0130_sensor_ctl.c Makefile obj root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/sensor/ar0130# cd ../.. root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp# ls 3a defog firmware include iniparser lib Makefile sensor root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp# cd lib/ root@ubuntu:/home/xjh/iot/hisi_development/Hi3518E_SDK/Hi3518E_SDK_V1.0.3.0/mpp/component/isp/lib# ls libsns_ar0130.a libsns_ar0130.so 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目录下。
# 省略部分代码 ISP_KO := ./ko ISP_LIB := ./lib .PHONY:clean all rel all: @mkdir -p $(REL_KO); cp $(ISP_KO)/*.ko $(REL_KO) -rf @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文件中定义如下:
export REL_INC := $(REL_DIR)/include export REL_LIB := $(REL_DIR)/lib export REL_KO := $(REL_DIR)/ko # REL_INC = ……/mpp/include # REL_LIB = ……/mpp/lib # 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驱动的流程。
SAMPLE_VENC_1080P_CLASSIC SAMPLE_COMM_VI_StartVi SAMPLE_COMM_VI_StartIspAndVi SAMPLE_COMM_ISP_Init 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驱动层是应用层)。
sensor_register_callback //位于ar0130_cmos.c文件文件 cmos_init_sensor_exp_function //位于ar0130_cmos.c文件文件 sensor_init //位于ar0130_sensor_ctl.c文件 sensor_init_720p_30fps //位于ar0130_sensor_ctl.c文件 sensor_write_register //位于ar0130_sensor_ctl.c文件 sensor_i2c_init //位于ar0130_sensor_ctl.c文件 ioctl
int sensor_i2c_init(void) { if(g_fd >= 0) { return 0; } #ifdef HI_GPIO_I2C int ret; g_fd = open("/dev/gpioi2c_ex", 0); if(g_fd < 0) { printf("Open gpioi2c_ex error!\n"); return -1; } #else int ret; g_fd = open("/dev/i2c-0", O_RDWR); if(g_fd < 0) { printf("Open /dev/i2c-0 error!\n"); return -1; } ret = ioctl(g_fd, I2C_SLAVE_FORCE, sensor_i2c_addr); if (ret < 0) { printf("CMD_SET_DEV error!\n"); return ret; } #endif return 0; }
2、sensor_register_callback函数的分析
(1)该函数内容如下,由代码结构可知有ISP、AE、AWB三部分,我们重点关注ISP部分。
int sensor_register_callback(void) { ISP_DEV IspDev = 0; HI_S32 s32Ret; ALG_LIB_S stLib; ISP_SENSOR_REGISTER_S stIspRegister; AE_SENSOR_REGISTER_S stAeRegister; AWB_SENSOR_REGISTER_S stAwbRegister; cmos_init_sensor_exp_function(&stIspRegister.stSnsExp); s32Ret = HI_MPI_ISP_SensorRegCallBack(IspDev, AR0130_ID, &stIspRegister); if (s32Ret) { printf("sensor register callback function failed!\n"); return s32Ret; } stLib.s32Id = 0; strncpy(stLib.acLibName, HI_AE_LIB_NAME, sizeof(HI_AE_LIB_NAME)); cmos_init_ae_exp_function(&stAeRegister.stSnsExp); s32Ret = HI_MPI_AE_SensorRegCallBack(IspDev, &stLib, AR0130_ID, &stAeRegister); if (s32Ret) { printf("sensor register callback function to ae lib failed!\n"); return s32Ret; } stLib.s32Id = 0; strncpy(stLib.acLibName, HI_AWB_LIB_NAME, sizeof(HI_AWB_LIB_NAME)); cmos_init_awb_exp_function(&stAwbRegister.stSnsExp); s32Ret = HI_MPI_AWB_SensorRegCallBack(IspDev, &stLib, AR0130_ID, &stAwbRegister); if (s32Ret) { printf("sensor register callback function to awb lib failed!\n"); return s32Ret; } return 0; }
(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进行哪些操作。该参数是结构体变量,结构体的成员是一些函数指针,如下所示。
typedef struct hiISP_SENSOR_EXP_FUNC_S { HI_VOID(*pfn_cmos_sensor_init)(HI_VOID); HI_VOID(*pfn_cmos_sensor_exit)(HI_VOID); HI_VOID(*pfn_cmos_sensor_global_init)(HI_VOID); HI_S32(*pfn_cmos_set_image_mode)(ISP_CMOS_SENSOR_IMAGE_MODE_S *pstSensorImageMode); HI_VOID(*pfn_cmos_set_wdr_mode)(HI_U8 u8Mode); /* the algs get data which is associated with sensor, except 3a */ HI_U32(*pfn_cmos_get_isp_default)(ISP_CMOS_DEFAULT_S *pstDef); HI_U32(*pfn_cmos_get_isp_black_level)(ISP_CMOS_BLACK_LEVEL_S *pstBlackLevel); HI_U32(*pfn_cmos_get_sns_reg_info)(ISP_SNS_REGS_INFO_S *pstSnsRegsInfo); /* the function of sensor set pixel detect */ HI_VOID(*pfn_cmos_set_pixel_detect)(HI_BOOL bEnable); } ISP_SENSOR_EXP_FUNC_S;
这些函数指针指向哪些函数?或者说什么时候被填充的?
如下所示,在cmos_init_sensor_exp_function(&stIspRegister.stSnsExp)函数中完成了上述函数指针的填充工作。其中sensor_init等实体函数,位于ar0130_sensor_ctl.c文件中,它们是sensor驱动要提供的函数。
HI_S32 cmos_init_sensor_exp_function(ISP_SENSOR_EXP_FUNC_S *pstSensorExpFunc) { memset(pstSensorExpFunc, 0, sizeof(ISP_SENSOR_EXP_FUNC_S)); pstSensorExpFunc->pfn_cmos_sensor_init = sensor_init; pstSensorExpFunc->pfn_cmos_sensor_exit = sensor_exit; pstSensorExpFunc->pfn_cmos_sensor_global_init = sensor_global_init; pstSensorExpFunc->pfn_cmos_set_image_mode = cmos_set_image_mode; pstSensorExpFunc->pfn_cmos_set_wdr_mode = cmos_set_wdr_mode; pstSensorExpFunc->pfn_cmos_get_isp_default = cmos_get_isp_default; pstSensorExpFunc->pfn_cmos_get_isp_black_level = cmos_get_isp_black_level; pstSensorExpFunc->pfn_cmos_set_pixel_detect = cmos_set_pixel_detect; pstSensorExpFunc->pfn_cmos_get_sns_reg_info = cmos_get_sns_regs_info; return 0; }
(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开发指南》。