相机开发指南
本文主要介绍 SpacemiT K1 平台 Camera 模块的快速上手开发。
K1 仅支持 MIPI 类型接口,使用 SpacemiT camera 驱动框架。
Camera 快速点亮导览
点亮已支持的摄像头(两步)
第一步:确定摄像头接口并运行命令 确定摄像头连接的 MIPI CSI 接口(如 CSI1),运行以下命令(以 CSI1 为例):
cam-test /usr/share/camera_json/csi1_camera_detect.json
如果成功,如下示例输出,系统会自动生成可用的 JSON 文件到 /usr/share/camera_json/
目录。如果失败,说明没有支持此摄像头,或者硬件有问题,建议联系进迭时空的技术支持。
......
I: ./sensors/cam_sensors_module.c(235): "detect ov16a10_spm sensors in csi1: success, set 3840x2160 to 1920x1080"
I: auto_detect_camera(1401): "auto detect sensor ===================== finish "
I: update_json_file(672): "save json to /usr/share/camera_json/csi1_camera_auto.json success"
从上述运行 log 还可以得出:
- ov16a10 sensor 生成的 json 默认使用模式 0
- sensor 输出分辨率为 3840x2160
- isp 输出分辨率为 1920x1080
json 配置文件的更多描述,请参阅下文的 JSON 参数说明。
第二步:启动摄像头出图测试
运行下列命令,启动摄像头出图 500 帧,并保存第 250 帧。
cam-test /usr/share/camera_json/csi1_camera_auto.json
- 正常运行的输出 log 请参阅 正常运行单路在线测试 log 章节
- 如果运行失败,建议联系进迭时空的技术支持
第三步(可选):屏幕预览
如果摄像头需要实现屏幕预览,可以参考 gstreamer_user_guide 文档里的摄像头应用章节里的 MIPI 摄像头说明。
点亮一款新的摄像头
通常仅需要调整 cam-test
应用层的代码即可快速支持,所有 camera 相关的内核配置、DTS 无需修改。
点亮 sensor 所依赖的上下电 GPIO、MCLK 时钟、MIPI lane 配置等硬件功能,在方案对外发布前已由内部工程师验证完毕。极少数情况下需要修改 DTS 和驱动(如因外部不可抗拒原因必须修改主板 MIPI CSI 接口电路,上电 GPIOA 更改为 GPIOB,MCLKA 更改为 MCLKB 等),此情况下建议联系进迭工程师支持。
新摄像头点亮步骤
如果不考虑特殊情况,点亮一款新摄像头,建议按照以下步骤展开:
-
复用相近型号代码
- 根据当前摄像头型号,复用列表中已支持的相近型号的应用代码
- 主要复用摄像头应用的代码结构排布(减少开发工作量)
- 修改函数名称、结构体名称为当前摄像头型号
- 详情参阅本文里的 Bring up 章节
-
配置传感器参数
- 阅读摄像头数据手册,确定:
- 寄存器位数
- I2C 地址
- 上电流程
- ID 寄存器及 ID 值
- 修改 sensor 应用代码
- 上电流程可参阅本文里的 [Sensor 驱动] 章节
- 阅读摄像头数据手册,确定:
-
配置寄存器参数
- 配置 setting tab 寄存器数组
- 根据原厂提供的信息或计算值,确定:
- lane 数 / HTS / VTS / MCLK / FPS / PCLK / 分辨率 / data Lane 等
- 完善函数内容(重点关注
xxx_spm_get_sensor_capbility
和xxx_spm_get_sensor_work_info
函数)
-
调整寄存器地址
- 修改
xxx_sensor.c
中的曝光、增益等寄 存器地址
- 修改
-
读 ID 测试
- 尝试上电读 ID
- 如果失败,重新检查步骤 1
-
出图测试
- 使用
single online test
测试(参阅本文里的 场景介绍 章节 - 如果失败,可使用
only viisp case
再测试 - 仍失败则检查步骤 3、4,或联系工程师分析
- 使用
-
验证 log
single online test
正常出图的 log 可参阅本文里的 实操 log 章节
备注:
关于测试应用及各 test 的介绍,可参阅本文里的 场景介绍 章节。
参考文档
-
Camera 模块规格特性
芯片规格说明书 -
ISP 效果调试
ISP PQ 工具用户指南 -
ISP API 开发
ISP API 开发指南
Camera 子系统硬件框图
核心功能模块说明:
-
SENSOR(图像传感器)
- 将从镜头传导过来的光线转换为电信号,再通过内部的 AD 转换为数字信号,最终输 出 MIPI RAW 数据。
-
CCIC(CMOS Camera Image Controller)
- 解析接收 Sensor 发送的 MIPI 数据。
-
IDI(ISP 数据输入模块)
- 接收 CCIC 发送的数据或者从 DDR 读取数据,发送到 ISP Pipeline
- 同时将 CCIC 数据转存至 DDR
-
ISP-PIPE(图像处理流水线)
- ISP 硬件内部 pipeline
- 进行一系列图像相关算法的处理
-
ISP-FORMAT(输出格式控制)
- ISP 硬件内部模块
- 控制输出的图像格式
-
ISP-DMA(直接内存访问)
- ISP 硬件内部模块
- 将图像输出到 DDR 中
-
CPP(后处理单元)
- 图像降噪处理和边缘增强。
系统数据流说明
原始光信号 → SENSOR(光电转换)→ CCIC(数据接收)→ IDI(数据路由)→ ISP-PIPE(图像处理)→ ISP-FORMAT(格式转换)→ ISP-DMA(内存写入)→ CPP(后处理)→ 最终输出
Camera 驱动框架
说明:
本章节内容仅需了解即可,实际开发中除 Sensor 驱动部分外,其他内容在 sensor bring up 时基本不会涉及。
框架简介
SpacemiT camera 驱动基于 Linux 内核 V4L2 框架实现,主要功能包括:
- 硬件寄存器访问
- 中断响应处理
- 缓冲区与事件管理
- 用户空间 SDK 接口提供
在 Camera 子系统中各个模块之间的关系如下图所示:
从软件调用关系来看,从上到下依次分为以下三层:
-
用户空间(Userspace):运行在用户空间。
- 主要包括 ISP、CPP、VI 和 tuningtools 软件库,以及 ISP、CPP 的 firmware 库和 sensor 模块的源码。
- 用户主要通过调用 ISP、CPP、VI 和 sensor 模块的接口来实现 camera 应用场景。Firmware 库中的接口由 ISP 和 CPP 软件库在内部调用。
- 另外,如果用户需要使用 ASR 的 tuning tool 来调试 ISP/CPP 的输出图像效果,需要在应用程序中调用
libtuningtools.so
的接口来创建 tuning server。
-
内核空间(Kernel Space):
- 运行在内核空间。
- 主要提供 ISP、CPP、VI、CCIC 和 sensor 的驱动。
-
硬件层(Hardware Layer):
- 这是驱动实际调用的硬件模块。
对于 sensor bring up 需求,主要关注用户空间的 APP demo 实现,即如何调用 SDK 接口满足场景需求。
源码结构
~/k1x/linux-6.6/drivers/media/platform/spacemit/camera$ tree
.
|-- built-in.a
|-- cam_ccic
| |-- ccic_drv.c
| |-- ccic_drv.h
| |-- ccic_hwreg.c
| |-- ccic_hwreg.h
| |-- csiphy.c
| |-- csiphy.h
| |-- dptc_drv.c
| |-- dptc_drv.h
| `-- dptc_pll_setting.h
|-- cam_cpp
| |-- cpp_compat_ioctl32.c
| |-- cpp_compat_ioctl32.h
| |-- cpp_dmabuf.c
| |-- cpp_dmabuf.h
| |-- cpp_iommu.c
| |-- cpp_iommu.h
| |-- cpp-v2p0.c
| |-- k1x_cpp.c
| |-- k1x_cpp.h
| |-- regs-cpp-iommu.h
| |-- regs-cpp-v2p0.h
| `-- regs-fbc-v2p0.h
|-- cam_isp
| |-- k1x_isp_drv.c
| |-- k1x_isp_drv.h
| |-- k1x_isp_pipe.c
| |-- k1x_isp_pipe.h
| |-- k1x_isp_reg.c
| |-- k1x_isp_reg.h
| |-- k1x_isp_statistic.c
| `-- k1x_isp_statistic.h
|-- cam_plat
| |-- cam_plat.c
| `-- cam_plat.h
|-- cam_sensor
| |-- cam_sensor.c
| `-- cam_sensor.h
|-- cam_util
| |-- cam_dbg.c
| `-- cam_dbg.h
|-- Kconfig
|-- Makefile
|-- modules.order
`-- vi
|-- cam_block.c
|-- cam_block.h
|-- k1xvi
| |-- fe_isp.c
| |-- fe_isp.h
| |-- hw-seq
| | |-- hw_ccic.c
| | |-- hw_ccic.h
| | |-- hw_dma.c
| | |-- hw_dma.h
| | |-- hw_iommu.c
| | |-- hw_iommu.h
| | |-- hw_isp.c
| | |-- hw_isp.h
| | |-- hw_postpipe.c
| | |-- hw_postpipe.h
| | |-- hw_reg.h
| | `-- hw_reg_iommu.h
| |-- k1xvi.c
| `-- k1xvi.h
|-- mlink.c
|-- mlink.h
|-- spacemit_videobuf2.h
|-- subdev.c
|-- subdev.h
|-- vdev.c
|-- vdev.h
|-- vsensor.c
`-- vsensor.h
驱动配置
执行以下命令进入 bianbu-linux 的内核配置
make linux-menuconfig
找到对应的宏配置,依次打开即可。
Symbol: SPACEMIT_K1X_CAMERA_V2 [=y] │
│ Type : tristate │
│ Defined at drivers/media/platform/spacemit/camera/Kconfig:8 │
│ Prompt: SPACEMIT K1X camera and video capture V2 support │
│ Depends on: MEDIA_SUPPORT [=y] && MEDIA_PLATFORM_SUPPORT [=y] && MEDIA_PLATFORM_DRIVERS [=y] │
│ Location: │
│ -> Device Drivers │
│ -> Multimedia support (MEDIA_SUPPORT [=y]) │
│ -> Media drivers │
│ -> Media platform devices (MEDIA_PLATFORM_DRIVERS [=y]) │
│ (1) -> SPACEMIT K1X camera and video capture V2 support (SPACEMIT_K1X_CAMERA_V2 [=y]) │
│ Selects: MEDIA_CONTROLLER [=y] && VIDEO_V4L2_SUBDEV_API [=y]
上面是 CONFIG_SPACEMIT_K1X_CAMERA_V2
配置的路径,选择打开之后,在把剩余的 Camera 相关配置打开即可。打开并保存完毕后,可以通过输出保存的 ./output/k1/build/linux-custom/.config
文件确认,如下所示。
#
# SPACEMIT K1X Camera And Video V2
#
CONFIG_SPACEMIT_K1X_CAMERA_V2=y
CONFIG_SPACEMIT_K1X_CCIC_V2=y
CONFIG_SPACEMIT_K1X_VI_V2=y
CONFIG_SPACEMIT_K1X_VI_IOMMU=y
CONFIG_SPACEMIT_K1X_ISP_V2=y
CONFIG_SPACEMIT_K1X_CPP_V2=y
CONFIG_SPACEMIT_K1X_SENSOR_V2=y
Sensor 驱动
Sensor 相关的驱动代码位于 linux/drivers/media/platform/spacemit/camera/cam_sensor
目录。驱动加载后会在系统中生成设备节点 /dev/cam_sensorX
,其中 X
为 sensor device ID,也就是下文提到的 camera ID。
Sensor 驱动是一个轻量级的字符驱动,主要功能包括:
- 控制 sensor 的电源(power)。
- 封装 I2C 的读写操作。
在 bring up(首次启动或初始化)过程中,需要特别注意以下两个函数:
camsnr_of_parse()
函数表明了当前支持解析的 sensor dts 节点中的属 性内容。cam_sensor_power_set()
函数内定义的 power on/off 流程是否和新 sensor 操作流程一致。
在实际操作中,需要根据具体 sensor 的硬件设计和 Datasheet 来调整上电和下电流程:
-
操作一致的情况
如果新 sensor 的上电和下电流程与现有代码一致(例如imx135
sensor),可以直接调用ioctl CAM_SENSOR_UNRESET
来完成上电操作。例如:- 在
imx135_init
函数中调用sensor_hw_unreset
函数完成上电操作。 - 在
imx135_deinit
函数中调用sensor_hw_reset
函数完成下电操作。
- 在
-
操作不一致的情况
如果新 sensor 的上电和下电流程与现有代码不一致(例如gc2375h
sensor),需要结合 sensor 字符串驱动透出的ioctl
接口,自定义上电和下电函数。例如:- 自定义
gc2375h_power_on
函数,组合出满足gc2375h
sensor 上电要求的操作。 - 自定义
gc2375h_power_off
函数,组合出满足gc2375h
sensor 下电要求的操作。
- 自定义
DTS 介绍
和 Camera 相关的 DTS 配置主要分布在以下几个文件(方案间可能会有细微差别):
路径:arch/riscv/boot/dts/spacemit/k1-x-camera-sensor.dtsi
作用:各类sensor的配置信息
路径:arch/riscv/boot/dts/spacemit/k1-x-camera-sdk.dtsi
作用:ccic、csiphy、isp、vi、cpp的配置信息
路径:arch/riscv/boot/dts/spacemit/k1-x_pinctrl.dtsi
作用:camera所依赖的pinctr1配置信息
路径:arch/riscv/boot/dts/spacemit/k1-xxxx.dts
作用:不同方案的board相关配置
Pinctrl
目前仅有 camera MCLK 引脚的配置是通过 pinctrl 进行定义的。
路径: arch/riscv/boot/dts/spacemit/k1-x_pinctrl.dtsi
pinctrl_camera0: camera0_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_53, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk0 */
>;
};
pinctrl_camera1: camera1_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_58, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk1 */
>;
};
pinctrl_camera2: camera2_grp {
pinctrl-single,pins =<
K1X_PADCONF(GPIO_120, MUX_MODE1, (EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)) /* cam_mclk2 */
>;
};
路径: arch/riscv/boot/dts/spacemit/k1-x-camera-sensor.dtsi
/* imx315 */
backsensor: cam_sensor@0 {
......
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_camera0>;
......
status = "okay";
};
GPIO
查看开发板原理图 在开发板上,MIPI CSI 接口(如 CSI0、CSI1、CSI2)通常会使用 GPIO 来控制摄像头模块的复位信号和上下电信号。通常至少会有一组 GPIO 用于这些功能。假设 MIPI CSI0 接口的复位信号 GPIO 为 GPIO 111,上下电信号 GPIO 为 GPIO 113,并且该接口连接的是 camera0:imx135 摄像头模块(建议 camera ID 和 MIPI CSI 接口编号一致)。
方案 DTS 中 backsensor 配置如下。
路径:arch/riscv/boot/dts/spacemit/k1-xxxx.dts
//为了提高效率,需要如下使用GPIO
//GPIO_111 后边的2表示连续要配置的GPIO,示例中表示GPIO_111,GPIO_112。
&pinctrl {
pinctrl-single,gpio-range = <
&range GPIO_111 2 (MUX_MODE0 | EDGE_NONE | PULL_DOWN | PAD_1V8_DS2)
>;
};
&gpio {
gpio-ranges = <
&pinctrl 111 GPIO_111 2
>;
};
/* imx315 */
&backsensor {
pwdn-gpios = <&gpio 113 0>;
reset-gpios = <&gpio 111 0>;
......
status = "okay";
};
路径:arch/riscv/boot/dts/spacemit/k1-x-camera-sensor.dtsi
//camera ID0对应imx135
&soc {
/*imx315 */
backsensor: cam sensor@0 {
cell-index =<0>;
status ="okay";
};
};
pwdn-gpios,reset-gpios 跟 sensor 模组的供电配置有关,sensor 驱动中使用这组配置完成 sensor 的上、下电和 reset 操作。不同的 sensor 模组配置可能不一样,bring up 时需要仔细修改。
Sesnor dts 配置
k1-x-camera-sensor.dtsi 内定义的 sensor 配置如下
/* imx315 */
backsensor: cam_sensor@0 {
cell-index = <0>;
twsi-index = <0>;
dphy-index = <0>;
compatible = "spacemit,cam-sensor";
clocks = <&ccu CLK_CAMM0>;
clock-names = "cam_mclk0";
/* 该部分内容已经被移动到顶层的dts
afvdd28-supply = <&ldo_12>;
avdd28-supply = <&ldo_10>;
dvdd12-supply = <&ldo_20>;
iovdd18-supply = <&ldo_11>;
cam-supply-names = "afvdd28", "avdd28", "dvdd12", "iovdd18";
*/
......
status = "okay";
};
cell-index
表示整个 sensor 所在的 device ID,这个 device ID 和上层使用的 sensor device ID 完全匹配twsi-index
表示 sensor 使用的 I2C core 的 ID,使用前要确保对应的 i2c bus dts 配置已经开启,具体请参阅 I2C 章节。dphy-index
表示 sensor 使用的 PHY ID。clocks/clock-names
表示 sensor 使用的 MCLK 的时钟源。
Bring up 总结
在 bring up 过程中,对驱动的修改主要有以下几步:
-
检查硬件平台的 CSI PHY 和 CCIC 配置
如果是硬件平台上首次 bring up 当前位置的 sensor 模组,需要检查k1-x-camera-sdk.dtsi
中定义的 csiphy 和 ccic 配置是否正确。如果不是首次 bring up,或者相关配置已经正确,可以跳过此步骤。 -
修改 sensor 配置
根据新 sensor 的需求,修改k1-x-camera-sensor.dtsi
或顶层 DTS 文件中定义的 sensor 配置,以适配新的 sensor。 -
检查并调整 sensor 驱动的电源管理流程
检查 sensor 驱动中的 power on/off 流程是否适配新的 sensor。如果不适配,可能需要修改 sensor 驱动代码,以确保电源管理流程符合新 sensor 的要求。
User 层 cam_sensors 库
在 User 层,sensor 模组的操作经过编译后会封装在 libcam_sensors
库中。该模块的源码目录位于 xxxx/package-src/k1x-cam/sensors
。
公共操作
Sensor 的公共操作代码位于 sensor
子目录内。cam_sensor.c
文件定义了所有 sensor 的通用操作,通常不需要修改。对于具体型号的 sensor,xxx_sensor.c
文件定义了该 sensor 的通用操作。
- 如果
sensor
子目录下已经存在待 bring up 的 sensor 的xxx_sensor.c
文件,则无需修改; - 如果没有,需要为新的 sensor 添加
xxx_sensor.c
文件。
操作函数集
xxx_sensor.c
文件定义了 struct SENSOR_OBJ
结构体,作为 sensor 对外的操作函数集。该结构体包含以下成员变量:
-
const char* name;
sensor 的名字。 -
int (*pfnInit)(void** pHandle, int sns_id, uint8_t sns_addr);
sensor 初始化。 -
int (*pfnDeinit)(void* handle);
sensor 清除初始化。 -
int (*pfnGlobalConfig)(void* handle, SENSOR_WORK_INFO_S* work_info);
根据输入的 work info 全局配置 sensor。 -
int (*pfnSetParam)(void* handle, const SENSOR_INIT_ATTR_S* init_attr);
在初始化前设置 sensor 的 3A 参数。 -
int (*pfnStreamOn)(void* handle);
sensor 开始数据流。 -
int (*pfnStreamOff)(void* handle);
sensor 停止数据流。 -
int (*pfnGetSensorOps)(void* handle, ISP_SENSOR_REGISTER_S* pSensorFuncOps);
获取 sensor 注册给 ISP 的回调操作函数集。 -
int (*pfnDetectSns)(void* handle, SENSOR_VENDOR_ID_S* vendor_id);
检测 sensor。 -
int (*pfnWriteReg)(void* handle, uint16_t regAddr, uint16_t value);
写寄存器。 -
int (*pfnReadReg)(void* handle, uint16_t regAddr, uint16_t* value);
读寄存器。
ISP 回调操作函数集
xxx_sensor.c
文件定义了 struct spmISP_SENSOR_FUNC_S
结构体,作为 sensor 注册给 ISP 的回调函数集。该结构体包含以下成员变量:
-
int (*pfn_sensor_write_reg)(void*snsHandle, uint32_t regAddr, uint32_t value);
写 sensor 寄存器。 -
int (*pfn_sensor_get_isp_default)(void*snsHandle, uint32_t u32ChanelId, uint32_t camScene, ISP_SENSOR_DEFAULT_S *pstDef);
获取 ISP 使用的 Tuning data 参数。 -
int (*pfn_sensor_get_isp_black_level)(void*snsHandle, uint32_t u32ChanelId, ISP_SENSOR_BLACK_LEVEL_S *pstBlackLevel);
获取 sensor 默认的 Black level。 -
int (*pfn_sensor_get_reg_info)(void*snsHandle, ISP_SENSOR_REGS_INFO_S *pstSensorRegsInfo);
获取 sensor 寄存器操作的基本配置。 -
int (*pfn_sensor_dump_info)(void*snsHandle);
在 debug 时 dump 需要的 sensor 信息。 -
int (*pfn_sensor_group_regs_start)(void*snsHandle);
sensor group writer 的 start 操作。 -
int (*pfn_sensor_group_regs_done)(void*snsHandle);
sensor group writer 的 end 操作。
ISP AE 回调操作函数集
xxx_sensor.c
文件定义了 struct spmISP_SENSOR_AE_FUNC_S
结构体作为 sensor 注册给 ISP AE 算法的回调函数集。结构体包含以下成员变量:
-
int (*pfn_sensor_get_ae_default)(void* snsHandle, uint32_t u32ChanelId, ISP_SENSOR_AE_DEFAULT_S *pstSensorAeDft);
获取 sensor 默认的 AE 参数。 -
int (*pfn_sensor_fps_set)(void* snsHandle, float f32Fps);
设置 sensor 的 fps。 -
int (*pfn_sensor_get_expotime_by_fps)(void* snsHandle, float f32Fps);
根据设置的 fps 获取 sensor 的最大曝光时间。 -
int (*pfn_sensor_expotime_update)(void* snsHandle, uint32_t u32ChanelId, uint32_t u32ExpoTime, ISP_SENSOR_VTS_INFO_S *pstSensorVtsInfo);
更新 sensor 和 exposure time 相关的寄存器值,仅更新软件的值,不实际 写寄存器。 -
int (*pfn_sensor_gain_update)(void* snsHandle, uint32_t u32ChanelId, uint32_t *pAgainVal, uint32_t *pDgainVal);
更新 sensor 和 gain 相关的寄存器值,仅更新软件的值,不实际写寄存器。 -
int (*pfn_get_aelib_default_settings)(void* snsHandle, uint32_t u32ChanelId, AE_LIB_DEFAULT_SETTING_S **ppstAeLibDefault);
获取指定 ISP pipeline AE 算法使用的 Tuning data 参数。
ISP AWB 回调操作函数集
xxx_sensor.c
文件定义了 struct spmISP_SENSOR_AWB_FUNC_S
结构体作为 sensor 注册给 ISP AWB 算法的回调函数集。结构体包含以下成员变量:
-
int (*pfn_sensor_get_awb_default)(void* snsHandle, uint32_t u32ChanelId, ISP_SENSOR_AWB_DEFAULT_S *pstSensorAwbDft);
获取 sensor 默认的 AWB 参数。 -
int (*pfn_get_awblib_default_settings)(void* snsHandle, uint32_t u32ChanelId, AWB_LIB_DEFAULT_SETTING_S **ppstAwbLibDefault);
获取指定 ISP pipeline AWB 算法使用的 Tuning data 参数。。
差异化操作
对于一个 sensor 模组来说,即使使用的是同一个 sensor,不同的模组在不同项目上可能需要很多差异化的配置,例如 sensor setting 或适配的 ISP tuning data。这些差异化的代码位于 module
子目录内。对于一个新的 sensor 模组,需要创建一个子目录来定义相关操作。