Step to UEFI (44) —– 获得按键

在Shell下面编写工具程序,我们经常需要和用户进行交互,需要取得客户按键的信息。

对于这个问题,可以使用 EFI_SIMPLE_TEXT_INPUT_PROTOCOL 的 ReadKeyStroke 来解决【参考1】

readkeystroke

写一个小程序来验证一下

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

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.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;


int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_INPUT_KEY	Key;
  EFI_STATUS Status;
  
  while (1)
	{
		Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);	
		if (Status == EFI_SUCCESS)	{
			break;
		}
		gST -> ConOut -> OutputString(gST->ConOut,L"Test......");
		gBS->Stall(500);
	}  
  Print(L"\n\r Scancode [%d], UnicodeChar [%c] \n\r",Key.ScanCode,Key.UnicodeChar);
  return EFI_SUCCESS;
}

 

按下 F1 检测结果:

F1key

按下 q键 检测结果:

qkey

代码下载

KeyTest1

参考:

1.UEFI 2.4 P445

Arduino Pro Micro 打造PPT遥控器

之前,我们介绍过如何用Arduino Uno打造一个PPT遥控器【参考1】,缺点是制作过程复杂,用到的器件较多导致整体稳定性不好。这里介绍使用 Arduino Pro Micro来做一个同样的PPT遥控器。

原理上和之前的并没有多少差别,都是通过模拟USB键盘的方式来进行控制。差别在于Pro Micro是缩小版的 Leonardo ,内部集成了USB Slave控制器,我们不需要再花费精力模拟自己为USB Keyboard.
硬件连接示意图:

ppt1

代码是直接修改自示例“KeyboardMessage”

ppt2

/* 
 Keyboard Button test
 
 For the Arduino Leonardo and Micro.
 
 Sends a text string when a button is pressed.
 
 The circuit:
 * pushbutton attached from pin 2 to +5V
 * 10-kilohm resistor attached from pin 4 to ground
 
 created 24 Oct 2011
 modified 27 Mar 2012
 by Tom Igoe
 
 This example code is in the public domain.
 
 http://www.arduino.cc/en/Tutorial/KeyboardButton
 */

const int buttonPinA = A1;          // input pin for A
const int buttonPinB = A2;          // input pin for B
int previousButtonStateA = HIGH;   // for checking the state of a pushButton
int previousButtonStateB = HIGH;   // for checking the state of a pushButton

void setup() {
  // make the pushButtonA pin an input:
  pinMode(buttonPinA, INPUT);
  // make the pushButtonB pin an input:
  pinMode(buttonPinB, INPUT);
  // initialize control over the keyboard:
  Keyboard.begin();
  delay(10000);
}

void loop() {
  // read the pushbutton:
  int buttonStateA = digitalRead(buttonPinA);
  int buttonStateB = digitalRead(buttonPinB);  
  // if the button state has changed, 
  if ((buttonStateA != previousButtonStateA) 
    // and it's currently pressed:
  && (buttonStateA == HIGH)) {
    // type out a message
    //Keyboard.print("A");
    Keyboard.press(KEY_LEFT_ARROW);
    Keyboard.releaseAll();
  }
  // if the button state has changed, 
  if ((buttonStateB != previousButtonStateB) 
    // and it's currently pressed:
  && (buttonStateB == HIGH)) {
    // type out a message
    //Keyboard.print("B");
    Keyboard.press(KEY_RIGHT_ARROW);
    Keyboard.releaseAll();
  }  
  // save the current button state for comparison next time:
  previousButtonStateA = buttonStateA; 
  previousButtonStateB = buttonStateB;   
}

 

实物图

ppt3

工作的视频

http://www.tudou.com/listplay/mAjgE5Ac_Sg/Fz0piTvQ9Cs.html?resourceId=414535982_06_02_99

参考:

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://arduino.cc/en/Reference/KeyboardModifiers 键值定义

Step to UEFI (43) —– 取得当前目录的 EFI_DEVICE_PATH_PROTOCOL

在 EFI_SHELL_PROTOCOL 中提供了 EfiShellGetDevicePathFromFilePath 函数

具体可以在 \ShellPkg\Application\Shell\ShellProtocol.h 看到原型

/**
  Converts a file system style name to a device path.

  This function converts a file system style name to a device path, by replacing any
  mapping references to the associated device path.

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

  @return                       The pointer of the file path. The file path is callee
                                allocated and should be freed by the caller.
  @retval NULL                  The path could not be found.
  @retval NULL                  There was not enough available memory.
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
EfiShellGetDevicePathFromFilePath(
  IN CONST CHAR16 *Path
  )

 

在【参考1】中,可以看到下面的介绍

getcurdp

经过试验,这个函数结合之前我们获得当前目录的函数完全可以达到目标。但是,如同【参考2】提到的,某些情况下,因为 Shell 不提供 EFI_SHELL_PROTOCOL,这个函数无法使用。

再查查 EFI_SHELL_ENVIRONMENT2 发现其中有功能类似的 NameToPath 函数

Convert a file system style name to a device path.

This function will convert a shell path name to a Device Path Protocol path. This function will allocate any required memory for this operation and it is the responsibility of the caller to free that memory when no longer required.

If anything prevents the complete conversion free any allocated memory and return NULL.

Parameters
[in]	Path	The path to convert.
Return values
!NULL	A pointer to the callee allocated Device Path.
NULL	The operation could not be completed.

 

最后,决定仿照 ShellLib.h 中的 ShellGetCurrentDir,写一个混合上述两种函数的函数。最后的代码就是这样

#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;
/**
  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;
  CHAR16 *R;
  
  R=(CHAR16 *)ShellGetCurrentDir(NULL);
  
  Print(L"Current dir [%s]\n",ShellGetCurrentDir(0));
  
  DevicePath=ShellGetDevicePath(R);
  
  Print(L"Type        [%d]\nSub-Type    [%d]\nLength      [%d]\n",
				DevicePath->Type,
				DevicePath->SubType,
				DevicePath->Length[0] + DevicePath->Length[1]*256);
  
  return EFI_SUCCESS;
}

 

运行结果

getCurDP

代码下载

GetCurDP

参考:

1.UEFI_Shell_Spec_2_1_July02release P28
2.http://www.lab-z.com/getcurdir2/ Shell GetCurDir 补遗
3.http://www.bluestop.org/edk2/docs/UDK2010.SR1/struct_e_f_i___s_h_e_l_l___e_n_v_i_r_o_n_m_e_n_t2.html EFI_SHELL_ENVIRONMENT2 Struct Reference

Step to UEFI (42) —– 给Shell加一个时间显示功能

目标是:在Shell的右上角实现一个时间显示,类似DOS下的那种内存驻留程序,不影响其他程序的运行一直显示时间。

方法:首先想到的最简单方法是修改Shell.bin的代码,在开始的地方加入一个定时器,触发之后,首先保存当前光标位置,然后移动光标到右上角,自动更新显示时间,最后再将光标移回原位置。

需要修改的文件是 \ShellPkg\Application\Shell 下面的 Shell.C

定时器触发之后动作的代码如下:

//LabZDebug_Start
/**
  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_TIME   ET;
  UINTN		x;
  UINTN		y;
  

  //Get cursor postion
  x = gST->ConOut->Mode->CursorColumn;
  y = gST->ConOut->Mode->CursorRow;
  //Move cursor to Up-Left 
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,70,0);  
  //Output current time
  gRT->GetTime(&ET, NULL);  
  Print(L"%2d:%2d:%2d",ET.Hour,ET.Minute,ET.Second);
  //Move cursor back
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,x,y);  

  return ;
}
//LabZDebug_End

 

设置定时器的代码如下

	//LabZDebug_Start
	Status  = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL | EVT_TIMER,
                    TPL_CALLBACK,
                    Timeout,
                    NULL,
                    &TimerOne
                    );

    if (EFI_ERROR (Status)) {
        Print(L"Create Event Error! \r\n");
    }
	else {
		Status = gBS->SetTimer (
                   TimerOne,
                   TimerPeriodic,
                   MultU64x32 (1000, 1)
                   );

    if (EFI_ERROR (Status)) {
        Print(L"Set Timer Error! \r\n");
		}
	}
	//LabZDebug_End

 

最后还要销毁定时器,否则会发生 exit 到 setup 之后崩溃的情况

FreeResources:
	//LabZDebug_Start
	    gBS->SetTimer (TimerOne, TimerCancel, 0);
		gBS->CloseEvent (TimerOne);	
	//LabZDebug_End

 

之后需要重新编译出来 Shell.efi 替换原先的文件。运行结果如下

stim

工作的视频:

http://www.tudou.com/programs/view/DcPCkOrO2lQ/?resourceId=414535982_06_02_99

完整的代码下载

ShellC

Arduino 打造一个音量计

根据上一次的LM386的设计【参考1】,以及网上的设计【参考2】,用Arduino做一个音量计。首先,音频从MIC进入,经过LM386的放大后接入到Arduino的模拟输入上,经过 DAC 量化之后显示在 1602上。

电路方面,和【参考1】差别在于我们不再使用喇叭而是直接将放大后的OUT信号接入到 Arduino的A5上。

显示方面,我们使用【参考3】提到的方法来自定义字符充当强度指示。

下面的图片与其说是原理图不如说是连接图更合适

vu1

//www.lab-z.com 
//在 1602 上显示音量的小程序

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

int value=100;

// custom charaters

LiquidCrystal_I2C lcd(0x27,16,2);

//定义进度块
byte p1[8] = {
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10};

byte p2[8] = {
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18};

byte p3[8] = {
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C};

byte p4[8] = {
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E};

byte p5[8] = {
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F};

void setup() {
               lcd.init();           //初始化LCD
               lcd.backlight();		 //打开背光
				
			//将自定义的字符块发送给LCD
			//P1 是第一个,P2 是第二个,以此类推
                lcd.createChar(0, p1);			 
                lcd.createChar(1, p2);
                lcd.createChar(2, p3);
                lcd.createChar(3, p4);
                lcd.createChar(4, p5);
            //MIC输入放大之后在 A0 输入Arduino    
                pinMode(A0, INPUT);
}

//显示音量强度
//从左到右一共有 5 * 16 =80 点,一共是 80+1=81 个状态
void showprg(int value)
{
        //第一行显示当前VU值
                lcd.setCursor(0,0);
                lcd.print("     VU=");
                lcd.print(value);

                
		//移动光标到第二行
                lcd.setCursor(0,1);

        //显示全黑的块
                for (int i=1;i<value / 5;i++) {
                                lcd.write(4);
                        } //for (int i=1;i<a;i++) 


                // drawing charater's colums
		// 显示除去全黑块之后的零头
                switch (value % 5) {
                  case 0:
                        break;
                  case 1:
                        lcd.write(0);
                        break;
                  case 2:
                        lcd.write(1);
                        break;
                  case 3:
                        lcd.write(2);
                        break;
                  case 4:
                        lcd.write(3);
                        break;
                  } //switch (peace)

	       // 用空格填充剩下的位置
              for (int i =0;i<(16-value / 5);i++) {
                lcd.print(" ");   }
}

void loop()
{
     //输入的是0-1023,用函数将这个值对应到[0,80]上 
        showprg(map(analogRead(A0),0,1023,0,80));
		delay(100);
              
}

 

测试方法,在右边用一个手机播放声音,MIC将音频信号转化为电平信号,经过LM386放大后,通过Arduino A5进行量化,最终显示在LCD1602上。

vu2

工作视频:

http://www.tudou.com/programs/view/oW_isUBnIEU/?resourceId=414535982_06_02_99

这个还只是一个模型,很粗糙,对于VU的动态显示范围不大,如果想让人类更容易理解需要考虑将现在的线性显示改为非线性的。

另外,工作时我发现比较奇怪的事情,就是如果静音的时候,会显示UV 大约 35左右,但是如果有动态声音播放的时候反而会出现 16 这样值,不清楚原因。

==============================================================================
另外的另外,研究了一下前面那个口哨开关的模拟输出。下面是电路图,右下角红色框起来的是模拟输出电路部分

vu3

查了一下,发现这种用法是电压跟随器,下图来自【参考4】。

vu4

电压跟随器,顾名思义,是实现输出电压跟随输入电压的变化的一类电子元件。也就是说,电压跟随器的电压放大倍数恒小于且接近1【参考5】。
多少输入就是多少输出…….这也就是为什么这个模块驱动能力很差带不动喇叭的原因。
参考:

1. http://www.lab-z.com/lm386-with-mic/ LM386 with MIC
2.http://www.arduino-hacks.com/arduino-vu-meter-lm386electret-microphone-condenser/ Arduino VU meter – LM386+electret microphone condenser
3. http://www.lab-z.com/1602progressbar/ 用 1602实现进度条
4. http://www.geek-workshop.com/thread-551-1-1.html LM358双运算放大器
5. http://baike.baidu.com/link?url=85VzUTT6n3IrCaAATZSg5jsg_A-zQYFrizj6jqGP7PzRg1aJe2yTddMihqJ7_UgRMa_GxnAYakSmmjM-9nhsz_ 电压跟随器

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