Totol Phase I2C工具选型指南
还是上次那颗芯片的问题,还在Debug。有了新的需求,忽然发现没有合适的工具,只好研究一番
上一篇给出了GetCurDir的方法,不过这样玩多多少少看起来让人觉得不够简洁。于是又在EDK2 ShellPkg中翻了一下,在\ShellPkg\Include\Library\ShellLib.h 中有定义 ShellGetCurrentDir 函数
/** Retreives the current directory path. If the DeviceName is NULL, it returns the current device's current directory name. If the DeviceName is not NULL, it returns the current directory name on specified drive. @param[in] DeviceName The name of the file system to get directory on. @retval NULL The directory does not exist. @retval != NULL The directory. **/ CONST CHAR16* EFIAPI ShellGetCurrentDir ( IN CHAR16 * CONST DeviceName OPTIONAL );
看一下具体实现在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c
/** Retreives the current directory path If the DeviceName is NULL, it returns the current device's current directory name. If the DeviceName is not NULL, it returns the current directory name on specified drive. @param DeviceName the name of the drive to get directory on @retval NULL the directory does not exist @return != NULL the directory **/ CONST CHAR16* EFIAPI ShellGetCurrentDir ( IN CHAR16 * CONST DeviceName OPTIONAL ) { // // Check for UEFI Shell 2.0 protocols // if (gEfiShellProtocol != NULL) { return (gEfiShellProtocol->GetCurDir(DeviceName)); } // // Check for EFI shell // if (mEfiShellEnvironment2 != NULL) { return (mEfiShellEnvironment2->CurDir(DeviceName)); } return (NULL); }
就是说它内部实际上是直接判断了当前哪个Protocol能用,就用对应的来实现取得当前目录的功能。于是,直接使用它就能避免我们需要特别选择 Protocol 的烦恼。尝试编写程序如下
#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; int EFIAPI main ( IN int Argc, IN char **Argv ) { CHAR16 *R=0; R=(CHAR16 *)ShellGetCurrentDir(NULL); Print(L"%s",ShellGetCurrentDir(0)); return EFI_SUCCESS; }
同时给出对应的 INF,如果你再编译时出现Link之类的错误,最大的可能就是头文件和INF中没有引用对应的库支持。
## @file # A simple, basic, application showing how the Hello application could be # built using the "Standard C Libraries" from StdLib. # # Copyright (c) 2010 - 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. # # 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 = 0x00010006 BASE_NAME = getcurdir2 FILE_GUID = 4ea97c46-7491-4dfd-0028-747010f3ce5f MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib # # VALID_ARCHITECTURES = IA32 X64 IPF # [Sources] GetCurDir2.c [Packages] StdLib/StdLib.dec MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] LibC LibStdio ShellCEntryLib ShellLib [Protocols] [BuildOptions]
从功能上来说,和之前的完全一样
翻看 Shell Specification【参考 1】,发现上面介绍了一个Shell下取得当前目录的函数
看起来这个函数很简单,于是动手写程序。注意到 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中有声明,于是直接 extern EFI_SHELL_PROTOCOL *gEfiShellProtocol; 过来用。之所以能这样做,是因为这个位置是 Shell 程序在编译过程中特别加入的头,具体请参考之前的文章。
编译完成之后,进入虚拟环境实验,非常奇怪,每次运行都会出现错误提示,然后NT32就会崩溃掉。
真相到底是什么呢?一检查,gEfiShellProtocol == 0 。还要追根溯源,到定义他的地方去查看。
代码在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c
为了Debug,在关键的地方添加上的一点代码,输出信息
Status = gBS->OpenProtocol( ImageHandle, &gEfiShellParametersProtocolGuid, (VOID **)&gEfiShellParametersProtocol, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (EFI_ERROR(Status)) { gEfiShellParametersProtocol = NULL; } SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestA\n\r"); //LabZDebug if (gEfiShellParametersProtocol == NULL || gEfiShellProtocol == NULL) { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestB\n\r"); //LabZDebug // // Moved to seperate function due to complexity // Status = ShellFindSE2(ImageHandle); if (EFI_ERROR(Status)) { DEBUG((DEBUG_ERROR, "Status: 0x%08x\r\n", Status)); mEfiShellEnvironment2 = NULL; } Status = gBS->OpenProtocol(ImageHandle, &gEfiShellInterfaceGuid, (VOID **)&mEfiShellInterface, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (EFI_ERROR(Status)) { mEfiShellInterface = NULL; } } if (0==gEfiShellProtocol) { SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestE\n\r"); //LabZDebug } SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestC\n\r"); //LabZDebug
输出的结果是: TestA TestB TestE 和 TestC。 就是说这段代码尝试取 Shell Protocol 但是没有取到。再仔细看一下代码,这里没有取得到。因此,此路不通。再向下看发现初始化了EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2,再看了一下他的定义,在\ShellPkg\Include\Protocol\EfiShellEnvironment2.h 中
/// 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; SHELLENV_CUR_DIR 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;
因此,尝试一下SHELLENV_CUR_DIR. 这个函数头文件也在同一个文件中
/** This function returns a string array containing the current directory on a given device. If DeviceName is specified, then return the current shell directory on that device. If DeviceName is NULL, then return the current directory on the current device. The caller us responsible to free the returned string when no longer required. @param[in] DeviceName The name of the device to get the current directory on, or NULL for current device. @return String array with the current directory on the current or specified device. **/ typedef CHAR16* (EFIAPI *SHELLENV_CUR_DIR) ( IN CHAR16 *DeviceName OPTIONAL );
最后的 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> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2; /*** Demonstrates basic workings of the main() function by displaying a welcoming message. Note that the UEFI command line is composed of 16-bit UCS2 wide characters. The easiest way to access the command line parameters is to cast Argv as: wchar_t **wArgv = (wchar_t **)Argv; @retval 0 The application exited normally. @retval Other An error occurred. ***/ int EFIAPI main ( IN int Argc, IN char **Argv ) { Print(L"%s",mEfiShellEnvironment2->CurDir(0)); return EFI_SUCCESS; }
对应的 INF 代码如下
## @file # A simple, basic, application showing how the Hello application could be # built using the "Standard C Libraries" from StdLib. # # Copyright (c) 2010 - 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. # # 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 = 0x00010006 BASE_NAME = getcurdir FILE_GUID = 4ea97c46-7491-4dfd-0027-747010f3ce5f MODULE_TYPE = UEFI_APPLICATION VERSION_STRING = 0.1 ENTRY_POINT = ShellCEntryLib # # VALID_ARCHITECTURES = IA32 X64 IPF # [Sources] GetCurDir.c [Packages] StdLib/StdLib.dec MdePkg/MdePkg.dec ShellPkg/ShellPkg.dec [LibraryClasses] LibC LibStdio ShellCEntryLib ShellLib [Protocols] [BuildOptions]
运行结果
说明工作正常。
完整下载
参考:
1.UEFI Shell Specification July 2, 2014 EFI_SHELL_PROTOCOL.GetCurDir() P24
研究一下如何 RESETSYSTEM,直接使用 RUNTIMESERVICES 是很好的选择。首先是查看资料 UEFI 2.4 是不二的选择。
7.5.1 Reset System
具体参数解释:EFI_RESET_TYPE 有: EfiResetCold,EfiResetWarm, EfiResetShutdown,和EfiResetPlatformSpecific。Cold是系统级别的完全重启。 Warm也是系统级别的重启,但主要是CPU级别的重启。(具体的话,以前确实有Warm Reset和Cold Reset的差别,但是我感觉实际BIOS设计上,这两个并不是区分的特别清晰。传统上的CPU的Warm Reset有可能导致几百次重启之后系统挂掉,而测试人员根本不会听取你关于他们差别的解释,于是最好的办法仍然是都用Cold Reset来解决)。Shutdown的话,就是关机了。EfiResetPlatformSpecific从解释上来看好像是将一个EFI_GUID指定的Reset类型存放在ResetData中(类似记录重启原因吗?)
EfiResetPlatformSpecific 太复杂,这里只是研究一下简单的 Reset 最后编写程序如下
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellCEntryLib.h> #include <stdio.h> #include <stdlib.h> #include <wchar.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; /*** Demonstrates basic workings of the main() function by displaying a welcoming message. Note that the UEFI command line is composed of 16-bit UCS2 wide characters. The easiest way to access the command line parameters is to cast Argv as: wchar_t **wArgv = (wchar_t **)Argv; @retval 0 The application exited normally. @retval Other An error occurred. ***/ int EFIAPI main ( IN int Argc, IN char **Argv ) { gRT -> ResetSystem(EfiResetCold,EFI_SUCCESS,0,NULL); return EFI_SUCCESS; }
运行之后系统就重启了。
下载 MainRST
之前的文章【参考1】,提出了一个问题:为什么 CLIB 下面收到的参数 IN
char
**Argv 实际上是一个 Unicode ?
为了回答这个问题,还要在代码中寻找答案。同样,追踪一下当我们使用 CLIB 的时候,编译过程中程序被添加了什么。分析方法和之前的类似,我们最终得到下面这个结果:
_ModuleEntryPoint:\MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c 入口还是他
框架没变
ProcessLibraryConstructorList (1)
ProcessModuleEntryPointList (2)
ProcessLibraryDestructorList (3)
(1) ProcessLibraryConstructorList:\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c
Status = UefiRuntimeServicesTableLibConstructor (ImageHandle, SystemTable); (1.1) Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable);(1.2) Status = UefiLibConstructor (ImageHandle, SystemTable); (1.3) Status = ShellLibConstructor (ImageHandle, SystemTable); (1.4) Status = UefiHiiServicesLibConstructor (ImageHandle, SystemTable); (1.5) Status = __wchar_construct (ImageHandle, SystemTable); (1.6)
(1.1) UefiRuntimeServicesTableLibConstructor :\MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c
(1.2) UefiBootServicesTableLibConstructor :\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c
(1.3) UefiLibConstructor :\MdePkg\Library\UefiLib\UefiLib.c
(1.4) ShellLibConstructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c
ShellLibConstructorWorker //加载一些 Shell Protocol
(1.5) UefiHiiServicesLibConstructor :\MdeModulePkg\Library\UefiHiiServicesLib\UefiHiiServicesLib.c
(1.6) __wchar_construct :\StdLib\LibC\Wchar\ConsDecons.c
(2) ProcessModuleEntryPointList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c
return ShellCEntryLib (ImageHandle, SystemTable); (2.1)
(2.1) ShellCEntryLib : \ShellPkg\Library\UefiShellCEntryLib\UefiShellCEntryLib.c
ReturnFromMain = ShellAppMain ( EfiShellParametersProtocol->Argc, EfiShellParametersProtocol->Argv );
(2.1.1) ShellAppMain : \StdLib\LibC\Main\Main.c
ExitVal = (INTN)main( (int)Argc, (wchar_t **)Argv);
对照 map 文件可以看到这个main就是我们写的 ArgTest中的Main
0001:000007d0 _ShellGetEnvironmentVariable 00000a50 f UefiShellLib:UefiShellLib.obj
0001:000007eb _ShellIsFile 00000a6b f UefiShellLib:UefiShellLib.obj
0001:00000830 _UefiHiiServicesLibConstructor 00000ab0 f UefiHiiServicesLib:UefiHiiServicesLib.obj
0001:000008a8 _main 00000b28 f ArgTest:ArgTest.obj
0001:00000962 _GetPerformanceCounter 00000be2 f BaseTimerLibNullTemplate:TimerLibNull.obj
0001:00000967 _GetPerformanceCounterProperties 00000be7 f BaseTimerLibNullTemplate:TimerLibNull.obj
(3) ProcessLibraryDestructorList :\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ArgTest\ArgTest\DEBUG\AutoGen.c
Status = __wchar_deconstruct (ImageHandle, SystemTable); (3.1) Status = ShellLibDestructor (ImageHandle, SystemTable); (3.2)
(3.1) __wchar_deconstruct :\StdLib\LibC\Wchar\ConsDecons.c
(3.2) ShellLibDestructor :\ShellPkg\Library\UefiShellLib\UefiShellLib.c
上面的调用关系可以用下面的图来简单总结一下
====================================分割线====================================
我们在运行期确定是下面的代码来取得参数的
// // try to get shell 1.0 interface instead. // Status = SystemTable->BootServices->OpenProtocol(ImageHandle, &gEfiShellInterfaceGuid, (VOID **)&EfiShellInterface, ImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (!EFI_ERROR(Status)) { // // use shell 1.0 interface // ReturnFromMain = ShellAppMain ( EfiShellInterface->Argc, EfiShellInterface->Argv ); } else { ASSERT(FALSE); }
对于这个EfiShellInterface ,我们可以在 《EFI Shell Developer’s Guide》 找到。
因此,可以看到,取出来的Argc就是CHAR16.
参考:
1.http://www.lab-z.com/step-to-uefi-15%EF%BC%89-%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0-again/
前面一篇介绍了 ConOut 的换行,然后问题就来了:为什么 Print 的String不需要 \n \r 呢?
这里继续分析:
首先看一下ClsTest.map
0001:0000006d _DebugAssert 000002cd f BaseDebugLibNull:DebugLib.obj
0001:0000006e _DebugAssertEnabled 000002ce f BaseDebugLibNull:DebugLib.obj
0001:00000071 _InternalPrint 000002d1 f UefiLib:UefiLibPrint.obj
0001:000000b1 _Print 00000311 f UefiLib:UefiLibPrint.obj
0001:000000cc _InternalAllocatePool 0000032c f UefiMemoryAllocationLib:MemoryAllocationLib.obj
0001:000000f3 _UnicodeVSPrint 00000353 f BasePrintLib:PrintLib.obj
0001:00000112 _BasePrintLibFillBuffer 00000372 f BasePrintLib:PrintLibInternal.obj
就是说 Print 是来自 UefiLibPrint.Obj,接下来搜索 UefiLibPrint 能找到2个,用实验的方法确定我们需要的是在 \MdePkg\Library\UefiLib\UefiLibPrint.c
INTN
EFIAPI
Print (
IN CONST CHAR16 *Format,
...
)
{
VA_LIST Marker;
UINTN Return;
VA_START (Marker, Format);
Return = InternalPrint (Format, gST->ConOut, Marker);
VA_END (Marker);
return Return;
}
继续追 InternalPrint 发现它调用下面的语句
Return = UnicodeVSPrint (Buffer, BufferSize, Format, Marker);
而这个函数在 \MdePkg\Library\BasePrintLib\PrintLib.c 中
UINTN
EFIAPI
UnicodeVSPrint (
OUT CHAR16 *StartOfBuffer,
IN UINTN BufferSize,
IN CONST CHAR16 *FormatString,
IN VA_LIST Marker
)
{
ASSERT_UNICODE_BUFFER (StartOfBuffer);
ASSERT_UNICODE_BUFFER (FormatString);
return BasePrintLibSPrintMarker ((CHAR8 *)StartOfBuffer, BufferSize >> 1, FORMAT_UNICODE | OUTPUT_UNICODE, (CHAR8 *)FormatString, Marker, NULL);
}
继续追踪 BasePrintLibSPrintMarker 发现他在 \MdePkg\Library\BasePrintLib\PrintLibInternal.c
其中有一个程序段,如下
case '\r': Format += BytesPerFormatCharacter; FormatCharacter = ((*Format & 0xff) | (*(Format + 1) << 8)) & FormatMask; if (FormatCharacter == '\n') { // // Translate '\r\n' to '\r\n' // ArgumentString = "\r\n"; } else { // // Translate '\r' to '\r' // ArgumentString = "\r"; Format -= BytesPerFormatCharacter; } break; case '\n': // // Translate '\n' to '\r\n' and '\n\r' to '\r\n' // ArgumentString = "\r\n"; Format += BytesPerFormatCharacter; FormatCharacter = ((*Format & 0xff) | (*(Format + 1) << 8)) & FormatMask; if (FormatCharacter != '\r') { Format -= BytesPerFormatCharacter; } break;
就是说,实际上他在检查字符串是否有 \n 和 \r如果有,那么用 \n \r 替换之(文件中有2处干这个事情的,第一个是在分析 “%”,第二个才是我们想要的)。为了验证,我们将上面这段替换的代码删除,重新编译,运行结果如下:
上面一次运行结果是修改之前,下面是修改之后。可以看到,当我们去掉那段自己添加 \n \r做结尾的代码之后,同样会出现只换行不移动到行首的问题。
结论:Print 之所以 \n 直接就能换行移动到行首,是因为他代码中有特殊处理。
前面的一篇文章遇到了奇怪的问题,字符串输出看起来很不规整。于是研究一下为什么。
首先,试试 Application 是否也会有这样的显示问题,修改程序如下
#include <Uefi.h> #include <Library/UefiLib.h> #include <Library/ShellLib.h> #include <Library/UefiApplicationEntryPoint.h> extern EFI_BOOT_SERVICES *gBS; extern EFI_SYSTEM_TABLE *gST; extern EFI_RUNTIME_SERVICES *gRT; // // Entry point function // EFI_STATUS UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { Print(L"www.lab-z.com\n"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n"); return EFI_SUCCESS; }
我们看到有同样的现象。
为了查看汇编级别的程序,我们可以在 ClsTest.inf 加入下面的代码
[BuildOptions] MSFT:*_*_IA32_CC_FLAGS = /Oi- /FAcs
真正有效的成分是 /FAcs,这让编译器在编译过程中生成C语言和汇编代码对应的中间文件。
再次编译之后我们可以在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest 找到 ClsTest.cod
文件。这就是我们想要的。它的内容如下:
; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 TITLE c:\edk2\AppPkg\Applications\ClsTest\ClsTest.c .686P .XMM include listing.inc .model flat INCLUDELIB OLDNAMES PUBLIC ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ ; `string' PUBLIC ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ ; `string' PUBLIC ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ ; `string' PUBLIC ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ ; `string' ; COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w' DB 00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H DB '-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH DB 00H, 00H, 00H ; `string' PUBLIC _UefiMain ; Function compile flags: /Ogspy ; File c:\edk2\apppkg\applications\clstest\clstest.c ; COMDAT _UefiMain _TEXT SEGMENT _UefiMain PROC ; COMDAT ; 22 : Print(L"www.lab-z.com\n"); 00000 68 00 00 00 00 push OFFSET ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ 00005 e8 00 00 00 00 call _Print ; 23 : gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\n"); 0000a a1 00 00 00 00 mov eax, DWORD PTR _gST 0000f 8b 40 2c mov eax, DWORD PTR [eax+44] 00012 c7 04 24 00 00 00 00 mov DWORD PTR [esp], OFFSET ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ 00019 50 push eax 0001a ff 50 04 call DWORD PTR [eax+4] ; 24 : gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\n"); 0001d a1 00 00 00 00 mov eax, DWORD PTR _gST 00022 8b 40 2c mov eax, DWORD PTR [eax+44] 00025 68 00 00 00 00 push OFFSET ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ 0002a 50 push eax 0002b ff 50 04 call DWORD PTR [eax+4] ; 25 : gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n"); 0002e a1 00 00 00 00 mov eax, DWORD PTR _gST 00033 8b 40 2c mov eax, DWORD PTR [eax+44] 00036 68 00 00 00 00 push OFFSET ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ 0003b 50 push eax 0003c ff 50 04 call DWORD PTR [eax+4] 0003f 83 c4 18 add esp, 24 ; 00000018H ; 26 : ; 27 : return EFI_SUCCESS; 00042 33 c0 xor eax, eax ; 28 : } 00044 c3 ret 0 _UefiMain ENDP END
特别注意到,编译后,我们定义的字符串汇编写成下面这样形式的Unicode字符串
CONST SEGMENT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS
可以看到上面只有 0ah 这是换行的意思,并没有“换行然后切换到下行行首”的意思。
找到原因,我们可以加上切换到行首,就是下面这个样子
gST->ConOut->OutputString(gST->ConOut,L"LABZ Test4\n\r"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test5\n\r");
再编译查看生成的 COD 文件
CONST SEGMENT ??_C@_1BK@FBECEOIH@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA5?$AA?6?$AA?$AN?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '5', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@ CONST SEGMENT ??_C@_1BK@JNOIEOBJ@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA4?$AA?6?$AA?$AN?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '4', 00H, 0aH, 00H, 0dH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@CEEMAFJE@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA3?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '3', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@OPBANGDB@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA2?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '2', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BI@GJIEKEJP@?$AAL?$AAA?$AAB?$AAZ?$AA?5?$AAT?$AAe?$AAs?$AAt?$AA1?$AA?6?$AA?$AA@ DB 'L' DB 00H, 'A', 00H, 'B', 00H, 'Z', 00H, ' ', 00H, 'T', 00H, 'e', 00H DB 's', 00H, 't', 00H, '1', 00H, 0aH, 00H, 00H, 00H ; `string' CONST ENDS ; COMDAT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ CONST SEGMENT ??_C@_1BO@BCGMLOBC@?$AAw?$AAw?$AAw?$AA?4?$AAl?$AAa?$AAb?$AA?9?$AAz?$AA?4?$AAc?$AAo?$AAm?$AA?6?$AA?$AA@ DB 'w' DB 00H, 'w', 00H, 'w', 00H, '.', 00H, 'l', 00H, 'a', 00H, 'b', 00H DB '-', 00H, 'z', 00H, '.', 00H, 'c', 00H, 'o', 00H, 'm', 00H, 0aH DB 00H, 00H, 00H ; `string'
运行结果:
最后,提一个问题,如果程序写成这个样子
EFI_STATUS UefiMain ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { Print(L"www.lab-z.com\n"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test1\r"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test2\r"); gST->ConOut->OutputString(gST->ConOut,L"LABZ Test3\n"); return EFI_SUCCESS; }
输出结果应该是什么样的呢?
DHT11 是一款温度湿度传感器。具体可以看【参考1】。参考中使用的是单独的元件,而我使用的是做好的模块,因此不需要额外的电阻。
硬件方面,只有三根线,GND VCC 和OUT。 对照参考中的程序,很容易上手。
下面的代码也是参考中的代码,只是修改了一下波特率为 9600,这样方便我们测试之后,直接打开串口监视。
#include "dht11.h" dht11 DHT11; #define DHT11PIN 3 //DHT11 PIN 3 连接UNO 3 void setup() { Serial.begin(9600); Serial.println("DHT11 TEST PROGRAM "); Serial.print("LIBRARY VERSION: "); Serial.println(DHT11LIB_VERSION); Serial.println(); } void loop() { Serial.println("\n"); int chk = DHT11.read(DHT11PIN); Serial.print("Read sensor: "); switch (chk) { case DHTLIB_OK: Serial.println("OK"); break; case DHTLIB_ERROR_CHECKSUM: Serial.println("Checksum error"); break; case DHTLIB_ERROR_TIMEOUT: Serial.println("Time out error"); break; default: Serial.println("Unknown error"); break; } Serial.print("Humidity (%): "); Serial.println((float)DHT11.humidity, 2); Serial.print("Temperature (oC): "); Serial.println((float)DHT11.temperature-2, 2); delay(2000); }
简单测试一下,对着传感器吹一口气,数值会有变化
完整代码下载
DHT11Test
参考:
1.http://www.geek-workshop.com/forum.php?mod=viewthread&tid=997&extra=&highlight=dht11&page=1 DHT11 测试
两年前,再次学习单片机,此次入手的单片机比它的爹妈强多了。不仅把rs232接口用usb硬连接到pc而且直接写其rom!(本人不是硬件专业人士先进东西见的少)。琢磨着做个啥当学习目标呢,做个pc控制的小车吧。首先得解决mcu对数据的处理,恩,定义个包头aa55加个数据长度字0xh加个命令字0xh,再加上数据字。差资料显示状态机方式处理最好。写完后写上位程序。嘿嘿masm32是我的最爱,不过3天调试成功!为了增加可玩性,哈加入tcp方式传输命令流。一切调试ok,接上小车,呵呵大告成功!上传视频共同分享快乐!
pmason_rose 联系方式 pmason_rose@qq.com(332779423)