一. 简介
二维码分两种:堆叠式和矩阵式。堆叠式二维码编码原理是建立在一维条码的基础上,按需要堆积成二行或多行。常用的有:Code 16k,Code 49,PDF417,MicroPDF417.矩阵式二维条码是在一个矩形空间通过黑白像素在矩阵中的不同分布进行编码。常用的有:Code One,MaxiCode,QR Code,Data Matrix,Han Xin Code,Grid Matrix.
我现在调试的是symbol的se4500,并且是通过软解码方式实现。Se4500可解码的有:PDF417,Data Matrix,Maxicode,QR Code,MicroQR,Aztec.
发现在网上介绍二维条码的很少,刚好我调试了,就写下一些经验。
二. 二维条码软解码的实现
1. 驱动的移植
当然如果刚好厂商有在你的平台的调试过,那集成驱动就简单多了。但是很遗憾的是,symbol的二维条码只是在TI上调试过。初步看上去,三星和TI在FIMC的架构上却别还是很大的。这就导致驱动的框架变了,那就需要在TI的基础上修改驱动来适应三星的驱动结构。
(1) I2C设备
Se4500是I2C设备,对其的配置比如分辨率等式通过I2C来通信的。首先将其添加到系统的I2C总线里。如下:
staticstruct s3c_platform_fimc fimc_plat_lsi = {
.srclk_name ="mout_mpll",
.clk_name ="sclk_fimc",
.lclk_name ="sclk_fimc_lclk",
.clk_rate =166750000,
#ifdefCAM_ITU_CH_A
.default_cam =CAMERA_PAR_A,
#else
.default_cam =CAMERA_PAR_B,
#endif
.camera ={
//===modify for camera
#ifdefCONFIG_VIDEO_SE4500
&se4500,
#endif
},
.hw_ver =0x43,
};
#ifdefCONFIG_VIDEO_SE4500
staticstruct gc0308_platform_data se4500_plat = {
.default_width = 752,//640,
.default_height = 480,
.pixelformat = V4L2_PIX_FMT_YUYV,
.freq = 24000000,
.is_mipi = 0,
};
staticstruct i2c_board_info se4500_i2c_info = {
I2C_BOARD_INFO("se4500",0x5c),
.platform_data = &se4500_plat,
};
staticstruct s3c_platform_camera se4500= {
//#ifdefCAM_ITU_CH_A
//.id =CAMERA_PAR_B,
//#else
.id =CAMERA_CSI,_C,
//#endif
.type =CAM_TYPE_ITU,
.fmt =ITU_601_YCBCR422_8BIT,
.order422 =CAM_ORDER422_8BIT_YCBYCR,//CAM_ORDER422_8BIT_CBYCRY,
.i2c_busnum = 1, //3,//uses gpio emulated
.info =&se4500_i2c_info,
.pixelformat =V4L2_PIX_FMT_YUYV,
.srclk_name ="mout_mpll",
.clk_name ="sclk_cam1",
.clk_rate =24000000, /* 24MHz */
.line_length =752, /* 640*480 */
/* default resol for preview kind of thing */
.width =752,
.height =480,
.window ={
.left = 16,
.top = 0,
.width = 752 - 16,
.height = 480,
},
/* Polarity */
.inv_pclk =1,
.inv_vsync =0,
.inv_href =0,
.inv_hsync =0,
.initialized =0,
.cam_power = set_cam4500_main_power,
};
#endif
.i2c_busnum= 1,
挂载在哪个I2C上,这里将设备挂载在I2C1上,和其他的摄像头一样。
&se4500_i2c_info
I2C设备的信息,比如I2C设备的名称以及I2C设备的slave地址。
极性设置,行场同步极性及时钟极性等,这个也很重要,如果时钟极性不对很可能会导致图像显示失真等问题。
(2) 电源控制方面
Se4500的电源有三路电源,并且都是3.3V供电,供电没有先后顺序,同时上电就可以了,真正被调用是在drivers/media/video/Samsung/fimc/fimc_capture.c中的函数:fimc_camera_init()
{
ctrl->cam->cam_power(1);
}
实现的地方在:arch/arm/mach-smdkv210/mach-smdkc110.c中的函数:.cam_power = set_cam4500_main_power
(3) 驱动的实现
可以先看/include/media/v4l2-i2c-drv.h文件,在这里已经实现v4l2的I2C设备的注册,我们需要做的是填充结构体v4l2_i2c_data。结构体内各个成员中主要的probe函数,在probe设备中调用如下函数:
v4l2_i2c_subdev_init(&pSE4500->subdev,pClient, &se4500_ops);
此函数的主要作用是将V4L2设备和I2C设备关联起来,因camera既是I2C设备也是V4L2子设备,同时也初始化了V4L2子设备的操作函数。
当然要先检测是否存在设备se4500,故将se4500_s_config赋给函数指针s_config,在se4500_s_config中会暂时先打开电源,使用se4500_detect函数通过I2C发送命令。如果此时可以检测到设备。接下来就是正式打开电源了。打开电源的操作已经在之前介绍过了。接下来就是需要发指令enable设备,使其开始扫描等工作。
se4500_open此函数正是使能函数,将其赋值给函数指针.init。按照v4l2的流程就会顺利调用到函数指针.init去打开se4500.这个是很重要的函数。
关于指令的具体意思,可以参看se4500的规格书。
驱动的移植到这就基本上完成了,当然还有Makefile Kconfig的添加修改。
2. 解码功能的实现
驱动移植完了,安装sdl后,使用decode功能你可以看到扫描灯已经亮了。这说明至少sensor已经工作了。不过现在很可能图像不能显示或者显示不正常。下面来分析图像显示问题。
(1) 首先要确保照片的格式
上图可以看到se4500出来的是8灰阶数据,要先转换成YUV422格式,最后又取出Y分量。我们系统里已经是默认设置图片数据格式为YUV422了,如果想知道怎么设置可以看文件device\samsung\proprietary\libcamera\SecCameraHWInterface.cpp文件中,如voidCameraHardwareSec::initDefaultParameters(int cameraId)中有 p.setPictureFormat(CameraParameters::PIXEL_FORMAT_JPEG);这就是设置图片格式的。
到这已经确定格式没有问题了。
(2) 输出分辨率的设置
虽然格式没问题了,但是发现图像虽然有变化但是图像都是花的。其实此现象不能准确定位,到底是我们系统这边处理的问题还是sensor出来的图像就有问题。我们可以取raw数据来看图像是否正常。获取的方法如下:
Sensor将数据输出到DMA中保存,然后DMA再将数据输出到framebuff中显示出来。存储时调用fimc_dqbuf_capture函数,最终到static int fimc_add_outqueue(struct fimc_control *ctrl, int i)函数中,这里保存了四帧数据,每两帧的数据时一样的,也就是说,其实是保存了两帧不同的数据。
#if 1
{
DMA_COUNT++;
if(DMA_COUNT==200)
{
struct file*filp=NULL;
mm_segment_tfs;
void*vir_add_dma;
//charraw_buff[752*480];
//memset(raw_buff,0,1);
DMA_COUNT=0;
printk("@@@1frame!!!!!!!\n");
vir_add_dma=(void*)ioremap((int)buf->base[FIMC_ADDR_Y],buf->length[FIMC_ADDR_Y]);
#if 1
filp=filp_open("/data/dmaraw",O_RDWR|O_CREAT,0777);
if(!IS_ERR(filp))
{
fs=get_fs();
set_fs(KERNEL_DS);
filp->f_op->write(filp,vir_add_dma,buf->length[FIMC_ADDR_Y],&filp->f_pos);
set_fs(fs);
filp_close(filp,NULL);
}
else
printk("openDMA raw fail!\n");
#endif
}
}
#endif
上面这段代码就是将一帧数据保存到系统的/data/dmaraw文件中。稍微解释下上段代码。内核中不能直接操作DMA的物理地址,首先需要将地址映射成一个虚拟地址如下:vir_add_dma=
(void *)ioremap((int)buf->base[FIMC_ADDR_Y],buf->length[FIMC_ADDR_Y]);
得到了地址后,我们就可以对地址进行读写操作了。
首先要获得一个文件的句柄,如下:
filp=filp_open("/data/dmaraw",O_RDWR|O_CREAT,0777);
但这里值得注意的是如果要将数据保存在用户空间,内核中是不能直接访问的。
需要将内核空间扩展到用户空间。可以利用如下函数:
set_fs(KERNEL_DS);
不过还要考虑到用完了用户空间需要还原,于是可以使用fs=get_fs();
来获取还原点和set_fs(fs)来恢复内核空间。
在上面已经可以操作用户空间了,直接使用读写函数,注意内核中的读写函数式filp->f_op->write和filp->f_op->read。
回归正题,使用如上方法保存到的raw数据,修改格式为。Raw.raw然后使用ps打开,并将图像设置为752*480.可以看到图像显示为如下:
可以看到图像很明显不对。这样我们就分析出,得到的数据就已经出问题了。然后将重点放在se4500的配置上。当然首先确认分辨率问题。
在.h文件中设置了如下:
#define SE4500_SENSOR_WIDTH 376//752
#define SE4500_SENSOR_HEIGHT 480
这里是将其设置成了376*480.理论上应该没问题,但是在.c中发现有如下:
#define SE4500_SENSOR_WIDTH_32B ((SE4500_SENSOR_WIDTH + 31) &~31)
通过此宏其实已经将分辨率改变了,得到的值为:378*480.
那我们就先修改此,将其改成:
#define SE4500_SENSOR_WIDTH_32B SE4500_SENSOR_WIDTH
这样分辨率就确保是376*480.
经过这样修改后得到如下的图像:
可以看到图像大致是正常的。但是还是有很多的白色的点点,给人感觉是某些像素丢失的样子。
(3) 极性修正
Mach-smdkc110.c文件中有对极性的设置如下:
static structs3c_platform_camera se4500= {
/* Polarity */
.inv_pclk =0,
.inv_vsync =0,
.inv_href =0,
.inv_hsync =0,
.initialized = 0,
};
有时钟极性和行场同步极性,此时发现将时钟极性反转,修改为:
static structs3c_platform_camera se4500= {
/* Polarity */
.inv_pclk = 1,
.inv_vsync =0,
.inv_href =0,
.inv_hsync =0,
.initialized = 0,
};
图片显示正常了。
三. 三个摄像头功能
Android2.2时只支持单个摄像头,并且若想支持双摄像头修改的文件多而且繁复。不过到了2.3开始就已经默认支持前后双摄像头了。只是还不支持三摄像头,不过比起2.2支持双摄像头的修改来说简单了很多,毕竟2.3对摄像头的支持已经更完善了。
1. 内核的修改
在mach-smdkc110.c中,接口设置中配置三个摄像头的支持如下:
static struct s3c_platform_fimc fimc_plat_lsi = {
.srclk_name = "mout_mpll",
.clk_name = "sclk_fimc",
.lclk_name = "sclk_fimc_lclk",
.clk_rate = 166750000,
#ifdef CAM_ITU_CH_A
.default_cam = CAMERA_PAR_A,
#else
.default_cam = CAMERA_PAR_B,
#endif
.camera = {
#ifdef CONFIG_VIDEO_S5K5BA
#ifdef CONFIG_VIDEO_OV5640
&ov5640,
#endif
#ifdef CONFIG_VIDEO_OV2659
&ov2659,
#endif
#ifdef CONFIG_VIDEO_SE4500
&se4500,
#endif
},
.hw_ver = 0x43,
};
并且注意要将se4500放在最后,因为我们将其设置成第三个摄像头。系统的原先的两个摄像头仍然为前后置。内核的修改就OK了。
2. 系统的修改
(1)获得三个摄像头
首先每次去打开摄像头时,都会去获取摄像头的数目。所以需要增加一个。在HAL层的代码seccamerahwinterface.cpp中有如下函数:
extern "C" int HAL_getNumberOfCameras()
{
return sizeof(sCameraInfo)/ sizeof(sCameraInfo[0]);
}
这个函数的作用就是反馈给上层,目前设备上有多少个camera。我们需要修改结构体并添加一成员:
static CameraInfo sCameraInfo[] = {
{
CAMERA_FACING_BACK,
0, // orientation : 0 90 180 270
},
//#ifdef BOARD_USES_DUAL_CAMERA
{
CAMERA_FACING_FRONT,
0, // orientation : 0 90 180 270
},
//#endif
{
CAMERA_FACING_OTHER,
0,
}
};
红色部分为添加的代码。CAMERA_FACING_OTHER是个枚举变量的成员,找到枚举定义的地方即在文件camera.h中。添加如下:
enum {
CAMERA_FACING_BACK = 0, /*The facing of the camera is opposite to that of the screen. */
CAMERA_FACING_FRONT = 1,/* The facing of the camera is the same as that of the screen. */
CAMERA_FACING_OTHER=2 //add by jhuang for se4500
};
这样系统就可以获得到三个摄像头了。
(2)三个摄像头的支持
发现修改了上面的虽然获得到了三个摄像头,但是发送cameraID=2时返回是不可用的参数。其实是有一个宏设置了系统支持的摄像头最大值。在文件cameraservice.h中修改为最大为3,如下:
/***modify by jhuang for se4500****/
#if 0
#define MAX_CAMERAS 2
#else
#define MAX_CAMERAS 3
/***modify by jhuang for se4500****/
#endif
这样就可以去发送cameraID:0 12.
(4) 添加第三个摄像头的信息
const struct camera_info g_camera_info[3] = {
ov5640_info,
ov2659_info,
se4500,
};
这里要注意,结构体se4500要放在最后,因是作为第三个摄像头用的。继续完成结构体se4500如下:
const struct camera_info se4500 =
{
752,//752, // max_preview_width
480, // max_preview_height
752,//1600, // max_snapshot_width
480,//1200, // max_snapshot_height
640, // postview_width
640, // postview_width_wide
480, // postview_height
16, // postview_bpp
160, // thumbnail_width
120, // thumbnail_height
16, // thumbnail_bpp
false, // flag_jpeg_stream
90, // focal_length
30, // frame_rate
false, //flag_auto_focus_support
false, //flag_flash_support
false, //flag_scene_mode_support
false, //flag_vt_call_support;
false, // flag_hw_v_mirror
false, // true,//flag_hw_h_mirror
0,// 180, // hw_rotate
};
这个结构体中,最重要的是红色字体标注的,一定要设置成752*480.
另外摄像头的切换,是靠cameraID来设置的。测试时可以简单将文件seccamerahwinterface.cpp中的CameraHardwareSec::CameraHardwareSec(intcameraId)此构造函数的参数可以修改来打开指定的摄像头。这可以用来底层测试用的。