链接
- JZ2440 数码相框项目 扩展项目介绍
- JZ2440 数码相框项目 扩展项目(一) 多文件图标 (二) 显示png
- JZ2440 数码相框项目 扩展项目(四) 加快显示速度
- JZ2440 实现截图 保存为png格式
- JZ2440 数码相框项目 扩展项目 1-4 源码下载
扩展项目三
1.目标
支持鼠标
2.分析
该程序已经实现了 input_manager.c 的抽象,支持鼠标在应用层只需要增加一个鼠标子类就可以完成,还要实现驱动方面的程序,获取 usb鼠标的输入,并通过 input子系统上报,应用层读取数据,实现数据的二次封装和鼠标指针的显示。
3.实现
a.usb鼠标驱动
USB驱动——描述符、URB、管道
在入口函数分配设置注册一个 (struct usb_driver)和一个(struct input_dev),
让应用程序可以通过 /dev/event* 来获取事件的发生。
出口函数注销这两个结构体。
通过打印数据来确定usb数据中哪些数据对应哪些按键和滑动。
这里直接贴我测试后的完整代码。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/hid.h>
#include <linux/usb/input.h>
#include <linux/usb.h>
#include <linux/slab.h>
struct def_btn_keyval {
int keyval; // 输入子系统定义的按键编号
unsigned char btn; // 我鼠标 byte0 数据比特位
};
static struct input_dev * mouse_input_dev; // 输入子系统对象
static struct urb* mouse_urb; // usb urb
// URB(USB Request Block,USB请求块)是USB数据传机制使用的核心数据结构。URB供USB协议栈使用。
// URB在include/linux/usb.h 文件中定义。
static char* mouse_buf_addr; // 供urb使用内存的虚拟地址
static unsigned int mouse_buf_phys_addr; // 供urb使用内存的物理地址
static int max_data_len; // usb 数据包最大长度
#define MOUSE_BTN_LEFT 0x01
#define MOUSE_BTN_RIGHT 0x02
#define MOUSE_BTN_MID 0x04
#define MOUSE_BTN_DOWN 0x08 // 侧边键1
#define MOUSE_BTN_UP 0x10 // 侧边键2
#define NUM_BTNS (5) // 我鼠标有五个按键
static struct def_btn_keyval btn_keyvals[NUM_BTNS] = {
{BTN_LEFT , MOUSE_BTN_LEFT },
{BTN_RIGHT , MOUSE_BTN_RIGHT},
{BTN_MIDDLE , MOUSE_BTN_MID },
{BTN_FORWARD, MOUSE_BTN_DOWN },
{BTN_BACK , MOUSE_BTN_UP },
};
static int isErrPtr(void * ptr)
{
return ptr == NULL || IS_ERR(ptr);
}
#define isErrPtr(ptr) (isErrPtr((void*)ptr)) // 是否是错误指针,是否是空指针
static int mouse_btn_event(unsigned char now) //鼠标按键事件
{
static unsigned char last;
register unsigned char tmp, btn;
register int i;
if((tmp = (now ^ last))) { // 计算有哪些按键状态发生了改变
last = now;
for(i=0;i<NUM_BTNS;i++) {
btn = btn_keyvals[i].btn;
if(tmp & btn) { // 如果按键状态有改变才上报
if(now & btn) // 当前状态是按下
input_report_key(mouse_input_dev, btn_keyvals[i].keyval, 1);
else // 当前状态是松开
input_report_key(mouse_input_dev, btn_keyvals[i].keyval, 0);
}
}
}
return tmp; // 无状态改变返回 0, 有状态改变返回 非0
}
static void mouse_irq(struct urb *urb) // usb urb 产生的事件
{
char * data = (char *)urb->transfer_buffer; // 获取 urb 使用的内存
register int sync = 0; // 是否需要上报事件
#if 0 // 用来测试哪些数据对应哪些事件
int datalen = urb->transfer_buffer_length;
static int cnt = 0;
register int i;
printk("%5d ",cnt++);
for(i=0;i<datalen;i++)
printk("%02x ",data[i]);
printk("\n");
#endif
sync = mouse_btn_event(data[0]); // Byte 0 包括5个按键的事件
if(data[3]) { // Byte 3 是滚轮,正数代表上滚,负数代表下滚
sync = 1;
input_report_rel(mouse_input_dev, REL_WHEEL, data[3]);
}
if(data[2] && data[2] == data[6]) { // Byte 2 6 是垂直方向,正数代表下移,负数代表上移
sync = 1;
input_report_rel(mouse_input_dev, REL_Y, data[2]);
}
if(data[1] && data[1] == data[4]) { // Byte 1 4 是水平方向,正数代表右移,负数代表左移
sync = 1;
input_report_rel(mouse_input_dev, REL_X, data[1]);
}
if(sync) // 上报事件
input_sync(mouse_input_dev);
usb_submit_urb (urb, GFP_ATOMIC); // 结束此次处理
}
static int mouse_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_device *dev = interface_to_usbdev(intf); // 获取usb设备对象
struct usb_host_interface *host_interface;
struct usb_endpoint_descriptor *endpoint;
printk("VID : %04x\n",dev->descriptor.idVendor); // 供应商代号
printk("PID : %04x\n",dev->descriptor.idProduct); // 生产商代号
host_interface = intf->cur_altsetting; // 接口当前的设置,包含接口描述符和该接口所拥有的端点
if (host_interface->desc.bNumEndpoints != 1) // 端点数量
return -ENODEV;
endpoint = &host_interface->endpoint[0].desc; // 获取端点
if (!usb_endpoint_is_int_in(endpoint)) // 端点是中断传输模式,方向为输入
return -ENODEV;
max_data_len = endpoint->wMaxPacketSize; // 数据包最大长度
printk(" usb mouse device is ok !! \n");
// 给 urb 分配内存,物理地址必须连续,内部使用dma
mouse_buf_addr = usb_alloc_coherent(dev, max_data_len, GFP_ATOMIC, &mouse_buf_phys_addr);
if(isErrPtr(mouse_buf_addr)) {
printk("<<fault>> usb_buffer_alloc mouse_buf_addr \n");
return -1;
}
mouse_urb = usb_alloc_urb(0, GFP_KERNEL); // 分配 urb 对象
if(isErrPtr(mouse_urb)) {
printk("<<fault>> usb_alloc_urb mouse_urb \n");
return -1;
}
// 设置 urb 参数
usb_fill_int_urb(mouse_urb, dev, usb_rcvintpipe(dev, endpoint->bEndpointAddress),
mouse_buf_addr, max_data_len/*(maxp > 8 ? 8 : maxp)*/,
mouse_irq, NULL, endpoint->bInterval);
mouse_urb->transfer_dma = mouse_buf_phys_addr; // 物理地址
mouse_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_submit_urb(mouse_urb, GFP_KERNEL); // 注册 urb
return 0;
}
static void mouse_disconnect(struct usb_interface *intf)
{
struct usb_device *dev = interface_to_usbdev(intf);
usb_kill_urb(mouse_urb); // 注销 urb
usb_free_urb(mouse_urb); // 释放 urb 内存
usb_free_coherent(dev, max_data_len, mouse_buf_addr, mouse_buf_phys_addr); // 释放 urb 内存
printk("disconnect !!\n");
}
static struct usb_device_id mouse_id_table [] = {
{ USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT,
USB_INTERFACE_PROTOCOL_MOUSE) }, // usb 匹配协议,从内核其他驱动处copy
{ } /* Terminating entry */
};
static struct usb_driver mouse_driver = {
.name = "mouse_drv",
.probe = mouse_probe, // usb 匹配成功后调用该函数
.disconnect = mouse_disconnect, // usb 断开链接后调用该函数
.id_table = mouse_id_table, // usb 匹配的规则
};
static int __init module_init_mouse(void)
{
int ret, i;
mouse_input_dev = input_allocate_device(); // 分配输入子系统对象
if(isErrPtr(mouse_input_dev)) {
printk("<<fault>> mouse_probe input_allocate_device \n");
return -1;
}
// REL_HWHEEL : Horizontal roller
// REL_WHEEL : Vertical roller
set_bit(EV_KEY, mouse_input_dev->evbit); // 设置输入设备能产生按键类事件
set_bit(EV_REL, mouse_input_dev->evbit); // 设置输入设备能产生相对位移事件
for(i=0;i<NUM_BTNS;i++)
set_bit(btn_keyvals[i].keyval, mouse_input_dev->keybit); // 设置能产生哪些按键事件
set_bit(REL_X, mouse_input_dev->relbit); // 设置能产生水平方向相对位移事件
set_bit(REL_Y, mouse_input_dev->relbit); // 设置能产生垂直方向相对位移事件
set_bit(REL_WHEEL, mouse_input_dev->relbit); // 设置能产生垂直方向滚轮事件
if(input_register_device(mouse_input_dev)) { // 注册输入子系统对象
printk("<<fault>> mouse_probe input_register_device \n");
return -1;
}
ret = usb_register(&mouse_driver); // 注册 usb 设备驱动
if (ret == 0)
printk("usb_register mouse_driver ok !\n");
return ret;
}
static void __exit module_exit_mouse(void)
{
input_unregister_device(mouse_input_dev); // 注销输入子系统对象
input_free_device(mouse_input_dev); // 释放输入子系统内存
usb_deregister(&mouse_driver); // 注销 usb 设备驱动
}
module_init(module_init_mouse);
module_exit(module_exit_mouse);
MODULE_LICENSE("GPL");
b.应用层
应用层主要是读取驱动注册的输入子系统生成的设备文件来获取输入事件进行二次封装,并且显示鼠标的图像。
在 input 文件夹下添加 mouse.c , 分配注册一个 T_InputOpr ,在 <input_manager.h> 中定义
包含头文件 <linux/input.h> 来获取输入事件
#include <linux/input.h>
/*
struct input_event { // <linux/input.h> 中定义
struct timeval time; // 事件发生的时间
__u16 type; // 事件类型如 EV_KEY EV_REL
__u16 code; // 事件代码如 REL_X REL_Y BTN_LEFT
__s32 value; // 事件的值
};*/
void GetInputEvent(void)
{
struct input_event event;
int fd = open("/dev/event0", O_RDONLY); // 以阻塞的方式打开设备
while (1)
{
ret = read(fd, &event, sizeof(struct input_event)); // 没有事件发生时会阻塞
switch (event.type) {
//......
}
}
}
包含头文件 <linux/fb.h> 来获取显示屏信息和显存
#include <linux/fb.h>
#define FB_DEVICE_NAME "/dev/fb0"
static int g_fb_fd; // 显示设备文件描述符
static struct fb_var_screeninfo g_tFBVar; // 显示设备可变参数
static struct fb_fix_screeninfo g_tFBFix; // 显示设备固定参数
static unsigned char *g_pucFBMem; // 显存地址
static unsigned int g_dwScreenSize; // 显存大小 = xres * yres * bpp / 8
static unsigned int g_dwLineWidth; // 一行长度 = xres * bpp / 8
static unsigned int g_dwPixelWidth; // 像素长度 = bpp / 8
static int FBDeviceInit()
{
int ret;
g_fb_fd = open(FB_DEVICE_NAME, O_RDWR); // 可读可写
ret = ioctl(g_fb_fd, FBIOGET_VSCREENINFO, &g_tFBVar);
ret = ioctl(g_fb_fd, FBIOGET_FSCREENINFO, &g_tFBFix);
g_dwScreenSize = g_tFBVar.xres * g_tFBVar.yres * g_tFBVar.bits_per_pixel / 8;
g_pucFBMem = (unsigned char *)mmap(NULL , g_dwScreenSize, PROT_READ | PROT_WRITE, MAP_SHARED, g_fb_fd, 0);
g_dwLineWidth = g_tFBVar.xres * g_tFBVar.bits_per_pixel / 8;
g_dwPixelWidth = g_tFBVar.bits_per_pixel / 8;
printf("g_pucFBMem = 0x%08X\n",(int)g_pucFBMem);
printf("g_dwScreenSize = 0x%08X\n", g_dwScreenSize);
printf("xres = %d yres = %d\n", g_tFBVar.xres, g_tFBVar.yres);
printf("g_dwLineWidth = %d\ng_dwPixelWidth = %d\n", g_dwLineWidth, g_dwPixelWidth);
return 0;
}
显示鼠标:定义一个结构体来存放鼠标当前位置,和该位置一小块的显存,每次移动前先恢复该块显存,然后保存即将移动覆盖掉的显存,最后在该块显存上绘制鼠标。
结构体定义如下:
typedef struct FbTmpBuf {
int x; // x 坐标
int y; // y 坐标
int xlen; // x 方向字节长度 xlen = xres * bpp / 8
int ylen; // y 方向高度 ylen = yres
char* buf; // 一小块 "显存"
}T_FbTmpBuf, *PT_FbTmpBuf;
在数码相框项目中,其他线程也会绘制显示屏,所以可能在切换页面时留下一小块上一个页面的残留,
就像这样:
所以优化一下 render.c 的代码,添加绘制的事件,在绘制后通知注册到该事件的所有模块(为了简单起见这里没有使用链表,因为只有鼠标一个模块有这个需求)
定义如下:
// <render.h>
typedef void (* __render_event)(void);
int register_render_event(__render_event render_event); // 使用该函数注册绘制事件
// <render.c>
static __render_event g_render_event_only_for_mouse = NULL;
#define call_render_event() \ // 在每一个绘制函数最后调用该宏
if(g_render_event_only_for_mouse) \
g_render_event_only_for_mouse();
int register_render_event(__render_event render_event) // 使用该函数注册绘制事件
{
if (!render_event || g_render_event_only_for_mouse) {
printf("register_render_event error !\n");
return -1; // 注册失败返回 非0
}
else {
g_render_event_only_for_mouse = render_event;
return 0; // 注册成功返回 0
}
}
然后在 mouse.c 中实现函数并注册到该事件中,跳过恢复鼠标显存的步骤,直接记录当前鼠标位置,保存显存后,再重绘鼠标即可。
4.BUG
虽然解决了残留方块的问题,但是目前程序还是有一些问题:
a. 其他线程在绘制图像的时候,鼠标模块也在绘制,所以可能留下一些残影
解决办法:在鼠标和其他线程操作显存时加互斥锁,保证同一时间对显存只有一个操作,不过可能造成其他线程绘制时感觉鼠标卡顿。
b. 点击菜单栏后会出现黑色的鼠标
问题分析:在 render.c 的 InvertButton 函数中,对按钮的颜色翻转是直接使用的显存,所以导致白色的鼠标在点击后还是呈现白色(因为上文添加的绘制事件),释放鼠标后,白色鼠标被翻转成黑色,并再次被绘制事件的通知记录下来,所以留下了黑色的鼠标。
解决办法:在 InvertButton 函数中不要直接使用显存,而是使用图片信息进行翻转和显示。
5.优化
在 input_manager.h 中有对输入事件的定义如下:
typedef struct InputEvent {
struct timeval tTime; /* 发生这个输入事件时的时间 */
int iType; /* 类别: stdin, touchsceen */
int iX; /* X/Y座标 */
int iY;
int iKey; /* 按键值 */
int iPressure; /* 压力值 */
}T_InputEvent, *PT_InputEvent;
第一眼看到这个时候很疑惑,这个 iKey 按键值是啥啊? 触摸屏还有按键值吗?
后来发现应该是用来标识键盘输入的,
既然如此,不如改成这样:
#define INPUT_TYPE_STDIN 0
#define INPUT_TYPE_TS 1
#define INPUT_TYPE_MOUSE 2
typedef struct InputEvent {
struct timeval tTime; /* 发生这个输入事件时的时间 */
int iType; /* 类别: stdin, touchsceen, mouse */
union __type
{
struct __stdin {
int iKey; /* 按键值 */
}stdin;
struct __touchsceen {
int iX; /* X/Y座标 */
int iY;
int iPressure; /* 压力值 */
}touchsceen;
struct __mouse {
int iX; /* X/Y座标 */
int iY;
int leftState; /* 左键状态 */
int rightState; /* 右键状态 */
}mouse;
}type;
}T_InputEvent, *PT_InputEvent;
这样感觉提高了内存使用率,而且一目了然哪一类输入事件有哪些成员,没有混用的问题。(个人意见)
说明
驱动程序中上报了我鼠标的所有按键事件,但是我的应用源码中只使用了位移和左键,并且直接将输入作为触摸屏类型上报到 input_manager ,并没有添加新功能,如果您愿意,可以拓展其他输入到程序中。