发布新日志

  • Linux内核驱动自动创建设备节点文件

    2013-03-12 15:26:55

    分类: LINUX

    Linux下生成驱动设备节点文件的方法有3个:1、手动mknod;2、利用devfs;3、利用udev
    在刚开始写Linux设备驱动程序的时候,很多时候都是利用mknod命令手动创建设备节点,实际上Linux内核为我们提供了一组函数,可以用来在模块加载的时候自动在/dev目录下创建相应设备节点,并在卸载模块时删除该节点。
    在2.6.17以前,在/dev目录下生成设备文件很容易,
    devfs_mk_bdev
    devfs_mk_cdev
    devfs_mk_symlink
    devfs_mk_dir
    devfs_remove
    这几个是纯devfs的api,2.6.17以前可用,但后来devfs被sysfs+udev的形式取代,同时期sysfs文件系统可以用的api:
    class_device_create_file,在2.6.26以后也不行了,现在,使用的是device_create ,从2.6.18开始可用
    struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, const char *fmt, ...)
    从2.6.26起又多了一个参数drvdata: the data to be added to the device for callbacks
    不会用可以给此参数赋NULL
    struct device *device_create(struct class *class, struct device *parent,
    dev_t devt, void *drvdata, const char *fmt, ...)
     
    下面着重讲解第三种方法udev
    在驱动用加入对udev的支持主要做的就是:在驱动初始化的代码里调用class_create(...)为该设备创建一个class,再为每个设备调用device_create(...)( 在2.6较早的内核中用class_device_create)创建对应的设备。
    内核中定义的struct class结构体,顾名思义,一个struct class结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用 device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应 device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
    struct class和class_create(…) 以及device_create(…)都包含在在/include/linux/device.h中,使用的时候一定要包含这个头文件,否则编译器会报错。
    struct class定义在头文件include/linux/device.h中
    class_create(…)在/drivers/base/class.c中实现
    device_create(…)函数在/drivers/base/core.c中实现
    class_destroy(...),device_destroy(...)也在/drivers/base/core.c中实现调用过程类似如下:
    static struct class *spidev_class;
     
    /*-------------------------------------------------------------------------*/
     
    static int __devinit spidev_probe(struct spi_device *spi)
    {
            ....
            
            dev = device_create(spidev_class, &spi->dev, spidev->devt,
                    spidev, "spidev%d.%d",
                    spi->master->bus_num, spi->chip_select);
            ...
    }
     
    static int __devexit spidev_remove(struct spi_device *spi)
    {
        ......
        device_destroy(spidev_class, spidev->devt);
        .....
     
        return 0;
    }
     
    static struct spi_driver spidev_spi = {
        .driver = {
            .name =        "spidev",
            .owner =    THIS_MODULE,
        },
        .probe =    spidev_probe,
        .remove =    __devexit_p(spidev_remove),
     
    };
     
    /*-------------------------------------------------------------------------*/
     
    static int __init spidev_init(void)
    {
        ....
     
        spidev_class = class_create(THIS_MODULE, "spidev");
        if (IS_ERR(spidev_class)) {
            unregister_chrdev(SPIDEV_MAJOR, spidev_spi.driver.name);
            return PTR_ERR(spidev_class);
        }
        ....
    }
    module_init(spidev_init);
     
    static void __exit spidev_exit(void)
    {
        ......
        class_destroy(spidev_class);
        ......
    }
    module_exit(spidev_exit);
     
    MODULE_DESCRIPTION("User mode SPI device interface");
    MODULE_LICENSE("GPL");
     
     下面以一个简单字符设备驱动来展示如何使用这几个函数 
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
     
    int HELLO_MAJOR = 0;
    int HELLO_MINOR = 0;
    int NUMBER_OF_DEVICES = 2;
     
    struct class *my_class;
    //struct cdev cdev;
    //dev_t devno;
     
    struct hello_dev {
    struct device *dev;
    dev_t chrdev;
    struct cdev cdev;
    };

    static struct hello_dev *my_hello_dev = NULL;

    struct file_operations hello_fops = {
     .owner = THIS_MODULE
    };
     
    static int __init hello_init (void)
    {
    int err = 0;
    struct device *dev;

    my_hello_dev = kzalloc(sizeof(struct hello_dev), GFP_KERNEL);
    if (NULL == my_hello_dev) {
    printk("%s kzalloc failed!\n",__func__);
    return -ENOMEM;
    }

    devno = MKDEV(HELLO_MAJOR, HELLO_MINOR);
    if (HELLO_MAJOR)
    err= register_chrdev_region(my_hello_dev->chrdev, 2, "memdev");
    else
    {
    err = alloc_chrdev_region(&my_hello_dev->chrdev, 0, 2, "memdev");
    HELLO_MAJOR = MAJOR(devno);
    }  
    if (err) {
    printk("%s alloc_chrdev_region failed!\n",__func__);
    goto alloc_chrdev_err;
    }
    printk("MAJOR IS %d\n",HELLO_MAJOR);

    cdev_init(&(my_hello_dev->cdev), &hello_fops);
    my_hello_dev->cdev.owner = THIS_MODULE;
    err = cdev_add(&(my_hello_dev->cdev), my_hello_dev->chrdev, 1);
    if (err) {
    printk("%s cdev_add failed!\n",__func__);
    goto cdev_add_err;
    }
    printk (KERN_INFO "Character driver Registered\n");

    my_class = class_create(THIS_MODULE,"hello_char_class");  //类名为hello_char_class
    if(IS_ERR(my_class)) 
    {
    err = PTR_ERR(my_class);
    printk("%s class_create failed!\n",__func__);
    goto class_err;
    }

    dev = device_create(my_class,NULL,my_hello_dev->chrdev,NULL,"memdev%d",0);      //设备名为memdev
    if (IS_ERR(dev)) {
    err = PTR_ERR(dev);
    gyro_err("%s device_create failed!\n",__func__);
    goto device_err;
    }

    printk("hello module initialization\n");
    return 0;
     
    device_err:
    device_destroy(my_class, my_hello_dev->chrdev);
    class_err:
    cdev_del(my_hello_dev->chrdev);
    cdev_add_err:
    unregister_chrdev_region(my_hello_dev->chrdev, 1);
    alloc_chrdev_err:
    kfree(my_hello_dev);
    return err;
    }
     
    static void __exit hello_exit (void)
    {
    cdev_del (&(my_hello_dev->cdev));
    unregister_chrdev_region (my_hello_dev->chrdev,1);
    device_destroy(my_class, devno);         //delete device node under /dev//必须先删除设备,再删除class类
    class_destroy(my_class);                 //delete class created by us
    printk (KERN_INFO "char driver cleaned up\n");
    }
     
    module_init (hello_init);
    module_exit (hello_exit);
     
    MODULE_LICENSE ("GPL");
    这样,模块加载后,就能在/dev目录下找到memdev这个设备节点了。
     例2:内核中的drivers/i2c/i2c-dev.c
    在i2cdev_attach_adapter中调用device_create(i2c_dev_class, &adap->dev,
             MKDEV(I2C_MAJOR, adap->nr), NULL,
             "i2c-%d", adap->nr);
    这样在dev目录就产生i2c-0  或i2c-1节点
     
    接下来就是udev应用,udev是应用层的东西,udev需要内核sysfs和tmpfs的支持,sysfs为udev提供设备入口和uevent通道,tmpfs为udev设备文件提供存放空间
    udev的源码可以在去相关网站下载,然后就是对其在运行环境下的移植,指定交叉编译环境,修改Makefile下的CROSS_COMPILE,如为mipsel-linux-,DESTDIR=xxx,或直接make CROSS_COMPILE=mipsel-linux-,DESTDIR=xxx 并install
    把主要生成的udevd、udevstart拷贝rootfs下的/sbin/目录内,udev的配置文件udev.conf和rules.d下的rules文件拷贝到rootfs下的/etc/目录内
    并在rootfs/etc/init.d/rcS中添加以下几行:
    echo “Starting udevd...”
    /sbin/udevd --daemon
    /sbin/udevstart
    (原rcS内容如下:
    # mount filesystems
    /bin/mount -t proc /proc /proc
    /bin/mount -t sysfs sysfs /sys
    /bin/mount -t tmpfs tmpfs /dev
    # create necessary devices
    /bin/mknod /dev/null c 1 3
    /bin/mkdir /dev/pts
    /bin/mount -t devpts devpts /dev/pts
    /bin/mknod /dev/audio c 14 4
    /bin/mknod /dev/ts c 10 16
    这样当系统启动后,udevd和udevstart就会解析配置文件,并自动在/dev下创建设备节点文件
     
  • Linux字符驱动中动态分配设备号与动态生成设备节点

    2013-03-12 14:53:13

    在驱动程序中初始化入口函数中,向内核注册一个设备后,往往要注册一个类

    例如
    static int __init mydriver_init(void) //驱动程序的初始化
    {  
       ……
        MYDRIVER_Major = register_chrdev(0, DEVICE_NAME, &mydriver_fops); //向内核注册一个设备,返回值为注册的主设备号
        if (MYDRIVER_Major < 0)
        {
            printk(DEVICE_NAME " can't register major number\n");
            return MYDRIVER_Major;
        }
        ……
        mydriver_class = class_create(THIS_MODULE, DEVICE_NAME); 
    //注册一个类,使mdev可以在"/dev/"目录下 面建立设备节点
        ……
        //创建一个设备节点,节点名为DEVICE_NAME
        device_create(mydriver_class, NULL, MKDEV(MYDRIVER_Major, 0), NULL, DEVICE_NAME);
        ……
    }

    从linux内核2.6的某个版本之后,devfs不复存在,udev成为devfs的 替代。相比devfs,udev有很多优势,在此就不罗嗦了,提醒一点,udev是应用层的东东,不要试图在内核的配置选项里找到它;加入对udev的支 持很简单,以作者所写的一个字符设备驱动为例,在驱动初始化的代码里调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备。大致用法如下:
    struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
    class_device_create(myclass, NULL, MKDEV(major_num, 0), NULL, “my_device”);

    device_create()  replaces  class_device_create()  in 2.6.21
    这样的module被加载时,udev daemon就会自动在/dev下创建my_device设备文件

    Linux字符驱动中动态分配设备号与动态生成设备节点
    http://www.cnblogs.com/zhuyp1015/archive/2012/05/22/2514008.html

    class_create(),class_device_create()或device_create()自动创建设备文件结点
    http://yuxu9710108.blog.163.com/blog/static/237515342011612104030470/

    Linux高级字符设备驱动
    http://www.linuxidc.com/Linux/2012-05/60469.htm


  • Android PMEM驱动研究(一)

    2013-03-05 08:51:21

    PMEM并不像Ashmem和binder那样,选中就可以被Android系统使用,他是一个platform设备,需要注册才可以使用.
      下面以S3C6410为例,描述使用流程:
      1)选中内核选项
      Device Drivers --->
       Misc devices --->
          Android pmem allocator
      2)修改你的dev.c注册文件,添加如下内容:

    java代码:
    1. #ifdef CONFIG_ANDROID_PMEM
    2. static struct android_pmem_platform_data android_pmem_pdata = {
    3. .name = "pmem",
    4. .start = PMEM_BASE,
    5. .size = PMEM_BASE_SIZE,
    6. .no_allocator = 1,
    7. .cached = 1,
    8. };

    9. static struct android_pmem_platform_data android_pmem_adsp_pdata = {
    10. .name = "pmem_adsp",
    11. .start = PMEM_ADSP_BASE,
    12. .size = PMEM_ADSP_BASE_SIZE,
    13. .no_allocator = 0,
    14. .cached = 0,
    15. };

    16. struct platform_device android_pmem_device = {
    17. .name = "android_pmem",
    18. .id = 0,
    19. .dev = { .platform_data = &android_pmem_pdata },
    20. };

    21. struct platform_device android_pmem_adsp_device = {
    22. .name = "android_pmem",
    23. .id = 1,
    24. .dev = { .platform_data = &android_pmem_adsp_pdata },
    25. };
    26. #endif
    复制代码
       3)在驱动注册列表中添加如下内容:

    java代码:
    1. static struct platform_device *smdk6410_devices[] __initdata = {
    2. #ifdef CONFIG_ANDROID_PMEM
    3. &android_pmem_device,
    4. &android_pmem_adsp_device,
    5. #endif
    6. };
    复制代码
    4)分配物理地址我用了128MB的最后8MB
      #define PMEM_BASE 0x57900000
      #define PMEM_BASE_SIZE SZ_1M*4
      #define PMEM_ADSP_BASE 0x57c00000
      #define PMEM_ADSP_BASE_SIZE SZ_1M*4
      5)重新编译内核
      6)修改bootargs 减少Linux可管理的MEM
      MEM=120MB
      7)重新启动系统
      启动信息:
      pmem: 1 init
      pmem_adsp: 0 init
      8)查看dev目录,多了pmem和pmem_adsp
  • Linux下linux/arch/arm/plat-s3c24xx/gpio.c的理解

    2013-03-04 18:56:05

     午饭过后,毛毛摸着小肚子又和大家见面了,我们大家一起来把上一篇文章没有学习完的知识学习完。如果毛毛讲的好,今天就奖励陶毛毛看一集《大耳朵图图》。

      好的,我们现在开始学习吧!今天早上在我们的上一篇文章“Linux下arch/arm/mach-s3c2410/include/mach/gpio-nrs.h的理解”里面介绍了一系列的宏,但是我们不知道,为什么Linux内核要这样做?今天下午毛毛就带领大家一起来看看linux/arch/arm/plat-s3c24xx/gpio.c里面的应用。

      毛毛通过查询内核代码,发现linux/arch/arm/plat-s3c24xx/gpio.c,主要是定义了一些Linux下对I/O口的标准配置和读写。包括以下函数:

    void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function);

    unsigned int s3c2410_gpio_getcfg(unsigned int pin);

    void s3c2410_gpio_pullup(unsigned int pin, unsigned int to);

    int s3c2410_gpio_getpull(unsigned int pin);

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);

    unsigned int s3c2410_gpio_getpin(unsigned int pin);

    unsigned int s3c2410_modify_misccr(unsigned int clear, unsigned int change);

    int s3c2410_gpio_getirq(unsigned int pin);

    上面函数的作用毛毛就不用再一一解释了,各位同学打开gpio.c文件就会理解,因为这些函数就是对I/O口的输入输出方向,数据读写和配置的函数。

    接下来毛毛仅仅从void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);这个函数来讨论上一篇文章中宏定义的作用。

    首先,我们还是先将这个函数的源程序列出来给大家

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
    {
     void __iomem *base = S3C24XX_GPIO_BASE(pin);  //宏计算,得到pin的基地址
     unsigned long ffs = S3C2410_GPIO_OFFSET(pin);      //宏计算,得到pin的相对基地址的偏移地址
     unsigned long flags;
     unsigned long dat;

     local_irq_save(flags);                  //类似于进入临界区                 

     dat = __raw_readl(base + 0x04);           //读I/O口数据
     dat &= ~(1 << offs);
     dat |= to << offs;
     __raw_writel(dat, base + 0x04);            //写I/O口数据

     local_irq_restore(flags);                //类似于退出临界区
    }

    从以上程序大家可能还不能看出什么来,但是毛毛就是那么“打破砂锅问到底”,毛毛进入S3C24XX_GPIO_BASE(pin),S3C2410_GPIO_OFFSET(pin)这两个宏。

    这两个宏定义在arch/arm/mach-s3c2410/include/mach/regs-gpio.h这个文件里面:

    #define S3C2410_GPIO_BASE(pin)   ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
    #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)

    (S3C24XX_VA_GPIO,是一个宏,代表I/O口的虚拟基地址;这个地方不再多讲,1:我自己对虚拟地址和实际物理地址还不是太熟悉,不能误人子弟。2:如果要讲需要说的东西太多,不容易一下讲清楚,还请各位同学自己琢磨)

    好的,到现在为止我们已经将基本上所有的东西列出来来了,接下来毛毛会用一个例子来将上面讲到的东西联系起来。

    首先我们看下面这句C程序:s3c2410_gpio_setpin(S3C2410_GPA(0), 1);

    这句程序的意思就是将S3C2410端口A的0口置1,相信大家和毛毛一样都懂的。

    S3C2410_GPA(0)是Linux下arch/arm/mach-s3c2410/include/mach/gpio-nrs.h里面的宏定义,进入函数void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);毛毛发现S3C2410_GPA(0)这个参数是作为宏S3C2410_GPIO_BASE(pin)和S3C2410_GPIO_OFFSET(pin)的参数。

    至此我们应该比较明了了吧,今天上午我们对arch/arm/mach-s3c2410/include/mach/gpio-nrs.h文件里面所做的工作就是用来供这两个宏使用的。

    但是为什么要这样定义呢?

    细心的毛毛在arch/arm/mach-s3c2410/include/mach/regs-gpio.h(也就是定义基地址和偏移地址的宏所在的文件)文件里发现一下程序:

    #define S3C2410_GPA0_ADDR0   (1<<0)

    #define S3C2410_GPA1_ADDR16  (1<<1)

    #define S3C2410_GPA2_ADDR17  (1<<2)

    #define S3C2410_GPA3_ADDR18  (1<<3)

    #define S3C2410_GPA4_ADDR19  (1<<4)

    #define S3C2410_GPA5_ADDR20  (1<<5)

    #define S3C2410_GPA6_ADDR21  (1<<6)

    #define S3C2410_GPA7_ADDR22  (1<<7)

    #define S3C2410_GPA8_ADDR23  (1<<8)

    #define S3C2410_GPA9_ADDR24  (1<<9)

    #define S3C2410_GPA10_ADDR25 (1<<10)
    #define S3C2400_GPA10_SCKE   (1<<10)

    #define S3C2410_GPA11_ADDR26 (1<<11)
    #define S3C2400_GPA11_nCAS0  (1<<11)

    #define S3C2410_GPA12_nGCS1  (1<<12)
    #define S3C2400_GPA12_nCAS1  (1<<12)

    #define S3C2410_GPA13_nGCS2  (1<<13)
    #define S3C2400_GPA13_nGCS1  (1<<13)

    #define S3C2410_GPA14_nGCS3  (1<<14)
    #define S3C2400_GPA14_nGCS2  (1<<14)

    #define S3C2410_GPA15_nGCS4  (1<<15)
    #define S3C2400_GPA15_nGCS3  (1<<15)

    #define S3C2410_GPA16_nGCS5  (1<<16)
    #define S3C2400_GPA16_nGCS4  (1<<16)

    #define S3C2410_GPA17_CLE    (1<<17)
    #define S3C2400_GPA17_nGCS5  (1<<17)

    #define S3C2410_GPA18_ALE    (1<<18)

    #define S3C2410_GPA19_nFWE   (1<<19)

    #define S3C2410_GPA20_nFRE   (1<<20)

    #define S3C2410_GPA21_nRSTOUT (1<<21)

    #define S3C2410_GPA22_nFCE   (1<<22)

    大家看出什么来了吗?如果没有看出来也没有关系,毛毛把上面程序再列写一下你就知道了

    void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
    {
     void __iomem *base = S3C24XX_GPIO_BASE(pin);  //宏计算,得到pin的基地址
     unsigned long ffs = S3C2410_GPIO_OFFSET(pin);      //宏计算,得到pin的相对基地址的偏移地址
     unsigned long flags;
     unsigned long dat;

     local_irq_save(flags);                  //类似于进入临界区                 

     dat = __raw_readl(base + 0x04);           //读I/O口数据
     dat &= ~(1 << offs);
     dat |= to << offs;
     __raw_writel(dat, base + 0x04);            //写I/O口数据

     local_irq_restore(flags);                //类似于退出临界区
    }

    知道了吧,其实就是位的偏移地址,也就是指定读写的位。

    到这里也许大家还是不太明白那到底和几天上午说的arch/arm/mach-s3c2410/include/mach/gpio-nrs.h有什么关系。

    接下来我们慢慢解释:其实我们的每个I/O端口的I/O口数目是0到n,由物理地址上面的四个字节控制(包括读写和配置),四个字节共32位,I/O 0到32对应于,四个自己的每一个位。所以arch/arm/mach-s3c2410/include/mach/gpio-nrs.h文件里面所实现的就是从I/O端口A的0开始到I/O端口H排列成一个从低到高的数字。而这个数字用来为下面的这两个宏来使用,从而求得相对于I/O控制寄存器的起始地址的相对地址和在所在I/O端口寄存器内的偏移位地址。

    #define S3C2410_GPIO_BASE(pin)   ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)
    #define S3C2410_GPIO_OFFSET(pin) ((pin) & 31)

    (((pin) & ~31) >> 1)得到相对于起始地址0x56000000的偏移基地址(PORTA求出来为0x00,PORTB求出来为0x10,PORTC求出来为0x20,从而和物理地址相吻合)

    ((pin) & 31)得到在所在控制寄存器的相对位地址

    好了,到此基本上就说完了,希望大家能懂。如果有不懂的地方请给我留言。

  • LabVIEW生成EXE文件

    2011-05-14 16:04:08

    LabVIEW做成安装程序。

    看了网上许多讨论将LabView程序生成exe文件的,但如果只生成exe文件的话,只有在安装了LabVIEW8.2引擎的电脑上才能运行。
    这里针对新出的LabVIEW8.2中文版程序打包做一个介绍。将打包文件拷贝到其他没有安装LabVIEW和其引擎的PC后,可以运行。这样做的缺点是打包生成的文件比较大。如果大家有更好的办法,希望可以贡献给大家。

           打开要打包的Vi,在菜单中选择工具-生成可执行的文件…..,弹出图1,选择你要创建项目的文件夹目录后,继续

    图1

    LabVIEW做成安装程序。 - leo - Leo

    在出现的画面里见图2填写保存exe文件的路径。其他设定基本无需改动。点击确定,就可以将你的程序转换成exe文件了。这是生成的exe文件还只能在安装了LabVIEW8.2引擎的电脑上运行。我们还需进行下一步打包。见图3

    图 2

    LabVIEW做成安装程序。 - leo - Leo

    这时还剩下下面的图3。右键点 程序生成规范, 选择新建安装程序,弹出图4。

    图3

    LabVIEW做成安装程序。 - leo - Leo

    图4

    LabVIEW做成安装程序。 - leo - Leo

    在源文件选项中,将你的vi选中,将其移动到右边目标文件中。在附加安装程序 选项中,选中第一项,如图5。

    图5

    LabVIEW做成安装程序。 - leo - Leo

    其他设定基本无需更改,点击生成即可。
    完成上面的设定后,就可以在目标文件夹中找到生成的安装程序。将这个安装程序拷贝的其他电脑上安装后,就可以运行你的Vi了。

    labview打包为EXE后独立运行的支持文件(8.6)
          经常看到有人提问,把Labview打包后,怎么要不有安装labview的电脑上运行?

           这里把打包后可执行文件的EXE文件中,加入labview运行的RUNTIME文件就可以。

    在添加中,经常也有人不知道那些文件是有用的,而是全部添加,造成添加后文件很大,因为原

    LabVIEW Run-Time文件要92.1M,再加上自己编写的程序和数据,差不多需要100多M。这给传输和存贮都带来极大的不便。

    这里对LabVIEW Run-Time文件进行了精简。只有20M,压缩后只有7M多。很方便使用。

     

    使用方法:

    先把自己的文件打包为EXE,

    然后把些文件全部复制到生成的EXE文件夹中,即可在没有安装labview的电脑上运行。

     

    这个是8。6版本的LabVIEW Run-Time文件,其它的同样。找到相应版本的这些文件就可以。

    这个文件中,语言中有中文,也在就是在打包EXE的过程中,要把运行时的语言设置为中文,把其它的取消。

     用LabVIEW生成exe文件时需注意的问题?
           1、将所有的VI与支持文件都添加到项目中,并保存项目中的所有VI。

    2、确保所有的VI都能正常运行。

    3、程序中最好不要用绝对路径,尤其对于动态载入的VI。一般来说都采用相对主VI的相对路径。

    4、如果程序中使用了动态链接库或.lvlib,必须将动态链接文件或.lvlib也添加到项目中去。

    5、如果使用MathScript节点,不要使用LabVIEW Run-Time不支持的 MathScript函数。 同时你还需要将M文件也添加到项目中去。

  • 开发JN ZigBee应用程序的各种API(包括函数和变量、常量等)

    2010-06-08 16:44:55

     

    ●●片上外设API

    函数:

    除中断处理函数

    void vHwDeviceIntCallback (

    uint32 u32DeviceId,

    uint32 u32ItemBitmap);

    之外,其他所有的函数命名方式为:

    返回类型+AHI_+设备+功能

    变量、常量:

    中断有关的枚举类型:u32DeviceId、u32ItemBitmap

    ---------------------------------------------------------------

    ●●BOS API

    函数:

    命名方式为:

    返回类型+Bos+功能

    变量、常量:

    ---------------------------------------------------------------

    ●●ZDP API

    有三类函数:

    设备发现、服务发现、绑定

    命名方式为:

    zdp+功能

    返回数据类型如何确定??

    常量、变量:

    ZDP STATUS

    ZDP Cluster ID

    ---------------------------------------------------------------

    ●●AF API

    有两类函数:

    AF数据实体函数(AFDE)、AF管理实体函数(AFME)

    AF数据实体函数(AFDE)

    命名方式为:

    afde+功能

    仅一个函数:afdeDataRequest

    AF数据管理函数(AFME)

    命名方式为:

    afme+功能

    变量、常量:(在文件af.h中)

    (AFME):KVP、MSG

    AF_Transaction_s 、AF_Msg_Transaction_s 、AF_Kvp_Transaction_s 等

    (AFME):Node、Power、Simple;Complex、User

    AF_NodeDescriptor_s、AF_PowerDescriptor_s、AF_SimpleDescriptor_s;

    AF_ComplexDescriptor_s、AF_UserDescriptor_s、

    ---------------------------------------------------------------

    ●●ZigBee应用API

    有三类函数:应用初始化函数、应用到协议栈函数、协议栈到应用

    应用初始化函数:冷启动、热启动函数

    命名:AppColdStart 、AppWarmStart

    应用到协议栈函数:多以JZS_开头

    命名方式为:

    JZS_+返回类型+功能

    几个例外:vAppSaveContexts 、u16AppGetContextSize 、vAppGetContexts 、eAppSetContexts 

     

     

    命名方式为

    JZA_+返回类型+功能

    变量、常量:

    tuJZS_StackEvent 

    tsJZS_Config的协议栈变量:JZS_sConfig;

    协议栈事件枚举变量:JZS_EVENT_xxxx

    协议栈到应用函数:都以以JZA开头
  • 开始学习ZigBee

    2010-06-08 16:35:22

    开始学习Jennic公司的JN5139 芯片(内置ZigBee)

     

  • Qt编写串口通信程序全程图文讲解(二)(

    2009-10-22 22:16:13

    上一篇文章中已经介绍了实现最简单的串口接收程序的编写,这篇将对程序内容进行分析。

    1.首先应说明操作串口的流程。

    步骤一:设置串口参数,如:波特率,数据位,奇偶校验,停止位,数据流控制等。

    步骤二:选择串口,如windows下的串口1为“com1”,Linux下为“ttyS0”等。

    步骤三:读或写串口。

    步骤四:关闭串口。

    (我们上一个程序没有写串口和关闭串口的功能,打开串口也是在构造函数里完成的,因为那只是为了用最简单的方法完成串口程序的编写。在以后的文章里我们将会对它进行修改和完善。)

    2.下面我们将按照上面的操作串口的流程,讲解第一个程序的编写。

    第一,我们在写程序之前,应该浏览一下那6个文件,大概看一下它们里面都是什么内容,各个文件各个类之间有什么联系。在win_qextserialport.cpp文件中,我们看它的最后一个构造函数,会发现,串口可以在这里进行初始化。


    Win_QextSerialPort::Win_QextSerialPort(const QString & name, const PortSettings& settings, QextSerialBase::QueryMode mode) {

    Win_Handle=INVALID_HANDLE_VALUE;

    setPortName(name);

    setBaudRate(settings.BaudRate);

    setDataBits(settings.DataBits);

    setStopBits(settings.StopBits);

    setParity(settings.Parity);

    setFlowControl(settings.FlowControl);

    setTimeout(settings.Timeout_Millisec);

    setQueryMode(mode);

    init();

    }

    它共有三个参数,其中第一个参数const QString & name,应该是串口的名字,是QString类型,我们可以用串口1即“com1”,不用过多说明。下面我们主要研究第二个和第三个参数。

    第二,我们查看第二个参数的位置。

    在Qt Creator的菜单中选择Edit->Find/Replace->All projects,如下图。

    在弹出的对话框中输入要查找的内容PortSettings,如下图。

    点击Search后,便能在下面显示出整个工程中所有PortSettings的位置。如下图。

    我们点击第一条,可以看到在qextserialbase.h文件中有一个struct PortSettings,如下图。


    我们双击这一条,进入相应的文件。如下图。

    struct PortSettings

    {

    BaudRateType BaudRate;

    DataBitsType DataBits;

    ParityType Parity;

    StopBitsType StopBits;

    FlowType FlowControl;

    long Timeout_Millisec;

    };

    可以看到在这个结构体里定义了串口初始化的各个参数,而对于BaudRateType等类型的定义,我们在这个结构体的上面可以看到,它们是多个枚举变量。如下图。


    这时我们便应该明白了,这个结构体便是实现串口参数设置的。

    第三,定义串口参数。

    BaudRateType BaudRate;

    波特率设置,我们设置为9600,即程序中用BAUD9600;

    DataBitsType DataBits;

    数据位设置,我们设置为8位数据位,即DATA_8;

    ParityType Parity;

    奇偶校验设置,我们设置为无校验,即PAR_NONE;

    StopBitsType StopBits;

    停止位设置,我们设置为1位停止位,即STOP_1;

    FlowType FlowControl;

    数据流控制设置,我们设置为无数据流控制,即FLOW_OFF;

    long Timeout_Millisec;

    延时设置,我们设置为延时500ms,即500;

    这样便写出了程序中的那句:

    struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};

    我们定义了一个结构体变量myComSetting,并对其进行了初始化。

    第四,设置第三个参数。

    我们先按上面的方法找到它的定义位置,在qextserialbase.h中,如下图。

    可以看到查询模式也是枚举变量,有两个选项,我们选择第二个EventDriven,事件驱动。

    到这里,我们就可以定义Win_QextSerialPort类的变量了,就是那句:

    myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);

    它完成了串口的选择和串口的初始化。

    第五,写打开串口函数和读串口函数。

    查看win_qextserialport.h文件,我们会发现Win_QextSerialPort类继承自QextSerialBase类。

    查看qextserialbase.h文件,我们会发现QextSerialBase类继承自QIODevice 类。

    我们在Qt的帮助中查看QIODevice 类,如下图。

    其部分内容如下图。可以看到其中有enum OpenModeFlag { NotOpen, ReadOnly, WriteOnly, ReadWrite, ..., Unbuffered },virtual bool open ( OpenMode mode ),QByteArray readAll ()等内容。

    而下面的信号函数中有void readyRead ();它可以查看串口是否有新的数据传来。


    所以,我们可以用这个类里的这些函数操作串口。

    如程序中的语句:

    myCom ->open(QIODevice::ReadWrite);

    //我们调用了其中的open函数,用ReadWrite可读写的方式进行打开串口;

    connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));

    //我们关联信号readyRead(),和自己写的槽函数readMyCom(),当串口有数据传来时进行读串口操作。

    void MainWindow::readMyCom() //自己写的读串口函数

    {

    QByteArray temp = myCom->readAll();

    //我们调用readAll()函数,读取串口中所有数据,在上面可以看到其返回值是QByteArray类型。

    ui->textBrowser->insertPlainText(temp);

    //调用insertPlainText()函数,是窗口上的文本浏览器中连续输出数据,而不是每次写数据前都清除以前的

    //数据,可以在Qt的帮助里查看这个函数的说明

    }

    这样我们便写完了所有的语句,最后只需要在mainwindow.h文件中加入相应的头文件,对象声明,函数声明即可。

          这里需要说明的是我们一定要学会查看文件和使用帮助文档,将我们不懂得知识一点一点搞明白。

          在下一篇中我们将会加入“打开串口”,“关闭串口”,“发送数据”三个按钮,将整个程序进行完善。

  • Qt编写串口通信程序全程图文讲解(一)(转)

    2009-10-22 22:14:03

    首先说明我们的编程环境是windows xp下,在Qt Creator中进行,如果在Linux下或直接用源码编写,程序稍有不同,请自己改动。

    http://hi.baidu.com/yafeilinux/blog/item/8cf8f89539311a14d31b70a2.html

           在Qt中并没有特定的串口控制类,现在大部分人使用的是第三方写的qextserialport类,我们这里也是使用的该类。我们可以去

    http://sourceforge.net/projects/qextserialport/files/

    进行下载,也可以去下载论坛上的

    http://www.qtcn.org/bbs/read.php?tid=22847

    下载到的文件为:qextserialport-1.2win-alpha.zip

    其内容如下图:

    我们在windows下只需要使用其中的6个文件:

    qextserialbase.cpp和qextserialbase.h,qextserialport.cpp和qextserialport.h,win_qextserialport.cpp和win_qextserialport.h

    如果在Linux下只需将win_qextserialport.cpp和win_qextserialport.h 换为 posix_qextserialport.cpp和posix_qextserialport.h即可。

    下面我们将讲述详细编程过程,这里我们先给出完整的程序,然后再进行逐句分析。

    1.打开Qt Creator,新建Qt4 Gui Application,工程名设置为mycom,其他使用默认选项。

    (注意:建立的工程路径不能有中文。)

    2.将上面所说的6个文件复制到工程文件夹下,如下图。

    3.在工程中添加这6个文件。

    在Qt Creator中左侧的文件列表上,鼠标右击工程文件夹,在弹出的菜单中选择Add Existing Files,添加已存在的文件。如下图:

    选择工程文件夹里的那6个文件,进行添加。如下图。

    添加好后文件列表如下图所示:

    4.点击mainwindow.ui,在窗口上加入一个Text Browser,用来显示信息。如下图。


    5.在mainwindow.h的相应位置添加头文件#include "win_qextserialport.h",添加对象声明Win_QextSerialPort *myCom;,添加槽函数声明void readMyCom();,添加完后,如下图。

    6.在mainwindow.cpp的类的构造函数中添加如下语句。

    MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent), ui(new Ui::MainWindow)

    {

    ui->setupUi(this);

    struct PortSettings myComSetting = {BAUD9600,DATA_8,PAR_NONE,STOP_1,FLOW_OFF,500};

    //定义一个结构体,用来存放串口各个参数

    myCom = new Win_QextSerialPort("com1",myComSetting,QextSerialBase::EventDriven);

    //定义串口对象,并传递参数,在构造函数里对其进行初始化

    myCom ->open(QIODevice::ReadWrite);

    //以可读写方式打开串口

    connect(myCom,SIGNAL(readyRead()),this,SLOT(readMyCom()));

    //信号和槽函数关联,当串口缓冲区有数据时,进行读串口操作

    }

    在下面添加readMyCom()函数的定义,添加如下代码。

    void MainWindow::readMyCom()     //读串口函数

    {

    QByteArray temp = myCom->readAll();

    //读取串口缓冲区的所有数据给临时变量temp

    ui->textBrowser->insertPlainText(temp);

    //将串口的数据显示在窗口的文本浏览器中

    }

    添加完代码后如下图。


    此时如果运行程序,已经能实现读取串口数据的功能了。我们将单片机采集的温度信息由串口传给计算机,效果如下图。

    这样最简单的串口通信程序就完成了。可以看到它只需要加入几行代码即可,非常简单。

    在下一篇中我们将详细分析添加的每一条语句。

  • Linux串口编程简介

    2009-10-20 19:18:10

    Linux串口编程简介(转至http://hi.baidu.com/adane/blog/item/b3bac3fd4c922440d7887d6f.html)

    Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。

    《Serial Programming Guide for POSIX Operating Systems》

    计算机串口的引脚说明

    序号 信号名称 符号 流向 功能
    2 发送数据 TXD DTE→DCE DTE发送串行数据
    3 接收数据 RXD DTE←DCE DTE 接收串行数据
    4 请求发送 RTS DTE→DCE DTE 请求 DCE 将线路切换到发送方式
    5 允许发送 CTS DTE←DCE DCE 告诉 DTE 线路已接通可以发送数据
    6 数据设备准备好 DSR DTE←DCE DCE 准备好
    7 信号地        信号公共地
    8 载波检测 DCD DTE←DCE 表示 DCE 接收到远程载波
    20 数据终端准备好 DTR DTE→DCE DTE 准备好
    22 振铃指示 RI DTE←DCE 表示 DCE 与线路接通,出现振铃

    串口操作需要的头文件

    #include <stdio.h> /*标准输入输出定义*/

    #include <stdlib.h> /*标准函数库定义*/

    #include <unistd.h> /*Unix 标准函数定义*/

    #include <sys/types.h>

    #include <sys/stat.h>

    #include <fcntl.h> /*文件控制定义*/

    #include <termios.h> /*PPSIX 终端控制定义*/

    #include <errno.h> /*错误号定义*/

    在 Linux 下串口文件是位于 /dev 下的

    串口一 为 /dev/ttyS0

    串口二 为 /dev/ttyS1

    打开串口是通过使用标准的文件打开函数操作:

    int fd;

    /*以读写方式打开串口*/

    fd = open( "/dev/ttyS0", O_RDWR);

    if (-1 == fd){

    /* 不能打开串口一*/

    perror(" 提示错误!");

    }

    最基本的设置串口包括波特率设置,效验位和停止位设置。

    串口的设置主要是设置 struct termios 结构体的各成员值。

    struct termio

    { unsigned short c_iflag; /* 输入模式标志 */

    unsigned short c_oflag; /* 输出模式标志 */

    unsigned short c_cflag; /* 控制模式标志*/

    unsigned short c_lflag; /* local mode flags */

    unsigned char c_line; /* line discipline */

    unsigned char c_cc[NCC]; /* control characters */

    };

    设置这个结构体很复杂,我这里就只说说常见的一些设置:

    波特率设置

    下面是修改波特率的代码:

    struct termios Opt;

    tcgetattr(fd, &Opt);

    cfsetispeed(&Opt,B19200); /*设置为19200Bps*/

    cfsetospeed(&Opt,B19200);

    tcsetattr(fd,TCANOW,&Opt);

    设置波特率的例子函数:

    /**

    *@brief 设置串口通信速率

    *@param fd 类型 int 打开串口的文件句柄

    *@param speed 类型 int 串口速度

    *@return void

    */

    int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,

    B38400, B19200, B9600, B4800, B2400, B1200, B300, };

    int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,

    19200, 9600, 4800, 2400, 1200, 300, };

    void set_speed(int fd, int speed){

    int i;

    int status;

    struct termios Opt;

    tcgetattr(fd, &Opt);

    for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {

    if (speed == name_arr[i]) {

    tcflush(fd, TCIOFLUSH);

    cfsetispeed(&Opt, speed_arr[i]);

    cfsetospeed(&Opt, speed_arr[i]);

    status = tcsetattr(fd1, TCSANOW, &Opt);

    if (status != 0) {

    perror("tcsetattr fd1");

    return;

    }

    tcflush(fd,TCIOFLUSH);

    }

    }

    }

    效验位和停止位的设置:

    无效验 8位 Option.c_cflag &= ~PARENB;

    Option.c_cflag &= ~CSTOPB;

    Option.c_cflag &= ~CSIZE;

    Option.c_cflag |= ~CS8;

    奇效验(Odd) 7位 Option.c_cflag |= ~PARENB;

    Option.c_cflag &= ~PARODD;

    Option.c_cflag &= ~CSTOPB;

    Option.c_cflag &= ~CSIZE;

    Option.c_cflag |= ~CS7;

    偶效验(Even) 7位 Option.c_cflag &= ~PARENB;

    Option.c_cflag |= ~PARODD;

    Option.c_cflag &= ~CSTOPB;

    Option.c_cflag &= ~CSIZE;

    Option.c_cflag |= ~CS7;

    Space效验 7位 Option.c_cflag &= ~PARENB;

    Option.c_cflag &= ~CSTOPB;

    Option.c_cflag &= &~CSIZE;

    Option.c_cflag |= CS8;

    设置效验的函数:

    /**

    *@brief 设置串口数据位,停止位和效验位

    *@param fd 类型 int 打开的串口文件句柄

    *@param databits 类型 int 数据位 取值 为 7 或者8

    *@param stopbits 类型 int 停止位 取值为 1 或者2

    *@param parity 类型 int 效验类型 取值为N,E,O,,S

    */

    int set_Parity(int fd,int databits,int stopbits,int parity)

    {

    struct termios options;

    if ( tcgetattr( fd,&options) != 0) {

    perror("SetupSerial 1");

    return(FALSE);

    }

    options.c_cflag &= ~CSIZE;

    switch (databits) /*设置数据位数*/

    {

    case 7:

    options.c_cflag |= CS7;

    break;

    case 8:

    options.c_cflag |= CS8;

    break;

    default:

    fprintf(stderr,"Unsupported data sizen"); return (FALSE);

    }

    switch (parity)

    {

    case ''n'':

    case ''N'':

    options.c_cflag &= ~PARENB; /* Clear parity enable */

    options.c_iflag &= ~INPCK; /* Enable parity checking */

    break;

    case ''o'':

    case ''O'':

    options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/

    options.c_iflag |= INPCK; /* Disnable parity checking */

    break;

    case ''e'':

    case ''E'':

    options.c_cflag |= PARENB; /* Enable parity */

    options.c_cflag &= ~PARODD; /* 转换为偶效验*/

    options.c_iflag |= INPCK; /* Disnable parity checking */

    break;

    case ''S'':

    case ''s'': /*as no parity*/

    options.c_cflag &= ~PARENB;

    options.c_cflag &= ~CSTOPB;break;

    default:

    fprintf(stderr,"Unsupported parityn");

    return (FALSE);

    }

    /* 设置停止位*/

    switch (stopbits)

    {

    case 1:

    options.c_cflag &= ~CSTOPB;

    break;

    case 2:

    options.c_cflag |= CSTOPB;

    break;

    default:

    fprintf(stderr,"Unsupported stop bitsn");

    return (FALSE);

    }

    /* Set input parity option */

    if (parity != ''n'')

    options.c_iflag |= INPCK;

    tcflush(fd,TCIFLUSH);

    options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/

    options.c_cc[VMIN] = 0; /* Update the options and do it NOW */

    if (tcsetattr(fd,TCSANOW,&options) != 0)

    {

    perror("SetupSerial 3");

    return (FALSE);

    }

    return (TRUE);

    }

    需要注意的是:

    如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/

    options.c_oflag &= ~OPOST; /*Output*/

    设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。

    • 发送数据

      char buffer[1024];int Length;int nByte;nByte = write(fd, buffer ,Length)

    • 读取串口数据

      使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。

      可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。

      char buff[1024];int Len;int readByte = read(fd,buff,Len);

    关闭串口就是关闭文件。

    close(fd);

    下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件

    /**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,

    但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。

    **********************************************************************/

    #define FALSE -1

    #define TRUE 0

    /*********************************************************************/

    int OpenDev(char *Dev)

    {

    int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY

    if (-1 == fd)

    {

    perror("Can''t Open Serial Port");

    return -1;

    }

    else

    return fd;

    }

    int main(int argc, char **argv){

    int fd;

    int nread;

    char buff[512];

    char *dev = "/dev/ttyS1"; //串口二

    fd = OpenDev(dev);

    set_speed(fd,19200);

    if (set_Parity(fd,8,1,''N'') == FALSE) {

    printf("Set Parity Errorn");

    exit (0);

    }

    while (1) //循环读取数据

    {

    while((nread = read(fd, buff, 512))>0)

    {

    printf("nLen %dn",nread);

    buff[nread+1] = '''';

    printf( "n%s", buff);

    }

    }

    //close(fd);

    // exit (0);

    }

  • 重整

    2009-09-24 16:57:34

    休息了几个月,现在又重新开始我的LINUX&ARM9
  • QT4安装说明

    2009-07-03 15:36:56

    Linux下嵌入式Qt4的安装
    2009-04-26 10:03

        安装的是Qt4.4.3,注意QT4和QT3的安装有一些不同,用装QT3的方法安装QT4可能会出现一些错误。

       QT4的安装粗略过程:

    1、从Trolltech公司主页上去下载qt-x11-opensource-src-4.4.3.tar.gz文件包

    http://www.qtsoftware.com/downloads

    2、解压:gunzip qt-x11-opensource-src-4.4.3.tar.gz;
    tar xvf qt-x11-opensource-src-4.4.3.tar.gz;

    3、运行configure脚本:

    进入qt-x11-opensource-src-4.4.3文件夹,

    输入./configure,运行shell脚本程序;

    4、编译:gmake;

    5、安装:gmake install;

    (第3、4、5步运行时间比较长,根据机器的配置不同而不同。)

    6、设置环境变量:

    打开/etc/profile文件,在该文件的末尾加上以下语句:

    PATH=/usr/local/Trolltech/Qt-4.4.3/bin:$PATH

    QTDIR=/usr/local/Trolltech/Qt-4.4.3

    MANPATH=$QTDIR/man:$MANPATH

    LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH

    export PATH QTDIR MANPATH LD_LIBRARY_PATH

    保存后重启。

    我遇到中文显示乱码的功能,解决办法如下:

    运行:/usr/local/Trolltech/Qt-4.4.3/bin/qtconfig,在Font项将字体设置为Bitstream charter

    退出保存。

    至于最后一步环境变量的设置经试验QT3的方法可以成功,即:

    编写/root中的.bash_profile 加入如下几行

    QTDIR=/usr/local/qt
        PATH=$QTDIR/bin:$PATH
        MANPATH=$QTDIR/man:$MANPATH
        LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH

        export QTDIR PATH MANPATH LD_LIBRARY_PATH

    QT4的方法未试验

  • 74ls595 74hc595介绍

    2009-06-05 21:49:14

    74LS595,74HC595中文资料
    2008-05-14 12:36

    74LS595,74HC595引脚图,管脚图

              ________

    QB--|1            16|--Vcc

    QC--|2            15|--QA

    QD--|3            14|--SI

    QE--|4           13|--/G

    QF--|5            12|--RCK

    QG--|6           11|--SRCK

    QH--|7           10|--/SRCLR

    GND- |8          9|--QH'

            |________|

    74595的数据端:

    QA--QH: 八位并行输出端,可以直接控制数码管的8个段。

    QH': 级联输出端。我将它接下一个595的SI端。

    SI: 串行数据输入端。

    74595的控制端说明:

    /SRCLR(10脚): 低点平时将移位寄存器的数据清零。通常我将它接Vcc。

    SRCK(11脚):上升沿时数据寄存器的数据移位。QA-->QB-->QC-->...-->QH;下降沿移位寄存器数据不变。(脉冲宽度:5V时,大于几十纳秒就行了。我通常都选微秒级)

    RCK(12脚):上升沿时移位寄存器的数据进入数据存储寄存器,下降沿时存储寄存器数据不变。(通常我将RCK置为低电平,) 当移位结束后,在RCK端产生一个正脉冲(5V时,大于几十纳秒就行了。我通常都选微秒级),更新显示数据。

    /G(13脚): 高电平时禁止输出(高阻态)。如果单片机的引脚不紧张,用一个引脚控制它,可以方便地产生闪烁和熄灭效果。比通过数据端移位控制要省时省力。

    注:74164和74595功能相仿,都是8位串行输入转并行输出移位寄存器。74164的驱动电流(25mA)比74595(35mA)的要小,14脚封装,体积也小一些。

    74595的主要优点是具有数据存储寄存器,在移位的过程中,输出端的数据可以保持不变。这在串行速度慢的场合很有用处,数码管没有闪烁感。

    与164只有数据清零端相比,595还多有输出端时能/禁止控制端,可以使输出为高阻态。

  • 一种实现视频捕捉的简单方法

    2009-05-28 16:50:41

     摘 要  当前,随着视频监控、可视电话、电视会议等多媒体应用技术的迅速发展,对数字视频捕获技术的要求越来越高。实现视频捕获的方法有很多,本文主要介绍了微软公司VFW(Video for Windows)软件包中的AVICap窗口类的成员函数和一些关键宏,以及与编写视频捕捉程序紧密相关的几个结构体。最后,通过一个视频捕捉应用程序,展示了如何具体实现视频捕捉,以及要注意的相关问题。
        关键词 VFW;AVICap窗口类;视频捕捉;捕捉窗口;视频对话框
     
    0  引言
     
        随着数字视频监控、可视电话、电视会议等多媒体技术应用的迅速兴起,越来越多的场合需要对数字视频信号进行捕捉。通常来说,捕捉实时数字视频信号是一个比较复杂的过程,但是,微软公司开发的Video for Windows SDK软件包中AVICap窗口类提供了一条捷径,借助于该窗口类,能够很方便地将视频捕捉的各种功能组合到应用程序中去。

    1  AVICap窗口类简介

        ACICap支持实时的视频流捕捉和视频单帧捕捉。使用ACICap窗口类可创建具有一些基本功能的窗口,例如视频图像的预览、设置捕捉参数的对话框、音频、视频捕捉的独立控制等。ACICap中的回调函数可使应用程序向用户提供有关捕捉的状态,包括进行的过程指示,以及任何可能产生的错误。开发人员可以设置一个标志用来指示在什么时候采集到音频,什么时候采集到视频。这样,应用程序可以直接使用数据而无需写入AVI文件中。
        AVICap窗口类提供了以下功能:
        ◆  单独控制音频、视频的采集;
        ◆  采用overlay(实时叠加)或preview(预览)方式显示视频图像;
        ◆  与ICM和ACM同时工作,将音频和视频数据直接压缩到应用程序中;
        ◆  将音频、视频流直接压缩入AVI文件而不需要开发人员详细了解AVI文件格式的细节;
        ◆  动态了解视频和音频的输入设备;
        ◆  创建、保存和载入调色板;
        ◆  将图像调色板拷贝到剪切板上;
        ◆  控制MCI设备;
        ◆  捕捉单帧图像并以DIB格式保存。

    2  AVICap窗口类的主要函数、宏简介

        AVICap提供给开发人员一整套函数,用这些函数可以实现许多视频捕捉程序所需的窗口管理;同时,在整个捕捉过程中仍然保留全部的控制。这些函数形式简单,采用基于消息的接口来获取硬件里的音频和视频信号,同时控制着视频流采集到磁盘的过程。
        ACICap的函数能够使开发人员以很少的投入来创建具有基本捕捉功能的采集程序,这些函数是高级的、经过优化的、为开发人员创建具有自己特性的应用程序留有很大的灵活性。 
        下面是ACICap提供给开发人员编写捕捉程序的几个重要函数和宏。
        ◆ 创建捕捉窗口
    HWND VFWAPI capCreateCaptureWindow(
          LPCSTR lpszWindowName,// 捕捉窗口名字
          DWORD dwStyle,// 捕捉窗口的风格
          int x,// 窗口左上角x轴坐标
          int y,// 窗口左上角y轴坐标
          int nWidth,// 窗口的宽度
          int nHeight,// 窗口的高度
          HWND HWnd,// 父窗口句柄
          Int nID// 捕捉窗口的ID号
    );
        如果该函数调用成功 则函数返回窗口的句柄 否则函数返回NULL。
        ◆ 捕捉窗口与设备连接
    BOOL capDriverConnect(
        hwnd,// 捕捉窗口的句柄
        iIndex// 设备驱动号
    );
        如果连接成功,返回TRUE否则函数返回FALSE。
        ◆ 获取视频捕捉设备功能
    BOOL capDriverGetCaps(
        hwnd,// 捕捉窗口句柄
        psCaps,// 指向一个用于存储返回值CAPDRIVERCAPS结构的指针
        wSize// CAPDRIVERCAPS结构占用的字节数
    );
        如果捕获窗口与捕获驱动连接成功,则返回TURE,否则,返回FALSE。
        ◆ 设置捕捉设备的参数
    BOOL capCaptureSetSetup(
       hwnd,// 设置窗口句柄
       psCapParms,// 指向一个用于存储返回值的CAPDRIVERCAPS结构的指针
       wSize// CAPDRIVERCAPS结构占用的字节数
    );
        设置成功返回TURE,否则返回FALSE。
    ◆ 设置处理回调状态
    BOOL capSetCallbackOnStatus(
       hwnd,// 捕捉窗口句柄
       fpProc// 指向状态回调函数的指针
    );
    ◆ 设置错误处理
    BOOL capSetCallbackOnError(
       hwnd,// 捕捉窗口句柄
       fpProc// 指向错误回调函数的指针
    );
        ◆ 设置数据文件名
    BOOL capFileSetCaptureFile(
       hwnd,// 捕捉窗口句柄
       szName// 指向字符串的指针,字符串内容为捕获文件名
    );
       设置一个由szName指向的字符串作为文件名,用于存储从捕捉窗口hWnd采集的视频图像数据,成功,返回TRUE,否则返回FALSE。
        ◆ 开始捕捉视频
    BOOL capCaptureSequence(
           hwnd
    );
       触发程序开始捕捉视频图像并将其保存到数据文件。
        ◆ 视频源设置对话框
        BOOL capDlgVideoSource( hwnd ); // hwnd:捕捉窗口句柄
        视频源设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoSource来判断驱动程序是否支持这一功能。
        ◆ 视频格式设置对话框
        BOOL capDlgVideoFormat( hwnd ); // hwnd:捕捉窗口句柄
    视频格式设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoFormat来判断驱动程序是否支持这一功能。
         ◆ 视频显示方式设置对话框
        BOOL capDlgVideoDisplay( hwnd ); // hwnd:捕捉窗口句柄
    视频格式设置对话框对于每一个捕捉驱动程序来说,是唯一的。而且,有些驱动程序不一定支持这一功能。应用程序可以通过检测CAPDRIVERCAPS结构的成员变量fHasDlgVideoDisplay来判断驱动程序是否支持这一功能。
        ◆ 视频压缩设置对话框
        BOOL capDlgVideoDisplay( hwnd ); // hwnd:捕捉窗口句柄
        视频格式设置对话框允许用户在视频捕获期间选择不同的压缩器。

    3  几个重要结构

        编写视频捕捉程序往往要用到以下与视频捕捉相关的结构
        (1)CAPTUREPARMS:包括控制视频流捕捉过程的参数,这一结构被用来得到和设置影响捕捉速率、捕捉时的缓冲区数目、以及捕捉如何结束时的参数。
        (2)CAPSTATUS:定义了捕捉窗口的当前状态,如:以象素为单位表示图像的高、宽、 预览和重叠方式的标志量,尺寸缩放的标志量等。
    因为捕捉窗口的状态随各种各样的消息而改变,所以当应用程序需要功能菜单项,决定捕捉窗口的真实状态或者调用视频格式对话框时,都应该更新这一结构中的信息。
        (3)CAPDRIVERCAPS:定义了视频捕捉驱动程序的功能,如:驱动程序的数目索引 是否支持视频叠加功能等。当应用程序将捕捉窗口与视频捕捉驱动程序相连接时,应该发送消息WM_CAP_DREVER_GET_CAPS或者调用宏capDriverGetCaps将驱动程序的功能拷贝一份到该结构中。
        (4)VIDEOHDR:定义了视频数据块的头信息,其数据成员lpData(指向数据缓存的指针),和dwBufferLenth(数据缓存的大小)经常用到。

    4  视频捕捉举例

        这是一个视频捕捉应用程序,实现了视频捕捉的基本功能,并且可以设置视频的来源、视频的格式等。
    #include "Vfw.h"
    #include <windowsx.h>
    #pragma comment( lib, "Vfw32.lib" )   // 使用Vfw32.lib库
     
    LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr);
    HWND hMyWnd;
    LPBITMAPINFO lpbi;   // 视频格式
    void CWin32VideoCaptureDlg::OnBnClickedOk()
    {
    hMyWnd=this->m_hWnd;
    // Step 1. 建立捕获窗口
    HWND hWndC=capCreateCaptureWindow(
    (LPSTR)"MyCaptureWindow",
    WS_CHILD | WS_VISIBLE,
    0,0,160,120,
    this->m_hWnd,
    0);
    // Step 2. 与驱动程序建立连接
    LRESULT fOK = ::SendMessage (hWndC, WM_CAP_ DRIVER_ CONNECT, 0, 0L);
     
    // Step 3. 列举出目前系统所拥有的捕获驱动程序
             char szDeviceName[80];
             char szDeviceVersion[80];
             CString DeviceList;
             for (int wIndex = 0; wIndex < 10; wIndex++)         {
                if (capGetDriverDescription (wIndex,szDeviceName,\
                                sizeof (szDeviceName), szDeviceVersion, \
                                sizeof (szDeviceVersion)))     {
                                // 列出系统已安装的驱动程序,让用户选择
                                DeviceList.Append(szDeviceName);
                       }
             }
             MessageBox(DeviceList,"系统中 Capture Driver 描述");
     
    // Step 4. 获得驱动程序的性能
        //我们据此判断使用者是否可以设定 Video Source 和 Video Format
        CAPDRIVERCAPS CapDriverCaps;
        ::SendMessage (hWndC, WM_CAP_DRIVER_GET _CAPS, \
                       sizeof (CAPDRIVERCAPS), (LONG) (LPVOID) &CapDriverCaps);
     
    // Step 5.选择视频源
             if (CapDriverCaps.fHasDlgVideoSource)
                       capDlgVideoSource(hWndC);  
     
    // Step 6.调整视频格式
           CAPSTATUS CapStatus;
             if (CapDriverCaps. FhasDlg VideoFormat){
                       capDlgVideoFormat(hWndC);
                       capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
                       }
    // Step 7.调整视频输出特性(亮度,对比度,色深)
             if (CapDriverCaps.fHasDlgVideoDisplay)
                       capDlgVideoDisplay(hWndC);
     
    // Step 8. 获得硬件状态
             BOOL bOK=capGetStatus(hWndC, &CapStatus, sizeof (CAPSTATUS));
     
    // Step 9. 调整捕捉窗口大小
             ::SetWindowPos(hWndC, NULL, 0, 0, CapStatus. uiImage Width, \
                        CapStatus.uiImageHeight,
    SWP_NOZORDER | SWP_NOMOVE);
     
    // Step 10. 设置视频格式
             DWORD dwSize;
             dwSize = capGetVideoFormatSize(hWndC);
             lpbi = (LPBITMAPINFO) GlobalAllocPtr(GHND, dwSize);
             capGetVideoFormat(hWndC, lpbi, dwSize);
     
    // Step 11. 视频预览
             capPreviewRate(hWndC, 66);    
             capPreview(hWndC, TRUE);     
     
    // Step 12. 当填满一帧后,调用回调函数处理
    BOOL bOk= capSetCallbackOnFrame(hWndC, capVideo StreamCallback);
    }
    int gdwFrameNum=0;
    char *gachBuffer=new char[100];
    LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr){
       if (!hWnd)
            return FALSE;
            wsprintf(gachBuffer, "Preview frame# %ld 一个pixel=%d bits; 宽=%d; 高=%d;
                   (RGB)=(%d,%d,%d) 使用长度=%d" ,\
                   gdwFrameNum++, lpbi->bmiHeader. biBit Count,\
    lpbi->bmiHeader.biWidth, lpbi-> bmiHeader.  biHeight,\
                                   (int)*(lpVHdr->lpData),\
                                   (int)*(lpVHdr->lpData+1),\
                                   (int)*(lpVHdr->lpData+2),\
                                   lpVHdr->dwBytesUsed  );
            SetWindowText(hMyWnd, (LPSTR)gachBuffer);
            return (LRESULT) TRUE ;
      }
        这里要指出的一点是,如果在程序Link中出现不能识别函数的错误时,可以采取两种方式:
        * 在#include<vfw.h>后加入一行预编译代码:#pragma  comment(lib,“vfw32”)
        * 或者,在Link设置中加入vfw32.lib。

    5  结束语

        微软公司开发的Video For Windows具有简单易用、开发容易的优点,可以使得不了解组件编程技术的软件编程人员快速开发出适合自己的视频捕获应用程序。但是,它也具有一些不足之处,诸如:不能处理音、视频同步问题;不支持变长帧编码技术等。针对这些不足,微软目前使用DirectShow与WDM Stream Class来解决问题,但是,这些技术比较复杂,实现起来相对困难,因此,可以预见,在今后的一段时间里,AVICap技术仍将是视频捕获实现的重要选项。

    参考文献

        1  Microsoft Inc. Video for Windows Developed Toolkit Programming Guide. 2001
        2  张基温,贾中宁,李伟. Visual C++ 程序开发基础. 北京:高等教育出版社. 2002
        3 王超龙,陈志华. Visual C++ 6.0 入门与提高. 北京:人民邮电出版社. 2002
        作者简介:沈 旭 (1979-),男,山东单县人,硕士,计算机专业教师,主要研究方向:嵌入式系统、视频水印等,sjrgl@sina.com
  • VFW视频采集详细介绍-转

    2009-05-27 18:29:15

    关键词 VFW 视频采集 VC

    作者(原创)逄格民 2007-11-06

    刚刚做了一个利用VFW(Video For Windows)的视频采集程序,就想写出来,给需要的人分享一下。程序并不复杂,
    关键是在没人指导的情况下,学习是比较痛苦和漫长的过程,我经历了这个过程,如果大家想避免走弯路,直接看我
    下面的解释就好了。由于我仅仅作出了结果,对很多东西的理解也许并不完全正确或者是完全错误,愿请指教。

    提前说一句,我的程序是在Visual C++6.0平台下写的。

    下面我慢慢说,你也慢慢听。

    1 什么是VFW

    VFW 是微软的一个软件包,至少可以用来开发视频采集程序,当然还有别的用处,但不是我想关心的。VFW提供了
    基于消息的接口,而这些接口,也可以利用它本省定义的宏来实现。

    2 怎么使用VFW

    写之前提示一句,可以参照MSDN看下面的内容,一定会更好。

    (1)创建一个基于对话框的程序,工程名称Grasp

    因为要用VFW,所以要包含头文件

    可在GraspDlg.h中加入 #include<Vfw.h>,然后Project

    ->Settings,在link标签页的Object/library modules :里面加入

    Vfw32.lib

    (2)在CGraspDlg类中添加一个窗口句柄

    HWND m_hVideo;

    (3)利用capCreateCaptureWindow函数创建窗口,并且得到返回的窗口句柄。

    m_hVideo=::capCreateCaptureWindow("Me",WS_CHILD | WS_VISIBLE,
    0,0,500,500,m_hWnd,0);

    上面这个函数写在BOOL CGraspDlg::OnInitDialog()中。参数m_hWnd是你的工程中

    对话框的句柄,窗口类中都有这个成员变量,而对话框的类是窗口类的子类,记得?

    (4)用capSetCallbackOnFrame宏注册回调函数,也写在BOOL CGraspDlg::OnInitDialog()中。

    capSetCallbackOnFrame(m_hVideo, FrameCallbackProc);

    上面第二个参数是回调函数的地址,名字可以自己来定义,但是回调函数必须有如下参数和返回值。

    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr);
    人家规定的,咱们也没办法,就照着写就好啦。

    解释一下,什么是回调函数呢,它有什么用处?

    回调函数,就是你自己写的函数,符合规定的参数和返回值类型,符合规定的调用约定,比如上面这个函数
    就是回调函数,参数和返回值类型都是规定好的,调用约定为CALLBACK,CALLBACK其实是一个宏
    #define CALLBACK __stdcall
    满足一定条件时,此函数可以被系统自动调用,在回调函数当中,你可以写自己的代码完成一定功能。
    比如在这里,用capSetCallbackOnFrame(m_hVideo, FrameCallbackProc)注册后,当每得到一桢数据后,系统就
    调用函数FrameCallbackProc。
    (5)因为注册了回调函数,所以,当然要自己写出这个函数了。在GraspDlg.cpp中,且在
    BOOL CGraspDlg::OnInitDialog()函数之前写下面代码:

    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr)
    {
    if (!ghVideo)
    return FALSE;

    return (LRESULT) TRUE ;
    }
    目前为止,该回调函数还没有什么作用,一会儿我们再来编写函数当中的代码,现在我就写的话,你也不见得看懂,
    不是么。一会儿写的话,你就可以轻松明白了。
    注意在这个函数中的ghVideo 了么?其实就和上面的m_hVideo一样,可是这里是全局函数,m_hVideo是对话框类的成员变量,
    我写m_hVideo编译器是不认识的,对吧,所以,我又在GraspDlg.cpp当中定义了一个全局变量
    HWND ghVideo;
    并且,在m_hVideo=::capCreateCaptureWindow("Me",WS_CHILD | WS_VISIBLE,
    0,0,500,500,m_hWnd,0);
    之后加上一句ghVideo=m_hVideo; 这样就可以用ghVideo了。
    (6)在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    char szDeviceName[80];
    char szDeviceVersion[80];
    int wIndex;

    for (wIndex = 0; wIndex < 10; wIndex++)
    {
    if (capGetDriverDe
    scription (wIndex, szDeviceName,
    sizeof (szDeviceName), szDeviceVersion,
    sizeof (szDeviceVersion)) )
    {
    if(capDriverConnect(m_hVideo,wIndex))
    {

    }
    }

    }
    上面代码中,capGetDriverDe
    scription是列举所有可用视频的驱动程序,如果列举成功,用capDriverConnect进行连接。
    其实,我的机器上就装了一个摄像头,所以,只有当wIndex=0的时候,列举成功,并且连接也成功。这段代码好像很奇怪,因为
    列举成功之后,不论是否连接上,都没有做任何事情。其实可以用下面代码代替:

    char szDeviceName[80];
    char szDeviceVersion[80];

    //Get Driver description
    //and the code can also be deleted as you want.
    capGetDriverDe
    scription (0 szDeviceName,
    sizeof (szDeviceName), szDeviceVersion,
    sizeof (szDeviceVersion));
    //connect window to driver
    capDriverConnect(m_hVideo,0);
    (7)到这里,再加下面两句话你就会有成就感了,在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    capPreviewRate(m_hVideo, 40); // 设置Preview模式的显示速率
    capPreview(m_hVideo, TRUE); //启动Preview模式
    如果到此为止,已经完成了视频采集的全过程,你运行一下,就可以看到摄像头拍摄的画面了,显示在你的对话框上。
    但是,我的目的还没有达到,我其实想在每一桢显示之前,能处理一下这一桢的数据,那么,去哪里找这桢数据存
    放的位置呢?
    (8)为了完成我的目标,我把步骤(7)中的两句代码先注释掉。在对话框上加一个按钮,并在对单击做出响应的响应函数
    中写下面代码:
    capGrabFrame(m_hVideo);
    这是一个宏,将鼠标移动到这段代码上,右键单击,选择Go To Definition of capGrabFrame,你会看到
    #define capGrabFrame(hwnd) ((BOOL)AVICapSM(hwnd, WM_CAP_GRAB_FRAME, (WPARAM)0, (LPARAM)0L))
    而继续察看AVICapSM宏你会看到其实是在调用SendMessage函数呢,对吧,其实就是在发送消息。至于消息谁处理了,我们就不去
    关心了,我们关心的是,发送消息后,系统会调用我们刚才注册的回调函数
    LRESULT CALLBACK FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr) ;
    (9)好了,如果你单击按钮,capGrabFrame(m_hVideo)就发送消息了,然后,我们就进入回调函数了,这太好了。
    看到回调函数传递的两个参数了么?我们更关心第二个参数,这个就是单击按钮我们捕捉到的一桢数据的入口啊!
    LPVIDEOHDR 是结构体VIDEOHDR的指针,而在MSDN中察看结构体VIDEOHDR,我们就可以找到桢数据的存贮位置指针了。

    VIDEOHDR定义如下:
    typedef struct videohdr_tag {
    LPBYTE lpData;
    DWORD dwBufferLength;
    DWORD dwBytesUsed;
    DWORD dwTimeCaptured;
    DWORD dwUser;
    DWORD dwFlags;
    DWORD_PTR dwReserved[4];
    } VIDEOHDR, NEAR *PVIDEOHDR, FAR * LPVIDEOHDR;
    看到结构体中第一个参数了么?这个就是我们想要的桢数据的指针!后面参数,包括缓冲区长度等。
    (10)终于得到了缓冲区的数据,可是,又一个问题出现了,缓冲区中的数据到底具体是啥含义啊?
    这桢图像多大啊?size 是多少乘多少的啊?就是我们想要的像素信息么?
    好的,我先告诉你,缓冲区中全部是像素信息,我们照着我的步骤这样做,其实是默认了一些参数,包括图像的
    长度,宽度,色彩数,等等,那么,这个默认的值是多少呢?
    (11)用一下capGetVideoFormat宏吧,你会得到想要的东西。
    在BOOL CGraspDlg::OnInitDialog()中继续添加如下代码:
    BITMAPINFO bmpInfo;
    capGetVideoFormat(m_hVideo,&bmpInfo,sizeof(BITMAPINFO));
    BITMAPINFO结构体内容自己看MSDN.定义如下

    typedef struct tagBITMAPINFO {
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
    } BITMAPINFO, *PBITMAPINFO;

    而BITMAPINFOHEADER定义如下:

    typedef struct tagBITMAPINFOHEADER{
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
    } BITMAPINFOHEADER, *PBITMAPINFOHEADER;

    加入步骤(11)的两句话,调试运行,我发现,bmpInfo.bmiHeader.biWidth为320,就是采集的图像宽度;
    bmpInfo.bmiHeader.biHeight为240,就是采集的图像高度,这些都是默认值,也可以改变这些值,通过
    capSetVideoFormat宏来实现。
    (12)那么,我们在步骤(9)中,回调函数第二个参数对应的结构体VIDEOHDR中,图像数据缓冲区的大小
    dwBufferLength是多少呢?我们可以在回调函数中加一个MessageBox函数,输出这个值,
    我们就可以发现,为230400,这个数很好,正好等于图像宽度X图像高度的3倍,
    也就是说,是图像像素数目的3倍,这就对了,每个像素用3个字节存储的嘛。好啦,我们知道了,桢缓冲区中,存储
    的完全是图像的像素信息,那么,具体哪个值对应哪个像素呢?
    存储顺序是这样的:先从图像最下面一行开始,从左向右,依次存储,每一个像素用连续的3个字节,分别为B(蓝色分量),
    G(绿色分量),R(红色分量)。然后存储倒数第二行,仍然按照图像从左向右存储,然后倒数第三行,
    倒数第四行。。。。。。等等,最后存储正数第一行。

    好啦,我所有想说的都说完啦,你明白了么?好累,现在是晚上,快10点钟了,我正好要去跑步去了。

  • Linux内核模块参数权限

    2009-04-26 19:38:55

    Linux内核模块参数权限

    在进行linux内核模块编程时,常常需要给模块传递参数,其作用是从使用的设备号到驱动应当任何操作的几个方面. 例如, SCSI 适配器的驱动常常有选项控制标记命令队列的使用, IDE 驱动允许用户控制 DMA 操作. 如果你的驱动控制老的硬件, 还需要被明确告知哪里去找硬件的 I/O 端口或者 I/O 内存地址. 内核通过在加载驱动的模块时指定可变参数的值, 支持这些要求.
           参数常常被声明为一个静态全局变量,如static int num=10;然后使用module_param(参数名,参数类型,参数读写权限)为模块定义一个参数,例如:
    module_prarm(num,int,S_IRUGO);
    这样你就可以在insmod(装载模块)的时候为参数指定值,如果没有指定则使用默认值,例如上面的num=10,则10是参数num的默认值。下面以一个例子说明:
    #include <linux/init.h>
    #include <linux/module.h>
    MODULE_LICENSE("Dual BSD/GPL");
    static int num=10;
    module_param(num,int,S_IRUGO);
    static int hello_init(void)
    {
        printk("Hello module init.\n");
        printk("num=%d\n",num);
        return 0;
    }

    static void   hello_exit(void)
    {
        printk("Goodbye module exit.\n");
    }

    module_init(hello_init);
    module_exit(hello_exit);

    MODULE_DESCRIPTION("a simple module");
    MODULE_ALIAS("hello");
    将以上代码保存为文件hello.c,编写Makefile文件对其进行编译:
    # Makefile2.6
    ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
    else
    PWD  := $(shell pwd)
    KVER ?= $(shell uname -r)
    KDIR := /lib/modules/$(KVER)/build
    all:
        $(MAKE) -C $(KDIR) M=$(PWD)
    endif
    然后make就可以了,make会生成:
    |-- Module.symvers
    |-- built-in.o
    |-- hello.ko
    |-- hello.mod.c
    |-- hello.mod.o
    `-- hello.o
    这些文件,其中的hello.ko就是编译生成的模块,使用insmod将hello.ko模块装载进内核,这个需要超级用户权限,我的系统是ubuntu7.10,所以使用如下命令装载:
    sudo insmod hello.ko num=20
    使用dmesg命令查看运行结果:
    xiyoulinux@xiyoulinux-desktop:~/module$ dmesg #实际上是读取/var/log/messages文件的内容
    ... #省略以前的信息
    [14801.675260] Hello module init.
    [14801.675265] num=20
    这就是运行结果。如果你插入的是sudo insmod hello.ko,那么:
    使用dmesg命令查看运行结果:
    xiyoulinux@xiyoulinux-desktop:~/module$ dmesg #实际上是读取/var/log/messages文件的内容
    ... #省略以前的信息
    [14801.675260] Hello module init.
    [14801.675265] num=10 #取默认值
    以上是内核模块的编译和运行情况,实际上你还需要用到lsmod(查看模块是否被插入,一般在打印出来的第一行里)、rmmod(卸载装载的模块,只有当模块的引用计数为0时才能被卸载)等命令。
           那么实质上当你装载模块hello.ko时,系统会在/sys/module下生成一个hello文件夹:

    xiyoulinux@xiyoulinux-desktop:/sys/module/hello$ tree
    .
    |-- holders
    |-- initstate
    |-- parameters
    |   `-- num
    |-- refcnt
    |-- sections
    |   |-- __param
    |   `-- __versions
    `-- srcversion

    3 directories, 6 files
    其中parameters目录中就存放的是该模块的参数,一个参数对应一个文件,文件的内容为参数的默认值例如:
    xiyoulinux@xiyoulinux-desktop:/sys/module/hello/parameters$ tree
    .
    `-- num

    0 directories, 1 file
    xiyoulinux@xiyoulinux-desktop:/sys/module/hello/parameters$ cat num
    10

    在module_param(num,int,S_IRUGO);
    定义参数时,其中的参数读写权限S_IRUGO其实是对参数文件的读写权限,所以权限的设置值就和对文件的设置值一样,例如上面对num参数权限的设置S_IRUGO就是对所有用户具有读的权限,而S_IRUGO|S_IWUSR 则允许 root 来改变参数。

  • VMware linux 在2.4.20-8 中编译2.6.15.5 内核

    2009-04-26 17:13:22

    软件准备:

    gcc-3.2.2-5.i386.rpm  :用来编译

    ncurses-5.6.tar.gz:用来配合make menuconfig 命令配置内核

    bison-2.4.tar.gz :语法解析器

    flex-2.5.35.tar.bz2 :词法解析器

    m4-1.4.9.tar.gz: 

     

    modutils-2.4.26.tar.bz2 : http://www.kernel.org/pub/linux/utils/kernel/module-init-tools/

    module-init-tools-3.2.2.tar.bz2:  2.6配套工具包

    http://www.kernel.org/pub/linux/utils/kernel/modutils/v2.4/

     

    linux-2.6.15.5.tar.bz2 : 内核源码包

     

    第一步:

        将上面所有软件都拷贝到虚拟机中(非/mnt下)

    第二步:

        将内核源码包linux-2.6.15.5.tar.bz2 放入/usr/src目录中,并用 tar -jxvf linux-2.6.15.5.tar.bz2 将其解压

        ,然后用命令将linxu符号连接映射到linux-2.6.15.5 :ln -s  linux-2.6.15.5  linux

    第三步:rpm -ivh gcc-3.2.2-5.i386.rpm 安装GCC

    第三步:安装ncurses-5.6.tar.gz

               tar zxvf  ncurses-5.6.tar.gz   

               进入解压后目录 ./configure 

               make 

               make install  安装

    第四步:安装bison-2.4.tar.gz

                ./configure --prefix=/usr
                 make

                 make check  :本软件包自带测试套件,能执行一些测试,以确定它是否编译正确

                 make install

    第五步:安装flex-2.5.35.tar.bz2

    ./configure --prefix=/usr &&
    make &&
    make install

    一些程序并不知道flex而是试图寻找lex程序(事实上,flex是实现lex功能的另一种也是更好的选择)。为了满足少数一些程序的需要,我们将创建一个lex脚本,这个脚本调用flex并通过它来模仿lex的输出文件命名惯例。

    通过下面的命令创建一个新文件 /usr/bin/lex : 

    cat > /usr/bin/lex << "EOF"
    #!/bin/sh
    # Begin /usr/bin/lex

    exec /usr/bin/flex -l "$@"

    # End /usr/bin/lex
    EOF
    chmod 755 /usr/bin/lex

     

    第六步:升级m4

    首先要卸载原来的m4,然后再安装新的m4

     

    ./configure --prefix=/usr

                 make

                 make check  :本软件包自带测试套件,能执行一些测试,以确定它是否编译正确

                 make install

     

    第七步:安装modutils-2.4.26.tar.bz2 (如果原来安装了modutils 需要将其卸载:rpm -e --nodeps modutils)

    (由于Linux2.6内核的内核模块处理过程有所改变,因此Linux2.4内核下的modutils工具包已不在适合Linux2.6内核).

     ./configure &&
    make &&
    make install

    (参照:http://docs.huihoo.com/lfs/lfs-4.0/chapter06/modutils.html)

    第八步:安装module-init-tools-3.2.2.tar.bz2

    tar -jxvf module-init-tools-3.2.2.tar.bz2

    ./configure --prefix=/sbin

    make

    make install

    ./generate-modprobe.conf /etc/modprobe.conf

     

    第九步:配置内核

    make menuconfig

     

    第十步:编译内核

    make bzImage (生成使用gzip压缩的内核,生成的文件位于/usr/src/linux/arch/i386/boot目录)

    make modules modules_install(编译内核模块并安装到/lib/modules/2.6.15.5目录)

  • 使用avilib进行avi文件的读写

    2009-04-16 21:40:18

    使用avilib进行avi文件的读写
     
    #include <stdio.h>
    #include <stdlib.h>
    #ifdef _WIN32
    #include <windows.h>
    #else
    typedef long DWORD;
    #endif
    #include "avilib.h"
    /*
    avi转avi
    by notsobad
    使用avilib进行avi文件的读写
    这个例子是用来将一个avi写入另一个avi
    just a simple test
    */



    int main(int argc, const char **argv)
    {
    char * vidbuf = malloc(327680);
    char * audbuf = malloc(100000);
    long framesize;
        avi_t *avifile;
        avi_t* pAviHandle;
        int frame;
        int frames;
        int framew = 0;
        int frameh = 0;
        double framerate = 0.0f;


        pAviHandle = AVI_open_output_file("test.avi");   
        if(!pAviHandle)
        {        
                printf("avi file open failed \n");
                return -1;
        }
        avifile = AVI_open_input_file("../vp31.avi", 1); //打开源文件
        frames = AVI_video_frames(avifile);
        framew = AVI_video_width(avifile);
        frameh = AVI_video_height(avifile);
        framerate = AVI_frame_rate(avifile);   

          AVI_set_video(pAviHandle,framew,frameh,framerate,AVI_video_compressor(avifile));//设置video文件头
          AVI_set_audio(pAviHandle, AVI_audio_channels(avifile),
                                    AVI_audio_rate(avifile),
                                    AVI_audio_bits(avifile),
                                    AVI_audio_format(avifile),
                                    AVI_audio_mp3rate(avifile));
           //设置audio文件头                        
        //framesize=(long)framew*frameh*3/2;
          if (AVI_seek_start( avifile )) //寻找文件开始
          {
                 printf("bad seek start\n");            
                 return -2;
          }
         for (frame. = 0; frame. < frames;frame++)//read and write avi per fream
         {
            int iskeyframe;//if it's keyframe
            AVI_set_video_position(avifile, frame);//将avi文件移动到第fream帧
            long audio_bytes=AVI_audio_size(avifile,frame);//get audo size
           
            AVI_read_frame(avifile,(char *)vidbuf,&iskeyframe);//read fream set video into vidbuf
            AVI_read_audio(avifile, (char *)audbuf,audio_bytes);//set audio into audbuf
            /*
            long AVI_audio_size(avi_t *AVI, long frame)
            int AVI_write_audio(avi_t *AVI, char *data, long bytes)
            long AVI_read_audio(avi_t *AVI, char *audbuf, long bytes)
            */
           AVI_write_frame(pAviHandle,vidbuf,AVI_frame_size(avifile, frame),iskeyframe);//write video fream by fream
           AVI_write_audio(pAviHandle,audbuf,audio_bytes);//write audio fream by fream

          }
          AVI_close(pAviHandle);   
          AVI_close(avifile);   
        return 1;
  • 关于QT程序在qvfb 和 Xwindows 运行的实例

    2009-04-09 20:46:05

     

    qclient.cpp

    /*********************************************************************
    *******
        ** $Id: /sample/10/qclient.h2.3.2edited 2004-10-12 $
        **
        ** Copyright (C) 2004-2005 OURSELEC AS. All rights reserved.
        ** Http://www.ourselec.com
        ** This file is part of an example program for Qt. This example
        ** program may be used, distributed and modified without limitation.
        **
    **********************************************************************
    *******/
    #include "qclient.h"
    #include <qsocket.h>
    #include <qapplication.h>
    #include <qvbox.h>
    #include <qhbox.h>
    #include <qtextview.h>
    #include <qlineedit.h>
    #include <qlabel.h>
    #include <qlayout.h>
    #include <qpushbutton.h>
    #include <qtextstream.h>
    #include <qpoint.h>
    QClient::QClient(QWidget *parent, const char *name)
        : QWidget(parent, name)
    {
        infoText = new QTextView(this);
        QHBox *hb = new QHBox(this);
        inputText = new QLineEdit(hb);
        QHBox *addrBox = new QHBox(this);
        QLabel *ip= new QLabel("IP:", addrBox, "ip");
        ip->setAlignment(1);
        addrText= new QLineEdit(addrBox);
        QLabel *port = new QLabel("PORT:", addrBox, "port");
        port->setAlignment(1);
        portText= new QLineEdit(addrBox);
        QHBox *buttonBox = new QHBox(this);
        QPushButton *send= new QPushButton(tr("Send"), hb);
        QPushButton *close = new QPushButton(tr("Close connection"),
                    buttonBox);
        QPushButton *quit= new QPushButton(tr("Quit"), buttonBox);
        QPushButton *Connect = new QPushButton(tr("Connect"),
                    addrBox);
        connect(send, SIGNAL(clicked()),
              SLOT(sendToServer()) );
        connect(close, SIGNAL(clicked()),
              SLOT(closeConnection()) );
        connect(quit, SIGNAL(clicked()),
              qApp, SLOT(quit()) );
        connect(Connect, SIGNAL(clicked()),
              SLOT(connectToServer()) );
        socket = new QSocket(this);
        connect(socket, SIGNAL(connected()),
                SLOT(socketConnected()) );
        connect(socket, SIGNAL(connectionClosed()),
                SLOT(socketConnectionClosed()) );
        connect(socket, SIGNAL(readyRead()),
                SLOT(socketReadyRead()) );
        connect(socket, SIGNAL(error(int)),
                SLOT(socketError(int)) );
        QVBoxLayout *l = new QVBoxLayout(this);
        l->addWidget(infoText, 10);
        l->addWidget(hb, 1);
        l->addWidget(addrBox, 1);
        l->addWidget(buttonBox, 1);
       //connect to the server
       infoText->append(tr("Tying to connect to the server"));
    }

    void QClient::closeConnection()
    {
         socket->close();
         if (QSocket::Closing == socket->state()) {
              // We have a delayed close
              connect(socket, SIGNAL(delayedCloseFinished()),
                        SLOT(socketClosed()));
         } else {
              // The socket is closed
              socketClosed();
         }
    }

    void QClient::sendToServer()
    {
         // write to the server
         if (QSocket::Connection == socket->state()) {
              QTextStream os(socket);
              os << inputText->text() << "\n";
              inputText->setText("");
         } else {
              // The socket is unconnected
              infoText->append(tr("The server is lost\n"));
         }
    }

    void QClient::connectToServer()
    {
         // connect to the server
         socket->connectToHost(addrText->text(), (portText->text()).toInt());
    }

    void QClient::socketReadyRead()
    {
         // read from the server
         while (socket->canReadLine()) {
               infoText->append(socket->readLine());
         }
    }

    void QClient::socketConnected()
    {
         infoText->append(tr("Connected to server\n"));
    }

    void QClient::socketConnectionClosed()
    {
         infoText->append(tr("Connection closed by the server\n"));
    }

    void QClient::socketClosed()
    {
         infoText->append(tr("Connection closed\n"));
    }

    void QClient::socketError(int e)
    {
         if (e == QSocket::ErrConnectionRefused) {
               infoText->append(tr("Connection Refused\n"));
         } else if (e == QSocket::ErrHostNotFound) {
               infoText->append(tr("Host Not Found\n"));
         } else if (e == QSocket::ErrSocketRead) {
               infoText->append(tr("Socket Read Error\n"));
         }
    }

    qclient.h

     /**************************************************************
        **************
        ** $Id: /sample/10/qclient.h2.3.2edited 2004-10-12 $
        **
        ** Copyright (C) 2004-2005 OURSELEC AS. All rights reserved.
        ** Http://www.ourselec.com
        ** This file is part of an example program for Qt. This example
        ** program may be used, distributed and modified without limitation.
        **
    **********************************************************************
    *******/
    #ifndef QCLIENT_H
    #define QCLIENT_H

    #include <qsocket.h>
    #include <qapplication.h>
    #include <qvbox.h>
    #include <qhbox.h>
    #include <qtextview.h>
    #include <qlineedit.h>
    #include <qlabel.h>
    #include <qpushbutton.h>
    #include <qtextstream.h>
    class QClient : public QWidget
    {
         Q_OBJECT
    public:
         QClient(QWidget *parent = 0, const char *name = 0);
    private slots:
      void closeConnection();     //关闭网络连接
         void sendToServer();     //发送数据到服务器端
         void connectToServer();
         void socketReadyRead();
         void socketConnected();
         void socketConnectionClosed();
         void socketClosed();
         void socketError(int);
    private:
         QSocket *socket;
         QTextView *infoText;
         QLineEdit *addrText;
         QLineEdit *portText;
         QLineEdit *inputText;
    };
    #endif
    //QCLIENT_H

    main.cpp

    /*********************************************************************
    *******
        ** $Id: /sample/10/main.cpp2.3.2edited 2004-10-12 $
        **
        ** Copyright (C) 2004-2005 OURSELEC AS. All rights reserved.
        ** Http://www.ourselec.com
        ** This file is part of an example program for Qt. This example
     ** program may be used, distributed and modified without limitation.
        **
    **********************************************************************
    *******/

    #include <qapplication.h>
    #include "qclient.h"
    int main( int argc, char **argv )
    {
         QApplication app( argc, argv );
         QClient *client = new QClient( 0 );
         app.setMainWidget( client );
         client->show();
         int result = app.exec();
         return result;
    }

    server.cpp

    /*********************************************************************
    *******
        ** $Id: qt/server.cpp 3.0.5edited Oct 12 2001 $
        **
        ** Copyright (C) 1992-2000 Trolltech AS. All rights reserved.
        **
        ** This file is part of an example program for Qt. This example
        ** program may be used, distributed and modified without limitation.
        **
    **********************************************************************
    *******/

    #include <qsocket.h>
    #include <qserversocket.h>
    #include <qapplication.h>
    #include <qvbox.h>
    #include <qtextview.h>
    #include <qlabel.h>
    #include <qpushbutton.h>
    #include <qtextstream.h>

    #include <stdlib.h>
    /*The ClientSocket class provides a socket that is connected with a client.
    For every client that connects to the server, the server creates a new
    instance of this class.
    */
    class ClientSocket : public QSocket
    {
         Q_OBJECT
    public:
         ClientSocket( int sock, QObject *parent=0, const char *name=0 ) :
              QSocket( parent, name )
         {
              line = 0;
              connect( this, SIGNAL(readyRead()), SLOT(readClient()) );
              connect( this, SIGNAL(connectionClosed()),
                        SLOT(connectionClosed()) );
              setSocket( sock );
         }
         ~ClientSocket()
         {
         }
    private slots:
         void readClient()
         {
               while ( canReadLine() ) {
                   QTextStream os( this );
                   os << line << ": " << readLine();
                   line++;
               }
         }
          void connectionClosed()
        {
               delete this;
         }
    private:
         int line;
    };


    /*
    The SimpleServer class handles new connections to the server. For every
    client that connects, it creates a new ClientSocket -- that instance is now
    responsible for the communication with that client.
    */

    class SimpleServer : public QServerSocket
    {
         Q_OBJECT
    public:
         SimpleServer( QObject* parent=0 ) :
             QServerSocket( 4242, 1, parent )
         {
             if ( !ok() ) {
                   qWarning("Failed to bind to port 4242");
                   exit(1);
             }
         }
         ~SimpleServer()
        {
        }
         void newConnection( int socket )
        {
           (void)new ClientSocket( socket, this );
            emit newConnect();
         }
    signals:
         void newConnect();
    };
    /*
    The ServerInfo class provides a small GUI for the server. It also creates the
    SimpleServer and as a result the server.
    */
    class ServerInfo : public QVBox
    {
         Q_OBJECT
    public:
         ServerInfo()
         {
              SimpleServer *server = new SimpleServer( this );
            QString itext = QString(

                   "This is a small server example.\n"
                 "Connect with the client now."
              );
            QLabel *lb = new QLabel( itext, this );
            lb->setAlignment( AlignHCenter );
            infoText = new QTextView( this );
            QPushButton *quit = new QPushButton( "Quit" , this );
            connect( server, SIGNAL(newConnect()),
            SLOT(newConnect()) );
            connect( quit, SIGNAL(clicked()), qApp, SLOT(quit()) );
       }
          ~ServerInfo()
         {
         }
    private slots:
         void newConnect()
         {
               infoText->append( "New connection\n" );
         }
    private:
         QTextView *infoText;
    };

    int main( int argc, char** argv )
    {
         QApplication app( argc, argv );
         ServerInfo info;
         app.setMainWidget( &info );
         info.show();
         return app.exec();
    }
    #include "server.moc"

    其中,qclient.h qclient .cpp  main.cpp   是客户端程序  server.cpp 是服务端程序

    服务器端和客户端的程序单独编译后,一个在 Qvfb 上运行,

    一个在 X 上运行,结果如图所示:

     

  • 关于QT程序在qvfb 和 Xwindows 运行的一点理解

    2009-04-09 20:19:29

    关于QT程序在qvfb 和 Xwindows 运行的一点理解

            关于QT 的环境安装见《 安装与建立QT/E桌面的运行环境》   一文。    

            要使QT程序在不同的平台运行,就得重新编译源程序, 它是一次编写,处处编译的,

        要在qvfb   上运行用下面的环境变量配置:

    export QTDIR=/s3c2410_linux/QT/qt-2.3.7
    export TMAKE=/s3c2410_linux/QT/tmake
    export TMAKEPATH=$TMAKE/lib/qws/linux-x86-g++
    export PATH=$PATH:$TMAKE/bin
    export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH

        

        要在Xwindows 上运行用下面的环境变量配置:

    #bash set environment for QTX11
    export QTDIR=$PWD/qt-x11-2.3.2
    export PATH=$QTDIR/bin:$PATH
    export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH
    export TMAKEPATH=$PWD/tmake/lib/linux-g++
    export PATH=$TMAKEPATH:$PATH

             注 意除了在QTDIR和LD_LIBRARY_PATH的不同,还有TMAKEPATH的不同,因为一般嵌入式设备上都是在framebuffer的方式,在X11上就是用qvfb 来模拟framebuffer的。

            要用framebuffer立式运程序就要用tmake\lib\qws\下的对应的编译器,这里是linux-x86平台,所以用linux-x86-g++。惹不是framebuffer就要用\tmake\lib\下的,这里是linux-g++。

    tmake\lib\qws\linux-x86-g++\tmake.conf

    # $Id: tmake.conf,v 1.1 1999/11/08 03:06:27 warwick Exp $
    #
    # tmake configuration for linux-g++
    #

    TEMPLATE   = app
    CONFIG    = qt warn_on release

    TMAKE_CC   = gcc
    TMAKE_CFLAGS   = -pipe
    TMAKE_CFLAGS_WARN_ON = -Wall -W
    TMAKE_CFLAGS_WARN_OFF =
    TMAKE_CFLAGS_RELEASE = -O2 -fno-default-inline
    TMAKE_CFLAGS_DEBUG = -g
    TMAKE_CFLAGS_SHLIB = -fPIC
    TMAKE_CFLAGS_YACC = -Wno-unused -Wno-parentheses
    TMAKE_CFLAGS_THREAD = -D_REENTRANT

    TMAKE_CXX   = g++
    TMAKE_CXXFLAGS   = $$TMAKE_CFLAGS -DQWS -fno-exceptions -fno-rtti
    TMAKE_CXXFLAGS_WARN_ON = $$TMAKE_CFLAGS_WARN_ON
    TMAKE_CXXFLAGS_WARN_OFF = $$TMAKE_CFLAGS_WARN_OFF
    TMAKE_CXXFLAGS_RELEASE = $$TMAKE_CFLAGS_RELEASE
    TMAKE_CXXFLAGS_DEBUG = $$TMAKE_CFLAGS_DEBUG
    TMAKE_CXXFLAGS_SHLIB = $$TMAKE_CFLAGS_SHLIB
    TMAKE_CXXFLAGS_YACC = $$TMAKE_CFLAGS_YACC
    TMAKE_CXXFLAGS_THREAD = -D_REENTRANT

    TMAKE_INCDIR   =
    TMAKE_LIBDIR   =
    TMAKE_INCDIR_X11 =
    TMAKE_LIBDIR_X11 =
    TMAKE_INCDIR_QT   = $(QTDIR)/include
    TMAKE_LIBDIR_QT   = $(QTDIR)/lib
    TMAKE_INCDIR_OPENGL = /usr/X11R6/include
    TMAKE_LIBDIR_OPENGL = /usr/X11R6/lib

    TMAKE_LINK   = g++
    TMAKE_LINK_SHLIB = g++
    TMAKE_LFLAGS   =
    TMAKE_LFLAGS_RELEASE =
    TMAKE_LFLAGS_DEBUG =
    TMAKE_LFLAGS_SHLIB = -shared
    TMAKE_LFLAGS_SONAME = -Wl,-soname,
    TMAKE_LFLAGS_THREAD =
    TMAKE_RPATH   = -Wl,-rpath,

    TMAKE_LIBS   =
    TMAKE_LIBS_X11   =
    TMAKE_LIBS_X11SM =
    TMAKE_LIBS_QT   = -lqte
    TMAKE_LIBS_QT_THREAD = -lqte-mt
    TMAKE_LIBS_OPENGL =
    TMAKE_LIBS_THREAD = -lpthread

    TMAKE_MOC   = $(QTDIR)/bin/moc
    TMAKE_UIC   = $(QTDIR)/bin/uic

    TMAKE_AR   = ar cqs
    TMAKE_RANLIB   =

    TMAKE_TAR   = tar -cf
    TMAKE_GZIP   = gzip -9f

    tmake\lib\linux-g++\tmake.conf

    #
    # $Id: tmake.conf,v 1.15 1999/07/28 15:21:49 hanord Exp $
    #
    # tmake configuration for linux-g++
    #

    TEMPLATE   = app
    CONFIG    = qt warn_on release

    TMAKE_CC   = gcc
    TMAKE_CFLAGS   = -pipe
    TMAKE_CFLAGS_WARN_ON = -Wall -W
    TMAKE_CFLAGS_WARN_OFF =
    TMAKE_CFLAGS_RELEASE = -O2
    TMAKE_CFLAGS_DEBUG = -g
    TMAKE_CFLAGS_SHLIB = -fPIC
    TMAKE_CFLAGS_YACC = -Wno-unused -Wno-parentheses

    TMAKE_CXX   = g++
    TMAKE_CXXFLAGS   = $$TMAKE_CFLAGS
    TMAKE_CXXFLAGS_WARN_ON = $$TMAKE_CFLAGS_WARN_ON
    TMAKE_CXXFLAGS_WARN_OFF = $$TMAKE_CFLAGS_WARN_OFF
    TMAKE_CXXFLAGS_RELEASE = $$TMAKE_CFLAGS_RELEASE
    TMAKE_CXXFLAGS_DEBUG = $$TMAKE_CFLAGS_DEBUG
    TMAKE_CXXFLAGS_SHLIB = $$TMAKE_CFLAGS_SHLIB
    TMAKE_CXXFLAGS_YACC = $$TMAKE_CFLAGS_YACC

    TMAKE_INCDIR   =
    TMAKE_LIBDIR   =
    TMAKE_INCDIR_X11 = /usr/X11R6/include
    TMAKE_LIBDIR_X11 = /usr/X11R6/lib
    TMAKE_INCDIR_QT   = $(QTDIR)/include
    TMAKE_LIBDIR_QT   = $(QTDIR)/lib
    TMAKE_INCDIR_OPENGL = /usr/X11R6/include
    TMAKE_LIBDIR_OPENGL = /usr/X11R6/lib

    TMAKE_LINK   = g++
    TMAKE_LINK_SHLIB = g++
    TMAKE_LFLAGS   =
    TMAKE_LFLAGS_RELEASE =
    TMAKE_LFLAGS_DEBUG =
    TMAKE_LFLAGS_SHLIB = -shared
    TMAKE_LFLAGS_SONAME = -Wl,-soname,

    TMAKE_LIBS   =
    TMAKE_LIBS_X11   = -lXext -lX11 -lm
    TMAKE_LIBS_X11SM = -lICE -lSM
    TMAKE_LIBS_QT   = -lqt
    TMAKE_LIBS_QT_OPENGL = -lqgl
    TMAKE_LIBS_OPENGL = -lMesaGL -lMesaGLU -lXmu

    TMAKE_MOC   = moc

    TMAKE_AR   = ar cqs
    TMAKE_RANLIB   =

    TMAKE_TAR   = tar -cf
    TMAKE_GZIP   = gzip -9f

    看了一下致有这些不同


    TMAKE_CFLAGS_RELEASE = -O2 -fno-default-inline    qws

    TMAKE_CFLAGS_RELEASE = -O2
                          
    TMAKE_CXXFLAGS   = $$TMAKE_CFLAGS -DQWS -fno-exceptions -fno-rtti    qws

    TMAKE_CXXFLAGS   = $$TMAKE_CFLAGS

    TMAKE_CXXFLAGS_THREAD = -D_REENTRANT           qws

    TMAKE_INCDIR_X11 = /usr/X11R6/include
    TMAKE_LIBDIR_X11 = /usr/X11R6/lib


    TMAKE_RPATH   = -Wl,-rpath,       qws

    下面看看*.pro 有什么不同。

       server/server.pro (qvfb :$TMAKE/lib/qws/linux-x86-g++)
             TEMPLATE = app
              CONFIG   = qt warn_on release
             HEADERS   =
             SOURCES   = server.cpp
             TARGET   = server


    server/server.pro (Xwindows :tmake/lib/linux-g++)
             TEMPLATE = app
             CONFIG   = qt warn_on release
             HEADERS   =
              SOURCES   = server.cpp
              TARGET   = server
    它们的工程文件是一样的。

    下面看看Makefile有什么不同。

    server/Makefile (qvfb :$TMAKE/lib/qws/linux-x86-g++)

    #############################################################################
    # Makefile for building server
    # Generated by tmake at 14:44, 2008/07/15
    #     Project: server
    #    Template: app
    #############################################################################

    ####### Compiler, tools and options

    CC = gcc
    CXX = g++
    CFLAGS = -pipe -Wall -W -O2 -fno-default-inline -DNO_DEBUG
    CXXFLAGS= -pipe -DQWS -fno-exceptions -fno-rtti -Wall -W -O2 -fno-default-inline -DNO_DEBUG
    INCPATH = -I$(QTDIR)/include
    LINK = g++
    LFLAGS =
    LIBS = -L$(QTDIR)/lib -lqte
    MOC = $(QTDIR)/bin/moc

    TAR = tar -cf
    GZIP = gzip -9f

    ####### Files

    HEADERS =
    SOURCES = server.cpp
    OBJECTS = server.o
    SRCMOC = server.moc
    OBJMOC =
    DIST =
    TARGET = server

    ####### Implicit rules

    .SUFFIXES: .cpp .cxx .cc .C .c

    .cpp.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .cxx.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .cc.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .C.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .c.o:
    $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $<

    ####### Build rules

    all: $(TARGET)

    $(TARGET): $(OBJECTS) $(OBJMOC)
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(LIBS)

    moc: $(SRCMOC)

    tmake: Makefile

    Makefile: server.pro
    tmake server.pro -o Makefile

    dist:
    $(TAR) server.tar server.pro $(SOURCES) $(HEADERS) $(DIST)
    $(GZIP) server.tar

    clean:
    -rm -f $(OBJECTS) $(OBJMOC) $(SRCMOC) $(TARGET)
    -rm -f *~ core

    ####### Compile

    server.o: server.cpp \
       server.moc

    server.moc: server.cpp
    $(MOC) server.cpp -o server.moc

    server/Makefiel (Xwindows :tmake/lib/linux-g++)

    #############################################################################
    # Makefile for building server
    # Generated by tmake at 10:47, 2008/07/15
    #     Project: server
    #    Template: app
    #############################################################################

    ####### Compiler, tools and options

    CC = gcc
    CXX = g++
    CFLAGS = -pipe -Wall -W -O2 -DNO_DEBUG
    CXXFLAGS= -pipe -Wall -W -O2 -DNO_DEBUG
    INCPATH = -I$(QTDIR)/include
    LINK = g++
    LFLAGS =
    LIBS = -L$(QTDIR)/lib -lqt -L/usr/X11R6/lib -lXext -lX11 -lm
    MOC = moc

    TAR = tar -cf
    GZIP = gzip -9f

    ####### Files

    HEADERS =
    SOURCES = server.cpp
    OBJECTS = server.o
    SRCMOC = server.moc
    OBJMOC =
    DIST =
    TARGET = server

    ####### Implicit rules

    .SUFFIXES: .cpp .cxx .cc .C .c

    .cpp.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .cxx.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .cc.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .C.o:
    $(CXX) -c $(CXXFLAGS) $(INCPATH) -o $@ $<

    .c.o:
    $(CC) -c $(CFLAGS) $(INCPATH) -o $@ $<

    ####### Build rules

    all: $(TARGET)

    $(TARGET): $(OBJECTS) $(OBJMOC)
    $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(OBJMOC) $(LIBS)

    moc: $(SRCMOC)

    tmake: Makefile

    Makefile: server.pro
    tmake server.pro -o Makefile

    dist:
    $(TAR) server.tar server.pro $(SOURCES) $(HEADERS) $(DIST)
    $(GZIP) server.tar

    clean:
    -rm -f $(OBJECTS) $(OBJMOC) $(SRCMOC) $(TARGET)
    -rm -f *~ core

    ####### Compile

    server.o: server.cpp \
       server.moc

    server.moc: server.cpp
    $(MOC) server.cpp -o server.moc

    下面几项Makefile有所不同。

    server/Makefile (qvfb :$TMAKE/lib/qws/linux-x86-g++)


    CFLAGS = -pipe -Wall -W -O2 -fno-default-inline -DNO_DEBUG
    CXXFLAGS= -pipe -DQWS -fno-exceptions -fno-rtti -Wall -W -O2 -fno-default-inline -DNO_DEBUG


    LIBS = -L$(QTDIR)/lib -lqte
    MOC = $(QTDIR)/bin/moc

    server/Makefiel (Xwindows :tmake/lib/linux-g++)


    CFLAGS = -pipe -Wall -W -O2 -DNO_DEBUG
    CXXFLAGS= -pipe -Wall -W -O2 -DNO_DEBUG

    LIBS = -L$(QTDIR)/lib -lqt -L/usr/X11R6/lib -lXext -lX11 -lm
    MOC = moc

    红色比较主要。

662/4<1234>
Open Toolbar