测试了一下编译速度

最近在搞 Connected Standby,基本上就是不停的在编译。忽然发现编译速度让人难以忍受。于是晚上抽空测试了一下编译的速度。
测试的基本方法是在编译的批处理开始和结尾加入类似下面的语句用来测算时间:
del /s /q build
echo %time% >> start.txt
echo %time% >> end.txt
首先上场的是工作用的笔记本电脑:Windows 8 系统, I5-4300 1.9-2.5G的CPU,内存是 8G,使用SSD。首先进行的是每次都要 clean的完全编译,时间基本上在28分钟左右。然后测试的是非完全编译,时间基本上在18分钟左右。很多时候,可能会修改一些配置文件,这样也会导致完全编译。所以,我基本上告别了笔记本的编译。

tbl1

再试试台式机,普通的HDD,CPU比较给力是 Intel I7 870 2.93-3.07G,内存8G。

tbl2

我个人感觉和硬盘可能有关系,所以用软件虚拟了一个2G的硬盘,从结果上来看确实有提升,但是感觉不大……

tbl3

看起来 CPU 的速度对于BIOS编译的影响更大一些。

关于 Arduino float 类型的2个问题和实验

第一个问题:如何输出一个 float 的值。可以使用dtostrf 函数【参考1】。这个函数接受4个参数【参考2】。
char* dtostrf(double _val,signed char _width, unsigned charprec, char* _s)
_val:要转换的float或者double值。
_width:转换后整数部分长度。
_prec:转换后小数部分长度。
_s:转换以后的结果放在_s中。
第二个问题:如何传输一个 float。 个人的建议是:按照byte 传输。这样避免了复杂的转换以及精度额损失。一个float 是4个字节的大小,我们直接把它们读取出来即可。
编写一个代码来实验:

float f1=1.234567;
float f2=1.234568;
byte *p;
byte  x1[]={0x4B,0x06,0x9E,0x3F};
byte  x2[]={0x53,0x06,0x9E,0x3F};
unsigned long x3=0x3F9E064B;
float *f;
  
  
void setup() {
  char s[20];
  Serial.begin(115200);
//我们将 f1 这个 float类型的数值转换为字符串类型,
//整数部分保留1位,小数部分保留6位
//可以从结果看到,可以正常输出1.234567
  dtostrf(f1,1,6,s);
  Serial.println(s);
  
//我们将 f1 这个 float类型的数值转换为字符串类型,
//整数部分保留1位,小数部分保留5位
//可以从结果看到,输出结果是 1.23456,最后的7被丢掉了
  dtostrf(f1,1,5,s);
  Serial.println(s);
//我们将 f1 这个 float类型的数值转换为字符串类型,
//整数部分保留1位,小数部分保留7位
//可以从结果看到,输出1.2345670 ,对于多出来的自动补零
  dtostrf(f1,1,7,s);
  Serial.println(s); 
  
//我们将一个 float 拆开为4个bytes输出 
  p=(byte *)&f1;
  Serial.print(*p,HEX);Serial.print(' ');
  Serial.print(*(p+1),HEX);Serial.print(' ');
  Serial.print(*(p+2),HEX);Serial.print(' ');
  Serial.print(*(p+3),HEX);Serial.println(' ');
  p=(byte *)&f2;
  Serial.print(*p,HEX);Serial.print(' ');
  Serial.print(*(p+1),HEX);Serial.print(' ');
  Serial.print(*(p+2),HEX);Serial.print(' ');
  Serial.print(*(p+3),HEX);Serial.println(' ');
  
//同样,我们可以将4个bytes合成一个 float 
  f=(float * )&x1;
  dtostrf(*f,1,6,s);
  Serial.println(s); 
  
  f=(float * )&x2;
  dtostrf(*f,1,6,s);
  Serial.println(s); 
  
  f=(float * )&x3;
  dtostrf(*f,1,6,s);
  Serial.println(s);   
}
  
void loop() {
  // put your main code here, to run repeatedly:
  //Serial.println("ggg");
}

 

运行结果:
214631m6m64qplpxg6fbit

因为 float这种类型的定义是有统一标准的,所以上面的方法还可以将 arduino 的数值通过串口传递给PC上的应用程序而不发生误差。
参考:
1. http://blog.protoneer.co.nz/ardu … ually-works/Arduino Float to String (That actually works)
2. http://www.geek-workshop.com/thread-3383-1-1.html【LCD12864】发个很多朋友都能用的上的东西,int转char,用于12864显示数值~

USB条码枪改直显和蓝牙

条码是人类的伟大发明之一,特别是对于零售业主来说。你可以看到现在从大到小的商店,都会放着一台收银机或者电脑,一个条码枪。用扫描的方式能够马上取得售卖的价格。有了条码枪,记忆力已经不是售货员必须的要求了。而在此之前,人么或者需要记住复杂的零售价格,或者用贴标的方式给每一样商品都打上“价签”,显而易见的是如果要进行调价,对于营业人员将是灾难,更简单一些的则是或者是像小抄一样在柜台边上准备一个小本子,忘记价格的时候只能偷看。此外,诸如火车站汽车站旁边的店铺则是店主根据相面结果来决定要价的。

从原理上来说是这样的:USB Barcode Scanner使用的是 USB Keyboard 的 HID协议,条码上面的每一个字符都会像按键一样从USB端口输入到 PC中。
首先使用 USB HOST Shield来解析USB Barcode Scanner的HID协议,和解析USB 键盘协议一样,我们够获得每次输入的键盘的按键信息,每一次输入的字符会被拼接到字符串中,直到收到结束的符号。这个字符串就是完整的条码信息。之后,我们将这个信息显示在 1602 LCD上,同时使用蓝牙键盘模块把这个字符传输到电脑上。

实验用到的设备如下:
USB条码枪 x1
USB Host Shield x1
蓝牙 HID键盘模块 x1
扩展面包板 x1 (可选)

完整的代码:

#include "LiquidCrystal_I2C.h"
#include <hidboot.h>
  
LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x27 for a 16 chars and 2 line display
  
char KeyPress[]=  {0x0C,0x00,0xA1,0x01,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00};
char KeyRelease[]={0x0C,0x00,0xA1,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
String Barcode;
  
class KbdRptParser : public KeyboardReportParser
{
  protected:
    void OnKeyDown  (uint8_t mod, uint8_t key);
};
  
void SendKeyByBT()
{
    for (byte i=0;i<sizeof(KeyPress);i++)
    {
     Serial.write(KeyPress[i]);
    }
   for (byte i=0;i<sizeof(KeyRelease);i++)
    {
     Serial.write(KeyRelease[i]);
    } 
}
void KbdRptParser::OnKeyDown(uint8_t mod, uint8_t key)
{
  uint8_t c = OemToAscii(mod, key);
  
  if (c)
  {
      KeyPress[6]=key;
      SendKeyByBT();   
      
      //Serial.print("ASCII: ");
      //Serial.println((char)c);
  
      Barcode=Barcode+ (char) c;
      if ((c==19) || (Barcode.length()> 40)) {
          lcd.clear();
           lcd.setCursor(0,0);
          
          //这里是单独发送的,因为我测试发现 1.6.10 的IDE存在一个 bug
         //lcd.print(Barcode) 会丢失字符
          for (byte i=0;i<Barcode.length();i++)
            {
              lcd.print(Barcode[i]);
              if (i==15) { lcd.setCursor(0,1);}
              }
          //Serial.println(Barcode);
          Barcode="";
      }
  }
  
}
  
USB     Usb;
HIDBoot<HID_PROTOCOL_KEYBOARD>    HidKeyboard(&Usb);
uint32_t next_time;
KbdRptParser Prs;
  
void setup()
{
  Serial.begin(9600);
  lcd.init();                      // initialize the lcd
  
  // Print a message to the LCD.
  lcd.backlight();
  //mySerial.begin(9600);
  
  //Serial.println("Start");
  
  if (Usb.Init() == -1)
    Serial.println("OSC did not start.");
  
  delay( 200 );
  
  next_time = millis() + 5000;
  
  HidKeyboard.SetReportParser(0, (HIDReportParser*)&Prs);
}
  
void loop()
{
  Usb.Task();
}
[align=left]

barc1
除了条形码之外,现在还有 RFID 之类的也能完成自动识别的工作,显而易见价格便宜是条形码最大的优点是。对于一般场合的应用,编码信息也足够用,因此在未来条形码还会有着巨大的生命力。

也许有一天,人们用条码来进行身份的识别和验证,就像下面这样:

bar2

完整代码下载
Barcodescaner

工作的视频

AMI code Shell 可能存在问题

最近有朋友询问 Shell 下编写 NSF 批处理的问题. 后来经过研究,他使用的是 AMI 自带的 Shell,在批处理方面存在一些问题,没有办法正确解析 IF 语句。

我在模拟器中确认过,同样的版本(2.50) UDK2015 自带的 SHELL 不存在这样的问题。

最后,他从U盘启动我给他的 SHELL 同样也没有问题,因此判定是 AMI 自带的 Shell 有一些特别的地方。

如果你的批处理遇到类似的状况,不妨尝试一下 UDK 自带的 Shell.

Step to UEFI (115)zLib 的压缩功能

有朋友留言我才想起来很早之前介绍过 zLib 的编译,但是没有编写一个完整的例子。这次补上这一块。

首先需要安装好zLib(现在都是在 UDK2015下面进行编译),我在 AppPkg.dsc 中加入下面的语句:

  CacheMaintenanceLib|MdePkg/Library/BaseCacheMaintenanceLib/BaseCacheMaintenanceLib.inf

  UefiHandleParsingLib|ShellPkg/Library/UefiHandleParsingLib/UefiHandleParsingLib.inf
  
  zLib|AppPkg/Applications/zsource/zlib.inf 
###################################################################################################
#
# Components Section - list of the modules and components that will be processed by compilation
#                      tools and the EDK II tools to generate PE32/PE32+/Coff image files.

 

之后就可以直接使用了。
压缩很简单,用下面这个函数即可

int compress (Bytef *dest,   uLongf *destLen, const Bytef *source, uLong sourceLen);

 

特别需要注意的是,压缩是在内存中进行的,所以需要开辟一段用来存放压缩结果的空间,这个空间的大小也就是上面的destLen。因此,在运行这个函数之前最好线运行一下预测压缩之后大小的函数:

 uLong compressBound (uLong sourceLen);

 

压缩之后的结果不会超过这个函数返回值(一般情况下要小得多)

压缩很简单,下面介绍解压缩:

int uncompress (Bytef *dest,   uLongf *destLen,const Bytef *source, uLong sourceLen);

 

和压缩函数非常类似,但是有一点特别注意的:这个函数用到的 destLen 是解压之后的大小,但是zLib没有提供预测解压之后大小的函数。据说原因是因为解压缩是压缩的“反函数”,因此,在压缩的时候应该已经知道解压后的大小。具体在使用的时候需要想办法自己定义结构体之类的将原始大小传递给解压函数以便开辟内存空间。譬如,在存放压缩的buffer前面加上4字节的大小之类的。

基本概念了解完就上代码,例子实现的功能是将 zlibtest.efi 压缩为test.lbz。然后再解压保存为 test.efi。

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

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Library/ShellLib.h>

#include  "zlib.h"

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

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_FILE_HANDLE   FileHandle;
	RETURN_STATUS     Status;  
	EFI_FILE_INFO     *FileInfo = NULL;
	UINT32  			SourceSize;  
	UINT32  			preSize;    
	CHAR8  *source=NULL;	
	CHAR8  *dest=NULL;	
//Read a source file
	//Open the file given by the parameter
	Status = 
		ShellOpenFileByName(
				L"zlibtest.efi", 
				(SHELL_FILE_HANDLE *)&FileHandle,
                EFI_FILE_MODE_READ , 
				0);
	if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }	
	  
	//Get file size	  
	FileInfo = 
		ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle);	

	//Allocate a memory buffer
	source = 
		AllocateZeroPool((UINTN) FileInfo-> FileSize);
	if (source == NULL) {
	  Print(L"Allocate source memory error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}

	SourceSize = (UINT32) (FileInfo-> FileSize & 0xFFFFFFFF);

	//Load the whole file to the buffer
	Status = ShellReadFile(FileHandle,&SourceSize,source);
	if (EFI_ERROR(Status)) {
		Print(L"Read file error [%r]\n",Status);
		ShellCloseFile(&FileHandle);
		return (EFI_SUCCESS);
	}
	ShellCloseFile(&FileHandle);
//Compress	
	preSize = compressBound((UINT32)SourceSize);
	Print(L"Source file size   : %d\n",SourceSize);  
	Print(L"Compress Bound Size: %d\n",preSize); 
	
	dest = AllocateZeroPool(preSize);	
	if (dest == NULL) {
	  Print(L"Allocate dest memory error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}
	  
	//Output
	Print(L"Compressing.........\n");	
	Status=compress(dest,&preSize,source,SourceSize);
	if (Status != Z_OK) {
	  Print(L"Compress error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}	
	Print(L"Compressing complete!\n");	
	Print(L"Compressed size: %d\n",preSize);  
	
//Save compressed result to a file
	//Create a new file
	Status = ShellOpenFileByName(L"test.lbz", 
                            (SHELL_FILE_HANDLE *)&FileHandle,
                            EFI_FILE_MODE_READ |
	 					    EFI_FILE_MODE_WRITE|
							EFI_FILE_MODE_CREATE, 
							0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
	}	
					
	Status = ShellWriteFile(FileHandle,
					&preSize,
					dest
				);
			
	//Close the source file
	ShellCloseFile(&FileHandle);
	
//This program is just a demo for showing how to use zlib.
//Uncompress test
	Status=uncompress(source,&SourceSize,dest,preSize);
	if (Status != Z_OK) {
	  Print(L"Decompress error!\n");
      return (SHELL_OUT_OF_RESOURCES);   
	}
	
	//Output
	Print(L"decompress Size: %d\n",SourceSize);

	//Create a new file
	Status = ShellOpenFileByName(L"test.efi", 
                            (SHELL_FILE_HANDLE *)&FileHandle,
                            EFI_FILE_MODE_READ |
	 					    EFI_FILE_MODE_WRITE|
							EFI_FILE_MODE_CREATE, 
							0);  
	if(Status != RETURN_SUCCESS) {
			Print(L"CreatFile failed [%r]!\n",Status);
			return EFI_SUCCESS;
	}	
					
	Status = ShellWriteFile(FileHandle,
					&SourceSize,
					source
				);
			
	//Close the source file
	ShellCloseFile(&FileHandle);	
	
	FreePool(dest);  
	FreePool(source);   
  return EFI_SUCCESS;
}

 

运行结果:

zlib1

我们直接比较压缩前和解压后的结果,是相同的。

完整的代码下载:

zlibtest

参考:
1. http://www.cppblog.com/woaidongmao/archive/2009/09/07/95495.html zlib用法简单说明

Step to UEFI (114)CHAR16大小写转换

最近编写程序,需要用到 CHAR16 的大小写转换,忽然惊奇的发现远比CHAR8的转化复杂,没有可以直接使用的函数。研究了一番,猜测产生这样问题的原因是 CHAR16 是定义给 Unicode 使用的。而Unicode 需要支持多种语言格式,比方说:’a’的大写是’A’,‘一’的大写就是‘壹’了,所以处理上也是复杂的。

从 UEFI Spec 来看,标准的做法要求使用EFI_UNICODE_COLLATION_PROTOCOL 。对应的头文件在 \MdePkg\Include\Protocol\UnicodeCollation.h 中。有下面几个功能:

///
/// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive 
/// comparisons of strings. 
///
struct _EFI_UNICODE_COLLATION_PROTOCOL {
  EFI_UNICODE_COLLATION_STRICOLL    StriColl;  
  EFI_UNICODE_COLLATION_METAIMATCH  MetaiMatch; 
  EFI_UNICODE_COLLATION_STRLWR      StrLwr;
  EFI_UNICODE_COLLATION_STRUPR      StrUpr;

  //
  // for supporting fat volumes
  //
  EFI_UNICODE_COLLATION_FATTOSTR    FatToStr;
  EFI_UNICODE_COLLATION_STRTOFAT    StrToFat;
  
  ///
  /// A Null-terminated ASCII string array that contains one or more language codes.
  /// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format.
  /// When it is used for UnicodeCollation, it is specified in ISO 639-2 format.
  ///
  CHAR8                             *SupportedLanguages;
};

 

StriColl 进行大小写敏感的字符串比较
MetaiMatch 判断字符串是否匹配一个带有通配符的模版
StrLwr 字符串转为小写
StrUpr 字符串转为大写
FatToStr 将8.3格式的OEM字符集的FAT文件名转为字符串
StrToFat 将字符串转为OEM定义的字符集
*SupportedLanguages ASCII给出的语言的列表

上述只是简单的介绍,我也并没有仔细验证,更详细的请阅读UEFI Spec Protocols – String Services 章节。
除了,上面介绍的方法,如果你确定字符串中只有 ASCII , 那么还可以直接处理字符串中的 ASCII ,下面的实例演示了上面的2种做法:

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

#include <Protocol/UnicodeCollation.h>

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

EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

VOID
EFIAPI
lowercase (
  IN OUT CHAR16	*Str
  )
{
	UINT32	i;
	CHAR8	*p;
	p=(CHAR8 *)Str;
	for (i=0;i<StrSize(Str);i++) {
		//Print(L"%d ",*p);
		if ((*p>='A')&&(*p<='Z')) {
			*p=*p+32;
		}
		p++;
	}
	
}	
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS	Status;
  CHAR16		*s1=L"Www.Lab-z.Com 1243";
  CHAR16		*s2=L"Www.Lab-z.Com 1243";
  
  if (mUnicodeCollation == NULL) {
		Status = gBS->LocateProtocol(
			&gEfiUnicodeCollation2ProtocolGuid,
			NULL,
			(VOID**)&mUnicodeCollation);

		if (EFI_ERROR(Status)) {
			Print(L"Can't find UnicodeCollation2 Protocol!\n"); 
			return EFI_SUCCESS;
		}
  }

  mUnicodeCollation->StrUpr(
    mUnicodeCollation,
    s1);
  Print(L"%s\n",s1);
  
  lowercase (s2);
  Print(L"%s\n",s2);  
  return EFI_SUCCESS;
}

 

运行结果:

ultest

完整的代码下载

ULTest

Step to UEFI (113)获得简单的输入的方法

很多时候,我们的程序需要和用户进行简单的交互,当然可以设计菜单之类的,但是从代码的角度来说依然很复杂,我们并不希望为了获得一个确认而大费周章。这时候,可以直接使用 Shell 提供的ShellPromptForResponse 函数。
函数原型在ShellLib.h 中:

/**
  Prompt the user and return the resultant answer to the requestor.

  This function will display the requested question on the shell prompt and then
  wait for an apropriate answer to be input from the console.

  If the SHELL_PROMPT_REQUEST_TYPE is SHELL_PROMPT_REQUEST_TYPE_YESNO, ShellPromptResponseTypeQuitContinue
  or SHELL_PROMPT_REQUEST_TYPE_YESNOCANCEL then *Response is of type SHELL_PROMPT_RESPONSE.

  If the SHELL_PROMPT_REQUEST_TYPE is ShellPromptResponseTypeFreeform then *Response is of type
  CHAR16*.

  In either case *Response must be callee freed if Response was not NULL;

  @param Type                     What type of question is asked.  This is used to filter the input
                                  to prevent invalid answers to question.
  @param Prompt                   The pointer to a string prompt used to request input.
  @param Response                 The pointer to Response, which will be populated upon return.

  @retval EFI_SUCCESS             The operation was successful.
  @retval EFI_UNSUPPORTED         The operation is not supported as requested.
  @retval EFI_INVALID_PARAMETER   A parameter was invalid.
  @return other                   The operation failed.
**/
EFI_STATUS
EFIAPI
ShellPromptForResponse (
  IN SHELL_PROMPT_REQUEST_TYPE   Type,
  IN CHAR16         *Prompt OPTIONAL,
  IN OUT VOID       **Response OPTIONAL
  );

 

简单的说这个函数提供了3种用法:

1. 使用ShellPromptResponseTypeFreeform 参数,接收全部输入的字符串;
2. 使用ShellPromptResponseTypeYesNo 这样,用户输入 y 表示 Yes, n 表示 No
3. 使用ShellPromptResponseTypeEnterContinue 进行按键等待的

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

#include  "ShellLib.h"

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

SHELL_PROMPT_RESPONSE
EFIAPI
Resp()
{
	
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR16 *InputStr = NULL;
 
	ShellPromptForResponse(
			ShellPromptResponseTypeFreeform, 
			L"Please input->", 
			(VOID**)&InputStr);
	Print(L"Get input by freeform %s\n",InputStr);

	ShellPromptForResponse(
			ShellPromptResponseTypeEnterContinue, 
			L"Press Enter to continue...",
			NULL);

	ShellPromptForResponse(
			ShellPromptResponseTypeAnyKeyContinue, 
			L"Press any key to continue...",
			NULL);			
			
	SHELL_PROMPT_RESPONSE *Resp=NULL;			
	ShellPromptForResponse(ShellPromptResponseTypeYesNo, L"Type 'y' or 'n'->",(VOID**)&Resp);
    switch (*(SHELL_PROMPT_RESPONSE*)Resp) {
          case ShellPromptResponseNo:
            Print(L"Choose No\n");
            break;
          case ShellPromptResponseYes:
            Print(L"Choose Yes\n");
            break;	
		  default:
			break;
	}

	
	
	return EFI_SUCCESS;
}

 

运行结果:

sinput

可以看到,这个函数还可以用来实现回车键继续或者任意键继续的功能。
关于这个函数,具体的实现代码可以在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中看到,有兴趣的朋友可以深入研究一下。

完整的代码下载
InputTest

好用的UEFI GUID工具

前面提到过在编写UEFI 程序的时候不可避免的需要引用PROTOCOL 等等的 GUID。最近在网上看到了一个Python 编写的工具,作者是Xiang Lian【参考1】,可以将项目中定义的 GUID都提取到一个文件中个,这样便于查找和使用。我实验了一下,挺好用的推荐给大家。

#!/c:\python27\python.exe
#
#      Filename:   guidxref.py
#       Written:   Xiang Lian
#
#
# This python script scans through UDK2010 build tree and saves all GUID definitions into
# an output file. Refer list TargetFileTypes for currently suported source file types.
#
#-------------------------------------------------------------------------------------------------
#
#   Rev history:   rev 1.0    10/07/2012
#                       - Guid_In_h had missed some lower-cased GUIDs;
#
#                  rev 1.1    10/07/2012
#                       - Added captured groups in Guid_In_h, refer RegFormatOutput;
#
#                  rev 1.2    10/08/2012
#                       - Swapped content in output line so that GUID strings always come first
#
#                  rev 1.5    10/08/2012
#                       - Simplified os.walk logic to significantly reduce the redundant scans
#                       - Added summary to report total number of files scanned by type
#
#                  rev 1.6    10/08/2012
#                       - Added logging module, to turn on logging output:
#                             . Uncomment the logging.basicConfig line
#                             . Choose level in basicConfig
#                       - Always save result into a newly create output file
#
#                  rev 2.0    10/09/2012
#                       - Created function NormalizeGuidString to replace the buggy RegFormatOutput pattern.
#                         Now all output GUIDs have a fixed length and are made uppercase
#
#                  rev 2.2    10/11/2012
#                       - Added list ExcludedDirs which folders will not be scanned
#                       - Added lambda function toU to conver registry format GUIDs to uppercase
#                       - Output filenames are now including timestamp strings
#                       - Logging filenames are always saved into a newly created file (suffixed with seq#)
#
#                  rev 3.0    10/11/2012
#                       - Added filter to remove some invalid lines in the output
#                       - Output lines are sorted and all duplicates removed
#
#                  rev 3.1    10/19/2012
#                       - Collected all user customizable values/configs into class UserConfig
#                       - Minor adjustment on summary output
#
#                  rev 3.2    10/25/2012
#                       - Modified Guid_In_h pattern to match wider range of GUID formats (in EDK)
#
#                  rev 3.3    10/30/2012
#                       - Defined VimScriptMode switch to output result in vim script format
#
#                  rev 3.4    10/31/2012
#                       - Added .c into the scan file list (TargetFileTypes)
#                       - Added (?i) in Guid_In_h: This makes the entire pattern case in-sensitive
#                       - Resolved lines like: EFI_GUID  PciIoProtocolInstallEventGuid = {...};
#                       - Resolved lines containing tabs
#                       - No more year info in result file name
#
#                  rev 3.5    10/31/2012
#                       - Added NormalizedGuidLine for final validity check
#
#
#
#
#
#
# Cases currently cannot be handled:
#
#    EFI_GUID  gEfiUsbKeyboardDriverGuid = {
#      0xa05f5f78, 0xfb3, 0x4d10, 0x90, 0x90, 0xac, 0x4, 0x6e, 0xeb, 0x7c, 0x3c
#    };
# 
#----------------------------------------------------------------------------------------------------
#
import os, re, string, sys
import logging
import datetime


#
# Global variables
#
class UserConfig:
  """ This class defines a set of constants and configurations which can be customized by 
      the script user. 
  """
  ScriptRev = " Ver 3.5"

  # To generate logging output, change this to 1
  LoggingEnable = 0

  # Set this to 1 to generate the result file in vim script format
  VimScriptMode = 0

  # The maximum character width of the console output line
  MAXLINEWIDTH = 110

  # This list provides all the file types to be scanned
  TargetFileTypes = {'.h' : 0, '.dec' : 0, '.inf' : 0, '.dsc' : 0, '.c' : 0}

  # Directories to be excluded from the scan
  ExcludedDirs = ('.svn', 'Build', 'uefi64native')

  # Base file name for result file
  BaseOutputName = "guidxref_"

  # Base file name for logging output
  BaseLogName = ".\debug"



# This defines the continuation character at end of line
ContinuingEol = "\\\n"

# Define directive in GUID definition (usually in .h and .c files)
DefineDirective = "#define"

# Header part of the GUID definition line (usually in INF or DSC files)
RegGuidDef = "^.*\=\s*"          #note: "^\(.*\)\=\s*" doesn't work!

# GUID Definitive format - Below pattern matches lines like: 
#      { 0xbf9d5465, 0x4fc3, 0x4021, {0x92, 0x5, 0xfa, 0x5d, 0x3f, 0xe2, 0xa5, 0x95}}
Guid_In_h = "(?i)\{\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*{?\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+),\s*\
0x([0-9a-f]+)\s*}?\s*\}.*"

#This is buggy, has already been replaced by NormalizeGuidString2
RegFormatOutput = r"\1-\2-\3-\4\5-\6\7\8\9\10\11"   # Note: have to add prefix 'r' to make it raw here

# GUID Registry format - Below pattern matches lines like:  FILE_GUID = A5102DBA-C528-47bd-A992-A2906ED0D22B
Guid_In_Inf = "[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+-[0-9a-fA-F]+"

# Normalized GUID lines like: A5102DBA-C528-47bd-A992-A2906ED0D22B  xxxx
NormalizedGuidLine = r"[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}  [^#]+"


#################################### Functions Definition #################################################

def NormalizeGuidString (matchobj):
  """ Definitive format GUID string normalization - Prefixing with 0s for every captured group
      
      Parameter matchobj is a MatchObject instance which contains one or more subgroups of the pattern
      match done by the re.sub() method. It's same as the return object of re.search(). 
  """
  hex = [""]
  for i in range (1, 12):
    hex.append(matchobj.group(i))
  hex[1]  = format (int(hex[1],  16), '08x').upper()
  hex[2]  = format (int(hex[2],  16), '04x').upper()
  hex[3]  = format (int(hex[3],  16), '04x').upper()
  hex[4]  = format (int(hex[4],  16), '02x').upper()
  hex[5]  = format (int(hex[5],  16), '02x').upper()
  hex[6]  = format (int(hex[6],  16), '02x').upper()
  hex[7]  = format (int(hex[7],  16), '02x').upper()
  hex[8]  = format (int(hex[8],  16), '02x').upper()
  hex[9]  = format (int(hex[9],  16), '02x').upper()
  hex[10] = format (int(hex[10], 16), '02x').upper()
  hex[11] = format (int(hex[11], 16), '02x').upper()
  return hex[1]+'-'+hex[2]+'-'+hex[3]+'-'+hex[4]+hex[5]+'-'+hex[6]+hex[7]+hex[8]+hex[9]+hex[10]+hex[11]


def SearchGuidsFromList (SrcList, filename):
  """ This function searchs for GUID definitions from a given string list.
  """
  GuidLines = []
  for n,line in enumerate(SrcList):
    while line.endswith(ContinuingEol):
      line = line.rstrip(ContinuingEol)
      #this doesnt work?? line = re.sub("\\\n", "", line)
      MergeLine = [line, SrcList[n+1]]
      line = "".join(MergeLine)      # This converts a list to a string
      del SrcList[n+1]
      #logging.debug ("  Merged line #%d, %s", n, line)
  
    # Now start searching for GUID pattern

    # Process .inf and .dsc files
    match = re.search(Guid_In_Inf, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a registry format GUID")
      line = re.sub(RegGuidDef, filename + "  ", line)       # Trim out useless part
      line = re.sub(Guid_In_Inf, lambda toU: toU.group().upper(), line) # Convert GUID to uppercase
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = line.lstrip()
      line = re.sub("\A(.*?)\s+(.*)", r"\2  \1", line)       # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

    # Process .h and .dec files
    match = re.search(Guid_In_h, line, re.IGNORECASE | re.MULTILINE)
    if match:
      logging.debug ("Found a definitive GUID")
      line = re.sub(DefineDirective, "", line)             # Trim out useless part
      line = re.sub(Guid_In_h, NormalizeGuidString, line)  # Convert to registry format
      #str = raw_input ("................................................. Press ENTER key to continue:")
      line = re.sub(r"\A(.*?)EFI_GUID\s+", "", line)   # Trim EFI_GUID 
      line = line.lstrip()
      line = re.sub("\A(.*?)[ =\t]+(.*)", r"\2  \1", line)   # Swap it. lx-'\A' and '?' are both important
      GuidLines.append(line)

  return GuidLines


def main():

  # Configure the logging module to send debug messages to file
  #
  # Refer:
  #    logging.debug (msg, *args)
  #    logging.info (msg, *args)
  #    logging.warning (msg, *args)
  #    logging.error (msg, *args)
  #    logging.critical (msg, *args)
  #    logging.setLevel (level)
  #    logging.disable (level)
  #
  # Valid values to set for level (by severity) in basicConfig are: NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
  #
  #full format: logging.basicConfig(level=logging.DEBUG, filename='debug.log', format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
  if UserConfig.LoggingEnable:
    for seq in range (0, 99):
      LogFileName = UserConfig.BaseLogName + format (seq, '02d') + ".log"
      if not os.path.exists(LogFileName):
        break
      seq += 1
    logging.basicConfig(level=logging.DEBUG, filename=LogFileName, format='%(levelname)s: %(message)s')

  # Ensure input parameter is valid
  if len(sys.argv) < 2:
    print "Usage - ", sys.argv[0], " <Directory>\n"
    exit(0)

  RootDir = sys.argv[1]
  if (os.path.exists(RootDir) == False):
    print "Invalid directory name, please try again!"
    exit(1)
  
  # Determine output file
  now = datetime.datetime.now()
  ##suffix = now.strftime ("%Y_%m_%d_%H_%M_%S")
  suffix = now.strftime ("%m_%d_%H_%M_%S")

  if UserConfig.VimScriptMode == 0:
    ofile = open(UserConfig.BaseOutputName + suffix + ".txt", "w")
    # Print header message
    ofile.write("Generated by " + sys.argv[0] + UserConfig.ScriptRev + "\n")
    ofile.write("=" * 40 + "\n\n")
  else:
    ofile = open(UserConfig.BaseOutputName + suffix + ".vim", "w")
    # Print header option settings
    ofile.write(":set mm=2000000\n")
    ofile.write(":set undolevels=-1\n")

  # Traverse the root directory path for required source files
  TotalGuids = 0
  TempBuffer = []
  for root, dirs, files in os.walk(RootDir):
    # Check directories to be excluded from the scan
    for folder in UserConfig.ExcludedDirs:
      if folder in dirs:
        dirs.remove(folder)

    logging.debug ('  root = %s', root)
    #logging.debug ('  dirs = %s', dirs)
    #for file in files:
    #logging.debug ('  file = %s', file)
  
    for file in files:
      for type in UserConfig.TargetFileTypes.keys():
        if file.endswith(type):
          logging.info ("Found needed files = %s\\%s", root, file)
          UserConfig.TargetFileTypes[type] += 1

          # Scan the file for GUID strings
          try:
            fullpath = os.path.join(root, "".join(file))
            ifile = open(fullpath, "r")
          except:
            print folder, "\\", file, " could not be opened, abort!"
            sys.exit(2)
          
          logging.debug ('Opening source file ........%s', fullpath)
          AllLines = ifile.readlines()
          GuidList = SearchGuidsFromList (AllLines, "".join(file))
          if (len(GuidList) > 0):
            OutputLineWidth = UserConfig.MAXLINEWIDTH - len(fullpath)
            print fullpath, "." * OutputLineWidth, len(GuidList)
            for line in GuidList:
              # Remove some invalid lines
              if not re.search (r"\/\/$", line, re.MULTILINE):    #lx: "\Z" and "\z" don't work. ??
                TempBuffer.append(line)

          ifile.close()

  # Remove duplicates from the list
  #
  # [Python.docs] A set object is an **unordered** collection of distinct hashable objects. Common uses 
  # include membership testing, removing duplicates from a sequence, and computing mathematical operations 
  # such as intersection, union, difference, and symmetric difference.
  #
  TempBuffer = list(set(TempBuffer))

  # Now sort the list
  #
  # [Python.docs] sorted (iterable [, key][, reverse])
  #
  #   key - specifies a function of one argument that is used to extract a comparison key from
  #         each list element: key=str.lower. The default value is None (compare the elements directly)
  #   reverse - a boolean value. If set to True, the list elements are sorted as if each comparison 
  #             were reversed.
  #
  TempBuffer = sorted(TempBuffer)
  for line in TempBuffer:
    if re.match(NormalizedGuidLine, line):
      #
      # Ideally we don't need to add this extra validity check on each processed line. However there
      # are always some corner cases or various special cases which cannot be well handled by current
      # code logic. These filtered lines here can later be investigated when I have spare time.
      # 
      TotalGuids += 1
      if UserConfig.VimScriptMode:
        line = re.sub(r"\A([^ ]*)  (.*)$", r":%s/\1/\2/e", line)
      ofile.write(line)
    else:
      #lx-Adding following lines will unexpectedly remove the next line as well, why?
      #TempBuffer.remove(line)
      print "Invalid line: ", line

  ofile.close()

  # Print summary
  print "\n", "-" * 50, "Summary", "-" * 55
  for type in UserConfig.TargetFileTypes.keys():
    print "Scanned ", format (UserConfig.TargetFileTypes[type], '04d'), format (type, "4s"), " files"

  print "\nTotal number of GUIDs found: ", TotalGuids 


#
# Why do we need this?
# A .py file can be interpreted by Python as either standalone program to execute directly,
# or a module to be imported into other .py files. 
#   1) Standalone program - __name__ equals to "__main__"; 
#   2) imported as a module - __name__ equals to something else, therefore contents behind
#      the if statement won't get executed.
#
if __name__ == "__main__":
  main()

 

下载:

guidxref

参考:
1. https://github.com/simonlian/guidref/blob/master/guidxref.py

Step to UEFI (112)FSNTx和FSx

之前的文章提到过,我注意到UDK2014自带的Shell 使用的是 Fsnt0 这样的盘符,而我重新编译生成的却是 Fs0 这样的盘符。今天研究和实验相关代码,解开了困扰已久的问题。

首先,这个盘符的概念不是UEFI 的,而是Shell 这个程序自己创建和分配的。于是在代码中搜索,在UefiShellCommandLib.c 找到了下面的代码:

/**
  Function to generate the next default mapping name.

  If the return value is not NULL then it must be callee freed.

  @param Type                   What kind of mapping name to make.

  @retval NULL                  a memory allocation failed.
  @return a new map name string
**/
CHAR16*
EFIAPI
ShellCommandCreateNewMappingName(
  IN CONST SHELL_MAPPING_TYPE Type
  )
{
  CHAR16  *String;
  ASSERT(Type < MappingTypeMax);

  String = NULL;

  String = AllocateZeroPool(PcdGet8(PcdShellMapNameLength) * sizeof(String[0]));
  UnicodeSPrint(
    String,
    PcdGet8(PcdShellMapNameLength) * sizeof(String[0]),
    Type == MappingTypeFileSystem?L"FS%d:":L"BLK%d:",
    Type == MappingTypeFileSystem?mFsMaxCount++:mBlkMaxCount++);

  return (String);
}

 

可以看到,使用的是FS%d 这样的来进行分配,用重新编译Shell的方式确定这个位置。

然后我们修改代码,增加一个字符作为标记:

Type == MappingTypeFileSystem?L"FSz%d:":L"BLK%d:",

 

根据【参考1】进行编译,当然我们已经工作在UDK2015上,但是之前的方法仍然是可行的。编译好Shell.efi 之后,我们还要查看一下 \Nt32Pkg\Nt32Pkg.fdf 对于 Shell 的调用。看起来和之前不同了,我们猜测使用了UefiShell.inf 。

################################################################################
#
# FILE statements are provided so that a platform integrator can include
# complete EFI FFS files, as well as a method for constructing FFS files
# using curly "{}" brace scoping. The following three FILEs are
# for binary shell, binary fat and logo module.
#
################################################################################
!ifndef $(USE_OLD_SHELL)
INF  ShellBinPkg/UefiShell/UefiShell.inf
!else
INF  EdkShellBinPkg/FullShell/FullShell.inf
!endif

INF FatBinPkg/EnhancedFatDxe/Fat.inf

FILE FREEFORM = PCD(gEfiIntelFrameworkModulePkgTokenSpaceGuid.PcdLogoFile) {
    SECTION RAW = MdeModulePkg/Logo/Logo.bmp
  }

 

在打开UefiShell.inf 根据指引替换了 Shell.efi 为新编译出来的。再 build nt32和 build run。 得到的结果如下,可以看到,盘符发生了变化:

image001

我们再用十六进制工具打开 shell.efi ,搜索 fsz (特别注意这是unicode不是 ascii)

image003

因此,盘符的前缀就是在这里生成的。

然后我们再返回到 UDK2014中,打开模拟器运行结果如下:

image005

这就是我们一直疑惑的 FsntX: 直接使用十六进制编辑工具打开 Shell_Full.efi ,搜索 Fsnt 。找到之后我们修改为 Fsnz,再保存,重新build Nt32,然后再次运行编译器,结果就发生了变化:

image007
于是,可以解释我们之前的疑惑了:他们 Publish 的 Shell.efi 和我们拿到的并非一套代码,一些细节存在差别。
本文只相当于定性的分析,后面我们还会进行更详细的分析,到底盘符是如何来的,就是这样。

参考:
1. http://www.lab-z.com/how2buildshell/ How to build Shell.efi

Arduino 101 可以用来生成 1-1Mhz的方波

最近在查看资料【参考1】的时候,发现 101 提供了定时器输出 PWM 的功能。

“定时器输出PWM
除了作中断源使用,定时器也可以用作PWM输出,CurieTimerOne提供的pwmStart函数可以输出PWM。
在之前的章节中使用的analogWrite函数输出的PWM,周期固定,占空比可调,可用作LED调光;tone函数输出的PWM,周期不变,占空比可调,可用作无源蜂鸣器发声;而pwmStart输出的PWM周期和占空比都可调,更具灵活性,适用场合更广。
需注意的是pwmStart是重载函数,其有两种重载方式:

pwmStart(unsigned int outputPin, double dutyPercentage, unsigned int periodUsec);

pwmStart(unsigned int outputPin, int dutyRange, unsigned int periodUsec);

参数outputPin为输出PWM的引脚编号,periodUsec为每个周期的时间,单位为微秒。
而第二个参数可以为double 型,也可以为int型。当参数为double 型时,编译器会以dutyPercentage进行重载,参数以百分比形式表示PWM占空比;当参数为int型时,编译器会以dutyRange进行重载,参数以0到1023的形式表示PWM占空比;”

正好,手上有示波器,随手测量一下,发现效果不错

下面是生成的 1K 结果:
1K

2.5K 结果:
25k

25K 结果:
205k

参考:
1.http://www.arduino.cn/thread-42007-1-1.html 【Arduino101教程】定时器的使用