WDK 提供的 USBView

网上能找到的都是旧的,不支持 usb 3.0。 于是在 MS 的网站上找了一个最新版本的,下面是来自 WDK for Win10, 64位的:

usbview

具体介绍可以看下面:

USBView
USBView (Universal Serial Bus Viewer, Usbview.exe) is a Windows graphical user interface application that enables you to browse all USB controllers and connected USB devices on your computer. USBView works on all versions of Windows.
Where to get USBView

USBView is included in Debugging Tools for Windows.
USBView is also available in the Windows driver samples repository on GitHub.
Using USBView

USBView can enumerate USB host controllers, USB hubs, and attached USB devices. It can also query information about the devices from the registry and through USB requests to the devices.
The main USBView window contains two panes. The left pane displays a connection-oriented tree view, enabling you to select any USB device.
The right pane displays the USB data structures that pertain to the selected USB device. These structures include Device, Configuration, Interface, and Endpoint Descriptors, as well as the current device configuration.

来自:https://msdn.microsoft.com/en-us/library/windows/hardware/ff560019(v=vs.85).aspx

=========================================================
2024年5月28日  更新一个 Win11 的

Leonrado Uno 转接板和修改后的USB Host库

有时候,我们希望在 Leonrado 上使用 Uno 的Shield,但是会发现两者的引脚并不兼容。比如: uno 上的 SPI 位置和 leonrado 不同。

这种情况下就可以使用下面的转接板。
a2u

主要是调整了 i2c 和 spi 使得转接后的能够兼容设计给 uno 的sheild.

a2u2

比如,我直接的应用就是让 USB Host Shield 可以在 Leonrado 上工作。同时,设计上考虑到国内基本上买不到合适的长脚母座,所以使用了母座外加排针的方式, 堆叠之后的样式如下:

a2u3

电路板下载 zLeo2UnoShieldv1.0

之后,我实验编译使用Leonrado 自带USB Keyboard功能的时候遇到奇怪的问题,始终提示没有加入include “keyboard.h”。经过一番研究发现,出现这个问题的原因是:IDE定义USB keyboard的时候定义了HID Class,而USBHost 库在解析USB设备的时候同样定义了一个HID Class,二者存在冲突。
对此,需要修改避免HID Class 的冲突。我选择修改 USB Host库,因为这个毕竟是第三方库。

最终将库中的 HID Class全部修改为 NewHID ,这样我们可以在 Leonrado 同时使用 USB Host 和 模拟键盘鼠标了。

附件是修改之后的版本,亲测有效:

USB_Host_Shield_Library_2.0_Modified

从源代码到 FFS 文件

本文会以 BdsDxe.ffs 的生成为例,介绍一下从 EFI 到 FFS的编译过程。
所有的实验都是建立在UDK2015 NT32Pkg的基础上。
首先,要保证NT32能够正常编译运行。之后,在 build目录中取得生成的BIOS文件:nt32.fd。使用 fmmt –v nt32.fd 查看一下这个BIOS的组成。可以看到其中只有一个 FV , 然后有 BdsDxe的ffs。我们可以在\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\ 目录下,找到编译生成的 BdsDxe.efi。 在 \Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe可以看到文件名为6D33944A-EC75-4855-A54D-809C75241F6C.ffs 的文件。就是说我们前面看到的BdsDxe .efi,最终得到了一个ffs文件。
ffs1

ffs2

下面就针对这个过程逐一进行分析。
1. *.PE32 文件的生成。具体的生成命令可以在在6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32.txt中看到:
GenSec -s EFI_SECTION_PE32 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32 c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\BdsDxe.efi
使用工具比较一下生成前后的文件,可以看到只有头部存在一点差异。
ffs2
再查看 GenSec 的源代码,当指定为 EFI_SECTION_PE32 的时候,头的内容是 EFI_SECTION_PE32(0x10)+Length。所以和之前的EFI 相比,增加的是 0x10 和0x13004 (EFI文件的长度)
2. GUIDED文件的生成,在6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided.txt 可以看到, 使用的是 GenSec 工具,作为输入的是 PE32, UI和 VER文件。
GenSec -s EFI_SECTION_GUID_DEFINED –sectionalign 1 –sectionalign 1 –sectionalign 1 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32 c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.2.ui c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver
3. 目前我尚不知道 *.UI文件是如何生成的,希望有懂的朋友指导一下,谢谢!
4. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver.txt 文件可以得知*.VER 是这样生成的
GenSec -s EFI_SECTION_VERSION -n 1.0 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver

5. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com.txt 文件可以得知 .COM 的生成方式如下(COM 并非DOS下面的可执行文件,而是 COMPRESS的缩写)。经过了这步,因为压缩的原因,生成的结果比压缩之前小了很多,比较起来也会发现“面目全非”。
GenSec -s EFI_SECTION_COMPRESSION -c PI_STD -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided
6. 从6D33944A-EC75-4855-A54D-809C75241F6C.ffs.txt 可以看到 FFS是如何生成的:
GenFfs -t EFI_FV_FILETYPE_DRIVER -g 6D33944A-EC75-4855-A54D-809C75241F6C -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6C.ffs -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com
7. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx.txt,可以看到 dpx 文件的生成过程
GenSec -s EFI_SECTION_DXE_DEPEX -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\BdsDxe.depex

8. 接下来是最后一步,使用COM 和 DPX 生成 FFS 文件,从6D33944A-EC75-4855-A54D-809C75241F6C.ffs.txt 文件可以看到操作动作
GenFfs -t EFI_FV_FILETYPE_DRIVER -g 6D33944A-EC75-4855-A54D-809C75241F6C -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6C.ffs -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com

最后生成的 FFS 和 COM 差别很小
ffs3

了解了整体的BIOS结构,做很多事情,比如:编写无需重新编译代码,直接修改ROM的工具等等。本文只是简单的对于FFS的生成进行了研究,希望这篇文章对于需要了解EDK2 Firmware 结构的朋友有所帮助。

示波器查看串口通讯的波形

虽然使用了串口很久,但是一直没有深入研究波形。最近看了一篇介绍【参考1】,随手用示波器抓了一下波形。
下面是资料中提到的例子。在无数据传输时,串口总线上应该是高电平。当有数据传输时,首先会拉低一个时钟周期,这是 Start Bit。之后是有用的数据,长度是根据双方约定好的。最后可以跟着一个校验位(也可以没用),最终用一个拉高的时钟周期表示传输完成,这是Stop Bit。然后总线继续拉高处于Idle状态。
u1
下面示波器抓取的实际波形,双方的通讯设置如下:
u2
发送0xAA
u3

发送 0x55
u4

最后附上一个常用的对 0x3F8 初始化的代码段

#define PORT 0x3f8 /* COM1 */

void init_serial() {
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 0, 0x01); // Set divisor to 1 (lo byte) 115200 baud
outb(PORT + 1, 0x00); // (hi byte)
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
}

参考:
1.http://www.unm.edu/~zbaker/ece238/slides/UART.pdf

让 Leonorade的键盘有“输出”能力

Arduino Leonarado 和其他型号相比,最大的特点是可以方便的将自身模拟为USB键盘和鼠标。从USB总线的角度来说,数据通讯本身是双向的。从整体角度来说键盘只是输入设备,并没有输出的能力,但是如果仔细观察会发现键盘上有三个指示灯,分别是:NumLock,ScrollLock和CapsLock。这三个指示灯作用如下:
1. Num Lock 是副键盘中数字键盘的开关。在这个键对应的键盘指示灯关闭的情况下,小键盘的按键用来移动光标(上下左右、行首、行尾等等),在这个键对应的键盘指示打开的情况下,即锁定数字键,小键盘的按键用来输入数字;
2. Caps Lock 是大小写锁定键,在这个键对应的键盘指示灯关闭的情况下,键盘输入的字母都是小写,否则键盘输入都为大写;
3. Scroll lock (滚动锁定键)最初是用来设计为DOS下自动滚动屏幕的,但是进入Windows出现图形化的界面之后,这个按键就没有用途了,只是为了兼容等等考虑在通用键盘设计上仍然有所保留。
如果你的电脑有两个以上的键盘,还能观察到一个有趣的现象:在一个键盘上按下上述三个键中的某一个,那么其他的键盘状态也会跟着发生变化。原理上来说,是Windows会将收到的切换信息再“广播”出去,保证所有的键盘状态都是同步的。我用USB逻辑分析仪抓包,当在其他键盘上按下NumLock时,系统会向每一个USB键盘发出广播。比如:下面系统中有3个键盘,当键盘1按下 NumLock 后,系统还会通知全部三个键盘“NumLock状态改变”的消息。
kb1

我们无需关心Windows中消息的格式,对于USB来说,USBHost (Windows的PC),会送出一个 SET Package来通知 USB Device(USB 键盘)。下图是我用USB逻辑分析仪抓包的结果:

kb2

进一步展看查看协议,是发送了 07 给USB 键盘
kb3

上面的原理可以帮助我们在Arduino Leonarado上实现。简单起见,我们的目标是在ArduinoLeonarado 上装上三个LED,让这三个LED和我的 USB键盘上三个LED实现同步。原生的ArduinoIDE 并没有设计这个功能,因此,需要对源代码进行修改:
第一个需要修改的地方是 \arduino\libraries\Keyboard\src\Keyboard.cpp 中的 USBKeyboard的HID 描述符(Descriptor)。增加了下面斜体字表示的部分:
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//ZT_DEBUG
[i] 0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x05, // USAGE_MAXIMUM (5)
0x91, 0x02, // OUTPUT (Data,Var,Abs) // LED report
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x01, // OUTPUT (Constant) // padding[/i]
//ZT_DEBUG

0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)

当然,具体作用请参考USB HID协议对照解读。简单的解释:修改之前的描述符只有INPUT部分,就是键盘告诉系统“我只能发出数据”;增加部分的作用是键盘告诉系统能够接收HOST过来的状态信息(USB协议中,INPUT指的是设备对主机的方向,OUTPUT指的是主机对设备)。我们的修改是加入“我还能接收”的能力。如果不声明这样的能力,系统不会将数据发送过来,具体实验中,一些USB接口的小键盘上面的LED不会跟随主机状态变化。
下面需要做的就是实际处理数据了。1.6.X 系列的代码和之前的差别很大,对于键盘这部分是分开在 HID 和Keyboard 的类中。

Keyboard 只有输出的代码,通过调用 HID 类来进行发送的处理。因此,我们需要在HID 类中开一个接口。
重新定义如下:

处理部分, 在\arduino\hardware\arduino\avr\libraries\HID\src\HID.cpp 中的 boolHID_::setup(USBSetup& setup)函数中,当我们收到 Set_Report 就是系统发过来的关于LED的设置,我们取下来即可。

对于 HID 开一个接口,直接返回即可

uint8_t HID_::getLedStatus(void)
{
returnled;
}

我们给用户的接口是 Keyboard 类,我们要在上面田间一个读取的接口,

在这只文件中声明 \arduino\libraries\Keyboard\src\Keyboard.h

在s\arduino\libraries\Keyboard\src\Keyboard.cpp 文件中实现

编写一个例子,使用 Arduino 同步显示当前键盘的LED状态:

D5 最左侧灯,D6中间灯,D7 最右侧灯。

#include <Keyboard.h>

uint8_t old=0xFF,n=0xFF;
     
void setup() {
  pinMode(A0, INPUT_PULLUP);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);

  Keyboard.begin();

  Serial.begin(9600);
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
}
  
void loop() {

  n=Keyboard.getLedStatus();
  if (n!=old) {
     Serial.println(n);
     old=n;
     if (0!=(n&1) +) {
          digitalWrite(5,HIGH);
      }
     else {
          digitalWrite(5,LOW);
      }
     if (0!=(n&2)) {
          digitalWrite(6,HIGH);
      }
     else {
          digitalWrite(6,LOW);
      }
     if (0!=(n&4)) {
          digitalWrite(7,HIGH);
      }
     else {
          digitalWrite(7,LOW);
      }
  }
  delay(100);
}

 

kb4
工作的视频

Lenorade的按键值

对于 Leonardo 来说,最大的特点就是可以直接模拟USB键盘鼠标。

一般来说,当我们需要发送某一个按键的时候可以直接使用Keyboard.print(‘a’)这样的方式,或者在Keyboard.h 中找到定义好的键值,比如:#define KEY_F1 0xC2。

我最近遇到一个问题:这个文件中定义的键值我无法在其他资料上找到,让人非常恼火,例如:没有一份资料上说 CAPS LOCK值为0xC1,但是使用Keyboard.h中定义的 #define KEY_CAPS_LOCK 0xC1 确实是好用的。最后才想起来应该去看一下对应的代码(开源的优点)。最终,找到的代码如下:

size_t Keyboard_::press(uint8_t k)
{
       uint8_t i;
       if (k >= 136) {                     // it's a non-printing key (not a modifier)
              k = k - 136;
       } else if (k >= 128) {    // it's a modifier key
              _keyReport.modifiers |= (1<<(k-128));
              k = 0;
       } else {                        // it's a printing key
              k = pgm_read_byte(_asciimap + k);
              if (!k) {
                     setWriteError();
                     return 0;
              }
              if (k & 0x80) {                                          // it's a capital letter or other character reached with shift
                     _keyReport.modifiers |= 0x02;  // the left shift modifier
                     k &= 0x7F;
              }
       }
      
       // Add k to the key report only if it's not already present
       // and if there is an empty slot.
       if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&
              _keyReport.keys[2] != k && _keyReport.keys[3] != k &&
              _keyReport.keys[4] != k && _keyReport.keys[5] != k) {
             
              for (i=0; i<6; i++) {
                     if (_keyReport.keys[i] == 0x00) {
                            _keyReport.keys[i] = k;
                            break;
                     }
              }
              if (i == 6) {
                     setWriteError();
                     return 0;
              }     
       }
       sendReport(&_keyReport);
       return 1;
}

 

就是说,当我们使用KEY_CAPS_LOCK ==0xC1,会先有一个 193-136=57的动作,最后发送出去的实际上是 57(0x39),在USB HID Usage Tables 有下面的定义:

同样的文档中我们可以查到 Scroll Lock == 71(0x47) Num Lock == 83 (0x53)。于是,定义如下:

#define KEY_SCROLL_LOCK 0xCF
#define KEY_NUM_LOCK 0xDB

最终,我们编写一个代码,能让键盘上的三个LED逐次亮灭

#include "Keyboard.h"
 
#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB
 
void setup() {
  pinMode(A0, INPUT_PULLUP);
  Keyboard.begin();
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
 
}
 
void loop() {
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);
 
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000);
 
}