UEFI SPEC

UEFI 相关的 Spec 可以在 https://uefi.org/specifications 下载到。但是国内访问并不稳定,因此在这里放置一份:

1.ACPI Specification
当前最新版本 ACPI Specification Version 6.3

2.UEFI Specification
当前最新版本 UEFI Specification Version 2.7 (Errata A)

3.UEFI Shell Specification
当前最新版本 UEFI Shell Specification Version 2.2

4.UEFI Platform Initialization Specification 如果你发现代码里面有看起来很正规的 Protocol 但是 UEFI Spec 中找不到,可以到这份Spec中看看
当前最新版本 UEFI Platform Initialization Specification Version 1.7

5.UEFI Platform Initialization Distribution Packaging Specification
当前最新版本 UEFI Platform Initialization Distribution Packaging Specification Version 1.1

Step to UEFI (169)BMP 放在 EFI 文件中(下)

这篇文章的上篇是2015年8月写的【参考1】,三年后终于填坑。时隔久远先介绍一下目标:很多时候我们的代码需要显示图片,但是我们不希望图片作为单独的文件放在外面,因此需要想办法“打包”到 EFI中。第一种方法就是用工具将图片转换为 C 头定义,然后在编译中直接内存调用,这就是上篇使用的方法。可以看到比较麻烦也不算正规。最近偶然看到了EFI_HII_PACKAGE_IMAGES 的定义,琢磨了一下可以通过这样的方法将图片打包成为 EFI文件中的一个 Section,然后再进行解析调用。需要注意的是之前版本的 UDK 中并不支持这种方法,本文实验都是在 UDK2018 中进行的【参考2】。

先介绍一下原理(网上无法找到EFI_HII_PACKAGE_IMAGES 的使用方法,下面的都是实验得出的,如果有问题欢迎指出。【参考3】提供了一个关于 HII 的介绍,建议阅读本文之前先浏览此文):

1. 在代码中设定一个 *.idx 文件,其中用下面这样的形式给出要打包的 Image
#image IMG_LOGO TestImage.bmp

2. 其中的IMG_LOGO 是一个编号,代码中并没有定义,但是在 Build 之后的目录中能找到, 这应该是对应工具在编译过程中自动生成的

#define IMG_LOGO 0x0001

3. *.c 代码中一定要有 IMAGE_TOKEN (IMG_LOGO) 这样的定义,即使放在注释中也无所谓,但是一定要有,否则无法生成 2 提到的定义,应该是有工具会做扫描

4. 有了 HII 的定义,在编译时会在生成的 EFI 中加入一个 名称为 HII的Section,和通常的 Windows PE 处理 Resource 文件的方法一样

5. 在 Build 目录的 OUTPUT 下面,有一个生成的 hithii.rc,可以用 VS 直接打开之,看到的内容和上面的是相同的

6. 使用 Hii Package List Protocol 可以取得 PackageListHeader

7. 取得了 PackageListHeader 之后整体格式如下

下面是手工分析的结果,涉及到的结构体可以在 UefiInternalFormRepresentation.h 中看到。

最终代码如下:
HIIImageTest.c

/** @file
  Logo DXE Driver, install Edkii Platform Logo protocol.

Copyright (c) 2016 - 2017, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/


#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/HiiDatabase.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/HiiImageEx.h>
#include <Protocol/PlatformLogo.h>
#include <Protocol/HiiPackageList.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PrintLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseLib.h>

#include <Library/UefiApplicationEntryPoint.h>

#define EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID \
  { \
    0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a } \
  }
  
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;

//DO NOT REMOVE IMAGE_TOKEN (IMG_LOGO)

/**
  Entrypoint of this module.

  This function is the entrypoint of this module. It installs the Edkii
  Platform Logo protocol.

  @param  ImageHandle       The firmware allocated handle for the EFI image.
  @param  SystemTable       A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
)
{
  EFI_STATUS                  Status;
  EFI_HII_PACKAGE_LIST_HEADER *PackageListHeader;
  EFI_HII_IMAGE_PACKAGE_HDR   *ImageHeader;
  EFI_HII_IMAGE_BLOCK         *ImageBlocktType;
  EFI_HII_IIBT_IMAGE_24BIT_BASE  *ImageData;
  EFI_GRAPHICS_OUTPUT_PROTOCOL   *GraphicsOutput;
  UINT16        i,j;
  UINT8*        RGB32;
  
        //Step1. Get Package List Header Address
        
        //
        // Retrieve HII package list from ImageHandle
        //
        Status = gBS->OpenProtocol (
                        ImageHandle,
                        &gEfiHiiPackageListProtocolGuid,
                        (VOID **) &PackageListHeader,
                        ImageHandle,
                        NULL,
                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                        );
                        
        if (EFI_ERROR (Status)) {
          Print(L"HII Image Package with logo not found in PE/COFF resource section\n");
          return Status;
        }
        
        Print(L"PackageList :\n   GUID=[%g] Length=[%X]\n",
                      PackageListHeader->PackageListGuid,
                      PackageListHeader->PackageLength);
     
        //Step2. Parser HII Image
        ImageHeader=(EFI_HII_IMAGE_PACKAGE_HDR*)(PackageListHeader+1);
        Print(L"Image Header:\n Length=[%d]  Type=[%d]\n",ImageHeader->Header.Length,ImageHeader->Header.Type);
        Print(L"ImageOffset %d  PalOffset %d\n",ImageHeader->ImageInfoOffset,ImageHeader->PaletteInfoOffset);
     
        ImageBlocktType=(EFI_HII_IMAGE_BLOCK *)(ImageHeader+1);
        Print(L"ImageBlockType %x\n",*ImageBlocktType);
     
        ImageData=(EFI_HII_IIBT_IMAGE_24BIT_BASE *)(ImageBlocktType+1);
        Print(L"ImageData:\n  Width=[%d]   Heigth=[%d]\n",ImageData->Width,ImageData->Height);

        Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
        if (EFI_ERROR(Status)) {
                Print(L"Loading Graphics_Output_Protocol error!\n");
                return EFI_SUCCESS;
        }

        //Step3. Get BMP Image
        RGB32 = (UINT8*)AllocatePool(ImageData->Width *  ImageData->Height *4);
        for (j=0;j<ImageData->Height;j++) {
                for (i=0;i<ImageData->Width;i++) {
                        RGB32[(j*ImageData->Width+i)*4]  =  ImageData->Bitmap[(j*ImageData->Width+i)].b;//Blue   
                        RGB32[(j*ImageData->Width+i)*4+1]=  ImageData->Bitmap[(j*ImageData->Width+i)].g; //Green 
                        RGB32[(j*ImageData->Width+i)*4+2]=  ImageData->Bitmap[(j*ImageData->Width+i)].r; //Red  
                        RGB32[(j*ImageData->Width+i)*4+3]=  0;
                }
        }   
        
        //Step4. Show the Image
        GraphicsOutput->Blt(
                        GraphicsOutput, 
                        (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) RGB32,
                        EfiBltBufferToVideo,
                        0, 0, 
                        200, 0, 
                        ImageData->Width, ImageData->Height, 0); 
        
        FreePool(RGB32);
        
  return Status;
}

HIIImageTest.idf
#image IMG_LOGO TestImage.bmp

#image IMG_LOGO TestImage.bmp

HIIImageTest.inf

## @file
#  The default logo bitmap picture shown on setup screen.
#
#  Copyright (c) 2016 - 2017, Intel Corporation. All rights reserved.<BR>
#
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = hit
  MODULE_UNI_FILE                = LogoDxe.uni
  FILE_GUID                      = F74D20EE-37E7-48FC-97F7-9B1047749C69
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain
  
#
#  This flag specifies whether HII resource section is generated into PE image.
#
  UEFI_HII_RESOURCE_SECTION      = TRUE

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64
#

[Sources]
  TestImage.bmp
  HIIImageTest.c
  HIIImageTest.idf

[Packages]
  MdeModulePkg/MdeModulePkg.dec
  MdePkg/MdePkg.dec
  StdLib/StdLib.dec   
  ShellPkg/ShellPkg.dec 
  
[LibraryClasses]
  DebugLib
  UefiApplicationEntryPoint
  PrintLib    
  UefiLib
  
[Protocols]
  gEfiHiiPackageListProtocolGuid     ## PRODUCES CONSUMES

运行结果(NT32 环境下运行结果,实体机上相同):

总结:从实验可以看到,需要处理的图片是作为PE 的 Resource 存在 EFI 中的,用这样的方法应该也会有机会将一些其他二进制文件打包到EFI 文件中。
完整的代码下载

参考:

  1. http://www.lab-z.com/bmpinefi/ Step to UEFI (59) —– BMP 放在EFI文件中(上)
  2. https://github.com/tianocore/edk2/commit/333ba578fef4dff8921051410c5b56f63e7eeadb 提到这个事情。为了支持这个功能需要修改编译工具,应该从 UDK2017 就已经支持:

BaseTools: support generating image package from BMP/JPEG/PNG files

BaseTools add support to generating image package from BMP/JPEG/PNG

files.

1) New file type *.idf Image definition file to describe HII image

resource. It is the ASCII text file, and includes one or more “#image

IMAGE_ID [TRANSPARENT] ImageFileName”.

2) New IMAGE_TOKEN macro is used to refer to IMAGE_ID.

3) New AutoGen header file $(MODULE_NAME)ImgDefs.h to include the

generated ImageId definition.

4) New $(MODULE_NAME)Idf.hpk or $(MODULE_NAME)Images are generated

as the output binary HII image package.

Cc: Liming Gao <liming.gao@intel.com>

Contributed-under: TianoCore Contribution Agreement 1.0

Signed-off-by: Yonghong Zhu <yonghong.zhu@intel.com>

Reviewed-by: Liming Gao <liming.gao@intel.com>

3. https://blog.csdn.net/jiangwei0512/article/details/80386945 BIOS/UEFI基础——UEFI用户交互界面使用说明之C代码实现(HII Package)

面包板的 328P Arduino

用面包板来玩 DIP 封装的 328P 是绝对没问题的。需要注意的有如下几点:

1.如果出现可以刷 BootLoader 但是无法正常工作,请检查晶振是否起振(最好使用示波器);

2.如果 5V 工作正常,但是 3.3V 供电后看起来不工作(比如无法控制 Pin13 上的LED,或者串口无输出)。那么请检查 VCC 是否和 AVCC 连接起来。我不知道为什么这两个Pin 不连接的时候,5V 供电能够正常工作但是 3.3V 供电后就不工作。

Arduino 制作 HDMI Audio 测试工具

最近有客户报告:当插入 HDMI 从外接的显示器显示时,有一定概率出现显示正常但是 HDMI Audio 失效的情况。这个问题的概率不高,但是对客户影响严重。为了复制现象,需要不断插拔HDMI来验证,经常是插拔50次之后抓到一次现象,随之而来的是再来50次,更可怕的是据说某些修改之后会出现插拔100次才会出现一次现象。这对于我这样只愿意动脑不愿动手的人来说是一个极大的考验。

这种情况下,必须考虑用机器来实现自动化的测试。思路是淘宝上买一个带有遥控器的HDMI切换器,通过 Arduino 模拟遥控的操作来实现切换到 HDMI 上然后进行检测。于是,选择了下面这货14元+8元的运费。这个切换器有三个输入口,可以对应三台HDMI输出设备;有一个输出口,可以连接一台显示器

接下来要解决的问题如下:
1. 如何进行遥控?实现遥控之后如何保证切换成功(红外线遥控容易受到光线的影响)。
2. 如何检测当前是Audio 是否正常。
经过研究,第一个问题可以使用下面这个模块来解决:
红外解码模块 编码模块 红外无线通信 接收发射串口通信模块

因为这是发射和接收一体的模块,刚好可以用来解码HDMI切换器发射器的数据,解码之后发射同样的内容就能实现遥控的操作。遥控板上有3个按键,分别对应HDMI 的三个端口。经过实验遥控板按键发送数据如下:
HDMI IN1: 0xF1 0x80 0x7F 0x02 0xA1
HDMI IN2: 0xA1 0xF1 0x80 0x7F 0x04
HDMI IN3: 0xA1 0xF1 0x80 0x7F 0x06
接下来还有一个问题,如何保证切换正常。比如,当前是 HDMI1 输出,我切花发送切换HDMI2命令之后需要确认切换是否成功(红外遥控受到环境影响较大)。经过研究,可以使用切换器上的LED来判断,每一个输入端口都有一个对应的LED用来表示当前选中的输入端口。只需要读取LED电平即可得知。此外,我们再使用额外的LED来标记当前的输入 HDMI Port。
最终,硬件连接如下:


完整代码:

const int LED1=14;
const int LED2=16;
const int LED3=10;

void setup()
{
  pinMode(LED1,OUTPUT);
  pinMode(LED2,OUTPUT);
  pinMode(LED3,OUTPUT);
  digitalWrite(LED1,LOW);
  digitalWrite(LED2,LOW);
  digitalWrite(LED3,LOW);

  pinMode(A1,INPUT_PULLUP);
  pinMode(A2,INPUT_PULLUP);
  pinMode(A3,INPUT_PULLUP);
    
    Serial.begin(9600);
    Serial1.begin(9600);    
    

}

void loop()
{
  char  c=' ';
  int   retry;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (c=='1') { //Command for Switch to HDMI1
            retry=3;
            while (retry>0) {
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x02);
              Serial1.write(0xA1);
              delay(200);
              if (analogRead(A1)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go1");
            }
        }
        if (c=='2') {//Command for Switch to HDMI2
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x04);
              delay(200);
              if (analogRead(A2)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go2");
            } 
        }
        if (c=='3') {//Command for Switch to HDMI3
            retry=3;
            while (retry>0) {
              Serial1.write(0xA1);
              Serial1.write(0xF1);
              Serial1.write(0x80);
              Serial1.write(0x7F);
              Serial1.write(0x06);
              delay(200);
              if (analogRead(A3)>20) {retry--;}
              else {retry=0;}
              Serial.println("Go3");
              }
        } //if (c=='3')
    }
    
   if (analogRead(A1)<20) {digitalWrite(LED1,HIGH);}
   else {digitalWrite(LED1,LOW);}
   if (analogRead(A2)<20) {digitalWrite(LED2,HIGH);}
   else {digitalWrite(LED2,LOW);}
   if (analogRead(A3)<20) {digitalWrite(LED3,HIGH);}
   else {digitalWrite(LED3,LOW);}        
}

 

为了配合这个设备,我们需要编写上位机的代码, 上位机是运行在被测试机器上。主要的逻辑就是:发送切换命令切换到其他HDMI Port,之后再发送命令切换回来,然后检查音频是否正常,如果不正常就停止。如果正常就继续运行。软件界面如下:

代码:

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.Runtime.InteropServices;

namespace WindowsFormsApplication8
{
    public partial class Form1 : Form
    {   //当前切换到 HDMI[x]
        int current=1;
        //切换次数
        int loops = 0;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //如果当前测试还没有开始,那么开始测试,否则停止
            if (button1.Text == "Start") { button1.Text = "Stop"; }
            else { button1.Text = "Start"; timer1.Enabled = false;  return; }

            //检查选中的 HDMI 数量
            int counter = 0;
            for (int i= 0; i < 3; i++) {
                if (checkedListBox1.GetItemChecked(i)) { counter++; }
            }
            //如果只有1个HDMI 被选中,提示出错
            if (counter < 2) { System.Windows.Forms.MessageBox.Show("Please choose at least 2 HDMI"); return; }
            if (serialPort1.IsOpen) { serialPort1.Close(); }
            //打开选中的 COMx
            serialPort1.PortName = comboBox1.SelectedItem.ToString();
            serialPort1.Open();
            textBox1.AppendText("Open " + comboBox1.SelectedItem.ToString()+"\n");
            //1秒之内开始测试
            timer1.Interval = 1000;
            timer1.Enabled = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //设置可以选择的串口号
            comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames());
            comboBox1.SelectedIndex = 0;
            for (int i = 0; i < checkedListBox1.Items.Count; i++) {
                checkedListBox1.SetItemChecked(i,true);
            }

            //输出当前的串口设备名称
            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"))
                    {
                        //Console.WriteLine(queryObj["Caption"]);
                        textBox1.AppendText(queryObj["Caption"].ToString()+"\n");
                    }
            }
            searcher.Dispose();

        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (serialPort1.IsOpen) {
                textBox1.AppendText("close " + comboBox1.SelectedItem.ToString());
                serialPort1.Close();
            };
        }


        // native calls
        [DllImport("winmm.dll")]
        public static extern int waveOutGetNumDevs();

        private void timer1_Tick(object sender, EventArgs e)
        {
            //如果当前是 HDMI2
            if (current == 2) {
                //检查设备是否有 Audio
                int inum = waveOutGetNumDevs();
                if (inum == int.Parse(maskedTextBox1.Text))  //发现错误
                {
                    textBox1.AppendText("Audio failed");
                    timer1.Enabled = false;
                    return;
                }
            }
            //20秒后触发下一次切换
            if (timer1.Interval == 1000) { timer1.Interval = 20000; }
            //尝试下一个HDMI
            current = (current + 1) % 3+1;
            //检查下一个是否被选中
            while (checkedListBox1.GetItemChecked(current-1)==false) {
                current = (current + 1) % 3+1;
            }
            if (current == 2) { loops++; }
            //切换到下一个可用的HDMI设备
            textBox1.AppendText("Switch to " + current.ToString()+"\n");
            textBox1.AppendText("Loops " + loops.ToString() + "\n");
            serialPort1.Write(current.ToString());
        }

        private void label1_Click(object sender, EventArgs e)
        {

        }
    }


}

 

编译 Windows 下的 EDAX

EDAX 是一款很厉害的黑白棋引擎,项目地址是https://github.com/abulmo/edax-reversi
除了引擎,它提供了一个文本的简单界面,用户能够方便的进行测试。本文介绍一下如何实现使用 VS2015编译出一个能够工作的EXE文件。

1. 在https://github.com/abulmo/edax-reversi/releases 页面有如下下载链接。Edax-linux.7z 是 Linux 版本的可执行程序;edax-ms-windows 是 Windows版本的可行性程序;eval 是开局库,运行的时候必须放在可执行文件同一个目录中;最后是不同压缩格式的两个Source Code。

对我们来说,需要下载 Source Code.zip

2. 对于下载的代码,options.c 文件需要进行修改,其中定义了const char *(boolean[2]) = {“false”, “true”}; 这样的变量,变量名称 Boolean 不妥,我的解决方法是修改为const char *(booleanx[2]) = {“false”, “true”}; 然后将这个文件中的所有 boolean 都修改为 booleanx。

3. 编译命令(我没有搞清楚如何使用 nmake来编译这个项目)
cl /I”..\include” /O2 /GL /fp:fast /favor:INTEL64 /arch:AVX /D “NDEBUG” /D “inline=__inline” /D “__func__=__FUNCTION__” /D “USE_MSVC_X64” /D “POPCOUNT” /MT all.c ws2_32.lib /FewEdax-x64.exe /link /ltcg:pgi /machine:x64 /VERSION:4.4

4. 下载 eval.dat,解压到 data 目录下就可以运行测试了

一站式获得系统信息的工具 Intel SSU

很多时候,为了分析研究现象,我们需要收集一些信息,比如:当前Windows版本,BIOS 版本或者 ME 版本等等。 Intel SSU 就是为此设计的。这个工具介绍如下:

Intel® System Support Utility. Intel® System Support Utility (Intel® SSU) is a standalone utility that performs a detailed scan and report of your computer system information and devices. Intel SSU produces an output file that can be saved, viewed, and shared by the user.

可以在下面的链接下载到:

https://downloadcenter.intel.com/download/25293/Intel-System-Support-Utility-for-Windows-

这个软件无需安装,下载后执行即可:

点击 Scan 后可以得到下面的结果


可以切换到更详细的信息栏

有兴趣的朋友可以试试。

libpng warning: iCCP: known incorrect sRGB profile

今天我在编译一个 C# Console 的代码时候忽然出现了“libpng warning: iCCP: known incorrect sRGB profile”一堆的 Warning。因为前几天编译运行的时候还没有遇到过这样的事情。研究了很久。网上的资料都是说 libpng 是一个 Png 的解码库,但是我的代码没有Png 相关的任何内容。

最终,我写了一个只有一行 Console.WriteLine() 输出的程序,运行之后出现了同样的现象我才确定不是因为我的代码导致了问题。

搜索的时候看到一篇文章提到可能和输入法相关。于是我去控制面板中根据时间排序惊奇的发现 QQ拼音输入法 排在第一位,然后想起来今天手欠,看到输入法提示选择了升级。卸载之后再次运行,世界终于清静了。

很早之前遇到过这样的事情,那次是搜狗输入法导致我的 Delphi 无法调试编译好的EXE文件,更加让人觉得崩溃。半年之后才发现是搜狗导致的,于是果断卸载之。没想到今天又遇到这样的情况。

ps: 更让人吃惊的是,现在的 QQ 输入法就是搜狗做的“
2013年09月16日,腾讯4.48亿美元注资搜狗,宣布将搜搜和QQ输入法业务与搜狗现有业务进行合并。 ”看起来他们已经整合的差不多了。

Step to UEFI (168)Setup 中替换字符串

以前设置BIOS的门槛挺高,至少对于不熟悉英文的人进入满屏英文的Setup界面很容易变成丈二和尚———摸不着头脑。但是随着时代的进步,汉化Setup界面并不复杂,在 EDK2 上已经加入了多语言支持功能。但是,某些情况下,Setup中的选项不是来自BIOS Source Code 而是第三方的UEFI ROM,遇到这样的情况,经常会出现一片中文夹杂了一行英文的情况。为了更详细的描述这样的情况,用下面的例子来进行说明。在UDK2017中,有一个 RAM Disk 功能,这个功能在之前的系列文章中有介绍过。默认情况下,NT32Pkg 没有加载这个功能,但是编译过程中会生成 RamDiskDxe.efi。实验如下:


1. 先进入 Setup ,可以看到目前是没有相关选项的

2. 进入 Shell 之后,加载 RamDiskDxe.efi

3. 再进入 Setup,可以看到已经出现了 Ram Disk 的选项

这里,实验的目标就是在不修改 RamDiskDxe.efi 的情况下,当语言选择为法语时,修改 Ram Disk 的名称。
首先研究一下 Setup 的选项,经过研究发现在\MdeModulePkg\Library\UefiHiiLib\HiiString.c中有一个HiiGetString的函数。从注释可以看出,这个函数负责返回 Hii Package中的字符串,因此,我们在其中添加代码,当发现语言设置为法语时,并且取得 Ram Disk 相关的String后,用我们定义的 String 替换掉正常的返回值从而实现“翻译”的动作。

/**
  Retrieves a string from a string package in a specific language.  If the language
  is not specified, then a string from a string package in the current platform 
  language is retrieved.  If the string can not be retrieved using the specified 
  language or the current platform language, then the string is retrieved from 
  the string package in the first language the string package supports.  The 
  returned string is allocated using AllocatePool().  The caller is responsible 
  for freeing the allocated buffer using FreePool().
  
  If HiiHandle is NULL, then ASSERT().
  If StringId is 0, then ASSET.

  @param[in]  HiiHandle  A handle that was previously registered in the HII Database.
  @param[in]  StringId   The identifier of the string to retrieved from the string 
                         package associated with HiiHandle.
  @param[in]  Language   The language of the string to retrieve.  If this parameter 
                         is NULL, then the current platform language is used.  The 
                         format of Language must follow the language format assumed 
                         the HII Database.

  @retval NULL   The string specified by StringId is not present in the string package.
  @retval Other  The string was returned.

**/
EFI_STRING
EFIAPI
HiiGetString (
  IN EFI_HII_HANDLE  HiiHandle,
  IN EFI_STRING_ID   StringId,
  IN CONST CHAR8     *Language  OPTIONAL
  )

最终代码如下:

  //
  // Return the Null-terminated Unicode string
  //
  //LABZ_Start
  if (String!=NULL) {
          DEBUG((DEBUG_INFO, "Get [%s]\n",String));
          if (StrCmp(String,L"Choisir la Langue")==0) { //这里判断一下语言是否已经选择为法语
                  DEBUG((DEBUG_INFO, "Let's speek French\n"));
                  FrenchX=TRUE; //做一个标记
                }
          //只有在切换为法语以及发现需要替换的字符串时再下手
          if ((FrenchX)&&(StrCmp(String,L"RAM Disk Configuration")==0)) {
                  DEBUG((DEBUG_INFO, "Replace item string\n"));
                  //"RAM Disk Configuration"
                  //"Jia zhuang fayu       "
                  StrCpy(String,L"Jia zhuang fayu       ");
                }      
          }
  //LABZ_End

需要特别注意的是:返回值是放在 String 中的,它是由HiiGetString()函数进行空间分配,然后由调用者负责分配。因此,我们需要在 String 中修改,而不能简单的用另外一个变量来赋值给 String,否则在运行时会出现不可预料的错误。

重新实验,加载 RamDiskDxe.efi ,再选择法语后运行结果:

在 Log 中我们可以看到下面的 Debug Message:
1.发现语言选项切换为法语

2.发现选项,替换之。

上面的实验都是在 UDK2017 NT32中进行的。完整的代码和 RamDiskDxe.efi 可以在这里下载:

额外说两句,法国是非常有名的老牌资本主义国家,譬如每个人耳熟能详的名言“知识就是力量,法国就是培根”。

不过从目前的新闻来看,这个国家正在一步步走向衰落。
纵观历史,我们所处的时代正是国运上升的时期,也许是我们的幸运吧。希望通过我们的努力能让我们的后辈有着更多的选择。

Teensy 打造USB纯数字麦克风

很多人喜欢在文章中引用名人名言来证明自己的观点,但是大多数名人名言都是有前提条件的,甚至前后段意义完全相反。比方说:打算宽恕别人的时候可以说“以德报怨”是中华民族的传统美德;打算复仇的时候就可以完整的引用“以德报怨,何以报德”。又比方说:在作文中我们用了无数次的“天才就是1%的灵感加上99%的汗水”;但是爱迪生要表达的是“天才就是1%的灵感加上99%的汗水,但那1%的灵感是最重要的,甚至比那99%的汗水都要重要”。所以,在我看来,这样的名人名言除了凑凑字数别无大用。与之相反,理科生的世界中,各种定理和名言充满了简洁和力量。比如:奈奎斯特教导我们说“要从抽样信号中无失真地恢复原信号,抽样频率应大于2倍信号最高频率。 抽样频率小于2倍频谱最高频率时,信号的频谱有混叠。 抽样频率大于2倍频谱最高频率时,信号的频谱无混叠。”

这句话可以指导我们对音频进行采样,语音信号频率范围是:300Hz~3.4kHz,因此,电话系统的 8000Hz 采样率对于传递语音信息已经足够了。

要想让电脑处理音频,先要用声能到电能的元件进行转换,再经过放大之后达到一定幅度才能进行采样(否则只有100mv不适合采样),可以看到在这个过程中会引入需要经过很多步骤而每一个步骤都会引入不确定性,因此,很长一段时间以内,音频处理都算得上是玄学。

最近我接触到了数字麦克风(MEMS micphone),和传统的相比数字麦克风输出的是数字信号,而不是通常的模拟信号。产品的基本形态有方形的,也有圆形的,形状与通常的驻极体传声器类似。基本结构为:一个换能器,用于产生代表声信号的模拟信号(通常由驻极体振膜及其背电极来完成的);和大于1的单比特∑-Δ调制器模数变换器,按照过抽样速率并以∑-Δ调制比特流的形式从模拟信号中产生数据输出信号。

数字麦克风的最大优点是抗干扰能力强,无需像传统传声器那样内置高频滤波电容、滤波器电路。数字麦克风因其固有的特点,不会受到那些来自电脑、网络、射频际磁场信号源的干扰、影响,因此在接入的时候,无需采用屏蔽线,可以有效地利用相关产品的有限空间。【参考1】

这次使用的是SPH0645LM4H (SPH0645) 数字麦克风模块

特别注意,有孔的一面是正面,需要面对音源。

硬件连接方式

Teensy 3.2     SPH0645LM4H

        GND             SEL

        D23              LRCL

         D13             DOUT

         D9                     BCLK

         GND                     GND

   3.3V               3V 

       

https://www.pjrc.com/teensy/gui/index.html?info=AudioInputI2S# 可以直接使用图形方式设定连接

可以用这样的方式直接生成需要的文件头部。最终的源代码:

#include &lt;Audio.h>

#include &lt;Wire.h>

#include &lt;SPI.h>

#include &lt;SD.h>

#include &lt;SerialFlash.h>

 

// GUItool: begin automatically generated code

AudioInputI2S            i2s1;           //xy=223,84

AudioOutputUSB           usb1;           //xy=435,172

AudioConnection          patchCord1(i2s1, 0, usb1, 0);

// GUItool: end automatically generated code

 

void setup() {

  // put your setup code here, to run once:

   AudioMemory(80);

}

 

void loop() {

}

除了上面提到的数字麦克风的优点之外,它还有如下缺点:

1.     对于处理器有要求。比如 Teensy 3.X 能够支持这款麦克风,最主要是因为它上面有对应的处理单元。而像 Arduino Uno,并没有这样的硬件基础,因此不能直接使用;

2.     价格昂贵。本次试验用到的模块价格为 48元。单纯的数字麦克风数字麦克风模块也要十几块;

另外,现在绝大多数的笔记本都采用这种麦克风作为内置麦克风。相信未来,这种麦克风会有更大的用途。

参考:

1. https://baike.baidu.com/item/%E6%95%B0%E5%AD%97%E9%BA%A6%E5%85%8B%E9%A3%8E/6847775

使用VirtualBox 进行UEFI 网络实验

最近研究了一下 UEFI 下的网络编程。需要解决的首要问题是:如何进行相关实验。对于我们来说最便捷的莫过于使用虚拟机,《UEFI 原理与编程》推荐的是使用 NT32 环境加SnpNt32Io 来完成模拟。但是我这边实验一直不成功,可能是Winpcap 安装不正确导致的。最终经过研究比较,确定了使用 VirtualBox作为实验对象。

第一个目标是要讲实验用到的驱动传入到虚拟机中。我们创建虚拟硬盘然后将需要内容 COPY到其中。

1. 打开 Disk Management,使用 Create VHD 功能

2. 创建一个 500MB,Fixed Size (可能是出于兼容性原因,这里建议用 Fixed Size)

3. 创建之后需要分区和格式化,Windows只能格式化成 NTFS 格式,这里需要使用 DiskGenius来完成。直接创建一个500MB大小的 ESP分区。然后文件系统需要为 FAT32。保存分区后格式化。

4. 上述操作结束之后,可以在 Windows 中对刚出现的盘符进行操作,COPY进需要的文件。比如:这里放入 Intel 网卡驱动和 UDK2018的WebServer 的UEFI Applicaiton

5. 再到 Disk Management 中 Detach VHD

至此,已经完成虚拟机硬盘的创建,并且放入了必须的文件。下面就是在 VirtualBox中验证的过程。

6. 在 VirtualBox 中新建一个虚拟机,一定要 Enable EFI ,此外,要将刚才制作的 VHD 文件挂上去。

上面的设置完成之后就可以进行网络方面的测试了。有几个基本的命令:
1. ifconfig –l eth0 //查看第一个网卡的配置信息
2. ifconfig –s eth0 DHCP //设置使用 DHCP 取得IP 地址,因为使用 Bridge 的网卡设定,新创建好的虚拟机和HOST主机一样是“暴露”在网络上的,我的 HOST 主机是通过 DHCP 获得IP的,因此虚拟机同样可以从 DHCP 上获得IP

上述完成之后,运行 WebServer.efi ,运行之后这个EFI Application会监听网络 Port 80,等待 HTTP 请求。在 Host 机器中,使用 Chrome 访问虚拟机的IP 80端口,结果如下:

就是说访问到了虚拟机提供的 WebServer 。
额外的话:
1. 除了 VirtualBox , QEMU 也是一个很好的选择。同时 QEMU 能够运行编译出来的BIOS Image 相对来说更胜一筹。但是我在设置网络的时候,QEMU 一直没能实现 Bridge ,只有 NAT,可能是因为我的HOST 是 Windows 导致的。只有NAT这对于使用来说显示颇多限制,因此,最终选择 VirtualBox 来实现模拟环境。
2. DiskGenius注册版可以直接编辑VHD 文件,挂载之后可以直接对其写入文件。普通免费版没有这个功能。
3. VHD 还可以使用ImDisk 直接挂载到 Windows 上然后写入数据。官方网站 http://www.ltr-data.se/opencode.html/
4. 实验发现 VirtualBox 的UEFI BIOS无法识别 IDE 的光驱,但是 SATA 的可以。用制作 ISO 的方法直接传递EFI Application 也是一个好方法。

20210531 补充

1.虚拟机中网络设置如下

2.实验用到的编译好的 WebServer.efi 和网卡驱动下载