本文共 13120 字,大约阅读时间需要 43 分钟。
在使用嵌入式linux设备做点到点之间的图像传输,比如linux平台采集摄像头数据,然后通过wifi或是蓝牙等设备将图像数据发送到手机端,最后使用手机显示出图像。图像处理和图像传输是在应用层完成,在应用层写代码和调试代码都是比较容易的。但是如果需要调试摄像头驱动的一些参数,涉及到驱动层的东西就会比较的麻烦。在我使用的这个平台,假如我要调试摄像头驱动gc0308的寄存器,它需要每改一次寄存器值然后重现烧如一次固件,最后再看图像效果,这样的调试方法是在会让人奔溃。
其实,在linux的驱动中,它已经提供了一个调试v4l2 设备的接口。在linux源代码中,我们可以看到如下的定义:
struct v4l2_subdev_core_ops { int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip); int (*log_status)(struct v4l2_subdev *sd); int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n, struct v4l2_subdev_io_pin_config *pincfg); int (*init)(struct v4l2_subdev *sd, u32 val); int (*load_fw)(struct v4l2_subdev *sd); int (*reset)(struct v4l2_subdev *sd, u32 val); int (*s_gpio)(struct v4l2_subdev *sd, u32 val); int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc); int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl); int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl); int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls); int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls); int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls); int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm); int (*g_std)(struct v4l2_subdev *sd, v4l2_std_id *norm); int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm); long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);#ifdef CONFIG_VIDEO_ADV_DEBUG int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg); int (*s_register)(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg);#endif int (*s_power)(struct v4l2_subdev *sd, int on); int (*interrupt_service_routine)(struct v4l2_subdev *sd, u32 status, bool *handled); int (*subscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub); int (*unsubscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub);};
在v4l2_subdev_core_ops中已经为我们提供了直接操作v4l2 设备寄存器的接口,只需要我们定义了宏CONFIG_VIDEO_ADV_DEBUG 就可以了。下面就直接介绍该如何配置和设计在线调试。
(1)打开CONFIG_VIDEO_ADV_DEBUG 宏,该宏直接在linux的配置文件中配置就可以了,入下图:
使能debug选项就可以了。
(2)驱动设备添加操作接口
在初始化struct v4l2_subdev_core_ops 结构体的时候,添加一个直接读寄存器和一个写寄存器的接口,如下:
static const struct v4l2_subdev_core_ops sensor_core_ops = { .g_chip_ident = sensor_g_chip_ident, .g_ctrl = sensor_g_ctrl, .s_ctrl = sensor_s_ctrl, .queryctrl = sensor_queryctrl, .reset = sensor_reset, .init = sensor_init, .s_power = sensor_power, .ioctl = sensor_ioctl,#ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = sensor_g_register, .s_register = sensor_s_register,#endif};
sensor_g_register 是读取寄存器的值,sensor_s_register 是写入寄存器的值,这两个函数的定义如下:
#ifdef CONFIG_VIDEO_ADV_DEBUGstatic int sensor_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *para){ int ret; unsigned short reg_addr = 0; unsigned short reg_value = 0; reg_addr = para->size; ret = sensor_read(sd,reg_addr,®_value); if(ret < 0) { printk("sensor_g_register ret=%d; reg_addr=%d; reg_value=%d\n ",ret, reg_addr, reg_value); return ret; } return reg_value;}static int sensor_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *para){ int ret; unsigned short reg_addr; unsigned short reg_val; __u32 size; __u64 reg; size = para->size; reg = para->reg; reg_addr = (unsigned short)size; reg_val = (unsigned short)reg; ret = sensor_write(sd, reg_addr, reg_val); if(ret < 0) { printk("sensor_s_register ret = %d, reg_addr=%d, reg_val=%d\n",ret,reg_addr,reg_val); } return ret;}#endif
上面的代码需要注意,在数据类型中struct v4l2_dbg_register 除了设备配对之外,它定义了三个变量,size,reg,val 。在我的平台上val 的值是怎么都传输不到这里,还有一个问题就是在内核的参数不能够返回到应用层,具体的原因没有去做更深入的分析。在这里,为了实现可以调试的功能,我使用了一种投机取巧的方法来实现。我使用size 参数来传递地址,使用reg参数来传递需要设置的寄存器的值,最后使用函数的返回值来实现读取的寄存器值返回到应用层。
(3)应用层直接操作寄存器
下面写了一个测试小程序,通过ioctl 直接访问上面驱动中实现的接口
/*=============================================================================# FileName: test.c# Desc: ioctl to set/get vedio driver register# Author: licaibiao# Version: =============================================================================*/#include#include #include #include #include #include #include #define FILE_VIDEO "/dev/video0"int open_camera(void){ int fd; struct v4l2_input inp; fd = open(FILE_VIDEO, O_RDWR | O_NONBLOCK,0); if(fd < 0) { fprintf(stderr, "%s open err \n", FILE_VIDEO); exit(EXIT_FAILURE); }; inp.index = 0; if (-1 == ioctl (fd, VIDIOC_S_INPUT, &inp)) { fprintf(stderr, "VIDIOC_S_INPUT \n"); } return fd;}//dbg.match.type = V4L2_CHIP_MATCH_I2C_DRIVER;//strcpy(dbg.match.name,"gc0308");//dbg.match.type = V4L2_CHIP_MATCH_I2C_ADDR;//dbg.match.addr = 0x21;void v4l2_write_reg(int fd, int reg_addr, int reg_val){ int ret; struct v4l2_dbg_register dbg; dbg.match.type = 4; dbg.match.addr = 1; dbg.size = (__u32)reg_addr; dbg.reg = (__u64)reg_val; dbg.val = 0; ret = ioctl(fd, VIDIOC_DBG_S_REGISTER, &dbg); if(ret < 0) { printf("sensor IOCTL data ERR ret = %d\n",ret); }}int v4l2_read_reg(int fd, int reg_addr){ int ret; struct v4l2_dbg_register dbg; dbg.match.type = 4; dbg.match.addr = 1; dbg.size = (__u32)reg_addr; dbg.reg = 0; dbg.val = 0; ret = ioctl(fd, VIDIOC_DBG_G_REGISTER, &dbg); if(ret < 0) { printf("sensor IOCTL data ERR reg_addr= %x ret = %d\n",reg_addr,ret); } return ret;}void main(void){ int fd; int reg_addr= 0; int reg_val = 0; fd = open_camera(); printf("fd = %d\n ",fd); reg_addr = 0x00; reg_val = v4l2_read_reg(fd,reg_addr); printf(" v4l2 read reg %x value = %x\n",reg_addr,reg_val); reg_addr = 0x0f; reg_val = v4l2_read_reg(fd,reg_addr); printf(" v4l2 read reg %x value = %x\n",reg_addr,reg_val); reg_addr = 0x0f; reg_val = 0x00; v4l2_write_reg(fd, reg_addr, reg_val); usleep(10000); reg_addr = 0x0f; reg_val = v4l2_read_reg(fd,reg_addr); printf(" v4l2 read reg %x value = %x\n",reg_addr,reg_val); close(fd);}
上面的代码需要注意一下的是struct v4l2_dbg_register 的初始化。我的是linux3.10.65 版本,在videodev2.h中有如下的定义:
/* VIDIOC_DBG_G_REGISTER and VIDIOC_DBG_S_REGISTER */#define V4L2_CHIP_MATCH_BRIDGE 0 /* Match against chip ID on the bridge (0 for the bridge) */#define V4L2_CHIP_MATCH_HOST V4L2_CHIP_MATCH_BRIDGE#define V4L2_CHIP_MATCH_I2C_DRIVER 1 /* Match against I2C driver name */#define V4L2_CHIP_MATCH_I2C_ADDR 2 /* Match against I2C 7-bit address */#define V4L2_CHIP_MATCH_AC97 3 /* Match against anciliary AC97 chip */#define V4L2_CHIP_MATCH_SUBDEV 4 /* Match against subdev index */struct v4l2_dbg_match { __u32 type; /* Match type */ union { /* Match this chip, meaning determined by type */ __u32 addr; char name[32]; };} __attribute__ ((packed));struct v4l2_dbg_register { struct v4l2_dbg_match match; __u32 size; /* register size in bytes */ __u64 reg; __u64 val;} __attribute__ ((packed));
它这里需要做设备的匹配,在我的平台,我分别使用过chip ID,I2C driver name,I2C address来匹配,都没能实现设备的匹配匹配,只能使用V4L2_CHIP_MATCH_SUBDEV 来匹配,但是在我的交叉编译工具链中,前面三种方法它都定义了,唯独V4L2_CHIP_MATCH_SUBDEV没有定义,所以在上面的测试程序中我直接将 dbg.match.type = 4;
测试小程序的执行结果如下:
/tmp # ./test v4l2 read reg 0 value = 9b v4l2 read reg f value = 10 v4l2 read reg f value = 0/tmp # /tmp #
(4)将测试程序移植到图像传输工程中
实现思路是这样的,在图像传输的进程中,创建一个线程,在线程中创建一个消息队列,用来接收操作指令。另外再写一个小程序,往消息队列中发送操作指令,这样就可以实时的看到图像的效果。接收端的程序代码如下:
/********************************************************************** licaibiao add interface to debug v4l2 camera*********************************************************************/void v4l2_write_reg(int fd, int reg_addr, int reg_val){ int ret; struct v4l2_dbg_register dbg; dbg.match.type = 4; dbg.match.addr = 1; dbg.size = (__u32)reg_addr; dbg.reg = (__u64)reg_val; dbg.val = 0; ret = ioctl(fd, VIDIOC_DBG_S_REGISTER, &dbg); if(ret < 0) { printf("!!!!! sensor write data ERR !!!!! ret = %d\n",ret); } else { printf("write register 0x%x value = 0x%x \n", reg_addr, reg_val); } } void v4l2_read_reg(int fd, int reg_addr) { int ret; struct v4l2_dbg_register dbg; dbg.match.type = 4; dbg.match.addr = 1; dbg.size = (__u32)reg_addr; dbg.reg = 0; dbg.val = 0; ret = ioctl(fd, VIDIOC_DBG_G_REGISTER, &dbg); if(ret < 0) { printf("sensor IOCTL data ERR reg_addr= %x ret = %d\n",reg_addr,ret); } else { printf("read register 0x%x value = 0x%x \n", reg_addr, ret); } } struct msg_st { long int msg_type; char text[BUFSIZ]; }; void *main_v4l2_debug_camera(void* arg) { int running = 1; int msgid = -1; int reg_addr = 0; int reg_val = 0; int len = 0; int i = 0; int fd; struct msg_st data; long int msgtype = 0; fd = *(int*)arg; printf("\n\n\n enter main_v4l2_debug_camera thread \n\n\n"); msgid = msgget((key_t)1234, 0666 | IPC_CREAT); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); //exit(EXIT_FAILURE); } while(running) { len = msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0); if(len < 0) { fprintf(stderr, "msgrcv failed with errno: %d\n", errno); if(msgctl(msgid, IPC_RMID, 0)) { printf("remove msg ERR \n "); } pthread_exit(0); //exit(EXIT_FAILURE); }else { if(strncmp(data.text, "end", 3) == 0) { running = 0; if(msgctl(msgid, IPC_RMID, 0)) { printf("remove msg ERR \n "); } pthread_exit(0); } for(i=1; i= '0')&&(data.text[i] <= '9')) ||((data.text[i] >= 'A')&&(data.text[i] <= 'F')) ||((data.text[i] >= 'a')&&(data.text[i] <= 'f')) ||(data.text[i] == ' ')) { }else { printf("1plase enter: read/write: reg_addr: reg_value e.g. w 12 34 \n"); goto err; } } if((data.text[0]=='W')||(data.text[0]=='w')) { if((data.text[1] != ' ')||(data.text[4] != ' ')) { printf("2plase enter: read/write: reg_addr: reg_value e.g. w 12 34 \n"); goto err; } sscanf(&data.text[2], "%x", ®_addr); sscanf(&data.text[5], "%x", ®_val); //printf("write: addr = %x val = %x\n",reg_addr, reg_val); v4l2_write_reg(fd, reg_addr,reg_val); } if((data.text[0]=='R')||(data.text[0]=='r')) { if(data.text[1] != ' ') { printf("3plase enter: read/write: reg_addr: reg_value e.g. w 12 34 \n"); goto err; } sscanf(&data.text[2], "%x", ®_addr); //printf("read : addr = %x \n",reg_addr); v4l2_read_reg(fd, reg_addr); } } err: i = i; } } /********************************************************************/
添加线程:
if(pthread_create(&V4L2_Contect->msg_id, NULL, main_v4l2_debug_camera, &V4L2_Contect->mCamFd)){ printf("V4L2 pthread create ERR !!!!\n");}发送消息队列端的代码实现如下:
/*=============================================================================# FileName: msg_test.c# Desc: send message to camera process to ioctl v4l2# Author: Licaibiao# Version: # LastChange: 2017-01-20 # History:=============================================================================*/#include#include #include #include #include #include #define MAX_TEXT 512struct msg_st{ long int msg_type; char text[MAX_TEXT];};int main(){ int running = 1; struct msg_st data; char buffer[BUFSIZ]; int msgid = -1; int len; msgid = msgget((key_t)1234, 0666 | IPC_CREAT); if(msgid == -1) { fprintf(stderr, "msgget failed with error: %d\n", errno); exit(EXIT_FAILURE); } printf("Enter cmd format,R/W,addr,val: r/w 12 33 \n"); while(running) { printf("Enter cmd : "); fgets(buffer, BUFSIZ, stdin); data.msg_type = 1; strcpy(data.text, buffer); len = strlen(data.text); if(msgsnd(msgid, (void*)&data, len, 0) == -1) { fprintf(stderr, "msgsnd failed\n"); exit(EXIT_FAILURE); } if(strncmp(buffer, "end", 3) == 0) running = 0; usleep(100000); } exit(EXIT_SUCCESS);}
运行结果如下:
/tmp # ./send Enter cmd format,R/W,addr,val: r/w 12 33 Enter cmd : r 14read register 0x14 value = 0x10 Enter cmd : w 14 13write register 0x14 value = 0x13 Enter cmd : endmsgrcv failed with errno: 43
从上面的结果中,可以看出寄存器的读取和设置已经正常,但是在我的平台中执行:msgctl(msgid, IPC_RMID, 0) 删除消息队列的时候会出现问题,导致不能正常退出,该问题后续有时间再定位。
综上,已经可以实现V4L2 点对点传输图像的在线调试了,还遗留几个问题后续定位。
(一)struct v4l2_dbg_register 中的val变量值不能从应用层传输到驱动层。
(二)struct v4l2_dbg_register 中的变量值不能从驱动层反馈回应用层。
(三)应用层结束应用时删除消息队列出错。
这就是最近在弄的调试接口,记录下来方便大家参考,同时对于遗留问题,如果有哪位朋友有解决方案,欢迎一起讨论。
2017.01.20
转载地址:https://caibiao-lee.blog.csdn.net/article/details/54630815 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!