Step to UEFI (41) —– x64 的 FreqCalc程序

之前的一篇文章《Step to UEFI (9)—-使用RDTSC计算当前CPU 频率》【参考1】给出了一个计算当前CPU频率的方法。不过 Tim 给我留言,他说这篇文章的程序无法在 x64下正常编译:

freq

我猜测原因是因为我的程序使用的内嵌汇编,内嵌汇编无法被X64的编译器正常编译的。关于这个说法可以看【参考2】。

动手在 UDK2014 下面实验,不过在看到描述的问题之前还是要走一段路的。

使用编译命令 build -a X64 -p AppPkg\AppPkg.dsc 得到下面的错误信息

c:\edk\AppPkg\AppPkg.dsc(94): error 000E: File/directory not found in workspace
        c:\edk\PerformancePkg\Library\DxeTscTimerLib\DxeTscTimerLib.inf

 

检查 AppPkg.dsc

修改

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/DxeTscTimerLib/DxeTscTimerLib.inf

 

修改为

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/TscTimerLib/DxeTscTimerLib.inf

 

再次编译,有下面的错误信息

Processing meta-data .. done!
Building ... c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\.\CheckSum.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /FIAutoG
en.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /X /Zc:wchar_t /GL- /Ic:\edk\MdePkg\Library\BaseLib\X64  /Ic:\edk\MdePkg\Library\BaseLib  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\M
dePkg\Include  /Ic:\edk\MdePkg\Include\X64 c:\edk\MdePkg\Library\BaseLib\CheckSum.c
'C:\Program' 不是内部或外部命令,也不是可运行的程序或批处理文件。
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe' : return code '0x1' Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 

build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:22:09, Mar.09 2015
Build total time: 00:00:03

 

尝试在命令行窗口直接运行”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe” 错误提示是:

C:\EDK>"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"
系统找不到指定的路径。

 

这是因为目前的编译环境缺少 64位编译器。我使用的是 vs2008 express 版本,默认是没有 64位的cl.exe的。

找到 ‘x86_amd64’ 拷贝到 “C:\Program Files\Microsoft Visual Studio 9.0\VC\bin”

需要的朋友可以在这里下载到 x86_amd64

再次编译,然后就能看到 Tim 说的问题啦

C:\Program Files\Microsoft Visual Studio 9.0\VC\bin

Building ... c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\OUTPUT\.\FreqCalc.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /
FIAutoGen.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /Ic:\edk\AppPkg\Applications\FreqCalc  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\MdePkg\Include  /Ic:\edk\MdePkg\Includ
e\X64  /Ic:\edk\ShellPkg  /Ic:\edk\ShellPkg\Include  /Ic:\edk\MdeModulePkg  /Ic:\edk\MdeModulePkg\Include c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c
FreqCalc.c
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(15) : error C4235: nonstandard extension used : '__asm' keyword not supported on this architecture
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : warning C4550: expression evaluates to a function which is missing an argument list
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'value'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'eax' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2109: subscript requires array or pointer type
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2065: 'edx' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2143: syntax error : missing ';' before '}'
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"' : return code '0x2'
Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:26:31, Mar.09 2015
Build total time: 00:01:16

 

然后就考虑如何解决,网上搜索了一下,有人遇到同样的问题【参考3】,具体的解决方法在【参考4】

简单的说,就是单独写一个 asm 然后在对应的 Inf中声明一下即可。

针对我遇到的问题,程序如下

FreqCalc.inf

## @file
#  Sample UEFI Application Reference EDKII Module
#
#  This is a sample shell application that will print "UEFI Hello World!" to the 
#  UEFI Console based on PCD setting.
#
#  It demos how to use EDKII PCD mechanism to make code more flexible.
#
#  Copyright (c) 2008 - 2011, Intel Corporation. All rights reserved.<BR>
#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = FreqCalc
  FILE_GUID                      = 6987936E-ED34-44db-AE97-2FA5E4ED2216
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain 

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources]
  FreqCalc.c
  ReadTsc1.asm

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  PcdLib
  ShellCEntryLib
  ShellLib


[FeaturePcd]


[Pcd]

 

然后在FreqCalc.c 写成下面这样,特别注意要声明一下你用的那个汇编语言中的函数名

//
// FreqCalc.C
//

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

EFI_SYSTEM_TABLE	*gST;
EFI_BOOT_SERVICES  	*gBS;

UINT64
EFIAPI
zReadTsc (
  VOID
);

//
// Entry point function - ShowVersion
//
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{

  UINT64  elsp;

  gST = SystemTable;
  gBS = SystemTable->BootServices;

  elsp=zReadTsc();

  gBS -> Stall(1000000);  
  Print(L"CPU Frequency: %ld \n",zReadTsc() - elsp);

  return EFI_SUCCESS;
}

 

然后还有一个ReadTsc1.asm (其实 uefi 里面提供了一个 x64的 ReadTsc.asm,可以直接使用,这里是为了让读者看的更清楚,取了一个不会重复的名称)

    .code

;------------------------------------------------------------------------------
; UINT64
; EFIAPI
; zReadTsc (
;   VOID
;   );
;------------------------------------------------------------------------------
zReadTsc  PROC
    rdtsc
    shl     rdx, 20h
    or      rax, rdx
    ret
zReadTsc  ENDP

    END

 

接下来就可以正常编译了,生成的 efi 文件会比 x64的大一些。然后我在实体机的 x64 shell下面运行成功。就是说上面的方法是没问题的。

FreqCalc

参考:

1.http://www.lab-z.com/rdtsc/ Step to UEFI (9)—-使用RDTSC计算当前CPU 频率

2.http://stackoverflow.com/questions/1295452/why-does-msvc-not-support-inline-assembly-for-amd64-and-itanium-targets 简单解释,说x64下无法支持 _ASM汇编

3.http://biosren.com/viewthread.php?tid=6822&highlight=%C4%DA%C7%B6%2B%BB%E3%B1%E0 請問如何在EDK2的AppPkg裡面使用asm (有人问同样的问题)

4.http://www.biosren.com/thread-6632-1-1.html 給個UEFI 內嵌彙編的小程序吧? 本文的主要参考,其中介绍了如何写单独写一个ASM文件

LM386 with MIC

最近有一个获取环境声音送入Arduino处理的需求。想起来之前入手过带有microphone模拟输出的模块。找出来之后发现他有模拟输出,但是应该是因为放大后的信号太小(没有功率放大),无法达到Arduino的量化要求(可惜没有示波器否则可以定量分析一下)。
image002

后来又想起之前入手过一个音频放大模块,主芯片是 LM386,于是拿出来实验。

image004

我的第一个错误是:没搞清楚输入和输出。一端是 GND GND IN VCC (两个GND是连通的),一端是 OUT 和 GND。刚开始我错误的认为OUT和GND端应该接MIC,IN 用来驱动喇叭。后来认真看了卖家商品介绍,上面提到“板载喇叭接线座”方才恍然大悟。IN 应该是输入的音频信号,另外一端是对喇叭的直接驱动。

第二个错误是:我将MIC直接接入IN。试验了很久都没有反应。隐约觉得什么地方搞错了。后来在网上搜索了一下LM386的典型应用【参考1】,又恍然大悟,要输入的是不断变化的信号,而非直流。

image005

咪头的接法,来自【参考1】.实际电路中,我用的是24K的电阻。
image006

图片来自【参考1】

下面这部分电路不用管,LM386模块完全负责了。

lmsch

此外还需要特别注意的是电路中的MIC有正负极的差别,100uF的电容也有正负极的区别,接反了不工作。

最终,实物是这样的:

lm3861

模块上有一个可以调节放大倍数的可变电阻。放大倍数太大会出现噪音很大失真严重的问题。

工作视频(只是演示工作情况,电路方面和上面的介绍有差别):

http://www.tudou.com/programs/view/KiIwulH_p2s/?resourceId=0_06_02_99

额外的话

1.麦克风的英文是Microphone ,缩写是 MIC(又叫咪头,如果你想在淘宝上买的话,直接搜咪头就好)。我以为英文应该是 micphone,上网搜了一下发现犯这样错的人真不少……

2.供电对功放的影响很大。开始我用USB适配器(220V-5V)作为电源,噪音挺大的;后来换了一个USB充电宝效果就好了很多,估计如果有可能直接用干电池效果会更好吧?

3.时代在变化,上一次买的语音放大套件还都是独立元件,焊接调试都是蛮麻烦的事情,转眼间集成度就高了很多。

参考:

1. http://www.arduino-hacks.com/arduino-vu-meter-lm386electret-microphone-condenser/ Arduino VU meter – LM386+electret microphone condenser

2. http://www.learningaboutelectronics.com/Articles/How-to-connect-a-LM386-audio-amplifier-chip How to Connect a LM386 Audio Amplifier Chip to a Circuit

3. https://lowvoltage.wordpress.com/2011/05/15/lm386-mic-amp/ LM386 microphone amplifier

=================================================================
2015年4月6日 又研究了一下上面提到的带有麦克风的模块,是通过 LM358D 来进行放大的。我在网上查到有LM358和LM386一同工作来做一个小功放的。我试验了一下,结果比我直接MIC+LM368差多了,根本出不来什么声音。可能是电路设计上的缘故吧,毕竟那个模块的主要作用是输出当前是否出现了一个超过设定音强的声音(有个好听的名字叫“口哨开关”)。

Arduino Uno 采样速度

首先测试一下普通的 Arduino UNO 的采样速度

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  analogReference(INTERNAL); //调用板载1.1V基准源
}


void loop() {
  int i;
  float voltage;
  int sensorValue;
  unsigned long elsp=millis();
  for (i=0;i<10000;i++)
    {
      // read the input on analog pin 0:
      sensorValue = analogRead(A0);
    }  
  Serial.println(millis()-elsp);
  delay(10000);
}

 

运行结果是 1120 左右,就是说采样10000次用时1.12s,采样频率大约是 8928.57Hz (次/秒)。

找了一段代码【参考6】能够提高采样速度

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

 

写个程序测试一下

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  analogReference(INTERNAL); //调用板载1.1V基准源

// set prescale to 16
sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;  
}


void loop() {
  int i;
  float voltage;
  int sensorValue;
  unsigned long elsp=millis();
  for (i=0;i<10000;i++)
    {
      // read the input on analog pin 0:
      sensorValue = analogRead(A0);
      
    }  
  Serial.println(millis()-elsp);

  delay(10000);
}

 

运行输出结果在 170 左右,就是说采样10000次用时0.170s,采样频率大约是 58823.53Hz (次/秒)。

原理上来说Arduino 的 ADC 是用一种逐次逼近比较的方法来进行ADC转换的。通俗的说就是内部有一个比较器,每次内部升高一个电压和外部指定的Pin进行比较,根据大于小于来判定外部指定脚上的电压。资料说做这样一个比较大约需要10次,13个机器周期。而比较的频率是主控频率分频给出来的,有一个分频因子,默认是128,分频因子越小,比较的速度也就越快。

adcfreq

这里只是简单说一下,如果想了解ADC具体的工作原理,请阅读参考中提到的文章。

多说两句批评一下《Arduino技术内幕》这本书,刚开始我是阅读这本书来学习Arduino ADC的,但是阅读的时候感觉一直在外面游荡,不知道是原作者不懂还是翻译的人不明白,反正这一章的介绍让我一头雾水。

ainside

参考:

1. http://www.geek-workshop.com/thread-1848-1-1.html Arduino入门教程–第十七课–使用内部基准源进行ADC采样
2. 冷知识-这次讲ADC采样率-11月8号更新-上传高速率采样库-后续加入示波器库
http://www.geek-workshop.com/thread-11511-1-1.html
3. http://apcmag.com/arduino-analog-to-digital-converter-how-it-works.htm/ 介绍 Arduino ADC 如何工作
4. http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/
5. http://wenku.baidu.com/link?url=I3DA7sWeIOxPntLf89u_MO8V30InxZlEWzDu7BxsXPQlJtprgkzUsdQmIEiBPSOBrdq1_iccg-qxxkOh1ROvfz3C9vbt55Axy_f1JAFZJTq 基于Arduino的音乐频谱显示器方案概述
6. http://forum.arduino.cc/index.php?topic=6549.0
7. http://meettechniek.info/embedded/arduino-analog.html Arduino: Analog measurements

在 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,然后调用存在的功能。