Windows 11 管理员权限打开 cmd 窗口,输入如下命令即可:
Taskkill /fi "pid ge 1" /f
无需任何工具100% 0x000007f或f4蓝屏,重启后对系统无任何影响。
Windows 11 管理员权限打开 cmd 窗口,输入如下命令即可:
Taskkill /fi "pid ge 1" /f
无需任何工具100% 0x000007f或f4蓝屏,重启后对系统无任何影响。
这里提供一个在一个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 存在一些差别,在处理的时候代码需要不同对待。
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;
| 特性 | SMBIOS 2.X | SMBIOS 3.X |
|---|---|---|
| 地址空间 | 32位地址 | 64位地址 |
| 表大小限制 | 最大 65535 字节 | 最大 4GB |
| 入口点标识 | “SM” + “DMI“ | “SM3“ |
| 入口点大小 | 31 字节 (0x1F) | 24 字节 (0x18) |
| 结构计数 | 明确指定结构数量 | 不指定,需遍历到Type 127 |
| 校验和 | 两个校验和 | 一个校验和 |
实践发现,目前机器有不同的实现方式,比如:声明了 3.0 但是实际上仍然是 2.0 的结构;3.0 和 2.0 同时共存,这种情况下看起来 Windows 更倾向于使用 3.o 提供的信息。
在 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);
}
完整的代码:
工作的视频
这篇文章来自 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 的系统和基于 eSPI 的系统之间的差异。

图-LPC系统图

图 – eSPI 系统图
从上图可以看到,很多总线和功能能够“打包”到 eSPI中。
eSPI 规范指定了几种可通过总线进行通信的模式或通道:
如果您准备将您的设计迁移到支持 eSPI 总线,我们的 MEC14xx 和 MEC17xx 嵌入式控制器是绝佳选择。Microchip 是首批支持 eSPI 总线的公司之一,并被英特尔® 选为其 eSPI 开发的验证合作伙伴。这意味着我们的设备已通过英特尔 eSPI 主站的全面验证。英特尔还选择了我们的 EC 作为其参考验证平台,确保它们获得英特尔的全面支持。访问我们的嵌入式控制器设计中心 ,了解更多关于如何将您的计算设计迁移到这项新总线技术的信息。
参考:
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 的调试输出,方便研究。
最近编写代码的时候,忽然提出一个问题,按照下面的写法会导致内存泄漏的问题吗?
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<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标准引入了更灵活的变量声明方式,允许在代码块的任意位置声明局部变量,只要遵循“先定义后使用”的原则即可。这种改进使得程序员可以在需要使用变量的地方才进行声明,从而提高了代码的可读性和编写灵活性。“
一般来说,计算机系的毕业生很容易编写出来一个词法分析器,能够将输入的文本解析为 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
今年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 无线互联通讯的方法。这次测试的是:一个 DFRobot 的 FireBeetle ESP32 和 ESP32S3 通过 ESPNOW 互联。
代码要点:
#include <esp_now.h>
#include <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(&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, &AddressA[0]);
} else {
// 设定本机 MAC 为 AddressB
esp_wifi_set_mac(WIFI_IF_STA, &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(&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 *)&message, sizeof(message));
} else {
result = esp_now_send(AddressA, (uint8_t *)&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通信演示】