在 TPanel 上做图

Delphi 的TPanel 是没有 Canavs属性的,因此无法直接在上面绘制图形。用下面的方法可以绕过限制,实现绘图的功能。

示例代码:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, StdCtrls;

type
  TForm2 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
var
  PanelCanvas:TControlCanvas;
begin
  PanelCanvas:=TControlCanvas.Create;
  PanelCanvas.Control:=panel1;
  with PanelCanvas do
  begin
      pen.Color:=clGreen;
      pen.Width:=5;
      MoveTo(0,0);
      LineTo(Panel1.Width,Panel1.Height);

      MoveTo(Panel1.Width-1,0);
      LineTo(0,Panel1.Height);
  end;

  PanelCanvas.Free;
end;

end.

 

按下Button后,会在 Panel 对角线绘制直线。

dp

代码和可执行文件下载

DrawOnPanel

Step to UEFI (40) —– 从Shell下向Windows传值

很早之前,BIOS如果想把一些值传递给OS,通常都是使用CMOS的。但是这样的方法有着下面的缺点:复杂度高,需要和使用位置的人协商,否则没人知道你放在CMOS中什么地方
不确定性大,可能会在代码中冲突,无法确定别人是否也用到你选择的那个CMOS位
每次传递值少,CMOS一般也就128 BYTES,很容易耗尽。

现在进化到了UEFI的时代,可以在 Shell 上将一些内容存放在内存中,然后轻松的传输到Windows中。这里只是演示这种做法,总体来说还是非常简单的。

最关键的函数就是下面这个:

Status = pBS->AllocatePool(EfiReservedMemoryType, Hdr1->Length, &Ptr);

 

#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_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                Status;	
  UINT8                		*Buffer;  
  
  Status = gBS -> AllocatePool (EfiACPIMemoryNVS, 0x100 , & Buffer);

  * Buffer    = 'L';
  *(Buffer+1) = 'A';  
  *(Buffer+2) = 'B';  
  *(Buffer+3) = '-';  
  *(Buffer+4) = 'Z';  
  *(Buffer+5) = '.';  
  *(Buffer+6) = 'C';  
  *(Buffer+7) = 'O';  
  *(Buffer+8) = 'M';    
  *(Buffer+9) = '1';   
  printf("Memory1 [%x]\n",Buffer);
  
  //Status = gBS -> FreePool (Buffer);

    Status = gBS -> AllocatePool (EfiReservedMemoryType, 0x100 , & Buffer);

  * Buffer    = 'L';
  *(Buffer+1) = 'A';  
  *(Buffer+2) = 'B';  
  *(Buffer+3) = '-';  
  *(Buffer+4) = 'Z';  
  *(Buffer+5) = '.';  
  *(Buffer+6) = 'C';  
  *(Buffer+7) = 'O';  
  *(Buffer+8) = 'M';    
  *(Buffer+9) = '2';   
  printf("Memory2 [%x]\n",Buffer);
  
  return EFI_SUCCESS;
}

 

运行结果(虚拟机下面的测试)
mo1

直接查看,可以看到确实在内存中写入了
mo2

mo3

再检查一下E820 Table,可以看到放置字符串的内存已经被标记为占用
mo4

之后,我们在实体机器上运行,首先还是shell下面写,然后到Windows中读取对应的内存。可以看到我们在对应的内存中能够看到写入的String.
mo5

mo6

因此,这个方法是可行的。

代码下载
Mem2OS

Arduino 遥控小灯的开关

目标:用 Arduino 遥控小灯的开关
材料:
1. Arduino Uno 一块
2. 2262/2272四路无线遥控套件M4非锁接收板 配四键无线遥控器(收发一套)
3. 继电器 (因为目前的Demo控制电压很小,所以最基础的型号就可以)
4. 5V充电宝一个
5. 面包板一块
6. 面包板电源一块
7. 小恐龙变色灯一个

选择无线遥控器的原因是:价格便宜,不涉及到解码输出简单稳定,易于使用。

原理上介绍是这样的:Arduino循环判断遥控接收模块输出管脚电压,如果出现HIGH,表示对应的按钮被按下。遥控器上有4个按钮,对应着接收器输入上面的D0-D3。实验使用D0 和 D1 两个脚分别控制继电器的开关。

电路图如下,左侧因为没有面包板电源的元件图样,所以用电池盒表示,实际电路中为一个3.3V电源。右侧因为没有接收模块的元件图样,所以用一个其他模块替代。无线模块在之前的实验中也使用过,可以在【参考1】上看到。

const int InputPinD0=8; //接收模块D0 Pin
const int InputPinD1=9; //接收模块D1 Pin
const int ControlPin=3; //控制继电器
void setup()
{
    pinMode(ControlPin,OUTPUT);     
    pinMode(InputPinD0,INPUT_PULLUP); //输入,上拉10K
    pinMode(InputPinD1,INPUT_PULLUP);     
}

void loop()
{
  int n =digitalRead(InputPinD0);  //检查引脚是否有遥控输入
  if (n==HIGH) {
    digitalWrite(ControlPin,HIGH);
  }

  int m =digitalRead(InputPinD1);  
  if (m==HIGH) {
    digitalWrite(ControlPin,LOW);
  }
     
}

 

对于上面提到的10K上拉,可以在【参考2】中查到。

原理图

315_bb

最后的样子

image (1)

工作视频

参考:

1. http://www.lab-z.com/%E7%94%A8-arduino-%E6%89%93%E9%80%A0ppt%E9%81%A5%E6%8E%A7%E5%99%A8/ 用 Arduino 打造PPT遥控器
2. http://geek-workshop.com/forum.php?mod=viewthread&tid=2874&highlight=%C9%CF%C0%AD Arduino入门教程–第二十三课–使用IO口内部上拉功能

Step to UEFI (39) —– 编写一个GetCursorPosition函数

我们知道,可以使用 ConOut 下面的 SetCursorPosition 来设置光标输出的位置【参考1】。

setcursorposition

但是,找了一圈也没有找到 GetCursorPosition ,我怎么知道当前光标在哪呢?查查C手册,通常使用conio.h 中的 wherex,wherey 来完成这个功能。问题是, clib 连这个头文件都没有……..

经过一番查找,在【参考2】中有人提到可以使用 Mode 中的 CursorColumn 和 CursorRow 来完成这个功能。于是,编写简单的程序验证一下,程序非常简单,随机生成一个位置显示X,然后在它后面输出当前这个X的位置:

#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_SYSTEM_TABLE			 *gST;

/**
  Get the position of cursor
**/
VOID GetCursorPosition(
	IN	UINTN	*x,
	IN	UINTN	*y
)
{
	*x = gST->ConOut->Mode->CursorColumn;
	*y = gST->ConOut->Mode->CursorRow;
	
	return;
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
	UINTN wherex;
	UINTN wherey;
	UINTN i;
	
	for (i=0; i<5; i++)
	  {
		gST -> ConOut -> SetCursorPosition(gST -> ConOut,rand()%70,rand() % 20);
		GetCursorPosition(&wherex,&wherey);
		Print(L"x[%02d]:[%02d] ",wherex, wherey);
		
	  }
  
  return EFI_SUCCESS;
}

 

运行结果:

Getcur

完整代码下载:

GetCursor

参考:

1.UEFI Spec 2.4 P460
2.http://biosren.com/viewthread.php?tid=4164&highlight=%B9%E2%B1%EA EFI下对应wherex()作用的FUN是哪个?该怎么用?

为什么我们需要客服人员

说点好玩的事情。我刚装机器的时候,安装了一个 ghost 版本的 win7 ,内置了QQ电脑管家,没想到一开机的时候这个软件就会崩溃。万般无奈,卸载之。然后发了一个说说。之后马上有客服加我QQ,后来又有回访的人加了我的QQ。 后来就没有了。

元旦的时候我又发了一次,提到这个事情,发表关于创业公司对大公司挑战的一点感想。其中有关键字,于是又有人加我,随后有下面的对话。

====================================================================
sunshine 2015/1/9 9:47:33
hi 请问您是?

Zt: 之前我遇到过电脑管家的问题

sunshine 2015/1/9 10:45:47
那目前呢?用管家有什么问题吗?

Zt:然后发了说说,你们就加我了.问题是启动的时候报错

sunshine 2015/1/9 10:46:31
是有崩溃的弹窗吗?

Zt:是的,我装的是 ghost 版本的

sunshine 2015/1/9 10:47:16
那下次碰到这个问题的时候 请点弹窗里的“错误详情” 里面会有2个文件,麻烦发给我一下。

Zt: 我可以给你找一下ghost文件,你们找个虚拟机试试

sunshine 2015/1/9 10:49:02
在正式环境下用吧
sunshine 2015/1/9 10:49:44
虚拟机我们自己也有 而且也有用户也在用 崩溃的情况还是很少见的 可能会有触发条件,需要有dump,就是崩溃文件。

Zt: 作为普通用户你觉得有多少人明白 dump文件。作为专业人士,你觉得有多少人用软件遇到问题,会用 windump 去调试?所以,这样的话,我还是换用其他软件了。

sunshine 2015/1/9 10:54:35
崩溃(crash)的时候错误详情里就是dump文件,如果用户点了窗口里的上报,也有会上报记录,我们的开发人员如果发现这个crash率异常升高,会去会查dump必要的话也会回访用户进行联系,但如果没有任何信息,我们也没办法去跟进。

sunshine 2015/1/9 10:55:40
您如果有重现问题,可以及时把这个文件发给我们看看,我们来查询下原因

sunshine 2015/1/9 10:57:03
并不是说我们不用ghost,不用虚拟机,我们版本发布之前会做各种兼容性测试,也会有用户帮我们体验,这部分用户也有很多是用虚拟机的,如果有重现的crash我们都会去跟进,但这些crash的原因并不一定都是一样的。

Zt: 谢谢 理解

====================================================================

事情不大,对我也没有什么影响。qq电脑管家唯一值得称赞的地方就是卸载做的很好。卸载程序运行之后几乎没有残留。

针对这个事情,我觉得值得更深入的思考。

对于腾讯来说,这个并非主营业务,所以毫无影响,最多是给 ghost装机的钱打了水漂(这个是很有中国特色的事情)。客户则会留下腾讯在安全领域毫无实力的印象。这都不是什么大问题。

进一步思考,我觉得这是不能直接让技术人员面对客户的问题,原因有两点:

1.技术人员工资通常比客服高,如果直接让技术人员面对客户,成本比较高

2.技术人员的思维就是纯技术性的。作为技术人员,我也直接面对过客户。深知无法重现的问题确实无法解决。这样的事情不会有耐心去复现。

3.不可能解决所有客户的问题。让大部分人满意或者说让重要的人满意就可以了。我在上一家公司,从老板那里学到的一个很重要的原则是:“客户不是上帝,只有大客户才是”

所以,客服人员之类的还是非常必要的。

Delphi 中的 TMediaPlayer 全屏

一个Delphi中使用TMediaPlayer全屏播放的例子,做法挺简单的。需要在TForm上放置2个按钮,一个用来播放,一个用来关闭TForm。然后再放置一个 TMediaPlayer 和 TPanel 控件即可。

所有的属性都是在运行期设置的。代码如下:

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, MPlayer, StdCtrls;

type
  TForm2 = class(TForm)
    MediaPlayer1: TMediaPlayer;
    Panel1: TPanel;
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  if MediaPlayer1.DeviceID<>0 then
    begin
      if (MediaPlayer1.Mode=mpplaying) then MediaPlayer1.Stop;
    end;

  MediaPlayer1.FileName:='md.wmv';
  MediaPlayer1.Open;
  MediaPlayer1.Play;
  MediaPlayer1.DisplayRect:=Rect(0,0,Screen.Width,Screen.Height);
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
  Form2.Close;
end;

procedure TForm2.FormActivate(Sender: TObject);
begin
  //设置Form没有边框
  Form2.BorderStyle:=bsNone;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  //Form最大化
  WindowState:=wsMaximized;
  //Panel也是最大化
  Panel1.Align:=alClient;
  //设置播放显示在Panel1上
  MediaPlayer1.Display:=Panel1;
end;

end.

 

默认情况下 XP 和 Windows7 至少要支持WMV ,下面是一个测试的视频

md

源代码和可执行文件下载

FullScreen

Step to UEFI (38) —– SetTimer 设定定时器(下)

上一篇是直接输出一个计数器,我们还可以让他直接打印当前的时间,需要修改的代码不多,替换Timeout函数即可:

/**
  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
  )
{
  EFI_STATUS  Status;
  EFI_TIME   ET;

  Status = gRT->GetTime(&ET, NULL);
  
  Print(L"%02d:%02d:%02d\r\n",ET.Hour,ET.Minute,ET.Second);
  return ;
}

 

settimes1

可以看到符合我们的期望。

接下来,我们之前的文章提到CLIB中也有时间相关的函数,我们尝试直接使用ctime函数。改动很小

/**
  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
  )
{
  time_t t;

  time(&t);
 
  printf("%s\n",ctime(&t));
  
  return ;
}

 

但是会导致TPL错误

2b51136803959fa789bb6da4793609ad

b723a4c7633c0b1b141d33777dcc3942

错误原因不详。我请 HZZZ 帮忙看了一下,他发现如果我们使用默认的Shell_Full.efi就会出现问题,但是如果使用代码重新编译一个出来就不会有同样的问题(意思是:我们能够确定这是Shell本身的问题,但是没有会出现问题的Shell代码,因此无从得知Root Cause)。会出现下面的问题

timertest3

对于这个问题,HZZZ Debug的结果是:新编译出来的Shell不支持 SE2 这个Protocol…….

因此,如果想写一个兼容性强的程序,最好直接使用 UefiShellLib 提供的 ShellGetExecutionBreakFlag 函数。这个函数会自动判断当前有哪个Protocol,然后调用存在的功能。

做一个蓝牙小车

起因是这样的:一个壕朋友希望我帮忙给他做个小车。具体要求是:能用蓝牙控制,负重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