分类: LINUX
2009-08-30 21:46:16
一、多功能设备与linux系统的通讯………………………………………………….3
二、HID USB设备驱动………………………………………………………………..4
三、NEC协议改RC5红外协议……………………………………………………....8
1、NEC协议和RC5协议编码……………………………………………………..8
2、GPIO模拟触发中断……………………………………………………………10
3、IR模块驱动程序…………………………………………………………….....10
4、测试中断………………………………………………………………………..11
四、基于标准鼠标设备porting层的开发…………………………………………...12
1、VO显示子平面…………………………………………………………………12
2、Porting层cursor代码开发……………………………………………………..13
五、制作鼠标图标……………………………………………………………………15
六、总结………………………………………………………………………………17
一、多功能输入设备与linux系统的通讯
思路:首先在标准linux系统下面测试,看是否可以与多功能设备正常通讯,能否读到我们的报文数据。设备靠驱动程序来驱动,如果不能识别需要编译驱动模块。
1)linux下设备操作都是跟文件一样,用read、write、ioctl等等来操作。
2)查对应的设备号和设备名,测试时需要打开对应设备,然后从设备上去读数据。
3)linux下的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中的消息传递机制:
设备有着自己特殊的按键键码,需要将一些标准的按键,比如0-9,X-Z等模拟成标准按键,比如KEY_0,KEY-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_REL,code值表示轨迹的类型。如指示鼠标的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_
#define EV_CNT (EV_
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时,表示鼠标右键弹起
(3)select的上键,相当于鼠标的滚轮向上滑动
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总线上的设备信息,包括Vendor、ProdID、Product。其中我们最关心的是Vendor、ProdID、Manufacturer、product等信息。
第二步:打开/usr/src/linux-
UNUSUAL_DEV(
“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文件系统,则在定制内核配置文件时把对ext3、ext2文件的支持直接编译进内核,否则当启用新的内核时机器会死掉,出现错误信息如下: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.ko、usb-uhci.ko 、usb-ohci.ko也是不可缺少的。另外,如果系统中SCSI支持也是模块方式,那么下面的模块也必须加载:#insmod scsi-mod.ko sd-mod.ko
在加载了这些模块后,我们插入U盘或者存储卡,就会发现系统中多个scsi硬盘,通过正
确mount它,就可以使用他们(scsi硬盘一般为/dev/sda1)# mount /dev/sda1 /mnt
9、scsi驱动模块:
scsi-mod.ko 对scsi设备的支持
sd-mod.ko 对scsi硬盘支持模块,针对usb硬盘
sr-mod.ko 对scsi光盘支持模块,针对usb光驱
ide-scsi.ko 该模块可以把ide设备模拟成scsi接口
10、内核编译
(1) HI3110E内核的根目录/root/X5STBV100R
因为盒子里自带两个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
11、查看.config配置文件
(1)input设备选择如下:
********* 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
(2)USB设备配置如下:
************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/X5STBV100R
下面的input和usb两个目录下面,注意一定要是.ko驱动模块(linux2.6内核只支持.ko驱动模块的动态插入、卸载)
12、查看驱动模块信息:
#lsmod 查看当前已经挂载的文档
#dmesg 查看模块输出信息
#rmmod 卸载模块
13、驱动程序的编写(以字符设备test在S
(1)申明头文件和全局变量
(2)read函数扩充
(3)write函数扩充
(4)ioctl函数扩充
(5)release函数扩充
(6)file_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/
在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里的两种协议、TC、sony共四种协议,而遥控器支持RC5\RC6协议,因此需要改掉底层的红外协议。面临的问题:
1)NEC协议和RC5协议的区别,编码规则分别是什么?
2)该用什么方式来触发中断?
3)怎么去捕捉中断?
4)怎么测试中断?
5)捕捉到中断后,怎么去抓键值,然后映射到ipanel中间件?
1、NEC协议和RC5协议编码
(1)NEC协议采用脉冲位置编码方式,利用脉冲间的时间间隔来区分“
位定义如下:
若以高电平触发,经过2.25ms后,再来一个高电平则触发,这个脉冲逻辑“1”,下个为逻辑“0”,其中560us以脉冲裕量(允许的误差),以时间间隔来区分逻辑“1”和逻辑“0”,分别是2.25ms和1.125ms。
(2)RC5协议编码
RC5协议采用双相编码方式,用不同的相位来代表“
其中每一位为1.8ms,高低电平各占0.9ms。所有的位都是高低电平混合,并且有的位是先高电平后低电平,而有的位是先低电平后高电平。由于时间间隔都一样,所以在采用上升沿或者下降沿来触发产生中断时,需要设法来切换这种变换方式,如果挫位挫了一位,整个数据都会错误。所以在HI3110e上采用GPIO模拟触发中断。
2、GPIO模拟触发中断
基本步骤:设置哪个管脚为输入---->设置中断触发方式(以哪个边沿是上升沿还是下降沿)---->清除中断标志----->使能引脚中断控制中断
海思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:中断号,hi3110e中IR模块的中断号是13
hi_ir_interrupt : 中断函数
SA_SHIRQ|SA_INTERRUPT:中断共享和快速中断标志
IR_DEVICE_NAME:中断名称,比如hi_ir
4、 linux2.6内核驱动程序makefile的编写:
obj-m += hi_irda.o
KERNELDIR = /root/X5STBV100R
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.ko(insmod模块时,会看到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引脚的定义不同,然后我们改用新文档里的GPIO_B 3 GPIO_G 3,重新编译调试,这次终于可以捕捉到中断了。
总结:这部分耽误了不少时间,碰到了问题不能总停留在一个点上,想方设法从其他角度考虑,尽快找出问题的根本所在,避免影响项目进度。