Openharmony MIPI Camera ov13850/ov13855调试1-kernel部分
1.环境
平台 : RK3588
系统版本:Openharmony 4.0 release
kernel版本:Linux-5.10
摄像头模组:OV13850,OV13855
2.kernel配置
参考文章RK3399 mipi Camera在 OpenHarmony-v3.2-Release系统上 驱动适配实践 – 文章 OpenHarmony开发者论坛
在Openharmony配置MIPI摄像头之前,建议选用linux下已配置好的摄像头模组,这样可以省去很多的麻烦。Openharmony 上面没有V4L2-ctl工具,大家可以先在Linux系统中调试好,然后将配置文件对比放入鸿蒙的系统中,注意内核版本要一致。
i2c、sensor、isp、mipi的配置,直接拷贝linux下的就可以
# dmesg | grep ov1385 [ 3.680394] ov13850 3-0010: driver version: 00.01.05 [ 3.680456] ov13850 3-0010: Failed to get power-gpios, maybe no use [ 3.680511] ov13850 3-0010: Looking up avdd-supply from device tree [ 3.680528] ov13850 3-0010: Looking up avdd-supply property in node /i2c@feab0000/ov13850-1@10 failed [ 3.680563] ov13850 3-0010: supply avdd not found, using dummy regulator [ 3.680680] ov13850 3-0010: Looking up dovdd-supply from device tree [ 3.680694] ov13850 3-0010: Looking up dovdd-supply property in node /i2c@feab0000/ov13850-1@10 failed [ 3.680714] ov13850 3-0010: supply dovdd not found, using dummy regulator [ 3.680755] ov13850 3-0010: Looking up dvdd-supply from device tree [ 3.680767] ov13850 3-0010: Looking up dvdd-supply property in node /i2c@feab0000/ov13850-1@10 failed [ 3.680785] ov13850 3-0010: supply dvdd not found, using dummy regulator [ 3.680829] ov13850 3-0010: could not get default pinstate [ 3.680839] ov13850 3-0010: could not get sleep pinstate [ 3.685734] ov13850 3-0010: Detected OV00d850 sensor, REVISION 0xb2 [ 3.685844] rockchip-csi2-dphy csi2-dphy0: dphy0 matches m00_b_ov13850 3-0010:bus type 5 [ 3.686088] ov13855 3-0036: Error applying setting, reverse things back [ 3.686106] ov13855: probe of 3-0036 failed with error -22
开机抓取内核打印,看到Detected OV00d850 sensor, REVISION 0xb2,说明驱动已经正常运行了。
接下来需要检查下/dev/video,/dev/v4l-subdevx,/dev/medie节点有没有生成,linux下跑通后,openharmony照搬这些都不会有问题。但是这个时候打开相机是黑的,下面我们要去确认一下openharmony下camera kernel部分是否运行正常。
3.Openharmony V4L2抓图
参考文章:Camera | 4.瑞芯微平台MIPI摄像头应用程序编写-CSDN博客
openharmony下没有v4l2-ctl,但是有ohos_camera_demo和V4L2_main两个工具可以使用。正常编译系统这两个工具不会生成。需要编译test或者指定模块编译,指定模块编译指令如下:
./build.sh --product-name {productname} --ccache --no-prebuilt-sdk --build-target v4l2_main ./build.sh --product-name {productname} --ccache --no-prebuilt-sdk --build-target ohos_camera_demo
ohos_camera_demo编译过程中遇到报错,v4l2_main使用lldb调试发现很多问题,修改调试V4L2_main的时间不如自己写v4l2小程序时间更快一些。关于V4L2 demo网上有很多例子,下面是两个小例子,可以在鸿蒙下运行使用。
3.1 cameraInfo.c
//cameraInfo.c #include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <linux/videodev2.h> int main(int argc, char *argv[]) { if(argc < 2){ printf("this process arg error\n"); return -1; } printf("camera device:%s\n", argv[1]); int cam_fd = open(argv[1], O_RDWR); if(cam_fd < 0){ printf("open the camera device:%s is error: %s\n", argv[1], strerror(errno)); return -2; } struct v4l2_capability cap; if(ioctl(cam_fd, VIDIOC_QUERYCAP, &cap) == -1){ printf("Unable query the ability of the device %s\n ", argv[1]); return -3; } printf("driver:\t%s\n", cap.driver); printf("card:\t%s\n", cap.card); printf("bus_info:\t%s\n", cap.bus_info); printf("version:\t%d\n", cap.version); printf("capability:\t%x\n", cap.capabilities); if((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE){ printf("the device %s is support capture\n", argv[1]); }else{ printf("the device %s is not support capture\n", argv[1]); } if((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING){ printf("the device %s is support streaming\n", argv[1]); }else{ printf("the device %s is not support streaming\n", argv[1]); } printf("device_caps:\t%x\n", cap.device_caps); close(cam_fd); return 0; }
运行结果
# ./cameraInfo /dev/video11 camera device:/dev/video11 driver: rkisp_v6 card: rkisp_mainpath bus_info: platform:rkisp0-vir1 version: 131585 capability: 84201000 the device /dev/video11 is not support capture the device /dev/video11 is support streaming device_caps: 4201000
从上面的打印信息可以看出 13850不支持V4L2_CAP_VIDEO_CAPTURE,v4l2_main里面的预览抓图用的是V4L2_CAP_VIDEO_CAPTURE所以会失败。
3.2 capture.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/time.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <asm/types.h> #include <linux/videodev2.h> #include <linux/v4l2-subdev.h> #define FMT_NUM_PLANES 1 struct buffer { void *start; size_t length; }; struct v4l2_dev { int fd; int sub_fd; const char *path; const char *name; const char *subdev_path; const char *out_type; enum v4l2_buf_type buf_type; int format; int width; int height; unsigned int req_count; enum v4l2_memory memory_type; struct buffer *buffers; unsigned long int timestamp; int data_len; unsigned char *out_data; }; struct v4l2_dev ov13850 = { //此处需要换成自己摄像头的信息 .fd = -1, .sub_fd = -1, .path = "/dev/video11", .name = "ov13850", .subdev_path = "/dev/v4l-subdev3", .out_type = "nv12", .buf_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, //选用摄像头支持的格式 .format = V4L2_PIX_FMT_NV12, .width = 800, .height = 600, .req_count = 4, .memory_type = V4L2_MEMORY_MMAP, .buffers = NULL, .timestamp = 0, .data_len = 0, .out_data = NULL, }; void close_device(struct v4l2_dev *dev) { if (dev->buffers) { for (unsigned int i = 0; i < dev->req_count; ++i) { if (dev->buffers[i].start) { munmap(dev->buffers[i].start, dev->buffers[i].length); } } free(dev->buffers); } if (-1 != dev->fd) { close(dev->fd); } if (-1 != dev->sub_fd) { close(dev->sub_fd); } return; } void exit_failure(struct v4l2_dev *dev) { close_device(dev); exit(EXIT_FAILURE); } void open_device(struct v4l2_dev *dev) { dev->fd = open(dev->path, O_RDWR | O_CLOEXEC, 0); if (dev->fd < 0) { printf("Cannot open %s\n\n", dev->path); exit_failure(dev); } printf("Open %s succeed - %d\n\n", dev->path, dev->fd); dev->sub_fd = open(dev->subdev_path, O_RDWR|O_CLOEXEC, 0); if (dev->sub_fd < 0) { printf("Cannot open %s\n\n", dev->subdev_path); exit_failure(dev); } printf("Open %s succeed\n\n", dev->subdev_path); return; } void get_capabilities(struct v4l2_dev *dev) { struct v4l2_capability cap; if (ioctl(dev->fd, VIDIOC_QUERYCAP, &cap) < 0) { printf("VIDIOC_QUERYCAP failed\n"); return; } printf("------- VIDIOC_QUERYCAP ----\n"); printf(" driver: %s\n", cap.driver); printf(" card: %s\n", cap.card); printf(" bus_info: %s\n", cap.bus_info); printf(" version: %d.%d.%d\n", (cap.version >> 16) & 0xff, (cap.version >> 8) & 0xff, (cap.version & 0xff)); printf(" capabilities: %08X\n", cap.capabilities); if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) printf(" Video Capture\n"); if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT) printf(" Video Output\n"); if (cap.capabilities & V4L2_CAP_VIDEO_OVERLAY) printf(" Video Overly\n"); if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) printf(" Video Capture Mplane\n"); if (cap.capabilities & V4L2_CAP_VIDEO_OUTPUT_MPLANE) printf(" Video Output Mplane\n"); if (cap.capabilities & V4L2_CAP_READWRITE) printf(" Read / Write\n"); if (cap.capabilities & V4L2_CAP_STREAMING) printf(" Streaming\n"); printf("\n"); return; } void set_fmt(struct v4l2_dev *dev) { struct v4l2_format fmt; memset(&fmt, 0, sizeof(fmt)); fmt.type = dev->buf_type; fmt.fmt.pix.pixelformat = dev->format; fmt.fmt.pix.width = dev->width; fmt.fmt.pix.height = dev->height; if (ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0) { printf("VIDIOC_S_FMT failed - [%d]!\n", errno); exit_failure(dev); } printf("VIDIOC_S_FMT succeed!\n"); dev->data_len = fmt.fmt.pix.sizeimage; printf("width %d, height %d, size %d, bytesperline %d, format %c%c%c%c\n\n", fmt.fmt.pix.width, fmt.fmt.pix.height, dev->data_len, fmt.fmt.pix.bytesperline, fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF, (fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF); return; } void require_buf(struct v4l2_dev *dev) { // 申请缓冲区 struct v4l2_requestbuffers req; memset(&req, 0, sizeof(req)); req.count = dev->req_count; req.type = dev->buf_type; req.memory = dev->memory_type; if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) == -1) { printf("VIDIOC_REQBUFS failed!\n\n"); exit_failure(dev); } if (dev->req_count != req.count) { printf("!!! req count = %d\n", req.count); dev->req_count = req.count; } printf("VIDIOC_REQBUFS succeed!\n\n"); return; } void alloc_buf(struct v4l2_dev *dev) { dev->buffers = (struct buffer *)calloc(dev->req_count, sizeof(*(dev->buffers))); for (unsigned int i = 0; i < dev->req_count; ++i) { struct v4l2_buffer buf; struct v4l2_plane planes[FMT_NUM_PLANES]; memset(&buf, 0, sizeof(buf)); memset(&planes, 0, sizeof(planes)); buf.type = dev->buf_type; buf.memory = dev->memory_type; buf.index = i; if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == dev->buf_type) { buf.m.planes = planes; buf.length = FMT_NUM_PLANES; } if (ioctl(dev->fd, VIDIOC_QUERYBUF, &buf) == -1) { printf("VIDIOC_QUERYBUF failed!\n\n"); exit_failure(dev); } if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == dev->buf_type) { dev->buffers[i].length = buf.m.planes[0].length; dev->buffers[i].start = mmap(NULL /* start anywhere */, buf.m.planes[0].length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, dev->fd, buf.m.planes[0].m.mem_offset); } else { dev->buffers[i].length = buf.length; dev->buffers[i].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, dev->fd, buf.m.offset); } if (dev->buffers[i].start == MAP_FAILED) { printf("Memory map failed!\n\n"); exit_failure(dev); } } printf("Memory map succeed!\n\n"); return; } void queue_buf(struct v4l2_dev *dev) { for (unsigned int i = 0; i < dev->req_count; ++i) { struct v4l2_buffer buf; struct v4l2_plane planes[FMT_NUM_PLANES]; memset(&buf, 0, sizeof(buf)); memset(&planes, 0, sizeof(planes)); buf.type = dev->buf_type; buf.memory = dev->memory_type; buf.index = i; if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == dev->buf_type) { buf.m.planes = planes; buf.length = FMT_NUM_PLANES; } if (ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0) { printf("VIDIOC_QBUF failed!\n\n"); exit_failure(dev); } } printf("VIDIOC_QBUF succeed!\n\n"); return; } void stream_on(struct v4l2_dev *dev) { enum v4l2_buf_type type = dev->buf_type; if (ioctl(dev->fd, VIDIOC_STREAMON, &type) == -1) { printf("VIDIOC_STREAMON failed!\n\n"); exit_failure(dev); } printf("VIDIOC_STREAMON succeed!\n\n"); return; } void get_frame(struct v4l2_dev *dev, int skip_frame) { struct v4l2_buffer buf; struct v4l2_plane planes[FMT_NUM_PLANES]; for (int i = 0; i <= skip_frame; ++i) { memset(&buf, 0, sizeof(buf)); buf.type = dev->buf_type; buf.memory = dev->memory_type; if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == dev->buf_type) { buf.m.planes = planes; buf.length = FMT_NUM_PLANES; } if (ioctl(dev->fd, VIDIOC_DQBUF, &buf) == -1) { printf("VIDIOC_DQBUF failed!\n\n"); exit_failure(dev); } dev->out_data = (unsigned char *)dev->buffers[buf.index].start; dev->timestamp = buf.timestamp.tv_sec * 1000000 + buf.timestamp.tv_usec; printf("image: sequence = %d, timestamp = %lu\n", buf.sequence, dev->timestamp); if (ioctl(dev->fd, VIDIOC_QBUF, &buf) == -1) { printf("VIDIOC_QBUF failed!\n"); exit_failure(dev); } } return; } void save_picture(const char *filename, unsigned char *file_data, unsigned int len, int is_overwrite) { FILE *fp; if (is_overwrite) fp = fopen(filename, "wb"); else fp = fopen(filename, "ab"); if (fp < 0) { printf("Open frame data file failed\n\n"); return; } if (fwrite(file_data, 1, len, fp) < len) { printf("Out of memory!\n"); //is_running = 0; } fflush(fp); fclose(fp); printf("Save one frame to %s succeed!\n\n", filename); return; } void stream_off(struct v4l2_dev *dev) { enum v4l2_buf_type type; type = dev->buf_type; if (ioctl(dev->fd, VIDIOC_STREAMOFF, &type) == -1) { printf("VIDIOC_STREAMOFF failed!\n\n"); exit_failure(dev); } printf("VIDIOC_STREAMOFF succeed!\n\n"); return; } void set_fps(struct v4l2_dev *dev, unsigned int fps) { int ret; struct v4l2_subdev_frame_interval frame_int; if (fps == 0) return; memset(&frame_int, 0x00, sizeof(frame_int)); frame_int.interval.numerator = 10000; frame_int.interval.denominator = fps * 10000; ret = ioctl(dev->sub_fd, VIDIOC_SUBDEV_S_FRAME_INTERVAL, &frame_int); if (ret < 0) { printf("VIDIOC_SUBDEV_S_FRAME_INTERVAL error\n"); goto set_fps_err; } printf("VIDIOC_SUBDEV_S_FRAME_INTERVAL [%u fps] OK\n", fps); set_fps_err: return; } int main(int argc, char *argv[]) { struct v4l2_dev *camdev = &ov13850; unsigned int fps = 0; unsigned int mode = 4; char name[40] = {0}; if (argc > 1) fps = atoi(argv[1]); open_device(camdev); // 1 打开摄像头设备 get_capabilities(camdev); set_fmt(camdev); // 2 设置出图格式 require_buf(camdev); // 3 申请缓冲区 alloc_buf(camdev); // 4 内存映射 queue_buf(camdev); // 5 将缓存帧加入队列 set_fps(camdev, fps); stream_on(camdev); // 6 开启视频流 get_frame(camdev, 9); // 7 取一帧数据 snprintf(name, sizeof(name), "/data/%s.%s", camdev->name, camdev->out_type); save_picture(name, camdev->out_data, camdev->data_len, 1); stream_off(camdev); // 8 关闭视频流 close_device(camdev); // 9 释放内存关闭文件 return 0; }
运行结果:
# ./capture Open /dev/video11 succeed - 3 Open /dev/v4l-subdev3 succeed ------- VIDIOC_QUERYCAP ---- driver: rkisp_v6 card: rkisp_mainpath bus_info: platform:rkisp0-vir1 version: 2.2.1 capabilities: 84201000 Video Capture Mplane Streaming VIDIOC_S_FMT succeed! width 800, height 600, size 720000, bytesperline 0, format NV12 VIDIOC_REQBUFS succeed! Memory map succeed! VIDIOC_QBUF succeed! VIDIOC_STREAMON succeed! image: sequence = 1, timestamp = 4725017196 image: sequence = 2, timestamp = 4725116985 image: sequence = 3, timestamp = 4725183467 image: sequence = 4, timestamp = 4725250146 image: sequence = 5, timestamp = 4725316665 image: sequence = 6, timestamp = 4725383248 image: sequence = 7, timestamp = 4725449836 image: sequence = 8, timestamp = 4725516407 image: sequence = 9, timestamp = 4725582957 image: sequence = 10, timestamp = 4725649521 Save one frame to /data/ov13850.nv12 succeed! VIDIOC_STREAMOFF succeed!
struct v4l2_dev ov13850里面的信息很重要,填错了会导致抓图失败,如果误以为是kernel v4l2存在问题会浪费很多时间,所以在linux下使用v4l2-ctl拿到摄像头的关键信息,对于调试帮助很大,当然你也可以编写类似的小程序去获取。buf_type 和format 一定要写与摄像头对应的值。抓图成功后将/data/ov13850.nv12提取出来。使用7yuv工具查看。
不出意外是这种黑乎乎的图片,这说明抓图成功了,之所以这么黑是因为ispserver没有运行起来或者没有这款摄像头的配置文件。解决这个问题的方法放在后面的文章,你觉得画面太暗可以调一下驱动的默认值
diff --git a/drivers/media/i2c/ov13850.c b/drivers/media/i2c/ov13850.c index d060c6e3f..7d3060ab1 100644 --- a/drivers/media/i2c/ov13850.c +++ b/drivers/media/i2c/ov13850.c @@ -60,7 +60,7 @@ #define OV13850_GAIN_MIN 0x10 #define OV13850_GAIN_MAX 0xf8 #define OV13850_GAIN_STEP 1 -#define OV13850_GAIN_DEFAULT 0x10 +#define OV13850_GAIN_DEFAULT 0xf8 //调增到最大 #define OV13850_REG_TEST_PATTERN 0x5e00 #define OV13850_TEST_PATTERN_ENABLE 0x80 @@ -292,7 +292,7 @@ static const struct regval ov13850_global_regs_r1a[] = { {0x4d04, 0x66}, {0x4d05, 0x65}, {0x5000, 0x0e}, - {0x5001, 0x01}, + {0x5001, 0x02}, //BLC disable {0x5002, 0x07}, {0x5013, 0x40}, {0x501c, 0x00}, @@ -523,7 +523,7 @@ static const struct regval ov13850_global_regs_r2a[] = { {0x4d05, 0x65}, {0x4d0b, 0x00}, {0x5000, 0x0e}, - {0x5001, 0x01}, + {0x5001, 0x02}, //BLC DISALBE {0x5002, 0x07},
然后再抓图就会是下面这样。虽然目前打开相机应用还是黑屏,但是capture已经抓到图,内核已经没有问题了,后面就需要调试camera框架层的东西了。