ESP32 搭配语音合成模块

之前介绍过SYN6288 模块【参考1】,这次配合 ESP32 实现随机生成一个时间,然后通过语音播放出来。

硬件使用的是ESP-WROOM-32 ESP-32S模块,语音模块的 RX 连接到 ESP32 的GPIO17:

String DataBuffer[11]={{"零"},{"一"},{"二"},{"三"},{"四"},{"五"},{"六"},{"七"},{"八"},{"九"},{"十"}};
                      
//存放转化后的汉字 Unicode值
char character[40];

#include <HardwareSerial.h>

//根据字符串计算计算出来的送到串口的值
char output[50];

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600);
  delay(1000);
}

void loop() {
  Serial.println("generate");

  int hh=random(13),mm=random(60);
  String timeStr="";

  Serial.print("Generate time: ");
  Serial.print(hh);Serial.print(":");Serial.println(mm);

  if (hh>9) {timeStr+="十";
    if (hh>10) {timeStr+=DataBuffer[hh-10];}
  }
  else {timeStr+=DataBuffer[hh];}
  timeStr+="点";

  if (mm==0) {timeStr+="整";}
  else
    if (mm<10) {timeStr+="零";timeStr+=DataBuffer[mm];}
    else if (mm%10==0) {timeStr+=DataBuffer[mm/10]; timeStr+="十";}
         else {timeStr+=DataBuffer[mm/10];timeStr+="十";timeStr+=DataBuffer[mm%10];}
  if(mm!=0) {timeStr+="分";}
 
    //timeStr="十二点五十八分";
  //首先输出一次Arduino 原始字符串 UTF8 的值
  for (int i =0;i<timeStr.length()*3;i++) {
     Serial.print(timeStr[i]&0xFF,HEX);
      Serial.print(' ');
  }
  Serial.println(' ');

  //将 UTF8 转化为 Unicode
    for (int i =0;i<timeStr.length()/3;i=i+1) {
      character[i*2]=((timeStr[i*3]&0xF)<<4)+((timeStr[i*3+1]>>2)&0xF);
      character[i*2+1]=((timeStr[i*3+1]&0x3)<<6)+(timeStr[i*3+2]&0x3F);
      Serial.print(character[i*2]&0xFF,HEX);
      Serial.print(' ');
      Serial.print(character[i*2+1]&0xFF,HEX);
      Serial.print(' ');      
    } 
  Serial.println(""); 

  output[0]=0xFD;
  output[1]=(timeStr.length()/3*2+3)>>8;
  output[2]=((timeStr.length()/3*2+3)&0xFF);
  output[3]=0x01;
  output[4]=0x03;
  //把字符串定义搬过去
  for (int i=0;i<timeStr.length()/3*2;i++) {
      output[i+5]=character[i];
    } 
  //计算一个校验和  
  output[timeStr.length()/3*2+5]=output[0];
  for (int i=1;i<timeStr.length()/3*2+5;i++) {
      output[timeStr.length()/3*2+5]=output[timeStr.length()/3*2+5] ^ output[i];
    }   
  
  for (int i =0;i<timeStr.length()/3*2+6;i++) {
      Serial.print(output[i]&0xFF,HEX);
      Serial.print(' ');
      Serial2.write(output[i]);
    }     
  
Serial.println(' ');
    delay(3000);
}

参考:

1. http://www.lab-z.com/ttssyn/ TTS 真人发音 SYN6288 模块

Step to UEFI (229)继续研究修改QEMU 显示的版本号

前面提到过 OVMF 的 Setup 首页版本号显示的代码【参考1】,在 \MdeModulePkg\Application\UiApp\FrontPage.c

  SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
  Status = Smbios->GetNext (Smbios, &SmbiosHandle, NULL, &Record, NULL);
  while (!EFI_ERROR(Status)) {
    if (Record->Type == SMBIOS_TYPE_BIOS_INFORMATION) {
      Type0Record = (SMBIOS_TABLE_TYPE0 *) Record;
      StrIndex = Type0Record->BiosVersion;
      GetOptionalStringByIndex ((CHAR8*)((UINT8*)Type0Record + Type0Record->Hdr.Length), StrIndex, &NewString);

      FirmwareVersionString = (CHAR16 *) PcdGetPtr (PcdFirmwareVersionString);
      if (*FirmwareVersionString != 0x0000 ) {
        FreePool (NewString);
        NewString = (CHAR16 *) PcdGetPtr (PcdFirmwareVersionString);
        UiCustomizeFrontPageBanner (3, TRUE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_BIOS_VERSION), NewString, NULL);
      } else {
        UiCustomizeFrontPageBanner (3, TRUE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_BIOS_VERSION), NewString, NULL);
        FreePool (NewString);
      }
    }

就是说出了从 SMBIOS 之外,还可以在 PCD 中直接给定。实验,在\OvmfPkg\OvmfPkgX64.dsc 文件中加入下面的代码

!if $(SMM_REQUIRE) == TRUE
  gUefiOvmfPkgTokenSpaceGuid.PcdSmmSmramRequire|TRUE
  gUefiCpuPkgTokenSpaceGuid.PcdCpuSmmEnableBspElection|FALSE
!endif

[PcdsFixedAtBuild]
#LABZ_Start
  gEfiMdeModulePkgTokenSpaceGuid.PcdFirmwareVersionString|L"Galileo 1.0.4"  
#LABZ_End

  gEfiMdeModulePkgTokenSpaceGuid.PcdStatusCodeMemorySize|1
  gEfiMdeModulePkgTokenSpaceGuid.PcdResetOnMemoryTypeInformationChange|FALSE
  gEfiMdePkgTokenSpaceGuid.PcdMaximumGuidedExtractHandler|0x10
  gEfiMdeModulePkgTokenSpaceGuid.PcdPeiCoreMaxFvSupported|6

编译之后,运行结果如下,可以看到新增加了  “Galileo 1.0.4”字样。

新加入了版本字符

但是,非常奇怪的是按照之前的方法,无法在 FFS 文件中找到对应的字符串。为了进一步研究,打开生成 COD 的功能。在 \MdeModulePkg\Application\UiApp\UiApp.inf 加入下面这样一行:

[BuildOptions]  
  MSFT:*_*_X64_CC_FLAGS = /FAsc /Od

再次编译,在

\Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Application\UiApp\UiApp\DEBUG\AutoGen.c 有如下定义:

GLOBAL_REMOVE_IF_UNREFERENCED const UINT16 _gPcd_FixedAtBuild_PcdFirmwareVersionString[14] = {71, 97, 108, 105, 108, 101, 111, 32, 49, 46, 48, 46, 52, 0 };

进一步,在 \Build\OvmfX64\DEBUG_VS2015x86\X64\MdeModulePkg\Application\UiApp\UiApp\AutoGen.cod 可以看到下面的定义

;	COMDAT _gPcd_FixedAtBuild_PcdFirmwareVersionString
CONST	SEGMENT
_gPcd_FixedAtBuild_PcdFirmwareVersionString DW 047H
	DW	061H
	DW	06cH
	DW	069H
	DW	06cH
	DW	065H
	DW	06fH
	DW	020H
	DW	031H
	DW	02eH
	DW	030H
	DW	02eH
	DW	034H
	DW	00H
CONST	ENDS

特别注意,每一个字符是 2Bytes,因此,就是说和之前不同,这里字符串使用的是 Unicode 编码方式。因此,一个字符会使用2个Bytes来进行保存。再次到 FFS 中搜索,可以看到如下字样:

十六进制编辑器直接编辑 Unicode 字符

尝试修改之,然后用前面直接运行 GenFDS 的方法产生的 ROM 文件,运行结果如下:

修修改QEMU Setup 界面之后的结果

进一步研究,在【参考2】有介绍,我们定义的gEfiMdeModulePkgTokenSpaceGuid.PcdFirmwareVersionString 是 FixedAtBuild PCD的PCD 变量,编译时会像宏一样直接展开放在代码中。

参考:

1.http://www.lab-z.com/qemusetup/ QEMU Setup 首页研究

2. https://blog.csdn.net/jiangwei0512/article/details/80288001 BIOS/UEFI基础——PCD

学习 Verilog 的好去处:HDLBits

带着问题进行学习是掌握一门技术非常有效的方法。

https://hdlbits.01xz.net/ 网站提供了 Verilog 的一些题目,有兴趣的朋友可以尝试进行练习。同时这个网站在提供 Verilog 基础语法教程的同时,还能够在线仿真你的 Verilog 模块,将你的输出与正确的时序比较。

如果你在解题时遇到问题,还可以在知乎“HDLBits 中文导学”专栏中看到解析(比如我经常无法理解题目意思),还可以在评论中参加讨论。

https://zhuanlan.zhihu.com/c_1131528588117385216

Step to UEFI (228)QEMU Setup 首页研究

最近研究了一下 OVMF 项目的 Setup 首页界面,实现的方法挺有意思。

第一个问题,具体代码在什么地方,如果我想增加一行字应该如何实现。

OVMF 运行首页

经过研究,相关代码在  MdeModulePkg\Application\UiApp\FrontPage.c 中。首先,在 FrontPageVfr.Vfr 定义了8个字符串变量,例如:STR_CUSTOMIZE_BANNER_LINE4_LEFT。 在首页上面左右各有4个。在 UpdateFrontPageBannerStrings() 函数中动态填充这些字符串。例如:从 SMBIOS 中取得CPU 信息显示出来:

    if ((Record->Type == SMBIOS_TYPE_PROCESSOR_INFORMATION) && !FoundCpu) {
      Type4Record = (SMBIOS_TABLE_TYPE4 *) Record;
      //
      // The information in the record should be only valid when the CPU Socket is populated.
      //
      if ((Type4Record->Status & SMBIOS_TYPE4_CPU_SOCKET_POPULATED) == SMBIOS_TYPE4_CPU_SOCKET_POPULATED) {
        StrIndex = Type4Record->ProcessorVersion;
        GetOptionalStringByIndex ((CHAR8*)((UINT8*)Type4Record + Type4Record->Hdr.Length), StrIndex, &NewString);
        UiCustomizeFrontPageBanner (2, TRUE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_CPU_MODEL), NewString, NULL);
        FreePool (NewString);

        ConvertProcessorToString(Type4Record->CurrentSpeed, 6, &NewString);
        UiCustomizeFrontPageBanner (2, FALSE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_CPU_SPEED), NewString, NULL);
        FreePool (NewString);

        FoundCpu = TRUE;
      }
    }

知道了上面的实现,如果想实现增加一行字符串,实现非常简单:在  FrontPageStrings.uni 文件中修改字符串增加内容即可:

#string STR_CUSTOMIZE_BANNER_LINE4_LEFT  #language en-US  "www.lab-z.com"
                                         #language fr-FR  ""

运行结果:

可以看到上面增加了 www.lab-z.com 字样

第二个问题,研究一下BIOS 版本号的显示。取得的方法非常类似,同样的文件中,代码如下:

    if (Record->Type == SMBIOS_TYPE_BIOS_INFORMATION) {
      Type0Record = (SMBIOS_TABLE_TYPE0 *) Record;
      StrIndex = Type0Record->BiosVersion;
      GetOptionalStringByIndex ((CHAR8*)((UINT8*)Type0Record + Type0Record->Hdr.Length), StrIndex, &NewString);

      FirmwareVersionString = (CHAR16 *) PcdGetPtr (PcdFirmwareVersionString);
      if (*FirmwareVersionString != 0x0000 ) {
        FreePool (NewString);
        NewString = (CHAR16 *) PcdGetPtr (PcdFirmwareVersionString);
        UiCustomizeFrontPageBanner (3, TRUE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_BIOS_VERSION), NewString, NULL);
      } else {
        UiCustomizeFrontPageBanner (3, TRUE, &NewString);
        HiiSetString (gFrontPagePrivate.HiiHandle, STRING_TOKEN (STR_FRONT_PAGE_BIOS_VERSION), NewString, NULL);
        FreePool (NewString);
      }
    }

就是说,如果没有定义 PcdFirmwareVersionString 那么会从 SMBIOS 中抓取BIOS 版本显示出来。

在 \OvmfPkg\SmbiosPlatformDxe\SmbiosPlatformDxe.c 可以看到版本信息定义如下:

#define TYPE0_STRINGS \
  "EFI Development Kit II / OVMF\0"     /* Vendor */ \
  "0.0.0\0"                             /* BiosVersion */ \
  "02/06/2015\0"                        /* BiosReleaseDate */

可以通过直接修改上面代码的方式来实现修改版本号的目标。作为“寻常不走路”的有志青年,当然不会满足于使用这样的方式来修改版本号。下面介绍直接在二进制上修改这个版本号。

不走寻常路

在 Build\OvmfX64\DEBUG_VS2015x86\FV\Ffs\4110465d-5ff3-4f4b-b580-24ed0d06747aSmbiosPlatformDxe 目录下,可以找到 4110465d-5ff3-4f4b-b580-24ed0d06747a.ffs 这个文件。打开文件可以搜索到 “1.2.3”字样:

可以直接搜索到字符串

尝试修改为 “4.5.6” 之后,再次使用之前介绍过的 GenFds ,生成 FD 文件,运行后就可以发现字符发生了变化:

版本号发生了变化

这个实验也从侧面证明了 GenFds 是将需要的 FFS 文件集合在一起,然后压缩放入指定的文件中。

SleepStudy简介

SleepStudy 是微软提供的一个查看功耗相关的功能,在所有的 Windows 10 中都有无需额外下载安装。使用方法:

1.打开 CMD 窗口,特别注意需要管理员权限;

2.运行   powercfg /sleepstudy, 之后会将报告生成在一个 html 文件中

例如,下面就是一个 Pass 的结果,可以看到系统进入了 MS,98% 的 Software Residency和 Hardware Residency。

而这个是一个 Fail 的结果:Software Residency 为0 ,没有获得到 Hardware Residency。

这个报告在进行 Stress 测试的时候非常有用,能够一次性看到每一个Loop的结果,当然如果有问题,还需要运行 PHM 进行分析。

这个报告在进行 Stress 测试的时候非常有用,能够一次性看到每一个Loop的结果,当然如果有问题,还需要运行 PHM 进行分析。

特别注意:

  1. SleepStudy是以15分钟作为统计间隔,如果你的测试不足 15分钟,测试结果不一定会出现在报告 中;
  2. 如果你的系统中有大量的事件记录(比如,机器在之前进行了 Restart 或者Shutdown Loop est), 结果导出可能会非常慢,看起来仿佛程序卡死一样,这时候请足够的耐心等待下去。换句话说,如果想通过 SleepStudy 查看系统 MS情况,最好找一台“干净”的系统来测试;
  3. 目前还没有清除 SleepStudy Log的办法,如果你知道不妨写在留言中分享给大家,谢谢。

2021年5月19日 最近又在测试 MS 功能,借此机会研究了一下清除 SleepStudy Log 的方法:清除 Event 是无效的,但是可以通过删除 Windows\system32\sleepStudy 目录下的内容来实现清除 SleepStudy Log 的目的。

Python 重新加载代码

当你使用  import YourFile.py 之后YourFile.py就会加载到内存中,即使你修改了本地的文件,再次调用也会是修改之前的代码。因此,这里需要用 reload 来强制重新加载。使用一个例子进行说明,首先编写一个简单的代码:

def Test():

print(“Test Script from labz”)

下面的步骤1导入上面的代码,然后运行 Test() 函数。接下来修改上面的代码,在输出的字符串末尾加入字符 2.步骤2中再次Import 然后运行,可以看到没有效果:

    之后从 imp 中 import reload 函数(我的 python 是 3.8 , 这里同 2.X 的版本有差别)。再次 reload 代码之后可以看到结果有变化。

总结:可以通过 from imp import reload, 之后每次 reload 你需要的文件即可刷新。

Step to UEFI (227)VS2019 EDK202008 下的 Libc 编译

LibC 是 EDK2 的一个库,提供了标准 C 库的支持。这个库在 VS2015 + EDK202008 的组合下工作正常,但是在 VS2019 下就会遇到一些问题,本文将介绍如何解决之。完成的目标是:完整编译 LibC 提供的 AppPkg 。LibC 可以在 https://github.com/tianocore/edk2-libc 下载到,打开之后内容如下:

Libc 解压之后文件

解压三个目标到 EDK202008 后即可开始编译。

使用 “x86 Native Tools Command Prompt for VS2019”

VS2019 x86 窗口

命令

  1. edksetup
  2. build -a X64 -p AppPkg\AppPkg.dsc -t VS2019  (特别注意, VS2019 必须大写)

之后就遇到了第一个错误:

“Socket.c
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(573): error C2220: the following warning is treated as an error
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(573): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1337): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1480): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(1892): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(2754): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
c:\buildbs\edk202008\StdLib\EfiSocketLib\Socket.c(2974): warning C4459: declaration of 'errno' hides global declaration
c:\buildbs\edk202008\StdLib\Include\errno.h(43): note: see declaration of 'errno'
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\cl.exe"' : return code '0x2'
Stop.”

解决方法是:在 StdLib.inc 末尾添加下面的内容:

# Temporarily restrict compiler warnings to those produced by VS2012.
  # Code that fails when these flags are removed will have to be rewritten
  # in order to pass.  This may be as simple as renaming an object, but may
  # require more significant changes.
    MSFT:*_VS2015_*_CC_FLAGS          = /Wv:11
    MSFT:*_VS2015x86_*_CC_FLAGS       = /Wv:11
    MSFT:*_VS2015xASL_*_CC_FLAGS      = /Wv:11
    MSFT:*_VS2015x86xASL_*_CC_FLAGS   = /Wv:11

#LABZ_Start
  # Temporarily restrict compiler warnings to those produced by VS2012.
  # Code that fails when these flags are removed will have to be rewritten
  # in order to pass.  This may be as simple as renaming an object, but may
  # require more significant changes.
    MSFT:*_VS2019_*_CC_FLAGS          = /Wv:11
    MSFT:*_VS2019x86_*_CC_FLAGS       = /Wv:11
    MSFT:*_VS2019xASL_*_CC_FLAGS      = /Wv:11
    MSFT:*_VS2019x86xASL_*_CC_FLAGS   = /Wv:11
#LABZ_End

再次编译会遇到下面这个这个错误:

        "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\link.exe" /OUT:c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\DEBUG\OobTx.dll /NOLOGO /NODEFAULTLIB /IGNORE:4001 /IGNORE:4281 /OPT:REF /OPT:ICF=10 /MAP /ALIGN:32 /SECTION:.xdata,D /SECTION:.pdata,D /Machine:X64 /LTCG /DLL /ENTRY:_ModuleEntryPoint /SUBSYSTEM:EFI_BOOT_SERVICE_DRIVER /SAFESEH:NO /BASE:0 /DRIVER /DEBUG /WHOLEARCHIVE  @c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\OUTPUT\static_library_files.lst
LibGdtoa.lib(ldtoa.obj) : error LNK2001: unresolved external symbol __fpclassifyd
BsdSocketLib.lib(res_send.obj) : error LNK2001: unresolved external symbol inet_ntoa
BsdSocketLib.lib(res_mkupdate.obj) : error LNK2001: unresolved external symbol inet_aton
BsdSocketLib.lib(ns_print.obj) : error LNK2001: unresolved external symbol inet_ntop
BsdSocketLib.lib(getnetbyht.obj) : error LNK2001: unresolved external symbol inet_network
c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx\DEBUG\OobTx.dll : fatal error LNK1120: 5 unresolved externals
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x64\link.exe"' : return code '0x460'
Stop.
build.py...
 : error 7000: Failed to execute command
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x86\nmake.exe /nologo tbuild [c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\OobTx\OobTx]


build.py...
 : error 7000: Failed to execute command
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.26.28801\bin\Hostx86\x86\nmake.exe /nologo tbuild [c:\buildbs\edk202008\Build\AppPkg\DEBUG_VS2019\X64\AppPkg\Applications\Sockets\DataSink\DataSink]


build.py...
 : error F002: Failed to build module
        c:\buildbs\edk202008\AppPkg\Applications\Sockets\OobTx\OobTx.inf [X64, VS2019, DEBUG]

解决方法是修改Conf/tools_def.txt 文件,注释掉一行。对于/WHOLEARCHIVE 这个参数的解释,可以在【参考1】看到,似乎是要求编译过程中把所有的可能的 OBJ 都编译一次?

*_VS2019_*_MAKE_FLAGS      = /nologo
*_VS2019_*_SLINK_FLAGS     = /NOLOGO /LTCG
*_VS2019_*_APP_FLAGS       = /nologo /E /TC
*_VS2019_*_PP_FLAGS        = /nologo /E /TC /FIAutoGen.h
*_VS2019_*_VFRPP_FLAGS     = /nologo /E /TC /DVFRCOMPILE /FI$(MODULE_NAME)StrDefs.h
#LABZ *_VS2019_*_DLINK2_FLAGS    = /WHOLEARCHIVE
*_VS2019_*_ASM16_PATH      = DEF(VS2019_BIN_IA32)\ml.exe
*_VS2019_*_DEPS_FLAGS      = DEF(MSFT_DEPS_FLAGS)
##################
# ASL definitions
##################

再次编译,就可以正常通过得到结果:

编译结果

修改后的 Libc 可以在这里下载

链接: https://pan.baidu.com/s/1xCjiZ6_Shsa0tWOdRCjGrg 提取码: k6n2

感觉目前 EDK2 对于 VS2019 的支持还是有问题的,对于有编译需求的朋友,我依然建议使用 VS2015。

参考:

/WHOLEARCHIVE (Include All Library Object Files)

Force the linker to include all object files in the static library in the linked executable.

Syntax

/WHOLEARCHIVE
/WHOLEARCHIVE:library

Arguments

library
An optional pathname to a static library. The linker includes every object file from this library.

Remarks

The /WHOLEARCHIVE option forces the linker to include every object file from either a specified static library, or if no library is specified, from all static libraries specified to the LINK command. To specify the /WHOLEARCHIVE option for multiple libraries, you can use more than one /WHOLEARCHIVE switch on the linker command line. By default, the linker includes object files in the linked output only if they export symbols referenced by other object files in the executable. The /WHOLEARCHIVE option makes the linker treat all object files archived in a static library as if they were specified individually on the linker command line.

The /WHOLEARCHIVE option can be used to re-export all the symbols from a static library. This allows you to make sure that all of your library code, resources, and metadata are included when you create a component from more than one static library. If you see warning LNK4264 when you create a static library that contains Windows Runtime components for export, use the /WHOLEARCHIVE option when linking that library into another component or app.

The /WHOLEARCHIVE option was introduced in Visual Studio 2015 Update 2.

C# SerialPort 发送8Bits的问题

最近在使用 SerialPort 类发送串口数据,惊奇的发现PC端程序发送 0xFF 对面(ESP32)只能收到 0x7F。但是用串口工具是可以正常发送的。于是针对这个问题进行研究。Baidu上说是因为奇偶校验导致的,我设置了无校验现象依旧。

查看 SerialPort 类发现它有一个 Encoding 属性,默认是ASCIIEncoding,一般来说,正经的 ASCII 是 0-128 就是0-0x7F,因此,直觉上这个问题是它导致的。最终在下面的页面搜索到了答案:

http://www.innovatic.dk/knowledg/SerialCOM/SerialCOM.htm

By default, strings and char’s are transmitted (not coded) as 7-bit ASCII where all characters above 127 are illegal and will be replaced with “?” (code 63) – even though you have defined 8 data bits! Therefore, you cannot use a string or char related method to send or read binary information (0-255) unless you change the encoding to something, which include all values up to 255 like:

   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(28591)  ' or
   YourSerialPort.Encoding = System.Text.Encoding.GetEncoding(1252)

于是按照上面的方法修改代码问题就得以解决。

Step to UEFI (226)EDK2 生成 ROM 最后的打包动作(上)

编译产生 BIOS ROM的最后一个步骤是将 FV 打包,那么这个操作是在哪里?带着这个问题,以 OVMF 的 PKG 为例进行研究。

我们平时编译使用的是 BaseTools\Bin\Win32下面的 build.exe ,这个文件是使用 Python 转 EXE工具生成的。如果想研究 Build.exe 具体的代码,可以将 build.exe 改名或者删除,然后保证当前 path 中含有BaseTools\BinWrappers\WindowsLike 路径,这样调用 Built 时会直接使用目录下的 Build.bat,该批处理内容如下:

@setlocal
@set ToolName=%~n0%
@%PYTHON_HOME%\python.exe %BASE_TOOLS_PATH%\Source\Python\%ToolName%\%ToolName%.py %*

如果你调用 build就会使用 Python.exe 来执行BaseTools\Source\Python\build\build.py。特别提醒,我实验发现 Python38 会出现如下的错误:

except BaseException, X:
^
SyntaxError: invalid syntax

修正方法是更换为 Python27 的版本(他们可以共存)。另外,这个问题也可能是我使用的 EDK2 版本的问题(我实验环境是EDK2201911).

代码入口在 main中,可以通过加入下面2条语句做一个标记:

print("www.lab-z.com\n")
subprocess.call("pause",shell=True)

添加在代码中入口位置

## Tool entrance method
#
# This method mainly dispatch specific methods per the command line options.
# If no error found, return zero value so the caller of this tool can know
# if it's executed successfully or not.
#
#   @retval 0     Tool was successful
#   @retval 1     Tool failed
#
def Main():

测试结果:

这里证明了上述代码就是程序入口

代码中MyBuild.Launch()是具体执行 build 动作的地方:

        MyBuild = Build(Target, Workspace, Option)
        GlobalData.gCommandLineDefines['ARCH'] = ' '.join(MyBuild.ArchList)
        if not (MyBuild.LaunchPrebuildFlag and os.path.exists(MyBuild.PlatformBuildPath)):
            MyBuild.Launch()

实验确定对应代码:

    ## Launch the module or platform build
    #
    def Launch(self):
        if not self.ModuleFile:
            if not self.SpawnMode or self.Target not in ["", "all"]:
                self.SpawnMode = False
                self._BuildPlatform()
            else:
                self._MultiThreadBuildPlatform()
            self.CreateGuidedSectionToolsFile()
        else:
            self.SpawnMode = False
            self._BuildModule()

self._MultiThreadBuildPlatform() 这里会完成所有的动作.这个函数在同一个文件中:

    ## Build a platform in multi-thread mode
    #
    def _MultiThreadBuildPlatform(self):
        SaveFileOnChange(self.PlatformBuildPath, '# DO NOT EDIT \n# FILE auto-generated\n', False)

我们的目标是找到最后将 FV 打包的过程,在代码中下面的位置加入 Pause 可以正常停下来,所以确定这个是我们目标动作的位置。

                    if self.Fdf:
    		print("www.lab-z.com\n")
subprocess.call("pause",shell=True)

                        #
                        # Generate FD image if there's a FDF file found
                        #
                        GenFdsStart = time.time()
                        LaunchCommand(Wa.GenFdsCommand, os.getcwd())

                        #
                        # Create MAP file for all platform FVs after GenFds.
                        #
                        self._CollectFvMapBuffer(MapBuffer, Wa, ModuleList)
                        self.GenFdsTime += int(round((time.time() - GenFdsStart)))
                    #
                    # Save MAP buffer into MAP file.
                    #
                    self._SaveMapFile(MapBuffer, Wa)

代码中加入下面两句输出具体调用的命令:

                        print("[%s]\n" % Wa.GenFdsCommand)
                        print("[%s]\n" % os.getcwd())
                        subprocess.call("pause",shell=True)

测试,在编译环境下输入下面的 command, 可以实现生成 OVMF.fd:

GenFds -f c:\buildbs\i2c\OvmfPkg\OvmfPkgX64.fdf --conf=c:\buildbs\i2c\conf -o c:\buildbs\i2c\Build\OvmfX64\DEBUG_VS2015x86 -t VS2015x86 -b DEBUG -p c:\buildbs\i2c\OvmfPkg\OvmfPkgX64.dsc -a X64  -D "EFI_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"  -D "EDK_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"  -D "TOOL_CHAIN_TAG=VS2015x86"  -D "TOOLCHAIN=VS2015x86"  -D "TARGET=DEBUG"  -D "FAMILY=MSFT"  -D "WORKSPACE=c:\\buildbs\\i2c"  -D "EDK_TOOLS_PATH=c:\\buildbs\\i2c\\basetools"  -D "ARCH=X64"  -D "ECP_SOURCE=c:\\buildbs\\i2c\\edkcompatibilitypkg"

结论:build 过程中的最后一段是调用 GenFds 来生成 BIOS 的 FD (或者叫做 ROM)文件。

推荐一个十六进制编辑器 HXD

之前我一直使用 WinHex作为十六进制编译器,但是WinHex 是收费的,经过研究找到了 HxD 这款软件,它是免费的十六进制编辑软件,主页在 https://mh-nexus.de/en/hxd/  经过一段时间的使用(主要是用于 ROM 文件的分析),感觉挺好用,有需要的朋友可以试试。

HXD 界面

它提供了如下功能:

  • Available as a portable and installable edition
  • RAM-Editor
    • To edit the main memory
    • Memory sections are tagged with data-folds
  • Disk-Editor (Hard disks, floppy disks, ZIP-disks, USB flash drives, CDs, …)
    • RAW reading and writing of disks and drives
    • for Win9x, WinNT and higher
  • Instant opening regardless of file-size
    • Up to 8EB; opening and editing is very fast
  • Liberal but safe file sharing with other programs
  • Flexible and fast searching/replacing for several data types
    • Data types: text (including Unicode), hex-values, integers and floats
    • Search direction: Forward, Backwards, All (starting from the beginning)
  • File compare (simple)
  • View data in Ansi, DOS, EBCDIC and Macintosh character sets
  • Checksum-Generator: Checksum, CRCs, Custom CRC, SHA-1, SHA-512, MD5, …
  • Exporting of data to several formats
    • Source code (Pascal, C, Java, C#, VB.NET)
    • Formatted output (plain text, HTML, Richtext, TeX)
    • Hex files (Intel HEX, Motorola S-record)
  • Insertion of byte patterns
  • File tools
    • File shredder for safe file deletion
    • Splitting or concatenating of files
  • Basic data analysis (statistics)
    • Graphical representation of the byte/character distribution
    • Helps to identify the data type of a selection
  • Byte grouping
    • 1, 2, 4, 8 or 16 bytes packed together into one column
  • “Hex only” or “text only”-modes
  • Progress-window for lengthy operations
    • Shows the remaining time
    • Button to cancel
  • Modified data is highlighted
  • Unlimited undo
  • “Find updates…”-function
  • Easy to use and modern interface
  • Goto address
  • Printing
  • Overwrite or insert mode
  • Cut, copy, paste insert, paste write
  • Clipboard support for other hex editors
    • Visual Studio/Visual C++, WinHex, HexWorkshop, …
  • Bookmarks
    • Ctrl+Shift+Number (0-9) sets a bookmark
    • Ctrl+Number (0-9) goes to a bookmark
  • Navigating to nibbles with Ctrl+Left or Ctrl+Right
  • Flicker free display and fast drawing