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()

Step to UEFI (103)Protocol 的私有数据

阅读《UEFI原理与编程》,第八章,开发UEFI服务。其中提到了 Protocol的私有数据。
之前我们介绍过 EFI_LOADED_IMAGE_PROTOCOL,在【参考1】的程序中,就有涉及到LOADED_IMAGE_PRIVATE_DATA,简单的说,定义的 PROTOCOL是这个结构体的一部分,就能够找到整个LOADED_IMAGE_PRIVATE_DATA的结构体,从而获得一些额外的信息。
总结一下,这样的私有数据是这样定义的:

#define PROTOCOLNAME_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')
typedef struct {
  UINTN     Signature;
  UINTN	    Var1;
  PROTOCOLNAME _PROTOCOL   PROTOCOLNAME;           
} PROTOCOLNAME_PRIVATE_DATA;

#define PROTOCOLNAME _PRIVATE_DATA_FROM_THIS(a) \
     CR(a, PROTOCOLNAME_PRIVATE_DATA, PROTOCOLNAME, PROTOCOLNAME _PRIVATE_DATA_SIGNATURE)

 

在初始化的时候,要创建一个实际的PROTOCOLNAME_PRIVATE_DATA,然后初始化需要的变量,最后像其他的Protocol安装一样,将PROTOCOLNAME_PRIVATE_DATA. PROTOCOLNAME 安装到合适的Handle上即可。
编写代码测试一下,基于之前我们写的 PrintDriver 代码,先修改 Print9.h。加入了下面的定义:

#define PRINT9_PRIVATE_DATA_SIGNATURE   SIGNATURE_32('p','r','t','9')

typedef struct {
  UINTN     Signature;
  UINTN		Var1;
  /// loaded PROTOCOLNAME
  EFI_PRINT9_PROTOCOL   PRINT9;           
} EFI_PRINT9_PRIVATE_DATA;

#define EFI_PRINT9_PRIVATE_DATA_FROM_THIS(a) \
          CR(a, EFI_PRINT9_PRIVATE_DATA, PRINT9, PRINT9_PRIVATE_DATA_SIGNATURE)

 

之后修改print.c。 这个 driver实现的功能很简单,每次调用UnicodeSPrint 函数的时候,会自动显示 EFI_PRINT9_PRIVATE_DATA 中的 Var1,并且增加1.

#include <PiDxe.h>
#include  <Library/UefiLib.h>
#include "Print9.h"
#include <Library/PrintLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/MemoryAllocationLib.h>
EFI_PRINT9_PRIVATE_DATA  *Image;
EFI_HANDLE  mPrintThunkHandle = NULL;
extern EFI_SYSTEM_TABLE			 *gST;

//Copied from \MdeModulePkg\Library\DxePrintLibPrint2Protocol\PrintLib.c
UINTN
EFIAPI
MyUnicodeSPrint (
  OUT CHAR16        *StartOfBuffer,
  IN  UINTN         BufferSize,
  IN  CONST CHAR16  *FormatString,
  ...
  )
{
  VA_LIST Marker;
  UINTN   NumberOfPrinted=1;
  CHAR16  *Buffer=L"12345678";
  
  VA_START (Marker, FormatString);
  //NumberOfPrinted = UnicodeVSPrint (StartOfBuffer, BufferSize, FormatString, Marker);
  VA_END (Marker);
  
  UnicodeSPrint(Buffer,8,L"%d",Image->Var1);
  gST->ConOut->OutputString(gST->ConOut,Buffer); 
  Image->Var1++;
  return NumberOfPrinted;
}

/**
  The user Entry Point for Print module.

  This is the entry point for Print DXE Driver. It installs the Print2 Protocol.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval Others            Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
PrintEntryPoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
	EFI_STATUS  Status=EFI_SUCCESS;

	//
	// Allocate a new image structure
	//
	Image = AllocateZeroPool (sizeof(EFI_PRINT9_PRIVATE_DATA));
	if (Image == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		goto Done;
	}	
	
    Image->Signature         = PRINT9_PRIVATE_DATA_SIGNATURE;
  
	Image->PRINT9.UnicodeBSPrint=UnicodeBSPrint;
	Image->PRINT9.UnicodeSPrint=MyUnicodeSPrint;
  	Image->PRINT9.UnicodeBSPrintAsciiFormat=UnicodeBSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeSPrintAsciiFormat=UnicodeSPrintAsciiFormat;	
  	Image->PRINT9.UnicodeValueToString=UnicodeValueToString;	
  	Image->PRINT9.AsciiBSPrint=AsciiBSPrint;	
  	Image->PRINT9.AsciiSPrint=AsciiSPrint;	
  	Image->PRINT9.AsciiBSPrintUnicodeFormat=AsciiBSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiSPrintUnicodeFormat=AsciiSPrintUnicodeFormat;	
  	Image->PRINT9.AsciiValueToString=AsciiValueToString;	
	
	Status = gBS->InstallMultipleProtocolInterfaces (
                  &mPrintThunkHandle,
                  &gEfiPrint9ProtocolGuid, 
				  &Image->PRINT9,
                  NULL
                  );
    ASSERT_EFI_ERROR (Status);

Done:

  return Status;
}

 

测试这个 Protocol 使用的还是之前的 pdt.efi,运行结果如下:

stu103

完整的代码下载

printdriver3

参考:
1. Step to UEFI (48) —– 被加载程序的ENTRYPOINT

Arduino打造 USB转蓝牙鼠标的装置

去年的这个时候,我做了一个 USB键盘转蓝牙的装置【参考1】,有很多朋友根据我的方法成功制作出自己的转接装置。本文将介绍如何用Arduino打造一个USB鼠标转蓝牙的装置。

从原理上来说,Arduino 通过 USB Host Shield 驱动 USB 鼠标,将其切换为 Boot Protocol 模式,该模式下鼠标会用固定的格式和Arduino通讯,这样避免了不同设备需要单独解析 HID Protocol的问题 。之后,Arduino将解析出来的鼠标动作通过串口,以规定的格式告知蓝牙模块,最后在手机或者电脑端即可以蓝牙鼠标的方式进行动作。

usbtobluetooth

使用的硬件如下

Arduino Uno  1块
USB Host Shield

 

1块
带面包板的Shield板

 

1块
XM-04-HID-M 蓝牙HID鼠标模块 1块
18650电池(选配)

 

2节
18650 电池盒(选配) 1个

 

最主要的配件如下

 

image004

和之前的键盘转接装置相比,最大的不同在于本文代码使用了USB Host Shield 2.0的库,大量的底层操作都被封装起来,编程上非常简单,完全可以专注于“做什么”而不是“如何做”,这也是 Arduino的魅力所在。

代码需要用到 USB Host Shield2.0这个库,安装的方法很简单,打开 Sketch->Include Library->Manage Libraries 调出 Library Manager,直接搜索 “USB Host”字样,然后点击Install即可。

(我们需要安装的是 USB Host Shield Library 2.0,这是用起来驱动 USB Host Shield的。另外那个 USBHost是给Due 用的)

image003

USB Host 解析出来的鼠标数据格式如下:

struct MOUSEINFO {

 

struct {

uint8_t bmLeftButton : 1;        //鼠标左键按下标记

uint8_t bmRightButton : 1;     //鼠标右键按下标记

uint8_t bmMiddleButton : 1; //鼠标中键按下标记

uint8_t bmDummy : 5;            //保留

};

int8_t dX;         //水平方向移动偏移

int8_t dY;         //垂直方向移动偏移

};

 

Arduino和蓝牙模块是通过串口进行通讯的,通讯报文长为8字节,每个字节分别如下【参考3】:

BYTE1 0x08 固定值(包长度)
BYTE2 0x00 固定值
BYTE3 0xA1 固定值
BYTE4 0x02 固定值
BYTE5 Button 1/2/3
BYTE6 X-Axis(-127~127)
BYTE7 Y-Axis(-127~127)
BYTE8 Whell (-127~+127)

 

我们需要做的只是将USB HOST 解析出来的MOUSEINFO转发给串口模块即可。

#include <hidboot.h>

#include <SPI.h>

 

#define BIT0  1

#define BIT1  2

#define BIT2  4

 

class MouseRptParser : public MouseReportParser

{

protected:

         void OnMouseMove     (MOUSEINFO *mi);

         void OnLeftButtonUp   (MOUSEINFO *mi);

         void OnLeftButtonDown      (MOUSEINFO *mi);

         void OnRightButtonUp (MOUSEINFO *mi);

         void OnRightButtonDown    (MOUSEINFO *mi);

         void OnMiddleButtonUp      (MOUSEINFO *mi);

         void OnMiddleButtonDown         (MOUSEINFO *mi);

};

 

void SendToBT(MOUSEINFO *mi)

{

     byte  Button=0;

    

     if (mi->bmLeftButton)

       Button |= BIT0;

     else

       Button & !BIT0;

      

      if (mi->bmRightButton)

       Button |= BIT1;

     else

       Button & !BIT1;

      

      if (mi->bmMiddleButton)

       Button |= BIT2;

     else

       Button & !BIT2;

    

     /*

     Serial.println("L Mouse Move");

     Serial.print("dx=");

     Serial.print(mi->dX, DEC);

     Serial.print(" dy=");

     Serial.println(mi->dY, DEC);

     Serial.println(Button,DEC);

     */

   

   

     Serial.write(0x08);  //BYTE1    

     Serial.write(0x00);  //BYTE2

     Serial.write(0xA1);  //BYTE3

     Serial.write(0x02);  //BYTE4

     Serial.write(Button);  //BYTE5        

     Serial.write(mi->dX);  //BYTE6        

     Serial.write(mi->dY);  //BYTE7

     Serial.write(0);  //BYTE8          

}

 

void MouseRptParser::OnMouseMove(MOUSEINFO *mi)

{

    SendToBT(mi);

};

void MouseRptParser::OnLeftButtonUp   (MOUSEINFO *mi)

{

    //Serial.println("L Butt Up");

    SendToBT(mi);   

};

void MouseRptParser::OnLeftButtonDown       (MOUSEINFO *mi)

{

    //Serial.println("L Butt Dn");

    SendToBT(mi);   

};

void MouseRptParser::OnRightButtonUp (MOUSEINFO *mi)

{

    //Serial.println("R Butt Up");

    SendToBT(mi);   

};

void MouseRptParser::OnRightButtonDown    (MOUSEINFO *mi)

{

    //Serial.println("R Butt Dn");

    SendToBT(mi);

};

void MouseRptParser::OnMiddleButtonUp      (MOUSEINFO *mi)

{

    //Serial.println("M Butt Up");

    SendToBT(mi);

};

void MouseRptParser::OnMiddleButtonDown          (MOUSEINFO *mi)

{

    //Serial.println("M Butt Dn");

    SendToBT(mi);   

};

 

USB     Usb;

 

HIDBoot<HID_PROTOCOL_MOUSE>    HidMouse(&Usb);

 

uint32_t next_time;

 

MouseRptParser                               Prs;

 

void setup()

{

    Serial.begin( 115200 );

    Serial.println("Start");

 

    if (Usb.Init() == -1)

        Serial.println("OSC did not start.");

 

    delay( 200 );

 

    next_time = millis() + 5000;

 

    HidMouse.SetReportParser(0,(HIDReportParser*)&Prs);

}

 

void loop()

{

  Usb.Task();

}

 

 

最终的成品,Arduino和其他Shield堆叠起来,使用电池独立供电

image002

特别注意的地方:

  1. 如果使用USB 供电,Arduino可能会遇到供电不足的问题(外围有USB Host Shield / USB Mouse / Bluetooth),解决办法是直接从圆形 DC 口接入电池,这就是本文使用18650电池的原因;
  2. 本文使用的蓝牙模块并非普通的串口蓝牙(HC05/06),而是专门的蓝牙鼠标模块。之前制作USB键盘转接设备的时候,很多朋友没有注意,购买的是 HC05/06这样的蓝牙串口模块,最后只得重新购买。关于蓝牙鼠标模块的更多信息可以在之前的介绍中看到【参考2】;
  3. 蓝牙 HID 模块默认波特率只有是 9600,在操作时有明显卡顿,需要对其下 AT 命令,将波特率升到 115200。

 

参考:

  1. http://www.arduino.cn/thread-17412-1-1.html U2B: USB键盘转蓝牙键盘的设备
  2. http://www.arduino.cn/thread-22076-1-1.html 介绍一个蓝牙鼠标模块
  3. XM-04-HID-M 蓝牙HID鼠标模块规格书0

WiDo 实现HTTP Server 的例子

最近在研究如何搭建一个 WIFI HTTP Server ,本打算使用ESP8266,但是购买了板子才发现资料比较少,联系卖家也没有结果。后来只得入手了一块 CC3000 的 WIFI 板子。这个芯片是 Arduino IDE 原生自带的,所以资料方面不是问题。入手的是 DFRobot 出品的 WiDo,主控芯片是 32U4,这意味着有足够的串口可供Debug之类使用, 更具体的说:主控芯片用 SPI 和 WIFI芯片CC3000打交道,然后多出来一个串口,USB上还有一个串口。

image001

根据【参考1】编写一个简单的程序,控制板子上 Pin 13 上面的LED亮灭。【参考1】的程序会向浏览器端发送一个 HTTP 的页面,上面有2个按钮,能够通过按下按钮控制亮灭。经过我的测试发现浏览器打卡会很慢,很多时候会卡死,并且打开之后页面是错乱的。我猜测原因可能是HTTP代码并不标准,有兼容性问题(我用的Chrome),另外用浏览器访问有可能会负载过高,比如,我偶然发现浏览器打开网站之后还有 GET favorite.ico 的动作。因此,我对程序做了一下简单的修改。修改之后HTTP页面只是简单显示一行字符。使用 http://ip/open 打开 LED,http://ip/close关闭LED。

1.直接访问 http://192.168.0.103 可以看到下面的信息

image002

具体的控制可以用 http://192.168.1.103/open 点亮,http://192.168.1.103/close 熄灭。
image003

2.此外,还可以用 curl 工具在控制台进行操作,和方法1 相比,这样的更简单可靠,避免浏览器“暗箱操作”

image004

#include <Adafruit_CC3000.h>
#include <SPI.h>
#include "utility/debug.h"
#include "utility/socket.h"

// These are the interrupt and control pins
#define ADAFRUIT_CC3000_IRQ   7  // MUST be an interrupt pin!
// These can be any two pins
#define ADAFRUIT_CC3000_VBAT  5
#define ADAFRUIT_CC3000_CS    10
// Use hardware SPI for the remaining pins
// On an UNO, SCK = 13, MISO = 12, and MOSI = 11
Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT,
SPI_CLOCK_DIVIDER); // you can change this clock speed


#define WLAN_SSID       "ChinaNet-73"           //这里填写你的 WIFI  名称
#define WLAN_PASS       "adminp1988"            //这里填写你的 WIFI 密码  
// Security can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2
#define WLAN_SECURITY   WLAN_SEC_WPA2

#define LISTEN_PORT           80   // What TCP port to listen on for connections.

Adafruit_CC3000_Server webServer(LISTEN_PORT);
boolean led_state;
//
void setup(void) {
        //简单起见,我们只用板载的 13pin 上的 LED 演示
        pinMode (13, OUTPUT);
		//默认是灭的
        digitalWrite (13, LOW);
        
		//使用串口输出Debug信息
        Serial.begin(115200);
        Serial.println(F("Hello, CC3000!\n")); 
        //while (!Serial);
        //Serial.println ("Input any key to start:");
        //while (!Serial.available ());
		//输出当前可用内存
        Serial.print("Free RAM: "); 
        Serial.println(getFreeRam(), DEC);

        /* Initialise the module */
        Serial.println(F("\nInitializing..."));
        if (!cc3000.begin()) {
                Serial.println(F("Couldn't begin()! Check your wiring?"));
                while(1);
        }

        Serial.print(F("\nAttempting to connect to ")); 
        Serial.println(WLAN_SSID);
        if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) {
                Serial.println(F("Failed!"));
                while(1);
        }

        Serial.println(F("Connected!"));

		//使用 DHCP 分配的IP 
        Serial.println(F("Request DHCP"));
        while (!cc3000.checkDHCP()) {
                delay(100); // ToDo: Insert a DHCP timeout!
        }  
		
        //显示当前的IP信息
        /* Display the IP address DNS, Gateway, etc. */
        while (! displayConnectionDetails()) {
                delay(1000);
        }

        // Start listening for connections
        webServer.begin();
        Serial.println(F("Listening for connections..."));
}

//
void loop(void) {
        // Try to get a client which is connected.
        Adafruit_CC3000_ClientRef client = webServer.available();
        if (client) {
		        //处理输入的 GET 信息,对于 GET 方法来说,Url中既有传递的信息
                processInput (client);
				//对发送 HTTP 请求的浏览器发送HTTP代码
                sendWebPage (client);
        }
        client.close();
}

//分析收到的 GET 方法的参数
void processInput (Adafruit_CC3000_ClientRef client) {
        char databuffer[45];
      //安全起见,保证截断
       databuffer[44]=’\0’;
        while (client.available ()) {
                client.read (databuffer, 40);
	
	  //下面这个代码是查找PC端发送的数据中的换行,以此作为字符串的结尾
                char* sub = strchr (databuffer, '\r');
                if (sub > 0)
                        *sub = '\0';
                Serial.println (databuffer);
				
                //下面是解析 GET 方法提供的参数
	    //如果是 open 命令,那么点亮 LED
                if (strstr (databuffer, "open") != 0) {
                        Serial.println (F("clicked open"));
                        digitalWrite (13, HIGH); 
                        led_state = true;
                } 
	     //如果是 close 命令,那么熄灭 LED 
                else if (strstr (databuffer, "close") != 0) {
                        Serial.println (F("clicked close"));
                        digitalWrite (13, LOW);
                        led_state = false;
                }
                break;
        }
}

void sendWebPage (Adafruit_CC3000_ClientRef client) {
        //为了节省空间,这里只发送简单的提示字符
        webServer.write ("Waiting for command");
        delay (20);
        client.close();
}

//输出当前WIFI 设备通过 DHCP 取得的基本信息
bool displayConnectionDetails(void) {
        uint32_t ipAddress, netmask, gateway, dhcpserv, dnsserv;

        if(!cc3000.getIPAddress(&ipAddress, &netmask, &gateway, &dhcpserv, &dnsserv)) {
                Serial.println(F("Unable to retrieve the IP Address!\r\n"));
                return false;
        } 
        else {
                Serial.print(F("\nIP Addr: ")); 
                cc3000.printIPdotsRev(ipAddress);
                Serial.print(F("\nNetmask: ")); 
                cc3000.printIPdotsRev(netmask);
                Serial.print(F("\nGateway: ")); 
                cc3000.printIPdotsRev(gateway);
                Serial.print(F("\nDHCPsrv: ")); 
                cc3000.printIPdotsRev(dhcpserv);
                Serial.print(F("\nDNSserv: ")); 
                cc3000.printIPdotsRev(dnsserv);
                Serial.println();
                return true;
        }
}

 

最后,对于初学者来说个人强烈不推荐使用“深圳四博智联科技有限公司开发的一款基于乐鑫ESP8266的各种板子“,原因是:资料很少,基本上没有Support,初学者使用的话很可能走很多弯路。
对于WiDo 来说,因为使用的是 CC3000,这也是 Arduino 官方用的 Wifi 使用的芯片,因此资料很全。不得不承认一点,国内Arduino方面的硬件价格便宜,但是软件还是很弱,特别是基础性的研究。如果你的项目比较急,或者对于稳定性要求高,首选官方支持的设备。

参考:
1. 通过网页按钮控制台灯,wido做服务器http://www.dfrobot.com.cn/community/forum.php?mod=viewthread&tid=10001&highlight=wido

Arduino 使用固态继电器的例子

平时我们使用的继电器都是下面这种,内部是有机械结构来控制通断的。在工作的时候我们会听到“咔”的声响。
image002
如果你是持续的操作或者是在安静的环境下使用,这种声音就会非常恼人。此时可以考虑使用固态继电器,长相如下:
image004

注意,固态继电器有直流和交流的差别,上面这样一个差不多11元,比继电器模块贵一些(如果你的继电器模块功能比较全,有光耦之类的其实二者价格相差不多)。
上面图片显示的就是我入手的,控制端输入3-32V直流,被控制端可以控制5-60V直流。
具体线路连接如下:
image006

#define ControlPin 8
void setup()
{
    Serial.begin(9600);
    pinMode(ControlPin,OUTPUT);      //该端口需要选择有#号标识的数字口
    digitalWrite(ControlPin,LOW);
}

void loop()
{
  char  c;

    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (']'==c) 
          {
            digitalWrite(ControlPin,HIGH);
          }
        if ('['==c) 
          {
            digitalWrite(ControlPin,LOW);
          }

    }
}

 

最后放一下工作的照片
image007
总结【参考2】固态继电器的优缺点
1、固态继电器的优点
(1)高寿命,高可靠:SSR没有机械零部件,有固体器件完成触点功能,由于没有运动的零部件,因此能在高冲击,振动的环境下工作,由于组成固态继电器的元器件的固有特性,决定了固态继电器的寿命长,可靠性高。
(2)灵敏度高,控制功率小,电磁兼容性好:固态继电器的输入电压范围较宽,驱动功率低,可与大多数逻辑集成电路兼容不需加缓冲器或驱动器。
(3)快速转换:固态继电器因为采用固体其间,所以切换速度可从几毫秒至几微妙。
(4)电磁干扰笑:固态继电器没有输入”线圈”,没有触点燃弧和回跳,因而减少了电磁干扰。大多数交流输出固态继电器是一个零电压开关,在零电压处导通,零电流处关断,减少了电流波形的突然中断,从而减少了开关瞬态效应。
(5)无噪音

2、固态继电器的缺点
(1)导通后的管压降大,可控硅或双相控硅的正向降压可达1~2V,大功率晶体管的饱和压浆液灾1~2V之间,一般功率场效应管的导通电祖也较机械触点的接触电阻大。
(2)半导体器件关断后仍可有数微安至数毫安的漏电流,因此不能实现理想的电隔离。
(3)由于管压降大,导通后的功耗和发热量也大,大功率固态继电器的体积远远大于同容量的电磁继电器,成本也较高。
(4)电子元器件的温度特性和电子线路的抗干扰能力较差,耐辐射能力也较差,如不采取有效措施,则工作可靠性低。
(5)固态继电器对过载有较大的敏感性,必须用快速熔断器或RC阻尼电路对其进行过在保护。固态继电器的负载与环境温度明显有关,温度升高,负载能力将迅速下降。

另外,一般的继电器输出有3个Pin分别是:地,常开和常闭。固态继电器只有2个Pin,购买的时候就需要确定是常开的还是常闭。
参考:
1. http://www.lab-z.com/mos%E6%8E%A7%E5%88%B6%E5%B0%8F%E7%81%AF%E6%B3%A1%E7%9A%84%E5%AE%9E%E9%AA%8C/ MOS控制小灯泡的实验
2. http://www.elecfans.com/yuanqijian/jidianqi/20121030295690.html 固态继电器简介及优缺点

【入门指南】图解如何使用 USBTinyIsp

每一个Arduino 的玩家都应该准备一个 ISP 下载器,有了它能让你的 Uno 玩出很多花样。这里就介绍一下如何使用 USBTinyISP。本文将介绍使用这款工具的三种刷写方式:

  • 第三方软件刷写 328P BootLoader的方法
  • Arduino IDE 刷写 328P BootLoader的方法
  • Arduino IDE 刷写328P上面程序的方法

首先,你要有一个USBTinyISP烧录器。下面的是购买自淘宝的 OCROBOT 出品,价格是 45元,属于较贵的。
image001

这款需要安装驱动才能工作,淘宝很多出售的号称不需要驱动程序,我也曾经入手过一个,一直无法使用。因此,建议购买之前要和卖家沟通好,索要驱动,特别的如果卖家要求关闭数字签名才能安装驱动也请不要购买。这样做会让你的系统暴露在风险之中,另外,64位Windows由于本身的特性无法关闭数字签名功能。

插入之后安装驱动,驱动来自【参考1】。安装之后是下面这样(特别提醒,如果插入之后出现无法获得USB Descriptor 的提示,那么请更换一个USB端口,产生这个问题的原因可能是某些USB3.0端口和这个卡上的控制芯片有兼容问题)

image002

image004

image003

image005

Arduino UNO 上有2个烧录口,一个是给 USB 转串口芯片使用的,一个是给 328P 使用的。通常我们需要烧录的是328P 【参考】。也就是下图中的橘色标记口。
image006

直接将USBTinyISP的头插入这个位置即可。需要注意的是:

  1. 建议切断Uno上的外部供电之后进行烧写;
  2. 插入是有方向的,插入之后无论正反,板子上的灯都会亮。但是反插无需担心烧毁

至此,硬件部分准备完毕,下面就是如何使用软件进行烧写了。

  • 第三方软件Avrdudess刷写 328P BootLoader的方法

运行 Avrdudess,先选择 USBTinyISP

image007
然后使用右侧Detect 按钮进行芯片识别。正常情况下,主芯片应该是328P,USB转串口芯片应该是16U2或者8U2如果没有识别,应该是插线反了导致的,请调换方向重新插入
image008
最后,选择要烧写的文件,再使用GO 按钮即可进行烧录
image009
特别提醒:不要调整右侧 Fuses 选项。

二.      Arduino IDE 刷写 328P BootLoader的方法

  1. Tools->Board->选择你的板子型号,比如:Uno
  2. Tools->Programmer->USBTinyISP 选择烧写器的型号

image0103.Tools->BurnBootloader 直接烧写 328P 的Bootloader

image011

烧写成功的话,会有下面的提示信息

image012

  • Arduino IDE 刷写328P上面程序的方法

某些情况下,我们需要直接烧写328P上面的程序,方法如下:

  1. IDE中保证你的代码能够正常编译通过
  2. IDE中选择板子型号和烧写器型号和上面介绍的步骤一样
  3. 使用File->Upload Using Programmer

image013
运行成功后有下面类似提示

image014

本文首发 http://www.arduino.cn/thread-21619-1-1.html

文章中提到的工具和驱动也可以在上面的网址找到

参考:

  1. https://learn.adafruit.com/usbtinyisp/drivers 其实国内大多数 Arduino 产品都是仿造国外的而已,软件和Firmware一直是软肋。
  2. https://www.lab-z.com/ardfim/

Arduino模拟鼠标的又一种方法

Arduino 模拟鼠标目前有三种方法:
1.外部加电阻 USB母头等元件,然后烧录模拟程序,用 328P作为处理器。这种方法的缺点是:原件多,不容易调试,占用板载资源多,做出来之后基本上不能完成什么功能了(因为Usb低速设备传输要求1.5Mb/s,而328P最高只有16MHz),有兴趣的朋友可以看一下之前我做的一个锁屏的装置【参考1】;
2.直接使用Leonardo 这样主控是ATmega32u4【参考2】 的板子。这种方法的好处是:Arduino 原生库支持,资料比较多,调试方便。个人推荐初学者如果有鼠标键盘的需要可以玩这个;
3.原版的Arduino Uno 上面使用的串口芯片是 16u2,可以给这个芯片刷写上一个特殊的Firmware,它和PC端用USB鼠标或者键盘通讯,然后和 328P 使用串口通讯。
本文介绍的就是第三种方法。
在玩第三种方法的时候,你需要特别准备一个烧写器。我用的是 USBTINY 这款。

image001

本次实验的目标是将uno模拟成鼠标。参考的资料来自下面的页面:

http://hunt.net.nz/users/darran/weblog/cca39/Arduino_UNO_Mouse_HID.html

我刷写的工具是 AvrDudess 2.4,用法很简单,接线之后(建议选购下载器的时候直接选带完整线的,否则每次接线也是很麻烦的事情),按下 Detect按钮,软件需要检查到正确芯片的类型,比如,我的转接芯片是 16u2。如果无法侦测,那么请检查连线。如果折腾了很久都不行,那么请联系卖家所要驱动和刷写工具。刚开始的时候我就在这里折腾了很长时间。

image002

image003

因为串口芯片被刷掉了,所以接下来也必须使用刷写器写入编译好的Arduino 程序。
image004

输入程序,确定编译无误

/* Arduino USB Mouse HID demo */

/* Author: Darran Hunt
 * Release into the public domain.
 */

struct {
    uint8_t buttons;
    int8_t x;
    int8_t y;
    int8_t wheel;	/* Not yet implemented */
} mouseReport;

uint8_t nullReport[4] = { 0, 0, 0, 0 };

void setup();
void loop();

void setup() 
{
    Serial.begin(9600);
    delay(200);
}

/* Move the mouse in a clockwise square every 5 seconds */
void loop() 
{
    int ind;
    delay(5000);

    mouseReport.buttons = 0;
    mouseReport.x = 0;
    mouseReport.y = 0;
    mouseReport.wheel = 0;

    mouseReport.x = -2;
    for (ind=0; ind<20; ind++) {
	Serial.write((uint8_t *)&mouseReport, 4);
	Serial.write((uint8_t *)&nullReport, 4);
    }

    mouseReport.x = 0;
    mouseReport.y = -2;
    for (ind=0; ind<20; ind++) {
	Serial.write((uint8_t *)&mouseReport, 4);
	Serial.write((uint8_t *)&nullReport, 4);
    }

    mouseReport.x = 2;
    mouseReport.y = 0;
    for (ind=0; ind<20; ind++) {
	Serial.write((uint8_t *)&mouseReport, 4);
	Serial.write((uint8_t *)&nullReport, 4);
    }

    mouseReport.x = 0;
    mouseReport.y = 2;
    for (ind=0; ind<20; ind++) {
	Serial.write((uint8_t *)&mouseReport, 4);
	Serial.write((uint8_t *)&nullReport, 4);
    }
}

 

接线如下:

用 IDE 上传内容,需要一些设置,指定刷写工具
image005
然后使用 File->Upload Using Programmer 来进行上传
image006
上传成功:
image007

成功之后,用Arduino Usb口连接电脑,你的鼠标每隔一段会自动旋转一圈,同时在设备管理器中会出现一个鼠标设备:
image008

这个和16u2 Firmware source code(Descriptors.c)中定义是相同的
.VendorID = 0x03EB,
.ProductID = 0x2041,
.ReleaseNumber = 0x0000

最后,特别提醒:设计模拟USB鼠标键盘之类的程序时,一定要考虑留多加一个运行条件。比如:某个引脚设定为低时才运行,或者上电10s才运行。否则有可能出现程序正常,但是因为键盘鼠标的干扰你没有再重新刷写新的代码的机会。

本文提到的16u2特别的 Firmware 下载 Arduino-mouse-0.1 源程序 arduino-mouse-0.1.tar

参考:
1. http://www.lab-z.com/20140101/ 用 Arduino 打造一个自动锁屏装置
2. http://www.arduino.cn/thread-1205-1-1.html Arduino Leonardo 中文介绍

Arduino Firmware简述

一个标准的 Arduino Uno上面有两个可以编程的IC,一个是负责USB 转串口的ATmega16U2,一个主控芯片ATmega328P,下图红色标记的就是16u2,绿色标记的是 328P.
image001
然后对应的有三种Firmware: 16U2 中有一个, 328P 中有两个。16u2的负责USB转串口;328P的一个Firmware是BootLoader,从功能上说主要是负责把 16u2收到串口数据刷新到328P 上;328P中的另外一个 Firmware 就是我们平常写的程序,编译之后生成的,用来完成我们期望的功能。

一般情况下,如果想更新16u2,需要额外的设备,比如 USB IPS ; 我们IDE只能更新328P 中的程序部分.328P 的BootLoader也是需要额外的设备来进行更新的。更新 16u2使用下图左上角框住部分的排针,更新 328P 使用下图中间橘色框图中指出的引脚。
image002
16u2的Firmware 可以在类似 \arduino-1.6.3\hardware\arduino\avr\firmwares\atmegaxxu2\arduino-usbserial 的路径中找到
328P Bootloader 的Firmware 可以在\arduino-1.6.3\hardware\arduino\avr\bootloaders\atmega 的路径中找到。

MPR121 触摸传感器模块

MPR121 是一款触摸传感器芯片,原理是通过检测电容变化来判断当前是否有触摸(接近)。

主要电器特性如下【参考1】:
1. 工作电压1.71-3.6v(芯片工作电压)
2. 通讯接口为 I2C
3. 12个检测端口
4. 带有1个 IRQ端口

我是在Taobao购买的模块,价格是16元【参考3】。经过搜索,这是仿sparkfun的,更多资料可以在【参考2】看到。

模块长得下面这样,有一个降压芯片,看起来可以直接使用 5V 供电。上面有12个口,可以接12个触摸按钮。IRQ 的作用是发出中断通知上面有触摸。ADD是芯片的I2C地址选择,接GND VDD SDA或者 SCL 地址分别是 0x5A 0x5B 0x5C 和 0x5D【来自参考4】。

image001

图片来自【参考2】

下面的代码可以正常工作(卖家的例程,有一些修改可以在 1.6.0 上编译通过)

#include "mpr121.h"
#include <Wire.h>

#define SENSORS       13
#define TOU_THRESH    0x1F
#define REL_THRESH    0x1A
#define PROX_THRESH   0x3f
#define PREL_THRESH   0x3c

// variables: capacitive sensing
bool touchStates[SENSORS];    // holds the current touch/prox state of all sensors
bool activeSensors[SENSORS] = {1,1,1,1,1,1,1,1,1,1,1,1,1}; // holds which sensors are active (0=inactive, 1=active)
bool newData = false;         // flag that is set to true when new data is available from capacitive sensor
int irqpin = 2;               // pin that connects to notifies when data is available from capacitive sensor

void setup(){

  // attach interrupt to pin - interrupt 1 is on pin 2 of the arduino (confusing I know)
  attachInterrupt(0, dataAvailable, FALLING);

  // set-up the Serial and I2C/Wire connections
  Serial.begin(9600);
  Wire.begin();

  // set the registers on the capacitive sensing IC
  setupCapacitiveRegisters();

}

void loop(){
  readCapacitiveSensor();
}

/**
 * dataAvailable Callback method that runs whenever new data becomes available on from the capacitive sensor. 
 *   This method was attached to the interrupt on pin 2, and is called whenever that pins goes low.
 */
void dataAvailable() {
  newData = true;
}

/**
 * readCapacitiveSensor Reads the capacitive sensor values from the MP121 IC. It makes a request to
 *   the sensor chip via the I2C/Wire connection, and then parses the sensor values which are stored on
 *   the first 13 bits of the 16-bit response msg.
 */
void readCapacitiveSensor(){
  if(newData){    
            Serial.println("yes");      
    //read the touch state from the MPR121
    Wire.requestFrom(0x5A,2); 
    byte tLSB = Wire.read();
    byte tMSB = Wire.read();
    uint16_t touched = ((tMSB << 8) | tLSB); //16bits that make up the touch states

    for (int i = 0; i < SENSORS; i++){  // Check what electrodes were pressed
      if (activeSensors[i] == 0) continue;
      char sensor_id [] = {'\0','\0','\0'};
      switch (i) {
        case 12:
          sensor_id[0] = 'P';
          break;
        default:
          if (i < 10) {
            sensor_id[0] = char( i+48 );
          } 
          else if (i < 12) {
            sensor_id[0] = char('1');
            sensor_id[1] = char( ( i % 10 ) + 48 );
          } 
      }
      if (sensor_id != '\0') {
        // read the humidity level

        // if current sensor was touched (check appropriate bit on touched var)
        if(touched & (1<<i)){      
          // if current pin was not previously touched send a serial message
          if(touchStates[i] == 0){          
            Serial.print(sensor_id);        
            Serial.print(":");
            Serial.println("1");
          } 
          touchStates[i] = 1;      
        } else {
          // if current pin was just touched send serial message
          if(touchStates[i] == 1){
            Serial.print(sensor_id);
            Serial.print(":");
            Serial.println("0");
          }
          touchStates[i] = 0;
        }        
      }
    }
    newData = false;
  }
}

/**
 * setupCapacitiveRegisters Updates all of configurations on the MP121 capacitive sensing IC. This includes
 *   setting levels for all filters, touch and proximity sensing activation and release thresholds, debounce,
 *   and auto-configurations options. At the end it activates all of the electrodes.
 */
void setupCapacitiveRegisters(){

  set_register(0x5A, ELE_CFG, 0x00); 
  
  // Section A - filtering when data is > baseline.
    // touch sensing
    set_register(0x5A, MHD_R, 0x01);
    set_register(0x5A, NHD_R, 0x01);
    set_register(0x5A, NCL_R, 0x00);
    set_register(0x5A, FDL_R, 0x00);

    // prox sensing 
    set_register(0x5A, PROX_MHDR, 0xFF);
    set_register(0x5A, PROX_NHDAR, 0xFF);
    set_register(0x5A, PROX_NCLR, 0x00);
    set_register(0x5A, PROX_FDLR, 0x00);

  // Section B - filtering when data is < baseline.
    // touch sensing
    set_register(0x5A, MHD_F, 0x01);
    set_register(0x5A, NHD_F, 0x01);
    set_register(0x5A, NCL_F, 0xFF);
    set_register(0x5A, FDL_F, 0x02);
  
    // prox sensing
    set_register(0x5A, PROX_MHDF, 0x01);
    set_register(0x5A, PROX_NHDAF, 0x01);
    set_register(0x5A, PROX_NCLF, 0xFF);
    set_register(0x5A, PROX_NDLF, 0xFF);

  // Section C - Sets touch and release thresholds for each electrode
    set_register(0x5A, ELE0_T, TOU_THRESH);
    set_register(0x5A, ELE0_R, REL_THRESH);
   
    set_register(0x5A, ELE1_T, TOU_THRESH);
    set_register(0x5A, ELE1_R, REL_THRESH);
    
    set_register(0x5A, ELE2_T, TOU_THRESH);
    set_register(0x5A, ELE2_R, REL_THRESH);
    
    set_register(0x5A, ELE3_T, TOU_THRESH);
    set_register(0x5A, ELE3_R, REL_THRESH);
    
    set_register(0x5A, ELE4_T, TOU_THRESH);
    set_register(0x5A, ELE4_R, REL_THRESH);
    
    set_register(0x5A, ELE5_T, TOU_THRESH);
    set_register(0x5A, ELE5_R, REL_THRESH);
    
    set_register(0x5A, ELE6_T, TOU_THRESH);
    set_register(0x5A, ELE6_R, REL_THRESH);
    
    set_register(0x5A, ELE7_T, TOU_THRESH);
    set_register(0x5A, ELE7_R, REL_THRESH);
    
    set_register(0x5A, ELE8_T, TOU_THRESH);
    set_register(0x5A, ELE8_R, REL_THRESH);
    
    set_register(0x5A, ELE9_T, TOU_THRESH);
    set_register(0x5A, ELE9_R, REL_THRESH);
    
    set_register(0x5A, ELE10_T, TOU_THRESH);
    set_register(0x5A, ELE10_R, REL_THRESH);
    
    set_register(0x5A, ELE11_T, TOU_THRESH);
    set_register(0x5A, ELE11_R, REL_THRESH);

  // Section D - Set the touch filter Configuration
    set_register(0x5A, FIL_CFG, 0x04);  

  // Section E - Set proximity sensing threshold and release
    set_register(0x5A, PRO_T, PROX_THRESH);   // sets the proximity sensor threshold
    set_register(0x5A, PRO_R, PREL_THRESH);   // sets the proximity sensor release

  // Section F - Set proximity sensor debounce
    set_register(0x59, PROX_DEB, 0x50);  // PROX debounce

  // Section G - Set Auto Config and Auto Reconfig for prox sensing
    set_register(0x5A, ATO_CFGU, 0xC9);  // USL = (Vdd-0.7)/vdd*256 = 0xC9 @3.3V   
    set_register(0x5A, ATO_CFGL, 0x82);  // LSL = 0.65*USL = 0x82 @3.3V
    set_register(0x5A, ATO_CFGT, 0xB5);  // Target = 0.9*USL = 0xB5 @3.3V
    set_register(0x5A, ATO_CFG0, 0x0B);

  // Section H - Start listening to all electrodes and the proximity sensor
    set_register(0x5A, ELE_CFG, 0x3C);
}

/**
 * set_register Sets a register on a device connected via I2C. It accepts the device's address, 
 *   register location, and the register value.
 * @param address The address of the I2C device
 * @param r       The register's address on the I2C device
 * @param v       The new value for the register
 */
void set_register(int address, unsigned char r, unsigned char v){
  Wire.beginTransmission(address);
  Wire.write(r);
  Wire.write(v);
  Wire.endTransmission();
}

 

运行结果:

image004

完整的代码下载

MPR121

参考:
1. http://wenku.baidu.com/link?url=77EtEpflHOBF9LmqwOvIwe5ONZ6I548h4BcBk4Ep1XWVO_RVDj9fycwoku44RENseV48lzvnrnDasY3UAMHsBuuU7yVdxFsAfxq-zbDiEhy MPR121中文数据手册
2. https://learn.sparkfun.com/tutorials/mpr121-hookup-guide
3. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.kpoNRc&id=19968128319&_u=pkf8s90f4c
4. MPR121 DataSheet

===============================================================================
额外说一下这个传感器在 Pro Micro 上的连接方法:

1. Pro Micro 的中断:“The Pro Micro has five external interrupts, which allow you to instantly trigger a function when a pin goes either high or low (or both). If you attach an interrupt to an interrupt-enabled pin, you’ll need to know the specific interrupt that pin triggers: pin 3 maps to interrupt 0, pin 2 is interrupt 1, pin 0 is interrupt 2, pin 1 is interrupt 3, and pin 7 is interrupt 4.”
推荐 使用 Pin7-Interrupt 4
2. Pro Micro Pin2- SDA Pin3-SCL

Step to UEFI (77) —–改造 Stall和 MV

我们在 EDK2 的代码中能看到 Shell下部分命令的代码,这里介绍如何把这样的代码提取出来做成能够独立编译和运行的程序。简单起见,以 Stall 命令和 MV 命令为例。

经过试验,这些命令中使用到的大部分函数都可以在 ShellLib.h 中找到,我们要做的只是把这个文件copy一份到我们程序下面。
最后修改之后的程序如下:

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] = L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] = L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]= L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_PROBLEM_VAL[] = L"Error. The argument '%s' has incorrect value.\r\n";
CONST CHAR16 STR_STALL_FAILED[]    = L"Error. BootService Stall() failed with %r.\r\n";

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS          Status;
	LIST_ENTRY          *Package;
	CHAR16              *ProblemParam;
    SHELL_STATUS        ShellStatus;
	UINT64              Intermediate;
  
	//
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM,ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  }  else {
    if (ShellCommandLineGetRawValue(Package, 2) != NULL) {
      Print(STR_GEN_TOO_MANY);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else if (ShellCommandLineGetRawValue(Package, 1) == NULL) {
      Print(STR_GEN_TOO_FEW);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      Status = ShellConvertStringToUint64(
				ShellCommandLineGetRawValue(Package, 1), &Intermediate, FALSE, FALSE);
      if (EFI_ERROR(Status) || ((UINT64)(UINTN)(Intermediate)) != Intermediate) {
        Print(STR_GEN_PROBLEM_VAL, ShellCommandLineGetRawValue(Package, 1));
        ShellStatus = SHELL_INVALID_PARAMETER;
      } else {
        Status = gBS->Stall((UINTN)Intermediate);
        if (EFI_ERROR(Status)) {
          Print(STR_STALL_FAILED, Status);
          ShellStatus = SHELL_DEVICE_ERROR;
        }
      }
    }
    ShellCommandLineFreeVarList (Package);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zstall

完整代码下载(特别注意,涉及到时钟的程序在NT32模拟环境中和实际环境中存在很大差别,不要在实际环境中使用为模拟环境编译的EFI文件)。
zStall

总结一下,如果想把一个命令改造为独立的程序,需要做下面的事情:

1. 增加 #define ASSERT(Expression) 这个定义,上面代码为了简单,我只是定义它为空
2. 拷贝 ShellLib.h 到你的代码目录下,然后用 “”直接使用
3. 改造程序中定义的字符串,这些字符串都是定义在 UNI 文件中。如果你没有多语言的需要,可以像我这样在代码中重新定义一次
4. 将所有的 ShellPrintHiiEx 都修改为 Print

下面再用 MV 命令的代码练习一下

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

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UnicodeCollation.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] =    L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]=    L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] =    L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_NO_CWD[]  =    L"Error. No current directory is specified.\r\n";
CONST CHAR16 STR_GEN_FILE_NF[] =    L"Error. File '%s' was not found.\r\n";
CONST CHAR16 STR_GEN_ERR_FILE[]=    L"Error. File '%s' error: %r\r\n";
CONST CHAR16 STR_MV_INV_SUB[]  = L"Error. Cannot move a directory into itself or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_RO[]   = L"Error. Cannot move a read-only File or Directory.\r\n";
CONST CHAR16 STR_MV_INV_CWD[]  = L"Error. Cannot move current working directory or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_FS[]   = L"Error. Cannot move between file systems.\r\n";
CONST CHAR16 STR_GEN_NO_MEM[]  = L"Error. Memory is not available.\r\n";
CONST CHAR16 STR_GEN_ERR_UK[]  = L"Error: %r\r\n";
CONST CHAR16 STR_MV_OUTPUT[]   = L"Moving %s -> %s\r\n";
CONST CHAR16 STR_GEN_RES_OK[]  = L"- [ok]\r\n";
CONST CHAR16 STR_GEN_MARG_ERROR[] = L"Error. The destination '%s' is ambigious.\r\n";
CONST CHAR16 STR_GEN_FILE_ERROR[] = L"Error. The destination is an existant file '%s'.\r\n";
      
//copy from \ShellPkg\Library\BasePathLib\BasePathLib.c
/**
  Removes the last directory or file entry in a path by changing the last
  L'\' to a CHAR_NULL.

  @param[in, out] Path    The pointer to the path to modify.

  @retval FALSE     Nothing was found to remove.
  @retval TRUE      A directory or file was removed.
**/
BOOLEAN
EFIAPI
PathRemoveLastItem(
  IN OUT CHAR16 *Path
  )
{
  CHAR16        *Walker;
  CHAR16        *LastSlash;
  //
  // get directory name from path... ('chop' off extra)
  //
  for ( Walker = Path, LastSlash = NULL
      ; Walker != NULL && *Walker != CHAR_NULL
      ; Walker++
     ){
    if (*Walker == L'\\' && *(Walker + 1) != CHAR_NULL) {
      LastSlash = Walker+1;
    }
  }
  if (LastSlash != NULL) {
    *LastSlash = CHAR_NULL;
    return (TRUE);
  }
  return (FALSE);
}

/**
  Function to take a destination path that might contain wildcards and verify
  that there is only a single possible target (IE we cant have wildcards that
  have 2 possible destination).

  if the result is sucessful the caller must free *DestPathPointer.

  @param[in] DestDir               The original path to the destination.
  @param[in, out] DestPathPointer  A pointer to the callee allocated final path.
  @param[in] Cwd                   A pointer to the current working directory.

  @retval SHELL_INVALID_PARAMETER  The DestDir could not be resolved to a location.
  @retval SHELL_INVALID_PARAMETER  The DestDir could be resolved to more than 1 location.
  @retval SHELL_INVALID_PARAMETER  Cwd is required and is NULL.
  @retval SHELL_SUCCESS            The operation was sucessful.
**/
SHELL_STATUS
EFIAPI
GetDestinationLocation(
  IN CONST CHAR16               *DestDir,
  IN OUT CHAR16                 **DestPathPointer,
  IN CONST CHAR16               *Cwd
  )
{
  EFI_SHELL_FILE_INFO       *DestList;
  EFI_SHELL_FILE_INFO       *Node;
  CHAR16                    *DestPath;
  UINTN                     NewSize;
  UINTN                     CurrentSize;

  DestList = NULL;
  DestPath = NULL;

  if (StrStr(DestDir, L"\\") == DestDir) {
    if (Cwd == NULL) {
      return SHELL_INVALID_PARAMETER;
    }
    DestPath = AllocateZeroPool(StrSize(Cwd));
    if (DestPath == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }
    StrCpy(DestPath, Cwd);
    while (PathRemoveLastItem(DestPath)) ;

    //
    // Append DestDir beyond '\' which may be present
    //
    CurrentSize = StrSize(DestPath);
    StrnCatGrow(&DestPath, &CurrentSize, &DestDir[1], 0);

    *DestPathPointer =  DestPath;
    return (SHELL_SUCCESS);
  }
  //
  // get the destination path
  //
  ShellOpenFileMetaArg((CHAR16*)DestDir, EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ|EFI_FILE_MODE_CREATE, &DestList);
  if (DestList == NULL || IsListEmpty(&DestList->Link)) {
    //
    // Not existing... must be renaming
    //
    if (StrStr(DestDir, L":") == NULL) {
      if (Cwd == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_INVALID_PARAMETER);
      }
      NewSize = StrSize(Cwd);
      NewSize += StrSize(DestDir);
      DestPath = AllocateZeroPool(NewSize);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Cwd);
      if (DestPath[StrLen(DestPath)-1] != L'\\' && DestDir[0] != L'\\') {
        StrCat(DestPath, L"\\");
      } else if (DestPath[StrLen(DestPath)-1] == L'\\' && DestDir[0] == L'\\') {
        ((CHAR16*)DestPath)[StrLen(DestPath)-1] = CHAR_NULL;
      }
      StrCat(DestPath, DestDir);
    } else {
      ASSERT(DestPath == NULL);
      DestPath = StrnCatGrow(&DestPath, NULL, DestDir, 0);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
    }
  } else {
    Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&DestList->Link);
    //
    // Make sure there is only 1 node in the list.
    //
    if (!IsNodeAtEnd(&DestList->Link, &Node->Link)) {
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_MARG_ERROR, DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
    if (ShellIsDirectory(Node->FullName)==EFI_SUCCESS) {
      DestPath = AllocateZeroPool(StrSize(Node->FullName)+sizeof(CHAR16));
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Node->FullName);
      StrCat(DestPath, L"\\");
    } else {
      //
      // cant move onto another file.
      //
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_FILE_ERROR,DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
  }

  *DestPathPointer =  DestPath;
  ShellCloseFileMetaArg(&DestList);

  return (SHELL_SUCCESS);
}


/**
  Function to clean up paths.  
  
  - Single periods in the path are removed.
  - Double periods in the path are removed along with a single parent directory.
  - Forward slashes L'/' are converted to backward slashes L'\'.

  This will be done inline and the existing buffer may be larger than required 
  upon completion.

  @param[in] Path       The pointer to the string containing the path.

  @retval NULL          An error occured.
  @return Path in all other instances.
**/
CHAR16*
EFIAPI
PathCleanUpDirectories(
  IN CHAR16 *Path
  )
{
  CHAR16  *TempString;
  UINTN   TempSize;
  if (Path==NULL) {
    return(NULL);
  }

  //
  // Fix up the '/' vs '\'
  //
  for (TempString = Path ; TempString != NULL && *TempString != CHAR_NULL ; TempString++) {
    if (*TempString == L'/') {
      *TempString = L'\\';
    }
  }

  //
  // Fix up the ..
  //
  while ((TempString = StrStr(Path, L"\\..\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 4;
    PathRemoveLastItem(Path);
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\..")) != NULL && *(TempString + 3) == CHAR_NULL) {
    *TempString = CHAR_NULL;
    PathRemoveLastItem(Path);
  }

  //
  // Fix up the .
  //
  while ((TempString = StrStr(Path, L"\\.\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 2;
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\.")) != NULL && *(TempString + 2) == CHAR_NULL) {
    *(TempString + 1) = CHAR_NULL;
  }



  return (Path);
}

STATIC EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

/**
  Function to compare 2 strings without regard to case of the characters.

  @param[in] Buffer1            Pointer to String to compare.
  @param[in] Buffer2            Pointer to second String to compare.

  @retval 0                     Buffer1 equal to Buffer2.
  @return < 0                   Buffer1 is less than Buffer2.
  @return > 0                   Buffer1 is greater than Buffer2.
**/
INTN
EFIAPI
StringNoCaseCompare (
  IN  CONST VOID             *Buffer1,
  IN  CONST VOID             *Buffer2
  )
{
  EFI_STATUS                Status;
  if (mUnicodeCollation == NULL) {
    Status = gBS->LocateProtocol(
      &gEfiUnicodeCollation2ProtocolGuid,
      NULL,
      (VOID**)&mUnicodeCollation);

    ASSERT(Status);
  }

  return (mUnicodeCollation->StriColl(
    mUnicodeCollation,
    *(CHAR16**)Buffer1,
    *(CHAR16**)Buffer2));
}

/**
  Function to validate that moving a specific file (FileName) to a specific
  location (DestPath) is valid.

  This function will verify that the destination is not a subdirectory of
  FullName, that the Current working Directory is not being moved, and that
  the directory is not read only.

  if the move is invalid this function will report the error to StdOut.

  @param FullName [in]    The name of the file to move.
  @param Cwd      [in]    The current working directory
  @param DestPath [in]    The target location to move to
  @param Attribute[in]    The Attribute of the file

  @retval TRUE        The move is valid
  @retval FALSE       The move is not
**/
BOOLEAN
EFIAPI
IsValidMove(
  IN CONST CHAR16   *FullName,
  IN CONST CHAR16   *Cwd,
  IN CONST CHAR16   *DestPath,
  IN CONST UINT64   Attribute
  )
{
  CHAR16  *Test;
  CHAR16  *Test1;
  CHAR16  *TestWalker;
  INTN    Result;
  UINTN   TempLen;
  if (Cwd != NULL && StrCmp(FullName, Cwd) == 0) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_CWD);
    return (FALSE);
  }
  Test = NULL;
  Test = StrnCatGrow(&Test, NULL, DestPath, 0);
  TestWalker = Test;
  ASSERT(TestWalker != NULL);
  while(*TestWalker == L'\\') {
    TestWalker++;
  }
  while(TestWalker != NULL && TestWalker[StrLen(TestWalker)-1] == L'\\') {
    TestWalker[StrLen(TestWalker)-1] = CHAR_NULL;
  }
  ASSERT(TestWalker != NULL);
  ASSERT(FullName   != NULL);
  if (StrStr(FullName, TestWalker) != 0) {
    TempLen = StrLen(FullName);
    if (StrStr(FullName, TestWalker) != FullName                    // not the first items... (could below it)
      && TempLen <= (StrLen(TestWalker) + 1)
      && StrStr(FullName+StrLen(TestWalker) + 1, L"\\") == NULL) {
      //
      // Invalid move
      //
      Print(STR_MV_INV_SUB);
      FreePool(Test);
      return (FALSE);
    }
  }
  FreePool(Test);
  if (StrStr(DestPath, FullName) != 0 && StrStr(DestPath, FullName) != DestPath) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_SUB);
    return (FALSE);
  }
  if ((Attribute & EFI_FILE_READ_ONLY) != 0) {
    //
    // invalid to move read only
    //
    Print(STR_MV_INV_RO);
    return (FALSE);
  }
  Test  = StrStr(FullName, L":");
  Test1 = StrStr(DestPath, L":");
  if (Test1 != NULL && Test  != NULL) {
    *Test  = CHAR_NULL;
    *Test1 = CHAR_NULL;
    Result = StringNoCaseCompare(&FullName, &DestPath);
    *Test  = L':';
    *Test1 = L':';
    if (Result != 0) {
      Print(STR_MV_INV_FS);
      return (FALSE);
    }
  }
  return (TRUE);
}

/**
  function to take a list of files to move and a destination location and do
  the verification and moving of those files to that location.  This function
  will report any errors to the user and continue to move the rest of the files.

  @param[in] FileList           A LIST_ENTRY* based list of files to move
  @param[out] Resp              pointer to response from question.  Pass back on looped calling
  @param[in] DestDir            the destination location

  @retval SHELL_SUCCESS             the files were all moved.
  @retval SHELL_INVALID_PARAMETER   a parameter was invalid
  @retval SHELL_SECURITY_VIOLATION  a security violation ocurred
  @retval SHELL_WRITE_PROTECTED     the destination was write protected
  @retval SHELL_OUT_OF_RESOURCES    a memory allocation failed
**/
SHELL_STATUS
EFIAPI
ValidateAndMoveFiles(
  IN CONST EFI_SHELL_FILE_INFO  *FileList,
  OUT VOID                      **Resp,
  IN CONST CHAR16               *DestDir
  )
{
  EFI_STATUS                Status;
  CHAR16                    *DestPath;
  CONST CHAR16              *Cwd;
  SHELL_STATUS              ShellStatus;
  CONST EFI_SHELL_FILE_INFO *Node;
  EFI_FILE_INFO             *NewFileInfo;
  CHAR16                    *TempLocation;
  UINTN                     NewSize;
  UINTN                     Length;
  VOID                      *Response;
  SHELL_FILE_HANDLE         DestHandle;
  CHAR16 STR_GEN_DEST_EXIST_OVR[] = L"Destination file already exists.  Overwrite? Yes, No, All, Cancel ";
  
  ASSERT(FileList != NULL);
  ASSERT(DestDir  != NULL);

  DestPath = NULL;
  Cwd      = ShellGetCurrentDir(NULL);
  Response = *Resp;

  //
  // Get and validate the destination location
  //
  ShellStatus = GetDestinationLocation(DestDir, &DestPath, Cwd);
  if (ShellStatus != SHELL_SUCCESS) {
    return (ShellStatus);
  }
  DestPath = PathCleanUpDirectories(DestPath);

  //
  // Go through the list of files and directories to move...
  //
  for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode(&FileList->Link)
    ;  !IsNull(&FileList->Link, &Node->Link)
    ;  Node = (EFI_SHELL_FILE_INFO *)GetNextNode(&FileList->Link, &Node->Link)
   ){
    if (ShellGetExecutionBreakFlag()) {
      break;
    }
    ASSERT(Node->FileName != NULL);
    ASSERT(Node->FullName != NULL);

    //
    // skip the directory traversing stuff...
    //
    if (StrCmp(Node->FileName, L".") == 0 || StrCmp(Node->FileName, L"..") == 0) {
      continue;
    }

    //
    // Validate that the move is valid
    //
    if (!IsValidMove(Node->FullName, Cwd, DestPath, Node->Info->Attribute)) {
      ShellStatus = SHELL_INVALID_PARAMETER;
      continue;
    }

    //
    // Chop off map info from "DestPath"
    //
    if ((TempLocation = StrStr(DestPath, L":")) != NULL) {
      CopyMem(DestPath, TempLocation+1, StrSize(TempLocation+1));
    }

    //
    // construct the new file info block
    //
    NewSize = StrSize(DestPath);
    NewSize += StrSize(Node->FileName) + SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
    NewFileInfo = AllocateZeroPool(NewSize);
    if (NewFileInfo == NULL) {
      Print(STR_GEN_NO_MEM);
      ShellStatus = SHELL_OUT_OF_RESOURCES;
    } else {
      CopyMem(NewFileInfo, Node->Info, SIZE_OF_EFI_FILE_INFO);
      if (DestPath[0] != L'\\') {
        StrCpy(NewFileInfo->FileName, L"\\");
        StrCat(NewFileInfo->FileName, DestPath);
      } else {
        StrCpy(NewFileInfo->FileName, DestPath);
      }
      Length = StrLen(NewFileInfo->FileName);
      if (Length > 0) {
        Length--;
      }
      if (NewFileInfo->FileName[Length] == L'\\') {
        if (Node->FileName[0] == L'\\') {
          //
          // Don't allow for double slashes. Eliminate one of them.
          //
          NewFileInfo->FileName[Length] = CHAR_NULL;
        }
        StrCat(NewFileInfo->FileName, Node->FileName);
      }
      NewFileInfo->Size = SIZE_OF_EFI_FILE_INFO + StrSize(NewFileInfo->FileName);
      Print(STR_MV_OUTPUT, Node->FullName, NewFileInfo->FileName);

      if (!EFI_ERROR(ShellFileExists(NewFileInfo->FileName))) {
        if (Response == NULL) {
          ShellPromptForResponse(ShellPromptResponseTypeYesNoAllCancel, STR_GEN_DEST_EXIST_OVR,  &Response);
        }
        switch (*(SHELL_PROMPT_RESPONSE*)Response) {
          case ShellPromptResponseNo:
            FreePool(NewFileInfo);
            continue;
          case ShellPromptResponseCancel:
            *Resp = Response;
            //
            // indicate to stop everything
            //
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return (SHELL_ABORTED);
          case ShellPromptResponseAll:
            *Resp = Response;
            break;
          case ShellPromptResponseYes:
            FreePool(Response);
            break;
          default:
            FreePool(Response);
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return SHELL_ABORTED;
        }
        Status = ShellOpenFileByName(NewFileInfo->FileName, &DestHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);
        ShellDeleteFile(&DestHandle);
      }


      //
      // Perform the move operation
      //
      Status = ShellSetFileInfo(Node->Handle, NewFileInfo);

      //
      // Free the info object we used...
      //
      FreePool(NewFileInfo);

      //
      // Check our result
      //
      if (EFI_ERROR(Status)) {
        Print(STR_GEN_ERR_UK, Status);
        ShellStatus = SHELL_INVALID_PARAMETER;
        if (Status == EFI_SECURITY_VIOLATION) {
          ShellStatus = SHELL_SECURITY_VIOLATION;
        } else if (Status == EFI_WRITE_PROTECTED) {
          ShellStatus = SHELL_WRITE_PROTECTED;
        } else if (Status == EFI_OUT_OF_RESOURCES) {
          ShellStatus = SHELL_OUT_OF_RESOURCES;
        } else if (Status == EFI_DEVICE_ERROR) {
          ShellStatus = SHELL_DEVICE_ERROR;
        } else if (Status == EFI_ACCESS_DENIED) {
          ShellStatus = SHELL_ACCESS_DENIED;
        }
      } else {
        Print( L"%s", STR_GEN_RES_OK);
      }
    }
  } // for loop

  FreePool(DestPath);
  return (ShellStatus);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS          Status;
  LIST_ENTRY          *Package;
  CHAR16              *ProblemParam;
  SHELL_STATUS        ShellStatus;
  UINTN               ParamCount;
  UINTN               LoopCounter;
  EFI_SHELL_FILE_INFO *FileList;
  VOID                *Response;

  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  ParamCount          = 0;
  FileList            = NULL;
  Response            = NULL;

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM , ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag(Package, L"-?")) {
      ASSERT(FALSE);
    }

    switch (ParamCount = ShellCommandLineGetCount(Package)) {
      case 0:
      case 1:
        //
        // we have insufficient parameters
        //
        Print(STR_GEN_TOO_FEW);
        ShellStatus = SHELL_INVALID_PARAMETER;
        break;
      case 2:
        //
        // must have valid CWD for single parameter...
        //
        if (ShellGetCurrentDir(NULL) == NULL){
          Print(STR_GEN_NO_CWD);
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, 1), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, 1));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            //
            ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellGetCurrentDir(NULL));
          }
        }

        break;
      default:
        ///@todo make sure this works with error half way through and continues...
        for (ParamCount--, LoopCounter = 1 ; LoopCounter < ParamCount ; LoopCounter++) {
          if (ShellGetExecutionBreakFlag()) {
            break;
          }
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, LoopCounter), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, LoopCounter));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            // Only change ShellStatus if it's sucessful
            //
            if (ShellStatus == SHELL_SUCCESS) {
              ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            } else {
              ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            }
          }
          if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
            Status = ShellCloseFileMetaArg(&FileList);
            if (EFI_ERROR(Status) && ShellStatus == SHELL_SUCCESS) {
              ShellStatus = SHELL_ACCESS_DENIED;
              Print(STR_GEN_ERR_FILE , ShellCommandLineGetRawValue(Package, 1), ShellStatus|MAX_BIT);
            }
          }
        }
        break;
    } // switch on parameter count

    if (FileList != NULL) {
      ShellCloseFileMetaArg(&FileList);
    }

    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  SHELL_FREE_NON_NULL(Response);

  if (ShellGetExecutionBreakFlag()) {
    return (SHELL_ABORTED);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zmv1

完整代码下载:

zMv