UEFI TIPS: 在一个程序中启动另外一个程序

这里提供一个在一个EFI程序中启动另外一个EFI 的例子,没有使用 UEFI Shell API ,放置在 ESP 分区后,可以启动当前的 Windows。

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Guid/FileInfo.h>

/**
 * 启动指定路径的 EFI 应用程序
 */
EFI_STATUS
StartEfiApplication (
  IN EFI_HANDLE        ParentImageHandle,
  IN CHAR16           *ApplicationPath
  )
{
    EFI_STATUS                      Status;
    EFI_HANDLE                      ChildImageHandle;
    EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
    EFI_LOADED_IMAGE_PROTOCOL       *ParentLoadedImage;
    UINTN                           ExitDataSize;
    CHAR16                          *ExitData;

    Print(L"=== Starting EFI Application: %s ===\n", ApplicationPath);

    // 步骤 1: 获取父镜像的 LoadedImage 协议
    Status = gBS->HandleProtocol(
        ParentImageHandle,
        &gEfiLoadedImageProtocolGuid,
        (VOID**)&ParentLoadedImage
    );
    if (EFI_ERROR(Status)) {
        Print(L"ERROR: Failed to get parent LoadedImage protocol: %r\n", Status);
        return Status;
    }
    Print(L"SUCCESS: Got parent LoadedImage protocol\n");

    // 步骤 2: 构建目标应用的设备路径
    DevicePath = FileDevicePath(ParentLoadedImage->DeviceHandle, ApplicationPath);
    if (DevicePath == NULL) {
        Print(L"ERROR: Failed to create device path for %s\n", ApplicationPath);
        return EFI_OUT_OF_RESOURCES;
    }
    Print(L"SUCCESS: Created device path\n");

    // 步骤 3: 加载目标镜像
    Status = gBS->LoadImage(
        FALSE,                  // BootPolicy - FALSE 表示不是启动策略
        ParentImageHandle,      // ParentImageHandle - 父镜像句柄
        DevicePath,             // DevicePath - 目标文件的设备路径
        NULL,                   // SourceBuffer - NULL 表示从设备路径加载
        0,                      // SourceSize - 0 表示从设备路径加载
        &ChildImageHandle       // ImageHandle - 返回的子镜像句柄
    );

    // 释放设备路径内存
    FreePool(DevicePath);

    if (EFI_ERROR(Status)) {
        Print(L"ERROR: LoadImage failed: %r\n", Status);
        return Status;
    }
    Print(L"SUCCESS: Image loaded successfully, Handle = 0x%lx\n", (UINTN)ChildImageHandle);

    // 步骤 4: 启动镜像
    Print(L"Starting image...\n");
    Status = gBS->StartImage(
        ChildImageHandle,       // ImageHandle - 要启动的镜像句柄
        &ExitDataSize,          // ExitDataSize - 返回退出数据大小
        &ExitData               // ExitData - 返回退出数据
    );

    // 步骤 5: 处理启动结果
    if (EFI_ERROR(Status)) {
        Print(L"ERROR: StartImage failed: %r\n", Status);
        
        // 如果有退出数据,显示它
        if (ExitData != NULL && ExitDataSize > 0) {
            Print(L"Exit Data Size: %d bytes\n", ExitDataSize);
            Print(L"Exit Data: %s\n", ExitData);
            
            // 释放退出数据内存
            gBS->FreePool(ExitData);
        }
    } else {
        Print(L"SUCCESS: Image started and returned: %r\n", Status);
        
        // 处理正常退出的数据
        if (ExitData != NULL && ExitDataSize > 0) {
            Print(L"Application returned data: %s\n", ExitData);
            gBS->FreePool(ExitData);
        }
    }

    // 步骤 6: 卸载镜像(如果需要)
    Print(L"Unloading image...\n");
    gBS->UnloadImage(ChildImageHandle);

    Print(L"=== Application execution completed ===\n\n");
    return Status;
}

/**
 * 检查文件是否存在
 */
EFI_STATUS
CheckFileExists (
  IN EFI_HANDLE     DeviceHandle,
  IN CHAR16        *FilePath
  )
{
    EFI_STATUS                      Status;
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *FileSystem;
    EFI_FILE_PROTOCOL               *Root;
    EFI_FILE_PROTOCOL               *File;

    // 获取文件系统协议
    Status = gBS->HandleProtocol(
        DeviceHandle,
        &gEfiSimpleFileSystemProtocolGuid,
        (VOID**)&FileSystem
    );
    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 打开根目录
    Status = FileSystem->OpenVolume(FileSystem, &Root);
    if (EFI_ERROR(Status)) {
        return Status;
    }

    // 尝试打开目标文件
    Status = Root->Open(
        Root,
        &File,
        FilePath,
        EFI_FILE_MODE_READ,
        0
    );

    if (!EFI_ERROR(Status)) {
        Print(L"File exists: %s\n", FilePath);
        File->Close(File);
    } else {
        Print(L"File not found: %s (Status: %r)\n", FilePath, Status);
    }

    Root->Close(Root);
    return Status;
}

/**
 * 主入口函数
 */
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
    EFI_STATUS                Status;
    EFI_LOADED_IMAGE_PROTOCOL *LoadedImage;
    EFI_INPUT_KEY             Key;

    // 清屏
    gST->ConOut->ClearScreen(gST->ConOut);
    
    Print(L"UEFI StartImage Example Application\n");
    Print(L"====================================\n\n");

    // 获取当前镜像信息
    Status = gBS->HandleProtocol(
        ImageHandle,
        &gEfiLoadedImageProtocolGuid,
        (VOID**)&LoadedImage
    );
    if (EFI_ERROR(Status)) {
        Print(L"Failed to get LoadedImage protocol: %r\n", Status);
        return Status;
    }

    // 示例 : 启动 Windows Boot Manager
    Print(L"Example 1: Starting Windows Boot Manager\n");
    CheckFileExists(LoadedImage->DeviceHandle, L"EFI\\Boot\\bootx64.efi");
    Status = StartEfiApplication(ImageHandle, L"EFI\\Boot\\bootx64.efi");
    Print(L"Windows Boot Manager result: %r\n\n", Status);

    // 等待用户按键
    Print(L"Press any key to exit...\n");
    gST->ConIn->Reset(gST->ConIn, FALSE);
    while (gST->ConIn->ReadKeyStroke(gST->ConIn, &Key) == EFI_NOT_READY) {
        gBS->Stall(10000); // 等待 10ms
    }

    return EFI_SUCCESS;
}
[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = sat
  FILE_GUID                      = 4ea97c46-7491-2025-1125-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = UefiMain

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  StartImageTest.c

[Packages]
  MdePkg/MdePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  UefiBootServicesTableLib
  
[Protocols]
  gEfiLoadedImageProtocolGuid
  gEfiSimpleFileSystemProtocolGuid
  gEfiSimpleTextInProtocolGuid
  gEfiSimpleTextOutProtocolGuid

  
[BuildOptions]

[Guids]

SMBIOS 2.X 和 3.X 区别

今天偶然发现 SMBIOS 2.X 和 3.X 存在一些差别,在处理的时候代码需要不同对待。

1. 入口点结构 (Entry Point Structure)定义的差异:

SMBIOS 2.X Entry Point

typedef struct {
  UINT8   AnchorString[4];           // "_SM_"
  UINT8   EntryPointStructureChecksum;
  UINT8   EntryPointLength;          // 0x1F
  UINT8   MajorVersion;
  UINT8   MinorVersion;
  UINT16  MaxStructureSize;
  UINT8   EntryPointRevision;
  UINT8   FormattedArea[5];
  UINT8   IntermediateAnchorString[5]; // "_DMI_"
  UINT8   IntermediateChecksum;
  UINT16  TableLength;               // 表长度
  UINT32  TableAddress;              // 32位表地址
  UINT16  NumberOfSmbiosStructures;
  UINT8   SmbiosBcdRevision;
} SMBIOS_TABLE_ENTRY_POINT;

SMBIOS 3.X Entry Point

typedef struct {
  UINT8   AnchorString[5];           // "_SM3_"
  UINT8   EntryPointStructureChecksum;
  UINT8   EntryPointLength;          // 0x18
  UINT8   MajorVersion;
  UINT8   MinorVersion;
  UINT8   DocRev;
  UINT8   EntryPointRevision;
  UINT8   Reserved;
  UINT32  TableMaximumSize;          // 表最大长度
  UINT64  TableAddress;              // 64位表地址
} SMBIOS_TABLE_3_0_ENTRY_POINT;

2. 主要技术差异

特性SMBIOS 2.XSMBIOS 3.X
地址空间32位地址64位地址
表大小限制最大 65535 字节最大 4GB
入口点标识SM” + “DMISM3
入口点大小31 字节 (0x1F)24 字节 (0x18)
结构计数明确指定结构数量不指定,需遍历到Type 127
校验和两个校验和一个校验和

实践发现,目前机器有不同的实现方式,比如:声明了 3.0 但是实际上仍然是 2.0 的结构;3.0 和 2.0 同时共存,这种情况下看起来 Windows 更倾向于使用 3.o 提供的信息。

基于 FireBeetle P4 制作一个USB 麦克风

在 FireBeetle P4 板子上,有一个 PDM 的麦克风。

基本实现原理是:通过ESP32  IDF编程使用 TinyUSB 架构将 P4 模拟为 USB UAC 设备,然后通过这个麦克风获得环境声音,这样就得到了一个 USB 麦克风。

需要特别注意的是 PDM 的初始化和 I2S 的会有一些差异,DFRobot 选择的这个数字麦克风资料很少,看起来只支持一个 24K 的采样率:

void init_pdm_rx(void) {
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
    i2s_new_channel(&chan_cfg, NULL, &rx);

    i2s_pdm_rx_config_t pdm_cfg = {
        .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE),
        //.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .slot_cfg = I2S_PDM_RX_SLOT_PCM_FMT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
                .gpio_cfg = {
            .clk = MIC_I2S_CLK,      // PDM clock
            // QUESTION - what about the LR clock pin? No longer relevant? Do we ties it high or low?
            .din = MIC_I2S_DATA,     // PDM data
            .invert_flags = { .clk_inv = false },
        },
    };
    pdm_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; // single mic

    i2s_channel_init_pdm_rx_mode(rx, &pdm_cfg);
    i2s_channel_enable(rx);
}

上述设置之后,就可以在回调函数中填充需要对主机反馈的数据了:

static esp_err_t usb_uac_device_input_cb(uint8_t *buf, size_t len, size_t *bytes_read, void *arg)
{
    if (!rx) {
        return ESP_FAIL;
    }
        //memcpy(buf,Buff,len);
        //*bytes_read=len;
        //return ESP_OK;
    return i2s_channel_read(rx, buf, len, bytes_read, portMAX_DELAY);
}

完整的代码:

工作的视频

eSPI 综述

这篇文章来自 MicroChip ,原文在【参考1】,标题是“是时候迁移到 eSPI 总线了吗?”(Is It Time to Migrate to the eSPI Bus?)。

大多数计算机用户都知道高速总线的存在,比如 PC 上配备的 PCI Express® (PCIe®) 附加卡或 USB 接口。然而,他们可能不知道所有计算机上都存在低速总线。多年来,这种总线一直用于连接各种设备,例如嵌入式控制器 (EC,笔记本上使用)、基板管理控制器 (BMC,服务器上用于远程管理)、Super I/O (SIO, 台式机) 、用于存储 BIOS 代码的SPINOR以及可信平台模块 (TPM) 到系统核心逻辑。这种低速总线最初被称为低引脚数 (LPC) 总线。 

随着计算行业需求的不断发展,更加灵活高效的增强型串行外设接口 (eSPI) 总线应运而生,以克服 LPC 总线的局限性。这款一体化总线由最新的 PC 计算芯片组支持,旨在取代 LPC 总线以及 SPI 总线、SMbus 和Sideband信号。这种情况下可以通过一个 GPIO 控制这个设备的供电重新给让它工作起来,这个 GPIO 就可以称作 Out band)。对于计算应用设计人员而言,从 LPC 总线迁移到 eSPI 总线具有以下优势:

  • 节省成本:由于 LPC 总线需要大量边带信号来实现电源排序和睡眠模式支持,因此它使用 13 个引脚连接到系统处理器。eSPI 协议使用虚拟线来实现其中一些信号,因此大多数实现中只需要五六个引脚,从而减少了引脚数量和成本。
  • 更低电压:LPC总线需要3.3VI/O信号,而eSPI总线使用1.8V,显著降低系统功耗。
  • 简化电路板布局和设计:LPC 总线需要同步 24 MHz 或 33 MHz 时钟,因此需要仔细的电路板布局,以确保时钟和数据信号长度与所有设备匹配。eSPI 总线使用来自系统处理器的主驱动时钟,从而简化了电路板布局和设计。
  • 低功耗状态:LPC 总线只能在系统处于 S0 状态时运行,而 eSPI 总线则可以在系统处于低功耗 S5 状态时运行。这可以实现许多系统改进,包括:
    • 用于支持电源排序的边带信号可以打包在eSPI 中传输从而变成虚拟线,就无需在硬件上拉出来线路。
    • EC可以在启动时共享系统SPI存储,从而无需在系统中添加额外的SPI芯片,从而降低系统成本。
    • 在 S5 状态下,eSPI 总线可用于核心逻辑与 EC 之间的通信。这样可以移除额外的边带通信总线,例如 I²C 和 PECI,从而减少电路板上的额外信号

以下两个图表显示了基于 LPC 的系统和基于 eSPI 的系统之间的差异。

图-LPC系统图

图 – eSPI 系统图

从上图可以看到,很多总线和功能能够“打包”到 eSPI中。

eSPI 规范指定了几种可通过总线进行通信的模式或通道:

  • 外设通道用于与位于 EC、BMC 和 SIO 中的设备(以前位于 LPC 总线上)进行通信。这些设备包括 UART、邮箱寄存器、端口 80 寄存器、嵌入式内存接口和键盘控制器。外设通道还支持总线主控通道。总线主控功能允许 EC 直接从主系统内存读取/写入数据。
  • 虚拟线通道用于将边带信号信息传输到/接收自 EC、BMC 和 SIO。来自外围设备(例如 UART)的中断也通过虚拟线通道传输。与 LPC 总线相比,该通道大大减少了 eSPI 总线的引脚数量和成本。
  • 带外 (OOB) 消息通道用于通过 eSPI 传输 SMBus 流量。这些消息可以包括系统逻辑和处理器温度值,或 SMBus 管理组件传输协议 (MCTP) 数据包。
  • 闪存访问通道允许系统处理器在 BIOS、管理引擎 (ME)、EC、BMC 和 SIO 之间共享系统 SPI Flash。这通过减少系统中 SPI Flash 芯片的数量来降低系统成本。

如果您准备将您的设计迁移到支持 eSPI 总线,我们的 MEC14xx 和 MEC17xx 嵌入式控制器是绝佳选择。Microchip 是首批支持 eSPI 总线的公司之一,并被英特尔® 选为其 eSPI 开发的验证合作伙伴。这意味着我们的设备已通过英特尔 eSPI 主站的全面验证。英特尔还选择了我们的 EC 作为其参考验证平台,确保它们获得英特尔的全面支持。访问我们的嵌入式控制器设计中心 ,了解更多关于如何将您的计算设计迁移到这项新总线技术的信息。

参考:

1.https://www.microchip.com/en-us/solutions/data-centers-and-computing/computing-solutions/technologies/espi

PY 更改默认值

Py 是 Python 的 Launcher,在安装的时候可以选择:

装好之后,运行 py 可以直接打开 Python。它的作用是让你方便的在不同版本之间切换。比如,你的系统安装了多个 Python,可以使用 py –list 进行查看:

默认情况下运行 py ,会运行 3.14 版本的。

一些资料上说可以通过修改 py.ini 的方法修改上面的列表,但是这个文件安装之后不会自动生成,有需要的话可以手工添加,例如下面的文件保存为 py.ini 然后放在 py.exe 同一个目录下,每次运行 py 会自动调用 Python 3.8:

;
; This is an example of how a Python Launcher .ini file is structured.
; If you want to use it, copy it to py.ini and make your changes there,
; after removing this header comment.
; This file will be removed on launcher uninstallation and overwritten
; when the launcher is installed or upgraded, so don't edit this file
; as your changes will be lost.
;
[defaults]
; Uncomment out the following line to have Python 3 be the default.
python=3.8

[commands]
; Put in any customised commands you want here, in the format
; that's shown in the example line. You only need quotes around the
; executable if the path has spaces in it.
;
; You can then use e.g. #!myprog as your shebang line in scripts, and
; the launcher would invoke e.g.
;
; "c:\Program Files\MyCustom.exe" -a -b -c myscript.py
;
;myprog="c:\Program Files\MyCustom.exe" -a -b -c

此外,还有一种方法是设定一个环境变量,例如:set py_python=3.13,再次运行py 会直接调用 python 3.13

上述方法来自【参考1】。

但是,上述方法并不能完全解决问题,比如,我的编译环境中会调用 py -3 来启动 Python ,测试下来 -3 参数会导致py 自动调用当前系统中最新版本的 Python。我这边研究之后的解决方法是:重新编译 Python Source Code 直接写死:

在Python-3.14.0\Python-3.14.0\PC\launcher2.c 中,首先在开头处定义需要的版本:

static FILE * log_fp = NULL;

wchar_t MyTag[] = L"3.8";

void
debug(wchar_t * format, ...)
{
    va_list va;

然后修改代码

       if (argLen > 0) {
            if (STARTSWITH(L"2") || STARTSWITH(L"3")) {
                
                // All arguments starting with 2 or 3 are assumed to be version tags
                //LAB-Z_Debug search->tag = arg;
                //LAB-Z_Debug search->tagLength = argLen;
                //LAB-Z_Debug_Start
                wchar_t MyTag[] = L"3.8";
                search->tag = MyTag;
                search->tagLength = 3;
                //LAB-Z_Debug_End
                search->oldStyleTag = true;
                search->restOfCmdLine = tail;
            } else if (STARTSWITH(L"V:") || STARTSWITH(L"-version:")) {

编译后的 py.exe 替换之前的 py.exe即可。

修改后的代码下载:

重新编译后的 py.exe 下载(固定调用 3.8)

参考:

1.https://docs.python.org/3/using/windows.html

2.调试时可以设置 set PYLAUNCHER_DEBUG=1 这样会打开 Py.exe 的调试输出,方便研究。

UEFI TIPS: 这样会导致内存泄漏吗?

最近编写代码的时候,忽然提出一个问题,按照下面的写法会导致内存泄漏的问题吗?

  for (UINTN i=0;i<1000;i++) {
	  UINTN x;
	  Print(L"%x\n",x++);
  }

为了验证这个,编写一个完整的 UEFI 代码进行测试:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

/***
  Print a welcoming message.

  Establishes the main structure of the application.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  
  for (UINTN i=0;i<1000;i++) {
	  UINTN x;
	  Print(L"%x\n",x++);
  }

  return(0);
}

对应的,在 INF 文件中定义生成汇编代码:

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

查看生成的 cod文件:

$LN6:
  00000	48 89 54 24 10	 mov	 QWORD PTR [rsp+16], rdx
  00005	48 89 4c 24 08	 mov	 QWORD PTR [rsp+8], rcx
  0000a	48 83 ec 48	 sub	 rsp, 72			; 00000048H

; 27   :   
; 28   :   for (UINTN i=0;i&lt;1000;i++) {

  0000e	48 c7 44 24 20
	00 00 00 00	 mov	 QWORD PTR i$1[rsp], 0
  00017	eb 0d		 jmp	 SHORT $LN4@ShellAppMa
$LN2@ShellAppMa:
  00019	48 8b 44 24 20	 mov	 rax, QWORD PTR i$1[rsp]
  0001e	48 ff c0	 inc	 rax
  00021	48 89 44 24 20	 mov	 QWORD PTR i$1[rsp], rax
$LN4@ShellAppMa:
  00026	48 81 7c 24 20
	e8 03 00 00	 cmp	 QWORD PTR i$1[rsp], 1000 ; 000003e8H
  0002f	73 2a		 jae	 SHORT $LN3@ShellAppMa

; 29   : 	  UINTN x;
; 30   : 	  Print(L"%x\n",x++);

  00031	48 8b 44 24 28	 mov	 rax, QWORD PTR x$2[rsp]
  00036	48 89 44 24 30	 mov	 QWORD PTR tv68[rsp], rax
  0003b	48 8b 54 24 30	 mov	 rdx, QWORD PTR tv68[rsp]
  00040	48 8d 0d 00 00
	00 00		 lea	 rcx, OFFSET FLAT:??_C@_17KDIHCDGM@?$AA?$CF?$AAx?$AA?6@
  00047	e8 00 00 00 00	 call	 Print
  0004c	48 8b 44 24 28	 mov	 rax, QWORD PTR x$2[rsp]
  00051	48 ff c0	 inc	 rax
  00054	48 89 44 24 28	 mov	 QWORD PTR x$2[rsp], rax

; 31   :   }

  00059	eb be		 jmp	 SHORT $LN2@ShellAppMa
$LN3@ShellAppMa:

可以看到:变量 X 就是 QWORD PTR i$1[rsp],在循环中并不会每次重新分配内存。因此,无需担心内存泄漏的问题。

查了一下资料:

“在C语言的早期版本中,局部变量的声明必须集中在函数或代码块的开头,位于任何可执行语句之前。这种限制在C89/ANSI C标准中得到了明确的规范。

随着C语言标准的发展,C99标准引入了更灵活的变量声明方式,允许在代码块的任意位置声明局部变量,只要遵循“先定义后使用”的原则即可。这种改进使得程序员可以在需要使用变量的地方才进行声明,从而提高了代码的可读性和编写灵活性。“

Flex Windows 下的简单测试

一般来说,计算机系的毕业生很容易编写出来一个词法分析器,能够将输入的文本解析为 Token 。但是业界已经有了成熟完善的方法和工具,Flex 就是其中的一个

Flex是一种用于生成词法分析器的工具,通过读取包含正则表达式和对应C代码的规则文件,自动生成可识别特定词法模式的C语言源代码。其输入文件由定义区、规则区、用户代码区组成,支持与语法分析器生成工具Bison协同工作。生成的词法分析器可应用于编译器开发、复杂系统建模、教学实验等领域,具有正则表达式语法兼容Lex、错误处理机制完善、自定义函数扩展灵活等技术特性。

Flex通过解析用户定义的正则表达式规则,生成C语言实现的词法分析器源码,可自动将输入文本分解为预定义的词法单元。生成的词法分析器包含默认入口函数,支持通过重定义宏实现自定义输入源。工具保持与Lex语法的高度兼容性,生成的代码可直接嵌入到C/C++工程项目中使用。

从上面也可以看到使用 Flex 的好处是:可以通过定义正则表达式规则来进行此法分析,方便编写C代码,此外生成的结果可以配合 Bison进行语法分析。之前提到过, ACPICA 提供的套件就是基于 Flex和Bison 编写的。

1.根据【参考1】配置环境

2.编写 lexer.l 代码如下:

%{
#include <stdio.h>
#include <stdlib.h>

// 手动定义Token类型(若无Bison)
#define NUMBER 256
#define ID     257
#define PLUS   258
int line_num = 1;
%}

DIGIT    [0-9]
LETTER   [a-zA-Z]
%%
{DIGIT}+    { printf("NUMBER: %s\n", yytext); return NUMBER; }
{LETTER}+   { printf("IDENTIFIER: %s\n", yytext); return ID; }
"+"         { printf("PLUS\n"); return PLUS; }
"\n"        { line_num++; }
[ \t]       ;  // 忽略空白字符
.           { printf("Unknown char: %s\n", yytext); }
%%

int yywrap() { return 1; }

3.Visual C++ 2019 编写 flextest.c 文件(不是 .cpp)如下:

#include <stdio.h>
#pragma warning(disable:4996)
#include "lex.yy.c"  // 包含Flex生成的代码

int main() {
    yyin = fopen("input.txt", "rb");
    if (!yyin) {
        perror("Failed to open input file");
        return 1;
    }


    while (yylex()) {
    }

    fclose(yyin);
    return 0;
}

4.使用时,先运行 flex lexer.l 生成 lex.yy.c(如果需要 debug 可以使用 flex -d lexer.l)。

5.编译flextest.c,然后配合如下测试文件

123
abc
+
xyz 456
@
123123
1222
4dd

6.运行结果如下

可以看到输出了识别到的各种Token

EDK2 202508 来了

今年8月份,EDK2 202508正式发布在:

https://github.com/tianocore/edk2/releases/tag/edk2-stable202508

从 History 来看,增加了一点新的功能

在上面的链接下载代码之后,大小是 25MB 左右,但是无法直接编译(build -a X64 -p EmulatorPkg\EmulatorPkg.dsc -t VS2019),会出现下面的错误

MdePkg.dec(33): error 000E: File/directory not found in workspace
C:\BuildBs\edk2508\MdePkg\Library\MipiSysTLib\mipisyst\library\include

产生的原因是这个目录下缺少文件,可以通过和抓到的完整版进行比较补全:

补全上面的之后,还是会碰到类似问题,也是因为缺少文件导致的,重复上述步骤补全。最终得到一个可以完整编译EmulatorPkg的。

于是,这次提供三个 Pacakge

1.原始的 EDK2 202508 (24.6MB)

edk2-edk2-stable202508_ORG.zip
链接: https://pan.baidu.com/s/1onGGn_4UFN3loVktKNrVGQ?pwd=labz 提取码: labz

2.修改后的EDK2 202508,可以编译 EmulatorPkg (207MB)

edk2508.7z
链接: https://pan.baidu.com/s/1fhPY4Hqqf5jOE6iqJ6Dheg?pwd=labz 提取码: labz

3.完整的 EDK2 202508 , 包括所有的第三方源代码(2.27G)

edk22508Full.7z
链接: https://pan.baidu.com/s/1TSbmW8v7jTtmdsXc40ScQQ?pwd=labz 提取码: labz

ESP32 ESPNOW 功能测试

ESP32 的 ESPNOW 给我们提供了一个方便的让 ESP32 无线互联通讯的方法。这次测试的是:一个 DFRobot 的 FireBeetle ESP32 和 ESP32S3 通过 ESPNOW 互联。

代码要点:

  1. 发送方需要知道接收方的 MAC,每一个ESP32都内置了一个独一无二的MAC;
  2. 通讯成功后,接收方能够得知发送方的 MAC;
  3. 简单起见,通过    esp_wifi_set_mac函数直接指定当前 ESP32 的 MAC,这样就不用专门查询了;
  4. 通过IDPIN区分两块板子,就是说测试的时候,一块板子这个引脚悬空,另外一个拉低
#include &lt;esp_now.h>
#include &lt;WiFi.h>
#include "MacAddress.h"
#include "WiFi.h"
#include "esp_wifi.h"

#define IDPIN 48

//  发射器地址(当前是发射) ESP32 A 的 MAC 地址
uint8_t AddressA[] = {'L', 'A', 'B', '-', 'Z', 'S'};
//  接收器地址 ESP32 B 的 MAC 地址
uint8_t AddressB[] = {'L', 'A', 'B', '-', 'Z', 'R'};

// 数据结构
typedef struct {
  char data[250];
} esp_now_message_t;

esp_now_message_t message;

void onDataReceive(const esp_now_recv_info *mac, const uint8_t *incomingData, int len) {
  esp_now_message_t receivedMessage;
  memcpy(&amp;receivedMessage, incomingData, sizeof(receivedMessage));
  if (digitalRead(IDPIN) == HIGH) {
    Serial.print("Received from ESP32 B: ");
  } else {
    Serial.print("Received from ESP32 A: ");
  }
  Serial.println(receivedMessage.data);
}

void setup() {
  pinMode(IDPIN, INPUT_PULLUP);
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

  if (digitalRead(IDPIN) == HIGH) {
    // 设定本机 MAC 为 AddressA
    esp_wifi_set_mac(WIFI_IF_STA, &amp;AddressA[0]);
  } else {
    // 设定本机 MAC 为 AddressB
    esp_wifi_set_mac(WIFI_IF_STA, &amp;AddressB[0]);
  }

  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  esp_now_peer_info_t peerInfo;
  if (digitalRead(IDPIN) == HIGH) {
    memcpy(peerInfo.peer_addr, AddressB, 6);
  } else {
    memcpy(peerInfo.peer_addr, AddressA, 6);
  }
  peerInfo.channel = 1;
  peerInfo.ifidx = WIFI_IF_STA;
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&amp;peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }

  esp_now_register_recv_cb(onDataReceive);
}

void loop() {
  if (Serial.available()) {
    int len = Serial.readBytesUntil('\n', message.data, sizeof(message.data));
    message.data[len] = '\0'; // 确保字符串以 null 结尾

    esp_err_t result;
    if (digitalRead(IDPIN) == HIGH) {
       result = esp_now_send(AddressB, (uint8_t *)&amp;message, sizeof(message));
    } else {
       result = esp_now_send(AddressA, (uint8_t *)&amp;message, sizeof(message));
    }

    if (result == ESP_OK) {
      if (digitalRead(IDPIN) == HIGH) {
        Serial.println("Sent to ESP32 B successfully");
      } else {
        Serial.println("Sent to ESP32 A successfully");
      }
    } else {
      Serial.println("Error sending the data");
    }
  }
}

工作的视频在 【ESP32和ESP32S3通过ESPNOW通信演示】