如何编写Linux的设备驱动程序Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和
思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的
区别.在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是
支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调
试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,
获得了一些经验,愿与Linux fans共享,有不当之处,请予指正.
以下的一些文字主要来源于khg,johnsonm的Write linux device driver,
Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关
device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依
据自己的试验结果进行了修正.
一. Linux device driver 的概念
系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统
内核和机器硬件之间的接口.设备驱动程序为应用程序屏蔽了硬件的细节,这样
在应用程序看来,硬件设备只是一个设备文件, 应用程序可以象操作普通文件
一样对硬件设备进行操作.设备驱动程序是内核的一部分,它完成以下的功能:
1.对设备初始化和释放.
2.把数据从内核传送到硬件和从硬件读取数据.
3.读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
4.检测和处理设备出现的错误.
在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是
块设备.字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际
的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,
当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际
的I/O操作.块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间
来等待.
已经提到,用户进程是通过设备文件来与实际的硬件打交道.每个设备文件都
都有其文件属性(c/b),表示是字符设备还蔤强樯璞?另外每个文件都有两个设
备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个
设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分
他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号
一致,否则用户进程将无法访问到驱动程序.
最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是
抢先式调度.也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他
的工作.如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就
是漫长的fsck.//hehe
(请看下节,实例剖析)
读/写时,它首先察看缓冲区的内容,如果缓冲区的数据
如何编写Linux操作系统下的设备驱动程序
Roy G
二.实例剖析
我们来写一个最简单的字符设备驱动程序.虽然它什么也不做,但是通过它
可以了解Linux的设备驱动程序的工作原理.把下面的C代码输入机器,你就会
获得一个真正的设备驱动程序.不过我的kernel是2.0.34,在低版本的kernel
上可能会出现问题,我还没测试过.//xixi
#define __NO_VERSION__
#include
#include
char kernel_version [] = UTS_RELEASE;
这一段定义了一些版本信息,虽然用处不是很大,但也必不可少.Johnsonm说所
有的驱动程序的开头都要包含,但我看倒是未必.
由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就
是一些系统调用,如 open,read,write,close...., 注意,不是fopen, fread.,
但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据
结构:
struct file_operations {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
这个结构的每一个成员的名字都对应着一个系统调用.用户进程利用系统调用
在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号
找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制
权交给该函数.这是linux的设备驱动程序工作的基本原理.既然是这样,则编写
设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域.
相当简单,不是吗?
下面就开始写子程序.
#include
#include
#include
#include
#include
unsigned int test_major = 0;
static int read_test(struct inode *node,struct file *file,
char *buf,int count)
{
int left;
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count left > 0 left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
这个函数是为read调用准备的.当调用read时,read_test()被调用,它把用户的
缓冲区全部写1.
buf 是read调用的一个参数.它是用户进程空间的一个地址.但是在read_test
被调用时,系统进入核心态.所以不能使用buf这个地址,必须用__put_user(),
这是kernel提供的一个函数,用于向用户传送数据.另外还有很多类似功能的
函数.请参考.在向用户空间拷贝数据之前,必须验证buf是否可用.
这就用到函数verify_area.
static int write_tibet(struct inode *inode,struct file *file,
const char *buf,int count)
{
return count;
}
static int open_tibet(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT;
return 0;
} static void release_tibet(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
这几个函数都是空操作.实际调用发生时什么也不做,他们仅仅为下面的结构
提供函数指针。
struct file_operations test_fops = {
NULL,
read_test,
write_test,
NULL, /* test_readdir */
NULL,
NULL, /* test_ioctl */
NULL, /* test_mmap */
open_test,
release_test, NULL, /* test_fsync */
NULL, /* test_fasync */
/* nothing more, fill with NULLs */
};
设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序
可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),
如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能
动态的卸载,不利于调试,所以推荐使用模块方式。
int init_module(void)
{
int result;
result = register_chrdev(0, "test", &test_fops);
if (result < 0) {
printk(KERN_INFO "test: can't get major number ");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令将编译好的模块调入内存时,init_module 函数被调用。在
这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符
设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是
零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,
参数三用来登记驱动程序实际执行操作的函数的指针。
如果登记成功,返回设备的主设备号,不成功,返回一个负值。
void cleanup_module(void)
{
unregister_chrdev(test_major, "test");
}
在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test
在系统字符设备表中占有的表项。
一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。
下面编译
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c
得到文件test.o就是一个设备驱动程序。
如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后
ld -r file1.o file2.o -o modulename.
驱动程序已经编译好了,现在把它安装到系统中去。
$ insmod -f test.o
如果安装成功,在/proc/devices文件中就可以看到设备test,
并可以看到它的主设备号,。
要卸载的话,运行
$ rmmod test
下一步要创建设备文件。
mknod /dev/test c major minor
c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices | awk "\$2=="test" {print \$1}"
就可以获得主设备号,可以把上面的命令行加入你的shell script中去。
minor是从设备号,设置成0就可以了。
我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。
#include
#include
#include
#include
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file ");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d ",buf);
close(testdev);
}
编译运行,看看是不是打印出全1 ?
以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,
DMA,I/O port等问题。这些才是真正的难点。请看下节,实际情况的处理。
如何编写Linux操作系统下的设备驱动程序
Roy G
三 设备驱动程序中的一些具体问题。
1. I/O Port.
和硬件打交道离不开I/O Port,老的ISA设备经常是占用实际的I/O端口,
在linux下,操作系统没有对I/O口屏蔽,也就是说,任何驱动程序都可以
对任意的I/O口操作,这样就很容易引起混乱。每个驱动程序应该自己避免
误用端口。
有两个重要的kernel函数可以保证驱动程序做到这一点。
1)check_region(int io_port, int off_set)
这个函数察看系统的I/O表,看是否有别的驱动程序占用某一段I/O口。
参数1:io端口的基地址,
参数2:io端口占用的范围。
返回值:0 没有占用, 非0,已经被占用。
2)request_region(int io_port, int off_set,char *devname)
如果这段I/O端口没有被占用,在我们的驱动程序中就可以使用它。在使用
之前,必须向系统登记,以防止被其他程序占用。登记后,在/proc/ioports
文件中可以看到你登记的io口。
参数1:io端口的基地址。
参数2:io端口占用的范围。
参数3:使用这段io地址的设备名。
在对I/O口登记后,就可以放心地用inb(), outb()之类的函来访问了。
在一些pci设备中,I/O端口被映射到一段内存中去,要访问这些端口就相当
于访问一段内存。经常性的,我们要获得一块内存的物理地址。在dos环境下,
(之所以不说是dos操作系统是因为我认为DOS根本就不是一个操作系统,它实
在是太简单,太不安全了)只要用段:偏移就可以了。在window95中,95ddk
提供了一个vmm 调用 _MapLinearToPhys,用以把线性地址转化为物理地址。但
在Linux中是怎样做的呢?
2 内存操作
在设备驱动程序中动态开辟内存,不是用malloc,而是kmalloc,或者用
get_free_pages直接申请页。释放内存用的是kfree,或free_pages. 请注意,
kmalloc等函数返回的是物理地址!而malloc等返回的是线性地址!关于
kmalloc返回的是物理地址这一点本人有点不太明白:既然从线性地址到物理
地址的转换是由386cpu硬件完成的,那样汇编指令的操作数应该是线性地址,
驱动程序同样也不能直接使用物理地址而是线性地址。但是事实上kmalloc
返回的确实是物理地址,而且也可以直接通过它访问实际的RAM,我想这样可
以由两种解释,一种是在核心态禁止分页,但是这好像不太现实;另一种是
linux的页目录和页表项设计得正好使得物理地址等同于线性地址。我的想法
不知对不对,还请高手指教。
言归正传,要注意kmalloc最大只能开辟128k-16,16个字节是被页描述符
结构占用了。kmalloc用法参见khg.
内存映射的I/O口,寄存器或者是硬件设备的RAM(如显存)一般占用F0000000
以上的地址空间。在驱动程序中不能直接访问,要通过kernel函数vremap获得
重新映射以后的地址。
另外,很多硬件需要一块比较大的连续内存用作DMA传送。这块内存需要一直
驻留在内存,不能被交换到文件中去。但是kmalloc最多只能开辟128k的内存。
这可以通过牺牲一些系统内存的方法来解决。
具体做法是:比如说你的机器由32M的内存,在lilo.conf的启动参数中加上
mem=30M,这样linux就认为你的机器只有30M的内存,剩下的2M内存在vremap
之后就可以为DMA所用了。
请记住,用vremap映射后的内存,不用时应用unremap释放,否则会浪费页表。
3 中断处理
同处理I/O端口一样,要使用一个中断,必须先向系统登记。
int request_irq(unsigned int irq ,
void(*handle)(int,void *,struct pt_regs *),
unsigned int long flags,
const char *device);
irq: 是要申请的中断。
handle:中断处理函数指针。
flags:SA_INTERRUPT 请求一个快速中断,0 正常中断。
device:设备名。
如果登记成功,返回0,这时在/proc/interrupts文件中可以看你请求的
中断。
4一些常见的问题。
对硬件操作,有时时序很重要。但是如果用C语言写一些低级的硬件操作
的话,gcc往往会对你的程序进行优化,这样时序就错掉了。如果用汇编写呢,
gcc同样会对汇编代码进行优化,除非你用volatile关键字修饰。最保险的
办法是禁止优化。这当然只能对一部分你自己编写的代码。如果对所有的代码
都不优化,你会发现驱动程序根本无法装载。这是因为在编译驱动程序时要
用到gcc的一些扩展特性,而这些扩展特性必须在加了优化选项之后才能体现
出来。
关于kernel的调试工具,我现在还没有发现有合适的。有谁知道请告诉我,
不胜感激。我一直都在printk打印调试信息,倒也还凑合。
关于设备驱动程序还有很多内容,如等待/唤醒机制,块设备的编写等。
Devices
Video4Linux provides the following sets of device files. These live on the character device formerly known as "/dev/bttv". /dev/bttv should be a symlink to /dev/video0 for most people.
Device Name Minor Range Function
/dev/video 0-63 Video Capture Interface
/dev/radio 64-127 AM/FM Radio Devices
/dev/vtx 192-223 Teletext Interface Chips
/dev/vbi 224-239 Raw VBI Data (Intercast/teletext)
Video4Linux programs open and scan the devices to find what they are looking for. Capability queries define what each interface supports. The described API is only defined for video capture cards. The relevant subset applies to radio cards. Teletext interfaces talk the existing VTX API.
Capability Query Ioctl
The VIDIOCGCAP ioctl call is used to obtain the capability information for a video device. The struct video_capability object passed to the ioctl is completed and returned. It contains the following information
name[32] Canonical name for this interface
type Type of interface
channels Number of radio/tv channels if appropriate
audios Number of audio devices if appropriate
maxwidth Maximum capture width in pixels
maxheight Maximum capture height in pixels
minwidth Minimum capture width in pixels
minheight Minimum capture height in pixels
The type field lists the capability flags for the device. These are as follows
Name Description
VID_TYPE_CAPTURE Can capture to memory
VID_TYPE_TUNER Has a tuner of some form
VID_TYPE_TELETEXT Has teletext capability
VID_TYPE_OVERLAY Can overlay its image onto the frame buffer
VID_TYPE_CHROMAKEY Overlay is Chromakeyed
VID_TYPE_CLIPPING Overlay clipping is supported
VID_TYPE_FRAMERAM Overlay overwrites frame buffer memory
VID_TYPE_SCALES The hardware supports image scaling
VID_TYPE_MONOCHROME Image capture is grey scale only
VID_TYPE_SUBCAPTURE Capture can be of only part of the image
The minimum and maximum sizes listed for a capture device do not imply all that all height/width ratios or sizes within the range are possible. A request to set a size will be honoured by the largest available capture size whose capture is no large than the requested rectangle in either direction. For example the quickcam has 3 fixed settings.
Frame Buffer
Capture cards that drop data directly onto the frame buffer must be told the base address of the frame buffer, its size and organisation. This is a privileged ioctl and one that eventually X itself should set.
The VIDIOCSFBUF ioctl sets the frame buffer parameters for a capture card. If the card does not do direct writes to the frame buffer then this ioctl will be unsupported. The VIDIOCGFBUF ioctl returns the currently used parameters. The structure used in both cases is a struct video_buffer.
void *base Base physical address of the buffer
int height Height of the frame buffer
int width Width of the frame buffer
int depth Depth of the frame buffer
int bytesperline Number of bytes of memory between the start of two adjacent lines
Note that these values reflect the physical layout of the frame buffer. The visible area may be smaller. In fact under XFree86 this is commonly the case. XFree86 DGA can provide the parameters required to set up this ioctl. Setting the base address to NULL indicates there is no physical frame buffer access.
Capture Windows
The capture area is described by a struct video_window. This defines a capture area and the clipping information if relevant. The VIDIOCGWIN ioctl recovers the current settings and the VIDIOCSWIN sets new values. A successful call to VIDIOCSWIN indicates that a suitable set of parameters have been chosen. They do not indicate that exactly what was requested was granted. The program should call VIDIOCGWIN to check if the nearest match was suitable. The struct video_window contains the following fields.
x The X co-ordinate specified in X windows format.
y The Y co-ordinate specified in X windows format.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
Clipping rectangles are passed as an array. Each clip consists of the following fields available to the user.
x X co-ordinate of rectangle to skip
y Y co-ordinate of rectangle to skip
width Width of rectangle to skip
height Height of rectangle to skip
Merely setting the window does not enable capturing. Overlay capturing (i.e. PCI-PCI transfer to the frame buffer of the video card) is activated by passing the VIDIOCCAPTURE ioctl a value of 1, and disabled by passing it a value of 0.
Some capture devices can capture a subfield of the image they actually see. This is indicated when VIDEO_TYPE_SUBCAPTURE is defined. The video_capture describes the time and special subfields to capture. The video_capture structure contains the following fields.
x X co-ordinate of source rectangle to grab
y Y co-ordinate of source rectangle to grab
width Width of source rectangle to grab
height Height of source rectangle to grab
decimation Decimation to apply
flags Flag settings for grabbing
The available flags are
Name Description
VIDEO_CAPTURE_ODD Capture only odd frames
VIDEO_CAPTURE_EVEN Capture only even frames
Video Sources
Each video4linux video or audio device captures from one or more source channels. Each channel can be queries with the VDIOCGCHAN ioctl call. Before invoking this function the caller must set the channel field to the channel that is being queried. On return the struct video_channel is filled in with information about the nature of the channel itself.
The VIDIOCSCHAN ioctl takes an integer argument and switches the capture to this input. It is not defined whether parameters such as colour settings or tuning are maintained across a channel switch. The caller should maintain settings as desired for each channel. (This is reasonable as different video inputs may have different properties).
The struct video_channel consists of the following
channel The channel number
name The input name - preferably reflecting the label on the card input itself
tuners Number of tuners for this input
flags Properties the tuner has
type Input type (if known)
norm The norm for this channel
The flags defined are
VIDEO_VC_TUNER Channel has tuners.
VIDEO_VC_AUDIO Channel has audio.
VIDEO_VC_NORM Channel has norm setting.
The types defined are
VIDEO_TYPE_TV The input is a TV input.
VIDEO_TYPE_CAMERA The input is a camera.
Image Properties
The image properties of the picture can be queried with the VIDIOCGPICT ioctl which fills in a struct video_picture. The VIDIOCSPICT ioctl allows values to be changed. All values except for the palette type are scaled between 0-65535.
The struct video_picture consists of the following fields
brightness Picture brightness
hue Picture hue (colour only)
colour Picture colour (colour only)
contrast Picture contrast
whiteness The whiteness (greyscale only)
depth The capture depth (may need to match the frame buffer depth)
palette Reports the palette that should be used for this image
The following palettes are defined
VIDEO_PALETTE_GREY Linear intensity grey scale (255 is brightest).
VIDEO_PALETTE_HI240 The BT848 8bit colour cube.
VIDEO_PALETTE_RGB565 RGB565 packed into 16 bit words.
VIDEO_PALETTE_RGB555 RGV555 packed into 16 bit words, top bit undefined.
VIDEO_PALETTE_RGB24 RGB888 packed into 24bit words.
VIDEO_PALETTE_RGB32 RGB888 packed into the low 3 bytes of 32bit words. The top 8bits are undefined.
VIDEO_PALETTE_YUV422 Video style YUV422 - 8bits packed 4bits Y 2bits U 2bits V
VIDEO_PALETTE_YUYV Describe me
VIDEO_PALETTE_UYVY Describe me
VIDEO_PALETTE_YUV420 YUV420 capture
VIDEO_PALETTE_YUV411 YUV411 capture
VIDEO_PALETTE_RAW RAW capture (BT848)
VIDEO_PALETTE_YUV422P YUV 4:2:2 Planar
VIDEO_PALETTE_YUV411P YUV 4:1:1 Planar
Tuning
Each video input channel can have one or more tuners associated with it. Many devices will not have tuners. TV cards and radio cards will have one or more tuners attached.
Tuners are described by a struct video_tuner which can be obtained by the VIDIOCGTUNER ioctl. Fill in the tuner number in the structure then pass the structure to the ioctl to have the data filled in. The tuner can be switched using VIDIOCSTUNER which takes an integer argument giving the tuner to use. A struct tuner has the following fields
tuner Number of the tuner
name Canonical name for this tuner (eg FM/AM/TV)
rangelow Lowest tunable frequency
rangehigh Highest tunable frequency
flags Flags describing the tuner
mode The video signal mode if relevant
signal Signal strength if known - between 0-65535
The following flags exist
VIDEO_TUNER_PAL PAL tuning is supported
VIDEO_TUNER_NTSC NTSC tuning is supported
VIDEO_TUNER_SECAM SECAM tuning is supported
VIDEO_TUNER_LOW Frequency is in a lower range
VIDEO_TUNER_NORM The norm for this tuner is settable
VIDEO_TUNER_STEREO_ON The tuner is seeing stereo audio
VIDEO_TUNER_RDS_ON The tuner is seeing a RDS datastream
VIDEO_TUNER_MBS_ON The tuner is seeing a MBS datastream
The following modes are defined
VIDEO_MODE_PAL The tuner is in PAL mode
VIDEO_MODE_NTSC The tuner is in NTSC mode
VIDEO_MODE_SECAM The tuner is in SECAM mode
VIDEO_MODE_AUTO The tuner auto switches, or mode does not apply
Tuning frequencies are an unsigned 32bit value in 1/16th MHz or if the VIDEO_TUNER_LOW flag is set they are in 1/16th KHz. The current frequency is obtained as an unsigned long via the VIDIOCGFREQ ioctl and set by the VIDIOCSFREQ ioctl.
Audio
TV and Radio devices have one or more audio inputs that may be selected. The audio properties are queried by passing a struct video_audio to VIDIOCGAUDIO ioctl. The VIDIOCSAUDIO ioctl sets audio properties.
The structure contains the following fields
audio The channel number
volume The volume level
bass The bass level
treble The treble level
flags Flags describing the audio channel
name Canonical name for the audio input
mode The mode the audio input is in
balance The left/right balance
step Actual step used by the hardware
The following flags are defined
VIDEO_AUDIO_MUTE The audio is muted
VIDEO_AUDIO_MUTABLE Audio muting is supported
VIDEO_AUDIO_VOLUME The volume is controllable
VIDEO_AUDIO_BASS The bass is controllable
VIDEO_AUDIO_TREBLE The treble is controllable
VIDEO_AUDIO_BALANCE The balance is controllable
The following decoding modes are defined
VIDEO_SOUND_MONO Mono signal
VIDEO_SOUND_STEREO Stereo signal (NICAM for TV)
VIDEO_SOUND_LANG1 European TV alternate language 1
VIDEO_SOUND_LANG2 European TV alternate language 2
Reading Images
Each call to the read syscall returns the next available image from the device. It is up to the caller to set format and size (using the VIDIOCSPICT and VIDIOCSWIN ioctls) and then to pass a suitable size buffer and length to the function. Not all devices will support read operations.
A second way to handle image capture is via the mmap interface if supported. To use the mmap interface a user first sets the desired image size and depth properties. Next the VIDIOCGMBUF ioctl is issued. This reports the size of buffer to mmap and the offset within the buffer for each frame. The number of frames supported is device dependent and may only be one.
The video_mbuf structure contains the following fields
size The number of bytes to map
frames The number of frames
offsets The offset of each frame
Once the mmap has been made the VIDIOCMCAPTURE ioctl starts the capture to a frame using the format and image size specified in the video_mmap (which should match or be below the initial query size). When the VIDIOCMCAPTURE ioctl returns the frame is not captured yet, the driver just instructed the hardware to start the capture. The application has to use the VIDIOCSYNC ioctl to wait until the capture of a frame is finished. VIDIOCSYNC takes the frame number you want to wait for as argument.
It is allowed to call VIDIOCMCAPTURE multiple times (with different frame numbers in video_mmap->frame of course) and thus have multiple outstanding capture requests. A simple way do to double-buffering using this feature looks like this:
/* setup everything */
VIDIOCMCAPTURE(0)
while (whatever) {
VIDIOCMCAPTURE(1)
VIDIOCSYNC(0)
/* process frame 0 while the hardware captures frame 1 */
VIDIOCMCAPTURE(0)
VIDIOCSYNC(1)
/* process frame 1 while the hardware captures frame 0 */
}
Note that you are not limited to only two frames. The API allows up to 32 frames, the VIDIOCGMBUF ioctl returns the number of frames the driver granted. Thus it is possible to build deeper queues to avoid loosing frames on load peaks.
While capturing to memory the driver will make a "best effort" attempt to capture to screen as well if requested. This normally means all frames that "miss" memory mapped capture will go to the display.
A final ioctl exists to allow a device to obtain related devices if a driver has multiple components (for example video0 may not be associated with vbi0 which would cause an intercast display program to make a bad mistake). The VIDIOCGUNIT ioctl reports the unit numbers of the associated devices if any exist. The video_unit structure has the following fields.
video Video capture device
vbi VBI capture device
radio Radio device
audio Audio mixer
teletext Teletext device
RDS Datastreams
For radio devices that support it, it is possible to receive Radio Data System (RDS) data by means of a read() on the device. The data is packed in groups of three, as follows: First Octet Least Significant Byte of RDS Block
Second Octet Most Significant Byte of RDS Block
Third Octet Bit 7: Error bit. Indicates that an uncorrectable error occurred during reception of this block.
Bit 6: Corrected bit. Indicates that an error was corrected for this data block.
Bits 5-3: Received Offset. Indicates the offset received by the sync system.
Bits 2-0: Offset Name. Indicates the offset applied to this data.
Linux 下基于ARM920T 的USB 摄像头图像采集
王永清 何 波 王 乾 郭 磊
(中国海洋大学,山东青岛,266071)
摘要:随着USB摄像头的普及和基于ARM核的嵌入式芯片的快速发展,二者结合的便携性越来越受到人们欢迎,而嵌入式Linux的迅速发展更为二者的结合铺平了道路,本文介绍了基于ARM920T的嵌入式Linux下利用USB摄像头采集图像的硬件、软件设计过程,最终实现了在目标板上图像的采集和显示。
关键词:ARM;USB摄像头;Video for Linux;图像采集;嵌入式Linux
中图分类号:TP335;TP274 文献标识码:B
基金资助: 教外司留[2005]383号
1、基于ARM920T的USB摄像头图像采集硬件平台
图(1) 硬件结构原理图
图(1)中各个主要模块基本组成描述如下:
① 微处理器(MPU):针对开发多媒体视频终端的需要,并考虑到系统外围设备的需求情况,本系统采用SAMSUNG公司内嵌ATM920T内核的三星S3C2410处理器。最高主频可达203MHz [1]。
② SDRAM存储部分采用两颗Hynix公司的HY57V561620CT内存,大小为32M。
③ FLASH存储器采用SAMSUNG公司的K9F1208UOM Nand Flash,大小为64M。
④ USB集线器芯片:采用ALCOR MICRO公司的AU9254A21,可扩展为4个USB外围接口,分别连接图(1)中所示的四个外围设备。
⑤ LCD:采用Sharp公司的3.5寸LCD,分辨率为240×320。
2、基于ARM920T的USB摄像头图像采集的软件系统
由于嵌入式Linux具有成本低、代码开放、移植性好的特点,其用于嵌入式系统的优势和发展潜力是不容置疑的。软件部分的搭建主要依赖于以下三个部分:
① Boatloader:可以从SAMSUNG公司的官方网站获取,经过交叉编译生成映像文件,然后通过JTAG接口将映像烧写到目标板,实现引导程序的装载[2]。
② Kernel:本系统采用Linux-2.4.18.tar.gz版本的内核。
③ 文件系统:由于本系统要进行动态的擦写FLASH,所以采用了支持此功能的YAFFS文件系统[3]。
Linux平台的驱动一般分为字符设备、块设备和网络设备三种类型。而在Linux下要使系统所挂接的外部设备正常工作,必须加载相应的驱动程序。Linux下对于一个硬件的驱动,可以有两种方式:一种是直接加载到系统的内核当中去,另一种是以模块方式进行加载,就是在编译内核的时候,同时生成可重定位的目标文件(.o文件)[4]。项目中所用的SBC2410X的实验板的USB主控器驱动程序模块为USB-OHCI-S3C2410.o。在Linux下要采集视频类数据,需要加载Video4Linux驱动模块Videodev.o。然后再加相应的摄像头驱动程序。在项目开发中,我们所使用的摄像头采用的USB控制器为ov511+,所对应的驱动程序模块为ov511.o。所以在系统启动时必须要通过如下命令:
>>insmod videodev.o
>>insmod usb-ohci-s3c2410.o
>>insmod ov511.o
来加载所需要的模块。通过开源项目spca5xx可以得到上边所需模块的全部源代码。上层软件部分我们参考了vidcat,vgrabber,w3cam,gqcam这几种软件的操作过程,重点参照了vidcat进行了V4L编程,使用了v4l.c和vidcat.c 中的函数,经过交叉编译,在实验板上实现了实时图像采集的目的。
在图(2)中显示了各个模块之间的关系,其中从上到下的箭头流向表示通过各个模块启动和配置摄像头,从下到上的箭头流向表示由摄像头所采集的图像数据经各个模块采集到用户指定的位置。
3、在Linux下采集并显示USB摄像头数据
Linux下摄像头的驱动程序是以81为主设备号,在编写应用程序的时候,要通过打开一个具有该主设备号的设备文件来建立与设备驱动程序的通信,我们所使用的Linux没有该文件,所以需要手工创建,并建立其软连接,因为要对文件进行操作,所以要改变其访问权限为666。我们用到的videodev.o模块即为视频部分的标准Video for Linux (简称V4L)。这个标准定义了一套接口,内核、驱动、应用程序以这个接口为标准进行通信。
3.1图像数据的采集过程:
第一步:要打开摄像头设备,而在Linux下可以通过系统的设备文件来访问设备,在前面我们创建并建立了摄像头的设备文件,所以文件描述符(dev)可以如下方法获取:
while (max_try) {
dev = open (device, O_RDWR);
if (dev == -1) {
if (!--max_try) {
fprintf (stderr, "Can't open device %s\n", device);
return (1);} /*max_try为试图打开设备的最多次数*/
sleep (1);
} else { break; }}
第二步:进行访问摄像头设备的状态信息。
首先我们可以在kernel的源代码中找到头文件videodev.h,这个头文件定义了我们要编写的应用程序的所有数据结构和函数。当然我们先要获得摄像头的信息,可以通过头文件中的video_capability结构来了解摄像头的性能。其函数接口是int v4l_check_size (int fd, int *width, int *height),读出其中的单元可按如下方法,宏VIDIOCGCAP定义为 _IOR('v',1,struct video_capability)。
struct video_capability vid_caps;
if (ioctl (fd, VIDIOCGCAP, &vid_caps) == -1) {
perror ("ioctl (VIDIOCGCAP)"); return -1;}
然后通过访问结构体vid_caps就可以读出摄像头可拍摄的图片类型、图片的最大最小高度和宽度。
第三步:通过控制摄像头来采集图象数据。
实现函数为image = get_image (dev, width, height, palette, &size),通过该函数可以将设备文件中的图象数据的信息读出来,该函数的返回值image为图片要存储的格式,例如png、jpeg等。但这样必须首先申请一块足够大的内存空间,我们是这样完成的:
map = malloc (width * height * bytes);
len = read (dev, map, width * height * bytes);
if (len <= 0) { free (map); return (NULL); }
这样采集到的图像数据就会先存到所分配到的内存空间中,然后进行下一步的象素和图片格式存储处理。
第四步:按照预定的象素值和图片格式来存储图像。
在驱动程序向应用程序传递图像数据是一个拷贝过程,所以应用程序在采集图片数据时,先将驱动程序中图片缓冲区中的数据拷贝到应用程序中,然后再控制和处理图片数据。
if (palette == VIDEO_PALETTE_YUV420P) {
convmap = malloc ( width * height * bytes );
v4l_yuv420p2rgb (convmap, map, width, height, bytes * 8);
memcpy (map, convmap, (size_t) width * height * bytes);
free (convmap); }
这就是拷贝的过程,memcpy()为拷贝函数,v4l_yuv420p2rgb()函数用来将原生图片转换为RGB格式的图像信息。然后通过前边image的返回值来分别调用函数put_image_png,put_image_jpeg来生成相应格式的图像信息。
3.2 通过QT编译的图片查看器查看摄像头采集的图像。
QT目前是在嵌入式Linux领域中比较流行的图形开发工具,在我们的文件系统中,采用了基于QT的图形界面Qtopia,以下是实现调用图像信息的QT语句。第一句表示将图片的路径以及图片的名字传给pm1,然后通过Qlabel类的pl传出图片给图片查看器,从而实现了图像信息的显示。
QPixmap pm1("picture_path/picture_name");
Qlabel p1;
p1—>SetPixmap(pm1);了;‘
在采集的图像数据中,可以自定义所存储的图片格式,大小及其像素,方便迅速,而Qtopia是基于QT的比较成熟的嵌入式图形界面,利用其来显示我们所采集的图像数据效果良好。
4、结束语
本文详细介绍了基于ARM920T的嵌入式Linux下的USB摄像头图像采集的硬件、软件构建过程,可以灵活应用于基于嵌入式的各种电子产品中。由于所采用的软件全部是开放源码而且免费获得,所以对于需要便携好而又要有较高的数据处理能力且成本要求严格的方面尤其适合。