Step to UEFI (271)资源打包再研究

在之前的文章中介绍过直接将 BMP文件存放在EFI 文件中 【参考1】,本文继续研究如何将其他格式的文件放在EFI 中的方法。

首先,我搜索了一下,没有发现直接将其他格式按照类似的方法放在EFI文件中的方法(从Win32编程的角度来说,这个是以Resource/资源文件的方式存放)。其次,如果将文件直接修改后缀为BMP,在编译过程中,工具会对资源文件进行检查,如果不是BMP将会报错。对于这种,可以将二进制文件通过加文件头的方式伪装成一个BMP文件。但是这样做估计会比较麻烦。

在阅读代码的过程中,处理BMP文件的代码位于 \BaseTools\Source\Python\AutoGen\GenC.py 文件中,可以看到支持三种图形格式,分别是PNG、JPG和BMP。对于后两者,都有针对这种类型的文件分析动作。

if File.Ext.upper() == '.PNG':
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.JPG':
        ImageType, = struct.unpack('4s', Buffer[6:10])
        if ImageType != b'JFIF':
            EdkLogger.error("build", FILE_TYPE_MISMATCH, "The file %s is not a standard JPG file." % File.Path)
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_JPEG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.BMP':
        TempBuffer, TempPalette = BmpImageDecoder(File, Buffer, PaletteIndex, FileObj.TransParent)
        if len(TempPalette) > 1:
            PaletteIndex += 1
            NewPalette = pack('H', len(TempPalette))
            NewPalette += TempPalette
            PaletteBuffer += NewPalette
            PaletteStr = WriteLine(PaletteStr, '// %s: %s: %s' % (DecToHexStr(PaletteIndex - 1, 4), ID, DecToHexStr(PaletteIndex - 1, 4)))
            TempPaletteList = AscToHexList(NewPalette)
            PaletteStr = WriteLine(PaletteStr, CreateArrayItem(TempPaletteList, 16) + '\n')
    ImageBuffer += TempBuffer
    BufferStr = WriteLine(BufferStr, '// %s: %s: %s' % (DecToHexStr(Index, 4), ID, DecToHexStr(Index, 4)))
    TempBufferList = AscToHexList(TempBuffer)
    BufferStr = WriteLine(BufferStr, CreateArrayItem(TempBufferList, 16) + '\n')
 
    StringH.Append(Line)
    Index += 1

于是,首先将【参考1】中的BMP文件后缀修改为 PNG ,然后重新编译。我们知道BMP文件开头的数值是 42 4D 76 6B,使用工具直接查看:

打包后的分拣分析,注意ASCII 的 BM 是 BMP的头

结合之前的知识,结合前面 PNG 打包的如下代码:

TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)
                                    TempBuffer += pack('I', len(Buffer))
                                    TempBuffer += Buffer

猜测格式应该是:

EFI_HII_PACKAGE_LIST_HEADER
EFI_HII_IMAGE_PACKAGE_HDR
Type(EFI_HII_IIBT_IMAGE_PNG==0x19)
Length(len(Buffer)) //这里也就是我们放入的 BMP 的大小

针对上面的内容,编写如下代码进行验证:

/** @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>
#include  <Library/ShellLib.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;
  UINT8                       *ImageData;
  UINTN                       FileSize;
  EFI_FILE_HANDLE             FileHandle;
   
        //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 :\nGUID=[%g] Length=[%X]\n",
                      PackageListHeader->PackageListGuid,
                      PackageListHeader->PackageLength);
 
        //Step2. Parser HII Image
        ImageHeader=(EFI_HII_IMAGE_PACKAGE_HDR*)(PackageListHeader+1);
        ImageData=(UINT8 *)(ImageHeader+1);
         
        FileSize=ImageData[1]+(ImageData[2]<<8)+(ImageData[3]<<16)+(ImageData[4]<<24);
        Print(L"Type: 0x%x  Size:%d\n",ImageData[0],FileSize);
 
        //Create a new file
        Status = ShellOpenFileByName(L"dump.bin", 
                                        (SHELL_FILE_HANDLE *)&FileHandle,
                                        EFI_FILE_MODE_READ |
                                        EFI_FILE_MODE_WRITE|
                                        EFI_FILE_MODE_CREATE, 
                                    0);  
        if(Status != RETURN_SUCCESS) {
                Print(L"CreatFile failed [%r]!\n",Status);
                return EFI_SUCCESS;
            }   
        Status = ShellWriteFile(FileHandle,
                        &FileSize,
                        &ImageData[5]);
   
        //Close the source file
        ShellCloseFile(&FileHandle);        
         
        Print(L"Dump.bin is generated!\n");
         
  return Status;
}

运行结果:

打包之后运行EFI 就释放资源文件

使用 Beyond Compare 可以看到dump.bin 和TestImage.png内容完全相同。

本文提到的完整文件:

HIIImageTest2下载

参考:

1. https://www.lab-z.com/stu169bmp/  BMP 放在 EFI 文件中(下)

Step to UEFI (270)EDK2: Win Host卡死的问题的解决方法

古语有云:“内事不决问百度,外事不决问谷歌”。对于我来说 Windows 下面有搞不懂的问题就直接问Windows专家天杀了。

最近偶然用到EDK2自带的模拟环境,偶尔会遇到运行WinHost.exe之后卡死,键盘无法输入的问题。之前也有热心的朋友提到过,这是因为输入法导致的。果真,在运行WinHost.exe 之前关闭输入法即可解决问题。

这两个都要关闭

很明显问题是WinHost  Windows下输入法有冲突,于是请教天杀,经过一夜的分析天杀很快给出了建议:

产生问题的原因是在代码在处理WM_IME_SETCONTEXT时出问题了,自绘制窗口需要响应输入法的这个消息。在模拟器中,用户没有输入中文的需求,因此直接禁用输入法是最简单的解决方法。

代码修改的方法是在\EmulatorPkg\Win\Host\WinGopScreen.c 文件中首先使用LoadLibraryEx()函数加载ImmDisableIME函数,然后调用之即可。

typedef WINBASEAPI BOOL  (WINAPI *ImmDisableIMEProc) (DWORD unnamedParam1);
typedef WINBASEAPI HIMC  (WINAPI *ImmAssociateContextProc) (HWND unnamedParam1, HIMC unnamedParam2);
 
/**
  This thread simulates the end of WinMain () application. Each Window needs
  to process its events. The messages are dispatched to
  WinNtGopThreadWindowProc ().
  Be very careful since WinNtGopThreadWinMain () and WinNtGopThreadWindowProc ()
  are running in a separate thread. We have to do this to process the events.
 
  @param  lpParameter            Handle of window to manage.
 
  @return if a WM_QUIT message is returned exit.
 
**/
DWORD
WINAPI
WinNtGopThreadWinMain (
  LPVOID  lpParameter
  )
{
  MSG                    Message;
  GRAPHICS_PRIVATE_DATA  *Private;
  RECT                   Rect;
   
//LABZ_Debug_Start
  HMODULE               Module;
  ImmDisableIMEProc     ImmDisable;
   
  Module = LoadLibraryEx (L"imm32.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
  ImmDisable = (ImmDisableIMEProc)GetProcAddress(Module, "ImmDisableIME");
 
  if (ImmDisable)
      ImmDisable(GetCurrentThreadId ());
//LABZ_Debug_End
 
  Private = (GRAPHICS_PRIVATE_DATA *)lpParameter;
  ASSERT (NULL != Private);

经过测试,上述代码运行之后不会有卡死的问题。有兴趣的朋友可以自行实验。

Step to UEFI (269)PRINT 前缀的研究

众所周知,在编写 UEFI Shell Application的时候,使用 PRINT(L”My String”) 可以非常容易的在屏幕上输出字符串。偶然机会看到了关于前缀“L”的介绍【参考1】。它能够决定后面的字符串的编码方式。简单介绍如下:

  • Ordinary character literals of type char, for example 'a'
  • UTF-8 character literals of type char (char8_t in C++20), for example u8'a'
  • Wide-character literals of type wchar_t, for example L'a'
  • UTF-16 character literals of type char16_t, for example u'a'
  • UTF-32 character literals of type char32_t, for example U'a'

编写一个简单的代码进行测试,使用上述四种前缀来定义字符串,然后在代码中输出字符串对应的内存数值:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
 
CHAR8  *s1="www.lab-z.com";
CHAR16 *s2=L"www.lab-z.com";
CHAR16 *s3=u"www.lab-z.com";
CHAR16 *s4=(CHAR16 *)U"www.lab-z.com";
     
UINTN OUTPUTCHAR(CHAR8 *p,UINTN Length) {
    UINTN i=0;
    for (;i<Length;i++) {
        Print(L"%02X  ",p[i]);
    }
    return 0;
}
 
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  Print(L"S1:[");OUTPUTCHAR((CHAR8*)s1,14);Print(L"]\n");
  Print(L"S2:[");OUTPUTCHAR((CHAR8*)s2,14*2);Print(L"]\n");
  Print(L"S3:[");OUTPUTCHAR((CHAR8*)s3,14*2);Print(L"]\n");
  Print(L"S4:[");OUTPUTCHAR((CHAR8*)s4,14*4);Print(L"]\n");
 
  return(0);
}

可以看出:

  1. 不加前缀,直接定义为 ASCII对应的数值;
  2. L 前缀,会使用 UTF-16 编码;
  3. u(小写)前缀,和L 一样,都是 UTF-16 编码;
  4. U(大写)前缀,将会使用UTF-32编码。

我们经常在GCC 编写的 UEFI 代码中看到使用u的定义方式,看起来这个应该是C语言标准的写法,具体可以在【参考2】和【参考3】看到。

参考:

  1. https://learn.microsoft.com/en-us/cpp/cpp/string-and-character-literals-cpp?view=msvc-170
  2. https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=msvc-170
  3. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1041r4.html

Step to UEFI (267)OVMF的 ACPI Table 的来源

之前介绍过在 Shell 下面查看 ACPI Table 的工具,例如【参考1】提到的:ACPIView。

接下来的问题就是:上面的这个 DSDT 是从哪里来的?

首先,在代码中找了一圈,竟然一无所获;接下来在Debug Log中查找,发现如下信息,其中的一个 Table 长度是 0x1772(6002)非常接近我们在 Shell 下看到的 6009。

ProcessCmdAllocate: File="etc/acpi/tables" Alignment=0x40 Zone=1 Size=0x20000 Address=0x7BDE000
ProcessCmdAddChecksum: File="etc/acpi/tables" ResultOffset=0x49 Start=0x40 Length=0x1772
ProcessCmdAddPointer: PointerFile="etc/acpi/tables" PointeeFile="etc/acpi/tables" PointerOffset=0x17D6 PointerSize=4
ProcessCmdAddPointer: PointerFile="etc/acpi/tables" PointeeFile="etc/acpi/tables" PointerOffset=0x17DA PointerSize=4
ProcessCmdAddChecksum: File="etc/acpi/tables" ResultOffset=0x17BB Start=0x17B2 Length=0x74
ProcessCmdAddChecksum: File="etc/acpi/tables" ResultOffset=0x182F Start=0x1826 Length=0x78
……………….
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDE000 (remaining: 0x20000): found "FACS" size 0x40
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDE040 (remaining: 0x1FFC0): found "DSDT" size 0x1772
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF7B2 (remaining: 0x1E84E): found "FACP" size 0x74
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF826 (remaining: 0x1E7DA): found "APIC" size 0x78
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF89E (remaining: 0x1E762): found "HPET" size 0x38
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF8D6 (remaining: 0x1E72A): found "WAET" size 0x28

这一段 Log 对应的代码在\OvmfPkg\AcpiPlatformDxe\QemuFwCfgAcpi.c 中。

为了验证猜想,在对应代码中加入调试信息,输出读取到QEMU 的 ACPI DSDT,具体在OvmfPkg\AcpiPlatformDxe\QemuFwCfgAcpi.c,修改如下:

Blob2Remaining -= (UINTN) PointerValue;
DEBUG ((DEBUG_INFO, "%a: checking for ACPI header in \"%a\" at 0x%Lx "
  "(remaining: 0x%Lx): ", __FUNCTION__, AddPointer->PointeeFile,
  PointerValue, (UINT64)Blob2Remaining));
 
TableSize = 0;
 
//
// To make our job simple, the FACS has a custom header. Sigh.
//
if (sizeof *Facs <= Blob2Remaining) {
  Facs = (EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE *)(UINTN)PointerValue;
    //LABZDebug_Start
    if ((Facs->Signature==EFI_ACPI_1_0_DIFFERENTIATED_SYSTEM_DESCRIPTION_TABLE_SIGNATURE)) {
                DEBUG ((DEBUG_INFO,"DSDt found\n"));
                p = (CHAR8*)(Facs);
                //p=p+sizeof(EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE);
                for (i=0;i<32;i++) {
                      DEBUG ((DEBUG_INFO,"%x ",p[i]&0xFF));
                }
        }
    // LABZDebug_End
  if (Facs->Length >= sizeof *Facs &&
      Facs->Length <= Blob2Remaining &&
      Facs->Signature ==
              EFI_ACPI_1_0_FIRMWARE_ACPI_CONTROL_STRUCTURE_SIGNATURE) {
    DEBUG ((DEBUG_INFO, "found \"%-4.4a\" size 0x%x\n",
      (CONST CHAR8 *)&Facs->Signature, Facs->Length));
    TableSize = Facs->Length;
 
  }
}

输出结果如下:

Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDE000 (remaining: 0x20000): found "FACS" size 0x40
Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDE040 (remaining: 0x1FFC0): DSDt found
44 53 44 54 72 17 0 0 1 9E 42 4F 43 48 53 20 42 58 50 43 20 20 20 20 1 0 0 0 42 58 50 43 Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF7B2 (remaining: 0x1E84E): Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF826 (remaining: 0x1E7DA): Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF89E (remaining: 0x1E762): Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF8D6 (remaining: 0x1E72A): Process2ndPassCmdAddPointer: checking for ACPI header in "etc/acpi/tables" at 0x7BDF8FE (remaining: 0x1E702): InstallQemuFwCfgTables: installed 6 tables
PcRtc: Write 0x20 to CMOS location 0x32

可以看到这个结果和ACPIViewApp Dump 出来的 DSDT 结果是相同的:

就是说:QEMU的 ACPI 来自虚拟机内部。

参考:

1. https://www.lab-z.com/acpiview/

推荐一个USB 2.0维修接头

有些时候,USB 2.0 设备由于长期使用,接头会出现接触问题,这种情况下直接更换USB接头是最好的选择。这里推荐下面这一款替换接头:

【淘宝】https://m.tb.cn/h.UJF7fNp?tk=E626djU8vYA CZ3457 「USB公头母头三件套 焊线式 USB插座 卡盒式A母 A公 A型带塑料外壳」 点击链接直接打开 或者 淘宝搜索直接打开

USB 公头替换装

推荐这款维修接头最主要的原因是上面自带了固定孔位,安装之后会非常结实,不会出现因为拉扯导致主体和外壳分离的问题(我买的另外一家的有这个问题)。

焊接之后使用UV电子胶固定一下就齐活儿了,很好用。

焊接之后的样子

通常USB线序和颜色对应关系如下:

红色 5V
白色 D-
绿色 D+
黑色 GND

Step to UEFI (268)EFI 资源文件再研究

在之前的文章中介绍过将 BMP文件直接存放在EFI 文件中的方法 【参考1】,本文继续研究如何将其他格式的文件放在EFI 中的方法。

首先,我搜索了一下,没有发现直接将其他格式按照类似的方法放在EFI文件中的方法(从Win32编程的角度来说,这个是以Resource/资源文件的方式存放)。其次,如果将文件直接修改后缀为BMP,在编译过程中,工具会对资源文件进行检查,如果不是BMP将会报错。对于这种问题,可以将二进制文件通过加文件头的方式伪装成一个BMP文件。但是这样做估计会比较麻烦。

在阅读代码的过程中,处理BMP文件的代码位于 \BaseTools\Source\Python\AutoGen\GenC.py 文件中,可以看到支持三种图形格式,分别是PNG、JPG和BMP。对于后两者,都有针对这种类型的文件分析动作。

if File.Ext.upper() == '.PNG':
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.JPG':
        ImageType, = struct.unpack('4s', Buffer[6:10])
        if ImageType != b'JFIF':
            EdkLogger.error("build", FILE_TYPE_MISMATCH, "The file %s is not a standard JPG file." % File.Path)
        TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_JPEG)
        TempBuffer += pack('I', len(Buffer))
        TempBuffer += Buffer
    elif File.Ext.upper() == '.BMP':
        TempBuffer, TempPalette = BmpImageDecoder(File, Buffer, PaletteIndex, FileObj.TransParent)
        if len(TempPalette) > 1:
            PaletteIndex += 1
            NewPalette = pack('H', len(TempPalette))
            NewPalette += TempPalette
            PaletteBuffer += NewPalette
            PaletteStr = WriteLine(PaletteStr, '// %s: %s: %s' % (DecToHexStr(PaletteIndex - 1, 4), ID, DecToHexStr(PaletteIndex - 1, 4)))
            TempPaletteList = AscToHexList(NewPalette)
            PaletteStr = WriteLine(PaletteStr, CreateArrayItem(TempPaletteList, 16) + '\n')
    ImageBuffer += TempBuffer
    BufferStr = WriteLine(BufferStr, '// %s: %s: %s' % (DecToHexStr(Index, 4), ID, DecToHexStr(Index, 4)))
    TempBufferList = AscToHexList(TempBuffer)
    BufferStr = WriteLine(BufferStr, CreateArrayItem(TempBufferList, 16) + '\n')
 
    StringH.Append(Line)
    Index += 1

接下来的实验中,首先将【参考1】中的BMP文件后缀修改为 PNG ,然后重新编译。我们知道BMP文件开头的数值是 42 4D 76 6B,使用工具直接在生成EFI文件中搜索,可以看到如下数据 :

十六进制工具查看生成的EFI 文件

结合之前的知识,结合前面 PNG 打包的如下代码:

123TempBuffer = pack('B', EFI_HII_IIBT_IMAGE_PNG)                 TempBuffer += pack('I', len(Buffer))                 TempBuffer += Buffer

猜测存储格式为:

1234EFI_HII_PACKAGE_LIST_HEADEREFI_HII_IMAGE_PACKAGE_HDRType(EFI_HII_IIBT_IMAGE_PNG==0x19)Length(len(Buffer)) //这里也就是我们放入的 BMP 的大小

为了便于验证,编写一个测试程序,它会将自身的资源文件保存为一个文件,这样可以方便的比较原始文件和EFI携带的资源文件:

#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>
#include  <Library/ShellLib.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;
  UINT8                       *ImageData;
  UINTN                       FileSize;
  EFI_FILE_HANDLE             FileHandle;
   
        //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 :\nGUID=[%g] Length=[%X]\n",
                      PackageListHeader->PackageListGuid,
                      PackageListHeader->PackageLength);
 
        //Step2. Parser HII Image
        ImageHeader=(EFI_HII_IMAGE_PACKAGE_HDR*)(PackageListHeader+1);
        ImageData=(UINT8 *)(ImageHeader+1);
         
        FileSize=ImageData[1]+(ImageData[2]<<8)+(ImageData[3]<<16)+(ImageData[4]<<24);
        Print(L"Type: 0x%x  Size:%d\n",ImageData[0],FileSize);
 
        //Create a new file
        Status = ShellOpenFileByName(L"dump.bin", 
                                        (SHELL_FILE_HANDLE *)&FileHandle,
                                        EFI_FILE_MODE_READ |
                                        EFI_FILE_MODE_WRITE|
                                        EFI_FILE_MODE_CREATE, 
                                    0);  
        if(Status != RETURN_SUCCESS) {
                Print(L"CreatFile failed [%r]!\n",Status);
                return EFI_SUCCESS;
            }   
        Status = ShellWriteFile(FileHandle,
                        &FileSize,
                        &ImageData[5]);
   
        //Close the source file
        ShellCloseFile(&FileHandle);        
         
        Print(L"Dump.bin is generated!\n");
         
  return Status;
}

在WinHost.exe 的模拟环境种运行结果如下:

WinHost 下运行结果

使用 Beyond Compare 可以看到dump.bin 和TestImage.png内容完全相同。

结论:将要存储的二进制文件后缀修改为 PNG 之后可以直接存储。

完整代码下载:

HIIImageTest2下载

参考:

1. https://www.lab-z.com/stu169bmp/  BMP 放在 EFI 文件中(下)

CH567 SPI NOR 测试

最近编写了一个 CH567 SPI 读写测试的代码,测试的 SPI NOR 是 gd25q128e(特别注意:这款在Taobao上有假货,使用几次之后就无法读写,包括 READ ID 都无法响应)。这款 SPI 每个Page有256 Bytes ;每个 Sector有 4K Bytes; Block有 32K或者64K字节。

CH567 支持2个 SPI Host,这次测试的是 SPI0。

硬件连接:硬件连接:

引脚    
1 CS#PA12 3.3V8 VCC
2 MISOPA15 3.3V7 HOLD#/RESET#
3 WP#3.3V PA136 SCLK
4 VSSGND PA145 MOSI

对CH567的连接方法

一些常用的命令定义如下:

FunctionCommand NameByte 1Byte 2Byte 3Byte 4Byte 5
Void WriteEnable()Write Enable06H
Void WriteDisable()Write Disable04H
UINT8 ReadStatus1()Read Status Register-105H(S7-S0)(cont.)
UINT8 ReadStatus2()Read Status Register-235H(S15-S8)(cont.)
UINT8 ReadStatus3()Read Status Register-315H(S23-S16)(cont.)
Void WriteStatus1(UINT8)Write Status Register-101HS7-S0
Void WriteStatus2(UINT8)Write Status Register-231HS15-S8
Void WriteStatus3(UINT8)Write Status Register-311HS23-S16
Void ReadData(UINT32, UINT16, PUINT8)Read Data03HA23-A16A15-A8A7-A0(D7-D0)
Void PageProgram()Page Program02HA23-A16A15-A8A7-A0D7-D0
Void SectorErase()Sector Erase20HA23-A16A15-A8A7-A0
Void BlockErase32K()Block Erase (32K)52HA23-A16A15-A8A7-A0
Void BlockErase64K()Block Erase (64K)D8HA23-A16A15-A8A7-A0
Void ChipErase()Chip EraseC7H
UINT32 ReadID()Read Identification9FH(M7-M0)(JDID15-JDID8)(JDID7-
JDID0)
(cont.)

特别注意这颗SPI NOR 有三个8Bits的 Status寄存器。

测试如下三个操作:

1.读取 0 开始的 16字节内容;

2.擦除 0 开始的一个 Sector(4K);

3.在 0 开始写入 16字节内容。

/*
 ============================================================================
 Name        : main_SDIO_TF.c
 Author      : wch_cc
 Version     : V1.0.0
 Copyright   :
 Description : ²Ù×÷TF¿¨
 ============================================================================
 */
 
#include <stdio.h>
#include <string.h>
#include <nds32_isr.h>
#include <nds32_intrinsic.h>
#include "CH568SFR.H"
#include "MYdebug.H"
 
__attribute__ ((aligned(8))) UINT8  Sendbuff[512*2] ;       //8×Ö½Ú¶ÔÆë
__attribute__ ((aligned(8))) UINT8  Recvbuff[512*2] ;       //8×Ö½Ú¶ÔÆë
 
#define  GD25Q_CMD_WRITEENABLE   0x06
#define  GD25Q_CMD_WRITEDISABLE  0x04
#define  GD25Q_CMD_READSTATUS1   0x05
#define  GD25Q_CMD_READSTATUS2   0x35
#define  GD25Q_CMD_READSTATUS3   0x15
#define  GD25Q_CMD_WRITESTATUS1  0x01
#define  GD25Q_CMD_WRITESTATUS2  0x31
#define  GD25Q_CMD_WRITESTATUS3  0x11
#define  GD25Q_CMD_READDATA      0x03
#define  GD25Q_CMD_PAGEPROGRAM   0x02
#define  GD25Q_CMD_SECTORERASE   0x20
#define  GD25Q_CMD_BLOCKERASE32K 0x52
#define  GD25Q_CMD_BLOCKERASE64K 0xD8
#define  GD25Q_CMD_CHIPERASE     0xC7
#define  GD25Q_CMD_READID        0x9F
 
/********************************* 引脚定义 ************************************
*    PA12  <===========>  SCS0
*    PA13  <===========>  SCK0
*    PA14  <===========>  MOSI0
*    PA15  <===========>  MISO0
*******************************************************************************/
#define  SPI0_CS_LOW()        R32_PA_CLR |= 1<<12
#define  SPI0_CS_HIGH()       R32_PA_OUT |= 1<<12
 
/*******************************************************************************
* Function Name  : SPI_MASTER_INIT
* Description    : SPI0主机模式初始化
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void SPI_MASTER_INIT ( void )
{
        R8_SPI0_CTRL_MOD = RB_SPI_MOSI_OE|RB_SPI_SCK_OE;                              /* MOSI,SCK输出使能,主机模式,方式0 */
        R8_SPI0_CLOCK_DIV = 0x0a;                                                     /* 10分频,100/10=10M */
        R32_PA_DIR |= (1<<14 | 1<<13 | 1<<12);                                        /* MOSI(PA14),SCK0(PA13),SCS(PA12)为输出*/
        R32_PA_PU  |=  1<<12 ;
        R8_SPI0_CTRL_CFG &= ~RB_SPI_DMA_ENABLE;
}
 
/*******************************************************************************
* Function Name  : SPI0_Trans
* Description    : 发送一字节数据
* Input          : data -要发送的数据
* Output         : None
* Return         : None
*******************************************************************************/
void SPI0_Trans( UINT8 data )
{
 
//    R8_SPI0_CTRL_MOD &= ~RB_SPI_FIFO_DIR;
//    R8_SPI0_BUFFER = data;
//    while( !(R8_SPI0_INT_FLAG & RB_SPI_FREE) );
 
        R32_SPI0_FIFO = data;
        R16_SPI0_TOTAL_CNT = 0x01;
        while( R8_SPI0_FIFO_COUNT != 0 );                                           /* 等待数据发送完毕 */
}
 
/*******************************************************************************
* Function Name  : SPI0_Recv
* Description    : 接收一字节数据
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
UINT8 SPI0_Recv( void )
{
//    R8_SPI0_CTRL_MOD &= ~RB_SPI_FIFO_DIR;
//    R8_SPI0_BUFFER = 0xFF;                                //启动传输
//    while( !(R8_SPI0_INT_FLAG & RB_SPI_FREE) );
//    return ( R8_SPI0_BUFFER );
 
        UINT8 data;
        R32_SPI0_FIFO = 0xff;
        R16_SPI0_TOTAL_CNT = 0x01;
        while( R8_SPI0_FIFO_COUNT != 0 );                                           /* 等待数据回来 */
        data = R8_SPI0_BUFFER;
        return data;
}
 
/*******************************************************************************
* Function Name  : SPIFlash_ReadID
* Description    : SPI Flash读取芯片ID
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT32 SPIFlash_ReadID()
{
        UINT32  temp = 0;
 
        R32_PA_CLR |=  1<<12 ;
 
        SPI0_Trans(GD25Q_CMD_READID);    //读取ID命令
        temp = SPI0_Recv();
        temp = temp<<8;
        temp |= SPI0_Recv();
        temp = temp<<8;
        temp |= SPI0_Recv();
 
        R32_PA_OUT |=  1<<12 ;
 
        return temp;
}
 
/*******************************************************************************
* Function Name  : WriteEnable
* Description    : 写使能
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WriteEnable( void )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITEENABLE );   //发送写使能命令
        SPI0_CS_HIGH();
}
 
/*******************************************************************************
* Function Name  : WriteDisable
* Description    : 关闭写
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WriteDisable( void )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITEDISABLE );   //发送写禁止命令
        SPI0_CS_HIGH();
}
 
/*******************************************************************************
* Function Name  : ReadStatus1
* Description    : 读取状态寄存器1
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus1()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS1 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : ReadStatus2
* Description    : 读取状态寄存器2
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus2()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS2 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : ReadStatus3
* Description    : 读取状态寄存器3
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
UINT8 ReadStatus3()
{
        UINT8 Status;
 
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_READSTATUS3 ); //发送读状态寄存器的命令
        Status = SPI0_Recv();      //读取状态寄存器
        SPI0_CS_HIGH();
 
        return Status;
}
 
/*******************************************************************************
* Function Name  : WriteStatus1
* Description    : 写状态寄存器1
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus1( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS1 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : WriteStatus2
* Description    : 写状态寄存器2
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus2( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS2 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : WriteStatus3
* Description    : 写状态寄存器3
* Input          : None
* Output         : None
* Return         :
*******************************************************************************/
void WriteStatus3( UINT8 Status )
{
        SPI0_CS_LOW();
        SPI0_Trans( GD25Q_CMD_WRITESTATUS3 );     //发送写状态寄存器的命令
        SPI0_Trans( Status );                     //发状态寄存器的数值
        SPI0_CS_HIGH();
 
        return ;
}
 
/*******************************************************************************
* Function Name  : ReadExternalFlash_SPI
* Description    : 读数据,注意:地址低8位必须为0
*******************************************************************************/
void ReadData( UINT32 StarAddr, UINT16 Len, PUINT8 RcvBuffer )
{
        UINT16 i;
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_READDATA);                                         //读命令'
        SPI0_Trans(((StarAddr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((StarAddr & 0xFFFF) >> 8));
        SPI0_Trans(StarAddr & 0xFF);
        for (i=0; i<Len; i++)
        {
            RcvBuffer[i]=SPI0_Recv();
        }
        SPI0_CS_HIGH();
        RcvBuffer[0]=0xFF;
}
 
/*******************************************************************************
* Function Name  : WaitExternalFlashIfBusy
* Description    : 等待芯片空闲(在执行Byte-Program, Sector-Erase, Block-Erase, Chip-Erase操作后)
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void WaitExternalFlashIfBusy( void )
{
        while ((ReadStatus1()&0x01) == 0x01 )
        {
            printf("Busy\n");
                ;    //等待直到Flash空闲
        }
}
/*******************************************************************************
* Function Name  : PageProgram
* Description    : 对一个page 进行写入
*******************************************************************************/
void PageProgram( UINT32 StarAddr, UINT16 Len, PUINT8 RcvBuffer )
{
        int i;
 
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(0x02);                                         //读命令
        SPI0_Trans(((StarAddr & 0xFFFFFF) >> 16));                //发送3字节地址
        SPI0_Trans(((StarAddr & 0xFFFF) >> 8));
        SPI0_Trans(StarAddr & 0xFF);
        for (i=0; i<Len; i++)
        {
                SPI0_Trans(RcvBuffer[i]);
        }
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();                               //等待写入结束
}
 
/*******************************************************************************
* Function Name  : SectorErase
* Description    : 擦除4K Flash  擦除一个扇区
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void SectorErase( UINT32 Dst_Addr )
{
        WriteEnable();
        WaitExternalFlashIfBusy();
        printf("SectorErase Status1:0x%01x\n",ReadStatus1());
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_SECTORERASE);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : Block32KErase
* Description    : 擦除32K Flash
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void Block32KErase( UINT32 Dst_Addr )
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_BLOCKERASE32K);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : Block64KErase
* Description    : 擦除64K Flash
* Input          : Dst_Addr 0-1 ffff ffff ,清除任意地址所在的扇区
* Output         : None
* Return         : None
*******************************************************************************/
void Block64KErase( UINT32 Dst_Addr )
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_BLOCKERASE64K);                                      //扇区擦除命令
        SPI0_Trans(((Dst_Addr & 0xFFFFFF) >> 16));                         //发送3字节地址
        SPI0_Trans(((Dst_Addr & 0xFFFF) >> 8));
        SPI0_Trans(Dst_Addr & 0xFF);
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
/*******************************************************************************
* Function Name  : ChipErase
* Description    : 擦除整片
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void ChipErase()
{
        WriteEnable();
 
        SPI0_CS_LOW();
        SPI0_Trans(GD25Q_CMD_CHIPERASE);     //整片擦除
        SPI0_CS_HIGH();
 
        WaitExternalFlashIfBusy();
}
 
int main()
{
 
        Interrupt_init( 1<<INT_ID_SDC );     /* 系统总中断开启 */
        SysClkInit();           /* 初始化系统时钟 */
        mInitSTDIO_UR3();       /* 调试信息初始化 printf调用 */
        SPI_MASTER_INIT();
        mDelaymS(200);
        printf("START SPI FLASH\n");
 
        // 读取 ID
        printf("id:0x%04x\n",SPIFlash_ReadID() );
         
        // 度状态寄存器
        printf("Status1:0x%01x\n",ReadStatus1());
        printf("Status2:0x%01x\n",ReadStatus2());
        printf("Status3:0x%01x\n",ReadStatus3());
         
        // 在所有的写入操作之前都要设置起来 WEL
        WriteEnable();
        WaitExternalFlashIfBusy();
        WriteStatus1(0x02);
        WaitExternalFlashIfBusy();
 
        WriteEnable();
        WaitExternalFlashIfBusy();
        WriteStatus3(0x00);
        WaitExternalFlashIfBusy();
         
        // 输出 Status Register
        printf("Status1:0x%01x\n",ReadStatus1());
        printf("Status2:0x%01x\n",ReadStatus2());
        printf("Status3:0x%01x\n",ReadStatus3());
         
        // 使用 16字节缓冲进行测试
        UINT8 Buffer[16];
         
        // 给缓冲区赋初始值便于观察
        Buffer[0]=0x12; Buffer[1]=0x34; Buffer[2]=0x56; Buffer[3]=0x78;
        Buffer[4]=0x9A; Buffer[5]=0xBC; Buffer[6]=0xDE; Buffer[7]=0xF0;
         
        // 1. 读取测试
        // 读取 SPI 起始处  16字节
        printf("Read Test\n");
        ReadData(0,16,&Buffer[0]);
        UINT16 i;
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
         
        // 2. Sector(4K Bytes)擦除测试
        printf("Sector Erase\n");
        SectorErase(0);
        ReadData(0,16,Buffer);
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
         
        // 3. PagePrgram (最大 一次写入256Bytes)测试
        printf("Page Program\n");
        Buffer[0]=0x12; Buffer[1]=0x34; Buffer[2]=0x56; Buffer[3]=0x78;
        Buffer[4]=0x9A; Buffer[5]=0xBC; Buffer[6]=0xDE; Buffer[7]=0xF0;
        PageProgram(0,8,Buffer);
        for (i=0;i<16;i++) {
                printf("%X ",Buffer[i]);
        }
        printf("\n");
        return 0;
}

冷知识:更换SoC后要重刷Firmware

最让 BIOS 工程师头大的问题就是要回答BIOS范畴之外的问题,例如:Hardware issue, OS issue 以及BIOS 之外的 Firmware issue。今天介绍的一个就是典型的别人会找BIOS工程师,但是真不归BIOS 管的问题。

在研发和调试的时候,非常特殊情况下我们需要更换SoC进行测试。更换之后务必重刷Firmware。这是因为在上电自检的时候,SoC中初始化代码如果发现 SoC损坏,为了避免更严重的问题(例如,SoC损坏之后异常发热有可能导致火灾),Firmware 会记录这个问题,下次再开机的时候先于BIOS执行的Firmware如果看到对应的标记会自动断电,在外界看起来更像是“SoC不上电”。

因此,在这种情况下,单纯更换SoC之后问题可能仍然存在,在重新刷写Firmware之后问题就会完全消失。

碰到上述问题时,经常有人将损坏SoC主板上的Firmware通过烧录器 Dump 为文件,将这个文件烧写到其他工作正常的设备,然后惊奇的发现之前工作正常的设备烧写之后也出现了故障设备同样的问题。接下来,他们会提出“请BIOS工程师解释一下”……..

有了上面的知识,下次遇到这种情况就可以直接告知原因。如果对方仍然想了解根本原因,只能联系SoC厂家了。

FireBeelte 显示环境声音频谱

前一段入手了Fermion: 全向MEMS麦克风模块(SEN0487),这款模块非常简单能够直接输出模拟信号。简单之处在于无需外部元件进行放大,当没有检测到声音时,输出电压在1.5V左右浮动,在说话时通过ADC采样可以很容易看到声音的波形。

首先跑了一下WIKI 给出的示例文件,使用 Arduino 自带的串口绘图工具查看了一下波形:

测试视频可以在B站看到:

可以看到内部自带了滤波,敏感度也不错。

接下来,配合之前多次使用的 12864 OLED制作一个频谱显示装置。代码参考 https://github.com/s-marley/ESP32_FFT_VU 了。硬件部分除了供电的 VCC和GND, 只需要将模块输出连接到A0(IO36)即可。

#include &lt;arduinoFFT.h>
#include "DFRobot_OLED12864.h"
 
// 12864 OLED 配置
#define I2C_addr 0x3c
#define pin_SPI_cs D2
 
#define SAMPLES         1024          // Must be a power of 2
#define SAMPLING_FREQ   40000         // Hz, must be 40000 or less due to ADC conversion time. Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.
#define AMPLITUDE       1000          // Depending on your audio source level, you may need to alter this value. Can be used as a 'sensitivity' control.
#define AUDIO_IN_PIN    A0            // Signal in on this pin
 
#define NOISE           500           // Used as a crude noise filter, values below this are ignored
const uint8_t kMatrixWidth = 16;                          // Matrix width
const uint8_t kMatrixHeight = 16;                         // Matrix height
 
#define NUM_BANDS       16            // To change this, you will need to change the bunch of if statements describing the mapping from bins to bands
 
#define BAR_WIDTH      (kMatrixWidth  / (NUM_BANDS - 1))  // If width >= 8 light 1 LED width per bar, >= 16 light 2 LEDs width bar etc
#define TOP            (kMatrixHeight - 0)                // Don't allow the bars to go offscreen
 
DFRobot_OLED12864 OLED(I2C_addr, pin_SPI_cs);
 
// Sampling and FFT stuff
unsigned int sampling_period_us;
byte peak[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // The length of these arrays must be >= NUM_BANDS
int oldBarHeights[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int bandValues[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
double vReal[SAMPLES];
double vImag[SAMPLES];
unsigned long newTime;
 
arduinoFFT FFT = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
 
void setup() {
  Serial.begin(115200);
 
  OLED.init();
 // OLED.flipScreenVertically();
 
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQ));
}
 
unsigned long int Elsp1;
int h[16];
 
void loop() {
 
  // Reset bandValues[]
  for (int i = 0; i &lt; NUM_BANDS; i++) {
    bandValues[i] = 0;
  }
 
  // Sample the audio pin
  for (int i = 0; i &lt; SAMPLES; i++) {
    newTime = micros();
    vReal[i] = analogRead(AUDIO_IN_PIN); // A conversion takes about 9.7uS on an ESP32
    vImag[i] = 0;
    while ((micros() - newTime) &lt; sampling_period_us) {
      /* chill */
    }
  }
 
  // Compute FFT
  FFT.DCRemoval();
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(FFT_FORWARD);
  FFT.ComplexToMagnitude();
 
  // Analyse FFT results
  for (int i = 2; i &lt; (SAMPLES / 2); i++) {    // Don't use sample 0 and only first SAMPLES/2 are usable. Each array element represents a frequency bin and its value the amplitude.
    if (vReal[i] > NOISE) {                    // Add a crude noise filter
 
      //16 bands, 12kHz top band
      if (i &lt;= 2 )           bandValues[0]  += (int)vReal[i];
      if (i > 2   &amp;&amp; i &lt;= 3  ) bandValues[1]  += (int)vReal[i];
      if (i > 3   &amp;&amp; i &lt;= 5  ) bandValues[2]  += (int)vReal[i];
      if (i > 5   &amp;&amp; i &lt;= 7  ) bandValues[3]  += (int)vReal[i];
      if (i > 7   &amp;&amp; i &lt;= 9  ) bandValues[4]  += (int)vReal[i];
      if (i > 9   &amp;&amp; i &lt;= 13 ) bandValues[5]  += (int)vReal[i];
      if (i > 13  &amp;&amp; i &lt;= 18 ) bandValues[6]  += (int)vReal[i];
      if (i > 18  &amp;&amp; i &lt;= 25 ) bandValues[7]  += (int)vReal[i];
      if (i > 25  &amp;&amp; i &lt;= 36 ) bandValues[8]  += (int)vReal[i];
      if (i > 36  &amp;&amp; i &lt;= 50 ) bandValues[9]  += (int)vReal[i];
      if (i > 50  &amp;&amp; i &lt;= 69 ) bandValues[10] += (int)vReal[i];
      if (i > 69  &amp;&amp; i &lt;= 97 ) bandValues[11] += (int)vReal[i];
      if (i > 97  &amp;&amp; i &lt;= 135) bandValues[12] += (int)vReal[i];
      if (i > 135 &amp;&amp; i &lt;= 189) bandValues[13] += (int)vReal[i];
      if (i > 189 &amp;&amp; i &lt;= 264) bandValues[14] += (int)vReal[i];
      if (i > 264          ) bandValues[15] += (int)vReal[i];
    }
  }
 
  // Process the FFT data into bar heights
  for (byte band = 0; band &lt; NUM_BANDS; band++) {
 
    // Scale the bars for the display
    int barHeight = bandValues[band] / AMPLITUDE;
    if (barHeight > TOP) barHeight = TOP;
 
    // Small amount of averaging between frames
    barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2;
 
    // Move peak up
    if (barHeight > peak[band]) {
      peak[band] = min(TOP, barHeight);
    }
    h[band] = barHeight;
 
    // Save oldBarHeights for averaging later
    oldBarHeights[band] = barHeight;
 
  }
 
  if (millis() - Elsp1 > 60) {
    for (byte band = 0; band &lt; NUM_BANDS; band++)
      if (peak[band] > 0) peak[band] -= 1;
       
    Elsp1 = millis();
  }
  /*
       //直接输出 FFT 结果
       for (int i=0;i&lt;16;i++) {
         if (bandValues[i]&lt;16) {Serial.print(" ");}
         Serial.print(bandValues[i],HEX);
         Serial.print(" ");
       }
       Serial.println("");
  */
 
  // 擦除上一次
  OLED.clear();  
  for (int i = 0; i &lt; 16; i++) {
   OLED.fillRect(8 * (16-i), 0, 8, 4 * h[i]);
   OLED.fillRect(8 * (16-i), peak[i]*4, 8, 3);
  }
   
  for (int i = 0; i &lt; 16; i++) {  
    Serial.print(h[i], HEX);
    Serial.print("");
  }
  Serial.println("");
  /*
  for (int i = 0; i &lt; 16; i++) {  
    Serial.print(peak[i], HEX);
    Serial.print("");
  }
  Serial.println("");
  */
 
  // 显示当前
  OLED.display();
}

最终成品的测试视频:

参考:

  1. https://www.dfrobot.com.cn/goods-3220.html
  2. https://wiki.dfrobot.com.cn/_SKU_SEN0487_Fermion_MEMS_Microphone_Sensor

作者ziv2013发布于分类Funny

发表回复

BellWin UP520US USB Power Splitter 介绍

最近在研究一款USB控制的排插:BellWin 出品的带有USB接口的排插(USB Power Splitter)型号是:UP520US。它带有五个插孔,一个用于控制插孔供电的USB接口和一个供电开关。这款排插不是给家用设计的,上面的的插孔和插头都是专门为机房这种特别场合设计的,在家用环境中必须使用转接头才能工作。

电器特性如下【参考1】:

输入:220V,20A

输入频率:50/60Hz

功耗:5W

每一路最高可支持15A,五组最大支持输出20A

工作温度:0-25℃

接口:IEC60320 C13

认证:RoHS,IPC,CUS

首先,测试一下它自带的控制程序。

用户可以通过下面的按钮控制每一路的开关。

接下来,使用 USB Package Viewer 设备抓取USB 数据包。USB Package Viewer(简称UPV)是一款国产的USB数据逻辑分析仪,能够抓取从 Low Speed 到 High Speed的数据包。有兴趣的朋友可以在【参考2】看到之前我的开箱视频。这个仪器有一个比较大的优点是能够实时显示,这个在分析研究数据量不大的USB设备时非常有用。比如,上位机进行一个动作立刻能够在分析软件上看到数据,这样便于对应数据和操作。

从设备管理器上也可以看出,这个设备使用USB HID 协议。

按下试验软件上的开关两次之后,在UPV中可以看到抓到了2个数据包:

经过多次试验,总结数据如下:

操作抓到的数据,总长度64Bytes
Port1 设置为 OFF0B 00 00 00 00 00 00 5A……5A
Port2 设置为OFF0B 00 00 00 00 01 00 5A……5A
Port3 设置为OFF0B 00 00 00 00 02 00 5A……5A
Port4 设置为OFF0B 00 00 00 00 03 00 5A……5A
Port5 设置为OFF0B 00 00 00 00 04 00 5A……5A
Port1 设置为 ON0B 00 00 00 00 00 01 5A……5A
Port2 设置为ON0B 00 00 00 00 01 01 5A……5A
Port3 设置为ON0B 00 00 00 00 02 01 5A……5A
Port4 设置为ON0B 00 00 00 00 03 01 5A……5A
Port5 设置为ON0B 00 00 00 00 04 01 5A……5A

需要注意的是,上面的数据末端,使用0x5A 填充,实际上使用任意数据进行填充都可以, USB 排插以前面的有效数据为准。

根据上述表格,很容易编写一个控制这个排插开关的代码(VS2019):

#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <setupapi.h>
 
extern "C" {
 
    void __stdcall
        HidD_GetHidGuid(
            OUT   LPGUID   HidGuid
        );
 
    typedef struct _HIDD_ATTRIBUTES {
        ULONG   Size; // = sizeof (struct _HIDD_ATTRIBUTES)
 
                      //
        // Vendor ids of this hid device
        //
        USHORT  VendorID;
        USHORT  ProductID;
        USHORT  VersionNumber;
 
        //
        // Additional fields will be added to the end of this structure.
        //
    } HIDD_ATTRIBUTES, * PHIDD_ATTRIBUTES;
 
    BOOLEAN __stdcall
        HidD_GetAttributes(
            IN  HANDLE              HidDeviceObject,
            OUT PHIDD_ATTRIBUTES    Attributes
        );
 
    BOOLEAN __stdcall
        HidD_SetFeature(
            _In_    HANDLE   HidDeviceObject,
            _In_reads_bytes_(ReportBufferLength) PVOID ReportBuffer,
            _In_    ULONG    ReportBufferLength
        );
}
 
#pragma comment( lib, "hid.lib" )
#pragma comment( lib, "setupapi.lib" )
 
void SetAll(HANDLE hUsb, bool AllOn)
{
    BOOL Result;
    UCHAR WriteReportBuffer[65];
 
    for (int i = 0; i < 8; i++) {
        WriteReportBuffer[i] = 0x00;
    }
    for (int i = 8; i < 65; i++) {
        WriteReportBuffer[i] = 0x5a;
    }
 
    WriteReportBuffer[1] = 0x0b;
    for (byte i = 0; i < 5; i++) {
        WriteReportBuffer[6] = i;
        if (AllOn) {
            WriteReportBuffer[7] = 0x01;
        }
 
        DWORD lpNumberOfBytesWritten;
 
        Result = WriteFile(hUsb,
            WriteReportBuffer,
            65,
            &lpNumberOfBytesWritten,
            NULL);
        //printf("Written %d bytes Result [%d]\n", lpNumberOfBytesWritten, Result);
    }
 
 
}
 
int main()
{
    GUID HidGuid;
    BOOL Result;
    int  counter = -1;
 
 
    HidD_GetHidGuid(&HidGuid);
    printf("HID GUID: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n"
        , HidGuid.Data1, HidGuid.Data2, HidGuid.Data3
        , HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2], HidGuid.Data4[3], HidGuid.Data4[4]
        , HidGuid.Data4[5], HidGuid.Data4[6], HidGuid.Data4[7]);
 
    HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid, NULL, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (INVALID_HANDLE_VALUE != hDevInfo)
    {
        SP_DEVICE_INTERFACE_DATA strtInterfaceData = { sizeof(SP_DEVICE_INTERFACE_DATA) };
        for (DWORD index = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &HidGuid, index, &strtInterfaceData); ++index)
        {
 
            char buf[1000];
            SP_DEVICE_INTERFACE_DETAIL_DATA& strtDetailData = (SP_DEVICE_INTERFACE_DETAIL_DATA&)buf[0];
            strtDetailData.cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
            if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &strtInterfaceData, &strtDetailData, _countof(buf), NULL, NULL))
            {
                printf("[%d] path: %ls\n", index, strtDetailData.DevicePath);
 
                HANDLE hUsb = CreateFile(strtDetailData.DevicePath,
                    NULL, FILE_SHARE_WRITE,
                    NULL, OPEN_EXISTING,
                    FILE_ATTRIBUTE_NORMAL,
                    NULL);
 
                HIDD_ATTRIBUTES strtAttrib = { sizeof(HIDD_ATTRIBUTES) };
                Result = HidD_GetAttributes(hUsb, &strtAttrib);
                CloseHandle(hUsb);
 
                if (TRUE == Result)
                {
                    if ((0x04D8 == strtAttrib.VendorID) &&
                        (0xFEDC == strtAttrib.ProductID))       //ÕÒµ½ÎÒÃÇ×Ô¼ºµÄÉ豸
                    {
                        printf("VendorID : %hX\n", strtAttrib.VendorID);
                        printf("ProductID: %hX\n", strtAttrib.ProductID);
                        printf("VerNumber: %hX\n", strtAttrib.VersionNumber);
 
                        hUsb = CreateFile(strtDetailData.DevicePath,
                            GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE,
                            NULL, OPEN_EXISTING,
                            FILE_ATTRIBUTE_NORMAL, NULL);
 
                        //Switch(hUsb); GetData(hUsb);
                        SetAll(hUsb,TRUE);
                        Sleep(3000);
                        SetAll(hUsb, FALSE);
 
                        CloseHandle(hUsb);
 
                    }//if ((0x8888==strtAttrib.VendorID) &&
                }            //if(TRUE==Result)
            } // if( SetupDiGetDeviceInterfaceDetail(hDevInfo,&strtInterfaceData,&strtDetailData,_countof(buf),NULL,NULL) )
        }    //for( DWORD index=0;
 
        if (GetLastError() != ERROR_NO_MORE_ITEMS)
        {
            printf("No more items!\n");
        }
 
        SetupDiDestroyDeviceInfoList(hDevInfo);
 
    }  //if( INVALID_HANDLE_VALUE != hDevInfo )
    system("PAUSE");
    return 0;
}

这个代码没有使用厂家提供的API (厂家提供了 DLL)而是直接对 HID设备编程,这样做的好处是无需外挂文件,一个 EXE 都可以搞定。坏处是,没有实现厂家API 提供的诸如取得序列号这样的功能,如果有需求还需要另外研究编写。但是无论如何,能够实现控制端口开关已经足够了。

综上所述:BellWin的UP520US是一款可以通过USB控制的排插,满足实验室安规要求。有需要的朋友可以进一步研究。

参考:

  1. http://powersplitter.bellwin.com.tw/UP520US.html
  2. https://www.bilibili.com/video/BV1CG4y1H7Xk/ USBPacketViewer