UEFI开发探索89- YIE002USB开发板(12 Linux编程)

请保留-> 【原文:  https://blog.csdn.net/luobing4365 和 http://yiiyee.cn/blog/author/luobing/】

对于USB HID的编程,在YIE002USB开篇时,就已经讨论过:

  1. 下位机是使用YIE002制作的USB HID设备,这是嵌入式开发;
  2. 上位机包括了Windows系统、UEFI系统和Linux系统。

上位机软件中,没有把Mac OSX包含进来,主要是因为我较少在苹果的系统上开发,而且也可以将其等同于Linux系统看待(同属于类Unix系)。

至今为止,Windows系统和UEFI系统,我们都已经完成了上位机的软件编写,下面该进入Linux的USB HID上位机软件开发了。

1 概述

对于Linux下的HID,网站“The Linux Kernel”上有非常详细的描述,值得仔细阅读,网址为:
https://www.kernel.org/doc/html/latest/hid/index.html。

我们所要访问的设备,是USB HID设备。在Linux系统中,常用的USB开源库,是libusb。这是一个用C语言开发的,跨平台的USB设备访问接口库。

在Github上找到这个开源库后,通读了一遍其文档。理论上使用它,可以完整地实现我们所需要的功能。不过,文档中也建议,如果是访问HID设备的话,可以使用hidapi。

hidapi是一个开源的操作HID设备的C语言库,适用于Windows、Linux和Mac OSX平台。它是针对HID设备的,对于其他USB设备不合适。

其源码仓库为:https://github.com/libusb/hidapi,主要目录如下:

hidapi: 头文件(所有平台共用一份头文件)hidapi.h
libusb:Linux系统实现源码文件hid.c,使用libusb库实现的方式
linux:Linux系统实现源码文件hid.c,使用内核接口(hidraw)实现方式
windows:Windows系统实现源码文件hid.c
mac:Mac OSX 系统实现源码文件hid.c
hidtest:测试代码 test.c

目前我们要开发的,是linux下的USB HID访问程序。从其代码结构可以看出,有两种方式可以使用,也即hidraw和libusb库可以使用。

由于hidapi封装得比较好,两种开发方式没有太大的区别。我大部分的工作,都是在考虑如何编写编译用的Makefile文件。

本篇主要hidraw的方式来开发Linux下的访问代码。

2 hidapi的准备

在开发过程中,我使用了虚拟机(Ubuntu 16.04)和实际机器(Ubuntu 18.04)来进行实验。前者用来开发hidraw方式的代码,后者用来开发libusb方式的代码。

使用两种开发环境,是因为我发现libusb方式的代码,在虚拟机上运行有各种问题,具体原因未知。不过,两种方式开发的代码,在实际机器上,都运行得很好。

2.1 安装必要组件

下载hidapi库到本地:

robin@robin-virtual-machine:~$ git clone https://github.com/libusb/hidapi.git

hidapi库比较小,应该能下载下来。如果还是有问题的话,建议在gitee上找到对应的库,用同样的方法git到本地。

安装必要的组件:

robin@robin-virtual-machine:~$ sudo apt-get install libudev-dev libusb-1.0-0-dev

所需要的组件,可以参考hidapi的文档中的要求。开发过程中,不需要使用GUI,因此,libfox-1.6-dev不需要安装。

如果编译所需的工具没有安装的话,也需要安装:

robin@robin-virtual-machine:~$ sudo apt-get install  build-essential pkg-config

build-essential包含了编译所需的工具,包括gcc、make等。pkg-config是linux下的一个命令,用于获得某个库/模块的所有编译相关的信息,在编译过程中需要用到。

2.2 hidapi主要接口

初始化和退出:

hid_init():初始化函数,无参数。
hid_exit():退出,实际上是销毁结构体等,在完成所有工作后必须调用,否则会造成内存泄漏。

枚举:

hid_enumerate():枚举设备,返回的是hid_device_info链表。一般使用hid_enumerate(0, 0)枚举所有设备。枚举一般用于获取设备ID或者设备路径。如果提前知道这些信息,亦可不用枚举。
hid_free_enumeration():释放枚举所用到的链表。

设备打开与关闭:

hid_open():打开指定 VID 和 PID 设备,返回设备结构体指针,如 hid_device *handle; handle = hid_open(0x8765, 0x4321, NULL)。设备读写和关闭函数时,均需要该结构体指针。
hid_open_path():根据设备路径打开设备,设备路径由hid_enumerate获取。如Linux下的 /dev/hidraw1。
hid_close():关闭设备。

Feature Report 收发:

hid_get_feature_report():获取 Feature report,也即采用Feature报告进行通信。
hid_send_feature_report():发送 Feature report。

读写函数:

hid_read():读取数据。
hid_write():写数据。

需要指出的是,网上不少文章,把hid_read()和hid_write()认为是读取Input report和发送Output report。这种理解是有问题的,从后面的实验可以看出,hid_read()和hid_write()并不是走的Input report和Output report的通信方式,至少这种理解不全面。

从实验结果来看,这两个函数,类似于Windows系统的ReadFile()和WriteFile()方式,而下位机对应的,是端点通信方式(参见第85篇博客)。也即在有多个端点(包括必有的端点0和其他端点)的情况下,走的是端点通信;只有端点0时,才会走Input report和Output report的通信方式。

实际上,在hidapi的源码中(inux文件夹下的hid.c),很明确地说到,hidraw方式没有实现Input report的获取。

当然,如何设计,还是取决于下位机的固件了。

3 使用hidapi的hidraw方式的示例

在了解了上述背景知识后,去实现需要的代码,就相对比较容易了。

hidapi提供了测试用的程序,位于hidtest文件夹下的test.c。linux版本的编译,可以在文件夹linux下,使用Makefile文件进行编译:

robin@robin-virtual-machine:~/hidapi/linux$ make -f Makefile-manual

编译之后,将会生成hidtest-hidraw执行文件。我们要在test.c的基础上,实现需要的功能。

3.1 准备开发目录

为方便后续的开发,需要自己建立开发用的文件夹,把需要的源文件和头文件包含进来。

新建文件夹hidraw,把hidapi的几个文件拷贝到此处:

linux/hid.c
hidapi/hidapi.h
hidtest/test.c

在文件夹linux下,有编译用的文件Makefile-manual。由于文件目录改变了,不能直接使用。可以在hidraw下新建Makefile,内容如下:

all: hidtest-hidraw
CC       ?= gcc
CFLAGS   ?= -Wall -g -fpic

LDFLAGS  ?= -Wall -g

COBJS     = hid.o test.o
OBJS      = $(COBJS)
LIBS_UDEV = `pkg-config libudev --libs` -lrt
LIBS      = $(LIBS_UDEV)
INCLUDES ?= `pkg-config libusb-1.0 --cflags`


# Console Test Program
hidtest-hidraw: $(COBJS)
	$(CC) $(LDFLAGS) $^ $(LIBS_UDEV) -o $@

# Objects
$(COBJS): %.o: %.c
	$(CC) $(CFLAGS) -c $(INCLUDES) $< -o $@

clean:
	rm -f $(OBJS) hidtest-hidraw $(COBJS)

.PHONY: clean libs

在hidraw文件夹下打开终端,输入make进行编译:

robin@robin-virtual-machine:~/luotest/hidapi$ make
cc -Wall -g -fpic -c `pkg-config libusb-1.0 --cflags` hid.c -o hid.o
cc -Wall -g -fpic -c `pkg-config libusb-1.0 --cflags` test.c -o test.o
cc -Wall -g hid.o test.o `pkg-config libudev --libs` -lrt -o hidtest-hidraw

可以看出,生成了可执行文件hidtest-hidraw。

下面将在test.c原有代码的基础上,实现USB HID设备的访问。

3.2 USB HID的hidraw方式代码开发

从之前的博客中我们知道,上位机有三种方式与USB HID设备进行通信。目前hidapi的代码中,提供了Feature report和hid_read()&hid_write()两种方式,没有提供Input report&Output report的通信方式。
主要是在Linux的hidraw中,并没有提供相应的接口。

插句题外话,我实在想不通,为什么在hidraw中,不提供Input report&Output report的接口。有知道的朋友,请在博客的评论区留言。

3.2.1 Feature report通信方式

Feature report通信,使用了hid.c中提供的两个函数:hid_send_feature_report()和hid_get_feature_report()。代码如下:

memset(yie_buf,0,sizeof(yie_buf));
	yie_buf[0] = 0x00;
	yie_buf[1] = 0xA0;
	yie_buf[2] = 0x0a;
	yie_buf[3] = 0x0b;
	yie_buf[4] = 0x0c;
	res = hid_send_feature_report(handle, yie_buf, 17);
	if (res < 0) {
		printf("Unable to send a feature report.\n");
	}

	memset(yie_buf,0,sizeof(yie_buf));

	// Read a Feature Report from the device
	// buf[0] = 0x2;
	res = hid_get_feature_report(handle, yie_buf, sizeof(yie_buf));
	if (res < 0) {
		printf("Unable to get a feature report.\n");
		printf("%ls", hid_error(handle));
	}
	else {
		// Print out the returned buffer.
		printf("Feature Report\n   ");
		printf("report number:%d\n   ",yie_buf[0]);
		for (i = 1; i < res; i++)
			printf("%02x ", yie_buf[i]);
		printf("\n");
	}

3.2.2 hid_read()&hid_write()通信方式

hid.c中提供了hid_read()和hid_write()函数,可实现类似Windows的ReadFile()和WriteFile()功能(仅对USB HID设备而言)。实现代码如下:

memset(yie_buf,0,sizeof(yie_buf));
	yie_buf[0] = 0x00;
	yie_buf[1] = 0xA0;
	yie_buf[2] = 0x0a;
	yie_buf[3] = 0x0b;
	yie_buf[4] = 0x0c;

	res = hid_write(handle, yie_buf, 17);
	if (res < 0) {
		printf("Unable to write()\n");
		printf("Error: %ls\n", hid_error(handle));
	}

	memset(yie_buf,0,sizeof(yie_buf));
	res = hid_read(handle, yie_buf, sizeof(yie_buf));
	if (res < 0) {
		printf("Unable to Read data.\n");
		printf("%ls", hid_error(handle));
	}
	else {
		// Print out the returned buffer.
		printf("Read data\n   ");
		for (i = 0; i < res; i++)
			printf("%02x ", yie_buf[i]);
		printf("\n");
	}

修改完后,使用make编译。插上自制的USB HID设备,运行测试代码,测试结果如下:

robin@robin-virtual-machine:~/luotest/hidapi$ sudo ./hidtest-hidraw
……//信息略
Manufacturer String: Robin
Product String: Robin's UEFI Explorer
Serial Number String: (77) My123
Unable to read indexed string 1
Indexed String 1: 
Feature Report
   report number:0
   a0 03 0b 0c 00 00 00 00 00 00 00 00 00 00 00 00 
Read data
   a0 01 0b 0c 00 00 00 00 00 00 00 00 00 00 00 00

注意返回数据的第2个字节,通过之前博客中的内容,可以知道已经实现了三种通信方式中的两种了。

为实现完整的三种通信方式,下一篇将使用libusb库重新构建代码。

Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ 89 hidraw下

1,115 total views, 3 views today

发表评论

电子邮件地址不会被公开。