Step to UEFI (142)UEFI 和 Arduino 的 HID 通讯

如果想让 Arduino 和 UEFI 进行交互,可以使用USB串口驱动,比如:【参考1】提供了一个FTDI的UEFI驱动,如果想用在 Arduino 上需要一些修改。此外,可行的方案就是通过USB HID 直接和 Arduino Leonardo进行通讯(Arduino Uno 也是可以的,但是需要修改 16U2的Firmware,比较麻烦和折腾)。本文就介绍一下具体的实现。

首先说一下硬件部分,在一个 Proto Shied上连接了一个串口转USB的小卡,将 TX/RX/GND 三根对应的接在一起就能正常工作了。

ehid1

软件方面,Arduino代码如下使用了NicoHood 的 HID库。代码就是不断检查 HID是否收到消息,如果有,那么就从Serial1 发送出去。

/*

  Copyright (c) 2014-2015 NicoHood

  See the readme for credit to other people.



  Advanced RawHID example



  Shows how to send bytes via RawHID.

  Press a button to send some example values.



  Every received data is mirrored to the host via Serial.



  See HID Project documentation for more information.

  https://github.com/NicoHood/HID/wiki/RawHID-API

*/



#include "HID-Project.h"





// Buffer to hold RawHID data.

// If host tries to send more data than this,

// it will respond with an error.

// If the data is not read until the host sends the next data

// it will also respond with an error and the data will be lost.

uint8_t rawhidData[64];



void setup() {



  Serial1.begin(115200);



  // Set the RawHID OUT report array.

  // Feature reports are also (parallel) possible, see the other example for this.

  RawHID.begin(rawhidData, sizeof(rawhidData));



}



void loop() {



  // Check if there is new data from the RawHID device

  auto bytesAvailable = RawHID.available();

  int c;

  if (bytesAvailable)

  {

    // Mirror data via Serial

    while (bytesAvailable--) {

      c=(RawHID.read()&0xFF);

      if (c / 16 ==0) {Serial1.print("0");}

      Serial1.print(c,HEX);

      Serial1.print("  ");

    }

  }

}

 

 

UEFI  Shell Application的原理就是使用 USBIO枚举系统中的全部USB 设备,找到VID/PID是Arduino Leonardo 的设备,再检测InterfaceClass是否为03 (HID),如果是那么就打开,用UsbSetReportRequest 发送一段随机数过去。完整代码如下:

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UsbIo.h>

#include <stdlib.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_GUID gEfiUsbIoProtocolGuid = 
 { 0x2B2F68D6, 0x0CD2, 0x44CF, { 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};

//C:\UDK2015\MdePkg\Library\UefiUsbLib\Hid.c
/**
  Set the report descriptor of the specified USB HID interface.

  Submit a USB set HID report request for the USB device specified by UsbIo,
  Interface, ReportId, and ReportType, and set the report descriptor using the
  buffer specified by ReportLength and Report.
  If UsbIo is NULL, then ASSERT().
  If Report is NULL, then ASSERT().

  @param  UsbIo         A pointer to the USB I/O Protocol instance for the specific USB target.
  @param  Interface     The index of the report interface on the USB target.
  @param  ReportId      The identifier of the report to retrieve.
  @param  ReportType    The type of report to retrieve.
  @param  ReportLength  The size, in bytes, of Report.
  @param  Report        A pointer to the report descriptor buffer to set.

  @retval  EFI_SUCCESS       The request executed successfully.
  @retval  EFI_TIMEOUT       A timeout occurred executing the request.
  @retval  EFI_DEVICE_ERROR  The request failed due to a device error.

**/
EFI_STATUS
EFIAPI
UsbSetReportRequest (
  IN EFI_USB_IO_PROTOCOL     *UsbIo,
  IN UINT8                   Interface,
  IN UINT8                   ReportId,
  IN UINT8                   ReportType,
  IN UINT16                  ReportLen,
  IN UINT8                   *Report
  )
{
  UINT32                  Status;
  EFI_STATUS              Result;
  EFI_USB_DEVICE_REQUEST  Request;

  //
  // Fill Device request packet
  //
  Request.RequestType = USB_HID_CLASS_SET_REQ_TYPE;
  Request.Request = EFI_USB_SET_REPORT_REQUEST;
  Request.Value   = (UINT16) ((ReportType << 8) | ReportId);
  Request.Index   = Interface;
  Request.Length  = ReportLen;

  Result = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &Request,
                    EfiUsbDataOut,
                    3000, //PcdGet32 (PcdUsbTransferTimeoutValue),
                    Report,
                    ReportLen,
                    &Status
                    );

  return Result;
}

UINTN GetUSB()
{
  EFI_STATUS    Status;
  UINTN         HandleIndex, HandleCount;
  EFI_HANDLE    *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL          *USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;
  EFI_USB_INTERFACE_DESCRIPTOR  IfDesc;
  UINT8                         arr[64];
  UINT8                         i;

  
  //Get all the Handles that have UsbIO Protocol
  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiUsbIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);
  if (EFI_ERROR(Status)) 
  {
    Print(L"ERROR : Get USBIO count fail.\n");
    return 0;
  }   

  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
    Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiUsbIoProtocolGuid,
                      (VOID**)&USBIO);
    if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open USBIO fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

    //Get USB Device Descriptor
    Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
    if (EFI_ERROR(Status))
     {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return EFI_SUCCESS;
     }
    
    //Find the device which VID and PID is Arduino Leonrado     
    if ((0x2341==DeviceDescriptor.IdVendor) && (0x8036==DeviceDescriptor.IdProduct))
        {
                //Show the PID and VID
                Print(L"Found a Leonrado Device VendorID = %04X, ProductID = %04X\n", 
                        DeviceDescriptor.IdVendor, 
                        DeviceDescriptor.IdProduct);       
                //
                // Get Interface Descriptor
                //
                Status = USBIO->UsbGetInterfaceDescriptor (USBIO, &IfDesc);
                if (EFI_ERROR (Status)) 
                {
                        Print(L"ERROR : Usb Get Interface Descriptor fail.\n");
                        return EFI_SUCCESS;
                }
                
                //Check the Interface Class for HID
                if (0x03 == IfDesc.InterfaceClass) 
                {
                        Print(L"Found HID device, send the data!\n");
  
                        for (i=0;i<64;i++) {
                           arr[i]=(UINT8) rand();
                           Print(L"%2X  ",arr[i]);
                        }
                        Print(L"\n");
                        Status=UsbSetReportRequest (
                                USBIO,
                                IfDesc.InterfaceNumber,
                                0,  //Report ID
                                HID_OUTPUT_REPORT,
                                sizeof(arr),
                                arr);    
                        if (EFI_ERROR (Status)) 
                        {
                                Print(L"Error=[%r]\n",Status);
                                return EFI_SUCCESS;
                        }        
                }
        }        
  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  GetUSB();
  return EFI_SUCCESS;
}

 

运行结果,Shell 下显示:

hidr

PC端串口接收到的数据如下:

ehid3

完整的代码和 X64 EFI 文件下载:

HIDTest

参考:

  1. http://www.lab-z.com/stufdti/ FTDI 串口驱动

发表评论

电子邮件地址不会被公开。 必填项已用*标注