JZ2440 摄像头驱动之设置属性_学习笔记
1、引言
(1)之前的程序只实现了数据的传输,在pc linux环境下智能看到摄像 头捕抓的数据,不能调节亮度等属性。
卸载自己写的驱动程序,安装系统自带的驱动,执行xawtv,可以查看可以调节的属性
2、设置属性
(1)应用程序xawtv部分分析
1. 先看APP以确定需要实现哪些接口
xawtv.c:
grabber_scan
ng_vid_open//对ng_vid_drivers链表的每一个成员都取出来,调用其open函数
v4l2_driver.open //v4l2_driver结构体的 v4l2_open函数
get_device_capabilities(h);
// 调用VIDIOC_QUERYCTRL ioctl确定是否支持某个属性
/* controls */对于0~MAX_CTRL,先设置ctrl的ID,调用ioctl看看驱动程序是否支持对应ID的属性,如果支持就把此属性记录下来,如果不支持吧ID改为-1,
for (i = 0; i < MAX_CTRL; i++) {
h->ctl[i].id = V4L2_CID_BASE+i;
if (-1 == xioctl(h->fd, VIDIOC_QUERYCTRL, &h->ctl[i], EINVAL) ||
(h->ctl[i].flags & V4L2_CTRL_FLAG_DISABLED))
h->ctl[i].id = -1;
}
怎么去获得/设置属性?
看drv0-v4l2.c
可见这2个函数:
v4l2_read_attr : VIDIOC_G_CTRL
v4l2_write_attr : VIDIOC_S_CTRL
所以: 视频驱动里要实现3个ioctl:
VIDIOC_QUERYCTRL//查询是否支持此属性
VIDIOC_G_CTRL //获得属性
VIDIOC_S_CTRL//设置属性
(2)底层驱动分析
/* 查询/获得/设置属性 */
.vidioc_queryctrl = myuvc_vidioc_queryctrl,
.vidioc_g_ctrl = myuvc_vidioc_g_ctrl,
.vidioc_s_ctrl = myuvc_vidioc_s_ctrl,
}
USB摄像头的内部结构回顾
videostreaming interface用于接收视频数据,设置属性应该操作videocontrl interface,里面有很多个单元,这些单元在代码里称为实体。
在uvc规范中(uvc1.5 class specification.pdf),找到processing Uint Descriptor,里面的bmcontrols的每一位对应一种属性
2. 硬件上怎么设置属性?
2.1 UVC规范里定义了哪些属性(参考上面内容)
: uvc_ctrl.c里数组: static struct uvc_control_info uvc_ctrls[]
//每一项对应一个属性
{
.entity= UVC_GUID_UVC_PROCESSING,// 属于哪了个entity(比如PU),我们要去设置亮度,需要把数据发给硬件,发给硬件上的videoctrol
interface的哪一个entity.把数据发给PU ,怎么知道数据用于设置PU的哪一个属性
.selector= PU_BRIGHTNESS_CONTROL, //
用于亮度(用于判别选择PU的某个属性)
.index= 0, // 对应Processing Unit Descriptor的bmControls[0]
.size= 2, // 数据长度为2字节
.flags= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
| UVC_CONTROL_RESTORE,//SET_CUR表示设置当前值(亮度),
},
2.2 我们的设备支持哪些属性
这需要去看描述符, 比如 Processing Unit Descriptor的bmControls的值为7f 14
可知BIT0为1,表示支持BRIGHTNESS
在代码里:
uvc_drvier.c
uvc_ctrl_init_device
// 对于每一个entity(IT,PU,SU,OT等)
list_for_each_entry(entity, &dev->entities, list) {
// 取出bmControls
bmControls = ....
// 计算bmControls里位值为1的个数,就是支持的属性个数
ncontrols += hweight8(bmControls[i]);
// 为每一个属性分配一个struct uvc_control
entity->controls = kzalloc..
// 设置这些struct uvc_control
ctrl = entity->controls;
for (...)
{
ctrl->entity = entity;
ctrl->index = i;
}
// 把uvc_control和uvc_control_info(数组)挂构
uvc_ctrl_add_ctrl(dev, info);
ctrl->info =
某个uvc_control_info数组项(同属于一个entity, index相同)
2.3 怎么去操作这些属性
参考 uvc_query_v4l2_ctrl
uvc_find_control
找到一个uvc_control_mapping结构体: uvc_ctrl.c里有static struct uvc_control_mapping uvc_ctrl_mappings[]
{
.id= V4L2_CID_BRIGHTNESS, //
APP根据ID来找到对应的属性
.name= "Brightness",
.entity= UVC_GUID_UVC_PROCESSING, //
属于哪了个entity(比如PU)
.selector= PU_BRIGHTNESS_CONTROL, //
用于亮度
.size= 16,
// (为2字节)数据占多少位(对于某些属性只用到里面的若干位)
.offset= 0, //
从哪位开始(用到的若干位从哪里开始 )
.v4l2_type= V4L2_CTRL_TYPE_INTEGER,
// 属性类别
.data_type= UVC_CTRL_DATA_TYPE_SIGNED,//
数据类型(有符号或无符号)
},
uvc_control_mapping结构体 用来更加细致地描述属性,和uvc_control_info基本一一对应。
uvc_query_ctrl
usb_control_msg(发起usb控制传输)
举例说明: 要设置亮度,怎么操作?
a. 根据PU的描述符的bmControls, 从它的bit0等于1知道它支持调节亮度
b. 在uvc_ctrls数组中根据entity和index找到这一项:
{
.entity= UVC_GUID_UVC_PROCESSING,
.selector= PU_BRIGHTNESS_CONTROL,
.index= 0,
.size= 2,
.flags= UVC_CONTROL_SET_CUR | UVC_CONTROL_GET_RANGE
| UVC_CONTROL_RESTORE,
},
知道了:这个设备支持SET_CUR, GET_CUR, GET_MIN等
要设置时,可以向PU的selector发数据, 发的数据是2字节
c.
根据应用程序给的ID值,在uvc_ctrl_mappings数组中根据ID找到对应的数组项
从而知道了更加细致的信息,
然后使用usb_control_msg读写数据
3. 怎么写代码?
实现3个ioctl: vidioc_queryctrl/vidioc_g_ctrl/vidioc_s_ctrl
vidioc_queryctrl : 发起USB控制传输获得亮度的最小值、最大值、默认值、步进值
vidioc_s_ctrl : 把APP传入的亮度值通过USB传输发给硬件
vidioc_g_ctrl : 发起USB传输获得当前亮度值
要点:数据发给谁?发给usb_device的
VideoControl Interface
里面的Processing Unit
里面的PU_BRIGHTNESS_CONTROL
3、函数实现
(1)查询支持属性
/* 参考:uvc_query_v4l2_ctrl */
int myuvc_vidioc_queryctrl (struct file *file, void *fh,
struct v4l2_queryctrl *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
//只支持调节亮度(判断ID值)
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
//对结构体进行清零并进行设置
memset(ctrl, 0, sizeof *ctrl);
ctrl->id = V4L2_CID_BRIGHTNESS;//设置iD值
ctrl->type = V4L2_CTRL_TYPE_INTEGER;//类型
strcpy(ctrl->name, "MyUVC_BRIGHTNESS");//名字
ctrl->flags = 0;
v4l2_queryctrl 结构体参考http://blog.****.net/u011425939/article/details/53671869
pipe = usb_rcvctrlpipe(myuvc_udev, 0);//把指定USB设备指定端点设置为一个控制IN端点。
unsigned int usb_rcvctrlpipe(struct usb_device *dev, unsigned int endpoint)
把指定USB设备指定端点设置为一个控制IN端点。
type |= USB_DIR_IN;//方向数输入
/* 发起USB传输确定这些值 */
//我们要操作的结构体是myuvc_udev,要操作的接口是控制接口myuvc_control_intf,访问哪一个Unit(entity)是ProcessingUnitID,访问PU里的哪一个属性根据PU_BRIGHTNESS_CONTROL,读取数据的大小为2,data用于存储读取到的数据,5000是5秒的超时时间,根据GET_MIN知道要读取的是min值
ret = usb_control_msg(myuvc_udev, pipe, GET_MIN, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->minimum = myuvc_get_le_value(data);/* Note signedness */读取到数据后设置min值,根据id值找到uvc_ctrl_mappings数组里面对应的项,根据数据所占位的大小和偏移值来从data中取出min值
ret = usb_control_msg(myuvc_udev, pipe,
GET_MAX, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->maximum = myuvc_get_le_value(data);/* Note signedness */
ret = usb_control_msg(myuvc_udev, pipe,
GET_RES, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->step = myuvc_get_le_value(data);/* Note signedness */step是阶梯值,也就是每调节1步变化值是多少
/* 发起USB传输
ret = usb_control_msg(myuvc_udev, pipe,
GET_DEF, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
ctrl->default_value = myuvc_get_le_value(data);/* Note signedness */获得默认值
printk("Brightness: min =%d, max = %d, step = %d, default = %d\n", ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);//打印最小值、最大值、阶梯值、默认值
return 0;
}
(2)获得属性
/* 参考 : uvc_ctrl_get */
int myuvc_vidioc_g_ctrl (struct file *file, void *fh,
struct v4l2_control *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
pipe = usb_rcvctrlpipe(myuvc_udev, 0);
type |= USB_DIR_IN;
/* 发起USB传输
ret = usb_control_msg(myuvc_udev, pipe,
GET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);//获得当前亮度值
if (ret != 2)
return -EIO;
ctrl->value = myuvc_get_le_value(data);/* Note signedness */进行转换
return 0;
}
(3)设置属性(把应用程序传进来的值转换成16位数据)
/* 参考: uvc_ctrl_set/uvc_ctrl_commit */
int myuvc_vidioc_s_ctrl (struct file *file, void *fh,
struct v4l2_control *ctrl)
{
__u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
unsigned int pipe;
int ret;
u8 data[2];
if (ctrl->id != V4L2_CID_BRIGHTNESS)
return -EINVAL;
myuvc_set_le_value(ctrl->value, data);//把传进来的value值转换成data值
pipe = usb_sndctrlpipe(myuvc_udev, 0);//把指定USB设备指定端点设置为一个控制OUT端点
type |= USB_DIR_OUT;//类型是输出
ret = usb_control_msg(myuvc_udev, pipe, SET_CUR, type, PU_BRIGHTNESS_CONTROL << 8,
ProcessingUnitID << 8 | myuvc_control_intf, data, 2, 5000);
if (ret != 2)
return -EIO;
return 0;
}