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号班机空难

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

Step to UEFI (106)取得AHCI SATA 的序列号

继续前面的话题,现在尝试直接输出AHCI HDD 的信息。资料上标明AHCI和IDE HDD输出的信息格式是相同的,所以这里会一同处理。

原理上:找到 DISK INFO PROTOCOL 后,判断 GUID 是否为 IDE和 AHCI 的,如果是,那么用Identify 来取得型号信息。返回构体EFI_ATAPI_IDENTIFY_DATA,具体定义在下面这个文件中:

\EdkCompatibilityPkg\Foundation\Framework\Protocol\IdeControllerInit\IdeControllerInit.h 中。

typedef struct {
    UINT16  config;             // General Configuration
    UINT16  obsolete_1;
    UINT16  specific_config;
    UINT16  obsolete_3;   
    UINT16  retired_4_5[2];
    UINT16  obsolete_6;   
    UINT16  cfa_reserved_7_8[2];
    UINT16  retired_9;
    CHAR8   SerialNo[20];       // ASCII 
    UINT16  retired_20_21[2];
    UINT16  obsolete_22;
    CHAR8   FirmwareVer[8];     // ASCII 
    CHAR8   ModelName[40];      // ASCII 
    UINT16  multi_sector_cmd_max_sct_cnt;
    UINT16  reserved_48;
    UINT16  capabilities_49;
    UINT16  capabilities_50;
    UINT16  obsolete_51_52[2];   
    UINT16  field_validity;
    UINT16  obsolete_54_58[5];
    UINT16  mutil_sector_setting;
    UINT16  user_addressable_sectors_lo;
    UINT16  user_addressable_sectors_hi;
    UINT16  obsolete_62;
    UINT16  multi_word_dma_mode;
    UINT16  advanced_pio_modes;
    UINT16  min_multi_word_dma_cycle_time;
    UINT16  rec_multi_word_dma_cycle_time;
    UINT16  min_pio_cycle_time_without_flow_control;
    UINT16  min_pio_cycle_time_with_flow_control;
    UINT16  reserved_69_74[6];
    UINT16  queue_depth;
    UINT16  reserved_76_79[4];
    UINT16  major_version_no;
    UINT16  minor_version_no;
    UINT16  cmd_set_support_82;
    UINT16  cmd_set_support_83;
    UINT16  cmd_feature_support;
    UINT16  cmd_feature_enable_85;
    UINT16  cmd_feature_enable_86;
    UINT16  cmd_feature_default;
    UINT16  ultra_dma_select;
    UINT16  time_required_for_sec_erase;
    UINT16  time_required_for_enhanced_sec_erase;
    UINT16  current_advanced_power_mgmt_value;
    UINT16  master_pwd_revison_code;
    UINT16  hardware_reset_result;
    UINT16  current_auto_acoustic_mgmt_value;
    UINT16  reserved_95_99[5];
    UINT16  max_user_lba_for_48bit_addr[4];
    UINT16  reserved_104_126[23];
    UINT16  removable_media_status_notification_support;
    UINT16  security_status;
    UINT16  vendor_data_129_159[31];
    UINT16  cfa_power_mode;
    UINT16  cfa_reserved_161_175[15];
    UINT16  current_media_serial_no[30];
    UINT16  reserved_206_254[49];
    UINT16  integrity_word;
} EFI_ATAPI_IDENTIFY_DATA;

 

代码如下:

#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>

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 gEfiDiskInfoAhciInterfaceGuid  = { 0x9e498932, 0x4abc, 0x45af, 
					{ 0xa3, 0x4d, 0x02, 0x47, 0x78, 0x7b, 0xe7, 0xc6 }};
EFI_GUID gEfiDiskInfoIdeInterfaceGuid   = { 0x5E948FE3, 0x26D3, 0x42B5, 
					{ 0xAF, 0x17, 0x61, 0x02, 0x87, 0x18, 0x8D, 0xEC }};

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS Status;
    UINTN HandleIndex, NumHandles;
    EFI_HANDLE *ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;
	UINT32                       BufferSize;	
    EFI_ATAPI_IDENTIFY_DATA      IdentifyData;	
	UINT32				i;
	
    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 AHCI and IDE
		if (!(CompareGuid (
				&DiskInfoProtocol->Interface, 
				&gEfiDiskInfoAhciInterfaceGuid)||
			(CompareGuid (
				&DiskInfoProtocol->Interface,
			    &gEfiDiskInfoIdeInterfaceGuid)
			))) {	
				continue;
			}	

		BufferSize   = sizeof (EFI_ATAPI_IDENTIFY_DATA);
		Status = DiskInfoProtocol->Identify (
                         DiskInfoProtocol,
                         &IdentifyData,
                         &BufferSize
                         );			
		
		Print(L"Model Name :");
		for (i=0;i<40;i=i+2) {
			Print(L"%c%c",
				IdentifyData.ModelName[i+1],
				IdentifyData.ModelName[i]);
		}
		Print(L"\n");
	}

  return EFI_SUCCESS;
}

 

在 KabyLake HDK 板子上运行上述代码,结果如下:
ahci1

细心的朋友可能注意到,输出并不是直接输出序列号,而是有一个顺序上的调整:
Print(L”%c%c”, IdentifyData.ModelName[i+1], IdentifyData.ModelName[i]);
原因是,刚开始我试验的是直接顺序输出,但是发现结果是下面这样的:
ahci2

开始以为是 CHAR 对 CHAR16转换上的问题,后来查阅资料【参考1】,发现这里的行医比较特别。排列是 2/1/4/3/6/5……. 这样的:
ahci3

所以,修改代码手工做一次反转就可以了。
完整的代码下载:
diskinfoahci

参考:
1. http://www.t13.org/Documents/UploadedDocuments/docs2013/d2161r5-ATAATAPI_Command_Set_-_3.pdf

Windows常见开发包

https://msdn.microsoft.com/windows/hardware/commercialize/kits-and-tools-overview

Windows SDK for Windows 10 用来编写 Application的头文件库文件和工具 Windows SDK for Windows 10 contains headers, libraries, and tools you can use when you create apps that run on Windows operating systems.

WDK 10 用来编写Driver的头文件库文件和工具 WDK 10 contains the tools to build, test, debug, and deploy drivers for Windows 10.

Enterprise WDK (EWDK) 将上面2个放在一起的 The Enterprise WDK (EWDK) is a kit that large organizations can use as an alternative to downloading and installing the SDK and WDK individually on each computer.

Windows symbols 符号文件,用来调试 Windows的 Symbol files make it easier to debug your code.

Windows Hardware Lab Kit (HLK) for Windows 10 用来测试运行 Windows 设备的工具 Windows Hardware Lab Kit (HLK) for Windows 10

HLK supplemental test content 测试多媒体时可能需要的音频视频文件 Some tests, like graphics and multimedia tests, require additional files for testing.

ADK for Windows 10 (Windows Assessment and Deployment Kit) OEM和OEM用来制作安装镜像的工具
Download the Windows ADK to install tools and documentation for OEMs and ODMs to customize Windows 10 images, assess the quality and performance of systems or components, and to deploy Windows operating systems to new computers.

A device for UEFI Application testing

In the last posts, as the NT32 environment has too much limitations. We have to test my applications on the real system. I use a USB disk for these kinds of thing. After compiling, I have to copy my application to this disk. Then unplug and plug again to the testing system (I use Kabylake HDK now). After that I can test my application on the real system. If some bugs are found, I have to repeat this action. As you can see it’s really inconvenience.
For this purpose, I begin to study the “matrix switch IC” carefully. After all, I find that the common switching IC in the market could only support 300Mhz. While the USB 2.0 device requires 480Mhz. No one know what will happen if I make one. It’s not a good idea for me to DIY. At last, I begin to search the solution on Taobao.com. Soon, I find this one:

su

It has a mechanical switcher inside. And the most important thing: it’s very cheap. Only 7.8 Yuan (less than $2).
I buy one USB switcher and 2 USB cables (1.5m for 3Yuan).
The usage is that:
1st plug a USB disk in this switcher.
2nd Every time the complication is completed, copy the application to this USB Disk.
3rd Switch to the working PC and make your experiment in real system.

I have used this device for a period, it works well.

Step to UEFI (109)显示Shell 下面的历史信息

之前有网友询问过一个问题:他的程序调用了另外一个 Application,希望能够获得另外那个 Application的运行结果。最近,偶然看到了 Shell 下记录历史信息的功能,具体头文件在\ShellPkg\Application\Shell\ConsoleLogger.h 中有定义。

typedef struct _CONSOLE_LOGGER_PRIVATE_DATA{
  UINTN                             Signature;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   OurConOut;        ///< the protocol we installed onto the system table
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *OldConOut;       ///< old protocol to reinstall upon exiting
  EFI_HANDLE                        OldConHandle;     ///< old protocol handle
  UINTN                             ScreenCount;      ///< How many screens worth of data to save
  CHAR16                            *Buffer;          ///< Buffer to save data
  UINTN                             BufferSize;       ///< size of buffer in bytes

                                                      //  start row is the top of the screen
  UINTN                             OriginalStartRow; ///< What the originally visible start row was
  UINTN                             CurrentStartRow;  ///< what the currently visible start row is

  UINTN                             RowsPerScreen;    ///< how many rows the screen can display
  UINTN                             ColsPerScreen;    ///< how many columns the screen can display

  INT32                             *Attributes;      ///< Buffer for Attribute to be saved for each character
  UINTN                             AttribSize;       ///< Size of Attributes in bytes

  EFI_SIMPLE_TEXT_OUTPUT_MODE       HistoryMode;      ///< mode of the history log
  BOOLEAN                           Enabled;          ///< Set to FALSE when a break is requested.
  UINTN                             RowCounter;       ///< Initial row of each print job.
} CONSOLE_LOGGER_PRIVATE_DATA;

 

这个功能实现的方法是,在 Shell 启动的时,将系统中的EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 题材换成重新定义的,这样每次Application的输出都会被Shell截获存放在一个Buffer中,有需要的时候,再从 Buffer中取出(例如:使用 PageUp/PageDown 查看)。
对于我们来说,首先找到 Shell 的 Handle,之后枚举这个 Handle 上的全部protocol,找到SimpleTextOutProtocol,再根据找到的这个Protocol的偏移,反推算出CONSOLE_LOGGER_PRIVATE_DATA,最后就能访问到我们需要的CONSOLE_LOGGER_PRIVATE_DATA->buffer了。
代码如下,为了简单明了,我只显示Buffer最前面4行

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

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

extern EFI_HANDLE				 gImageHandle;

#define CR(Record, TYPE, Field, TestSignature)                                              \
    BASE_CR (Record, TYPE, Field)
	
typedef struct _CONSOLE_LOGGER_PRIVATE_DATA{
  UINTN                             Signature;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   OurConOut;        ///< the protocol we installed onto the system table
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *OldConOut;       ///< old protocol to reinstall upon exiting
  EFI_HANDLE                        OldConHandle;     ///< old protocol handle
  UINTN                             ScreenCount;      ///< How many screens worth of data to save
  CHAR16                            *Buffer;          ///< Buffer to save data
  UINTN                             BufferSize;       ///< size of buffer in bytes

                                                      //  start row is the top of the screen
  UINTN                             OriginalStartRow; ///< What the originally visible start row was
  UINTN                             CurrentStartRow;  ///< what the currently visible start row is

  UINTN                             RowsPerScreen;    ///< how many rows the screen can display
  UINTN                             ColsPerScreen;    ///< how many columns the screen can display

  INT32                             *Attributes;      ///< Buffer for Attribute to be saved for each character
  UINTN                             AttribSize;       ///< Size of Attributes in bytes

  EFI_SIMPLE_TEXT_OUTPUT_MODE       HistoryMode;      ///< mode of the history log
  BOOLEAN                           Enabled;          ///< Set to FALSE when a break is requested.
  UINTN                             RowCounter;       ///< Initial row of each print job.
} CONSOLE_LOGGER_PRIVATE_DATA;

#define CONSOLE_LOGGER_PRIVATE_DATA_FROM_THIS(a) CR (a, CONSOLE_LOGGER_PRIVATE_DATA, OurConOut, CONSOLE_LOGGER_PRIVATE_DATA_SIGNATURE)


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

	EFI_HANDLE	*HandleBuffer=NULL;
	UINTN		BufferSize=0,i;
  
	EFI_HANDLE	TheHandle;
	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *Dev;
	
	CONSOLE_LOGGER_PRIVATE_DATA       *ConsoleInfo;
	
	// Look up all gEfiShellProtocolGuid in the platform
	Status = gBS->LocateHandle(
		ByProtocol,
		&gEfiShellProtocolGuid,
		NULL,
		&BufferSize,
		HandleBuffer);
	if (Status == EFI_BUFFER_TOO_SMALL) {
		HandleBuffer = AllocateZeroPool(BufferSize);
		if (HandleBuffer == NULL) {
			return (EFI_BUFFER_TOO_SMALL);}
		//Get all the gEfiShellProtocolGuid Protocol in the system
		Status = gBS->LocateHandle(
			ByProtocol,
			&gEfiShellProtocolGuid,
			NULL,
			&BufferSize,
			HandleBuffer);
	}
	
	//Print(L"%d handles have been found!\n",(BufferSize / sizeof(EFI_HANDLE)));	

	if (BufferSize==0) {
			Print(L"No gEfiShellProtocolGuid found!\n"); 
			return EFI_SUCCESS; }
			
	//In fact there will be only one in system (in the Shell image)
	TheHandle=HandleBuffer[0];
	FreePool(HandleBuffer);

	//Get SimpleTextOutProtocol used by Shell
	Status = gBS->HandleProtocol (
					TheHandle,
					&gEfiSimpleTextOutProtocolGuid,
					&Dev);
	if (EFI_ERROR(Status))	{
		Print(L"Error when opening SimpleTextOutProtocol\n");
		return EFI_SUCCESS;
	}

	ConsoleInfo = CONSOLE_LOGGER_PRIVATE_DATA_FROM_THIS(Dev);	
	
	for (i=0;i<(ConsoleInfo->ColsPerScreen+2)*4;i++) {
		if (ConsoleInfo->Buffer[i] != 0) {Print(L"%c",ConsoleInfo->Buffer[i]);}
	}
	Print(L"\n");
	
	return EFI_SUCCESS;
}

 

运行结果,这是NT32模拟环境中的运行结果。同样我还在 Intel ApolloLake 上进行了实验,结果相同。

hs

完整的代码下载:

logbuffer

编译后的Application下载(IA32和X64)

lb

不要购买白色版本的USB Host Shield

前几日入手了新版的 USB Host Shield,今天拿到手了,拆开包装大吃一惊,发现有下面几个问题:
1. USB的方向变了,之前是和 UNO板载USB相反的方向,现在变成相同的方向,这样做的好处是方便整体设计,只要在一面开口即可(这个是 USB Host Shield 2.0 设计 升级的结果)。但是如果你之前设计过盒子,那么换Shield之后只能重新设计了;
image002
2. Shield上带 ICSP脚座(下图黑色方块),看起来这个Shield应该可以直接兼容Leonardo的板子(但是有可能要 rework);
3. 引脚选择的是最便宜的那种,很长,很软,很歪,插拔不了几次。断了一根的话,板子基本上就废了,真正使用的话恐怕只能一直固定着,尽量不插拔;
image004
4. 还是引脚的问题,很长,插入之后会长出来一截,总让人觉得没有插进去;
image006

更悲剧的是,入手之后上电无法使用,使用很早之前USB Host Shield 1.0的测试代码进行最基本的SPI 测试都无法通过。板子上也没有任何跳线,严重怀疑需要Rework一些位置才能工作起来。
因为是新买的,所以最后还是直接退货了事。因此,建议有需求的玩家,慎重选择这种白色的板子,为了方便省事,不妨直接选用DFRobot的板子。价格差不多是这种白色板子的3倍(110元左右),整体用料比较好,资料全面,更可靠(产品本身,不是服务)。

写完上面的话之后差不多一周,我入手了 DFRobot 的板子,广告是下面这样的,我用过两次他们家的板子,感觉用料非常扎实,工作也很好。售价 110,远比前面的贵。
image008
拿到手又是大吃一惊,和广告上的差别也太大了。
image010

除了不是彩色的插座之外,针脚同样也是之前买的那种很长,又非常软的。根本不值这个价格啊!同时还散发着浓烈的味道。我只听说过做高仿的古董,为了增加年代感会泡在粪坑中一年,但是从来没听说过电子产品也要这样处理。
毫无疑问的再次退货。
后来,极客工坊的togke(弘毅) 讲,国内的这种插接件其实挺难买的。原因是质量只有用了一段才知道,但是价格是很明显的。这样的小配件越是便宜出货量越大,结果造成了劣币驱逐良币,在市场上很难买到质量好的排针。而对于类似富士康这样的大公司来说,他们只愿意面对“大型客户”。于是,市面上无法买到合适质量的排针了。
又过了几次,我忽然想起来为什么之前买到的 USB Host Shield没有这样的问题?那块板子着实用了快2年。仔细端详,真相只有一个:在设计上似乎考虑到了这样的问题错开了一截,这样既可兼容原来的设计,又不会有选择排针的困惑。
image012
查一下原始的设计文档【参考1】如下
image014
可以看到原本是没有这样的设计,但是通过简单的修改,让板子更加适应国情。
从整个 Arduino 的产品来看,国内的作品水平还比较低,绝大多数卖家都是凭借“价格优势”来维持生存的。所有的产品也都以仿造为主。我不反对仿造之类的,但是单纯的一直如此是不会有出路的。

ps:最后我甚至怀疑买到的 DFRobot的是假货,于是我问了淘宝上全部售卖这个产品的卖家,得到的答案都是“只有这种,没有彩色排针的。”

参考:
1. https://www.sparkfun.com/products/9947

Step to UEFI (105)DiskinfoProtocol

这次介绍一下用来取得系统上硬盘信息的 Protocol: EFI_DISK_INFO_PROTOCOL。

在\MdePkg\Include\Protocol\DiskInfo.h 有他的原型:

///
/// Forward declaration for EFI_DISK_INFO_PROTOCOL
///
typedef struct _EFI_DISK_INFO_PROTOCOL  EFI_DISK_INFO_PROTOCOL;
///
/// The EFI_DISK_INFO_PROTOCOL provides controller specific information.
///
struct _EFI_DISK_INFO_PROTOCOL {
  ///
  /// A GUID that defines the format of buffers for the other member functions 
  /// of this protocol.
  ///
  EFI_GUID                  Interface;
  ///
  /// Return the results of the Inquiry command to a drive in InquiryData. Data
  /// format of Inquiry data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_INQUIRY     Inquiry;
  ///
  /// Return the results of the Identify command to a drive in IdentifyData. Data
  /// format of Identify data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_IDENTIFY    Identify;
  ///
  /// Return the results of the Request Sense command to a drive in SenseData. Data
  /// format of Sense data is defined by the Interface GUID.
  ///
  EFI_DISK_INFO_SENSE_DATA  SenseData;
  ///
  /// Specific controller. 
  ///
  EFI_DISK_INFO_WHICH_IDE   WhichIde;
};

 

更详细的介绍可以在 PI Specification 1.4 上找到。
对于不同类型的设备,比如 IDE 和 USB ,返回的数据格式是不同的。枚举到这个 PROTOCOL 之后需要检查EFI_GUID Interface 通过不同的GUID得知当前设备的类型。
下面先编写一个简单的 Demo,检查 GUID ,判断当前设备的类型:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Protocol/DiskInfo.h>
#include  <Library/BaseMemoryLib.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 gEfiDiskInfoIdeInterfaceGuid   = { 0x5E948FE3, 0x26D3, 0x42B5, 
					{ 0xAF, 0x17, 0x61, 0x02, 0x87, 0x18, 0x8D, 0xEC }};
EFI_GUID gEfiDiskInfoScsiInterfaceGuid  = { 0x08F74BAA, 0xEA36, 0x41D9, 
					{ 0x95, 0x21, 0x21, 0xA7, 0x0F, 0x87, 0x80, 0xBC }};
EFI_GUID gEfiDiskInfoUsbInterfaceGuid   = { 0xCB871572, 0xC11A, 0x47B5, 
					{ 0xB4, 0x92, 0x67, 0x5E, 0xAF, 0xA7, 0x77, 0x27 }};
EFI_GUID gEfiDiskInfoAhciInterfaceGuid  = { 0x9e498932, 0x4abc, 0x45af, 
					{ 0xa3, 0x4d, 0x02, 0x47, 0x78, 0x7b, 0xe7, 0xc6 }};
EFI_GUID gEfiDiskInfoNvmeInterfaceGuid  = { 0x3ab14680, 0x5d3f, 0x4a4d, 
					{ 0xbc, 0xdc, 0xcc, 0x38, 0x0, 0x18, 0xc7, 0xf7 }};
EFI_GUID gEfiDiskInfoUfsInterfaceGuid   = { 0x4b3029cc, 0x6b98, 0x47fb, 
					{ 0xbc, 0x96, 0x76, 0xdc, 0xb8, 0x4, 0x41, 0xf0 }};
int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS Status;
    UINTN HandleIndex, NumHandles;
    EFI_HANDLE *ControllerHandle = NULL;
	EFI_DISK_INFO_PROTOCOL	*DiskInfoProtocol;
	
    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;
        } 
		
		Print(L"Device[%d] GUID: %g",
				HandleIndex,
				DiskInfoProtocol->Interface);
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoIdeInterfaceGuid)) {	
				Print(L" IDE HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoScsiInterfaceGuid)) {	
				Print(L" Scsi HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUsbInterfaceGuid)) {	
				Print(L" USB HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoAhciInterfaceGuid)) {	
				Print(L" AHCI HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoNvmeInterfaceGuid)) {	
				Print(L" NVME HDD\n");
			}	
		if (CompareGuid (
				&DiskInfoProtocol->Interface, 
			&gEfiDiskInfoUfsInterfaceGuid)) {	
				Print(L" Ufs HDD\n");
			}				
	}

  return EFI_SUCCESS;
}

 

上述代码在UDK2014中编译通过,但是无法在 NT32环境下运行。于是在实体机KabyLake HDK 上实验, 板子上挂载了一个 SATA HDD 一个eMMC和两个USB Disk。运行结果如下:

dit

可以看出,当前的SATA 是AHCI 模式。此外, eMMC 设备是无法被识别出来的,也许后面会扩展到这种设备吧。
完整的代码下载:

diskinfotest

Step to UEFI Tips :介绍 BaseMemoryLib

最近在写代码时,需要用到一些关于 GUID 处理的函数。偶然间看到了 BaseMemoryLib.h 中有一些,顺便整理了一下这个库提供的函数。完整的头文件在 \MdePkg\Include\Library\BaseMemoryLib.h .

 

CopyMem   内存拷贝函数

SetMem       用8Bit来进行内存填充

SetMem16用 16Bit来进行内存填充

SetMem32用 32Bit来进行内存填充

SetMem64用 64Bit来进行内存填充

SetMemN  用 UINTN 来进行内存填充(UINTN在IA32下和 X64下大小不同)

ZeroMem  对指定内存清零

CompareMem  比较内存函数

ScanMem8   在内存中搜索一个 8Bit的指定值

ScanMem16   在内存中搜索一个 16Bit的指定值

ScanMem32   在内存中搜索一个 32Bit的指定值

ScanMem64   在内存中搜索一个 64Bit的指定值

ScanMemN     在内存中搜索UINTN大小的指定值

CopyGuid       复制一个 GUID 到另外一个 GUID中

CompareGuid  比较2个 GUID

ScanGuid         在内存中搜索一个给定的 GUID

IsZeroGuid       检查一个 GUID是否为0

IsZeroBuffer     检查一段内存是否为为全0

 

 

 

好玩的电子设备(2)

这次介绍一下USB逻辑分析仪。USB协议是比较复杂的通讯协议,使用差分信号进行传输,速度很快,编码上采用 NRZI 【参考1】,这样导致根本无法使用示波器之类的进行Debug (速度快,数据多,一直在变化)。因此,要想知道线路上究竟跑了什么数据只能使用专用的逻辑分析仪进行分析。下面是 Lecroy 出品的 Advisor T3 型USB分析仪,可以进行USB 2.0/3.0 协议的分析(不支持 USB Type C ,  有专门额外的设备可以用来分析这个协议)。

下面是设备的正面,A口和B口分别连接在设备和主机上,对于USB HOST 和 Salve来说是透明的。

image001

这是背面的照片,捕获的数据会通过这个接口输出。这是一个2.0的接口,因为内部存储空间有限,这个设备不适合长期抓取大量的数据。

image002

下面尝试抓取 Arduino USB 串口(16U2)的数据,我们先编写一个简单的代码,一直向串口输出字符串:

 

void setup() {

  // put your setup code here, to run once:

   Serial.begin(9600);

}



void loop() {

  // put your main code here, to run repeatedly:

  Serial.println("www.lab-z.com");

  delay(5000);

}

 

启动工作机上的逻辑分析仪软件之后就可以开始抓取数据了,这是软件界面

image003

 

image004

抓取的过程是:

  1. 开始记录
  2. 插入 Arduino UNO
  3. 打开串口工具记录
  4. 从串口工具对 Arduino 发送数据

 

第一步:设置设备的地址,这里可以看到分配给 Arduino 的USB地址是 0x0F

image005

第二步:HOST要求设备送出 Device Descriptor

 

REQUEST SUMMARY

Setup Data 80 06 00 01 00 00 12 00
Direction Device-to-host
Type Standard
Recipient Device
bRequest GET_DESCRIPTOR
wValue DEVICE
wIndex 0x0000
wLength 0x0012

 

DECODING INFORMATION

 

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bRequest   8   8   GET_DESCRIPTOR   0x06   bRequest HexVal: 0x06
  wValue   16   16   DEVICE type   0x0100   Type of Descriptor
  wIndex   16   32   0x0000   0x0000   index info

DEVICE Descriptor

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   0    0x12   0x12   Descriptor size is 18 bytes
  bDescriptorType   8   8    0x01   0x01   DEVICE Descriptor Type
  bcdUSB   16   16    0x0110   0x0110   USB Specification version 1.10
  bDeviceClass   8   32    0x02   0x02   The device belongs to the Communication Device/Interface Class
  bDeviceSubClass   8   40    0x00   0x00   Subclass 0x00 not defined
  bDeviceProtocol   8   48    0x00   0x00   The device uses the No class specific protocol required Protocol
  bMaxPacketSize0   8   56    0x08   0x08   Maximum packet size for endpoint zero is 8
  idVendor   16   64    0x2341   0x2341   Vendor ID is 9025: No Company Matches Id
  idProduct   16   80    0x0043   0x0043   Product ID is 67
  bcdDevice   16   96    0x0001   0x0001   The device release number is 0.01
  iManufacturer   8   112    0x01   0x01   The manufacturer string descriptor index is 1
  iProduct   8   120    0x02   0x02   The product string descriptor index is 2
  iSerialNumber   8   128    0xDC   0xDC   The serial number string descriptor index is 220
  bNumConfigurations   8   136    0x01   0x01   The device has 1 possible configurations

 

第三步,上面的描述符提到这个设备有1个 Configuration Descriptor, 主机要求设备送出 Configuration Descriptor

image006

REQUEST SUMMARY

Setup Data 80 06 00 02 00 00 FF 00
Direction Device-to-host
Type Standard
Recipient Device
bRequest GET_DESCRIPTOR
wValue CONFIGURATION, Index 0
wIndex 0x0000
wLength 0x00FF

 

DECODING INFORMATION

 

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bRequest   8   8   GET_DESCRIPTOR   0x06   bRequest HexVal: 0x06
  wValue   16   16   CONFIGURATION type, Index 0   0x0200   Type of Descriptor
  wIndex   16   32   0x0000   0x0000   index info

CONFIGURATION Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   0    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   8    0x02   0x02   CONFIGURATION Descriptor Type
  wTotalLength   16   16    0x003E   0x003E   The total length of data for this configuration is 62. This includes the combined length of all the descriptors returned Warning : The Value of wTotalLength is not equal to real length
  bNumInterfaces   8   32    0x02   0x02   This configuration supports 2 interfaces
  bConfigurationValue   8   40    0x01   0x01   The value 1 should be used to select this configuration
  iConfiguration   8   48    0x00   0x00   The device doesn’t have the string descriptor describing this configuration
  bmAttributes   8   56    0xC0   0xC0   Configuration characteristics : Bit 7: Reserved (set to one) 1 Bit 6: Self-powered 1 Bit 5: Remote Wakeup 0
  bMaxPower   8   64    0x32   0x32   Maximum power consumption of the device in this configuration is 100 mA

INTERFACE Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   72    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   80    0x04   0x04   INTERFACE Descriptor Type
  bInterfaceNumber   8   88    0x00   0x00   The number of this interface is 0.
  bAlternateSetting   8   96    0x00   0x00   The value used to select the alternate setting for this interface is 0
  bNumEndpoints   8   104    0x01   0x01   The number of endpoints used by this interface is 1 (excluding endpoint zero)
  bInterfaceClass   8   112    0x02   0x02   The interface implements the Communication Device/Interface class
  bInterfaceSubClass   8   120    0x02   0x02   The interface implements the Abstract Control Model Subclass
  bInterfaceProtocol   8   128    0x01   0x01   The interface uses the Common AT commands Protocol
  iInterface   8   136    0x00   0x00   The device doesn’t have a string descriptor describing this iInterface

Communication Class Specific INTERFACE Descriptor (5 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   144    0x05   0x05   Size of the descriptor, in bytes
  bDescriptorType   8   152    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   160    0x00   0x00   Header Funtional Descriptor Subtype
  bcdCDC   16   168    0x1001   0x1001   USB Class Definitions for Communications the Communication specification version 10.01

Communication Class Specific INTERFACE Descriptor (4 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   184    0x04   0x04   Size of the descriptor, in bytes
  bDescriptorType   8   192    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   200    0x02   0x02   Abstract Control Management Functional Descriptor Subtype
  bmCapabilities   8   208    0x06   0x06 Bit 0: Whether device supports the request combination of Set_Comm_Feature, Clear_Comm_Feature, and Get_Comm_Feature 0
Bit 1: Whether device supports the request combination of Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, and the notification Serial_State 1
Bit

Communication Class Specific INTERFACE Descriptor (5 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength….   8   216    0x05   0x05   Size of the descriptor, in bytes
  bDescriptorType   8   224    0x24   0x24   CS_INTERFACE Descriptor Type
  bDescriptorSubType   8   232    0x06   0x06   Union Functional Descriptor Subtype
  bControlInterface   8   240    0x00   0x00   The interface number of the Communications or Data Class interface
  bSubordinateInterface0   8   248    0x01   0x01   Interface number of subordinate interface in the Union

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   256    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   264    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   272    0x82   0x82   This is an IN endpoint with endpoint number 2
  bmAttributes   8   280    0x03   0x03   Types –
Transfer: INTERRUPT
Low Power: No
Pkt Size Adjust: No
  wMaxPacketSize   16   288    0x0008   0x0008   Maximum packet size for this endpoint is 8 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   304    0xFF   0xFF   The polling interval value is every 255 Frames. Undefined for Hi-Speed

INTERFACE Descriptor (9 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   312    0x09   0x09   Descriptor size is 9 bytes
  bDescriptorType   8   320    0x04   0x04   INTERFACE Descriptor Type
  bInterfaceNumber   8   328    0x01   0x01   The number of this interface is 1.
  bAlternateSetting   8   336    0x00   0x00   The value used to select the alternate setting for this interface is 0
  bNumEndpoints   8   344    0x02   0x02   The number of endpoints used by this interface is 2 (excluding endpoint zero)
  bInterfaceClass   8   352    0x0A   0x0A   The interface implements the Data Interface class
  bInterfaceProtocol   8   368    0x00   0x00   The interface uses the No class specific protocol required Protocol
  bInterfaceSubClass   8   360    0x00   0x00   The Subclass code is 0
  iInterface   8   376    0x00   0x00   The device doesn’t have a string descriptor describing this iInterface

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   384    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   392    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   400    0x04   0x04   This is an OUT endpoint with endpoint number 4
  bmAttributes   8   408    0x02   0x02   Types –
Transfer: BULK
Pkt Size Adjust: No
  wMaxPacketSize   16   416    0x0040   0x0040   Maximum packet size for this endpoint is 64 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   432    0x01   0x01   The polling interval value is every 1 Frames. If Hi-Speed, 1 uFrames/NAK

ENDPOINT Descriptor (7 bytes)

  Field     Length (bits)     Offset (bits)     Decoded     Hex Value                                                       Description                                                    
  bLength   8   440    0x07   0x07   Descriptor size is 7 bytes
  bDescriptorType   8   448    0x05   0x05   ENDPOINT Descriptor Type
  bEndpointAddress   8   456    0x83   0x83   This is an IN endpoint with endpoint number 3
  bmAttributes   8   464    0x02   0x02   Types –
Transfer: BULK
Pkt Size Adjust: No
  wMaxPacketSize   16   472    0x0040   0x0040   Maximum packet size for this endpoint is 64 Bytes. If Hi-Speed, 0 additional transactions per frame
  bInterval   8   488    0x01   0x01   The polling interval value is every 1 Frames. If Hi-Speed, every 1 uFrames

 

第五步,主机请求 String Descriptor

image007

我不熟悉USB CDC 协议,因此上面只能做一个简单的使用实例。

最后,多说两句:这款分析仪的价格在 20万人民币,个人基本上不用考虑购买一个玩玩儿了。此外,近两年有一些国产的逻辑分析仪,价格只有国外的十分之一,但是我没有实验过。期待随着时间的推移,国产分析设备有所长进,甚至有一天能够对 USB协议产生影响。大多数情况下,USB设备的调试还是依靠Slave Device 本身的Debug 输出(比如:Uart),普通开发者也不会遇到传输上面的问题。

参考:

  1. http://baike.baidu.com/view/1367029.htm NRZ-I编码

Step to UEFI (104)eMMC in the Shell

eMMC 应用在平板电脑上已经有一段时间了,本文给出了一个取得 eMMC CID 的例子。当然,你在其他资料上还会发现一些类似 MMC_INFO 之类的Protocol,但是根据我的实验,目前大多数系统只支持SD HOST IO 这一个 Protocol。此外,强烈建议一定在实体机上进行实验,避免出现费了很大力气编写代码,但是实际上第一步就无法完成的问题。
原理介绍:首先查找系统中的 SD_HOST_IO 的Protocol .目前看起来系统中只有一个这样的 Protocol ,所以我们用 LocateProtocol 就足够了。取得之后,通过这个 Protocol,对 eMMC 发送command。这个做法和 ATA 设备的 PassThrough Protocol很像。下面的代码是取得CID信息的,CID是用来识别eMMC一些基本信息的寄存器,比如 Serial Number,具体定义在 eMMC Specification中可以找到【参考1】 。
“8.2 CID register
The Card IDentification (CID) register is 128 bits wide. It contains the card identification information used during the card identification phase (MultiMediaCard protocol). Every individual flash or I/O card shall have an unique identification number. Every type of MultiMediaCard ROM cards (defined by content) shall have an unique identification number. Table 41 on page 112 lists these identifiers.The structure of the CID register is defined in the following sections”
image001

代码:

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

#include "SDHostIo.h"
#include "mmc.h"


EFI_GUID gEfiSdHostIoProtocolGuid = { 0xb63f8ec7, 0xa9c9, 0x4472, 
					{0xa4, 0xc0, 0x4d, 0x8b, 0xf3, 0x65, 0xcc, 0x51}};
 
//
// Command timeout will be max 100 ms 
//
#define  TIMEOUT_COMMAND     100

extern EFI_BOOT_SERVICES         *gBS;

EFI_STATUS
SendCommand (
  IN   EFI_SD_HOST_IO_PROTOCOL    *This,
  IN   UINT16                     CommandIndex,
  IN   UINT32                     Argument,
  IN   TRANSFER_TYPE              DataType,
  IN   UINT8                      *Buffer, OPTIONAL
  IN   UINT32                     BufferSize,    
  IN   RESPONSE_TYPE              ResponseType,
  IN   UINT32                     TimeOut,  
  OUT  UINT32                     *ResponseData
  )
/*++

  Routine Description:
    Send command by using Host IO protocol
  
  Arguments:
    This           - Pointer to EFI_SD_HOST_IO_PROTOCOL
    CommandIndex   - The command index to set the command index field of command register
    Argument       - Command argument to set the argument field of command register
    DataType       - TRANSFER_TYPE, indicates no data, data in or data out
    Buffer         - Contains the data read from / write to the device
    BufferSize     - The size of the buffer
    ResponseType   - RESPONSE_TYPE
    TimeOut        - Time out value in 1 ms unit
    ResponseData   - Depending on the ResponseType, such as CSD or card status

  Returns:  
    EFI_INVALID_PARAMETER
    EFI_UNSUPPORTED
    EFI_DEVICE_ERROR
    EFI_SUCCESS
 --*/   
{

  EFI_STATUS    Status;

  Status = This->SendCommand (
           This,
           CommandIndex,
           Argument,
           DataType,
           Buffer,
           BufferSize,
           ResponseType,
           TimeOut,
           ResponseData
           );
  if (!EFI_ERROR (Status)) {
    if (ResponseType == ResponseR1 || ResponseType == ResponseR1b) {
      //ASSERT(ResponseData != NULL);
	  Print(L"Error with code [%d]",(*ResponseData));
    }
  } else {
    This->ResetSdHost (This, Reset_DAT_CMD);
  }

  return Status;
}

int
EFIAPI
main (
  IN UINT32 Argc,
  IN CHAR16 **Argv
  )
{
	EFI_SD_HOST_IO_PROTOCOL   *SdHostIo;
	EFI_STATUS 		Status;	
	CID             CIDReg;
    OCR             OCRReg;
	UINT32          TimeOut=5000;
	
	Status = gBS->LocateProtocol (       
                  &gEfiSdHostIoProtocolGuid,
                  NULL,
                  &SdHostIo
                  );
	if (EFI_ERROR (Status)) {
		Print(L"No SdHost driver, Application is exiting!\n");
		return Status;
	}  	

    SdHostIo->EnableAutoStopCmd (SdHostIo, TRUE);
    SdHostIo->SetupDevice (SdHostIo);

    //
    // Go to Idle
    //
	SendCommand (
                SdHostIo,
                GO_IDLE_STATE,
                0,
                NoData,
                NULL,
                0,  
                ResponseNo,
                TIMEOUT_COMMAND,
                NULL
            );

    gBS->Stall (100 * 1000);

    //
    // Check voltage support, first time we use 0x40FF8080
    //
    SendCommand (
                SdHostIo,
				SEND_OP_COND,
                0x40FF8080,
                NoData,
                NULL,
                0,  
                ResponseR3,
                TIMEOUT_COMMAND,
                (UINT32*)&(OCRReg)
            );

    while (OCRReg.Busy != 1) {
		OCRReg.AccessMode = 0x02; // sector mode;
		SendCommand (
				  SdHostIo,
                  SEND_OP_COND,
                  *(UINT32*)&(OCRReg),
                  NoData,
                  NULL,
                  0,  
                  ResponseR3,
                  TIMEOUT_COMMAND,
                  (UINT32*)&(OCRReg)
                  );
						  
        gBS->Stall(100);

        TimeOut--;
        if (TimeOut == 0) {
            Print(L"Card is always busy\n");
            Status = EFI_TIMEOUT;
            goto Exit;
        } 
    } 

	SendCommand(
          SdHostIo,
          ALL_SEND_CID,
		0,
          NoData,
          NULL,
          0,  
          ResponseR2,
          TIMEOUT_COMMAND,
          (UINT32*)&(CIDReg)
          );
  
    Print (L" Product serial number      : %X\n",CIDReg.PSN);
    Print (L" Product revision           : %X\n",CIDReg.PRV);	
    Print (L" Product name               : %c%c%c%c%c%c\n",
								CIDReg.PNM[0],
								CIDReg.PNM[1],
								CIDReg.PNM[2],
								CIDReg.PNM[3],
								CIDReg.PNM[4],
								CIDReg.PNM[5]);
    Print (L" Manufacturer ID            : %X\n",CIDReg.MID);	
	
Exit:	
	return EFI_SUCCESS;
}

在kabyLake HDK 上运行结果:
image002

完整代码下载,其中还有代码生成的EFI程序,是 X64的。
getemmc

本文使用的头文件都是来自新版的 EDK2(比 UDK2015要新一些,我觉得UDK2017有可能会正式加入吧)【参考2】,有兴趣的读者可以自行查阅。
最后,既然有了CID,那么还可以读取一些关于 eMMC的其他信息,比如容量。请读者自己尝试完成。
最后的最后,推荐 Lenovo 出品的一款擦除 eMMC的工具,在 Shell 下运行,可以很快擦掉全部内容(应该使用 eMMC 的 Command 直接Erase的,所以能够达到很快的速度)。
gufd01ww

参考:
1. http://www.jedec.org/standards-documents/results/jesd84-b51 JESD84-A44.pdf
2. https://github.com/tianocore/edk2
3. http://support.lenovo.com/us/en/downloads/ds100934