这个模块有供电的问题,用 USB 供电容易出现无法使用的情况。
前几天入手一个 CC3000 WiFi Shield 发现无法使用,经过查找和卖家沟通发现这个是通病,
无论是国外出售的还是国内的都有很大概率出现 USB 供电时无法使用的情况,
因此,有 WIFI 通讯需要的朋友请购买其他型号的。
这个模块有供电的问题,用 USB 供电容易出现无法使用的情况。
前几天入手一个 CC3000 WiFi Shield 发现无法使用,经过查找和卖家沟通发现这个是通病,
无论是国外出售的还是国内的都有很大概率出现 USB 供电时无法使用的情况,
因此,有 WIFI 通讯需要的朋友请购买其他型号的。
之前入手过一个9.9元的小水泵【参考1】,正好这次做实验用上了。他的工作电压最高是12V,我用MOS管 + Arduino的PWM来实现电压的控制。电路图和之前一个控制小灯泡的非常类似【参考2】。
#define PWMPin 10
int n=255;
void setup()
{
Serial.begin(9600);
//该端口需要选择有#号标识的数字口
pinMode(PWMPin,OUTPUT);
}
void loop()
{
char c;
while (Serial.available() > 0)
{
c=Serial.read();
if (‘]’==c)
{
n=n+5;
}
if (‘[‘==c)
{
n=n-5;
}
//这里为了方便,加入一个可以直接开到最大值的字符
if (‘/’==c)
{
n=255;
}
//这里为了方便,加入一个可以直接关闭的字符
if (‘.’==c)
{
n=0;
}
if (n>255) {n=0;}
if (n<0) {n=255;}
analogWrite(PWMPin,n);
Serial.println(n);
}
}
具体的实验内容是:使用一个底部有孔的小盆,证明放水时产生漩涡的方向和地球自转偏向力无关,而和放水时水流方向有关。
参考:
1. http://www.lab-z.com/waterpump/?nsukey=ZI7FePgtjom%2F3mfgzX9EtL5onGMfsUajYidhT5jUCO2t0hFAbtL%2B1LlY6YXFtxT785b4i%2Fe%2BSSjzo0wnFsIu1w%3D%3D 小水泵套件
2. 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控制小灯泡的实验
一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?
首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。
typedef struct _EFI_USB_IO_PROTOCOL {
EFI_USB_IO_CONTROL_TRANSFER UsbControlTransfer;
EFI_USB_IO_BULK_TRANSFER UsbBulkTransfer;
EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER UsbAsyncInterruptTransfer;
EFI_USB_IO_SYNC_INTERRPUT_TRANSFER UsbSyncInterruptTransfer;
EFI_USB_IO_ISOCHRONOUS_TRANSFER UsbIsochronousTransfer;
EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER UsbAsyncIsochronousTransfer;
EFI_USB_IO_GET_DEVICE_DESCRIPTOR UsbGetDeviceDescriptor;
EFI_USB_IO_GET_CONFIG_DESCRIPTOR UsbGetConfigDescriptor;
EFI_USB_IO_GET_INTERFACE_DESCRIPTOR UsbGetInterfaceDescriptor;
EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR UsbGetEndpointDescriptor;
EFI_USB_IO_GET_STRING_DESCRIPTOR UsbGetStringDescriptor;
EFI_USB_IO_GET_SUPPORTED_LANGUAGES UsbGetSupportedLanguages;
EFI_USB_IO_PORT_RESET UsbPortReset;
} EFI_USB_IO_PROTOCOL;
来自【参考2】。
我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。
我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。
每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/UsbIo.h>
extern EFI_BOOT_SERVICES *gBS;
EFI_GUID gEfiUsbIoProtocolGuid =
{ 0x2B2F68D6, 0x0CD2, 0x44CF,
{ 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};
UINTN GetUSB( )
{
EFI_STATUS Status;
UINTN HandleIndex, HandleCount;
EFI_HANDLE *DevicePathHandleBuffer = NULL;
EFI_USB_IO_PROTOCOL *USBIO;
EFI_USB_DEVICE_DESCRIPTOR DeviceDescriptor;
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiUsbIoProtocolGuid,
NULL,
&HandleCount,
&DevicePathHandleBuffer);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Get USBIO count fail.\n");
return 0;
}
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
{
Status = gBS->HandleProtocol(
DevicePathHandleBuffer[HandleIndex],
&gEfiUsbIoProtocolGuid,
(VOID**)&USBIO);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Open USBIO fail.\n");
gBS->FreePool(DevicePathHandleBuffer);
return 0;
}
Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Usb Get Device Descriptor fail.\n");
gBS->FreePool(DevicePathHandleBuffer);
return 0;
}
Print(L"VendorID = %04X, ProductID = %04X\n",
DeviceDescriptor.IdVendor,
DeviceDescriptor.IdProduct);
}
gBS->FreePool(DevicePathHandleBuffer);
return HandleCount;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
GetUSB( );
return EFI_SUCCESS;
}
最终运行结果如下:
完整的代码和程序下载:
GetPIDVID
参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义
简单的说就是加载图像,在显示的时候使用tint(gray,alpha) 不断调整 alpha 让他越来越不透明。
例子如下:
PImage img;
int i=0;
void setup() {
size(640, 360);
img = loadImage("moonwalk.jpg"); // Load an image into the program
}
void draw() {
tint(255, i++); // Display at half opacity
image(img, 0, 0);
if (i==40) {
delay(5000);
exit();
}
delay(200);
}
完整例子下载:
前面介绍了USB 鼠标数据的读取,个人感觉最困难的部分就是解析HID Descriptor,不同的鼠标的Descriptor不同,发出来的格式也不同。相比之下,键盘切换到 Boot Protocol 之后问题就简单多了。本文介绍如何将鼠标切换到这个模式下。
我们在之前中断方式获得鼠标改变代码的基础上,这样会使得整体效率比较高。
当 Mouse 工作在 Boot Protocol下面的时候,默认使用下面的 HID Protocol【参考1】

根据资料,编写程序如下:
/* Mouse communication via interrupt endpoint */
/* Assumes EP1 as interrupt IN ep */
#include "max3421e.h"
#include "usb.h"
#define DEVADDR 1
#define CONFVALUE 1
#define EP_MAXPKTSIZE 5
EP_RECORD ep_record[ 2 ]; //endpoint record structure for the mouse
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup()
{
Serial.begin( 115200 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
mouse1_init();
}//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {
rcode = mouse1_poll();
if( rcode ) {
Serial.print("Mouse Poll Error: ");
Serial.println( rcode, HEX );
}//if( rcode...
}//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse1_init( void )
{
byte rcode = 0; //return code
byte tmpdata;
byte* byte_ptr = &tmpdata;
/**/
ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 )); //copy endpoint 0 parameters
ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
ep_record[ 1 ].sndToggle = bmSNDTOG0;
ep_record[ 1 ].rcvToggle = bmRCVTOG0;
Usb.setDevTableEntry( 1, ep_record );
/* Configure device */
rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
if( rcode ) {
Serial.print("Error configuring mouse. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}//if( rcode...
rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
if( rcode ) {
Serial.print("Set Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
rcode = Usb.getIdle( DEVADDR, 0, 0, 0, (char *)byte_ptr );
if( rcode ) {
Serial.print("Get Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
Serial.print("Idle Rate: ");
Serial.print(( tmpdata * 4 ), DEC ); //rate is returned in multiples of 4ms
Serial.println(" ms");
tmpdata = 0;
rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
if( rcode ) {
Serial.print("Set Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
/* Set boot protocol */
rcode = Usb.setProto( DEVADDR, 0, 0, 0 );
if( rcode ) {
Serial.print("Error attempting to configure boot protocol. Return code :");
Serial.println( rcode, HEX );
while( 1 ); //stop
}
Usb.setUsbTaskState( USB_STATE_RUNNING );
return;
}
/* Poll mouse via interrupt endpoint and print result */
/* assumes EP1 as interrupt endpoint */
byte mouse1_poll( void )
{
byte rcode,i;
char buf[ 3 ] = { 0 }; //mouse report buffer
unsigned long int libuf[ sizeof(buf) ];
unsigned long int x;
unsigned long int y;
/* poll mouse */
rcode = Usb.inTransfer( DEVADDR, 1, sizeof(buf), buf, 1 ); //
if( rcode ) { //error
if( rcode == 0x04 ) { //NAK
rcode = 0;
}
return( rcode );
}
for (int i=0;i<sizeof(buf);i++) {
libuf[i]=(buf[i]) & 0xff;
Serial.print(libuf[i],HEX); Serial.print(" ");
}
Serial.println("");
return( rcode );
}
运行结果:
完整的代码下载
特别注意:经过我的实验,不是所有的鼠标都支持 Boot Protocol ,前面实验中用到的Dell 和HP鼠标都无法正常工作,可能是这两种鼠标不支持Boot Protocol 。换一个角度说,如果客户使用 USB 键盘,那么一定要有进入 BIOS Setup的需求,但是客户通常不会有在Setup中使用鼠标的需求,所以鼠标对于Boot Protocol的支持不好也在情理之中。
我发现好用的反倒是一个杂牌鼠标:
参考:
1. HID Firmware Specification 1.11 P71
之前介绍过使用 Arduino 用硬件的方法直接读取 EDID 【参考1】,这里介绍一下如何在 Shell下读取 EDID 信息。
我们使用 EFI_EDID_DISCOVERED_PROTOCOL 这个 Protocol 来获得信息。当然,类似的还可以使用 EFI_EDID_ACTIVE_PROTOCOL,都是没有问题的。
在要获取 EDID 信息之前,最好先使用 “DH -p EdidDiscovered” 命令来确定你当前的系统中存在 Edid 信息。例如:
实现这个功能的代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/EdidDiscovered.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_HANDLE gImageHandle;
EFI_GUID gEfiEdidDiscoveredProtocolGuid =
{ 0x1C0C34F6, 0xD380, 0x41FA, { 0xA0, 0x49, 0x8A, 0xD0, 0x6C, 0x1A, 0x66, 0xAA }};
EFI_STATUS GetEDIDInfo()
{
EFI_STATUS Status;
EFI_HANDLE *EDIDHandleBuffer;
UINTN EDIDHandleCount, index, i;
EFI_EDID_DISCOVERED_PROTOCOL * EDID;
Status = gBS->LocateHandleBuffer (ByProtocol,
&gEfiEdidDiscoveredProtocolGuid,
NULL,
&EDIDHandleCount,
&EDIDHandleBuffer);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Can't get EdidDiscoveredProtocol.\n");
return FALSE;
}
Print(L"EDID count = %d\n", EDIDHandleCount);
for(index = 0 ; index < EDIDHandleCount ; index++)
{
Status = gBS->OpenProtocol( EDIDHandleBuffer[index],
&gEfiEdidDiscoveredProtocolGuid,
(VOID**)&EDID,
gImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Open EDID Protocol failed.\n");
return FALSE;
}
Print(L"%d\n", EDID->SizeOfEdid);
for(i = 0 ; i < 128 ; i++)
{
Print(L"%02X ", EDID->Edid[i]);
if ((i+1) % 16 ==0) {
Print(L"\n"); }
else
if ((i+1) % 8 ==0) { Print(L"- ");}
}
Print(L"\n");
Status = gBS->CloseProtocol(EDIDHandleBuffer[index],
&gEfiEdidDiscoveredProtocolGuid, gImageHandle, NULL);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Close EDID device handle failed.\n");
return FALSE;
}
}
return EFI_SUCCESS;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
GetEDIDInfo();
return EFI_SUCCESS;
}
上述程序运行结果
完整代码下载
比对之前获得的结果(我用的是同一个显示器,可以在【参考1】中看到),可以发现结果是相同的。
如果想程序解析每个值的具体含义,还按照【参考3】给出的例子。
参考:
1.http://www.lab-z.com/arduinoedid/ Arduino 读取显示器 EDID
2.http://biosren.com/thread-5921-1-1.html 求救如何取得EDID?
3.http://blog.fpmurphy.com/2012/09/accessing-edid-information-from-uefi-shell.html Accessing EDID Information From UEFI Shell
之前介绍过如何使用外部加一些元件从而让你的 Uno编程一个USB设备【参考1】,美中不足的地方是:
1.模拟方式实现USB,占用非常多的资源,基本上无法做其他事情;
2.需要外部元件,做起来比较麻烦。
本文根据【参考1】提供的信息,介绍一下如何将Uno通过刷写Firmware的方法编程一个Mouse。
先说一下原理:在标准的Uno上有一个USB转串口的芯片,Atmega 16U2 。通过对其重写Firmware,能够实现其他 USB 功能。重写之后这个芯片对AtMega 328 主芯片的通讯仍然是串口,就是说我们主芯片可以像串口通讯一样,直接发送数据到这个芯片上,这个芯片负责将数据转化为 USB信号发给PC。
在整个开始之前,首先,你必须有一个烧写器。这里推荐UsbTinyIsp,我使用的是 OCROBOT 出品的【参考2】。必须有这个东西,否则你一定会出现搞坏了没办法救回来的情况。
然后就可以使用工具刷新 16U2 的 Firmware。 这里推荐 AvrDude 这个工具,比较好的地方在于,这个工具有自动识别芯片的功能,你可以用它检查接线之类是否正常。选中Arduino-mouse.hex,然后点击Write即可,下方会有信息提示(这种工具本质都是调用AVRDUDE的命令行参数,只是AVRDUDE命令行非常复杂也不直观)。完成之后会出现下面的提示:
avrdude.exe: verifying …
avrdude.exe: 3872 bytes of flash verified
avrdude.exe done. Thank you.
接下来,在Arduino的IDE中打开Mouse_usb_demo.pde。
你需要确定 :
1.Tools->Board->选择的是Arduino Uno
2.Tools->Programmer下面选择的是 UsbTinyISP。
正常编译这个程序,不要选择编译并且上传,单纯选择编译即可。编译后,用File->Upload using Programmer 即完成上传。
再把Uno插在PC上,你会发现设备管理器中多出一个鼠标
PID VID如下所示:
实现的功能是你的鼠标每隔一段会自己转一圈。
完整的代码下载
Arduino-mouse
从这篇介绍也可以看出来,初学者尽量买原版的 Arduino Uno ,不要购买什么极客板等等,虽然会便宜一些,但是扩展性可玩性上下降很多。
参考:
1. http://hunt.net.nz/users/darran/weblog/cca39/Arduino_UNO_Mouse_HID.html Arduino UNO Mouse HID
2.这个东西是必须的,推荐这款有数字签名的驱动,支持Win8 64位,这样你就不必每次跑去关闭数字签名了。购买地址:
https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-684314961.37.VJHdrg&id=19035872312 。
当然淘宝上还有更便宜,我之前买了一个便宜的,说是无需驱动,结果无法用,非常懊恼……
Arduino 上面有模拟到数字(ADC)的采样功能但是没有数字到模拟的输出(DAC),在要求不是特别高的情况下 PWM 充当这一角色。
首先需要知道的是:不是所有的Pin都可以用来输出PWM,根据【参考1】,在Uno上的 3 5 6 9 10 和11才可以用来输出PWM.
编写一个简单的程序来调整占空比
int n=255;
const int PWMPin=6;
void setup()
{
Serial.begin(9600);
pinMode(PWMPin,OUTPUT); //该端口需要选择有#号标识的数字口
}
void loop()
{
char c;
while (Serial.available() > 0)
{
c=Serial.read();
if (']'==c)
{
n=n+5;
}
if ('['==c)
{
n=n-5;
}
if (n>255) {n=0;}
if (n<0) {n=255;}
analogWrite(PWMPin,n);
Serial.println(n);
}
}
我们使用 DFRobot 出品的 RoMeoBLE V1.0 作为实验器材。
占空比 250 时 , 250/255=98.039% ,测量值符合预期
占空比 200 时 ,200/255=78.431% ,测量值符合预期
占空比为0 时是一条低电平的直线,占空比255是一条高电平的直线,这里就不贴上来了。
不过特别注意到测量出来的频率是976.5Hz, 我更换了一个普通的UNO 结果还是 976HZ。这和很多资料中提到的 490Hz不同,经过研究,在【参考2】上找到了介绍,原来 D5 D6 的默认频率和其他的不同。换成 Pin6 修改上面的程序,看到的就是 490Hz了。
同样,根据【参考2】改一下频率,可以看到当前是 31.36884 KHz
int n=255;
const int PWMPin=9;
void setup()
{
Serial.begin(9600);
TCCR1B = TCCR1B & B11111000 | B00000001; // set timer 1 divisor to 1 for PWM frequency of 31372.55 Hz
pinMode(PWMPin,OUTPUT); //该端口需要选择有#号标识的数字口
}
void loop()
{
char c;
while (Serial.available() > 0)
{
c=Serial.read();
if (']'==c)
{
n=n+5;
}
if ('['==c)
{
n=n-5;
}
if (n>255) {n=0;}
if (n<0) {n=255;}
analogWrite(PWMPin,n);
Serial.println(n);
}
}
调整占空比为 245 时的样子
放大一点看看具体波形
参考:
1. http://www.diyleyuan.com/index.php?m=content&c=index&a=show&catid=29&id=616
2. https://arduino-info.wikispaces.com/Arduino-PWM-Frequency

3. http://playground.arduino.cc/Code/PwmFrequency 关于调整频率官方的介绍
4. https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM Secrets of Arduino PWM
http://www.diy-robots.com/?p=852 上面文章的翻译
http://www.diy-robots.com/?p=814 上面文章的翻译
标题看起来非常拗口,具体来说描述起来就是下面的问题:
“我想写一个简单的程序,先把某个app的Load进内存,然后在内存里爆搜一个特征字串,搜到之后将该内存第一个字节替换。以下为代码片段,碰到一个问题就是,我搜到特征字串之后,修改其内存的内容一直改不了,请问各位大大,是不是UEFI有相应的保护策略,不能修改LoadImage的内存?我个人觉得是不应该,因为我是LoadImage的宿主,我Load的内存应该是可以被我修改的。请大牛们指教啊!!!
Status=gBS->LoadImage(TRUE, ImageHandle, DstDevicePath, NULL, 0, &DstImageHandle); //LoadImage
if (!EFI_ERROR(Status))
{
Print(L”Load Image success\n”);
}
Status=gBS->HandleProtocol(DstImageHandle, &gEfiLoadedImageProtocolGuid,(void **) &LoadedImage);
if (EFI_ERROR(Status)) {
Print(L”Can not retrieve a LoadedImageProtocol handle for ImageHandle\n”);
gBS->Exit(ImageHandle,EFI_SUCCESS,0,NULL);
}
//Get the loaded image base address
imageBase=LoadedImage->ImageBase;
size=LoadedImage->ImageSize;
temp=(char *)imageBase;
//Search the sig;, replace the first byte
for (; temp<(char *)imageBase+size; temp++)
{
if (*temp==0x55 && *(temp+1)==0x00 && *(temp+2)==0x45 && *(temp+3)==0x00)
{
Print(L"Find sig\n");
*temp=0x45;
Print(L"addr %x\n",temp);
break;
}
}
上述问题来自【参考1】
这个一个有趣的问题,也不知道那个朋友最后是否成功。根据上面的问题,我做一下实验。
首先,准备一个被修改的 App。当然,根据之前的知识,这个 App 不能使用 CLIB 库,目前为止我还是不知道为什么无法加载调用这个库的 Application.
#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>
/**
The user Entry Point for Application. The user code starts with this function
as the real entry point for the application.
@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 other Some error occurs when executing this entry point.
**/
EFI_STATUS
EFIAPI
UefiMain (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
CHAR8 *s1= "This code comes from www.lab-z.com";
CHAR16 *s2=L" ";
CHAR16 *Result;
Result=AsciiStrToUnicodeStr(s1,s2);
Print(L"%s\n",s2);
return EFI_SUCCESS;
}
代码非常简单,将一个 ASCII 字符串转化为 Unicode 的,然后显示出来。使用 ASCII 的原因是为了便于查找。
上面的程序编译之后,使用十六进制工具打开可以直接查看到 ASCII 字符。
然后,继续编写加载和修改的程序如下:
#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;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
/**
GET DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
IN CHAR16 * CONST DeviceName OPTIONAL
)
{
//
// Check for UEFI Shell 2.0 protocols
//
if (gEfiShellProtocol != NULL) {
return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
}
//
// Check for EFI shell
//
if (mEfiShellEnvironment2 != NULL) {
return (mEfiShellEnvironment2->NameToPath(DeviceName));
}
return (NULL);
}
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_DEVICE_PATH_PROTOCOL *DevicePath;
EFI_HANDLE NewHandle;
EFI_STATUS Status;
UINTN ExitDataSizePtr;
CHAR16 *R=L"HelloWorld.efi";
EFI_LOADED_IMAGE_PROTOCOL *ImageInfo = NULL;
CHAR8 *temp;
Print(L"File [%s]\n",R);
DevicePath=ShellGetDevicePath(R);
//
// Load the image with:
// FALSE - not from boot manager and NULL, 0 being not already in memory
//
Status = gBS->LoadImage(
FALSE,
gImageHandle,
DevicePath,
NULL,
0,
&NewHandle);
if (EFI_ERROR(Status)) {
if (NewHandle != NULL) {
gBS->UnloadImage(NewHandle);
}
Print(L"Error during LoadImage [%X]\n",Status);
return (Status);
}
Status = gBS -> HandleProtocol (
NewHandle,
&gEfiLoadedImageProtocolGuid,
&ImageInfo
);
Print(L"ImageBase [%lX]\n",ImageInfo->ImageBase);
Print(L"ImageSize [%lX]\n",ImageInfo->ImageSize);
temp=(char *)ImageInfo->ImageBase;
//Search the sig;, replace the first byte
for (; temp<(char *)ImageInfo->ImageBase+ImageInfo->ImageSize; temp++)
{
//"lab" 6C 61 62
if (*temp==0x6C && *(temp+1)==0x61 && *(temp+2)==0x62)
{
Print(L"Find sig\n");
Print(L"addr %x\n",temp);
*(temp )=0x2D;
*(temp+1)=0x2D;
*(temp+2)=0x2D;
break;
}
}
//
// now start the image, passing up exit data if the caller requested it
//
Status = gBS->StartImage(
NewHandle,
&ExitDataSizePtr,
NULL
);
if (EFI_ERROR(Status)) {
if (NewHandle != NULL) {
gBS->UnloadImage(NewHandle);
}
Print(L"Error during StartImage [%X]\n",Status);
return (Status);
}
gBS->UnloadImage (NewHandle);
return EFI_SUCCESS;
}
加载部分的代码使用的是 【参考2】的框架,搜索部分的代码用的是前面问题中给出来的示例。我们在加载后的 Application 的空间中搜索 “lab” 字符串并且替换为 “—”。
运行结果如下,我们先执行了一次 HelloWorld.efi,可以看到他能正常打印字符串,之后再用我们的程序加载一次,可以看到字符串被修改掉了。
看起来并没有什么保护之类的,轻而易举的改掉了 Application 的内容。猜测之前提出问题的朋友有可能是被加载的代码用到了 CLIB, 或者是代码中的字符串是按照 UNICODE 给出来的,所以无法找到。
这样的动态加载可以用在一些特殊的地方,比如,我见过一款 DOS 下的测试软件,有一个主程序 EXE 和 n多个独立的 EXE 构成。主程序可以调用其他的 EXE 进行测试,但是单独的 EXE 无法执行,这样的好处是开发时可以独立开发单独模块,分发之后有主程序进行控制只在需要的环境中运行。
本文提到的代码下载
参考:
1. http://biosren.com/thread-4564-1-31.html
2. http://www.lab-z.com/efiloadedimageprotocol/ Step to UEFI (46) —– EFILOADEDIMAGEPROTOCOL的使用
有一个网友提出一个问题“现在要进行一个0.4us的延时,发现不管怎么调都只能调到15us,根本达不到要求,我用的芯片是MEGA32U4-AU,外部晶振是16MHz,求见解!!!!!”
我试验了一下,最终的程序如下:
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
for (long zdelay=0;zdelay<9; zdelay++) {
__asm__("nop\n\t");
}
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
这个程序运行之后的波形如下
可以看到中间的延时差不多有4.1331us(我用游标对齐,右下角显示x=4.1331). 让然这个值中还包括了一个拉GPIO的指令周期,大约会有 62.1ns的影响。此外,如果要求特别精确,在使用时还要考虑周期性中断的影响。这里就不说了…….
下面我们继续实验,尝试找到循环次数和实际delay时间的关系(因为涉及到编译器优化, long int的计算和判断,直接尝试计算机器周期不可行)。
首先尝试zdelay<8,测量结果是3.7013us

根据上述值结合循环简单猜测一下,对于这个循环体,固定部分耗时0.2469us (比如给变量赋初始值),循环部分每次耗时0.4318us
就是 T= 0.2469 + n *0.4318
根据这个计算循环zdelay<5应该是 2.4059us测量结果是2.3722us
加入我们打算delay 100us 根据上述公式应该循环 231次
代码:
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
for (long zdelay=0;zdelay<231; zdelay++) {
__asm__("nop\n\t");
}
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
最后,“极客工坊”的 sanyouhi 朋友指出,精确延时可以用写好的库来直接完成【参考1】
#define F_CPU 16000000
#include <util/delay.h>
void setup()
{
DDRB = 0X20;
}
void loop()
{
PORTB = 0X20;
_delay_us(0.4);
PORTB = 0;
}
对应的头文件在 \arduino-1.6.3\hardware\tools\avr\avr\include\util\delay.h 有机会的时候再研究一下。
后记,比较有意思,如果我把代码写为下面的形式
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
for (long zdelay=0;zdelay<17; zdelay++) {
}
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
中间delay 的周期和不写 for 循环是相同的,猜测原因是编译器的自动优化,当编译器发现空循环时会自动移除循环代码。
参考:
1.http://www.geek-workshop.com/thread-24982-1-1.html