ESP32 创建图片 WebServer

这次介绍一个比较好玩的:通过 ESP32 创建一个 WebServer ,通过浏览器访问的时候自动播放预先定义好的图片序列。

制作方法:

1.创建一个 640×480的图片,放上彩色文字,便于观察,保存为 JPG 格式

2.旋转文字,再保存成另外一个文件

3.重复上述动作我们能获得4个文件

4.使用 bin2c 工具将四个文件转化为4个C语言h文件

5.文件头定义中做一点调整,图片大小命名为  picN_size, 内容命名为 picN 这种

6.接下来就可以编写我们的代码了

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_http_server.h"
#include "pic\ph0.h"
#include "pic\ph1.h"
#include "pic\ph2.h"
#include "pic\ph3.h"
 
// 这里改成你自己的 WIFI 名称和密码
const char* ssid = "YOUWIFI";
const char* password = "PASSWORD";
 
#define PART_BOUNDARY "123456789000000000000987654321"
 
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
 
httpd_handle_t stream_httpd = NULL;
 
static esp_err_t stream_handler(httpd_req_t *req) {
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];
   
  static int index=0;
   
  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if (res != ESP_OK) {
    return res;
  }
 
  while (true) {
    // 这里设定一个循环,轮流发送PIC0-4
    if (index==0) {
      _jpg_buf_len = pic0_size;
      _jpg_buf = (uint8_t *)pic0; 
    } else if (index==1) {
      _jpg_buf_len = pic1_size;
      _jpg_buf = (uint8_t *)pic1; 
    } else if (index==2) {
      _jpg_buf_len = pic2_size;
      _jpg_buf = (uint8_t *)pic2; 
    } else if (index==3) {
      _jpg_buf_len = pic3_size;
      _jpg_buf = (uint8_t *)pic3; 
    } 
    // 如果发送完PIC3 接来发送PIC0
    index=(index+1)%4;
 
    if (res == ESP_OK) {
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if (res != ESP_OK) {
      break;
    }
    Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
  }
  return res;
}
 
void startPicServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
 
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
 
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
  }
}
 
void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
 
  Serial.begin(115200);
  Serial.setDebugOutput(false);
 
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
 
  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.print(WiFi.localIP());
 
  //启动
  startPicServer();
}
 
void loop() {
  delay(1);
}

7.运行之后串口监视器会输出当前 WebServer 的 IP ,将地址在浏览器中打开即可看到结果:

ESP32PICWebServer下载

Step to UEFI (260)Section Alignment(0x20) is not 4K

查看 Log 的过程中,发现在 DXE 阶段有错误报出,本文针对这个问题进行了一点研究。

首先,遇到的错误信息如下:

InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7543E98
ProtectUefiImageCommon - 0x7543B40
  - 0x0000000007105000 - 0x0000000000005E60
InstallProtocolInterface: AA0E8BC1-DABC-46B0-A844-37B8169B2BEA 710A9C0
Loading driver 4B28E4C7-FF36-4E10-93CF-A82159E777C5
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7543140
!!!!!!!!  InsertImageRecord - Section Alignment(0x20) is not 4K  !!!!!!!!
!!!!!!!!  Image - c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe\DEBUG\ResetSystemRuntimeDxe.pdb  !!!!!!!!
Loading driver at 0x00007AE1000 EntryPoint=0x00007AE14D4 ResetSystemRuntimeDxe.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 7543418
ProtectUefiImageCommon - 0x7543140
  - 0x0000000007AE1000 - 0x00000000000041A0

接下来定位。输出错误的代码位于 \mdemodulepkg\core\dxe\misc\MemoryAttributesTable.c 中的InsertImageRecord() 函数中:

/**
  Insert image record.
 
  @param  RuntimeImage    Runtime image information
**/
VOID
InsertImageRecord (
  IN EFI_RUNTIME_IMAGE_ENTRY  *RuntimeImage
  )
……………
  //
  // Get SectionAlignment
  //
  if (Hdr.Pe32->OptionalHeader.Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
    SectionAlignment  = Hdr.Pe32->OptionalHeader.SectionAlignment;
  } else {
    SectionAlignment  = Hdr.Pe32Plus->OptionalHeader.SectionAlignment;
  }
  SetMemoryAttributesTableSectionAlignment (SectionAlignment);
  if ((SectionAlignment & (RUNTIME_PAGE_ALLOCATION_GRANULARITY - 1)) != 0) {
    DEBUG ((DEBUG_WARN, "!!!!!!!!  InsertImageRecord - Section Alignment(0x%x) is not %dK  !!!!!!!!\n",
      SectionAlignment, RUNTIME_PAGE_ALLOCATION_GRANULARITY >> 10));
    PdbPointer = PeCoffLoaderGetPdbPointer ((VOID*) (UINTN) ImageAddress);
    if (PdbPointer != NULL) {
      DEBUG ((DEBUG_WARN, "!!!!!!!!  Image - %a  !!!!!!!!\n", PdbPointer));
    }
    goto Finish;
  }
…………

在\MdePkg\Include\X64\ProcessorBind.h 有如下定义:

///
/// Page allocation granularity for x64
///
#define DEFAULT_PAGE_ALLOCATION_GRANULARITY   (0x1000)
#define RUNTIME_PAGE_ALLOCATION_GRANULARITY   (0x1000)

可以看到,SectionAlignment 来自 Hdr.Pe32Plus->OptionalHeader.SectionAlignment,使用 CFF 查看 ResetSystemRuntimeDxe.efi 这个文件,注意下图中的2个位置。上述代码首先检查ImageType(Subsystem) 是否为EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER,如果是再继续检查SectionAlignment是否为4K,如果不是 4K 对齐那么就进行报错。

ImageType的定义在\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe.inf 文件中:

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = ResetSystemRuntimeDxe
  MODULE_UNI_FILE                = ResetSystemRuntimeDxe.uni
  FILE_GUID                      = 4B28E4C7-FF36-4e10-93CF-A82159E777C5
  MODULE_TYPE                    = DXE_RUNTIME_DRIVER
  VERSION_STRING                 = 1.0
 
  ENTRY_POINT                    = InitializeResetSystem

搜索所有 MODULE_TYPE 为 DXE_RUNTIME_DRIVER 的 Module 发现在运行期都有这样的问题。

确定了问题,找到了问题点,接下来就开始分析“为什么出现不满足条件”。

检查\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Universal\ResetSystemRuntimeDxe\ResetSystemRuntimeDxe\Makefile 文件有下面一行:

DLINK_FLAGS = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
(注意 /ALIGN:32 参数)

尝试修改上面为 /ALIGN:0x1000 修改之后再次编译(需要同样的环境,进入 Debug  目录直接运行 NMAKE,这种方法在之前的实验中有介绍过),生成的EFI如下:

我们需要修改EFI 的生成方式。在\Conf\tools_def.txt 可以看到默认的编译参数:

 DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
RELEASE_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4254 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /MERGE:.rdata=.data
NOOPT_VS2015x86_X64_DLINK_FLAGS    = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG

这里我们使用的是DEBUG版,所以需要修改为下面这样(/ALIGN:0x1000)

DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:0x1000 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG

修改之后重新编译OVMF,得到的ResetSystemRuntimeDxe.efi 和前面手工修改的结果相同。但是这样修改会导致编译最后一步无法通过:

Generating FVMAIN_COMPACT FV
Generating PEIFV FV
##### ['GenFv', '-a', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\Ffs\\PEIFV.inf', '-o', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\PEIFV.Fv', '-i', 'c:\\buildbs\\stable202108org\\Build\\OvmfX64\\DEBUG_VS2015x86\\FV\\PEIFV.inf']
Return Value = 2
GenFv: ERROR 3000: Invalid
  PE image Section-Alignment and File-Alignment do not match : c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\52C05B14-0B98-496c-BC3B-04B50211D680PeiCore\52C05B14-0B98-496c-BC3B-04B50211D680.ffs.
GenFv: ERROR 3000: Invalid
  Could not rebase c:\buildbs\stable202108org\Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\52C05B14-0B98-496c-BC3B-04B50211D680PeiCore\52C05B14-0B98-496c-BC3B-04B50211D680.ffs.
 
build.py...
 : error 7000: Failed to generate FV
build.py...
 : error 7000: Failed to execute command

看起来是因为 PE头中 SectionAlignment和FileAlignment 不同导致的,在【参考1】介绍了一个设置 FileAlignment 的参数。同样的,将这个参数加入到 tools_def.txt 文件中:

  DEBUG_VS2015x86_X64_DLINK_FLAGS  = /NOLOGO /NODEFAULTLIB /IGNORE:4001 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:0x1000 /FILEALIGN:0x1000 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:$(IMAGE_ENTRY_POINT) /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG
(注意  /FILEALIGN:0x1000 )

再次编译可以得到OVMF.FD ,使用QEMU 运行,Log 中就没有这个错误了。

这样的修改虽然能够解决问题,但是我们并不了解 InsertImageRecord() 函数的用途,后面有机会再继续进行分析。

参考:

1.https://blog.csdn.net/bagboy_taobao_com/article/details/7295575/

Step to UEFI (261)BootMenu插入自己的Application

在QEMU 的Boot Manager/Menu 中可以看到”EFI Internal Shell” 这个选项,选中之后会跳转到其中执行 Shell.efi,这次就尝试编代码在其中插入能够启动到自己Application的选项。

首先我们研究一下这个选项是如何插入的。在\ovmfpkg\library\platformbootmanagerlib\BdsPlatform.c 的 PlatformBootManagerAfterConsole()函数中有:

//
// Perform some platform specific connect sequence
//
PlatformBdsConnectSequence ();
 
EfiBootManagerRefreshAllBootOption ();
 
//
// Register UEFI Shell
//
PlatformRegisterFvBootOption (
  &gUefiShellFileGuid, L"EFI Internal Shell", LOAD_OPTION_ACTIVE
  );
 
RemoveStaleFvFileOptions ();
SetBootOrderFromQemu ();

其中gUefiShellFileGuid 的定义在 \ShellPkg\ShellPkg.dec

# FILE_GUID as defined in ShellPkg/Application/Shell/Shell.inf
gUefiShellFileGuid              = {0x7c04a583, 0x9e3e, 0x4f1c, {0xad, 0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1}

也可以在\ShellPkg\Application\Shell\Shell.inf 中看到

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = Shell
  FILE_GUID                      = 7C04A583-9E3E-4f1c-AD65-E05268D0B4D1 # gUefiShellFileGuid
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

使用工具可以看到:

接下来我们着手设计一个自己的 Application 用于验证,需要特别注意的是,这个程序不能使用 SHELL 的任何Protocol和Service,因为调用它的时候没有 Shell。代码非常简单:

1. BootTest.c 如下

#include  <Uefi.h>
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
        for (int i=0;i<1000;i++) {
                SystemTable->ConOut->OutputString(
                        SystemTable->ConOut,
                        L"www.lab-z.com\n");
        }
 
   
  return(0);
}

2. BootTest.inf如下:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = BootTest
  FILE_GUID                      = a912f198-7f0e-4803-b908-b757b806ec89
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain
 
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#
 
[Sources]
  BootTest.c
 
[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec
 
[LibraryClasses]
  UefiLib
  UefiApplicationEntryPoint
  DebugLib

EDK2 202108 已经没有 AppPkg , 所以我们直接放在 ShellPkg 中。接下来修改代码如下:

1.\OvmfPkg\OvmfPkgX64.dsc 中,插入 INF 文件

    <PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000
  }
 
  ShellPkg/Application/BootTest/BootTest.inf
 
!if $(SECURE_BOOT_ENABLE) == TRUE
  SecurityPkg/VariableAuthenticated/SecureBootConfigDxe/SecureBootConfigDxe.inf
  OvmfPkg/EnrollDefaultKeys/EnrollDefaultKeys.inf
!endif

2.\OvmfPkg\OvmfPkgX64.fdf 中插入

!if $(TOOL_CHAIN_TAG) != "XCODE5"
INF  ShellPkg/DynamicCommand/TftpDynamicCommand/TftpDynamicCommand.inf
INF  ShellPkg/DynamicCommand/HttpDynamicCommand/HttpDynamicCommand.inf
INF  OvmfPkg/LinuxInitrdDynamicShellCommand/LinuxInitrdDynamicShellCommand.inf
!endif
INF  ShellPkg/Application/Shell/Shell.inf
INF  ShellPkg/Application/BootTest/BootTest.inf
 
INF MdeModulePkg/Logo/LogoDxe.inf
 
#
# Network modules
#
!if $(E1000_ENABLE)
  FILE DRIVER = 5D695E11-9B3F-4b83-B25F-4A8D5D69BE07 {
    SECTION PE32 = Intel3.5/EFIX64/E3522X2.EFI
  }

3. \OvmfPkg\Library\PlatformBootManagerLib\BdsPlatform.c 文件做插入 BootMenu的动作

定义一个 GUID,来自 BootTest.inf 中的FILE_GUID        

EFI_GUID  gBootTest = {
    0xa912f198, 0x7f0e, 0x4803,
    { 0xb9, 0x08, 0xb7, 0x57, 0xb8, 0x06, 0xec, 0x89 } };

插入动作:

//
// Perform some platform specific connect sequence
//
PlatformBdsConnectSequence ();
 
EfiBootManagerRefreshAllBootOption ();
DEBUG((DEBUG_INFO, "LABZTest_Start\n"));
//
// Register a Test Application
//
PlatformRegisterFvBootOption(
    &gBootTest, L"LABZTest", LOAD_OPTION_ACTIVE
);
DEBUG((DEBUG_INFO, "LABZTest_End\n"));
//
// Register UEFI Shell
//
PlatformRegisterFvBootOption (
  &gUefiShellFileGuid, L"EFI Internal Shell", LOAD_OPTION_ACTIVE
  );

重新编译之后,可以用工具看到我们插入的 Application:

运行结果:

可以看到 BootMenu 中出现了我们设定的启动选项。

本文提到的用于测试的 BootTest:

BootTest下载

ESP32 Time Error

最近在编译一个 ESP32 Arduino代码的时候碰到了如下很奇怪的问题:

In file included from c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\bits\locale_facets_nonio.h:39,
                 from c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\locale:41,
                 from c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\iomanip:43,
                 from C:\Users\DaveX\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.4\libraries\BluetoothSerial\src\BTAddress.cpp:15:
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:64:11: error: '::clock' has not been declared
   using ::clock;
           ^~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:65:11: error: '::difftime' has not been declared
   using ::difftime;
           ^~~~~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:66:11: error: '::mktime' has not been declared
   using ::mktime;
           ^~~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:67:11: error: '::time' has not been declared
   using ::time;
           ^~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:68:11: error: '::asctime' has not been declared
   using ::asctime;
           ^~~~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:69:11: error: '::ctime' has not been declared
   using ::ctime;
           ^~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:70:11: error: '::gmtime' has not been declared
   using ::gmtime;
           ^~~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:71:11: error: '::localtime' has not been declared
   using ::localtime;
           ^~~~~~~~~
c:\users\DaveX\appdata\local\arduino15\packages\esp32\tools\xtensa-esp32-elf-gcc\gcc8_4_0-esp-2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\ctime:72:11: error: '::strftime' has not been declared
   using ::strftime;
           ^~~~~~~~

经过搜索,有人碰到了类似的问题,产生问题的原因是:使用的代码中有 time.h 这个名称的文件。我的代码中没有,但是使用到的 DFRobot_OLED12864 这个库里面有。解决方法很简单:把这个库中的time.h 改个名称即可。

希望能帮助到碰到同样问题的朋友。

Windows 11 测试版

分享两个用于测试的 Windows 11 ,一个是 22616,另外一个是25131。注意:这个是 Insider 版本只建议用于测试不要用于自己使用。

22616.1.220502-1800.NI_RELEASE_CLIENTMULTICOMBINED_UUP_X64FRE_NETFX_EN-US_FIX

链接: https://pan.baidu.com/s/1NstIoGTvPtZrLSi-wIztkA?pwd=f92y 提取码: f92y

25131.1000.220527-1351.RS_PRERELEASE_CLIENTMULTICOMBINED_UUP_X64FRE_NETFX_EN-US_FIX

链接: https://pan.baidu.com/s/1XinTLV3Hs3qvsxWpiDqLaw?pwd=r71h 提取码: r71h

Step to UEFI (259)DxeMain

接下来的代码在 \mdemodulepkg\core\dxe\dxemain\DxeMain.c。我们依旧是按照 Debug Log 的顺序研究。

  //
  // Initialize Memory Services
  //
  CoreInitializeMemoryServices (&HobStart, &MemoryBaseAddress, &MemoryLength);

这个函数位于\mdemodulepkg\core\dxe\gcd\Gcd.c,初始化内存服务。

/**
  External function. Initializes memory services based on the memory
  descriptor HOBs.  This function is responsible for priming the memory
  map, so memory allocations and resource allocations can be made.
  The first part of this function can not depend on any memory services
  until at least one memory descriptor is provided to the memory services.

  @param  HobStart               The start address of the HOB.
  @param  MemoryBaseAddress      Start address of memory region found to init DXE
                                 core.
  @param  MemoryLength           Length of memory region found to init DXE core.

  @retval EFI_SUCCESS            Memory services successfully initialized.

**/
EFI_STATUS
CoreInitializeMemoryServices (
  IN  VOID                  **HobStart,
  OUT EFI_PHYSICAL_ADDRESS  *MemoryBaseAddress,
  OUT UINT64                *MemoryLength
  )
CoreInitializeMemoryServices:
  BaseAddress - 0x3F59000 Length - 0x3CA7000
MinimalMemorySizeNeeded - 0x320000
(这里看起来有一个能够运行的最小内存的要求,具体怎么来的还不清楚)

接下来回到 DxeMain 中继续运行,接下来开始创建最重要的2个Table:System_Table 和 Runtime_Services

  //
  // Allocate the EFI System Table and EFI Runtime Service Table from EfiRuntimeServicesData
  // Use the templates to initialize the contents of the EFI System Table and EFI Runtime Services Table
  //
  gDxeCoreST = AllocateRuntimeCopyPool (sizeof (EFI_SYSTEM_TABLE), &mEfiSystemTableTemplate);
  ASSERT (gDxeCoreST != NULL);

  gDxeCoreRT = AllocateRuntimeCopyPool (sizeof (EFI_RUNTIME_SERVICES), &mEfiRuntimeServicesTableTemplate);
  ASSERT (gDxeCoreRT != NULL);

  gDxeCoreST->RuntimeServices = gDxeCoreRT;
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 7EC57F8
  ## Include/Protocol/LoadedImage.h
  gEfiLoadedImageProtocolGuid    = { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}
  //
  // Start the Image Services.
  //
  Status = CoreInitializeImageServices (HobStart);
  ASSERT_EFI_ERROR (Status);
\mdemodulepkg\core\dxe\image\Image.c

/**
  Add the Image Services to EFI Boot Services Table and install the protocol
  interfaces for this image.

  @param  HobStart                The HOB to initialize

  @return Status code.

**/
EFI_STATUS
CoreInitializeImageServices (
  IN  VOID *HobStart
  )

其中:

  //
  // Install the protocol interfaces for this image
  //
  Status = CoreInstallProtocolInterface (
             &Image->Handle,
             &gEfiLoadedImageProtocolGuid,
             EFI_NATIVE_INTERFACE,
             &Image->Info
             );
  ASSERT_EFI_ERROR (Status);

  ProtectUefiImage (&Image->Info, Image->LoadedImageDevicePath);

在\mdemodulepkg\core\dxe\misc\MemoryProtection.c 文件中:

ProtectUefiImageCommon - 0x7EC57F8
  - 0x0000000007EA3000 - 0x0000000000027000
(不清楚这个函数的左右,看介绍好像是用于加载 PE  格式,然后设置对应的代码为”Execute Only”)。

接下来返回 DxeMain 中继续:

  //
  // Log MemoryBaseAddress and MemoryLength again (from
  // CoreInitializeMemoryServices()), now that library constructors have
  // executed.
  //
  DEBUG ((DEBUG_INFO, "%a: MemoryBaseAddress=0x%Lx MemoryLength=0x%Lx\n",
__FUNCTION__, MemoryBaseAddress, MemoryLength));
DxeMain: MemoryBaseAddress=0x3F59000 MemoryLength=0x3CA7000
  DEBUG ((DEBUG_INFO | DEBUG_LOAD, "HOBLIST address in DXE = 0x%p\n", HobStart));
HOBLIST address in DXE = 0x78EA018
\MdePkg\MdePkg.dec
  ## Include/Protocol/Decompress.h
  gEfiDecompressProtocolGuid     = { 0xD8117CFE, 0x94A6, 0x11D4, { 0x9A, 0x3A, 0x00, 0x90, 0x27, 0x3F, 0xC1, 0x4D }}
  ## Include/Protocol/FirmwareVolumeBlock.h
  gEfiFirmwareVolumeBlockProtocolGuid = { 0x8f644fa9, 0xe850, 0x4db1, {0x9c, 0xe2, 0xb, 0x44, 0x69, 0x8e, 0x8d, 0xa4 } }
  ## Include/Protocol/DevicePath.h
  gEfiDevicePathProtocolGuid     = { 0x09576E91, 0x6D3F, 0x11D2, { 0x8E, 0x39, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }}
  ## Include/Protocol/FirmwareVolume2.h
  gEfiFirmwareVolume2ProtocolGuid = { 0x220e73b6, 0x6bdb, 0x4413, { 0x84, 0x5, 0xb9, 0x74, 0xb1, 0x8, 0x61, 0x9a } }

比较特别的是:

InstallProtocolInterface: D8117CFE-94A6-11D4-9A3A-0090273FC14D 7EC5080
InstallProtocolInterface: 8F644FA9-E850-4DB1-9CE2-0B44698E8DA4 78E6CB0
InstallProtocolInterface: 09576E91-6D3F-11D2-8E39-00A0C969723B 78E6D98
InstallProtocolInterface: 220E73B6-6BDB-4413-8405-B974B108619A 78E6630
InstallProtocolInterface: EE4E5898-3914-4259-9D6E-DC7BD79403CF 7EC5D10

最后一个定义在\OvmfPkg\OvmfPkgX64.fdf:

FILE FV_IMAGE = 9E21FD93-9C72-4c15-8C4B-E77F1DB2D792 {
   SECTION GUIDED EE4E5898-3914-4259-9D6E-DC7BD79403CF PROCESSING_REQUIRED = TRUE {
     #
     # These firmware volumes will have files placed in them uncompressed,
     # and then both firmware volumes will be compressed in a single
     # compression operation in order to achieve better overall compression.
     #
     SECTION FV_IMAGE = PEIFV
     SECTION FV_IMAGE = DXEFV
   }
 }

代码在\mdemodulepkg\core\dxe\sectionextraction\CoreSectionExtraction.c

/**
  Entry point of the section extraction code. Initializes an instance of the
  section extraction interface and installs it on a new handle.

  @param  ImageHandle   A handle for the image that is initializing this driver
  @param  SystemTable   A pointer to the EFI system table

  @retval EFI_SUCCESS           Driver initialized successfully
  @retval EFI_OUT_OF_RESOURCES  Could not allocate needed resources

**/
EFI_STATUS
EFIAPI
InitializeSectionExtraction (
  IN EFI_HANDLE                   ImageHandle,
  IN EFI_SYSTEM_TABLE             *SystemTable
  )
(感觉是将 PEI 和 DXE打包在一起)

在 ESP32 S2上使用 USB Host 模块

这次实验在 ESP32 S2 Saola 开发板上使用前面设计的Micro USB Host【参考1】。

首先遇到的问题是:ESP32 S2 的 SPI 在 Arduino 环境下工作不正常(对于这个问题的分析请参阅【参考2】)。为此,我们需要直接修改 位于 C:\Users\用户名\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.1\libraries\SPI\src\SPI.cpp 文件中的如下内容:

#if CONFIG_IDF_TARGET_ESP32
//LabZDebug_Start
#if CONFIG_IDF_TARGET_ESP32S2
        SPIClass SPI(HSPI);
#else
        SPIClass SPI(VSPI);
#endif
//LabZDebug_End
#else
SPIClass SPI(FSPI);
#endif

接下来修改 USB Host Shield 库文件:

  1. USB_Host_Shield_Library_2.0\usbhost.h 这个文件有下面3个地方需要修改:
    1. 1 这里给出用到的 SCK/MISO/MOSI/SS Pin的编号
#elif defined(ESP32)
//LABZDebug typedef SPi< P18, P23, P19, P5 > spi;
//LABZDebug_Start
           //SCK  MISO MOSI SS
typedef SPi< P10, P21, P19, P13 > spi;
//LABZDebug_End
#elif defined(ARDUINO_NRF52840_FEATHER)

1.2 给出SPI需要引脚编号才能正确的进行SPI初始化:

#elif defined(SPI_HAS_TRANSACTION)
        static void init() {
                //LABZDebug USB_SPI.begin(); // The SPI library with transaction will take care of setting up the pins - settings is set in beginTransaction()
				//LABZDebug_Start
				                USB_SPI.begin(10,21,19,13); // The SPI library with transaction will take care of setting up the pins - settings is set in beginTransaction()
				//LABZDebug_End
                SPI_SS::SetDirWrite();
                SPI_SS::Set();
        }
#elif defined(STM32F4)

1.3 降低速度(Max3421e 最高支持26Mhz, 但是因为 ESP32 无法分频出26M,所以实际上SPI 会以是20M速度工作。但是因为这次实验都是排线,所以频率高了之后会出现通讯错误的问题,为此需要进行降频)到4Mhz。

将文件中

        //LABZDebug USB_SPI.beginTransaction(SPISettings(26000000, MSBFIRST, SPI_MODE0)); // The MAX3421E can handle up to 26MHz, use MSB First and SPI mode 0
		//LABZDebug_Start
		USB_SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
		//LABZDebug_End

2.USB_Host_Shield_Library_2.0\UsbCore.h 这里给出用到的SS 和 INT Pin编号

#elif defined(ESP32)
//LABZDebug typedef MAX3421e<P5, P17> MAX3421E; // ESP32 boards
//LABZDebug_Start
               // SS  INT
typedef MAX3421e<P13, P5> MAX3421E; // ESP32 boards
//LABZDebug_End
#elif (defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__))
typedef MAX3421e<Pb4, Pb3> MAX3421E; // Sanguino

3.USB_Host_Shield_Library_2.0\avrpins.h 这里主要是声明前面用到的PXX 的定义否则编译会出错

MAKE_PIN(P3, 3); // RX0
//LABZDebug_Start
MAKE_PIN(P4, 4);   // INT
MAKE_PIN(P13, 13);   // CLK
MAKE_PIN(P26, 26);   // SS
//LABZDebug_End
MAKE_PIN(P21, 21); // SDA

之后使用 USBHIDBootMouse.ino 进行测试:

#include <hidboot.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

class MouseRptParser : public MouseReportParser
{
protected:
	void OnMouseMove	(MOUSEINFO *mi);
	void OnLeftButtonUp	(MOUSEINFO *mi);
	void OnLeftButtonDown	(MOUSEINFO *mi);
	void OnRightButtonUp	(MOUSEINFO *mi);
	void OnRightButtonDown	(MOUSEINFO *mi);
	void OnMiddleButtonUp	(MOUSEINFO *mi);
	void OnMiddleButtonDown	(MOUSEINFO *mi);
};
void MouseRptParser::OnMouseMove(MOUSEINFO *mi)
{
    Serial.print("dx=");
    Serial.print(mi->dX, DEC);
    Serial.print(" dy=");
    Serial.println(mi->dY, DEC);
};
void MouseRptParser::OnLeftButtonUp	(MOUSEINFO *mi)
{
    Serial.println("L Butt Up");
};
void MouseRptParser::OnLeftButtonDown	(MOUSEINFO *mi)
{
    Serial.println("L Butt Dn");
};
void MouseRptParser::OnRightButtonUp	(MOUSEINFO *mi)
{
    Serial.println("R Butt Up");
};
void MouseRptParser::OnRightButtonDown	(MOUSEINFO *mi)
{
    Serial.println("R Butt Dn");
};
void MouseRptParser::OnMiddleButtonUp	(MOUSEINFO *mi)
{
    Serial.println("M Butt Up");
};
void MouseRptParser::OnMiddleButtonDown	(MOUSEINFO *mi)
{
    Serial.println("M Butt Dn");
};

USB     Usb;
USBHub     Hub(&Usb);
HIDBoot<USB_HID_PROTOCOL_MOUSE>    HidMouse(&Usb);

MouseRptParser                               Prs;

void setup()
{
    Serial.begin( 115200 );
#if !defined(__MIPSEL__)
    while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
    Serial.println("Start");

    if (Usb.Init() == -1)
        Serial.println("OSC did not start.");

    delay( 200 );

    HidMouse.SetReportParser(0, &Prs);
}

void loop()
{
  Usb.Task();
}

运行结果如下:

关于 USB Host Shield 调试建议如下:

  1. 首先跑 board_qc.ino 确定SPI 连接是否正确。如果一直有问题,那么是 SPI 不通,需要研究MOSI信号是否正常发送;如果 MOSI/SCLK 都正常但是没有 MOISO 回复,那么请检查 RESET 是否为高;
  2. 接下来跑USBHIDBootMouse.ino 代码测试,如果有问题,应该是 INT Pin 设置的错误;
  3. 如果有线鼠标无法使用,那么可以实验无线鼠标,因为前者要求的功耗比较高,可能你从开发板中拉出来的5V供电不足。

参考:

  1. https://mc.dfrobot.com.cn/thread-312057-1-1.html 做一个Micro USB Host
  2. https://www.arduino.cn/thread-106240-1-1.html ESP32 S2 的 SPI

2022年5月28日 更新

偶然发现了 ESP32 S2 默认的 SPI 引脚【https://www.arduino.cn/thread-106240-1-1.html】,于是尝试不修改库的情况下直接使用。

硬件连接:


名称   
ESP32 S2ESP32 S2名称
INTIO17IO35MOSI
GNDGNDIO37MISO
MD-USB 母头 D-IO5SS
MD+USB 母头 D+IO36SCLK
VBCOMPN/A3.3VRESET
GNDGND3.3V3.3V

运行例子是 USB_Host_Shield_Library_2.0\examples\HID\USBHIDBootMouse

未经修改的 USB Host Shield 库如下:

Windows 下Victor86B 的数据读取

Victor 86B 是一款带有 USB接口的数字万用表,就是说用户可以从USB口来获取当前的万用表测量结果。经过实验以及结合说明书,数字部分表示如下:

比如,“1” 可以通过 Bit0/2 置1来进行表示, 因此 “1”对应 0x05 (0000 0101B)。类似的,每个数字表示如下:

            0           7D

            1           05

            2           5B 

            3           1F

            4           27

            5           3E

            6           7E  

            7           15

            8           7F

            9           3F

有了这个表格,再配合说明编写一个 C# 的Windows Console程序:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Diagnostics;

using System.Runtime.InteropServices;

// 程序中提到的接收 (read)和发送(write)都是以电脑端为主机的
namespace serialtest
{
    public partial class Form1 : Form
    {
        public const int COM_BAUDRATE = 2400; //当前波特率
        public Form1()
        {
            InitializeComponent();
        }

        byte[] readBuffer = new byte[1024]; //接收Buffer
        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;

            //输出当前的串口设备名称
            ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity");
            foreach (ManagementObject queryObj in searcher.Get())
            {
                if (queryObj["Caption"] != null)
                    if (queryObj["Caption"].ToString().Contains("(COM"))
                    {
                        textBox1.AppendText(queryObj["Caption"].ToString() + Environment.NewLine);
                    }
            }
            searcher.Dispose();

        }

        private void button1_Click(object sender, EventArgs e)
        {
            
            if (button1.Text == "PC Receive Stop")
            {
                // 开始接收 86B 发送过来的数据
                serialPort1.BaudRate = COM_BAUDRATE;
                serialPort1.PortName = comboBox1.SelectedItem.ToString();
                // 为了接收> 127 的 ASCII 必须有下面这一句
                serialPort1.Encoding = System.Text.Encoding.GetEncoding(28591);

                // 设置收到 MAX_N 字节后触发
                serialPort1.ReceivedBytesThreshold = 28;
                serialPort1.Open();
                button1.Text = "PC Receive Start";
            }
            else {
                // 停止接收
                button1.Text = "PC Receive Start";
                serialPort1.Close();
            }
        }

        private string ByteToString(byte b1,byte b2, Boolean first) {
            byte tmp = (byte)(((byte)(b1 << 4)) + ((byte)(b2 & 0xF))&0x7F);
            //                                   0     1     2     3     4     5     6     7     8     9   L   
            byte[] HexToValue =  new byte[] { 0x7D, 0x05, 0x5B, 0x1F, 0x27, 0x3E, 0x7E, 0x15, 0x7F, 0x3F ,0x68};
            String Result = "";
            if (first) {
                if ((b1 & 0x8) != 0) {
                    Result = Result + '-';
                }
            } else {
                if ((b1 & 0x8) != 0)
                {
                    Result = Result + '.';
                }
            }
            for (int i = 0; i < 11; i++) {
                if (tmp == HexToValue[i]) {
                    if (i == 10)
                    { Result = Result + "L"; }
                    else { Result = Result + i.ToString(); }
                        break;
                }
            }
            return Result;
        }
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            // 当前处于接收模式
            serialPort1.Read(readBuffer, 0, 28);
            int start;
            for (start = 0; start < 28; start++)
            {
                if ((byte)(readBuffer[start] & 0xF0) == 0x10) {
                    break;    
                }
            }
                
                // 收到的数据
                textBox2.AppendText(">>>>>>>>>>>>>>>>\r\n");
                for (int i = 0; i < 14; i++)
                {
                     textBox2.AppendText(readBuffer[i+start].ToString("X2")+ "  ");
                }
                textBox2.AppendText("\r\n");
                
                if ((readBuffer[start] & 8) != 0) {
                    textBox2.AppendText("AC");
                }
                if ((readBuffer[start] & 4) != 0)
                {
                    textBox2.AppendText("DC");
                }
                if ((readBuffer[start] & 2) != 0)
                {
                    textBox2.AppendText("AUTO");
                }
                if ((readBuffer[start] & 1) != 0)
                {
                    textBox2.AppendText("RS232\r\n");
                }

                textBox2.AppendText(
                    ByteToString(readBuffer[1+ start], readBuffer[2 + start],true)+
                    ByteToString(readBuffer[3 + start], readBuffer[4 + start],false)+
                    ByteToString(readBuffer[5 + start], readBuffer[6 + start], false) +
                    ByteToString(readBuffer[7 + start], readBuffer[8 + start], false)
                    );
                //textBox2.AppendText("\r\n");

            if ((readBuffer[9 + start] & 8) != 0) {
                textBox2.AppendText("u");
            }
            if ((readBuffer[9 + start] & 4) != 0)
            {
                textBox2.AppendText("n");
            }
            if ((readBuffer[9 + start] & 2) != 0)
            {
                textBox2.AppendText("K");
            }
            if ((readBuffer[9 + start] & 1) != 0)
            {
                textBox2.AppendText("--->|---");
            }

            if ((readBuffer[10 + start] & 8) != 0)
            {
                textBox2.AppendText("m");
            }
            if ((readBuffer[10 + start] & 4) != 0)
            {
                textBox2.AppendText("% Duty");
            }
            if ((readBuffer[10 + start] & 2) != 0)
            {
                textBox2.AppendText("M");
            }
            if ((readBuffer[10 + start] & 1) != 0)
            {
                textBox2.AppendText("BEEP");
            }

            if ((readBuffer[11 + start] & 8) != 0)
            {
                textBox2.AppendText("F");
            }
            if ((readBuffer[11 + start] & 4) != 0)
            {
                textBox2.AppendText("Ω");
            }
            if ((readBuffer[11 + start] & 2) != 0)
            {
                textBox2.AppendText("REF");
            }
            if ((readBuffer[11 + start] & 1) != 0)
            {
                textBox2.AppendText("HOLD");
            }

            if ((readBuffer[12 + start] & 8) != 0)
            {
                textBox2.AppendText("A");
            }
            if ((readBuffer[12 + start] & 4) != 0)
            {
                textBox2.AppendText("V");
            }
            if ((readBuffer[12 + start] & 2) != 0)
            {
                textBox2.AppendText("Hz");
            }
            if ((readBuffer[12 + start] & 1) != 0)
            {
                textBox2.AppendText("BAT");
            }

            if ((readBuffer[13 + start] & 4) != 0)
            {
                textBox2.AppendText("mV");
            }
            if ((readBuffer[13 + start] & 2) != 0)
            {
                textBox2.AppendText("℃");
            }
            textBox2.AppendText("\r\n");
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            serialPort1.Close();
        }


    }
}

运行结果:

Step to UEFI (258)DxeIpl

 前文提到了在 PEI 阶段的末尾,通过下面的代码调用 DxeIpl 进入 DXE 中。

  //
  // Enter DxeIpl to load Dxe core.
  //
  DEBUG ((EFI_D_INFO, "DXE IPL Entry\n"));
  Status = TempPtr.DxeIpl->Entry (
                             TempPtr.DxeIpl,
                             &PrivateData.Ps,
                             PrivateData.HobList
                             );

定义在  PeiMain.c 中

PEI_CORE_TEMP_POINTERS      TempPtr;

PEI_CORE_TEMP_POINTERS 定义如下:

///
/// Union of temporarily used function pointers (to save stack space)
///
typedef union {
  PEICORE_FUNCTION_POINTER     PeiCore;
  EFI_PEIM_ENTRY_POINT2        PeimEntry;
  EFI_PEIM_NOTIFY_ENTRY_POINT  PeimNotifyEntry;
  EFI_DXE_IPL_PPI              *DxeIpl;
  EFI_PEI_PPI_DESCRIPTOR       *PpiDescriptor;
  EFI_PEI_NOTIFY_DESCRIPTOR    *NotifyDescriptor;
  VOID                         *Raw;
} PEI_CORE_TEMP_POINTERS;

EFI_DXE_IPL_PPI 定义如下:

///
/// Final service to be invoked by the PEI Foundation.
/// The DXE IPL PPI is responsible for locating and loading the DXE Foundation.
/// The DXE IPL PPI may use PEI services to locate and load the DXE Foundation.
///
struct _EFI_DXE_IPL_PPI {
  EFI_DXE_IPL_ENTRY Entry;
};

/**
  The architectural PPI that the PEI Foundation invokes when
  there are no additional PEIMs to invoke.

  This function is invoked by the PEI Foundation.
  The PEI Foundation will invoke this service when there are
  no additional PEIMs to invoke in the system.
  If this PPI does not exist, it is an error condition and
  an ill-formed firmware set. The DXE IPL PPI should never
  return after having been invoked by the PEI Foundation.
  The DXE IPL PPI can do many things internally, including the following:
    - Invoke the DXE entry point from a firmware volume
    - Invoke the recovery processing modules
    - Invoke the S3 resume modules

  @param  This           Pointer to the DXE IPL PPI instance
  @param  PeiServices    Pointer to the PEI Services Table.
  @param  HobList        Pointer to the list of Hand-Off Block (HOB) entries.

  @retval EFI_SUCCESS    Upon this return code, the PEI Foundation should enter
                         some exception handling.Under normal circumstances,
                         the DXE IPL PPI should not return.

**/
typedef
EFI_STATUS
(EFIAPI *EFI_DXE_IPL_ENTRY)(
  IN CONST EFI_DXE_IPL_PPI        *This,
  IN EFI_PEI_SERVICES             **PeiServices,
  IN EFI_PEI_HOB_POINTERS         HobList
  );

对着应\MdeModulePkg\Core\DxeIplPeim\DxeLoad.c

//
// Module Globals used in the DXE to PEI hand off
// These must be module globals, so the stack can be switched
//
CONST EFI_DXE_IPL_PPI mDxeIplPpi = {
  DxeLoadCore
};

可以用加 DEBUG Message 的方法确定,前面的TempPtr.DxeIpl->Entry() 调用,执行的是\mdemodulepkg\core\dxeiplpeim\DxeLoad.c中的

/**
   Main entry point to last PEIM.

   This function finds DXE Core in the firmware volume and transfer the control to
   DXE core.

   @param This          Entry point for DXE IPL PPI.
   @param PeiServices   General purpose services available to every PEIM.
   @param HobList       Address to the Pei HOB list.

   @return EFI_SUCCESS              DXE core was successfully loaded.
   @return EFI_OUT_OF_RESOURCES     There are not enough resources to load DXE core.

**/
EFI_STATUS
EFIAPI
DxeLoadCore (
  IN CONST EFI_DXE_IPL_PPI *This,
  IN EFI_PEI_SERVICES      **PeiServices,
  IN EFI_PEI_HOB_POINTERS  HobList
  )

其中通过 gEfiPeiLoadFilePpiGuid 加载 DXE Core

Loading PEIM D6A2CB7F-6A18-4E2F-B43B-9920A733700A
Loading PEIM at 0x00007EA3000 EntryPoint=0x00007EA3D78 DxeCore.efi
  DEBUG ((DEBUG_INFO | DEBUG_LOAD, "Loading DXE CORE at 0x%11p EntryPoint=0x%11p\n", (VOID *)(UINTN)DxeCoreAddress, FUNCTION_ENTRY_POINT (DxeCoreEntryPoint)));

Loading DXE CORE at 0x00007EA3000 EntryPoint=0x00007EA3D78

最后,使用下面的代码跳入 DxeCore:

  //
  // Transfer control to the DXE Core
  // The hand off state is simply a pointer to the HOB list
  //
  HandOffToDxeCore (DxeCoreEntryPoint, HobList);

跳转之后首先进入一个DxeCoreEntryPoint  模块,代码在 \MdePkg\Library\DxeCoreEntryPoint

/**
  The entry point of PE/COFF Image for the DXE Core.

  This function is the entry point for the DXE Core. This function is required to call
  ProcessModuleEntryPointList() and ProcessModuleEntryPointList() is never expected to return.
  The DXE Core is responsible for calling ProcessLibraryConstructorList() as soon as the EFI
  System Table and the image handle for the DXE Core itself have been established.
  If ProcessModuleEntryPointList() returns, then ASSERT() and halt the system.

  @param  HobStart  The pointer to the beginning of the HOB List passed in from the PEI Phase.

**/
VOID
EFIAPI
_ModuleEntryPoint (
  IN VOID  *HobStart
  )

在这个函数中通过下面的函数转入 DXE Core:

  //
  // Call the DXE Core entry point
  //
  ProcessModuleEntryPointList (HobStart);

特别之处在于对应的ProcessModuleEntryPointList 位于\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Core\Dxe\DxeMain\DEBUG\AutoGen.c

VOID
EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )
{
  DxeMain (HobStart);
}

这个文件是编译期自动生成的,无法直接插入 DEBUG 来输出(编译时会被覆盖)。起初我以为是BIOS代码指定了上述的内容,但是在代码中无法搜索到类似的字样,后来经过研究这是 Basetools 里面代码生成的。在\BaseTools\Source\Python\AutoGen\GenC.py有如下定义:

gDxeCoreEntryPointString = TemplateString("""
${BEGIN}
VOID
EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )

{
  ${Function} (HobStart);
}
${END}
""")

如果我们在  ${Function} (HobStart); 前面加入一行注释,那么对应的Auto Gen.c 会变成如下:

EFIAPI
ProcessModuleEntryPointList (
  IN VOID  *HobStart
  )

{
  // www.lab-z.com
  DxeMain (HobStart);
}

调用的代码在 \mdemodulepkg\core\dxe\dxemain\DxeMain.c

// Main entry point to the DXE Core
//

/**
  Main entry point to DXE Core.

  @param  HobStart               Pointer to the beginning of the HOB List from PEI.

  @return This function should never return.

**/
VOID
EFIAPI
DxeMain (
  IN  VOID *HobStart
  )

从这里也可以看出:EDK2 编译过程中有BaseTools中的工具参与了编译过程,如果你在纯代码部分无法找到对应的内容,不妨考虑一下编译工具中。

最后,讲个好玩的事情:

肯·汤普森还有一个备受争议的行为,就是在UNIX里留后门。是的,这哥们竟然在代码里下毒。

最开始的时候,UNIX系统在贝尔实验室是供大家免费使用的。有人发现,肯·汤普森总能进入每个人的账户,于是一位同事就分析UNIX代码,重新编译了系统。

令人意想不到的是,肯·汤普森还是能进入他们的账户,贝尔实验室的科学家们却对此束手无策。

直到1983年,肯·汤普森在他的图灵奖获奖感言里揭示了这一秘密,原来,让他轻松“侵入”各位同事账户的秘诀不在UNIX代码,而在编译UNIX代码的C编译器里,而肯·汤普森正是编译器的开发者。

https://www.sohu.com/a/469445120_121124377

CH343 ESP32 串口模块

本文首发 https://mc.dfrobot.com.cn/thread-312276-1-1.html

前面介绍了ESP32 S2 的半针测试板,这里介绍一下配合这个半针测试板的烧写板。

这个烧写板有如下特点:

1.ESP32 自动下载无需按键;

2.板子上提供了下载按钮;

3.串口最高支持 6M 波特率;

设计电路图如下:

电路图

选择SOP16封装的 CH343,这样焊接非常简单。此外,上面中间的电路用于实现自动下载功能,会拉ESP32的EN_AUTO和IO0 Pin。右侧从上到下分别是:数据接口,供电接口和5V 转3.3V电路。

PCB

设计上预留了2个跳线,下图红框位置。短接的时候 Power Pin(桔色框)中会有对应的电压。这样的设计是因为 ESP32 使用 3.3V,但是如果需要支持 USB ,那么还需要5V电源。

渲染图

有了这样的卡,在你的ESP32 S2 设计上只要预留上图中右上方的引脚即可(特别注意TX RX 需要交叉)。

完整的工程下载: