检查当前是否支持 DEVSLP 的软件

DEVSLP 是 Intel 推出的用于 SATA硬盘的省电功能。在PCH上,有一个Pin,当HIGH时,表示要求硬盘进入省电模式,否则处于Active模式。

下面是一段介绍,来自【参考1】

DEVSLP is a signal that is sent to a SATA disk drive to tell it to enter a very low power state. The SATA specification has always allowed for the drive to enter a low power state, but in the past the drive had to keep its high speed transceiver circuitry powered up in order to receive the signal to wake up again. DEVSLP removes this power hungry requirement by using a seperate low speed pin.

The ability to enter an ultra low power state is crucial for ultrabooks and other battery powered devices. Using DEVSLP, manufacturers are able to produce devices with power consumption of micro Watts compared to milli Watts in previous generation drives

Where did they find the extra pin?

There were no spare pins in the SATA conector standard for 2.5″ and 3.5″ drives (SFF-8482). Revision 3.2 assigns pin P3 as DEVSLP. Pins P1,P2 and P3 used to be a 3.3V power supply to the drive, very few drives (if any) and very few systems ever implemented 3.3V power so the pins were re-designated. If you should happen to put a DEVSLP enabled drive in a system that provides 3.3V power, the drive will go into low power mode and stay there.

如果想实现这个功能,首先需要硬盘支持,在ATAPI的ID命令中有定义对应的位置(Device Sleep Supported)。然后需要在BIOS中打开对应的DEVSLP 功能。
推荐使用 TxBench工具【参考2】能够直接检测当前的硬盘是否支持以及系统中这个功能是否打开。

下图是BIOS中打开DEVSLP 的截图。 1标记位置是当前硬盘是否支持,2标记位置是当前系统是否支持DEVSLP。可以看到都是支持的。

image001

下图是BIOS中关闭DEVSLP的截图,可以对照上面查看。

image002

下面是对这块硬盘(Samsung 750 EVO )的简单性能测试:

image003

如果你是专门为 Connected Standby 选购硬盘的话,个人建议最好入手 Intel SSD最保险。

参考:
1. http://www.storageinterface.com/articles/12-sata-devslp
2. http://www.texim.jp/txbenchus.html

Step to UEFI (111)Shell下面的HDD菜单

最近在尝试编写Shell下面的一些关于磁盘工具,设计了一个Shell 下面硬盘的选择菜单。这个程序会枚举当前系统中的全部硬盘,然后以菜单的形式提供给客户操作。
程序流程介绍,首先,枚举系统中全部Block Io 的 Handle,之后检查每个 Block Io 的Media,看看是否为物理硬盘,因为我们的操作目标是完整的硬盘而不是某个分区。接下来,再针对每个Handle枚举上面的 DevicePath Protocol, 我们需要这个 Protocol 转化出来的String作为给用户的提示信息。最后,结合之前我们编写过的一个菜单程序的框架,将HDDx的选择和提示信息展示在屏幕上。用户通过上下键和回车键来选择希望操作的硬盘。
具体代码如下,其中包括了一些简单的注视,还有一些用于Debug的位置。

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

#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Protocol/DevicePath.h>
#include <Protocol/DevicePathToText.h>

extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_HANDLE					 gImageHandle;

EFI_GUID gEfiDevicePathToTextProtocolGuid =
		{ 0x8B843E20, 0x8132, 0x4852, 
			{ 0x90, 0xCC, 0x55, 0x1A, 0x4E, 0x4A, 0x7F, 0x1C }};
EFI_GUID	gEfiBlockIoProtocolGuid = 
		{ 0x964E5B21, 0x6459, 0x11D2, 
			{ 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
			
#define MAXDEVICEPATH  255	
typedef struct  {
	CHAR16		Name[6];
	CHAR16  	DevicePathString[MAXDEVICEPATH];
	EFI_HANDLE	Controller;
	EFI_BLOCK_IO_PROTOCOL   *BlockIo;
} tItem;

#define NUM  9
tItem	Items[]=	{
	{L"HDD-0",L"",NULL,NULL},
	{L"HDD-1",L"",NULL,NULL},
	{L"HDD-2",L"",NULL,NULL},
	{L"HDD-3",L"",NULL,NULL},
	{L"HDD-4",L"",NULL,NULL},
	{L"HDD-5",L"",NULL,NULL},
	{L"HDD-6",L"",NULL,NULL},
	{L"HDD-7",L"",NULL,NULL},
	{L"HDD-8",L"",NULL,NULL}
};	

void DrawMenu(UINTN x1,UINTN y1,UINTN Count)
{
	UINTN	i;
	EFI_INPUT_KEY	Key;
	EFI_STATUS		Status;
	int current=0;
	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1  ,L"%S",L"Choose the DISK you want to ERASE");	
	ShellPrintEx((UINT32)x1+40,(UINT32)y1+1,L"%S",L"Arrow Up/Down, Enter Select, ESC Quit");
	ShellPrintEx((UINT32)x1,(UINT32)y1,L"%H%S",Items[0].Name);
	for (i=1;i<Count;i++) {
		ShellPrintEx((UINT32)x1,(UINT32)(y1+i),L"%N%S",Items[i].Name);
	}
	ShellPrintEx(
		0,(UINT32)(y1+Count),
		L"%s \n",
		Items[current].DevicePathString
	);	
	
	Key.ScanCode=SCAN_NULL;
	while (SCAN_ESC!=Key.ScanCode)
    {
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%N%S",Items[current].Name);
			if (SCAN_UP == Key.ScanCode) {current = (UINT32)(current-1+Count)%Count;}
			if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%Count;}
			ShellPrintEx((UINT32)x1,(UINT32)(y1+current),L"%H%S",Items[current].Name);			
			/*
			ShellPrintEx(
					x1,y1+Count,
					L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r",
					current,
					Key.ScanCode,
					Key.UnicodeChar);		
			*/
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L" ");							
			for (i=0;i<MAXDEVICEPATH-1;i++)	{
				Print(L" ");				
			}
			ShellPrintEx(
					(UINT32)0,(UINT32)(y1+Count),
					L"%s \n",
					Items[current].DevicePathString
					);
					
		}
		if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) {
			//ShellPrintEx(x1,y1+Count,L"You have chosen: %N%S",Items[current].Name);
			break;
		}
	};	

}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS				Status;
    UINTN					HandleCount,HandleIndex,ItemIndex=0;
    EFI_HANDLE              *BlockControllerHandles = NULL;	
	EFI_BLOCK_IO_PROTOCOL   			*BlockIo;
	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL	*Device2TextProtocol = 0;
	
	//Get the Protcol for DevicePath to String
    Status = gBS->LocateProtocol(
            &gEfiDevicePathToTextProtocolGuid,
            NULL,
            (VOID**)&Device2TextProtocol
            );
	
	//Enumate all the handle which has Block Io Protocol
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiBlockIoProtocolGuid,
            NULL,
            &HandleCount,
            &BlockControllerHandles);  

   if (!EFI_ERROR(Status)) {
        for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) {
			//Get all the Block Io Protocol
            Status = gBS->HandleProtocol(
                    BlockControllerHandles[HandleIndex],
                    &gEfiBlockIoProtocolGuid,
                    (VOID**)&BlockIo);
			//Check if it's a logical disk, 
			//we only operate on a physical disk		
			if (BlockIo->Media->LogicalPartition == 0) {
				EFI_DEVICE_PATH_PROTOCOL*	DiskDevicePath=NULL;   
				//Get the Block Io Protocol
				Status = gBS->HandleProtocol(
					BlockControllerHandles[HandleIndex],
					&gEfiDevicePathProtocolGuid, 
					(VOID*)&DiskDevicePath
					);
				if (EFI_ERROR(Status)) {
					Print(L"Get device path error [%r]\n",Status);
					continue;
				} 
				{
					CHAR16*	TextDevicePath = 0;
					//Convert Disk path to a human readable string
					TextDevicePath = 
						Device2TextProtocol->ConvertDevicePathToText(DiskDevicePath, FALSE, TRUE); 
					//Print(L"%s\n", TextDevicePath);
					
					//Save all the data to a struct
					Items[ItemIndex].Controller=BlockControllerHandles[HandleIndex];
					Items[ItemIndex].BlockIo=BlockIo;
					StrCpy(Items[ItemIndex].DevicePathString,TextDevicePath);
					ItemIndex++;
					
					if(TextDevicePath)gBS->FreePool(TextDevicePath);				
				}
			}//if (BlockIo->Media->LogicalPartition == 0)
        }	//for (HandleIndex = 0;
		
		//You can output all the data you have gotten here
		//UINT32	i;
		//for (i=0;i<ItemIndex;i++)	{
		//	Print(L"%d %s %s\n",i,Items[i].Name,Items[i].DevicePathString);
		//}
		//Print(L"[%d]",ItemIndex);
		
		gST->ConOut->ClearScreen(gST->ConOut);	
		//Set our menu from (0,0) to (21,6), and there should be ItemIndex items
		DrawMenu(0,0,ItemIndex);
		
        gBS->FreePool(BlockControllerHandles);
	
    }			
	
  return EFI_SUCCESS;
}

 

程序在模拟其中运行结果如下,同样的我也在 Intel ApolloLake HDK 平台上试验过,工作也是正常的。

hddmenu

最后,如果真要很完美的话,应该再加入更多的提示信息,比如,对于U盘,要显示名称序列号之类的,这样才便于用户区分当前的操作对象。有兴趣的朋友,可以参考前面几篇文章,加入这个功能。
完整的代码和X64下Application的下载:

hddmenu

Lenorade的按键值

对于 Leonardo 来说,最大的特点就是可以直接模拟USB键盘鼠标。
一般来说,当我们需要发送某一个按键的时候可以直接使用Keyboard.print(‘a’)这样的方式,或者在Keyboard.h 中找到定义好的键值,比如:#define KEY_F1 0xC2。
但是最近我遇到一个问题:这个文件中定义的键值我无法在其他资料上找到,让人非常恼火,例如:没有一份资料上说 CAPS LOCK值为0xC1,但是使用Keyboard.h中定义的 #define KEY_CAPS_LOCK 0xC1 确实是好用的。最后才想起来应该去看一下对应的代码(开源的优点)。最终,找到的代码如下:

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

 

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

keydef

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

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

 

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

#include "Keyboard.h"

#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB

void setup() {
  pinMode(A0, INPUT_PULLUP);
  Keyboard.begin();
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
  
}

void loop() {
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
   
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);

  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000);  
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  
}

 

Step to UEFI (110)在 Shell 下面使用 Unicode 的制表符

在DOS时代,可以用Ascii制表符做出好看的界面。

tabm

同样的 Unicode 中也有制表符【参考1】

tabn

下面的代码就是枚举输出UEFI 下面定义的全部制表符。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.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
  )
{
	CHAR16 ChrSide[2] = {0,0};
	
ChrSide[0]=BOXDRAW_HORIZONTAL;
Print(L"BOXDRAW_HORIZONTAL [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_VERTICAL;
Print(L"BOXDRAW_VERTICAL [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_DOWN_RIGHT;
Print(L"BOXDRAW_DOWN_RIGHT [%s]\n",ChrSide);                 
ChrSide[0]=BOXDRAW_DOWN_LEFT;
Print(L"BOXDRAW_DOWN_LEFT [%s]\n",ChrSide);                  
ChrSide[0]=BOXDRAW_UP_RIGHT;
Print(L"BOXDRAW_UP_RIGHT [%s]\n",ChrSide);                   
ChrSide[0]=BOXDRAW_UP_LEFT;
Print(L"BOXDRAW_UP_LEFT [%s]\n",ChrSide);                    
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT;
Print(L"BOXDRAW_VERTICAL_RIGHT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_LEFT;
Print(L"BOXDRAW_VERTICAL_LEFT [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOWN_HORIZONTAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_HORIZONTAL;
Print(L"BOXDRAW_UP_HORIZONTAL [%s]\n",ChrSide);              
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL [%s]\n",ChrSide);        
ChrSide[0]=BOXDRAW_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_HORIZONTAL [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOWN_RIGHT_DOUBLE;
Print(L"BOXDRAW_DOWN_RIGHT_DOUBLE [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_RIGHT;
Print(L"BOXDRAW_DOWN_DOUBLE_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_RIGHT;
Print(L"BOXDRAW_DOUBLE_DOWN_RIGHT [%s]\n",ChrSide);          
ChrSide[0]=BOXDRAW_DOWN_LEFT_DOUBLE;
Print(L"BOXDRAW_DOWN_LEFT_DOUBLE [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_LEFT;
Print(L"BOXDRAW_DOWN_DOUBLE_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_LEFT;
Print(L"BOXDRAW_DOUBLE_DOWN_LEFT [%s]\n",ChrSide);           
ChrSide[0]=BOXDRAW_UP_RIGHT_DOUBLE;
Print(L"BOXDRAW_UP_RIGHT_DOUBLE [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_DOUBLE_RIGHT;
Print(L"BOXDRAW_UP_DOUBLE_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_DOUBLE_UP_RIGHT;
Print(L"BOXDRAW_DOUBLE_UP_RIGHT [%s]\n",ChrSide);            
ChrSide[0]=BOXDRAW_UP_LEFT_DOUBLE;
Print(L"BOXDRAW_UP_LEFT_DOUBLE [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_UP_DOUBLE_LEFT;
Print(L"BOXDRAW_UP_DOUBLE_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_DOUBLE_UP_LEFT;
Print(L"BOXDRAW_DOUBLE_UP_LEFT [%s]\n",ChrSide);             
ChrSide[0]=BOXDRAW_VERTICAL_RIGHT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_RIGHT_DOUBLE [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_RIGHT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_RIGHT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_RIGHT [%s]\n",ChrSide);      
ChrSide[0]=BOXDRAW_VERTICAL_LEFT_DOUBLE;
Print(L"BOXDRAW_VERTICAL_LEFT_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_LEFT;
Print(L"BOXDRAW_VERTICAL_DOUBLE_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_LEFT;
Print(L"BOXDRAW_DOUBLE_VERTICAL_LEFT [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOWN_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_DOWN_HORIZONTAL_DOUBLE [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOWN_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_DOWN_DOUBLE_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_DOUBLE_DOWN_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_DOWN_HORIZONTAL [%s]\n",ChrSide);     
ChrSide[0]=BOXDRAW_UP_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_UP_HORIZONTAL_DOUBLE [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_UP_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_UP_DOUBLE_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_DOUBLE_UP_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_UP_HORIZONTAL [%s]\n",ChrSide);       
ChrSide[0]=BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE;
Print(L"BOXDRAW_VERTICAL_HORIZONTAL_DOUBLE [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL;
Print(L"BOXDRAW_VERTICAL_DOUBLE_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL;
Print(L"BOXDRAW_DOUBLE_VERTICAL_HORIZONTAL [%s]\n",ChrSide); 
ChrSide[0]=BLOCKELEMENT_FULL_BLOCK;
Print(L"BLOCKELEMENT_FULL_BLOCK [%s]\n",ChrSide);            
ChrSide[0]=BLOCKELEMENT_LIGHT_SHADE;
Print(L"BLOCKELEMENT_LIGHT_SHADE [%s]\n",ChrSide);           
ChrSide[0]=GEOMETRICSHAPE_UP_TRIANGLE;
Print(L"GEOMETRICSHAPE_UP_TRIANGLE [%s]\n",ChrSide);         
ChrSide[0]=GEOMETRICSHAPE_RIGHT_TRIANGLE;
Print(L"GEOMETRICSHAPE_RIGHT_TRIANGLE [%s]\n",ChrSide);      
ChrSide[0]=GEOMETRICSHAPE_DOWN_TRIANGLE;
Print(L"GEOMETRICSHAPE_DOWN_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=GEOMETRICSHAPE_LEFT_TRIANGLE;
Print(L"GEOMETRICSHAPE_LEFT_TRIANGLE [%s]\n",ChrSide);       
ChrSide[0]=ARROW_LEFT;
Print(L"ARROW_LEFT [%s]\n",ChrSide);                         
ChrSide[0]=ARROW_UP;
Print(L"ARROW_UP [%s]\n",ChrSide);                           
ChrSide[0]=ARROW_RIGHT;
Print(L"ARROW_RIGHT [%s]\n",ChrSide);                        
ChrSide[0]=ARROW_DOWN;
Print(L"ARROW_DOWN [%s]\n",ChrSide);                         

  return EFI_SUCCESS;
}

 

上述代码运行结果:

tab4

tab3

tab2

tab1

完整的代码下载:

tabtest

使用上面的代码,绘制的一个方框:

tab5

参考:
1. https://en.wikipedia.org/wiki/Box-drawing_character

给Arduino 设计一个序列号

之前看到很多朋友询问过如何实现“序列号”的功能。就是对于每一个 Arduino 都有独立的编码,这样可以实现一些身份识别之类的功能。
最容易实现的就是每次都修改程序中的定义。但是这样用起来耗时很长,并且必须有源程序才行,琢磨了一下,提出一种软件的实现方法,欢迎大家一起讨论。
本文是纯软件的序列号实现,因此通用性会很好,这里使用 Arduino Uno 来实现。
第一个问题是:如何在命令行下进行刷写。首先将IDE中的上传调试功能打开

image001

这样你可以看到IDE具体使用了什么上传命令(Arduino IDE 只是一个外壳,具体的编译和上传是由其他程序实现的。IDE只是调用和重定向了运行的结果)

image002

具体的话,上面图片中的烧写代码如下:

C:\Users\labz\Documents\arduino\hardware\tools\avr/bin/avrdude -CC:\Users\ labz \Documents\arduino\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -PCOM18 -b115200 -D -Uflash:w:C:\Users\ labz \AppData\Local\Temp\build3ee88a1b99cef46fdbee27900bbb7d73.tmp/Blink.ino.hex:i

知道了这一行命令,我们可以直接在CMD窗口中输入这个命令从而完成上传的动作。
第二步,直接修改生成的 HEX文件。这里制作一个带有序列号的DEMO。我们定义程序的序列号为 20170101,从串口输出这个编号以便检查,代码如下:

String sign="LABZ";
String number="20170101";

// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
  // initialize digital pin 13 as an output.
  pinMode(13, OUTPUT);
  Serial.print(number);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(13, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(2000);              // wait for a second
  digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

 

运行结果如下
image003

我们的目标就是直接用工具修改这个序列号,然后上传到 Arduino 上。同样是根据前面的命令行,我们可以知道上传的HEX文件位置:
image004

用文本编辑软件即可打开这个文件,搜索 4C 41 42 5A (ASCII:LABZ, 这就是为什么我们要在代码中定义String Sign,它能帮助我们定位后面的序列号的位置。其实不用这个作为定位手段直接搜索代码中定义的序列号也是可以的)。

仔细查看,后面的数据 32 30 31 37 30 36 就是我们的序列号 “2017”。之后又遇到的问题是,这个HEX文件是有简单的格式的。根据【参考1】,我们可以得知每行最后一个数值是这一行的校验和。例如

:100CE400FB000F019B014C41425A00323031373036
image005

其中的最后一个 36 是校验和计算方法如下:

10+0C+E4+00+FB+00+0F+01+9B+01+4C+41+42+5A+00+32+30+31+37+30=0x4CA 校验和的意思是加上一个值之后最后2位会变成00,0xCA+0x36=0x100

假设,我们的目标是将序列号修改为 19731023 = 30 39 37 33 31 30 32 33

经过分析,我们修改如下
:100CE400FB000F019B014C41425A0031393733312B
:040CF4003032330067
其中最后的校验和是通过前面的数值计算得到的。当然,更简单的办法是根据第一步,直接烧写,烧录程序会提醒你的校验和不匹配。例如,我使用错误的检验和,0x36 在烧写的时候收到提示,当前计算结果为 0x2B:

image006

修改完成之后,再次查看,序列号就是我新定义的了。
image007

可以看出,使用手工修改还是比较麻烦的,但是掌握了方法,编写自动化的工具也就是分分钟的事情了。
参考:
1. http://www.manysj.com/thread-13501-1-1.html HEX文件格式详解

HSTI 的一个问题

这一切的起因是 MS WHQL测试的要求,为了确保 Connected Standby 功能,需要验证 secure 方面的设定(我目前还很难想象二者之间的关系,也许是在一睡一醒之间会放松警惕,所以MS要求我们特别注意?)

这个测试被称作System.Fundamentals.Firmware.CS.UEFISecureBoot.Provisioning 【参考1】。

为了完成这个测试 MS (我相信这种事情肯定不止MS一个人干的)引入新功能: HSTI Hardware Security Test Interface。

关于这个功能,在【参考2】有如下解释:
“HSTI helps avoid misconfiguration of security features on devices running Windows. Thus, HSTI provides better assurance of compliance with Windows Hardware Security Requirements. HSTI aims to simplify the interface for designing tests to ensure compliance, reducing the effort required to comply with Windows Hardware Security Requirements. The results of HSTI tests will be consumed by Windows Certification Tests and can be used to verify that devices have been properly configured to enable supported security features. These tests may be used to identify unsecure engineering devices in the field; for example, engineering devices which may contain unsecure test keys. The results of these tests may be used by the Windows operating system to display a watermark (or similar indicator) on unsecured devices. The IHV will develop reference security designs for their platforms that comply with the Windows Compatibility Requirements. In addition, IHVs and IBVs will also implement programmatic tests that verify proper enablement of the reference security implementations and report the results via the Hardware Security Test Interface. These tests are delivered to OEMs & ODMs as compiled modules (not source) and should work without modification. If an OEM/ODM deviates from reference security designs, these test modules may report failures, and the OEM/ODM will need to contact Microsoft to review the modifications and implement an additional HSTI instance that reports these exceptions. OEMs should be able to leverage these security modules with no modification required by following the reference design and documentation. OEMs who wish to add additional security modules, or modify the behavior of any security module, must undergo a design review with Microsoft. Silicon suppliers and IBVs who support Connected Standby systems must implement the platform independent interfaces for querying the respective hardware and firmware security states of their reference platforms. These implementations must be delivered as compiled modules. It is recommended that these modules be signed, and that a signature check is performed when they are run. The purpose is to query the hardware and firmware designs and states to report proper security provisioning of the platform. If an OEM wants to provide an alternate implementation of HSTI tested security features the OEM may provide additional tests. OEM security checks must at least fully cover one IHV or IBV security test. Before use, OEMs must submit to a design review by Microsoft and are subject to the same Documentation and Tool disclosure requirements as other HSTI test providers. Upon approval from Microsoft, the OEM may include security tests that extend upon the IHV and IBV tests. Note that OEM attestation is not required as part of the HSTI design. HSTI is not a list of requirements for OEMs; it is an interface to guarantee effective programmatic security testing of firmware, hardware, and configuration parameters. Silicon and firmware suppliers should make available to Microsoft all necessary security-related reference documentation and tools that they provide to OEMs. This documentation and tools should be made available no later than they are provided to Windows OEMs. This should include, but is not limited to, all documentation and tools related to fusing, installing and updating firmware, firmware and boot recovery, hardware diagnostics, firmware diagnostics, & boot diagnostics. This documentation and tools provided should be fully sufficient to perform HSTI checks in a lab environment.”

相信各位和我一样,看完了之后依然一头雾水。

根据我的理解,具体动作如下:BIOS 自己检查安全项目,将结果存放在内存中 HSTI Table,这也是双方协商好确定的 Windows 接口,通过它,Windows可以得知当前系统上的安全设定,比如Boot Guard 是否打开,SPI 是否已经设置保护等等。对于WHQL 来说只是读取这个Table而已。

实例:我拿到的测试报告有下面的Fail项目:

Error 0x00060002 Platform Security Specification – CPU Security Configuration – Pre-production silicon in use

查看代码,是可以在你的 codebase 中找到这个字符串的定义的,比如:

#define LABZ_ERROR_CODE_2 L”0x00060002″
#define LABZ_ERROR_STRING_2 L” Pre-production silicon in use\r\n”

然后再检查引用这个字符串的代码:

DEBUG ((DEBUG_INFO, ” 2. Sample Part \n”));

if ((ProcessorReadMsr64 (CpuX, MSR_LABZ_INFO) & BIT21) != 0) {
DEBUG ((DEBUG_INFO, “Fail: This is a sample part\n”));
HstiErrorString = BuildHstiErrorString (LABZ_ERROR_CODE_2 ,HSTI_CPU_SECURITY_CONFIGURATION, LABZ_ERROR_STRING_2 );
Result = FALSE;
FreePool (HstiErrorString);
}

就是说,BIOS 根据ProcessorReadMsr64 (CpuX, MSR_LABZ_INFO) & BIT21!=0这个条件判断当前系统是否为 Sample。如果是的话,就将这个ERROR 记录下来存放在HSTI 中。

原理就是这样,如果单纯的为了通过测试,可以去修改BIOS,修改自检的动作。比如,修改上面的条件,让BIOS不去记录这个错误从而通过WHQL。特别注意,一个错误可能是很多不同条件检查出来的结果。比如,检查 EC access, CSME access 的时候都会报告下面这个错误

Error 0x0001000A Platform Security Specification – SPI Flash Configuration – SPI Region Access Rights Invalid

下面再介绍如何检查修改是否生效:

1. 最最稳妥的就是拿去重做WHQL测试;
2. 如果细心的话,会注意到上面的代码是有Debug信息输出的,可以对照UART输出的DEBUG信息检查,如果修改后,如果没有这个信息,那么就是正确的了。推荐使用这个方法,查看Log对于定位错误的位置非常有帮助;
3. 需要检查你的codebase是否为最新版,很多检查寄存器的值并非不正确,只是你手中的版本没有包含这个值而已;
4. 因为HSTI是UEFI 定义的结构,还可以在UEFI 下面进行读取。这里推荐jyao1 编写的一个工具【参考3】,能够在Shell下展示这个结构体的信息。我重新在UDK2015上编译了一次,非常好用,这里送上代码和X64的EFI。
5. 如果有问题,直接问Intel。 Insyde 对此不清楚。

上面提到的完整代码和X64的EFI 程序下载 hstiwsmtdump

就是这样,祝好运。

参考:
1. https://msdn.microsoft.com/en-us/library/windows/hardware/mt712332.aspx
2. https://firmwaresecurity.com/2015/08/03/microsoft-windows-hsti-hardware-security-test-interface/ 这里提到的出处是 MSDN,但是原始界面已经不复存在了
3. https://github.com/jyao1/EdkiiShellTool/tree/master/EdkiiShellToolPkg

特别声明:本文是一篇小说,不可作为技术指导以及参考,非专业人士请不要用来检测使用的机器。

Arduino I2C GPIO模块

Arduino Uno 大概是用途最广泛的型号了,美中不足的是它的GPIO 有限,这里介绍一个GPIO的扩展模块。基于 PCA9555芯片的 GPIO模块。
image002
简单的说,这个模块是将I2C转换为 GPIO,一共支持16个GPIO。板子上通过跳线的方式可以选择当前的地址0x20-0x27一共8个,理论上可以级联出来8(个模块)*16(个GPIO)=128个GPIO。

image004
特地我去查了一下电气特性【参考1】:
电源支持2.3到5.5v(正常工作范围),IO 输入5V,支持每一个输出最大50mA,输入时每个最大20mA。电源供电最大输入 160mA(这意味着不能同时有3以上的 GPIO输出50mA,在使用的时候一定要特别注意,最好把它当作只能提供电平的控制模块)。
简单实验这个模块,用4个LED,负极通过一个1K电阻接地,虽然这样会让LED看起来很暗,但是我能够确定不会有过流的问题。然后用扩展版上的 P0.6,P0.7 ,P1.6和P1.7连接LED正极进行供电控制。

运行的代码来自 DFRobot【参考2】(看起来他们在很久之前做过一块类似的板子,但是目前已经停产了)。当I2C地址选择三个Pin都为0时,模块地址是0x20。另外,I2C访问部分,我修改为Wire.write 之前的Wire.send 函数已经取消了。

/****************************************************************************** 
Test Program for the 12C PCA9555 Board part number DFR0013 IIC TO GPIO module from dfrobot.com
16 outputs that I used to drive this relay board made in Bulgaria
http://www.denkovi.com/product/21/16-relay-board-for-your-pic-avr-project-12v.html
it's a great little expansion board that can be used to drive LEDs or anything you want.
made by peter@testelectronics.com
January 07th 2011
My biggest problem was figuring out the I2C address of the PCA9555.
If there are no jumpers the address is 1 0 0 '1 1 1'
Jumpers make the address 1 0 0 '0 0 0'. This is opposite of what I expected.
******************************************************************************/ 

#include <Wire.h> 

//  with no jumpers the full address is   1 0 0 1 1 1    1 0 0 A2 A1 A0  0x27 is the default address for the DFR0013 board with no jumpers.
#define PCA9555 0x20 // 0x27 is default address for the DFR0013 board with no jumpers.
                     // 0x20 is address for the DFR0013 board with all jumpers.
// COMMAND BYTE TO REGISTER RELATIONSHIP FROM PCA9555 DATA SHEET
// At reset, the device's ports are inputs with a high value resistor pull-ups to VDD
// If relays turning on during power up are a problem. Add a pull down resistor to each relay transistor base.

#define IN_P0 0x00 // Read Input port0
#define IN_P1 0x01 // Read Input port1
#define OUT_P0 0x02 // Write Output port0
#define OUT_P1 0x03 // Write Output port1
#define INV_P0 0x04 // Input Port Polarity Inversion port0 if B11111111 is written input polarity is inverted
#define INV_P1 0x05 // Input Port Polarity Inversion port1 if B11111111 is written input polarity is inverted
#define CONFIG_P0 0x06 // Configuration port0 configures the direction of the I/O pins 0 is output 1 is input
#define CONFIG_P1 0x07 // Configuration port1 configures the direction of the I/O pins 0 is output 1 is input

#define PAUSE 200

void setup()
{
  Wire.begin(PCA9555); // join i2c bus (address optional for master) tried to get working
  write_io (CONFIG_P0, B00000000); //defines all pins on Port0 are outputs
  write_io (CONFIG_P1, B00000000); //defines all pins on Port1 are outputs  
  write_io (OUT_P0, B00000000); //clears all relays
  write_io (OUT_P1, B00000000); //clears all relays
  delay (PAUSE);
}


void loop()
{
  write_io (OUT_P0, B00000001);
  delay (PAUSE);
  write_io (OUT_P0, B00000010);
  delay (PAUSE);
  write_io (OUT_P0, B00000100);
  delay (PAUSE);
  write_io (OUT_P0, B00001000);
  delay (PAUSE);
  write_io (OUT_P0, B00010000);
  delay (PAUSE);
  write_io (OUT_P0, B00100000);
  delay (PAUSE);
  write_io (OUT_P0, B01000000);
  delay (PAUSE);
  write_io (OUT_P0, B10000000);
  delay (PAUSE);
  write_io (OUT_P0, B00000000);
 
  
  write_io (OUT_P1, B00000001);
  delay (PAUSE);
  write_io (OUT_P1, B00000010);
  delay (PAUSE);
  write_io (OUT_P1, B00000100);
  delay (PAUSE);
  write_io (OUT_P1, B00001000);
  delay (PAUSE);
  write_io (OUT_P1, B00010000);
  delay (PAUSE);
  write_io (OUT_P1, B00100000);
  delay (PAUSE);
  write_io (OUT_P1, B01000000);
  delay (PAUSE);
  write_io (OUT_P1, B10000000);
  delay (PAUSE);
  write_io (OUT_P1, B00000000);
  
}
 
 
 void write_io(int command, int value)
{
  Wire.beginTransmission(PCA9555);
  Wire.write(command),Wire.write(value);
  Wire.endTransmission();
}

 

最终工作的样子:
image006

最后补充一些:
1. 这个模块在淘宝价格20左右,DFRobot新的替代品的价格在65左右;
2. 模块做工不错,看起来挺舒服,应该是机器焊接的(老婆偶然看到,竟然觉得挺精致)
3. Github 上有人提供了一个库,有兴趣的朋友可以试试。如果能够简化编程,都值得实验 https://github.com/nicoverduin/PCA9555

参考:
1. http://www.nxp.com/documents/data_sheet/PCA9555.pdf
2. http://wiki.dfrobot.com.cn/index.php/(SKU:DFR0013)IIC_TO_GPIO%E6%A8%A1%E5%9D%97

Step to UEFI (108)引用Protocol GUID的方法

每一个 Protocol 都有一个GUID,这是Protocol 正式的名称。编写一个Application,标准的做法是:

1. 在Application 的inf 文件, [Packages] 节中引用 MdePkg.dec , 实际上你用到的UEFI 标准的 Protocol GUID 在这个文件中都有定义;[Protocols] 节中定义你用到的 ProtocolGuid;

2.在Application的 c 文件中 extern 一下代码中用到的 GUID;

当然,这样的做法看起来不如直接在代码中使用 EFI_GUID 定义直接易懂,之前我写的很多代码为了省事采用的就是在 Application中重新定义的方法。

下面列出常用的 Protocol 的定义:

EFI_GUID gEfiLoadedImageProtocolGuid = {0x5B1B31A1, 0x9562,0x11D2,
        { 0x8E, 0x3F,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiLoadedImageDevicePathProtocolGuid = {0xBC62157E, 0x3E33,0x4FEC,
        { 0x99, 0x20,0x2D, 0x3B, 0x36, 0xD7,0x50, 0xDF } };
EFI_GUID gEfiSimpleTextOutProtocolGuid = {0x387477C2, 0x69C7,0x11D2,
        { 0x8E, 0x39,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiDevicePathProtocolGuid = {0x9576E91, 0x6D3F,0x11D2,
        { 0x8E, 0x39,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiSimplePointerProtocolGuid = {0x31878C87, 0xB75,0x11D5,
        { 0x9A, 0x4F,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiAbsolutePointerProtocolGuid = {0x8D59D32B, 0xC655,0x4AE9,
        { 0x9B, 0x15,0xF2, 0x59, 0x4, 0x99,0x2A, 0x43 } };
EFI_GUID gEfiSerialIoProtocolGuid = {0xBB25CF6F, 0xF1D4,0x11D2,
        { 0x9A, 0xC,0x0, 0x90, 0x27, 0x3F,0xC1, 0xFD } };
EFI_GUID gEfiEdidDiscoveredProtocolGuid = {0x1C0C34F6, 0xD380,0x41FA,
        { 0xA0, 0x49,0x8A, 0xD0, 0x6C, 0x1A,0x66, 0xAA } };
EFI_GUID gEfiEdidActiveProtocolGuid = {0xBD8C1056, 0x9F36,0x44EC,
        { 0x92, 0xA8,0xA6, 0x33, 0x7F, 0x81,0x79, 0x86 } };
EFI_GUID gEfiEdidOverrideProtocolGuid = {0x48ECB431, 0xFB72,0x45C0,
        { 0xA9, 0x22,0xF4, 0x58, 0xFE, 0x4,0xB, 0xD5 } };
EFI_GUID gEfiLoadFileProtocolGuid = {0x56EC3091, 0x954C,0x11D2,
        { 0x8E, 0x3F,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiLoadFile2ProtocolGuid = {0x4006C0C1, 0xFCB3,0x403E,
        { 0x99, 0x6D,0x4A, 0x6C, 0x87, 0x24,0xE0, 0x6D } };
EFI_GUID gEfiTapeIoProtocolGuid = {0x1E93E633, 0xD65A,0x459E,
        { 0xAB, 0x84,0x93, 0xD9, 0xEC, 0x26,0x6D, 0x18 } };
EFI_GUID gEfiDiskIoProtocolGuid = {0xCE345171, 0xBA0B,0x11D2,
        { 0x8E, 0x4F,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiBlockIoProtocolGuid = {0x964E5B21, 0x6459,0x11D2,
        { 0x8E, 0x39,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiUnicodeCollationProtocolGuid = {0x1D85CD7F, 0xF43D,0x11D2,
        { 0x9A, 0xC,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiPciRootBridgeIoProtocolGuid = {0x2F707EBB, 0x4A1A,0x11D4,
        { 0x9A, 0x38,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiPciIoProtocolGuid = {0x4CF5B200, 0x68B8,0x4CA5,
        { 0x9E, 0xEC,0xB2, 0x3E, 0x3F, 0x50,0x2, 0x9A } };
EFI_GUID gEfiScsiPassThruProtocolGuid = {0xA59E8FCF, 0xBDA0,0x43BB,
        { 0x90, 0xB1,0xD3, 0x73, 0x2E, 0xCA,0xA8, 0x77 } };
EFI_GUID gEfiScsiIoProtocolGuid = {0x932F47E6, 0x2362,0x4002,
        { 0x80, 0x3E,0x3C, 0xD5, 0x4B, 0x13,0x8F, 0x85 } };
EFI_GUID gEfiExtScsiPassThruProtocolGuid = {0x143B7632, 0xB81B,0x4CB7,
        { 0xAB, 0xD3,0xB6, 0x25, 0xA5, 0xB9,0xBF, 0xFE } };
EFI_GUID gEfiIScsiInitiatorNameProtocolGuid = {0x59324945, 0xEC44,0x4C0D,
        { 0xB1, 0xCD,0x9D, 0xB1, 0x39, 0xDF,0x7, 0xC } };
EFI_GUID gEfiUsbIoProtocolGuid = {0x2B2F68D6, 0xCD2,0x44CF,
        { 0x8E, 0x8B,0xBB, 0xA2, 0xB, 0x1B,0x5B, 0x75 } };
EFI_GUID gEfiUsbHcProtocolGuid = {0xF5089266, 0x1AA0,0x4953,
        { 0x97, 0xD8,0x56, 0x2F, 0x8A, 0x73,0xB5, 0x19 } };
EFI_GUID gEfiUsb2HcProtocolGuid = {0x3E745226, 0x9818,0x45B6,
        { 0xA2, 0xAC,0xD7, 0xCD, 0xE, 0x8B,0xA2, 0xBC } };
EFI_GUID gEfiDebugSupportProtocolGuid = {0x2755590C, 0x6F3C,0x42FA,
        { 0x9E, 0xA4,0xA3, 0xBA, 0x54, 0x3C,0xDA, 0x25 } };
EFI_GUID gEfiDebugPortProtocolGuid = {0xEBA4E8D2, 0x3858,0x41EC,
        { 0xA2, 0x81,0x26, 0x47, 0xBA, 0x96,0x60, 0xD0 } };
EFI_GUID gEfiDecompressProtocolGuid = {0xD8117CFE, 0x94A6,0x11D4,
        { 0x9A, 0x3A,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiAcpiTableProtocolGuid = {0xFFE06BDD, 0x6107,0x46A6,
        { 0x7B, 0xB2,0x5A, 0x9C, 0x7E, 0xC5,0x27, 0x5C } };
EFI_GUID gEfiEbcProtocolGuid = {0x13AC6DD1, 0x73D0,0x11D4,
        { 0xB0, 0x6B,0x0, 0xAA, 0x0, 0xBD,0x6D, 0xE7 } };
EFI_GUID gEfiSimpleNetworkProtocolGuid = {0xA19832B9, 0xAC25,0x11D3,
        { 0x9A, 0x2D,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiNetworkInterfaceIdentifierProtocolGuid = {0xE18541CD, 0xF755,0x4F73,
        { 0x92, 0x8D,0x64, 0x3C, 0x8A, 0x79,0xB2, 0x29 } };
EFI_GUID gEfiNetworkInterfaceIdentifierProtocolGuid_31 = {0x1ACED566, 0x76ED,0x4218,
        { 0xBC, 0x81,0x76, 0x7F, 0x1F, 0x97,0x7A, 0x89 } };
EFI_GUID gEfiPxeBaseCodeProtocolGuid = {0x3C4E603, 0xAC28,0x11D3,
        { 0x9A, 0x2D,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiPxeBaseCodeCallbackProtocolGuid = {0x245DCA21, 0xFB7B,0x11D3,
        { 0x8F, 0x1,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiBisProtocolGuid = {0xB64AAB0, 0x5429,0x11D4,
        { 0x98, 0x16,0x0, 0xA0, 0xC9, 0x1F,0xAD, 0xCF } };
EFI_GUID gEfiManagedNetworkServiceBindingProtocolGuid = {0xF36FF770, 0xA7E1,0x42CF,
        { 0x9E, 0xD2,0x56, 0xF0, 0xF2, 0x71,0xF4, 0x4C } };
EFI_GUID gEfiManagedNetworkProtocolGuid = {0x7AB33A91, 0xACE5,0x4326,
        { 0xB5, 0x72,0xE7, 0xEE, 0x33, 0xD3,0x9F, 0x16 } };
EFI_GUID gEfiArpServiceBindingProtocolGuid = {0xF44C00EE, 0x1F2C,0x4A00,
        { 0xAA, 0x9,0x1C, 0x9F, 0x3E, 0x8,0x0, 0xA3 } };
EFI_GUID gEfiArpProtocolGuid = {0xF4B427BB, 0xBA21,0x4F16,
        { 0xBC, 0x4E,0x43, 0xE4, 0x16, 0xAB,0x61, 0x9C } };
EFI_GUID gEfiDhcp4ServiceBindingProtocolGuid = {0x9D9A39D8, 0xBD42,0x4A73,
        { 0xA4, 0xD5,0x8E, 0xE9, 0x4B, 0xE1,0x13, 0x80 } };
EFI_GUID gEfiDhcp4ProtocolGuid = {0x8A219718, 0x4EF5,0x4761,
        { 0x91, 0xC8,0xC0, 0xF0, 0x4B, 0xDA,0x9E, 0x56 } };
EFI_GUID gEfiTcp4ServiceBindingProtocolGuid = {0x720665, 0x67EB,0x4A99,
        { 0xBA, 0xF7,0xD3, 0xC3, 0x3A, 0x1C,0x7C, 0xC9 } };
EFI_GUID gEfiTcp4ProtocolGuid = {0x65530BC7, 0xA359,0x410F,
        { 0xB0, 0x10,0x5A, 0xAD, 0xC7, 0xEC,0x2B, 0x62 } };
EFI_GUID gEfiIp4ServiceBindingProtocolGuid = {0xC51711E7, 0xB4BF,0x404A,
        { 0xBF, 0xB8,0xA, 0x4, 0x8E, 0xF1,0xFF, 0xE4 } };
EFI_GUID gEfiIp4ProtocolGuid = {0x41D94CD2, 0x35B6,0x455A,
        { 0x82, 0x58,0xD4, 0xE5, 0x13, 0x34,0xAA, 0xDD } };
EFI_GUID gEfiIp4ConfigProtocolGuid = {0x3B95AA31, 0x3793,0x434B,
        { 0x86, 0x67,0xC8, 0x7, 0x8, 0x92,0xE0, 0x5E } };
EFI_GUID gEfiIp4Config2ProtocolGuid = {0x5B446ED1, 0xE30B,0x4FAA,
        { 0x87, 0x1A,0x36, 0x54, 0xEC, 0xA3,0x60, 0x80 } };
EFI_GUID gEfiUdp4ServiceBindingProtocolGuid = {0x83F01464, 0x99BD,0x45E5,
        { 0xB3, 0x83,0xAF, 0x63, 0x5, 0xD8,0xE9, 0xE6 } };
EFI_GUID gEfiUdp4ProtocolGuid = {0x3AD9DF29, 0x4501,0x478D,
        { 0xB1, 0xF8,0x7F, 0x7F, 0xE7, 0xE,0x50, 0xF3 } };
EFI_GUID gEfiMtftp4ServiceBindingProtocolGuid = {0x2FE800BE, 0x8F01,0x4AA6,
        { 0x94, 0x6B,0xD7, 0x13, 0x88, 0xE1,0x83, 0x3F } };
EFI_GUID gEfiMtftp4ProtocolGuid = {0x78247C57, 0x63DB,0x4708,
        { 0x99, 0xC2,0xA8, 0xB4, 0xA9, 0xA6,0x1F, 0x6B } };
EFI_GUID gEfiAuthenticationInfoProtocolGuid = {0x7671D9D0, 0x53DB,0x4173,
        { 0xAA, 0x69,0x23, 0x27, 0xF2, 0x1F,0xB, 0xC7 } };
EFI_GUID gEfiHashServiceBindingProtocolGuid = {0x42881C98, 0xA4F3,0x44B0,
        { 0xA3, 0x9D,0xDF, 0xA1, 0x86, 0x67,0xD8, 0xCD } };
EFI_GUID gEfiHashProtocolGuid = {0xC5184932, 0xDBA5,0x46DB,
        { 0xA5, 0xBA,0xCC, 0xB, 0xDA, 0x9C,0x14, 0x35 } };
EFI_GUID gEfiHiiFontProtocolGuid = {0xE9CA4775, 0x8657,0x47FC,
        { 0x97, 0xE7,0x7E, 0xD6, 0x5A, 0x8,0x43, 0x24 } };
EFI_GUID gEfiHiiStringProtocolGuid = {0xFD96974, 0x23AA,0x4CDC,
        { 0xB9, 0xCB,0x98, 0xD1, 0x77, 0x50,0x32, 0x2A } };
EFI_GUID gEfiHiiImageProtocolGuid = {0x31A6406A, 0x6BDF,0x4E46,
        { 0xB2, 0xA2,0xEB, 0xAA, 0x89, 0xC4,0x9, 0x20 } };
EFI_GUID gEfiHiiConfigRoutingProtocolGuid = {0x587E72D7, 0xCC50,0x4F79,
        { 0x82, 0x9,0xCA, 0x29, 0x1F, 0xC1,0xA1, 0xF } };
EFI_GUID gEfiHiiConfigAccessProtocolGuid = {0x330D4706, 0xF2A0,0x4E4F,
        { 0xA3, 0x69,0xB6, 0x6F, 0xA8, 0xD5,0x43, 0x85 } };
EFI_GUID gEfiFormBrowser2ProtocolGuid = {0xB9D4C360, 0xBCFB,0x4F9B,
        { 0x92, 0x98,0x53, 0xC1, 0x36, 0x98,0x22, 0x58 } };
EFI_GUID gEfiDeviceIoProtocolGuid = {0xAF6AC311, 0x84C3,0x11D2,
        { 0x8E, 0x3C,0x0, 0xA0, 0xC9, 0x69,0x72, 0x3B } };
EFI_GUID gEfiUgaDrawProtocolGuid = {0x982C298B, 0xF4FA,0x41CB,
        { 0xB8, 0x38,0x77, 0xAA, 0x68, 0x8F,0xB8, 0x39 } };
EFI_GUID gEfiUgaIoProtocolGuid = {0x61A4D49E, 0x6F68,0x4F1B,
        { 0xB9, 0x22,0xA8, 0x6E, 0xED, 0xB,0x7, 0xA2 } };
EFI_GUID gEfiDriverConfigurationProtocolGuid = {0x107A772B, 0xD5E1,0x11D4,
        { 0x9A, 0x46,0x0, 0x90, 0x27, 0x3F,0xC1, 0x4D } };
EFI_GUID gEfiDriverConfiguration2ProtocolGuid = {0xBFD7DC1D, 0x24F1,0x40D9,
        { 0x82, 0xE7,0x2E, 0x9, 0xBB, 0x6B,0x4E, 0xBE } };
EFI_GUID gEfiSimpleTextInputExProtocolGuid = {0xDD9E7534, 0x7762,0x4698,
        { 0x8C, 0x14,0xF5, 0x85, 0x17, 0xA6,0x25, 0xAA } };
EFI_GUID gEfiIp6ServiceBindingProtocolGuid = {0xEC835DD3, 0xFE0F,0x617B,
        { 0xA6, 0x21,0xB3, 0x50, 0xC3, 0xE1,0x33, 0x88 } };
EFI_GUID gEfiIp6ProtocolGuid = {0x2C8759D5, 0x5C2D,0x66EF,
        { 0x92, 0x5F,0xB6, 0x6C, 0x10, 0x19,0x57, 0xE2 } };
EFI_GUID gEfiIp6ConfigProtocolGuid = {0x937FE521, 0x95AE,0x4D1A,
        { 0x89, 0x29,0x48, 0xBC, 0xD9, 0xA,0xD3, 0x1A } };
EFI_GUID gEfiMtftp6ServiceBindingProtocolGuid = {0xD9760FF3, 0x3CCA,0x4267,
        { 0x80, 0xF9,0x75, 0x27, 0xFA, 0xFA,0x42, 0x23 } };
EFI_GUID gEfiMtftp6ProtocolGuid = {0xBF0A78BA, 0xEC29,0x49CF,
        { 0xA1, 0xC9,0x7A, 0xE5, 0x4E, 0xAB,0x6A, 0x51 } };
EFI_GUID gEfiDhcp6ServiceBindingProtocolGuid = {0x9FB9A8A1, 0x2F4A,0x43A6,
        { 0x88, 0x9C,0xD0, 0xF7, 0xB6, 0xC4,0x7A, 0xD5 } };
EFI_GUID gEfiDhcp6ProtocolGuid = {0x87C8BAD7, 0x595,0x4053,
        { 0x82, 0x97,0xDE, 0xDE, 0x39, 0x5F,0x5D, 0x5B } };
EFI_GUID gEfiUdp6ServiceBindingProtocolGuid = {0x66ED4721, 0x3C98,0x4D3E,
        { 0x81, 0xE3,0xD0, 0x3D, 0xD3, 0x9A,0x72, 0x54 } };
EFI_GUID gEfiUdp6ProtocolGuid = {0x4F948815, 0xB4B9,0x43CB,
        { 0x8A, 0x33,0x90, 0xE0, 0x60, 0xB3,0x49, 0x55 } };
EFI_GUID gEfiTcp6ServiceBindingProtocolGuid = {0xEC20EB79, 0x6C1A,0x4664,
        { 0x9A, 0xD,0xD2, 0xE4, 0xCC, 0x16,0xD6, 0x64 } };
EFI_GUID gEfiTcp6ProtocolGuid = {0x46E44855, 0xBD60,0x4AB7,
        { 0xAB, 0xD,0xA6, 0x79, 0xB9, 0x44,0x7D, 0x77 } };
EFI_GUID gEfiVlanConfigProtocolGuid = {0x9E23D768, 0xD2F3,0x4366,
        { 0x9F, 0xC3,0x3A, 0x7A, 0xBA, 0x86,0x43, 0x74 } };
EFI_GUID gEfiEapProtocolGuid = {0x5D9F96DB, 0xE731,0x4CAA,
        { 0xA0, 0xD,0x72, 0xE1, 0x87, 0xCD,0x77, 0x62 } };
EFI_GUID gEfiEapManagementProtocolGuid = {0xBB62E663, 0x625D,0x40B2,
        { 0xA0, 0x88,0xBB, 0xE8, 0x36, 0x23,0xA2, 0x45 } };
EFI_GUID gEfiFtp4ServiceBindingProtocolGuid = {0xFAAECB1, 0x226E,0x4782,
        { 0xAA, 0xCE,0x7D, 0xB9, 0xBC, 0xBF,0x4D, 0xAF } };
EFI_GUID gEfiFtp4ProtocolGuid = {0xEB338826, 0x681B,0x4295,
        { 0xB3, 0x56,0x2B, 0x36, 0x4C, 0x75,0x7B, 0x9 } };
EFI_GUID gEfiIpSecConfigProtocolGuid = {0xCE5E5929, 0xC7A3,0x4602,
        { 0xAD, 0x9E,0xC9, 0xDA, 0xF9, 0x4E,0xBF, 0xCF } };
EFI_GUID gEfiDriverHealthProtocolGuid = {0x2A534210, 0x9280,0x41D8,
        { 0xAE, 0x79,0xCA, 0xDA, 0x1, 0xA2,0xB1, 0x27 } };
EFI_GUID gEfiDeferredImageLoadProtocolGuid = {0x15853D7C, 0x3DDF,0x43E0,
        { 0xA1, 0xCB,0xEB, 0xF8, 0x5B, 0x8F,0x87, 0x2C } };
EFI_GUID gEfiUserCredentialProtocolGuid = {0x71EE5E94, 0x65B9,0x45D5,
        { 0x82, 0x1A,0x3A, 0x4D, 0x86, 0xCF,0xE6, 0xBE } };
EFI_GUID gEfiUserManagerProtocolGuid = {0x6FD5B00C, 0xD426,0x4283,
        { 0x98, 0x87,0x6C, 0xF5, 0xCF, 0x1C,0xB1, 0xFE } };
EFI_GUID gEfiAtaPassThruProtocolGuid = {0x1D3DE7F0, 0x807,0x424F,
        { 0xAA, 0x69,0x11, 0xA5, 0x4E, 0x19,0xA4, 0x6F } };
EFI_GUID gEfiFirmwareManagementProtocolGuid = {0x86C77A67, 0xB97,0x4633,
        { 0xA1, 0x87,0x49, 0x10, 0x4D, 0x6,0x85, 0xC7 } };
EFI_GUID gEfiIpSecProtocolGuid = {0xDFB386F7, 0xE100,0x43AD,
        { 0x9C, 0x9A,0xED, 0x90, 0xD0, 0x8A,0x5E, 0x12 } };
EFI_GUID gEfiIpSec2ProtocolGuid = {0xA3979E64, 0xACE8,0x4DDC,
        { 0xBC, 0x7,0x4D, 0x66, 0xB8, 0xFD,0x9, 0x77 } };
EFI_GUID gEfiKmsProtocolGuid = {0xEC3A978D, 0x7C4E,0x48FA,
        { 0x9A, 0xBE,0x6A, 0xD9, 0x1C, 0xC8,0xF8, 0x11 } };
EFI_GUID gEfiBlockIo2ProtocolGuid = {0xA77B2472, 0xE282,0x4E9F,
        { 0xA2, 0x45,0xC2, 0xC0, 0xE2, 0x7B,0xBC, 0xC1 } };
EFI_GUID gEfiStorageSecurityCommandProtocolGuid = {0xC88B0B6D, 0xDFC,0x49A7,
        { 0x9C, 0xB4,0x49, 0x7, 0x4B, 0x4C,0x3A, 0x78 } };
EFI_GUID gEfiUserCredential2ProtocolGuid = {0xE98ADB03, 0xB8B9,0x4AF8,
        { 0xBA, 0x20,0x26, 0xE9, 0x11, 0x4C,0xBC, 0xE5 } };
EFI_GUID gPcdProtocolGuid = {0x11B34006, 0xD85B,0x4D0A,
        { 0xA2, 0x90,0xD5, 0xA5, 0x71, 0x31,0xE, 0xF7 } };
EFI_GUID gEfiTcgProtocolGuid = {0xF541796D, 0xA62E,0x4954,
        { 0xA7, 0x75,0x95, 0x84, 0xF6, 0x1B,0x9C, 0xDD } };
EFI_GUID gEfiHiiPackageListProtocolGuid = {0x6A1EE763, 0xD47A,0x43B4,
        { 0xAA, 0xBE,0xEF, 0x1D, 0xE2, 0xAB,0x56, 0xFC } };
EFI_GUID gEfiDriverFamilyOverrideProtocolGuid = {0xB1EE129E, 0xDA36,0x4181,
        { 0x91, 0xF8,0x4, 0xA4, 0x92, 0x37,0x66, 0xA7 } };
EFI_GUID gEfiIdeControllerInitProtocolGuid = {0xA1E37052, 0x80D9,0x4E65,
        { 0xA3, 0x17,0x3E, 0x9A, 0x55, 0xC4,0x3E, 0xC9 } };
EFI_GUID gEfiDiskIo2ProtocolGuid = {0x151C8EAE, 0x7F2C,0x472C,
        { 0x9E, 0x54,0x98, 0x28, 0x19, 0x4F,0x6A, 0x88 } };
EFI_GUID gEfiAdapterInformationProtocolGuid = {0xE5DD1403, 0xD622,0xC24E,
        { 0x84, 0x88,0xC7, 0x1B, 0x17, 0xF5,0xE8, 0x2 } };
EFI_GUID gEfiShellDynamicCommandProtocolGuid = {0x3C7200E9, 0x5F,0x4EA4,
        { 0x87, 0xDE,0xA3, 0xDF, 0xAC, 0x8A,0x27, 0xC3 } };
EFI_GUID gEfiDiskInfoProtocolGuid = {0xD432A67F, 0x14DC,0x484B,
        { 0xB3, 0xBB,0x3F, 0x2, 0x91, 0x84,0x93, 0x27 } };

 

Step to UEFI (107)取得USB DISK 的序列号

继续前面的话题,这次研究如何取得一个USB DISK的信息。最先想到的还是使用 DISK INFO PROTOCOL,不过到了实际编写代码的时候发现:我根本不知道返回值是什么格式。在代码中搜索了几次 gEfiDiskInfoUsbInterfaceGuid 发现无人使用。之后为了弄清楚格式,直接创建了一个256bytes大小的内存,还是用 identify 来获得返回值,最后发现:返回值是空的。因此,也就是说虽然 USB DISK上有安装这个 protocol,但是根本就是一个空的而已。
再调整思路,首先用 DISK INFO PROTOCOL取得USB DISK 的 Handle ,然后,在这个Handle上打开 USBIO,EFI_USB_IO_PROTOCOL.UsbGetStringDescriptor 可以用来取得描述符中相关的字符串【参考1】,具体结构可以在 \EdkCompatibilityPkg\Foundation\Include\IndustryStandard\usb.h 找到:

typedef struct {
  UINT8           Length;
  UINT8           DescriptorType;
  UINT16          BcdUSB;
  UINT8           DeviceClass;
  UINT8           DeviceSubClass;
  UINT8           DeviceProtocol;
  UINT8           MaxPacketSize0;
  UINT16          IdVendor;
  UINT16          IdProduct;
  UINT16          BcdDevice;
  UINT8           StrManufacturer;
  UINT8           StrProduct;
  UINT8           StrSerialNumber;
  UINT8           NumConfigurations;
} EFI_USB_DEVICE_DESCRIPTOR;

 

最后,用这个 函数来取得需要的字符串。

完整的代码:

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

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiDiskInfoProtocolGuid = { 0xD432A67F, 0x14DC, 0x484B, 
					{ 0xB3, 0xBB, 0x3F, 0x02, 0x91, 0x84, 0x93, 0x27 }};
EFI_GUID gEfiDiskInfoUsbInterfaceGuid   = { 0xCB871572, 0xC11A, 0x47B5, 
					{ 0xB4, 0x92, 0x67, 0x5E, 0xAF, 0xA7, 0x77, 0x27 }};

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

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS 	Status;
    UINTN 		HandleIndex, NumHandles;
    EFI_HANDLE 	*ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;	
	EFI_USB_IO_PROTOCOL 			*USBIO;
	EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;
	CHAR16                       *Manufacturer;
	CHAR16                       *Product;
	CHAR16                       *SerialNumber;
	
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiDiskInfoProtocolGuid,
            NULL,
            &NumHandles,
            &ControllerHandle);
	
    for (HandleIndex = 0; HandleIndex < NumHandles; HandleIndex++) {
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiDiskInfoProtocolGuid, 
                (VOID**)&DiskInfoProtocol,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
            continue;
        } 

		//We only deal with USB
		if (!(CompareGuid (
				&DiskInfoProtocol->Interface, 
				&gEfiDiskInfoUsbInterfaceGuid))) {	
				continue;
			}	
			
        Status = gBS->OpenProtocol(
                ControllerHandle[HandleIndex],
                &gEfiUsbIoProtocolGuid, 
                (VOID**)&USBIO,
                gImageHandle,
                NULL,
                EFI_OPEN_PROTOCOL_GET_PROTOCOL
                );		
        if (EFI_ERROR(Status)) {
			Print(L"ERROR : Open USBIO fail.\n");
            continue;
        } 
		
		Status = USBIO->UsbGetDeviceDescriptor
							(USBIO, &DeviceDescriptor);     
		if (EFI_ERROR(Status))
		{
			Print(L"ERROR : Get Device Descriptor fail.\n");
			return 0;
		}		
		
		Print(L"VendorID = %04X\nProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);  

		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrManufacturer,
                    &Manufacturer
                    );
		if (EFI_ERROR (Status)) {
				Manufacturer = L"";
		}

		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrProduct,
                    &Product
                    );
		if (EFI_ERROR (Status)) {
				Product = L"";	}
				
				
		Status = USBIO->UsbGetStringDescriptor (
                    USBIO,
                    0x0409, 			// English
                    DeviceDescriptor.StrSerialNumber,
                    &SerialNumber
                    );
		if (EFI_ERROR (Status)) {
				SerialNumber = L"";}
		
		Print(L"     Manufacturer :  %s\n",Manufacturer);
		Print(L"     Product      :  %s\n",Product);
		Print(L"     Serial Number:  %s\n",SerialNumber);		
						  
							  
	}
  return EFI_SUCCESS;
}

 

运行结果:
ditu1

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

另外,还可以直接枚举 USBIO 然后检查对应的 Class发现是USB MASS Storage 即是U盘,然后再重复上面取得字符串信息的动作。

参考:
1. UEFI Spec 2.4 P835 EFI_USB_IO_PROTOCOL.UsbGetStringDescriptor()

DFRobot SIM808 模块评测 GPS

人类对于世界的了解的越多,能够掌握的工具和方法就越多。

曾经读过方舟子写的《相对论有没有用?》【参考1】,让我大吃一惊的是日常用到的GPS就是相对论使用的典型:

“GPS是靠美国空军发射的24颗GPS卫星来定位的(此外还有几颗备用卫星),每颗卫星上都携带着原子钟,它们计时极为准确,误差不超过十万亿分之一,即每天的误差不超过10纳秒(1纳秒等于10亿分之一秒),并不停地发射无线电信号报告时间和轨道位置。这些GPS卫星在空中的位置是精心安排好的,任何时候在地球上的任何地点至少都能见到其中的4颗。GPS导航仪通过比较从4颗GPS卫星发射来的时间信号的差异,计算出所在的位置。

GPS卫星以每小时14000千米的速度绕地球飞行。根据狭义相对论,当物体运动时,时间会变慢,运动速度越快,时间就越慢。因此在地球上看GPS卫星,它们携带的时钟要走得比较慢,用狭义相对论的公式可以计算出,每天慢大约7微秒。

GPS卫星位于距离地面大约2万千米的太空中。根据广义相对论,物质质量的存在会造成时空的弯曲,质量越大,距离越近,就弯曲得越厉害,时间则会越慢。受地球质量的影响,在地球表面的时空要比GPS卫星所在的时空更加弯曲,这样,从地球上看,GPS卫星上的时钟就要走得比较快,用广义相对论的公式可以计算出,每天快大约45微秒。

在同时考虑了狭义相对论和广义相对论后,GPS卫星时钟每天还要快上大约38微秒,这似乎微不足道,但是如果我们考虑到GPS系统必须达到的时间精度是纳秒级的,这个误差就非常可观了(38微秒等于38000纳秒)。如果不校正的话,GPS系统每天将会累积大约10千米的定位误差,是没有用的。为此,在GPS卫星发射前,要先把其时钟的走动频率调慢100亿分之4.465,把10.23兆赫调为10.22999999543兆赫。此外,GPS卫星的运行轨道并非完美的圆形,与地面的距离和运行速度会有所变化,如果轨道偏心率为0.02,时间就会有46纳秒的误差。由于地球的自转,GPS导航仪在地球表面上的位移也会产生误差,例如当GPS导航仪在赤道上,而GPS卫星在地平线上时,由于位移产生的误差将会达到133纳秒。GPS导航仪在定位时还必须根据相对论进行计算纠正这些误差。

我手中的 DFRobot的SIM808 Shield支持GPS功能,这次我们就实验一下这个功能,GPS取得当前的时间和经纬度之后,结果显示在1602液晶上。使用到的硬件包括:

1. Arduino Uno 1块

2. DFRobot 的SIM808 Shield 1块

3. 18650 电池组 2块

4. 1602 LCD 一块

5. 亚克力切割的外壳 一对

特别注意的是,必须使用外接电源,USB端口供电不足以驱动模块,特别注意:一定要接上 GPS 天线,否则无法收到信号。

硬件准备妥当之后,先实验直接发送 AT 命令,这个实验不需要SIM卡:

1. 板上开关放置于3号位置

2. 下载 blink 程序(需要不占用 的程序)

3. 外接电源(我用的是2节18650,目前有7.5v左右)

4. 板上开关放置于2号位置

5. 用IDE自带的串口工具,输入 AT 可以看到自动回复 OK,这说明模块本身是正常的

6. 输入AT+CGNSPWR=1命令(打开GPS电源)AT+CGNSTST=1命令(开始从串口接收GPS数据)

7. 下面就可以看到获得的GPS数据了
image001

8. 结束的时候,使用AT+CGNSPWR=0命令关断GPS电源。

image002

我们在地图上看一下这个位置,很准

image003
软件方面需要下载DFRobot_SIM808的库,在https://github.com/DFRobot/DFRobot_SIM808。本文末尾提供了打包好的库文件。可以使用 Sketch->Include library->Add .zip library直接添加。

代码如下:

#include <LiquidCrystal_I2C.h>

#include <DFRobot_sim808.h>

 

DFRobot_SIM808 sim808(&Serial);

 

// Set the LCD address to 0x3F for a 16 chars and 2 line display

LiquidCrystal_I2C lcd(0x3f, 16, 2);

 

void setup() {

    Serial.begin(9600);

 

    // initialize the LCD

    lcd.begin();

 

    // Turn on the blacklight and print a message.

    lcd.backlight();

  

    //************* Turn on the GPS power************

    if( sim808.attachGPS())

      {

        Serial.println("Open the GPS power success");

        lcd.print("power success");

      }

    else

      {    

        Serial.println("Open the GPS power failure");

        lcd.print("power failure");        

      }

}

 

void loop() {

     char s[20];    

     //************** Get GPS data *******************

     if (sim808.getGPS()) {

      Serial.print(sim808.GPSdata.hour);

      Serial.print(":");

      Serial.print(sim808.GPSdata.minute);

      Serial.print(":");

      Serial.println(sim808.GPSdata.second);

      Serial.print("latitude :");

      Serial.println(sim808.GPSdata.lat);

      Serial.print("longitude :");

      Serial.println(sim808.GPSdata.lon);

 

      lcd.clear(); 

      lcd.print("GPS     bbdebbbbbbbhhh             b        "); 

      lcd.print(sim808.GPSdata.hour);  

      lcd.print(":"); 

      lcd.print(sim808.GPSdata.minute);  

      lcd.print(":"); 

      lcd.print(sim808.GPSdata.second);

      

 

      lcd.setCursor(0,1);

      lcd.print("(");

      dtostrf(sim808.GPSdata.lat,3,2,s);

      lcd.print(s);

      lcd.print(",");

      dtostrf(sim808.GPSdata.lon,3,2,s);

      lcd.print(s);

      lcd.print(")");

 

      //************* Turn off the GPS power ************

      sim808.detachGPS();

    }

 

  }

 

最终运行结果:

image004

image005

最后说点关于GPS 好玩的事情:

第一件事情:我在昆山打工的时候,有一天瞎溜达,走到一座大桥下,看到上面铭牌上刻着经纬度。那是很久以前的事情,久远到 google还没有被封,手机还没有 GPS 功能。我很好奇的记下了经纬度。回去宿舍在google map上查找这个经纬度,非常疑惑的看着坐标飞到了太平洋中。朱多年来心中疑惑一直没有解开:这样的标注是为了“乱了敌人锻炼了群众”吗?

第二件事情: 1983年9月1日清晨(UTC时间为8月31日傍晚),大韩航空007号班机进入苏联领空,遭苏联空军Su-15拦截机击落于库页岛西南方的公海。误入的原因是机长操作失误,没有切换到正确的导航模式【参考2】。这件事情之后美国宣布开放部份的GPS功能给民间使用。

参考:

1. http://view.news.qq.com/a/20090422/000029.htm 方舟子:相对论有没有用?

借用臧克家的一句话来描述方舟子“有的人死了,他还活着;有的人或者,他已经敏感字了”。

2. http://baike.baidu.com/item/%E5%A4%A7%E9%9F%A9%E8%88%AA%E7%A9%BA007%E5%8F%B7%E7%8F%AD%E6%9C%BA%E7%A9%BA%E9%9A%BE 大韩航空007号班机空难

如果非说苏联伟大的话,那么伟大的原因一定是它一次次将人类从自己的魔爪下解救出来。