做一个蓝牙小车

起因是这样的:一个壕朋友希望我帮忙给他做个小车。具体要求是:能用蓝牙控制,负重500g左右。经过考察,他决定在 DFRobot上选一款,毕竟DFRobot在国内也算首屈一指。根据他的需求,最开始选定的是下面这个:

image001

下订单的时候发现没有货。官方的解释是:春节期间都被洋人买光了……剩下的2个轮子有货的型号的太小不符合要求,最后选的是四个轮子的,就是下面这样的:

image002

上面像眼睛一样的是超声波距离传感器,传感器下面有个舵机,能够带动它进行不同方向的扫描。

下了订单付款之后,第二天就收到了,包装超级好,让我大吃一惊。双层包装,然后全部的小车套件放在里面的塑料工具箱中,用之前的话说叫做“高端大气上档次”。激动过后,先盘点下零件,很惊讶的发现其中竟然有两个巨大的轮子,更惊讶的是只有两个轮子。然后急忙找来装箱单,他们错发了个更昂贵的型号给我……。很无语,急忙拨打客服电话,接电话的是一个妹子,我简单说了下情况,她表示目前是18:20,他们已经下班了,让我明天9:30之后再打电话……第二天上午,打电话过去,问明情况之后,对方表示是他们搞错了,让我快递退回。不知道是不是因为发错了贵的所以才这么痛快。我又问是不是要顺风快递,对方略微迟疑,然后说除了顺风之外的所有都可以,理由是上海市内都是隔天到达。打完电话第二天是周日,赶紧给他们发了快递。周一他们顺利收到,又给我重新发了一个。收到之后第一件事情:打开数轮子

image003

包装比前面那个差多了,没有工具箱。如果让我评价做工最好的配件,我首选那个橘色的USB线。柔软,颜色鲜艳,手感很好。跑到他们官网看了一下,这个数据线要22元。

image004

接下来就开始了安装,首先是底盘和四个直流电机。所有的螺丝在最后组装完成之前都不要拧紧。

image005

没想到的是:安装直流电机后需要焊接电线,我以为能高级一些免烙铁。焊接的时候始终担心不小心弄坏了触点。四个电机,八个点,还好,没问题。

image006

底板的盖,也是电池盒安装的地方。需要5节电池,很奇怪的数字,如果以后用充电电池还不太好一次性充满。

image007

装上盖板就是这个样子,电池盒是在小车下方,面向地面。

image008

上层只有控制板需要安装一下,拧螺丝之类即可。安装简单,牢固度是比较差的。

image009

控制板是这样的(Romeo 三合一Arduino兼容控制器)

image010

本质上他是一个Arduino(ATmega328P),14 通道数字 I/O,6 PWM 通道 8通道10位模拟输入,3组I2C,自带蓝牙,带5个按键,能够直接带动2组直流电机……..
装上电池打开开关就是这个样子:

image011

然后我就开始研究如何蓝牙控制了。很快,我就陷入了迷茫:无论如何都无法让我的笔记本搜索到他的蓝牙设备。无法配对,控制更是无从谈起。我之前用过 HC05 的透传模块,感觉很容易就上手了,没想到这个折腾了很久都没有办法跨出去第一步。上DFRobot的论坛搜索,用AT命令调整各种参数,结果都一样,就是找不到设备。最终在一个帖子中找到了不是很确实的说法:控制板上的蓝牙很高级是蓝牙4.0,如果你的蓝牙Host太矬,是无法找到这个控制板的。想想估计我开发的笔记本很可能不支持蓝牙4.0。第二天,又找了一个平板电脑,肯定支持蓝牙4.0,果真很快找到了设备。配对之后又让我陷入了迷茫:没有出现串口设备啊。只好再搜索。找到一句话,大意是,如果你想用串口透传,那么请购买另外一个USB蓝牙配对的东东……。

心中权衡了一下时间成本和金钱,放弃之,换上我之前的 HC05模块,连接串口 RX/TX ,很快就能控制起来了。最终的样子是这样:

image012

估计了一下,有点不划算。控制板上的资源我只用了2个PWM 2个GPIO?其余的都没用上啊。

程序很简单,就是控制直流电机的转动,基本上就是网站上的Demo例子:

int E1 = 5;     //定义M1使能端
int E2 = 6;     //定义M2使能端
int M1 = 4;    //定义M1控制端
int M2 = 7;    //定义M1控制端
void stop(void){                 //停止
       digitalWrite(E1,LOW);   
       digitalWrite(E2,LOW);      
}  
 
void advance(char a,char b){           //前进
       analogWrite (E1,a);             //PWM调速
       digitalWrite(M1,HIGH);    
       analogWrite (E2,b);    
       digitalWrite(M2,HIGH);
}  
void back_off (char a,char b) {          //后退
       analogWrite (E1,a);
       digitalWrite(M1,LOW);   
       analogWrite (E2,b);    
       digitalWrite(M2,LOW);
}
void turn_L (char a,char b) {           //左转
       analogWrite (E1,a);
       digitalWrite(M1,HIGH);    
       analogWrite (E2,b);    
       digitalWrite(M2,LOW);
       delay(100);
}
void turn_R (char a,char b) {           //右转
       analogWrite (E1,a);
       digitalWrite(M1,LOW);    
       analogWrite (E2,b);    
       digitalWrite(M2,HIGH);
       delay(100);       
}

void setup(void) { 
    int i;
    for(i=6;i<=9;i++)
    pinMode(i, OUTPUT);  
    Serial.begin(9600);      //设置串口波特率
} 

void loop(void) { 
   if(Serial.available()>0){
     char val = Serial.read();
     if(val!=-1){
          switch(val){
             case 'w'://前进
                     advance (255,255);   //PWM调速
                     break;
             case 's'://后退
                     back_off (255,255);
                     break;
             case 'a'://左转
                     turn_L (255,255);
                     break;       
             case 'd'://右转
                     turn_R (255,255);
                     break;          
            }     
          delay(40); 
       }
      else stop();  
   }
   stop();
}

 

PC端控制软件的话,我用Putty 工具,打开串口 WASD 这几个键就能控制前后左右移动。

最后吐槽一下:

1.这个东西要比想象中复杂,我以为买了套件安装会简单多。

2.做工还是有点粗糙,行走直线的时候,你会发现这几个轮子会歪转起来不是完整的圆形,我尝试很多次调整,没什么效果。应该是直流电机和轮子连接上的问题。

3.极客工坊上有朋友说DFRobot发错东西不是一次了,他买个舵机也发错了。如果你对实效要求很高,比如:期望今天下订单,周末用来安装调试,那么一定要小心,搞不好拿到手才发现东西不对。

4.说明做的不好。我估算了一下,组装差不多花了10个小时。这个时间有点多,如果是初学者或者缺少工具的人会遇到很多意想不到的困难。如果能说明一下需要用到的工具就好了。安装说明是在线的,是中英文混合的。这个很讨厌,混合之后就有一个衔接的问题,让人头晕。历史也已经证明,中英文混合是很犯忌讳的事情。比如:有上级问一个人,“你叫什么名字?”“他回答:“Ni Fake”。上级很恼火,“还拽英文?”他一听,以为还要介绍自己的英文名,脱口而出“Fuck Ni”……后面的事情我们都知道了。

5.控制板的设计上还有点问题,有一个螺丝孔位我无法找到合适的螺丝。配件自带螺丝头过大,无法正常拧进去,我只好换了一个自己的螺丝,勉强凑合。

image013

期望 DFRobot 能够努力改进产品,也为国内客户提供更好的服务。有一种说法是国内的Arduino生产厂家不愿在国内进行销售,主要是客户少事情多,然后仿造的太多了根本赚不到什么钱…….

Step to UEFI (37) —– SetTimer 设定定时器(上)

众所周知:UEFI中没有中断(UEFI唯一一个中断int 0,timer )【参考1】,如果想实现一个定时器的功能,必须使用 Event。

实现的思路是:

1. CreateEvent 创建 Timer Event
2. SetTimer 设定 Periodic 触发
3. SetTimer 关闭定时器
4. CloseEvent 销毁 Timer Event

首先研究 CreateEvent ,这个函数是Boot Service中提供的【参考3】

settimer1

第一个参数给出创建的类型,我们要选择EVT_TIMER;第二个参数是优先级,对我们来说影响不大;第三个参数给出当Event发生时对应的处理函数;第四个参数我的理解是自定义的数据;第五个参数是创建出来的Event。

接下来再看看SetTimer函数,同样也是 Boot Service 中提供的服务

settimer2

第一个参数是你创建的Event;然后是Timer的类型,比如:周期性触发;最后是设定Timer的时间,多久触发一次,单位是100ns。

CloseEvent就很简单了

settimer3

程序还参考了 ShellPkg\Library\UefiShellNetwork1CommandsLib\Ping.c 的代码。

最终代码如下

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

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

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

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

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

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

STATIC CONST UINTN SecondsToNanoSeconds = 10000000;

UINTN	Counter = 0;
/**
  The callback function for the timer event used to get map.

  @param[in] Event    The event this function is registered to.
  @param[in] Context  The context registered to the event.
**/
VOID
EFIAPI
Timeout (
  IN EFI_EVENT      Event,
  IN VOID           *Context
  )
{
  Print(L"www.lab-z.com [%d]\r\n",++ Counter);
  return ;
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                TimerOne = NULL;
  BOOLEAN					ExitMark=FALSE;
  
  Status  = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL | EVT_TIMER,
                    TPL_CALLBACK,
                    Timeout,
                    NULL,
                    &TimerOne
                    );
    
    if (EFI_ERROR (Status)) {
        Print(L"Create Event Error! \r\n");
		return ;
    }

    Status = gBS->SetTimer (
                   TimerOne,
                   TimerPeriodic,
                   MultU64x32 (SecondsToNanoSeconds, 1)
                   );
    
    if (EFI_ERROR (Status)) {
        Print(L"Set Timer Error! \r\n");
		return ;
    }

	while (!ExitMark)
	{
		if (mEfiShellEnvironment2 -> GetExecutionBreak()) {ExitMark=TRUE;}
	}
    gBS->SetTimer (TimerOne, TimerCancel, 0);
    gBS->CloseEvent (TimerOne);	

  return EFI_SUCCESS;
}

 

运行结果如下
TimerTest

完整代码下载
TimerTest

后记:这部分对我来说还是比较复杂,在描述上定义概念可能会有偏差,如果阅读中发现,欢迎通知我及时改正。

参考:

1. http://blog.csdn.net/celiaqianhj/article/details/7180783 UEFI Events
2. http://biosren.com/viewthread.php?tid=2095&highlight=%B6%A8%CA%B1 什么是EFI Events?
3. UEFI Spec 2.4 P118

Step to UEFI (36) —– 枚举Shell下的全部盘符

目标:写一个小程序来枚举当前系统中的盘符。比如:FS0: FS1: 等等。

和这个需求最相近的参考文件是 map 功能,每次启动shell的时候他都会展示一下当前系统中的全部盘符。这个功能的代码在 ShellPkg\Library\UefiShellLevel2CommandsLib\Map.c 。大概研究了一下,实现的方法是分别枚举有 Simple File Protocol 和 Block IO Protocol 的Handle (在 PerformMappingDisplay 函数中),然后取每的 Device Path Protocol(在 PerformSingleMappingDisplay 函数中),最后从这个Protocol中获取对应的盘符。

map.c中使用 gEfiShellProtocol->GetMapFromDevicePath 功能取得名称,但是在实际测试过程中我的程序中取得到的 gEfiShellProtocol 不知为何一直为0. 最后只得使用 mEfiShellEnvironment2 -> GetFsName 来完实现这个功能。

ShellPkg\Include\Protocol\EfiShellEnvironment2.h 有 EFI_SHELL_ENVIRONMENT2的定义

/// EFI_SHELL_ENVIRONMENT2 protocol structure.
typedef struct {
  SHELLENV_EXECUTE                        Execute;
  SHELLENV_GET_ENV                        GetEnv;
  SHELLENV_GET_MAP                        GetMap;
  SHELLENV_ADD_CMD                        AddCmd;
  SHELLENV_ADD_PROT                       AddProt;
  SHELLENV_GET_PROT                       GetProt;
                          CurDir;
  SHELLENV_FILE_META_ARG                  FileMetaArg;
  SHELLENV_FREE_FILE_LIST                 FreeFileList;

  //
  // The following services are only used by the shell itself.
  //
  SHELLENV_NEW_SHELL                      NewShell;
  SHELLENV_BATCH_IS_ACTIVE                BatchIsActive;

  SHELLENV_FREE_RESOURCES                 FreeResources;

  //
  // GUID to differentiate ShellEnvironment2 from ShellEnvironment.
  //
  EFI_GUID                                SESGuid;
  //
  // Major Version grows if shell environment interface has been changes.
  //
  UINT32                                  MajorVersion;
  UINT32                                  MinorVersion;
  SHELLENV_ENABLE_PAGE_BREAK              EnablePageBreak;
  SHELLENV_DISABLE_PAGE_BREAK             DisablePageBreak;
  SHELLENV_GET_PAGE_BREAK                 GetPageBreak;

  SHELLENV_SET_KEY_FILTER                 SetKeyFilter;
  SHELLENV_GET_KEY_FILTER                 GetKeyFilter;

  SHELLENV_GET_EXECUTION_BREAK            GetExecutionBreak;
  SHELLENV_INCREMENT_SHELL_NESTING_LEVEL  IncrementShellNestingLevel;
  SHELLENV_DECREMENT_SHELL_NESTING_LEVEL  DecrementShellNestingLevel;
  SHELLENV_IS_ROOT_SHELL                  IsRootShell;

  SHELLENV_CLOSE_CONSOLE_PROXY            CloseConsoleProxy;
  HANDLE_ENUMERATOR                       HandleEnumerator;
  PROTOCOL_INFO_ENUMERATOR                ProtocolInfoEnumerator;
  GET_DEVICE_NAME                         GetDeviceName;
  GET_SHELL_MODE                          GetShellMode;
  SHELLENV_NAME_TO_PATH                   NameToPath;
  SHELLENV_GET_FS_NAME                    GetFsName;
  SHELLENV_FILE_META_ARG_NO_WILDCARD      FileMetaArgNoWildCard;
  SHELLENV_DEL_DUP_FILE                   DelDupFileArg;
  SHELLENV_GET_FS_DEVICE_PATH             GetFsDevicePath;
} EFI_SHELL_ENVIRONMENT2;

 

同一个文件中

/**
  Converts a device path into a file system map name.

  If DevPath is NULL, then ASSERT.

  This function looks through the shell environment map for a map whose device
  path matches the DevPath parameter.  If one is found the Name is returned via
  Name parameter.  If sucessful the caller must free the memory allocated for
  Name.

  This function will use the internal lock to prevent changes to the map during
  the lookup operation.

  @param[in] DevPath                The device path to search for a name for.
  @param[in] ConsistMapping         What state to verify map flag VAR_ID_CONSIST.
  @param[out] Name                  On sucessful return the name of that device path.

  @retval EFI_SUCCESS           The DevPath was found and the name returned
                                in Name.
  @retval EFI_OUT_OF_RESOURCES  A required memory allocation failed.
  @retval EFI_UNSUPPORTED       The DevPath was not found in the map.
**/
typedef
EFI_STATUS
(EFIAPI *SHELLENV_GET_FS_NAME) (
  IN EFI_DEVICE_PATH_PROTOCOL     * DevPath,
  IN BOOLEAN                      ConsistMapping,
  OUT CHAR16                      **Name
  );

 

根据上面的函数,编写程序如下

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

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

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

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

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

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

EFI_STATUS
EFIAPI
PerformSingleMappingDisplay(
  IN CONST EFI_HANDLE Handle
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevPath;
  EFI_DEVICE_PATH_PROTOCOL  *DevPathCopy;
  CHAR16                    *CurrentName;

  CurrentName = NULL;
  DevPath = DevicePathFromHandle(Handle);
  DevPathCopy = DevPath;
  mEfiShellEnvironment2->GetFsName(DevPathCopy,FALSE,&CurrentName);

  Print (L"%s \r\n", CurrentName);  
	
  if ((CurrentName) != NULL) { FreePool((CurrentName)); CurrentName = NULL; }

  return EFI_SUCCESS;
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                *HandleBuffer=NULL;
  UINTN                     BufferSize=0;
  UINTN                     LoopVar;
  BOOLEAN                   Found;
  //Copy from ShellLibConstructorWorker in \ShellPkg\Library\UefiShellLib\UefiShellLib.c
  //
  // UEFI 2.0 shell interfaces (used preferentially)
  //
  Status = gBS->OpenProtocol(
    gImageHandle,
    &gEfiShellProtocolGuid,
    (VOID **)&gEfiShellProtocol,
    gImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
   
  if (EFI_ERROR(Status)) {
    //
    // Search for the shell protocol
    //
    Status = gBS->LocateProtocol(
      &gEfiShellProtocolGuid,
      NULL,
      (VOID **)&gEfiShellProtocol
     );
    if (EFI_ERROR(Status)) {
      gEfiShellProtocol = NULL;
     }
  }
  
  //
  // Look up all SimpleFileSystems in the platform
  //
  Status = gBS->LocateHandle(
    ByProtocol,
    &gEfiSimpleFileSystemProtocolGuid,
    NULL,
    &BufferSize,
    HandleBuffer);
	
  if (Status == EFI_BUFFER_TOO_SMALL) {
		HandleBuffer = AllocateZeroPool(BufferSize);
		if (HandleBuffer == NULL) {
			return (SHELL_OUT_OF_RESOURCES);
		}
		Status = gBS->LocateHandle(
			ByProtocol,
			&gEfiSimpleFileSystemProtocolGuid,
			NULL,
			&BufferSize,
			HandleBuffer);
   }

  //
  // Get the map name(s) for each one.
  //
  for ( LoopVar = 0, Found = FALSE
      ; LoopVar < (BufferSize / sizeof(EFI_HANDLE)) && HandleBuffer != NULL
      ; LoopVar ++
     ) {
    Status = PerformSingleMappingDisplay(HandleBuffer[LoopVar]);
    if (!EFI_ERROR(Status)) {
      Found = TRUE;
    }
  }
  
  FreePool(HandleBuffer);
	
  return EFI_SUCCESS;
}

 

运行结果,和 Map命令的对比

ShowMap

完整的代码下载
ShowMap

实验 MPU-6050模块

在使用之前,需要特别注意的是:MPU-6050 本身只支持到 3.3V,如果你需要供电5V那么要特别确认一下你的板子是否有转换。我使用的下面这个模块上面带降压的,所以可以直接接5V.

T1OIRvXBXfXXXXXXXX_!!0-item_pic

另外,连接GND和VCC时一定要注意不能搞反了,否则转换芯片会急剧发热(推荐你第一次上电的时候留心这个芯片)。

首推极客工坊的文章【参考1】。但是可能是版本太老的缘故,在编译时(1.5.0)会出现core.a的错误。

随后查了一下,Arduino官方网站上有介绍和示例代码【参考2】:

// MPU-6050 Short Example Sketch
// By Arduino User JohnChi
// August 17, 2014
// Public Domain
#include<Wire.h>
const int MPU=0x68;  // I2C address of the MPU-6050
int16_t AcX,AcY,AcZ,Tmp,GyX,GyY,GyZ;
void setup(){
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Serial.begin(9600);
}
void loop(){
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU,14,true);  // request a total of 14 registers
  AcX=Wire.read()<<8|Wire.read();  // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)     
  AcY=Wire.read()<<8|Wire.read();  // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ=Wire.read()<<8|Wire.read();  // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp=Wire.read()<<8|Wire.read();  // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX=Wire.read()<<8|Wire.read();  // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY=Wire.read()<<8|Wire.read();  // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ=Wire.read()<<8|Wire.read();  // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)
  Serial.print("AcX = "); Serial.print(AcX);
  Serial.print(" | AcY = "); Serial.print(AcY);
  Serial.print(" | AcZ = "); Serial.print(AcZ);
  Serial.print(" | Tmp = "); Serial.print(Tmp/340.00+36.53);  //equation for temperature in degrees C from datasheet
  Serial.print(" | GyX = "); Serial.print(GyX);
  Serial.print(" | GyY = "); Serial.print(GyY);
  Serial.print(" | GyZ = "); Serial.println(GyZ);
  delay(333);
}

 

我使用的是 Arduino Pro Micro (这并非Arduino官方出品,是一种类似 Arduino Leonardo 的兼容产品) , 电路非常简单,使用面包板即可

6050

运行结果

6050r

取得值本身很简单,但是如果想解出正确的姿态还是蛮复杂的。

参考:

1.http://www.geek-workshop.com/thread-1017-1-1.html arduino学习笔记37 – Arduino Uno + MPU6050首例整合性6轴演示实验

2.http://playground.arduino.cc/Main/MPU-6050 MPU-6050 Accelerometer + Gyro

Step to UEFI (35) —– How to build Shell.efi

【特别提醒:下面的全部操作都是在UDK2014中完成,具体代码会与2010有差别】

第一个问题:我们运行的模拟环境(NT32)中的Shell是来自哪里?

回答:在 \Nt32Pkg\Nt32Pkg.fdf 中你可以看到下面的定义

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

INF FatBinPkg/EnhancedFatDxe/Fat.inf

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

如果你把上面的 FullShell.inf 替换成 EdkShellBinPkg\MinimumShell 下面的MinimumShell.inf 再次编译之后会发现使用的是Mini版本的Shell. 例如: Hexedit 这个命令只在Full版本中才有,Mini版本下不支持。(实验时候特别注意,如果你 Build了 MinimumShell.inf, 在 fsnt1: 下面有一个 Hexedit.efi)

2.如何重新Build Shell.efi?

根据 ShellBinPkg 目录下的 Readme.txt (没错,我也不知道为什么是在这个目录而不是ShellPkg下面的 ReadMe.txt) ,可以使用下面的命令进行Build:

build -a IA32 -p ShellPkg\ShellPkg.dsc -b RELEASE

3.实际操作。这是我们最常见到的Shell的模样,我下面要尝试给他添加一段String.

full

在 \ShellPkg\Application\Shell\Shell.c 可以看到输出版本信息的语句

    //
    // Display the version
    //
    if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion) {
      ShellPrintHiiEx (
        0,
        gST->ConOut->Mode->CursorRow,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_SHELL),
        ShellInfoObject.HiiHandle,
        SupportLevel[PcdGet8(PcdShellSupportLevel)],
        gEfiShellProtocol->MajorVersion,
        gEfiShellProtocol->MinorVersion
       );

      ShellPrintHiiEx (
        -1,
        -1,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_SUPPLIER),
        ShellInfoObject.HiiHandle,
        (CHAR16 *) PcdGetPtr (PcdShellSupplier)
       );

      ShellPrintHiiEx (
        -1,
        -1,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_UEFI),
        ShellInfoObject.HiiHandle,
        (gST->Hdr.Revision&0xffff0000)>>16,
        (gST->Hdr.Revision&0x0000ffff),
        gST->FirmwareVendor,
        gST->FirmwareRevision
       );
    }

    //
    // Display the version
    //
    if (!ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoVersion) {
      ShellPrintHiiEx (
        0,
        gST->ConOut->Mode->CursorRow,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_SHELL),
        ShellInfoObject.HiiHandle,
        SupportLevel[PcdGet8(PcdShellSupportLevel)],
        gEfiShellProtocol->MajorVersion,
        gEfiShellProtocol->MinorVersion
       );

      ShellPrintHiiEx (
        -1,
        -1,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_SUPPLIER),
        ShellInfoObject.HiiHandle,
        (CHAR16 *) PcdGetPtr (PcdShellSupplier)
       );

      ShellPrintHiiEx (
        -1,
        -1,
        NULL,
        STRING_TOKEN (STR_VER_OUTPUT_MAIN_UEFI),
        ShellInfoObject.HiiHandle,
        (gST->Hdr.Revision&0xffff0000)>>16,
        (gST->Hdr.Revision&0x0000ffff),
        gST->FirmwareVendor,
        gST->FirmwareRevision
       );
    }

	//LabZDebug_Start 加入我们定义的String
	ShellPrintHiiEx (
        -1,
        -1,
        NULL,
        STRING_TOKEN (STR_LABZ_UEFI),
        ShellInfoObject.HiiHandle
       );
	//LabZDebug_End

    //
    // Display the mapping
    //

    if (PcdGet8(PcdShellSupportLevel) >= 2 && !ShellInfoObject.ShellInitSettings.BitUnion.Bits.NoMap) {
      Status = RunCommand(L"map", NULL);
      ASSERT_EFI_ERROR(Status);
    }

同时在 \ShellPkg\Application\Shell\Shell.uni 加入我们自定义的字符串

#string STR_LABZ_UEFI		          #language en-US "www.lab-z.com 2014/12/12 build.....\r\n"

最后的结果如下,可以看到多出来一行我们自己定义的字符串:

modify

特别提醒:请注意文章中两张图片,实际上盘符是有差别的,一个是 FSNTx: 一个是 FSx。就是说在虚拟环境下模拟出来的盘符还是有差别的。目前我不清楚这个差别是如何导致的。

========================================================================
2015年3月31日补充

1.build shell.efi 之后生成的文件在 Build\Shell\RELEASE_MYTOOLS\IA32下面。同样他的子目录中也能找到Shell.efi (总共三个),他们内容相同

2.有一种情况是你替换了 Shell_Full.efi 之后,模拟器无法进入 shell,始终停留在Setup中。这种情况请检查输出的Log信息,我遇到的情况是因为改动有问题,导致 Shell.efi 是损坏的,无法正常Load起来。

Arduino Uno 的 Vin Pin

偶然注意到 Arduino Uno 上有个Vin Pin,但是AVR上没有对应的管脚,感觉比较奇怪,研究了一下。

ArduinoVin

原始图片来自【参考1】

这样的问题自然是要到电路图中查找答案。电路图和印刷版文件来自【参考2】

vin2

美中不足:虽然有PDF的电路图,但是不支持搜索,找了半天才在右上角找到

vin3

就是说Vin可以看作是DC输入的电压经过了一个二极管。因此,有两种情况:一是如果你插入了DC,那么Vin上会出现比DC稍微小一点的电压(因为经过了一个二极管);第二种情况,可以直接从这里灌进去一个电压。猜测这样的设计可能是为了某些有供电能力的Shield考虑吧。

关于这部分,有一些介绍【参考2】

External (non-USB) power can come either from an AC-to-DC adapter (wall-wart) or battery. The adapter can be connected by plugging a 2.1mm center-positive plug into the board’s power jack. Leads from a battery can be inserted in the Gnd and Vin pin headers of the POWER connector.

再查看 PCB 文件

vin31

对照 Vin 可以看到,它是接U1的 3 Pin的。

vin41

U1是 NCP1117ST50T3,简单翻翻手册【参考3】,这是一个降压的IC,Pin 3是输入

vin6

因此,Vin是一个可以直接对板子供电,电器特性功能和板子上DC插孔类似的输入脚。

参考:

1.http://arduino.cc/en/uploads/Main/ArduinoUno_R3_Front.jpg

2.http://arduino.cc/en/Main/ArduinoBoardUno

3.http://wenku.baidu.com/view/f66976af284ac850ad0242b9.html NCP1117

4.http://solderpad.com/solderpad/arduino-uno/ Bom List特别注意上面的一些名称和原本电路图有差别。

二维码对联

前一段看张鸣老师的书,上面提到对联是一种很重要的传统文化表现形式,尤其是春节时要四处张贴的春联。据说很多地区的大学生春节回家之后还要承担起为乡亲们题写春联的重任。母亲说当年他们插队时,逢年也会给老乡写春联。内容上通常是毛语录,比如:“金猴奋起千钧棒,玉宇澄清万里埃”或者“四海翻腾云水怒 五洲震荡风雷激”,一类的。

每年春节上街我都会觉得这是越来越重要的传统——至少从春联的价格上来看是这样。让我编点春联是没问题的,至于写的话因为现在提笔忘字以及本人的字都会被老婆笑话,还是免谈了。

作为一个专业的工程技术人员,灵机一动,想到了既然我只能编不能写可以用眼下最时髦的二维码技术来生成。有好奇心的人就会去扫一扫,然后就能看到了。具体做法如下:

首先,找个二维码生成软件。选择最高的误码率,简单的理解这个就是你能够覆盖生成二维码的比率。比如:25%的误码率意味着你可以覆盖画面上25%的区域仍然能够正常扫出。

错误修正容量

L水平 7%的字码可被修正

M水平 15%的字码可被修正

Q水平 25%的字码可被修正

H水平 30%的字码可被修正

【参考 1】

image001

【参考1】

我随便选了一个软件,二维码大师,最高误码率30%,然后选择最高分辨率。文本内容中输入上联“壹零做法神通广大不服来扫我”,输入过程中就自动生成二维码了,具体在下面三个地方设置和输入。

image002

选择保存为一个BMP文件。比如上面“壹零做法神通广大不服来扫我”,生成的结果就是下面的图片。

image003

然后修改这个图片即可,我最喜欢用的就是Windows的画笔工具。

image004

修改之后结果

image005

特别的,修改之后一定要用刚才的软件校验一下,避免做完了根本无法识别的状况。

image006

同样做法,再做一下下联“纵横成形网挂众多若信任联”。

image007
上面这个对联是我父亲拟的,横批就是“二维码”。

最后多说两句,大多数情况下人们只是注意上下问对仗是否工整而不大在意内容本身,好比,有女生给班主任写条子,”老师,我怀了男朋友得孩子”。老师看到之后大吃一惊“唉,现在的学生啊,真是的!都上初中了,怎么还分不清楚‘的地得’的用法?”。我随口拟了一个联儿,念给父亲听,他听了片刻说“对的比较工整,这是一个传统的联吧?”

image008

image009

参考:

1. http://zh.wikipedia.org/wiki/QR%E7%A2%BC QR码

Windows 7 64位下arduino驱动安装失败解决办法

最近入手一块 Arduino Micro Pro ,插在台式机上之后要求安装驱动,指定了 Arduino 目录下的 Drivers之后还出现了 Inf 段落无效的字样。忽然想起来我的 Win7 是Ghost版本,于是上网搜索解决办法。

在 http://blog.csdn.net/u013926582/article/details/24442583 和 http://www.arduino.cn/thread-2485-1-1.html 都有提到。

最后使用的方法是 查看 C:\Windows\inf\setupapi.dev.log (要从下向上搜索,最近安装的错误在文件尾)

发现下面的字样

dvi: {DIF_INSTALLDEVICEFILES} 17:08:34.464
dvi: Class installer: Enter 17:08:34.465
dvi: Class installer: Exit
dvi: Default installer: Enter 17:08:34.465
dvi: {Install FILES}mdmcpq.inf
inf: Opened PNF: ‘c:\windows\system32\driverstore\filerepository\arduino.inf_amd64_neutral_844213a156728dfe\arduino.inf’ ([strings])
inf: Opened PNF: ‘C:\Windows\INF\mdmcpq.inf’ ([strings])
inf: Opened PNF: ‘C:\Windows\INF\usb.inf’ ([strings.0804])
inf: {Install Inf Section [DriverInstall]}
inf: CopyFiles=FakeModemCopyFileSection (arduino.inf line 128)
cpy: Open PnpLockdownPolicy: Err=2. This is OK. Use LockDownPolicyDefault
flq: QueueSingleCopy…
flq: Inf : ‘c:\windows\system32\driverstore\filerepository\arduino.inf_amd64_neutral_844213a156728dfe\arduino.inf’
! flq: Missing SourceDisksFiles/SourceDisksNames information from INF.
! flq: Default INBOX source locations pulled from pre-built DrvIndex
flq: SourceRootPath: ‘C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc’
flq: {FILE_QUEUE_COPY}
flq: CopyStyle – 0x00002000
flq: {FILE_QUEUE_COPY}
flq: CopyStyle – 0x00002000
flq: SourceRootPath – ‘C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc’
flq: SourcePath – ‘\’
flq: SourceFilename – ‘usbser.sys’
flq: TargetDirectory- ‘C:\Windows\system32\DRIVERS’
flq: TargetFilename – ‘usbser.sys’
flq: {FILE_QUEUE_COPY exit(0x00000000)}
flq: {FILE_QUEUE_COPY exit(0x00000000)}
inf: {Install Inf Section [DriverInstall] exit (0x00000000)}
dvi: Processing co-installer registration section [DriverInstall.CoInstallers].
inf: {Install Inf Section [DriverInstall.CoInstallers]}
inf: {Install Inf Section [DriverInstall.CoInstallers] exit (0x00000000)}
dvi: Co-installers registered.
dvi: {Install INTERFACES}
dvi: Installing section [DriverInstall.Interfaces]
dvi: {Install INTERFACES exit 00000000}
dvi: {Install FILES exit (0x00000000)}
dvi: Default installer: Exit
dvi: {DIF_INSTALLDEVICEFILES – exit(0x00000000)} 17:08:34.479
ndv: Pruning file queue…
dvi: {_SCAN_FILE_QUEUE}
flq: ScanQ flags=620
flq: SPQ_SCAN_PRUNE_COPY_QUEUE
flq: SPQ_SCAN_FILE_COMPARISON
flq: SPQ_SCAN_ACTIVATE_DRP
flq: ScanQ number of copy nodes=1
! sig: GetNameSDInfo
! sig: Error 0: The operation completed successfully.
flq: ScanQ action=200 DoPruning=32
flq: ScanQ end Validity flags=620 CopyNodes=1
dvi: {_SCAN_FILE_QUEUE exit(0, 0x00000000)}
ndv: Committing file queue…
flq: {_commit_file_queue}
flq: CommitQ DelNodes=0 RenNodes=0 CopyNodes=1
flq: {SPFILENOTIFY_STARTQUEUE}
flq: {SPFILENOTIFY_STARTQUEUE – exit(0x00000001)}
flq: {_commit_copy_subqueue}
flq: subqueue count=1
flq: {SPFILENOTIFY_STARTSUBQUEUE}
flq: {SPFILENOTIFY_STARTSUBQUEUE – exit(0x00000001)}
flq: source media:
flq: SourcePath – [C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc]
flq: SourceFile – [usbser.sys]
flq: Flags – 0x00000000
flq: {SPFQNOTIFY_NEEDMEDIA}
flq: {SPFILENOTIFY_NEEDMEDIA}
flq: {SPFILENOTIFY_NEEDMEDIA – exit(0x00000000)}
flq: {SPFQNOTIFY_NEEDMEDIA – returned 0x00000000}
!!! flq: source media: SPFQOPERATION_ABORT.
!!! flq: Error 2: The system cannot find the file specified.
flq: {_commit_copy_subqueue exit(0x00000002)}
!!! flq: FileQueueCommit aborting!
!!! flq: Error 2: The system cannot find the file specified.
flq: {SPFILENOTIFY_ENDQUEUE}
flq: {SPFILENOTIFY_ENDQUEUE – exit(0x00000001)}
flq: {_commit_file_queue exit(0x00000002)}
ndv: Device install status=0x00000002
ndv: Performing device install final cleanup…
! ndv: Queueing up error report since device installation failed…
ndv: {Core Device Install – exit(0x00000002)} 17:08:34.493
dvi: {DIF_DESTROYPRIVATEDATA} 17:08:34.494
dvi: Class installer: Enter 17:08:34.494
dvi: Class installer: Exit

错误是说找不到 C:\Windows\System32\DriverStore\FileRepository\mdmcpq.inf_amd64_neutral_b53453733bd795bc(特别注意,不同系统,这个名字会不同,需要根据你当前系统中的错误来确定) 我进去 C:\Windows\System32\DriverStore\FileRepository\ 一看确实没有。

又在网上搜索了一下,在 http://www.arduino.cn/thread-2350-1-1.html 这里给出来两个64位的。

下载之后,首先将 UsbSer.sys 放到 Windows\System32\Drivers 下面,然后在 C:\Windows\System32\DriverStore\FileRepository\ 下面创建一个 mdmcpq.inf_amd64_neutral_b53453733bd795bc 目录把mdmcpq.zip中的全部内容放进去。

卸载之前安装的驱动,然后重新安装一次即可。

usbser

mdmcpq.inf

如果有遇到同样问题的朋友可以试试。

===========================================================================
2月16日 如果始终无法完成安装,建议检查一下你是否将设备插在 USB 3.0 的Port上。某些USB 3.0的 Controller 对于一些低速设备存在兼容性问题。如果是的话,请更换到 USB 2.0 的端口上再进行上述实验。

4月4日 在FileRepository 目录下创建目录的过程中,可能遇到“需要权限来执行此操作”的问题。解决方法是:

1.确保你的杀毒软件关闭了(比如,瑞星之类的你可以考虑直接卸载)
2.在FileRepository 目录上点鼠标右键,弹出的菜单上切换到安全页面。然后在上面的组或用户名栏目上检查是否有你当前账户。比如:开机登录的用户名是 player ,而这里没有。那么按下编辑键,在新弹出页面上用添加按钮,然后输入 player ,按下检查名称按钮就添加进去了。特别注意:其中的 SYSTEM 用户名,并非 Administrator,如果你是 Administrator 登录进来,那么需要手工添加一下。

Arduino 显示奶瓶温度

有个网友提出一个需求“做一个能够显示温度的奶瓶。奶瓶泡好奶后放在底座上,可直接给婴儿喝的绿灯亮,过烫红灯亮,过凉蓝灯亮。”这是他的一个作业,然后我就帮着做了一下。整体思路就是直接使用上一次做的那个红外温度计的方案,我想这样的方案恐怕在网上都是独一无二的吧。

使用到的基本元件有:Arduino Uno + LED + 红外温度传感器(上面有一个激光头用于瞄准)

使用红外温度传感器能够获得下列优点:

1.无需接触,不必考虑卫生的问题
2.准确度在可以接受的范围内
3.速度快,对准之后马上显示
4.兼容性好,各种型号的奶瓶都可以

缺点:

1.贵
2.有人说无法精确得知瓶中奶的温度,测量的是奶瓶温度而已

milkb2

milkb1

#include <Arduino.h>
#include <Wire.h>
#include "TN901.h"  
TN901 tn;          
int RedLed=10;  //Red Led Pin10
int GreenLed=7;  //Green Led Pin10
int BigRedLed=4;  //Big Led Pin4
void setup()
{
    Serial.begin(9600);
    tn.Init(13,12,11);  //初 始 化 data clk ack
    pinMode(RedLed,OUTPUT);
    digitalWrite(RedLed,LOW);
    pinMode(GreenLed,OUTPUT);
    digitalWrite(GreenLed,LOW);    
    pinMode(BigRedLed,OUTPUT);
    digitalWrite(BigRedLed,LOW);      
}
void loop()
{
   tn.Read();
   SerialValue();
   delay(2000);
}

void SerialValue()
{
   String s;
   s=">O"+String(tn.OT, DEC)+"E"+String(tn.ET,DEC)+"<";
   Serial.println(s);
   if (tn.OT>5000) { // Temp too high
         digitalWrite(RedLed,HIGH);
         digitalWrite(GreenLed,LOW);
         digitalWrite(BigRedLed,HIGH);        
   }
   else {
     if (tn.OT>2100) {// Temp is OK
         digitalWrite(RedLed,LOW);
         digitalWrite(GreenLed,HIGH);
         digitalWrite(BigRedLed,HIGH);        
         }
    else { //Temp is too Low      
         digitalWrite(RedLed,HIGH);
         digitalWrite(GreenLed,HIGH);
         digitalWrite(BigRedLed,LOW);
     
    }
   }  
}

 

设计上我们使用了2个LED,一个是小LED双色红色和绿色,一个是单独的大个红色的(手边只有这两种LED,更好的设计应该换个颜色的LED便于区分)。温度传感器是红外温度传感器。

温度区间:

>50 小LED的红色亮起 表示温度过高

50-20 小LED的绿色亮起 表示温度适合

<20 大红色LED亮起 表示温度过低 工作时的视频 首先展示一下电路,然后我们有一瓶大瓶冷水;放在上面的时候温度过低,大红色 LED亮起;然后我们换成奶瓶,其中是热水,温度过高,小红色LED亮起;然后我们将大瓶子的水兑到小瓶子中,温度会变得适中,于是小LED绿色会亮起。

这个方案对于作业来说应该是足够了。就是这样。

Step to UEFI (34) —– FindFile2 查找特定文件

前面介绍了如何枚举全部文件,这里介绍一下如何枚举特定的问题。比如,用 “M*.*” 匹配全部 M开头的文件。

参考 touch 命令的Source Code 很快有了方案,使用:ShellOpenFileMetaArg

对应的头文件在 \ShellPkg\Include\Library\ShellLib.h

/**
  Opens a group of files based on a path.

  This function uses the Arg to open all the matching files. Each matched
  file has a SHELL_FILE_ARG structure to record the file information. These
  structures are placed on the list ListHead. Users can get the SHELL_FILE_ARG
  structures from ListHead to access each file. This function supports wildcards
  and will process '?' and '*' as such.  The list must be freed with a call to
  ShellCloseFileMetaArg().

  If you are NOT appending to an existing list *ListHead must be NULL.  If
  *ListHead is NULL then it must be callee freed.

  @param[in] Arg                 The pointer to path string.
  @param[in] OpenMode            Mode to open files with.
  @param[in, out] ListHead       Head of linked list of results.

  @retval EFI_SUCCESS           The operation was sucessful and the list head
                                contains the list of opened files.
  @retval != EFI_SUCCESS        The operation failed.

  @sa InternalShellConvertFileListType
**/
EFI_STATUS
EFIAPI
ShellOpenFileMetaArg (
  IN CHAR16                     *Arg,
  IN UINT64                     OpenMode,
  IN OUT EFI_SHELL_FILE_INFO    **ListHead
  );

 

从介绍上来看,最主要的参数有2个:Arg输入要查找的路径,可以使用通配符。ListHead 返回结果。结果实际上是两部分一部分是 SHELL_FILE_ARG 结构体,另外一部分是 EFI_SHELL_FILE_INFO 结构体。前者只有一个,后者有一个或者很多个。他们使用链表结构联系在一起。上面这样的结构体感觉上很奇怪,不过确实是这样的。可以在Touch的 Source Code中看到。他使用 GetFirstNode 来跳过第一个不需要的结构体。这个函数可以在 \MdePkg\Library\BaseLib\LinkedList.c 里面看到

/**
  Retrieves the first node of a doubly-linked list.

  Returns the first node of a doubly-linked list.  List must have been 
  initialized with INTIALIZE_LIST_HEAD_VARIABLE() or InitializeListHead().
  If List is empty, then List is returned.

  If List is NULL, then ASSERT().
  If List was not initialized with INTIALIZE_LIST_HEAD_VARIABLE() or 
  InitializeListHead(), then ASSERT().
  If PcdMaximumLinkedListLenth is not zero, and the number of nodes
  in List, including the List node, is greater than or equal to
  PcdMaximumLinkedListLength, then ASSERT().

  @param  List  A pointer to the head node of a doubly-linked list.

  @return The first node of a doubly-linked list.
  @retval NULL  The list is empty.

**/
LIST_ENTRY *
EFIAPI
GetFirstNode (
  IN      CONST LIST_ENTRY          *List
  )
{
  //
  // ASSERT List not too long
  //
  ASSERT (InternalBaseLibIsNodeInList (List, List, FALSE));

  return List->ForwardLink;
}

 

最终编写程序如下

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

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

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

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

extern EFI_SHELL_PROTOCOL        *gEfiShellProtocol;

void PrintShellFileInfo(EFI_SHELL_FILE_INFO     *ShellFileInfo)
{
  //Print(L"Status [%d]\n",ShellFileInfo-> Status);
  Print(L"FullName [%s]\n",ShellFileInfo-> FullName);
  //Print(L"FileName [%s]\n",ShellFileInfo-> FileName);
  //Print(L"Handle [%d]\n",ShellFileInfo->Handle);  
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  //EFI_FILE_HANDLE   DirHandle;
  RETURN_STATUS     Status;
  EFI_SHELL_FILE_INFO *FileList=NULL;
  EFI_SHELL_FILE_INFO *Node;
  
  Status = ShellOpenFileMetaArg(L"fsnt0:\\a*.*", EFI_FILE_MODE_READ, &FileList);
  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
  }							   
  
  //Print(L"Signature [%X]\n",((SHELL_FILE_ARG *)&FileList)->Signature);  
  //Print(L"Status []\n",((SHELL_FILE_ARG *)&FileList)->Status);  
  //Print(L"ParentName [%s]\n",((SHELL_FILE_ARG *)&FileList)->ParentName);  
  //Print(L"FullName [%s]\n",((SHELL_FILE_ARG *)&FileList)->FullName);  
  //Print(L"FileName [%s]\n",((SHELL_FILE_ARG *)&FileList)->FileName);  

  
  // check that we have at least 1 file
  //
  if (FileList == NULL || IsListEmpty(&FileList->Link)) {
                Print(L"No Files Found!\n");
  } else {
		//
		// loop through the list and make sure we are not aborting...
		//
		for ( Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&FileList->Link)
			; !IsNull(&FileList->Link, &Node->Link) && !ShellGetExecutionBreakFlag()
			; Node = (EFI_SHELL_FILE_INFO*)GetNextNode(&FileList->Link, &Node->Link)){
					PrintShellFileInfo(Node);			   
				//
				// make sure the file opened ok
				//
				if (EFI_ERROR(Node->Status)){
					Print(L"OpenFile Error!\n");
				}

        }
    }
		  
   //
   // Free the fileList
   //
   if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
       Status = ShellCloseFileMetaArg(&FileList);
   }
   FileList = NULL;
		
  return EFI_SUCCESS;
}

 

运行结果

FindFIle2

可以看出能够正常查找到我们需要的 fsnt0:x下面a开头的文件。

代码下载

FindFile2

===============================================================================
最后有一个问题:
前面说过,ShellOpenFileMetaArg 输出的结果是2部分组成的,我的程序只是输出了后面的那个结构体,那么前面的那个结构体呢?

  Status = ShellOpenFileMetaArg(L"fsnt0:\\a*.*", EFI_FILE_MODE_READ, &FileList);
  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
  }							   
  
  Print(L"Signature [%X]\n",((SHELL_FILE_ARG *)&FileList)->Signature);  
  Print(L"Status []\n",((SHELL_FILE_ARG *)&FileList)->Status);  
  Print(L"ParentName [%s]\n",((SHELL_FILE_ARG *)&FileList)->ParentName);  
  Print(L"FullName [%s]\n",((SHELL_FILE_ARG *)&FileList)->FullName);  
  Print(L"FileName [%s]\n",((SHELL_FILE_ARG *)&FileList)->FileName); 

 

结果出来之后非常奇怪的是 Signature 的输出并非固定的数值,而根据 EfiShellEnvironment2.h 来看,这个似乎应该是一个固定的值。

不知道是我理解有偏差还是输出方法不对。有懂的朋友请指教一下。谢谢!