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框架层的东西了。

发表回复 0

Your email address will not be published. Required fields are marked *