用Arduino配合几个电阻就能实现VGA信号的输出
具体实现方法请参考下面的文章
之前的文章【参考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)
研究一下:UEFI APP 在编译的时候会加入什么头。
使用上一次的示例程序 ClrTest。稍微修改一下,去掉清屏的调用以便我们能看清结果:
#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"); return EFI_SUCCESS; }
编译命令是 build -p AppPkg\AppPkg.dsc
首先查看生成的 Makefile
在 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\Makefile
下面的语句指定了入口函数
DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP
/ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /MACHINE:X86 /LTCG /DLL
/ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO
/BASE:0 /DRIVER /DEBUG
同样的 Makefile中,给出入口函数
IMAGE_ENTRY_POINT = _ModuleEntryPoint
顺便看一眼 \Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\OUTPUT\ClsTest.map
__ModuleEntryPoint 00000260 f UefiApplicationEntryPoint:ApplicationEntryPoint.obj
就是说 ModuleEntryPoint 是从 ApplicationEntryPoint.obj 中链接进来的
这个函数的头文件在 \MdePkg\Include\Library\UefiApplicationEntryPoint.h
再进一步查找 \MdePkg\Library\UefiApplicationEntryPoint\UefiApplicationEntryPoint.inf 其中给出了对应的函数体的位置
[Sources] ApplicationEntryPoint.c
打开看看 \MdePkg\Library\UefiApplicationEntryPoint\ApplicationEntryPoint.c
EFI_STATUS EFIAPI _ModuleEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) ............... // // Call constructor for all libraries. // ProcessLibraryConstructorList (ImageHandle, SystemTable); // // Call the module's entry point // Status = ProcessModuleEntryPointList (ImageHandle, SystemTable); // // Process destructor for all libraries. // ProcessLibraryDestructorList (ImageHandle, SystemTable);
其中调用了4个函数,下面分别按图索骥
\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c
追进去,看一下
VOID EFIAPI ProcessLibraryConstructorList ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; Status = UefiBootServicesTableLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); Status = UefiRuntimeServicesTableLibConstructor (ImageHandle,SystemTable); ASSERT_EFI_ERROR (Status); Status = UefiLibConstructor (ImageHandle, SystemTable); ASSERT_EFI_ERROR (Status); }
1.1 追一下UefiBootServicesTableLibConstructor 发现它在\MdePkg\Library\UefiBootServicesTableLib\UefiBootServicesTableLib.c
EFI_STATUS EFIAPI UefiBootServicesTableLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { // // Cache the Image Handle // gImageHandle = ImageHandle; //看到这里也能明白之前文章中Extern 的gImageHandle哪里来了 ASSERT (gImageHandle != NULL); // // Cache pointer to the EFI System Table // gST = SystemTable; ASSERT (gST != NULL); // // Cache pointer to the EFI Boot Services Table // gBS = SystemTable->BootServices; ASSERT (gBS != NULL); return EFI_SUCCESS; }
1.2 再看看UefiRuntimeServicesTableLibConstructor 在 \MdePkg\Library\UefiRuntimeServicesTableLib\UefiRuntimeServicesTableLib.c
EFI_STATUS EFIAPI UefiRuntimeServicesTableLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { // // Cache pointer to the EFI Runtime Services Table // gRT = SystemTable->RuntimeServices; ASSERT (gRT != NULL); return EFI_SUCCESS; }
1.3 UefiLibConstructor 在\MdePkg\Library\UefiLib\UefiLib.c
EFI_STATUS EFIAPI UefiLibConstructor ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { return EFI_SUCCESS; }
2. ProcessModuleEntryPointList在
\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c
EFI_STATUS EFIAPI ProcessModuleEntryPointList ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { return UefiMain (ImageHandle, SystemTable); //至此,马上进入到了我们写的函数中 }
3.继续追ProcessLibraryDestructorList 这里应该是收尾的一些工作了
\Build\AppPkg\DEBUG_VS2008\IA32\AppPkg\Applications\ClsTest\ClsTest\DEBUG\AutoGen.c
VOID EFIAPI ProcessLibraryDestructorList ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { }
上面很罗嗦的说了很多,总结一下可以用下面的图表示调用过程
当然,说了很多如果没有实验不能保证正确性
验证的办法是在上述提到的过程里面插入下面的语句
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"LABZ Test X.X\n");
如果输出结果能够正常输出全部插入的,那么表示找到的点是正确的。
结果
============================================================
2024年10月9日
还可以在 UefiApplicationEntryPoint.inf文件中加入如下编译指令:
[BuildOptions]
MSFT:*_*_X64_CC_FLAGS = /FAsc /Od
之后,可以在对应的 Application 的 Build 目录下看到生成的 .COD 文件中出现的代码。
文档对应代码
// SDA (20) HDB15-12 // SCL (21) HDB15-15 // +5V HDB15-9 // GND HDB15-5 #include <Wire.h> const int i2c_port = 0x50; byte buffer[BUFFER_LENGTH]; // 128 byte EEPROM data buffer void setup() { Serial.begin(9600); Wire.begin(); while (!Serial) {;} } void loop() { Serial.println("(1) Read EDID and print."); Serial.println("(2) getInput()"); Serial.println("(3) Item 3"); Serial.println(""); while (!Serial.available()) {;} switch (Serial.parseInt()) { case 1: ddcRead(); break; case 2: getInput(); break; case 3: Serial.println("Item 3."); break; default: printError("Menu item does not exist."); } Serial.println("*************"); } void printError(String message) { Serial.println("Error: " + message); } void ddcRead() { int blocks = 128 / BUFFER_LENGTH; Serial.println("Reading DDC..."); Wire.beginTransmission(i2c_port); Wire.write(0); Wire.endTransmission(); for (int block = 0; block < blocks; block += BUFFER_LENGTH) { Wire.requestFrom(i2c_port, BUFFER_LENGTH); for (int i = 0; i < BUFFER_LENGTH; i++) { //Serial.println(block + i, HEX); byte x = Wire.read(); buffer[block + i] = x; //Serial.print(x, HEX); Serial.print(" "); } } Serial.println("Finished reading DDC."); printData(); } void printData() { int rows = 128 / 16; for (int row = 0; row < rows; row++) { Serial.print(" ("); if (row == 0) Serial.print(0, HEX); Serial.print(row * 16, HEX); Serial.print(") "); for (int half_col = 0; half_col < 2; half_col++) { for (int col = 0; col < 8; col++) { int index = (row * 16) + (half_col * 8) + col; byte b = buffer[index]; if (b < 16) { Serial.print(0, HEX); } Serial.print(b, HEX); Serial.print(" "); // Serial.print("["); Serial.print(index, HEX); Serial.print("]"); } if (half_col == 0) { Serial.print("- "); } else { Serial.println(); } } } } void getInput() { int input_buffer_len = 16; char input[input_buffer_len]; Serial.println("Enter a string. 32 chars max. Input not echoed."); while (!Serial.available()) { ; } int input_len = Serial.readBytes(&input[0], input_buffer_len); Serial.print("Input is: "); for (int i = 0; i < input_len; i++) { Serial.write(input[i]); } Serial.println("."); Serial.println(""); }
想实现一个清屏的功能,刚开始在CLIB中翻了半天没找到,用工具直接搜索了一下 clrscr (应该在 conio.h 中)压根儿没找到。估计是 CLIB没有支持,只好换个方法。想起来Syetem Table中有 EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 拿着SPEC翻了【参考1】一下,发现可以使用它的 ClearScreen 函数。
写一个简单的程序验证之:
// // ClearScreen // #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 ) { INTN i; for (i=0;i<1000;i++) { Print(L"."); } Print(L".\n"); gST -> ConOut -> SetAttribute(gST->ConOut,0x1); gST -> ConOut -> ClearScreen(gST->ConOut); //SystemTable ->ConOut ->ClearScreen(SystemTable ->ConOut); //Print(L"%lX\n",SystemTable ->ConOut ->ClearScreen); //Print(L"%lX\n",gST -> ConOut -> ClearScreen); //Print(L"%lX\n",gST->ConOut); return EFI_SUCCESS; }
工作正常,能够清屏。
程序下载 ClsTest
参考:
1.UEFI Spec 2.4 P459
2.http://biosren.com/viewthread.php?tid=3050&highlight=clearscreen 关于SHELL下面修改(前)背景色
入手了一个热电偶温度传感器,这种东西是专门用来测试温度的,接触式的,具有测量范围大,精度高的特点。Taobao上搜索 “Arduino 热电偶”,卖家没有几个,我是从“圣源电子制作”的店铺卖的。
他家直接提供的代码包有问题,其中对应的Arduino程序无法打开。不知道是否有其他朋友也遇到过同样的问题。好在网上搜索到了对应的库 http://www.geek-workshop.com/forum.php?mod=viewthread&tid=4554&highlight=max6675 。测试了一下工作正常。最后代码如下
#include "Max6675.h" Max6675 ts(8, 9, 10); // Max6675 module: SO on pin #8, SS on pin #9, CSK on pin #10 of Arduino UNO // Other pins are capable to run this library, as long as digitalRead works on SO, // and digitalWrite works on SS and CSK void setup() { ts.setOffset(0); // set offset for temperature measurement. // 1 stannds for 0.25 Celsius Serial.begin(9600); } void loop() { Serial.print(ts.getCelsius(), 2); Serial.print(" C / "); Serial.print(ts.getFahrenheit(), 2); Serial.print(" F / "); Serial.print(ts.getKelvin(), 2); Serial.print(" K\n"); delay(300); }
运行结果
完整的代码库下载
PT100