Chinaunix首页 | 论坛 | 博客
  • 博客访问: 34815
  • 博文数量: 13
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 150
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-11 11:28
文章分类

全部博文(13)

文章存档

2011年(1)

2009年(12)

我的朋友
最近访客

分类: LINUX

2009-07-11 11:37:34

一、多功能输入设备与linux系统的通讯

思路:首先在标准linux系统下面测试,看是否可以与多功能设备正常通讯,能否读到我们的报文数据。设备靠驱动程序来驱动,如果不能识别需要编译驱动模块。

1linux下设备操作都是跟文件一样,用readwriteioctl等等来操作。

2)查对应的设备号和设备名,测试时需要打开对应设备,然后从设备上去读数据。

3linux下的input设备系统的消息传递机制和原理。

4)根据资料中的报文信息来解析我们的数据,看接受到的数据是否正确。

1、多功能usb设备为HID(人机交换)设备,一般windows下面有驱动程序,插入windows下可以自动识别。但是在嵌入式linux平台比如机顶盒下面,由于内核和根文件系统是根据自己的需要来裁剪和制作的,一般没有其驱动,需要重新编译配置内核,生成.ko驱动模块,然后插入盒子的内核。

2、从硬盘分区中创建usb设备的挂载点:

#mount   /dev/sda1  /mnt/usb

3、鼠标点击进入linux虚拟机,插入usb设备,这样会在下面产生一个驱动的图标。

4# dmesg (查看模块的具体信息)

这样我们可以看到设备本身的名称以及在linux系统中对应的设备名称hiddev0 event0

5、  首先弄清input设备在linux中的消息传递机制:

设备有着自己特殊的按键键码,需要将一些标准的按键,比如09XZ等模拟成标准按键,比如KEY_0KEY-Z等,所以需要用到按键 模拟,具体方法就是操作/dev/input/event1文件,向它写入个input_event结构体就可以模拟按键的输入了。
linux/input.h中有定义,这个文件还定义了标准按键的编码等。
struct input_event {
    struct timeval time; //
按键时间
    __u16 type; //
类型,在下面有定义
    __u16 code; //
要模拟成什么按键
    __s32 value;//
是按下还是释放
};
code

   
事件的代码.如果事件的类型代码是EV_KEY,该代码code为设备键盘代码.代码植0~127为键盘上的按键代码,0x110~0x116 为鼠标上按键代码,其中0x110(BTN_ LEFT)为鼠标左键,0x111(BTN_RIGHT)为鼠标右键,0x112(BTN_ MIDDLE)为鼠标中键.其它代码含义请参看include/include/input.h文件。 如果事件的类型代码是EV_RELcode值表示轨迹的类型。如指示鼠标的X轴方向REL_X (代码为0x00),指示鼠标的Y轴方向REL_Y(代码为0x01),指示鼠标中轮子方向REL_WHEEL(代码为0x08)
type:

    EV_KEY,
键盘
    EV_REL,
相对坐标
    EV_ABS,
绝对坐标
value

   
事件的值.如果事件的类型代码是EV_KEY,当按键按下时值为1,松开时值为0;如果事件的类型代码是EV_ REL,value的正数值和负数值分别代表两个不同方向的值.
 Event types
    #define EV_SYN            0x00
    #define EV_KEY            0x01 //
按键

    #define EV_REL            0x02 //
相对坐标(轨迹球)
    #define EV_ABS            0x03 //
绝对坐标

    #define EV_MSC            0x04 //
其他
    #define EV_SW             0x05
    #define EV_LED            0x11 //LED
    #define EV_SND            0x12//
声音
    #define EV_REP            0x14//repeat
    #define EV_FF             0x15
    #define EV_PWR           0x16
    #define EV_FF_STATUS        0x17
    #define EV_MAX            0x1f
    #define EV_CNT            (EV_MAX+1)

 

6、写应用程序测试,看是否可以与linux系统正常通讯。

这样验证我们的设备是标准的HID鼠标设备,然后对着UEI公司提供的资料,如果报文ReportId等于2,是标准鼠标设备。测试有四个键是正常的,并且移动遥控器是mousemove事件,也可以收到对应的数据。

测试结果大致如下:

Event.type == EV_KEY时:

1)遥控器的select键相当鼠标左键:

event. value == 1时,表示按下鼠标左键

event. value == 0时,表示鼠标左键弹起

2)遥控器的Back键相当与鼠标右键:

event. value == 1时,表示按下鼠标右键

event. value == 0时,表示鼠标右键弹起

3select的上键,相当于鼠标的滚轮向上滑动

select的下键,相当与鼠标的滚轮向下滑动

event.type == EV_REL时:

表示遥控器在移动,若event.code = =0,表示向X方向移动,如果event.value为正值,表示向X方向的正方向移动;如果event.value为负值,表示向X方向的负方向移动。若event.code = =1,表示向Y方向移动,如果event.value为正值,表示向Y方向的正方向移动;如果event.value为负值,表示向Y方向的负方向移动。

总结:这部分我们总是只能测试到报文ReportID=2的报文数据,其他的两种数据接受不到,根据报文表去解析数据,完全没有半点规律。花了很多时间去考虑这个问题,以为UEI公司给的文档里的技术层次,他们都做到了。但是他们都是在windows下面开发的,而且只做到标准鼠标设备这个层次。问题:在项目开发之前,没有与UEI公司沟通了解他们的产品,根据他们的文档盲目的弄了好几天,这样浪费了不少时间。

 

二、HID USB设备驱动

思路:启动盒子,在/kmod目录下面之发现ehci-hcd.ko  ohci-hcd.ko,插入我们的多功能遥控器设备,半点反应都没有,一定是缺少hid usb设备的驱动模块。拿一般的U盘实验,发现可以识别,并且在/dev/scsi/host0/bus0/target0/lun0/part1能找到对应的设备,通过挂载可以对设备进行读写操作。面临的问题:

1)  查看hid设备需要哪些驱动模块的支持?

2)  编译配置内核,要生成对应的驱动模块

3)  将这些驱动模块插入内核识别后,该怎么去找它的设备号和设备名称,然后去测试是否正常通讯?

4)  测试是否可以跟标准linux系统一样能读到我们的报文数据?

5)  资料不全,没有kernel source code,无法编译内核,需要于海思沟通要资源。

 

       对于一般的U盘等移动存储设备,我们知道linux下都是通过usb-storage.o驱动模拟成scsi设备去支持的,有的设备之所以识别不了,通常是usb-storage驱动未包含此T厂商识别和产品识别信息。因此我们需要修改usb-storage中关于厂商识别和产品识别列表部分。

         第一步:通过cat  /proc/bus/usb/devices 得到当前系统探测到的usb总线上的设备信息,包括VendorProdIDProduct。其中我们最关心的是VendorProdIDManufacturerproduct等信息。

         第二步:打开/usr/src/linux-2.4.20-8/drivers/usb/storage/unusual-devs.h文件,我们可以看到所有已知的产品登记表,都是以UNUSUAL_DEV( idVendor,idProduct,bcdDeviceMin,bcdDeviceMax,Vendor_name,Product_name,use_protocol,usb_transport,init_function,Flags)方式登记的。其中相应的含义,可以根据命名来判断。

     UNUSUAL_DEV(07c4,a400,0x0000.0xffff,

“USB”,”Mass storage”,US_SC_SCSI,

US_PR_BULK,NULL,

US_FL_INQUIRY. |US_FL_START_STOP|

US_FL_MODE_XFATE)

     注意:添加以上几句的位置一定要正确。比较发现,usb-storage驱动对所有的注册都是按idVendor,idProduct数值从小到大排列的。

     填入以上信息后,可以重新编译生成内核或者usb-storage.ko模块,这样插入我们的设备就可以跟其他U盘一样作为SCSI设备去访问它。

    3110e平台上,当插入我们的u盘时,在/dev/scsi/host0/bus0/target0/lun0/part1 下面会出现一个设备,我们明显可以看到是把u盘当作SCSI设备来访问。然后我们可以挂载到其他目录进行读写:# mount -t vfat /dev/scsi/host0/bus0/target0/lun0/part1 /mnt

 

1、搭建3110e的环境(见搭建文档)

2、在用mkfs工具制作根文件系统时,如果提示找不到mkfs等工具,这时需要把mkfs.jffs2等工具当作编译器一样处理,把其路径添加到环境变量中去:export PATH = $PATH:/root/xxxxxxx/  (下面包含mkfs.jffs2工具)

3、如果系统是ext3文件系统,则在定制内核配置文件时把对ext3ext2文件的支持直接编译进内核,否则当启用新的内核时机器会死掉,出现错误信息如下:Kernel.pamc:no init found.try passing init=option to kernel………………………

4、  usb驱动:对于所有的usb设备,必须插入相应的usb控制器驱动模块(不同的平台名字不一样,make  menuconfig 可以看到对应的名字),例如海思3110e上的两个usb控制器驱动模块如下:ehci-hcd.ko    ohci-hcd.ko

5 一旦用新的已启用usb的内核重新引导后,若/proc/bus/usb下没有相应的usb设备,应输入以下命令将usb设备文件系统手动挂载到/proc/bus/usb:

# mount  –t  usbdevfs  none  /proc/bus/usb

为了在系统引导时自动挂载usb设备文件系统,将下面一行添加到

# /etc/bus/usb  usbdevfs  defaults  0  0

6、  usb鼠标为了使其正常工作,必须先插入模块usbmouse.ko mousedev.ko

# insmod  usbmouse.ko   mousedev.ko

7、如果是hid设备,在编译内核时需要HID input layer 支持和input core支持也作为模块方式安装,则需要插入usbhid.ko input.ko evdev.ko

#insmod usbhid.ko  input.ko    evdev.ko

8 u盘和usb读卡器

linux系统里这些设备都是一种叫做usb-storage方式进行驱动,需要使用他们必须加载此模块:# insmod  usb-storage.ko

当然,usbcore.kousb-uhci.ko usb-ohci.ko也是不可缺少的。另外,如果系统中SCSI支持也是模块方式,那么下面的模块也必须加载:#insmod  scsi-mod.ko   sd-mod.ko

  在加载了这些模块后,我们插入U盘或者存储卡,就会发现系统中多个scsi硬盘,通过正

mount它,就可以使用他们(scsi硬盘一般为/dev/sda1# mount  /dev/sda1  /mnt

9scsi驱动模块: 

scsi-mod.ko      scsi设备的支持

sd-mod.ko       scsi硬盘支持模块,针对usb硬盘

sr-mod.ko       scsi光盘支持模块,针对usb光驱

ide-scsi.ko      该模块可以把ide设备模拟成scsi接口

 

10、内核编译

(1)  HI3110E内核的根目录/root/X5STBV100R001C01B023/code/linux/kernel/linux-2.6.14.

因为盒子里自带两个usb控制器驱动模块ehci-hcd.ko ohci-hcd.ko,下面主要是编译input.ko  usbhid.ko  mousedev.ko  evdev.ko 四个驱动模块

2)在配置内核的时候,一定要选中下面几个选项:

Devices Drivers ---->Input device support---->

                          --------------- Generic input layer

                          ---------------- mouse

USB Support---->USB Human Interface Device(full  HID) support(这个菜单下面的所有选项最好选中)

11、查看.config配置文件

1input设备选择如下:

********* Input device support****************

CONFIG_INPUT=y

**************Userland interfaces***********

CONFIG_INPUT_MOUSEDEV=m

CONFIG_INPUT_MOUSEDEV_PSAUX=y

CONFIG_INPUT_MOUSEDEV_SCREEN_X=1024

CONFIG_INPUT_MOUSEDEV_SCREEN_Y=768

CONFIG_INPUT_JOYDEV=y

# CONFIG_INPUT_TSDEV is not set

CONFIG_INPUT_EVDEV=m

# CONFIG_INPUT_EVBUG is not set

 

**********Input Device Drivers**********

# CONFIG_INPUT_KEYBOARD is not set

CONFIG_INPUT_MOUSE=y

CONFIG_MOUSE_PS2=m

CONFIG_MOUSE_SERIAL=m

# CONFIG_MOUSE_VSXXXAA is not set

# CONFIG_INPUT_JOYSTICK is not set

# CONFIG_INPUT_TOUCHSCREEN is not set

# CONFIG_INPUT_MISC is not set

2USB设备配置如下:

************USB support***************

CONFIG_USB_ARCH_HAS_HCD=y

CONFIG_USB_ARCH_HAS_OHCI=y

CONFIG_USB=y

CONFIG_USB_DEBUG=y

 

**************USB Input Devices**************

CONFIG_USB_HID=m

CONFIG_USB_HIDINPUT=y

# CONFIG_HID_FF is not set

CONFIG_USB_HIDDEV=y

 

这样生成的驱动模块在/root/X5STBV100R001C01B023/code/linux/kernel/linux-2.6.14.

下面的inputusb两个目录下面,注意一定要是.ko驱动模块(linux2.6内核只支持.ko驱动模块的动态插入、卸载)

 

12、查看驱动模块信息:

#lsmod   查看当前已经挂载的文档

#dmesg   查看模块输出信息

#rmmod   卸载模块

 

13、驱动程序的编写(以字符设备testS3C2410开发板上为例子)

1)申明头文件和全局变量

2read函数扩充

3write函数扩充

4ioctl函数扩充

5release函数扩充

6file_operations test_fops = {};

7)注册函数

8)撤销函数

9)添加设备

<1>修改linux-2.14.x/driver/char/Makefile 在对应的位置添加一行:

Obj-$(CONFIG_TEST) += test.o

<2> linux-2.14.x/driver/char/config.in 对应位置添加一行:

        Bool  ‘test driver’  CONFIG_TEST

<3> 修改linux-2.14.x/driver/char/mem.c 在对应位置添加:

#ifdef  CONFIG_TEST

extern  void   test_init(void)

#endif

chr_dev_init()函数中添加:

#ifdef CONFIG_TEST

test_init();

#endif

<4> 修改vendor/sansumg/2410/makefile建立设备节点

  DEVICE部分添加:

       test, c, 254, 0

<5> make menuconfig 时,可以看到有testdevice ,这样将它选中,就可以编译进内核,这样固化在内核中。

 

14、测试驱动程序

1)将编译的驱动程序插入内核后,用lsmod命令可以看到插入的模块名称和设备号,也可以cat /proc/devices 查看。

2)创建设备节点:mknod  /dev/test   c  254  0(254是刚才查到的主设备号,0表示是由系统自动分配从设备号)

3)应用程序中要打开设备:fd = open(“/dev/test”, O_RDWR);这里可以设置阻塞BLOCK和非阻塞状态NONBLOCK

4)编译应用程序

gcc  -o  test_demo  test_demo.c

这样会生成可执行文件test_demo,然后运行./test_demo,可以测试到驱动模块。

总结:在编译配置hi3110e平台hid设备驱动模块比较顺利。在brcm7402平台上,调试了三天,编译生成的mousedev.ko   usbhid.ko  evdev.ko  input.ko  usbmouse.ko,都可以成功的插入到内核,但是总不能识别我们的多功能遥控设备,由于方法不对,耽误了几天时间,总以为是驱动程序模块的问题,原来厂商将usb模块的硬件改掉了,虽然盒子启动自动加载了ehci-hcd.ko ohci-hcd.ko两个usb控制器驱动。但是不管怎么调,插上我们的设备以及U盘连灯都不亮,然后在demo开发板上调试,插入对应模块,可以识别。

 

三、NEC协议改RC5红外协议

思路:查看hi3110e的底层IR模块的驱动,发现只支持NEC里的两种协议、TCsony共四种协议,而遥控器支持RC5\RC6协议,因此需要改掉底层的红外协议。面临的问题:

   1NEC协议和RC5协议的区别,编码规则分别是什么?

   2)该用什么方式来触发中断?

   3)怎么去捕捉中断?

   4)怎么测试中断?

   5)捕捉到中断后,怎么去抓键值,然后映射到ipanel中间件?

 

1NEC协议和RC5协议编码

1NEC协议采用脉冲位置编码方式,利用脉冲间的时间间隔来区分“0和“1”,编码规则如下:

位定义如下:

 

若以高电平触发,经过2.25ms后,再来一个高电平则触发,这个脉冲逻辑“1”,下个为逻辑“0”,其中560us以脉冲裕量(允许的误差),以时间间隔来区分逻辑“1”和逻辑“0”,分别是2.25ms1.125ms

 

2RC5协议编码

RC5协议采用双相编码方式,用不同的相位来代表“0和“1,编码规则如下:

其中每一位为1.8ms,高低电平各占0.9ms。所有的位都是高低电平混合,并且有的位是先高电平后低电平,而有的位是先低电平后高电平。由于时间间隔都一样,所以在采用上升沿或者下降沿来触发产生中断时,需要设法来切换这种变换方式,如果挫位挫了一位,整个数据都会错误。所以在HI3110e上采用GPIO模拟触发中断。

 

2GPIO模拟触发中断

基本步骤:设置哪个管脚为输入---->设置中断触发方式(以哪个边沿是上升沿还是下降沿)---->清除中断标志----->使能引脚中断控制中断

海思3110e的中断方式如下:

#define IR_GPIO_G  3

#define IR_GPIO_B  3

hi_gpio_dirset_bit(IR_GPIO_G, IR_GPIO_B, HI_GPIO_INPUT);

--------设置3 3 为管脚输入

hi_gpio_interruptdisable_bit(IR_GPIO_G, IR_GPIO_B);

--------使能屏蔽

hi_gpio_interruptsenseset_bit(IR_GPIO_G, IR_GPIO_B, HI_SENSE_EDGE);

-------设置为边沿触发

hi_gpio_interruptevenset_bit(IR_GPIO_G, IR_GPIO_B, HI_EVENT_FALLING_EDGE);

                                                  ---------设置下降沿触发

hi_gpio_interruptclear_bit(IR_GPIO_G, IR_GPIO_B);  

                                                  ---------清除中断标志

hi_gpio_interruptenable_bit(IR_GPIO_G, IR_GPIO_B);

                                                  ---------使能引脚中断

具体以哪个引脚来触发中断,查看板子datasheet图和原理图

 

3、  IR模块驱动程序

hi_ir_init()函数中:申请内存、注册设备、申请中断

 

1)注册设备:misc_register(&hi_ir_dev);

hi_ir_dev结构如下:

static struct miscdevice hi_ir_dev=

{

    MISC_DYNAMIC_MINOR,

    "hi_ir",

    &hi_ir_fops

};

 

hi_ir_fops结构如下:

static struct file_operations hi_ir_fops =

{

    owner    : THIS_MODULE,

    open     : hi_ir_open,

    ioctl     : NULL,

    poll     : hi_ir_select,

    read     : hi_ir_read,

    write    : NULL,

    release  : hi_ir_release,

};

里面的函数我们自己去填充。其中read函数是把内核态的数据读到用户态,而write函数或者ioctl函数是将用户态的数据写入内核态。

 

2)申请中断

request_irq(IR_DEVICE_IRQ_NO, &hi_ir_interrupt, SA_SHIRQ|SA_INTERRUPT, IR_DEVICE_NAME, &hi_ir_interrupt);

IR_DEVICE_IRQ_NO:中断号,hi3110eIR模块的中断号是13

hi_ir_interrupt        中断函数

SA_SHIRQ|SA_INTERRUPT:中断共享和快速中断标志

IR_DEVICE_NAME:中断名称,比如hi_ir

 

4、  linux2.6内核驱动程序makefile的编写:

obj-m += hi_irda.o

KERNELDIR = /root/X5STBV100R001C01B023/code/linux/kernel/linux-2.6.14(内核根目录)

PWD := $(shell pwd)

default:

$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:

rm -f hi_irda.ko hi_irda.mod.c hi_irda.mod.o hi_irda.o

这样直接make后,在当前目录(hi_irda.c所在目录)下面生成hi_irda.ko  hi_irda.mod.c hi_irda.mod.o hi_irda.o四个文件。(特别注意不能将用户程序的makefile拿来当驱动程序的makefile

 

5、  测试中断

将编译的hi_ir.ko插入盒子的内核(由于盒子自带一个hi_ir的驱动模块,是用NEC协议实现的),我们需要先卸掉盒子中的hi_ir,然后再动态插入我们的hi_ir.koinsmod模块时,会看到hi_ir_init()函数中的打印信息)。

1# cat /dev/misc/hi_ir

按下遥控器的键,会有反应,如果我们在中断处理函数里加了打印,这时会看到打印信息。

    2# cat /proc/devices

          可以看到我们注册设备的主设备号和名字

    3# cat /proc/interrupts

          可以看到我们申请的中断号是13,并且每按一次遥控器的按键,产生一次中断,这时候中断计数器会加1

值得注意的是:hi_ir_init()中必须要return 0.否则在编译成功后动态插入内核时,会出现exec format-1)的错误。所有的驱动程序初始化函数中都是这样。

在这一部分,碰到一个严重的问题:根据GPIO模拟中断原理和基本步骤,以及海思盒子的datasheet图以及原理图,编译生成的hi_ir.ko插入盒子内核总是不能响应中断,根据log查看,出现一个非法使用空指针的错误而导致内核core dump。下面有两种方法:一是换debug版本的内核,看调试信息来定义出错函数的位置和堆栈信息,但是这个盒子烧不了debug的内核。二是一个一个的函数仔细检查,发现没有空指针乱用的情况。下面再一次对照datasheet图和资料比较,发现资料都是基于debug内核版本的,而现在盒子里烧的是release版本的内核,然后与海思沟通,重新找他们要了一份datasheet图和原理图,发现两份文档里对IR模块GPIO管理员在2009年8月13日编辑了该文章文章。

-->
阅读(2120) | 评论(0) | 转发(0) |
0

上一篇:没有了

下一篇:HI3110E平台集成UEI多功能3D遥控器

给主人留下些什么吧!~~