Arduino 控制USB设备(3)取得描述符

对于USB设备来说,最重要的莫过于描述符。这里介绍如何获得描述符,以及部分描述符的含义。

程序来自http://www.circuitsathome.com/mcu/arduino-usb-host-part-3-descriptors

/* MAX3421E USB Host controller get configuration descriptor */
#include <spi.h>
#include "max3421e.h"
#include "usb.h"
 
#define LOBYTE(x) ((char*)(&(x)))[0]
#define HIBYTE(x) ((char*)(&(x)))[1]
#define BUFSIZE 256    //buffer size
 
void setup();
void loop();
 
MAX3421E Max;
USB Usb;
 
void setup()
{
  byte tmpdata = 0;
  Serial.begin( 9600 );
  Serial.println("Start");
  Max.powerOn();
  delay( 200 );
}
 
void loop()
{
  byte rcode;
  Max.Task();
  Usb.Task();
  if( Usb.getUsbTaskState() >= 0x80 ) {  //state configuring or higher
    rcode = getconfdescr( 1, 0 );                 //get configuration descriptor
    if( rcode ) {
      Serial.println( rcode, HEX );
    }
    while( 1 );                          //stop
  }
}
 
byte getconfdescr( byte addr, byte conf )
{
  char buf[ BUFSIZE ];
  char* buf_ptr = buf;
  byte rcode;
  byte descr_length;
  byte descr_type;
  unsigned int total_length;
  rcode = Usb.getConfDescr( addr, 0, 4, conf, buf );  //get total length
  LOBYTE( total_length ) = buf[ 2 ];
  HIBYTE( total_length ) = buf[ 3 ];
  if( total_length > 256 ) {    //check if total length is larger than buffer
    Serial.println("Total length truncated to 256 bytes");
    total_length = 256;
  }
  rcode = Usb.getConfDescr( addr, 0, total_length, conf, buf ); //get the whole descriptor
  while( buf_ptr < buf + total_length ) {  //parsing descriptors
    descr_length = *( buf_ptr );
    descr_type = *( buf_ptr + 1 );
    switch( descr_type ) {
      case( USB_DESCRIPTOR_CONFIGURATION ):
        printconfdescr( buf_ptr );
        break;
      case( USB_DESCRIPTOR_INTERFACE ):
        printintfdescr( buf_ptr );
        break;
      case( USB_DESCRIPTOR_ENDPOINT ):
        printepdescr( buf_ptr );
        break;
      default:
        printunkdescr( buf_ptr );
        break;
        }//switch( descr_type
    buf_ptr = ( buf_ptr + descr_length );    //advance buffer pointer
  }//while( buf_ptr <=...
  return( 0 );
}
/* prints hex numbers with leading zeroes */
// copyright, Peter H Anderson, Baltimore, MD, Nov, '07
// source: http://www.phanderson.com/arduino/arduino_display.html
void print_hex(int v, int num_places)
{
  int mask=0, n, num_nibbles, digit;
 
  for (n=1; n<=num_places; n++) {
    mask = (mask << 1) | 0x0001;
  }
  v = v & mask; // truncate v to specified number of places
 
  num_nibbles = num_places / 4;
  if ((num_places % 4) != 0) {
    ++num_nibbles;
  }
  do {
    digit = ((v >> (num_nibbles-1) * 4)) & 0x0f;
    Serial.print(digit, HEX);
  }
  while(--num_nibbles);
}
/* function to print configuration descriptor */
void printconfdescr( char* descr_ptr )
{
 USB_CONFIGURATION_DESCRIPTOR* conf_ptr = ( USB_CONFIGURATION_DESCRIPTOR* )descr_ptr;
  Serial.println("Configuration descriptor:");
  Serial.print("Total length:\t");
  print_hex( conf_ptr->wTotalLength, 16 );
  Serial.print("\r\nNum.intf:\t\t");
  print_hex( conf_ptr->bNumInterfaces, 8 );
  Serial.print("\r\nConf.value:\t");
  print_hex( conf_ptr->bConfigurationValue, 8 );
  Serial.print("\r\nConf.string:\t");
  print_hex( conf_ptr->iConfiguration, 8 );
  Serial.print("\r\nAttr.:\t\t");
  print_hex( conf_ptr->bmAttributes, 8 );
  Serial.print("\r\nMax.pwr:\t\t");
  print_hex( conf_ptr->bMaxPower, 8 );
  return;
}
/* function to print interface descriptor */
void printintfdescr( char* descr_ptr )
{
 USB_INTERFACE_DESCRIPTOR* intf_ptr = ( USB_INTERFACE_DESCRIPTOR* )descr_ptr;
  Serial.println("\r\nInterface descriptor:");
  Serial.print("Intf.number:\t");
  print_hex( intf_ptr->bInterfaceNumber, 8 );
  Serial.print("\r\nAlt.:\t\t");
  print_hex( intf_ptr->bAlternateSetting, 8 );
  Serial.print("\r\nEndpoints:\t\t");
  print_hex( intf_ptr->bNumEndpoints, 8 );
  Serial.print("\r\nClass:\t\t");
  print_hex( intf_ptr->bInterfaceClass, 8 );
  Serial.print("\r\nSubclass:\t\t");
  print_hex( intf_ptr->bInterfaceSubClass, 8 );
  Serial.print("\r\nProtocol:\t\t");
  print_hex( intf_ptr->bInterfaceProtocol, 8 );
  Serial.print("\r\nIntf.string:\t");
  print_hex( intf_ptr->iInterface, 8 );
  return;
}
/* function to print endpoint descriptor */
void printepdescr( char* descr_ptr )
{
 USB_ENDPOINT_DESCRIPTOR* ep_ptr = ( USB_ENDPOINT_DESCRIPTOR* )descr_ptr;
  Serial.println("\r\nEndpoint descriptor:");
  Serial.print("Endpoint address:\t");
  print_hex( ep_ptr->bEndpointAddress, 8 );
  Serial.print("\r\nAttr.:\t\t");
  print_hex( ep_ptr->bmAttributes, 8 );
  Serial.print("\r\nMax.pkt size:\t");
  print_hex( ep_ptr->wMaxPacketSize, 16 );
  Serial.print("\r\nPolling interval:\t");
  print_hex( ep_ptr->bInterval, 8 );
  return;
}
/*function to print unknown descriptor */
void printunkdescr( char* descr_ptr )
{
  byte length = *descr_ptr;
  byte i;
  Serial.println("\r\nUnknown descriptor:");
  Serial. print("Length:\t\t");
  print_hex( *descr_ptr, 8 );
  Serial.print("\r\nType:\t\t");
  print_hex( *(descr_ptr + 1 ), 8 );
  Serial.print("\r\nContents:\t");
  descr_ptr += 2;
  for( i = 0; i < length; i++ ) {
    print_hex( *descr_ptr, 8 );
    descr_ptr++;
  }
}

 

运行结果如下:

Usbdesc

手工分析USB的描述符简直就是噩梦…….最好有USB逻辑分析仪【参考1】,抓包解析USB数据非常方便。

多说两句关于【参考1】背后的故事:当年,我在一家国内最大的PC组装企业工作。有一次,加拿大的客户抱怨我们卖出去的机器无法使用他们的一款USB条码枪。我们的机器是 AMD RS780系列的商用机,这个系列的特点是:价格便宜,可以支持很多显示器(如果没有记错的话,用上标准的扩展配件可以同时支持5台显示器的显示,这在当时是很牛x的技术)。只是这个芯片组经常有稀奇古怪的问题,卖出去两三年之后会有客户报告奇怪的问题(销售总是承诺售后保修很长的时间)。比如:台湾邮政的客户惊奇的发现,每天早晨这个机器开机进入桌面之后会出现黑屏闪一次的问题等等。

USB条码枪这个客诉先到前端看了差不多三个月都没有搞定,走流程到全球售后研发这边了。组里正好有一台USB逻辑分析仪,我就拿着实验了一下。最后抓取得结果是这个条码枪和芯片组有兼容性问题,在判断USB设备速度的时候,K J 握手有问题。外国人和中国人在对待问题上差别蛮大,前者通常要求你找到Root Cause,即使时间很长也可以等待,找到之后,如果不是我们的问题,他们也能接受;而后者的要求通常是不管你什么Root Cause, 马上给我个能用的方法就行。他们的逻辑简单得也令人发指,最常见的说法是,我在XXX的机器上看不到现象,那就一定是你们的问题。

只是没想到 Root Cause 找到了,客户也表示接受了,我这边反倒闯了祸。

当时我们刚换了一个Team的主管,BIOS工程师出身,管着下面八九个人。据他所知,在 RS780 芯片组USB配置空间上有个寄存器,可以显示当前某个USB口上是否有USB设备插入。这个寄存器是 RS780芯片组特有的。这个主管坚持非要我去检查这个位是否设置起来。我说,逻辑分析仪结果都抓出来了还看啥啊? 最主要是我太懒了,看问题不一定花时间,但是找机器走流程装系统之类的很麻烦。一番话下去,主管为了不再暴露他对USB的无知,没再说下去,只是脸色变得铁青。从技术的角度来说,我的解释没有办法反驳。好比用示波器看到了波形,知道是硬件问题,Onwer肯定要换成 HW 工程师…….

最后看没看我忘记了,只是后来我在这个主管下面日子不好过了。再后来没办法自己找了个其他部门跑路了,办理转部门的时候这个主管非常“遗憾”的拖了我很久才撒手…….

这个故事告诉我们:

1. AMD 的芯片组不稳定啊!

2. 谦虚谨慎根据情况装孙子很重要,特别是国企类型的企业

3. 懂一半的领导比什么都不懂的更麻烦。

就是这样。

参考:

1.http://www.lab-z.com/wp-content/uploads/2013/02/usbhs.pdf 关于 USB 高速(HighSpeed)和中速(FullSpeed)的区分

USB HID class: Difference between report protocol and boot protocol?

USB HID class: Difference between report protocol and boot protocol?

Hi All

I would like to know:
1- For USB-HID class: What is the difference between report protocol and boot protocol?
2- For keyboard: the input report format is unique for both report/boot protocol?
3- When Host send Request(GET_DESCRIPTOR) specifying the report descriptor type, Will Device return the report descriptor including the input report data(i.e: 8-byte for keyboard) ? OR Host need to send Request GET_REPORT to get the input report?

In short, boot protocol is used on BIOS, report protocol is used on OS.

The device capability of boot protocol is shown at the interface triad,
(interfaceClass, interfaceSubclass, interfaceProtocol) field on the HID interface descriptor.
(interfaceClass, interfaceSubclass, interfaceProtocol) = (3, 1, 1): boot keyboard
(interfaceClass, interfaceSubclass, interfaceProtocol) = (3, 1, 2): boot mouse

BIOS checks just this triad, and it recognizes the device (interface) as specified.
BIOS doesn’t actually read out report descriptor from the device; It assumes that the device has standard keyboard or mouse report descriptor (*1) while the device in boot protocol.
After enumeration, BIOS puts Set_Protocol( BOOT ) to switch the device into boot protocol, if the device has boot capability. While BIOS is running, the device works as keyboard or mouse.
If the device doesn’t have boot capability, BIOS doesn’t enumerate the device.

At the start up of OS after BIOS, OS puts bus reset. The device gets back to default report protocol.
Usually, OS doesn’t put any Set_Protocol, the device is held in report protocol.
On the enumeration, OS reads out report descriptor of the device, and it determines the type of HID device, regardless of above subclass-protocol field.

(*1) see HID spec Appendix B: Boot Interface Descriptors

As I wrote above,
For boot protocol, the report format is fixed one.
For report protocol, you can define any report format on the report descriptor.

Device returns just the report descriptor, when host puts Get_Descriptor().
Actual input report is sent for Get_Report( input ) request, or for IN transfer over the interrupt IN endpoint.

本文来自 http://community.silabs.com/t5/8-bit-MCU/USB-HID-class-Difference-between-report-protocol-and-boot/td-p/78797

这篇文章简单介绍了一下USB键盘鼠标report protocol 和 boot protocol的差别,个人理解二者的差别是 Boot Protocol已经规定出来了发送数据的格式,这样在无需解析 HID 协议的情况下,可以直接解读出来键值等有用的信息,如果USB HOST资源有限,这样可以方便使用。比如:BIOS 中使用这个协议方便Setup处理。 report protocol 的话,就是标准的HID协议,需要根据Descriptor才能得知具体含义。

Step to UEFI (52) —– EFI_Graphics_Output_Protocol 清屏幕

EFI_Graphics_Output_Protocol 中的 Blt 可以实现在屏幕上绘制图形的功能。

bvf

其中的一个参数 EfiBltVideoFill 可以用来填充整个屏幕的颜色,从而实现清屏的目的。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include <Protocol/LoadedImage.h>



extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;

//Copied from  C\MdePkg\Include\Protocol\UgaDraw.h
typedef struct {
  UINT8 Blue;
  UINT8 Green;
  UINT8 Red;
  UINT8 Reserved;
} EFI_UGA_PIXEL;

//
// Drawing to the screen
//
VOID egClearScreen(IN EFI_UGA_PIXEL *FillColor)
{
    
    if (GraphicsOutput != NULL) {
        // EFI_GRAPHICS_OUTPUT_BLT_PIXEL and EFI_UGA_PIXEL have the same
        // layout, and the header from TianoCore actually defines them
        // to be the same type.
       GraphicsOutput->Blt(GraphicsOutput, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)FillColor, EfiBltVideoFill,
                           0, 0, 0, 0, GraphicsOutput->Mode->Info->HorizontalResolution, 
						   GraphicsOutput->Mode->Info->VerticalResolution, 0);
    }
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
    EFI_STATUS    Status;
    EFI_UGA_PIXEL color;
	UINTN i;
	
    Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
        GraphicsOutput = NULL;
		Print(L"Loading Graphics_Output_Protocol error!\n");
		return EFI_SUCCESS;
	}	

	for (i=0;i<255;i++)
	  {
		color.Blue  = i & 0xFF;	
		color.Green = i & 0xFF;
		color.Red   = i & 0xFF;
	
		egClearScreen(&color);
		gBS->Stall(5000);
	  }
  return EFI_SUCCESS;
  
}

 

工作视频:

http://www.tudou.com/programs/view/W6lvKHdeMX8/?resourceId=414535982_06_02_99

完整代码下载

GFXTest2

参考:

1. 本文参考 https://github.com/chengs 的代码 在此表示感谢!

Arduino 控制USB设备(2)硬件测试篇

这篇文章目标是让你知道你买的USB Host Shield能否正常工作。

我们要运行一段代码来确保板子工作正常。从经验的角度来看,这个非常必要,对于很多卖家来说,板子之间的差别只有进货价格的高低,他们对于质量一无所知。

下面例子中的代码来自 http://www.circuitsathome.com/mcu/arduino-usb-host-part-2-classes

第一个代码是测试SPI通信是否正常

/* MAX3421E USB Host controller SPI test */
/* This sketch tests SPI communication between Arduino and MAX3421E USB host controller */
#include <spi.h>
#include "max3421e.h"
 
void setup();
void loop();
 
byte i;
byte j = 0;
byte gpinpol_copy;
 
MAX3421E Max;
 
void setup()
{
    Serial.begin( 9600 );
    Max.powerOn();
    delay(200);
}
 
void loop()
{
  gpinpol_copy = Max.regRd( rGPINPOL );
  Serial.println("SPI test. Each  '.' indicates 64K transferred. Press any key to stop.");
  while( Serial.available() == 0 ) {
    for( i = 0; i < 255; i++ ) {
      Max.regWr( rGPINPOL, i );
      if( Max.regRd( rGPINPOL ) != i ) {
        Serial.println("SPI transmit/receive mismatch");
      }
    }//for( i = 0; i < 255; i++
      j++;
      if( j == 0 ) {
        Serial.print(".");
      }
  }//while( Serial.available() == 0
  Max.regWr( rGPINPOL, gpinpol_copy );
  Serial.println("\r\nStopped.");
  while( 1 );    //stop here
}

 

运行结果

usbspitest1

下面这个代码测试的是 MAX3421E 寄存器是否正常

/* This sketch dumps MAX3421E registers */
#include <spi.h>
#include "max3421e.h"
 
MAX3421E Max;  //MAX3421E instance
 
/* Regiser names/numbers for MAX3421E register dump */
typedef struct {
  const char* name;
  char number;
}
REGISTER_OUTPUT;
 
REGISTER_OUTPUT max_register[] = {
  { "\r\nRCVFIFO:\t",   rRCVFIFO    },
  { "\r\nSNDFIFO:\t",   rSNDFIFO    },
  { "\r\nSUDFIFO:\t",   rSUDFIFO    },
  { "\r\nRCVBC:\t",     rRCVBC      },
  { "\r\nSNDBC:\t",     rSNDBC      },
  { "\r\nUSBIRQ:\t",    rUSBIRQ     },
  { "\r\nUSBIEN:\t",    rUSBIEN     },
  { "\r\nUSBCTL:\t",    rUSBCTL     },
  { "\r\nCPUCTL:\t",    rCPUCTL     },
  { "\r\nPINCTL:\t",    rPINCTL     },
  { "\r\nREVISION:\t",  rREVISION   },
  { "\r\nIOPINS1:\t",   rIOPINS1    },
  { "\r\nIOPINS2:\t",   rIOPINS2    },
  { "\r\nGPINIRQ:\t",   rGPINIRQ    },
  { "\r\nGPINIEN:\t",   rGPINIEN    },
  { "\r\nGPINPOL:\t",   rGPINPOL    },
  { "\r\nHIRQ:\t",      rHIRQ       },
  { "\r\nHIEN:\t",      rHIEN       },
  { "\r\nMODE:\t",      rMODE       },
  { "\r\nPERADDR:\t",   rPERADDR    },
  { "\r\nHCTL:\t",      rHCTL       },
  { "\r\nHXFR:\t",      rHXFR       },
  { "\r\nHRSL:\t",      rHRSL       }
};
 
 
void setup()
{
  Serial.begin( 9600 );
  Max.powerOn();
}
 
void loop()
{
  unsigned char i;
  unsigned char numregs = sizeof( max_register )/sizeof( REGISTER_OUTPUT);
  for( i = 0; i < numregs; i++ ) {
    Serial.print( max_register[ i ].name);
    print_hex( Max.regRd( max_register[ i ].number ), 8 );
  }
  while(1);
 
}
 
/* prints hex numbers with leading zeroes */
// copyright, Peter H Anderson, Baltimore, MD, Nov, '07
// source: http://www.phanderson.com/arduino/arduino_display.html
void print_hex(int v, int num_places)
{
  int mask=0, n, num_nibbles, digit;
 
  for (n=1; n<=num_places; n++)
  {
    mask = (mask << 1) | 0x0001;
  }
  v = v & mask; // truncate v to specified number of places
 
  num_nibbles = num_places / 4;
  if ((num_places % 4) != 0)
  {
    ++num_nibbles;
  }
 
  do
  {
    digit = ((v >> (num_nibbles-1) * 4)) & 0x0f;
    Serial.print(digit, HEX);
  }
  while(--num_nibbles);
 
}

 

运行结果

usbspitest2

下面这个代码测试的是 USB 当前状态

/* MAX3421E interrupt loop */
#include <spi.h>
#include "max3421e.h"
 
MAX3421E Max;
 
byte rcode;
byte vbus_state;
 
void setup()
{
  Serial.begin( 9600 );
  Serial.println("Start");
  Max.powerOn();
}
 
void loop()
{
  Max.Task();
  print_vbus_state();
}
 
void print_vbus_state( void )
{
  char* vbus_states[] = { "Disconnected", "Illegal", "Full speed", "Low speed" };
  byte tmpbyte;
  static byte last_state = 4;
    tmpbyte = Max.getVbusState();
    if( tmpbyte != last_state ) {
      last_state = tmpbyte;
      Serial.println( vbus_states[ tmpbyte ] );
    }
    return;
}

 

刚开始没有插任何设备,显示为 Disconnected 状态。之后插入一个USB鼠标,识别为Low Speed设备。拔掉之后再插入两个不同的U盘,因为IC本身不支持High Speed,所以都显示为Full Speed设备。

usbspitest3

最后,三个修改后的完整代码可以在这里下载:

usb2

经过上述测试,可以确定你的板子没问题。

If you are using Delphi TComPort VCL ……

If you are using Delphi TComPort VCL for some Arduino programs. Please set FlowControl -> ControlDTR to dtrEnable. Otherwise, you will get nothing from Serial Port.

comport

It costed me one afternoon for this issue. I worked with a Arduino Pro Micro (Leonardo). The Serial Monitor of IDE worked well. Putty worked well and ‘Serial Port Utility’ worked well. Arduino Uno was tried. It works well……. Only my Delphi program couldn’t get anything from the Serial Port. At last, I noticed the example program of TComPort worked well. But if I deleted this VCL and added again, it would fail. At last I found this Properties.

I don’t know why. All the documents said Leonardo didn’t use DTR pin.

If you have any suggestion, please let me know. Thanks a lot.

Step to UEFI (51) —– EFI_Graphics_Output_Protocol获得基本信息

学习了一下如何获得 Shell 下当前的显示信息。通过 GraphicsOutputProtocol 来完成这个功能。这个 Protocol 在【参考1】 中有描述。

Capture

头定义在 \MdePkg\Include\Protocol\GraphicsOutput.h

typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;

///
/// Provides a basic abstraction to set video modes and copy pixels to and from 
/// the graphics controller's frame buffer. The linear address of the hardware 
/// frame buffer is also exposed so software can write directly to the video hardware.
///
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
  EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE  QueryMode;
  EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE    SetMode;
  EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT         Blt;
  ///
  /// Pointer to EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE data.
  ///
  EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE        *Mode;
};

 

从名称上来看,这个 Protocol 能够实现的功能是:查询/设置当前显示模式,将屏幕内容和内存互copy等。

这次实验的是查询功能。

查询的结果输出是 EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE 结构体

typedef struct {
  ///
  /// The number of modes supported by QueryMode() and SetMode().
  ///
  UINT32                                 MaxMode;
  ///
  /// Current Mode of the graphics device. Valid mode numbers are 0 to MaxMode -1.
  ///
  UINT32                                 Mode;
  ///
  /// Pointer to read-only EFI_GRAPHICS_OUTPUT_MODE_INFORMATION data.
  ///
  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION   *Info;
  ///
  /// Size of Info structure in bytes.
  ///
  UINTN                                  SizeOfInfo;
  ///
  /// Base address of graphics linear frame buffer.
  /// Offset zero in FrameBufferBase represents the upper left pixel of the display.
  ///
  EFI_PHYSICAL_ADDRESS                   FrameBufferBase;
  ///
  /// Amount of frame buffer needed to support the active mode as defined by 
  /// PixelsPerScanLine xVerticalResolution x PixelElementSize.
  ///
  UINTN                                  FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;

 

可以看到能够获得当前显示模式,屏幕分辨率和格式信息等。根据上面的信息,编写程序如下

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include <Protocol/LoadedImage.h>



extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
    EFI_STATUS Status;

    Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
    if (EFI_ERROR(Status)) {
        GraphicsOutput = NULL;
		Print(L"Loading Graphics_Output_Protocol error!\n");
		return EFI_SUCCESS;
	}	

	Print(L"Max mode     =[%d] \n",GraphicsOutput->Mode->MaxMode);
	Print(L"Current mode =[%d] \n",GraphicsOutput->Mode->Mode);
	Print(L"Version      =[%d] \n",GraphicsOutput->Mode->Info->Version);
	Print(L"Screen Width =[%d] \n",GraphicsOutput->Mode->Info->HorizontalResolution);
	Print(L"Screen height=[%d] \n",GraphicsOutput->Mode->Info->VerticalResolution);
	Print(L"Format       =[%d] \n",GraphicsOutput->Mode->Info->PixelFormat);
	Print(L"Num of pixel =[%d] \n",GraphicsOutput->Mode->Info->PixelsPerScanLine);
		
  return EFI_SUCCESS;
  
}

 

运行结果(还是NT32模拟器中)

gopinfo

完整代码下载:

GFXTest

参考:

1.UEFI Spec 2.4 P488 11.9 Graphics Output Protocol

==========================================================

2025年3月17日 额外同一个功能类似的代码

#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/DebugLib.h>

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
)
{
  EFI_STATUS Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
  UINTN HandleCount;
  EFI_HANDLE *HandleBuffer;
  UINTN Index;
  UINT32 ModeIndex;
  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
  UINTN SizeOfInfo;

  // Locate the handles that support the Graphics Output Protocol
  Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &HandleCount, &HandleBuffer);
  if (EFI_ERROR(Status)) {
    Print(L"Failed to locate handles for Graphics Output Protocol\n");
    return Status;
  }

  // Iterate over all handles
  for (Index = 0; Index < HandleCount; Index++) {
    // Get the Graphics Output Protocol instance
    Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiGraphicsOutputProtocolGuid, (VOID**)&GraphicsOutput);
    if (EFI_ERROR(Status)) {
      Print(L"Failed to handle protocol for handle %d\n", Index);
      continue;
    }
	Print(L"Controler [%d]\n", Index);
    // Iterate over each mode
    for (ModeIndex = 0; ModeIndex < GraphicsOutput->Mode->MaxMode; ModeIndex++) {
      // Get mode information
      Status = GraphicsOutput->QueryMode(GraphicsOutput, ModeIndex, &SizeOfInfo, &Info);
      if (EFI_ERROR(Status)) {
        Print(L"Failed to query mode %d\n", ModeIndex);
        continue;
      }

      // Output the resolution and pixel format
      Print(L"Mode %d: Resolution: %ux%u, Pixels Per Scanline: %u\n",
            ModeIndex,
            Info->HorizontalResolution,
            Info->VerticalResolution,
            Info->PixelsPerScanLine);

      // Free the mode information
      gBS->FreePool(Info);
    }
  }

  // Free the handle buffer
  gBS->FreePool(HandleBuffer);

  return EFI_SUCCESS;
}
## @file
#  A simple, basic, EDK II native, "hello" application.
#
#   Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.&lt;BR>
#   SPDX-License-Identifier: BSD-2-Clause-Patent
#
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = gm
  FILE_GUID                      = a912f198-2025-0317-b908-b757b806ec83
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  GetModeInfo.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  UefiLib
  ShellCEntryLib

Arduino String的问题

比如我想从串口得到一个输入,然后再输出到串口上,然后写下面的程序

String comdata="";
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);

}

void loop() {
// put your main code here, to run repeatedly:
while (Serial.available() &gt; 0)
{
comdata += Serial.read();
delay(2);
}
if (comdata.length()&gt;1) {Serial.println(comdata); comdata="";}

}

 

但是惊奇的发现,结果是下面这样。”1″ 对应为 “49”两个字符;“2”对应为 “50”两个字符………

dd

如果想得到想要的结果,需要做一个强制转换

String comdata="";
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);

}

void loop() {
// put your main code here, to run repeatedly:
while (Serial.available() &gt; 0)
{

comdata +=(char)Serial.read();
delay(2);
}
if (comdata.length()&gt;1) {Serial.println(comdata); comdata="";}

}

 

这样的结果就符合预期

ee

感谢极客工坊Super169这位朋友,他给出的解释:

String class 的特性, 當 operator “+” 之後是 numeric type, 會自動把數值先轉成 字符再加上去.
“1” 的數值就是 49, 由於 Serial.read() 的結果是 int type, 當你用 comdata += Serial.read() 時, comdata 是 String, Serial.read() 是 int, 就會執行 String “+” int 的 operation. 當你輸入第一個字 “1” 時, 就會先把 “1” (int value 為 ASCII 值, 即 49) 轉成 “49”, 然後再連接上去. 之後都是一樣了.

當你加上 (char) 的轉換後, comdata += (char)Serial.read(), 就變成是 String “+” char 的 operation, 這時就不需要任何轉換而直接連上去了.

这个问题的原文可以在 http://www.geek-workshop.com/thread-14981-1-1.html 看到

蓝牙控制小灯泡亮度的实验

这里实现用 Windows x86 平板电脑控制小灯泡亮度。

硬件方面在我们最初实验设备【参考1】的基础上增加一个蓝牙模块(这里建议使用蓝牙的朋友选用 HC06系列的,和HC05的不同,这个系列只有Slave的功能,但是个人感觉HC06更容易搜索连接上,我用HC05的时候每次都需要重新搜索配对设备,但是HC06上不用),用来和Windows平板进行通讯。

image001

代码方面,Arduino使用的程序非常简单,将串口收到的char当作PWM值直接输出。程序使用了2个串口,一个是通常的USB,同PC进行通讯,主要是为了方便Debug;真正工作的是另外一个进行蓝牙通讯的串口。

int  n=255;
void setup()
{
    Serial.begin(9600);
    Serial1.begin(9600);    
    pinMode(6,OUTPUT);      //该端口需要选择有#号标识的数字口
}

void loop()
{
  char  c;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        analogWrite(6,c);
        Serial.println(c);
    }
    while (Serial1.available() > 0)  
    {
        c=Serial1.read();
        analogWrite(6,c);
        Serial.println(c);
    }    
}

 

上位机使用的是Delphi 2010,使用控件很简单即可完成编程。

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, iComponent, iVCLComponent, iCustomComponent, iPositionComponent,
  iScaleComponent, iKnob, iSevenSegmentDisplay, iSevenSegmentBinary,
  iSevenSegmentInteger, StdCtrls, CPortCtl, CPort, Buttons;

type
  TForm2 = class(TForm)
    iKnob1: TiKnob;
    iSevenSegmentInteger1: TiSevenSegmentInteger;
    ComPort1: TComPort;
    Button1: TButton;
    Button2: TButton;
    procedure iKnob1PositionChange(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  ComPort1.ShowSetupDialog;
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
  if ComPort1.Connected then
    begin
      ComPort1.Close;
      Button2.Caption:='Connect';
    end
  else
    begin
      ComPort1.Open;
      ComPort1.WriteStr(chr(0));
      Button2.Caption:='Disconnect';
    end
end;

procedure TForm2.FormActivate(Sender: TObject);
begin
  iKnob1.Width:=Form2.Width;
end;

procedure TForm2.iKnob1PositionChange(Sender: TObject);
var
  c:byte;
begin
  c:=trunc(iKnob1.Position);
  iSevenSegmentInteger1.Value:=c;
  if Comport1.Connected then
    begin
      ComPort1.Write(&c,1);
    end;
end;

end.

 

界面
image002

上位机完整代码下载

knobtest

工作的视频:
http://www.tudou.com/programs/view/yE4EHhUFcvU/?resourceId=414535982_06_02_99

最后说点其他的:除了Apple和各式各样的 Anrdoid平板电脑,x86的Windows平板也在崛起。

相比之下,使用Windows平板编程有如下优点:
1. 编程简单。工具方面Delphi VB VC 都是非常成熟的工具,能在普通PC上运行的程序,即可顺利移植到Windows平板上(甚至可以说‘移植’这个词不合适,因为不用任何改动直接放上去即可);
2. 发布简单。从时效性上来说,不需要发布到什么市场,也不需要什么审核,各种方法让对方拿到即可运行;
3. 周边设备多多,比如:各种摇杆方向盘,价格也比Apple专用的低很多;
4. 程序运行非常稳定,除非程序有错误,否则根本不会出现那种莫名其妙的“闪退”;

此外,从我的实践的角度来说,Windows 平板目前还有如下的缺点:
1. Windows本质上是给有鼠标的机器运行的,而不是触摸类的设备。这两者在精度上差别很大,传统的Window的各种控件,默认的调用者也都是鼠标,如果直接用触摸操作起来很困难,也容易误操作。因此,我用普通台式机做平板程序的感受是:你一定要把你的用户当成视力有困难的人,能调大的菜单或者按钮一定要做到最大…….
2. 目前比较缺少Windows x86平板方面的中文资料,在使用板载的各种传感器时,缺少资料

参考:
1. http://www.lab-z.com/mos%E6%8E%A7%E5%88%B6%E5%B0%8F%E7%81%AF%E6%B3%A1%E7%9A%84%E5%AE%9E%E9%AA%8C/ MOS控制小灯泡的实验

Step to UEFI (50) —– 实现一个简单的菜单功能

屏幕显示功能搭配取按键信息的功能可以实现一个简单的菜单,用户可以使用上下键移动后选项,回车确认,ESC退出。

代码本身不复杂

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

//
// EFI Scan codes 
// copied from \EdkCompatibilityPkg\Foundation\Efi\Protocol\SimpleTextIn\SimpleTextIn.h
//
#define SCAN_NULL       0x0000
#define SCAN_UP         0x0001
#define SCAN_DOWN       0x0002
#define SCAN_ESC        0x0017
#define CHAR_CARRIAGE_RETURN  0x000D

#define	POSX 7
#define	POSY 3
#define NUM  5
CHAR16 HELPSTR[40]=L"UP/DOWN, ENTER , ESC";
CHAR16 ITEM[NUM][20]= {
				L"Item1 MOVSXX",
				L"Item2 CPUID",
				L"Item3 RETF",
				L"Item4 PUSHF",
				L"Item5 SUB"
				};

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{

  EFI_INPUT_KEY	Key;
  EFI_STATUS		Status;
  int current=0;
  int i;
  
  gST -> ConOut ->ClearScreen(gST->ConOut);
 
  ShellPrintEx(POSX-3,POSY-1,L"%N%S",HELPSTR);
  ShellPrintEx(POSX,POSY,L"%H%S",ITEM[0]);
  for (i=1;i<NUM;i++) 
	{
    ShellPrintEx(POSX,POSY+i,L"%N%S",ITEM[i]);
	}
	
  Key.ScanCode=SCAN_NULL;
  while (SCAN_ESC!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			ShellPrintEx(POSX,POSY+current,L"%N%S",ITEM[current]);
			if (SCAN_UP == Key.ScanCode) {current = (current-1+NUM)%NUM;}
			if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%NUM;}
			ShellPrintEx(POSX,POSY+current,L"%H%S",ITEM[current]);			
			ShellPrintEx(POSX,POSY+NUM,L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r",current,Key.ScanCode,Key.UnicodeChar);		
		}
		if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) {
			ShellPrintEx(POSX,POSY+NUM+1,L"You have chosen: %N%S",ITEM[current]);
			break;
		}
	};	

  return EFI_SUCCESS;
}

 

运行结果:

smenu

工作视频:

http://www.tudou.com/programs/view/R_932wqGuYw/?resourceId=414535982_06_02_99

完整的代码下载:

Menu

达文西的灯

前几天,偶然知道周星驰《国产凌凌漆》英文名称是“From Beijing with Love”。然后又去重温了一遍。

image001

零零七系列一定要有各种无厘头的道具,比如:要你命3000

image003

还有就是经典的太阳能手电。于是,仿造一个用打火机才能“点亮”的小灯。用到之前的MOS模块,小灯泡,还有就是火焰传感器。

image006

这种传感器的原理是:检测火焰或者波长在760纳米~1100纳米范围内的光源。

代码非常简单,火焰传感器输出接在Arduino的Pin9上。

#define firePin  9
#define lampPin  6

void setup()
{
    Serial.begin(9600);
    pinMode(lampPin,OUTPUT);      
    pinMode(firePin, INPUT);
}

void loop()
{
  if (0==digitalRead(firePin)) {  
    for (int i=1;i<128;i++)
      {
          analogWrite(lampPin,i); 
          delay(100);
      }  
    delay(5000);  
    for (int i=128;i>0;i--)
      {
          analogWrite(lampPin,i); 
          delay(100);
      }      
  }  
}

 

实现的效果是:当没有火焰的时候绝对不亮,如果用一个打火机晃一下,它就会亮起来。

工作视频:

http://www.tudou.com/programs/view/RtMg4ayv_dA/?resourceId=414535982_06_02_99

艺术源于生活,因为时代的发展,未必高于生活【参考2】。

参考:
1. http://www.lab-z.com/?p=3201 MOS控制小灯泡的实验
2. http://baike.baidu.com/link?url=Ldym-lA1eNVcdMMGXyE-ARW5R2ECOA02w-XbslPob1yitMhbngKTwggrS8HbzXDTPQ8UXz7-TIC7ekCaSKsUwK#1