Step to UEFI (73) —– 获得鼠标信息

前面《获得 USB 设备的PID和VID》中提到 USB 鼠标上面有一个 SimplePointer 的 Protocol。 本文介绍一下这个 Protocol 的使用。

在【参考1】上提到EFI_SIMPLE_POINTER_PROTOCOL 的作用是获得鼠标或者轨迹球的输入数据。
“This would include devices such as mice and trackballs.
The EFI_SIMPLE_POINTER_PROTOCOLallows information about a pointer device to be
retrieved. This would include the status of buttons and the motion of the pointer device since the last
time it was accessed. This protocol is attached the device handle of a pointer device, and can be used
for input from the user inthe preboot environment.”

程序原理:首先用 LocateProtocol 取得EFI_SIMPLE_POINTER_PROTOCOL (这里假设系统中只有一个),然后做一次 Reset ,通过这个动作也确定设备是否可以使用。
image001

之后,不断使用 GetState 轮询,如果发现前后两次获得的信息不同那么就输出解析结果,打印当前鼠标的状态。
image002

特别注意,取得的信息如下,是一个 INT32 类型的 RelativeMovement 数值,这个数值必须通过 EFI_SIMPLE_POINTER_MODE 中给出来的 UINT64类型的Resolution 除一次才能得到真正的移动信息。
image003

最终的程序如下:

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

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

// Include/Protocol/SimplePointer.h
EFI_GUID gEfiSimplePointerProtocolGuid  = { 0x31878C87, 0x0B75, 0x11D5, 
			{ 0x9A, 0x4F, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }};
  
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;
	EFI_SIMPLE_POINTER_PROTOCOL	*Mouse;
	EFI_SIMPLE_POINTER_STATE	Last,Current;
	EFI_INPUT_KEY				Key;
	INT32		X,Y,Z;
	
	//Get MP_Service Protocol
	Status = gBS->LocateProtocol (&gEfiSimplePointerProtocolGuid, NULL, (VOID**)&Mouse);
	if (EFI_ERROR (Status)) {
		Print(L"Unable to initialize EFI_SIMPLE_POINTER_PROTOCOL protocol interface!");
		return EFI_UNSUPPORTED;
	}
	
	Status = Mouse->Reset(Mouse,TRUE);
	if (EFI_ERROR (Status)) {
		Print(L"The device is not functioning correctly and could not be reset!");
		return EFI_UNSUPPORTED;
	}
 
	gST-> ConOut -> ClearScreen(gST->ConOut);
    Print(L"Resolution: [%lX] [%lX] [%lX] \n",
			Mouse->Mode->ResolutionX,
			Mouse->Mode->ResolutionY,
			Mouse->Mode->ResolutionZ);

 
	while (1) {
		Status = Mouse->GetState(Mouse,&Current);
		if (memcmp(&Current,&Last,sizeof(EFI_SIMPLE_POINTER_STATE))!=0) {
		
			X=(INT32) ( Current.RelativeMovementX / Mouse->Mode->ResolutionX);
			Y=(INT32) (Current.RelativeMovementY / Mouse->Mode->ResolutionY);
			Z=(INT32) ( Current.RelativeMovementZ / Mouse->Mode->ResolutionZ);
			
			Print(L"X=[%d] Y=[%d] Z=[%d] ",
					X,
					Y,
					Z);
			Current.LeftButton?
					Print(L"[Left Click]"):
					Print(L"[No Left   ]");
			Current.RightButton?
					Print(L"[Right Click]\n"):
					Print(L"[No Right   ]\n");
			memcpy(&Last,&Current,sizeof(EFI_SIMPLE_POINTER_STATE))	;	
		}
		Status = gST -> ConIn -> ReadKeyStroke (gST->ConIn,&Key);
		if (Status== EFI_SUCCESS) {
			break;
		}
	}
	return EFI_SUCCESS;
}

 

运行结果:

image004
上面可以看到有一些前后没有变化的数据也被打印出来,这个可能是因为取得的原始数据有不同,但是做过除法之后数据是相同的导致的。

关于上面程序需要注意的地方:
1.EDK自带的模拟环境虽然能找到这个 Protocol 但是实际上不会有数据出来的。我试验了很久才发现,同样的程序实体机中跑的很好,但是模拟器不会有结果,所以不能在模拟环境中实验;
2.在处理INT32 INT64输出时要特别注意输出方式,否则会有奇怪的结果。

完整的代码下载:
micetest1

参考:
1. UEFI Spec 2.4 P461

<<与孩子一起学编程>>中的“滑雪的人”游戏

最近在学习 Python,看了一本书<<与孩子一起学编程>>英文名是“Computer Programming for Kids and Other Beginners”,感觉挺有意思的。

Capture

书中推荐用 pygame 来编写游戏,我按照书上的程序打了一个滑雪者的小游戏。输入程序花了半小时,调试这个程序差不多花了2个小时。

最后调试通过的程序如下:

import pygame,sys,random

skier_images = ["skier_down.png","skier_right1.png",
                "skier_right2.png","skier_left2.png",
                "skier_left1.png"]

class SkierClass(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("skier_down.png")
        self.rect = self.image.get_rect()
        self.rect.center = [320,100]
        self.angle =0

    def turn(self,direction):
        self.angle = self.angle + direction
        if self.angle < -2: self.angle = -2
        if self.angle >  2: self.angle = 2
        center = self.rect.center
        self.image = pygame.image.load(skier_images[self.angle])
        self.rect = self.image.get_rect()
        self.rect.center =center
        speed = [self.angle,6 - abs(self.angle) * 2]
        return speed

    def move(self,speed):
        self.rect.centerx = self.rect.centerx + speed[0]
        if self.rect.centerx < 20: self.rect.centerx = 20
        if self.rect.centerx > 620: self.rect.centerx =620

class ObstacleClass(pygame.sprite.Sprite):
    def __init__(self,image_file,location,type):
        pygame.sprite.Sprite.__init__(self)
        self.image_file = image_file
        self.image = pygame.image.load(image_file)
        self.location = location
        self.rect = self.image.get_rect()
        self.rect.center = location
        self.type =type
        self.passed =False

    def scroll(self, t_ptr):
        self.rect.centery = self.location[1] - t_ptr

def create_map(start, end):
        obstacles = pygame.sprite.Group()
        gates = pygame.sprite.Group()
        locations = []
        for i in range(10):
            row = random.randint(start,end)
            col = random.randint(0,9)
            location = [col * 64+20,row * 64 +20]
            if not (location in locations):
                locations.append(location)
            type = random.choice(["tree","flag"])
            if type == "tree": img = "skier_tree.png"
            elif type == "flag": img ="skier_flag.png"
            obstact = ObstacleClass(img,location,type)
            obstacles.add(obstact)
        return obstacles

def animate():
        screen.fill([255,255,255])
        pygame.display.update(obstacles.draw(screen))
        screen.blit(skier.image, skier.rect)
        screen.blit(score_text,[10,10])
        pygame.display.flip()

def updateObstacleGroup(map0,map1):
        obstacles = pygame.sprite.Group()
        for ob in map0: obstacles.add(ob)
        for ob in map1: obstacles.add(ob)
        return obstacles

pygame.init()
screen = pygame.display.set_mode([640,640])
clock = pygame.time.Clock()
skier = SkierClass()
speed = [0,6]
map_position =0
points=0;
map0 = create_map(20,29)
map1 = create_map(10,19)
activeMap =0;
obstacles = updateObstacleGroup(map0,map1)
font = pygame.font.Font(None,50)
        
while True:
    clock.tick(30)
    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                    speed = skier.turn(-1)
            elif event.key == pygame.K_RIGHT:
                    speed = skier.turn(1)
    skier.move(speed)
    map_position+=speed[1]

    if map_position >= 640 and activeMap == 0:
            activeMap = 1
            map0 = create_map(20,29)
            obstacles = updateObstacleGroup(map0,map1)
    if map_position>= 1280 and activeMap == 1:
            activeMap = 0
            for ob in map0:
                ob.location[1] = ob.location[1] -1280
            map_position = map_position - 1280
            map1 = create_map(10,19)
            obstacles = updateObstacleGroup(map0,map1)
            
    for obstacle in obstacles:
            obstacle.scroll(map_position)
    hit = pygame.sprite.spritecollide(skier,obstacles,False)
    if hit:
        if hit[0].type == "tree" and not hit[0].passed:
            points = points -100
            skier.image = pygame.image.load("skier_crash.png")
            animate()
            pygame.time.delay(1000)
            skier.image = pygame.image.load("skier_down.png")
            skier.angle = 0
            speed =[0,6]
            hit[0].passed = True
        elif hit[0].type == "flag" and not hit[0].passed:
            points +=10
            obstacles.remove(hit[0])
    score_text = font.render("Score: "+str(points),1,(0,0,0))
    animate()

 

运行结果:

Untitled

如果你也恰好阅读这本书,遇到同样的问题,建议你参考上面的程序,此外需要特别注意的是 Python 使用缩进之类的来表示上下文之间的隶属关系。如果你的程序遇到莫名其妙的错误,请检查是不是缩进搞错了。比如:

class x:
def func1
def func2

这样,func2 是 x 的一个方法了,如果你在其他地方试图直接调用 func2 就会出现未定 func2 的问题。我因为这样的事情被卡了很久。

最后放上完整的代码(包括官方网站下载到的官方板的源程序),如果你调试不过,最后还可以用beyond compare 这样的对比工具找到原因。

skier

Step to UEFI —– TIPs

每次编译 Application 之后生成的 efi 文件都在

\Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\APPNAME\APPNAME 这样的目录中,每次需要手工 copy 到虚拟出来的目录下,这样比较麻烦。

经过研究发现可以在 \AppPkg\AppPkg.dsc 中做如下修改

#OUTPUT_DIRECTORY = Build/AppPkg
OUTPUT_DIRECTORY = Build/NT32IA32

这样,每次最后生成 efi 文件之后就会有一个自动 copy 的动作。

tips

Step to UEFI (72) —– MP_Service_Protocol 获得CPU信息

UEFI本身不支持多线程,据说主要原因是:UEFI设计本身是为了启动硬件,做一些比较简单的事情,如果支持多线程会使得设计复杂度直线上升,出现问题也不容易调试。

不过UEFI本身是有对应的Protocol 的,称作 EFI_MP_SERVICES_PROTOCOL 。 具体的这个 Protocol 可以在 PI spec 中找到(UEFI Spec中没有)。下面来自【参考1】

image001

同样可以作为参考的还有EFI_Toolkit_2.0.0.1 中的process.c 程序。

程序很简单,首先 LocateProtocol 到EFI_MP_SERVICES_PROTOCOL。然后,用 Protocol 提供的EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS 和EFI_MP_SERVICES_GET_PROCESSOR_INFO 来获得关于处理器的一些信息。需要注意的是EFI_MP_SERVICES_GET_PROCESSOR_INFO,输入的参数ProcessorNumber 意思是:当前要获得的 Processor 号,输入0 表示你要去的编号为 0 的Processor 的信息,输入3 表示你要去的编号为 3 的Processor 的信息,取值范围从0到NumberOfProcessors -1。

image002

最终的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Pi/PiDxeCis.h>
#include  <Protocol/MpService.h>

EFI_GUID  gEfiMpServiceProtocolGuid = { 0x3fdda605, 0xa76e, 0x4f46, 
						{ 0xad, 0x29, 0x12, 0xf4, 0x53, 0x1b, 0x3d, 0x08 }};
  
extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;
	EFI_MP_SERVICES_PROTOCOL	*MP=NULL;
	UINTN   i;	
	UINTN	NumProcessors;
	UINTN	NumberOfEnabledProcessors;

	EFI_PROCESSOR_INFORMATION	ProcessorInfo;
	
	//Get MP_Service Protocol
	Status = gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID**)&MP);
	if (EFI_ERROR (Status)) {
		Print(L"Unable to initialize MP protocol interface!");
		return EFI_UNSUPPORTED;
	}
	
	// Determine number of processors
	Status = MP->GetNumberOfProcessors( MP, &NumProcessors , &NumberOfEnabledProcessors );
	
	if (EFI_ERROR (Status))
	{
		Print( L"MP->GetNumEnabledProcessors:Unable to determine number of processors\n") ;
		return EFI_UNSUPPORTED;
	}	
	Print(L"Number of Processors %d\n",NumProcessors);
	Print(L"Number of Enabled Processors %d\n",NumberOfEnabledProcessors);
	
	//Get more information by GetProcessorInfo
	for (i=0;i<NumProcessors;i++)
	{
		Status = MP->GetProcessorInfo(MP, i , &ProcessorInfo);
		
		Print( L"Prcoessor #%d ACPI Processor ID = %lX, Flags = %x, Package = %x, Core = %x, Thread = %x \n", 
					i,
					ProcessorInfo.ProcessorId, 
					ProcessorInfo.StatusFlag,
					ProcessorInfo.Location.Package,
					ProcessorInfo.Location.Core,
					ProcessorInfo.Location.Thread);
	}
	
	return EFI_SUCCESS;
}

运行结果:

image003

完整的代码下载:

MPTest1

关于这部分程序,在《UEFI 原理与编程》第13章 深入了解多任务 有更详细的论述,如果你有这方面的需求,推荐直接阅读。

另外,如果你想获得更多CPU方面的信息,比如 Cache大小, 还可以通过 CPUID,但是非常不建议通过 SMBIOS 来获得,因为他并不可靠……

最后特别提醒:

开始编写程序时,我遇到了下面的错误,错误代码对于解决问题毫无帮助,最后经过分析发现是因为没有添加PiDxe.h头文件(感谢Marco指出)导致的。

c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(367) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(465) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(623) : error C2061: syntax error : identifier ‘EFI_MP_SERVICES_STARTUP_ALL_APS’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2061: syntax error : identifier ‘StartupThisAP’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(628) : error C2059: syntax error : ‘}’
NMAKE : fatal error U1077: ‘”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\cl.exe”‘ : return code ‘0x2’
Stop.

参考:

1. Vol2_DXE CIS_1_3.pdf P174

Arduino 玩家不要购买CC3000 WiFi Shield

这个模块有供电的问题,用 USB 供电容易出现无法使用的情况。

前几天入手一个 CC3000 WiFi Shield 发现无法使用,经过查找和卖家沟通发现这个是通病,

无论是国外出售的还是国内的都有很大概率出现 USB 供电时无法使用的情况,

因此,有 WIFI 通讯需要的朋友请购买其他型号的。

Arduino 装置验证漩涡方向的问题

之前入手过一个9.9元的小水泵【参考1】,正好这次做实验用上了。他的工作电压最高是12V,我用MOS管 + Arduino的PWM来实现电压的控制。电路图和之前一个控制小灯泡的非常类似【参考2】。

#define PWMPin 10

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

void loop()
{
char c;

while (Serial.available() > 0)
{
c=Serial.read();
if (‘]’==c)
{
n=n+5;
}
if (‘[‘==c)
{
n=n-5;
}
//这里为了方便,加入一个可以直接开到最大值的字符
if (‘/’==c)
{
n=255;
}
//这里为了方便,加入一个可以直接关闭的字符
if (‘.’==c)
{
n=0;
}
if (n>255) {n=0;}
if (n<0) {n=255;}
analogWrite(PWMPin,n);
Serial.println(n);

}
}

电路图
pump

具体的实验内容是:使用一个底部有孔的小盆,证明放水时产生漩涡的方向和地球自转偏向力无关,而和放水时水流方向有关。

参考:
1. http://www.lab-z.com/waterpump/?nsukey=ZI7FePgtjom%2F3mfgzX9EtL5onGMfsUajYidhT5jUCO2t0hFAbtL%2B1LlY6YXFtxT785b4i%2Fe%2BSSjzo0wnFsIu1w%3D%3D 小水泵套件
2. 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 (71) —– 获得 USB 设备的 PID 和 VID

一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?

首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。

typedef struct _EFI_USB_IO_PROTOCOL {
  EFI_USB_IO_CONTROL_TRANSFER            UsbControlTransfer;
  EFI_USB_IO_BULK_TRANSFER               UsbBulkTransfer;
  EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER    UsbAsyncInterruptTransfer;
  EFI_USB_IO_SYNC_INTERRPUT_TRANSFER     UsbSyncInterruptTransfer;
  EFI_USB_IO_ISOCHRONOUS_TRANSFER        UsbIsochronousTransfer;
  EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER  UsbAsyncIsochronousTransfer;
  EFI_USB_IO_GET_DEVICE_DESCRIPTOR       UsbGetDeviceDescriptor;
  EFI_USB_IO_GET_CONFIG_DESCRIPTOR       UsbGetConfigDescriptor;
  EFI_USB_IO_GET_INTERFACE_DESCRIPTOR    UsbGetInterfaceDescriptor;
  EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR     UsbGetEndpointDescriptor;
  EFI_USB_IO_GET_STRING_DESCRIPTOR       UsbGetStringDescriptor;
  EFI_USB_IO_GET_SUPPORTED_LANGUAGES     UsbGetSupportedLanguages;
  EFI_USB_IO_PORT_RESET                  UsbPortReset;
} EFI_USB_IO_PROTOCOL;

来自【参考2】。

我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。

image001

我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。

image002

每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:

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

#include  <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;

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

UINTN GetUSB( )
{
  EFI_STATUS  Status;
  UINTN       HandleIndex, HandleCount;
  EFI_HANDLE  *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL 			*USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;

  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;
      }

      Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Print(L"VendorID = %04X, ProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);      

  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}

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

最终运行结果如下:

image003

完整的代码和程序下载:
GetPIDVID

参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义

processing中如何实现淡入一幅图像

简单的说就是加载图像,在显示的时候使用tint(gray,alpha) 不断调整 alpha 让他越来越不透明。

例子如下:

PImage img;
int i=0; 

void setup() {
size(640, 360);
img = loadImage("moonwalk.jpg"); // Load an image into the program 
}

void draw() { 
  tint(255, i++); // Display at half opacity
  image(img, 0, 0);
  if (i==40) {
    delay(5000);
    exit();
  }
  delay(200);
}

完整例子下载:

Transparency

Arduino 控制USB设备(8) 鼠标的 Boot Protocol

前面介绍了USB 鼠标数据的读取,个人感觉最困难的部分就是解析HID Descriptor,不同的鼠标的Descriptor不同,发出来的格式也不同。相比之下,键盘切换到 Boot Protocol 之后问题就简单多了。本文介绍如何将鼠标切换到这个模式下。
我们在之前中断方式获得鼠标改变代码的基础上,这样会使得整体效率比较高。
当 Mouse 工作在 Boot Protocol下面的时候,默认使用下面的 HID Protocol【参考1】
image001
根据资料,编写程序如下:

/* Mouse communication via interrupt endpoint  */
/* Assumes EP1 as interrupt IN ep              */
#include "max3421e.h"
#include "usb.h"
 
#define DEVADDR 1
#define CONFVALUE 1
#define EP_MAXPKTSIZE 5
EP_RECORD ep_record[ 2 ];  //endpoint record structure for the mouse
 
 
void setup();
void loop();
 
MAX3421E Max;
USB Usb;
 
void setup()
{
    Serial.begin( 115200 );
    Serial.println("Start");
    Max.powerOn();
    delay( 200 );
}
 
void loop()
{
 byte rcode;
    Max.Task();
    Usb.Task();
    if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
        mouse1_init();
    }//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
    if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { 
        rcode = mouse1_poll();
        if( rcode ) {
          Serial.print("Mouse Poll Error: ");
          Serial.println( rcode, HEX );
        }//if( rcode...
    }//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse1_init( void )
{
 byte rcode = 0;  //return code
 byte tmpdata;
 byte* byte_ptr = &tmpdata;
  /**/
  ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 ));  //copy endpoint 0 parameters
  ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
  ep_record[ 1 ].sndToggle = bmSNDTOG0;
  ep_record[ 1 ].rcvToggle = bmRCVTOG0;
  Usb.setDevTableEntry( 1, ep_record );             
  /* Configure device */
  rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
  if( rcode ) {
    Serial.print("Error configuring mouse. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }//if( rcode...
  
  rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
  if( rcode ) {
    Serial.print("Set Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  
  rcode = Usb.getIdle( DEVADDR, 0, 0, 0, (char *)byte_ptr );
  if( rcode ) {
    Serial.print("Get Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  Serial.print("Idle Rate: ");
  Serial.print(( tmpdata * 4 ), DEC );        //rate is returned in multiples of 4ms
  Serial.println(" ms");
  tmpdata = 0;
  rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
  if( rcode ) {
    Serial.print("Set Idle error. Return code : ");
    Serial.println( rcode, HEX );
    while(1);  //stop
  }
  
    /* Set boot protocol */
    rcode = Usb.setProto( DEVADDR, 0, 0, 0 );
    if( rcode ) {
         Serial.print("Error attempting to configure boot protocol. Return code :");
         Serial.println( rcode, HEX );
         while( 1 );  //stop
    }  
    
  Usb.setUsbTaskState( USB_STATE_RUNNING );
  return;
}
/* Poll mouse via interrupt endpoint and print result */
/* assumes EP1 as interrupt endpoint                  */
byte mouse1_poll( void )
{
  byte rcode,i;
  char buf[ 3 ] = { 0 };                          //mouse report buffer

  unsigned long int libuf[ sizeof(buf) ];
  unsigned long int x;
  unsigned long int y;

  
  /* poll mouse */
  rcode = Usb.inTransfer( DEVADDR, 1, sizeof(buf), buf, 1 );  //

    if( rcode ) {  //error
      if( rcode == 0x04 ) {  //NAK
        rcode = 0;
      }
      return( rcode );
    }
    
    for (int i=0;i<sizeof(buf);i++) {
       libuf[i]=(buf[i]) & 0xff;
       Serial.print(libuf[i],HEX); Serial.print("  "); 
    }
    
    Serial.println("");
  
    return( rcode );
}

 

运行结果:

image003

完整的代码下载

bootmouse

特别注意:经过我的实验,不是所有的鼠标都支持 Boot Protocol ,前面实验中用到的Dell 和HP鼠标都无法正常工作,可能是这两种鼠标不支持Boot Protocol 。换一个角度说,如果客户使用 USB 键盘,那么一定要有进入 BIOS Setup的需求,但是客户通常不会有在Setup中使用鼠标的需求,所以鼠标对于Boot Protocol的支持不好也在情理之中。

我发现好用的反倒是一个杂牌鼠标:

bmouse

参考:

1. HID Firmware Specification 1.11 P71

Step to UEFI (70) —– 获取 EDID 信息

之前介绍过使用 Arduino 用硬件的方法直接读取 EDID 【参考1】,这里介绍一下如何在 Shell下读取 EDID 信息。

我们使用 EFI_EDID_DISCOVERED_PROTOCOL 这个 Protocol 来获得信息。当然,类似的还可以使用 EFI_EDID_ACTIVE_PROTOCOL,都是没有问题的。

在要获取 EDID 信息之前,最好先使用 “DH -p EdidDiscovered” 命令来确定你当前的系统中存在 Edid 信息。例如:

edidpre

实现这个功能的代码如下:

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

#include  <Protocol/EdidDiscovered.h>


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

extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiEdidDiscoveredProtocolGuid = 

{ 0x1C0C34F6, 0xD380, 0x41FA, { 0xA0, 0x49, 0x8A, 0xD0, 0x6C, 0x1A, 0x66, 0xAA }};

EFI_STATUS GetEDIDInfo()
{
    EFI_STATUS  Status;
    EFI_HANDLE *EDIDHandleBuffer;
    UINTN EDIDHandleCount, index, i;
    EFI_EDID_DISCOVERED_PROTOCOL * EDID;
    
    Status = gBS->LocateHandleBuffer (ByProtocol, 
                                      &gEfiEdidDiscoveredProtocolGuid, 
                                      NULL, 
                                      &EDIDHandleCount, 
                                      &EDIDHandleBuffer); 
    if (EFI_ERROR (Status))
    {
      Print(L"ERROR : Can't get EdidDiscoveredProtocol.\n");   
      return FALSE;
    }
    
    Print(L"EDID count = %d\n", EDIDHandleCount);
    for(index = 0 ; index < EDIDHandleCount ; index++)
    {
      Status = gBS->OpenProtocol( EDIDHandleBuffer[index],
                                  &gEfiEdidDiscoveredProtocolGuid,
                                  (VOID**)&EDID,
                                  gImageHandle,
                                  NULL,
                                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                  );
      if (EFI_ERROR (Status))
      {
        Print(L"ERROR : Open EDID Protocol failed.\n");   
        return FALSE;
      }          
      
      Print(L"%d\n", EDID->SizeOfEdid);
      for(i = 0 ; i < 128 ; i++)
      {
        Print(L"%02X ", EDID->Edid[i]);
		
		if ((i+1) % 16 ==0) { 
				Print(L"\n"); }
		else	
			    if ((i+1) % 8 ==0) { Print(L"- ");}
      }
      Print(L"\n");
                
      Status = gBS->CloseProtocol(EDIDHandleBuffer[index],
			&gEfiEdidDiscoveredProtocolGuid, gImageHandle, NULL); 
      
      if (EFI_ERROR (Status))
      {
        Print(L"ERROR : Close EDID device handle failed.\n");   
        return FALSE;
      }
    }    
  return EFI_SUCCESS;    
}

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

 

上述程序运行结果

edidr

完整代码下载

GetEDID

比对之前获得的结果(我用的是同一个显示器,可以在【参考1】中看到),可以发现结果是相同的。

bq1

bq2

如果想程序解析每个值的具体含义,还按照【参考3】给出的例子。

参考:
1.http://www.lab-z.com/arduinoedid/ Arduino 读取显示器 EDID
2.http://biosren.com/thread-5921-1-1.html 求救如何取得EDID?
3.http://blog.fpmurphy.com/2012/09/accessing-edid-information-from-uefi-shell.html Accessing EDID Information From UEFI Shell