在PC上使用VMWare,在ubuntu下创建petalinux工程,编译内核,vmware、vivdado、petalinux的安装详见alinx官方教程course4-linux实验中的步骤
创建petalinux工程,在工程目录下打开终端,输入命令准备编译内核
source /opt/pkg/petalinux/settings.sh
source /opt/Xilinx/Vivado/2017.4/settings64.sh
petalinux-config -c kernel
进入Devicedrivers -> USB Support,勾选USB Gadget Support(按Y)
进入USB Gadget Support,开启选项如下:
编译内核:
petalinux-build
生成镜像
petalinux-package --boot --fsbl ./images/linux/zynq_fsbl.elf --fpga --u-boot --force
将SD卡插入电脑连接到VMWare虚拟机,把petalinux工程中的images/linux中的BOOT.BIN和image.ub拷入SD卡的FAT分区中即可
将SD卡插入开发板,开机进入linux,检查有无usb_gadget配置项
ls /sys/kernel/config
如果出现usb_gadget,说明配置成功,可以进行后续步骤
我在linux上进行ls /sys/class/udc时遇到问题,没有udc,因此又在网上查找资料,重新回去弄了半天,最后发现的解决办法是在devicetree中,把usb0的dr_mode=“host”改为peripheral,该方法来自AMD xilinx论坛,原问题petalinux-20164-usb-failed?
devicetree文件在(petalinux项目名)/project-spec/meta-user/recipes-bsp/device-tree/files里:
修改:
cd到usb_gadget的目录下
cd /sys/kernel/config/usb_gadget/
使用mkdir方法创建一项配置
mkdir xjs_gadget
进入创建的配置中查看
cd xjs_gadget
ls
可以看到如下结果:
字符串是当设备连接到主机上时,主机上显示的设备名称、生产商之类的信息。
以下是需要配置的部分字符串参数及其含义。其中idProduct和Vendor必须是4位十六进制数,如果想要开发的USB设备在连接主机时具有专属名称,可以向USB协会提交申请。如果只是用来测试也可以填我用的这个。而strings下的字符串可以按自己的需要填写。
xjs_gadget
├── idProduct=0xa4ac# 产品id
├── idVendor =0x03FD# 产品厂商id
└── strings # 用于主机显示的相关文本
└── 0x409# 语言标识符(EN)
├── manufacturer ="flyingrt" #生产商名称
├── product ="flyingrt_mouse" #产品名称
└── serialnumber ="0001" #产品序列号
我的命令如下:
echo "0x03FD" > idVendor
echo "0xa4ac" > idProduct
mkdir strings/0x409
echo "flyingrtx" > strings/0x409/manufacturer
echo "0001" > strings/0x409/serialnumber
USB Gadget通过function配置实际的功能。每个function由protocol、subclass、report_desc、report_length这四项组成1。同一个USB Gadget可以通过一条线同时支持多个function(例如同时模拟鼠标和键盘)
首先建立功能项:
mkdir functions/hid.ms
使用ls查看,可以看到下面目录:
protocol指定HID设备使用的协议,对于键盘设备其值为1,鼠标设备其值为2。subclass指定子类。
USB-IF官网
USB-IF官网提供了HID description Tool可以编辑和生成描述符文件,并且附带了许多常见设备的示例。下载其网站提供的工具dt2_4.zip
解压后运行Dt.exe
选择file -> open ,打开其提供的mouse文件,即可查看:
可以使用file -> save as 保存为一个txt文件
我这里通过下面这段c代码向report_desc和report_length中写入二进制数据
#include
#include
#include
#include
#include
#define REPORT_DESC_SIZE 50
int main() {
int fd;
char *device = "/sys/kernel/config/usb_gadget/xjs_gadget/functions/hid.ms/report_desc";
char report_desc[REPORT_DESC_SIZE];
// 鼠标报告描述,这是一个示例,请根据你的设备规范修改
unsigned char mouse_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xa1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xa1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x03, // Usage Maximum (0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x09, 0x38, // Usage (Wheel)
0x15, 0x81, // Logical Minimum (-127)
0x25, 0x7f, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x03, // Report Count (3)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xc0, // End Collection
0xc0 // End Collection
};
// 打开报告描述文件
fd = open(device, O_WRONLY);
if (fd < 0) {
perror("Unable to open report_desc file");
return 1;
}
// 将鼠标报告描述写入文件
int desc_size = sizeof(mouse_report_desc);
int res = write(fd, mouse_report_desc, desc_size);
if (res < 0) {
perror("Error writing report_desc file");
close(fd);
return 1;
}
// 关闭文件
close(fd);
// 设置报告描述长度
fd = open("/sys/kernel/config/usb_gadget/xjs_gadget/functions/hid.ms/report_length", O_WRONLY);
if (fd < 0) {
perror("Unable to open report_length file");
return 1;
}
// 将报告描述长度写入文件
char length_str[10];
snprintf(length_str, sizeof(length_str), "%d", desc_size);
res = write(fd, length_str, strlen(length_str));
if (res < 0) {
perror("Error writing report_length file");
close(fd);
return 1;
}
// 关闭文件
close(fd);
return 0;
}
首先新建一个.c文件,将上面代码拷入其中
nano gen.c
然后编译c文件
gcc gen.c -o gen
最后运行
./gen
还需要写入protocol
echo "2" > functions/hid.ms/protocol
在configs目录下可以通过mkdir $configName创建Gadget的一个配置目录,名称可自定义。
mkdir c.1
root@zynq:/sys/kernel/config/usb_gadget/xjs_gadget/configs# cd c.1
root@zynq:/sys/kernel/config/usb_gadget/xjs_gadget/configs/c.1# ls
可以看到如下结果:
配置信息及其目录结构如下,其中string也是支持多语言的字符串。
configs
└── ${configName}
├──MaxPower=120 #最大电流,120mA
└── strings
└── 0x409
└── configuration "config1" #配置描述字符串
echo "120" > configs/c.1/MaxPower
mkdir configs/c.1/strings/0x409
echo "this is conf" > configs/c.1/strings/0x409/configuration
然后对于每个需要启用的function创建软链接,将配置的functions链接到当前使用的config,这样才能使用其功能:
ln -s functions/hid.ms/ configs/c.1/
这里需要用到udc,通过下面命令查看当前有多少个udc。我遇到的问题见内核配置。
ls /sys/class/udc
写入UDC即可开启运行。
echo ci_hdrc.0 >UDC
ls /dev/hid*
由于基于configs的配置,在系统重启后就会重置,所以可以把这些写成一个脚本,每次开机运行后就可以激活hid鼠标
nano start_mouse.sh
脚本内容如下,即上述所有操作:
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir xjs_gadget
cd xjs_gadget/
echo "0x03FD" > idVendor
echo "0xa4ac" > idProduct
mkdir strings/0x409
echo "flyingrtx" > strings/0x409/manufacturer
echo "0001" > strings/0x409/serialnumber
mkdir functions/hid.ms
echo "2" > functions/hid.ms/protocol
echo "3" > functions/hid.ms/report_length
mkdir configs/c.1
echo "120" > configs/c.1/MaxPower
mkdir configs/c.1/strings/0x409
echo "this is conf" > configs/c.1/strings/0x409/configuration
cd /home
./gen
cd /sys/kernel/config/usb_gadget/xjs_gadget/
cd /sys/kernel/config/usb_gadget/xjs_gadget/
ln -s functions/hid.ms/ configs/c.1/
echo ci_hdrc.0 >UDC
ls /dev/hid*
chmod 666 /dev/hidg0
cd /home
使用usb_slave口连接电脑,设备管理器中应当可以看到一个hid鼠标出现
我们通过往/dev/hidg0文件中写入二进制数据实现鼠标操作
鼠标发送给PC的数据每次4个字节
BYTE1 BYTE2 BYTE3 BYTE4
定义分别是:
BYTE1 –
|--bit7: 1 表示 Y 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出
|--bit6: 1 表示 X 坐标的变化量超出-256 ~ 255的范围,0表示没有溢出
|--bit5: Y 坐标变化的符号位,1表示负数,即鼠标向下移动
|--bit4: X 坐标变化的符号位,1表示负数,即鼠标向左移动
|--bit3: 恒为1
|--bit2: 1表示中键按下
|--bit1: 1表示右键按下
|--bit0: 1表示左键按下
BYTE2 – X坐标变化量,与byte的bit4组成9位符号数,负数表示向左移,正数表右移。用补码表示变化量
BYTE3 – Y坐标变化量,与byte的bit5组成9位符号数,负数表示向下移,正数表上移。用补码表示变化量
BYTE4 – 滚轮变化。
可以这样调试:
echo -e -n "\x02\xa0\x00\x00" > /dev/hidg0 # 第一个00000010右键按下,x移动
echo -e -n "\x00\xa0\x00\x00" > /dev/hidg0 # 仅x移动,byte1 00000000无操作,从而释放右键,完成一次右击
对于坐标移动,当其最高位为1时,向负方向移动
例如下面两个x坐标的移动数值:
00001000 "\x08\xd0\x00\x00" \xd0是1101 0000,鼠标左移, 不点击,不滚轮
00001000 "\x08\x50\x00\x00" \x50是0101 0000,鼠标右移, 不点击,不滚轮
使用python进行写入hidg0的操作:
data = b"\x02\xa0\x00\x00"
with open('/dev/hidg0', 'wb') as f:
f.write(data)
data = b"\x00\xa0\x00\x00"
with open('/dev/hidg0', 'wb') as f:
f.write(data)
此时应该可以看到电脑上的鼠标向左移动,并且点击了一次右键
#include
#include
#include
uint8_t byte1(int mid, int left, int right) {
uint8_t result = 0x00;
if (left) {
result += 0x01;
}
if (right) {
result += 0x02;
}
if (mid) {
result += 0x04;
}
return result;
}
int8_t byte2(int x) {
if (x >= 128) {
x = 127;
}
else if (x <= -128) {
x = -127;
}
return (int8_t)x;
}
int8_t byte3(int y) {
if (y >= 128) {
y = 127;
}
else if (y <= -128) {
y = -127;
}
return (int8_t)y;
}
void data_gen(int x, int y, int left, int mid, int right, uint8_t* result) {
result[0] = byte1(mid, left, right);
result[1] = byte2(x);
result[2] = byte3(y);
result[3] = 0x00;
}
int main(int argc, char* argv[]) {
int x = 0, y = 0, left = 0, mid = 0, right = 0;
if (argc > 1) {
x = atoi(argv[1]);
}
if (argc > 2) {
y = atoi(argv[2]);
}
if (argc > 3) {
left = atoi(argv[3]);
}
uint8_t result[4];
data_gen(x, y, left, mid, right, result);
FILE* filePointer;
filePointer = fopen("/dev/hidg0", "wb");
if (filePointer == NULL) {
printf("无法打开文件或创建文件。\n");
return 1;
}
fwrite(result, sizeof(uint8_t), sizeof(result), filePointer);
// 关闭文件
fclose(filePointer);
return 0;
}
这段代码实现的意思是move x y left(是否点击左键, 1点击, 0不点击),默认0,0,0
使用示例:
g++ move.cpp -o move # 编译
./move 50 0 # 鼠标左移50,y轴不移动,不点左键
通过上述操作,可以实现一个简易的鼠标操作。后续会进行更加完善的python操作封装。