Step to UEFI (277)QEMU 增加自定义的 FFS和读取

这次实验的是在 OVMF 生成的BIOS中插入一个Binary ,然后在代码中将这个Binary 读取出来。

第一个目标:在 OVMF 中插入 Binary。

1.我们准备一个 message.txt,其中内容是简单的字符串:

This is a test message comes from 
www.lab-z.com

2.在\OvmfPkg\OvmfPkgX64.fdf 文件中,加入下面的代码

!if $(E1000_ENABLE)
  FILE DRIVER = 5D695E11-9B3F-4b83-B25F-4A8D5D69BE07 {
    SECTION PE32 = Intel3.5/EFIX64/E3522X2.EFI
  }
!endif

#LABZDebug_Start
FILE FREEFORM = C3E36D09-2023-0829-A857-D5288FE33E28 Align=4K {
  SECTION RAW = OvmfPkg/LabzBin/message.txt
}
#LABZDebug_End

!include NetworkPkg/Network.fdf.inc
  INF  OvmfPkg/VirtioNetDxe/VirtioNet.inf

3.使用工具查看放置的FFS,可以看到正确的增加到 BIOS 中

这样,第一个目标已经完成,我们成功的生成了一个FFS文件。

第二个目标,将这个 FFS文件从FV中读取出来。之前我们做过类似的实验,在【参考1】中有介绍。这次我们编写一个 UEFI Shell Application ,显示前面插入的 FFS文件的内容。测试代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include  "PiFirmwareVolume.h"
#include  "PiFirmwareFile.h"
#include  "FirmwareVolume2.h"

INTN
EFIAPI
ShellAppMain (
    IN UINTN Argc,
    IN CHAR16 **Argv
)
{
	CONST EFI_GUID    NameGuid= { 0xC3E36D09, 0x2023, 0x0829,
		{ 0xA8, 0x57, 0xD5, 0x28, 0x8F, 0xE3, 0x3E, 0x28 }
	};
	EFI_SECTION_TYPE  SectionType=EFI_SECTION_RAW;

	VOID             *Buffer=NULL;
	UINTN             Size=0;
	UINT32            AuthenticationStatus=0;
	EFI_STATUS  	  Status;
	EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv;

	Status = gBS->LocateProtocol (
	             &gEfiFirmwareVolume2ProtocolGuid,
	             NULL,
	             (VOID **) &Fv
	         );
	if (EFI_ERROR (Status))
	{
		Print(L"[EFI_FIRMWARE_VOLUME2_PROTOCOL not found]\n");
		return EFI_NOT_FOUND;
	}
	//
	// Read desired section content in NameGuid file
	//
	Status      = Fv->ReadSection (
	                  Fv,
	                  &NameGuid,
	                  SectionType,
	                  0,
	                  &Buffer,
	                  &Size,
	                  &AuthenticationStatus);
	UINT8 *P=(UINT8 *)Buffer;
	Print(L"[EFI_FIRMWARE_VOLUME2_PROTOCOL %r]\n",Status);

	for (UINTN i=0; i<Size; i++)
	{
		Print(L"%c",P[i]);
	}
	Print(L"\n");

	return(0);
}

运行的结果如下图所示,可以看到正确的读取出我们存放的内容:

完整的代码下载:

参考:

1. https://www.lab-z.com/getffs/  代码读取一个 FFS

Step to UEFI (276)宏和结构体初始化表格

在 EDK2 中有一种比较有趣的定义和初始化Table 的方法,主要是基于 __VA_ARGS__ 这个宏。

“__VA_ARGS__是一个预处理宏,用于表示可变数量的参数。当在宏定义中使用__VA_ARGS__,它会自动展开为传递给宏的实际参数。以下是一个示例使用__VA_ARGS__的宏定义代码:
#include &lt;stdio.h>
 
#define PRINT_ARGS(...) printf(__VA_ARGS__)
 
int main() {
    PRINT_ARGS("Hello, %s!\n", "World");
    return 0;
}
上述代码中,宏定义PRINT_ARGS使用__VA_ARGS__来表示可变数量的参数,并通过printf函数打印参数。在main函数中,我们调用PRINT_ARGS宏来打印字符串"Hello, World!"。运行结果为输出"Hello, World!"。
总结:__VA_ARGS__是一个用于表示可变数量参数的预处理宏,在宏定义中使用它可以方便地处理不定数量的参数。“----来自百度

很多时候,我们定义一个 Table 用来传递一些常量,Table需要给出具体的长度,通过这个宏可以实现自动给出Table 的长度,避免用户手工计数的麻烦。

下面是一个示例代码:

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

#define MY_TABLE_INIT(Vid,Did,...) \
{ \
  { Vid, Did, (sizeof((UINT32[]){__VA_ARGS__})/sizeof(UINT32)) }, \
  { __VA_ARGS__ } \
}

typedef struct {
  UINT16  VendorId;
  UINT16  DeviceId;
  UINT16  DataDwords;
} MY_TABLE_HEADER;

typedef struct  {
  MY_TABLE_HEADER  Header;
  UINT32 Data[];
} ONE_TABLE;

ONE_TABLE Table = MY_TABLE_INIT (
  0x1234, 0x5678,
  
  // Raw Data
  0x01234567,
  0x89ABCDEF,
  0xFEDCBA98,
  0x76543210
);

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
  Print(L"Table:\n");
  Print(L"   VendorId:[%X]\n",Table.Header.VendorId);
  Print(L"   DeviceId:[%X]\n",Table.Header.DeviceId);
  Print(L"   Size    :[%X]\n",Table.Header.DataDwords);  
  
  for (int i=0;i<Table.Header.DataDwords;i++) {
	  Print(L"[%04X]",Table.Data[i]);  
  }
  Print(L"\n");
  return(0);
}

运行结果如下:

上面代码的解释如下:

1.首先我们定义一个 ONE_TABLE 结构体用来“携带”数据。

typedef struct  {
  MY_TABLE_HEADER  Header;
  UINT32 Data[];
} ONE_TABLE;

从定义可以看到,这个结构体包含了一个头,还有一个变长的数据段。头可以实现用于识别判断这个Table 是否为我们需要的目的,例如,其中有DID和VID 信息。具体定义如下,特别注意 DataDwords 给出了后面变长数据段的长度:

typedef struct {
  UINT16  VendorId;
  UINT16  DeviceId;
  UINT16  DataDwords;
} MY_TABLE_HEADER;

对于DataDwords 就是我们前面提到的“Table需要给出具体的长度”的问题。

2.为了解决上述问题,通过下面的宏来解决:

#define MY_TABLE_INIT(Vid,Did,...) \
{ \
  { Vid, Did, (sizeof((UINT32[]){__VA_ARGS__})/sizeof(UINT32)) }, \
  { __VA_ARGS__ } \
}

其中(sizeof((UINT32[]){__VA_ARGS__})/sizeof(UINT32)) 就是计算长度的代码。最终的结果是以 UINT32(DWORD)给出的。

3.初始化定义如下,可以看到DataDwords的计算是宏直接完成的,并不需要我们直接提供

ONE_TABLE Table = MY_TABLE_INIT (
  0x1234, 0x5678,
  
  // Raw Data
  0x01234567,
  0x89ABCDEF,
  0xFEDCBA98,
  0x76543210
);

可以看到,通过上面的方法可以帮助我们方便的实现可变数据的长度定义,有兴趣的朋友不妨尝试一下。

完整的代码下载:

批处理延时和计算经过时间

首先介绍一下批处理中延时的实现:下面代码实现延时3秒

CHOICE /T 3 /C ync /CS /D y

计算经过时间,以秒为单位:

@echo off
set "t=%time%"
::You code start here

::You code end here
set "t1=%time%"

if "%t1:~,2%" lss "%t:~,2%" set "add=+24"
set /a "times=(%t1:~,2%-%t:~,2%%add%)*3600+(1%t1:~3,2%%%100-1%t:~3,2%%%100)*60+(1%t1:~6,2%%%100-1%t:~6,2%%%100)" 
echo Time Used %times% Seconds
pause

上述代码合在一起进行测试:

@echo off
set "t=%time%"
::You code start here
CHOICE /T 3 /C ync /CS /D y
::You code end here
set "t1=%time%"

if "%t1:~,2%" lss "%t:~,2%" set "add=+24"
set /a "times=(%t1:~,2%-%t:~,2%%add%)*3600+(1%t1:~3,2%%%100-1%t:~3,2%%%100)*60+(1%t1:~6,2%%%100-1%t:~6,2%%%100)" 
echo Time Used %times% Seconds
pause

将一个文件中的多个 Sheet 内容合并的VBA

如下的 VBA 代码可以帮助我们将一个Excel文件中的多个 Sheet 合并到一起:

Sub Merge_Sheets()
    'Insert a new worksheet
    Sheets.Add
     
    'Rename the new worksheet
    ActiveSheet.Name = "ProfEx_Merged_Sheet"
     
    'Loop through worksheets and copy the to your new worksheet
    For Each ws In Worksheets
        ws.Activate
         
        'Don't copy the merged sheet again
        If ws.Name &lt;> "ProfEx_Merged_Sheet" Then
            ws.UsedRange.Select
            Selection.Copy
            Sheets("ProfEx_Merged_Sheet").Activate
             
            'Select the last filled cell
            ActiveSheet.Range("A1048576").Select
            Selection.End(xlUp).Select
             
            'For the first worksheet you don't need to go down one cell
            If ActiveCell.Address &lt;> "$A$1" Then
                ActiveCell.Offset(1, 0).Select
            End If
             
            'Instead of just paste, you can also paste as link, as values etc.
            ActiveSheet.Paste
         
        End If
         
    Next
End Sub

来源:

Merge Sheets: Easily Copy Excel Sheets Underneath on One Sheet!

ESP32 S2 Mini

最近发现了一款非常便宜的 ESP32 S2 开发板:wemos 的 ESP32 S2 MINI,价格在12元。这个建议甚至低于 Atmel 328P 芯片,更重要的是这个是开发板直接可以下载代码无需额外 USB转串口设备。

官方网站是 https://www.wemos.cc/en/latest/s2/s2_mini.html#

  • based ESP32-S2FN4R2 WIFI IC
  • Type-C USB
  • 4MB Flash
  • 2MB PSRAM
  • 27x IO
  • ADC, DAC, I2C, SPI, UART, USB OTG
  • Compatible with LOLIN D1 mini shields
  • Compatible with MicroPython, Arduino, CircuitPython and ESP-IDF
  • Default firmware: MicroPython

对于一般的开发已经足够用了。

在使用 Arduino 开发时,需要特别注意选择为  LOLIN S2 MINI 开发板,具体如下:

引脚定义在下面这个文件中(ESP32的大多数引脚都可以自行定义,但是为了更好的兼容,个人建议使用预定义值)

C:\Users\UserName\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.6\variants\lolin_s2_mini\pins_arduino.h

Arduino ESP32 I2C Slave 的例子

Arduino 作为 I2C Slave 算是比较冷门的使用方式,下面是一个实际的例子:

#include <Wire.h>
 
byte i2c_rcv=0;               // data received from I2C bus
 
void setup() {
  Wire.begin(0x08);           // join I2C bus as Slave with address 0x08
 
  // event handler initializations
  Wire.onReceive(dataRcv);    // register an event handler for received data
  Wire.onRequest(dataRqst);   // register an event handler for data request
  Serial.begin(115200);
}
 
void loop() {
}
 
//received data handler function
void dataRcv(int numBytes) {
  Serial.print("Slave Received ");
  Serial.print(numBytes);
  Serial.println("Bytes");
  while (Wire.available()) { // read all bytes received
    i2c_rcv = Wire.read();
    Serial.print("[");
    Serial.print(i2c_rcv);
    Serial.print("]");
  }
  Serial.println("");
}
 
// requests data handler function
void dataRqst() {
    Wire.write(i2c_rcv); // send potentiometer position
    Serial.print("Slave send ");
    Serial.print(i2c_rcv,HEX);
}

运行之后,Arduino 作为一个地址为 0x08 的I2C设备。当它收到 Master 发送过来的数据,会进入 void dataRcv(int numBytes)  函数,然后将收到的数据输出到串口上;当它收到 Master 发送的读请求,会进入void dataRqst() 函数,将之前收到的数据返回给 Master 。

试验使用 Leonardo 板子,使用调试器发送 10 17 表示对 0x08 地址的设备发送 0x17,之后调试器发送 11 01 表示从 0x08 设备读取一字节数据。

ESP32 I2C Slave Mode

ESP32 目前支持 I2C 的 Slave Mode ,就是说可以作为一个 I2C 设备存在。

具体的介绍在下面能看到,是一个大佬写了一个 Slave Mode 的库,后来整合到了官方 Release 中。

https://github.com/espressif/arduino-esp32/pull/5746/commits/f9f70d2f73d16f7fb50f59e05323cd041acce830

安装完最新的ESP32 Arduino支持包之后,可以在下面的路径中看到:

C:\Users\UserName\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.6\libraries\Wire

其中的 WireSlave 就是实现一个 I2C Slave 的完整例子。

充放电、升压、一键开机和断电验测试板

这是一个电路测试板,能够实现下面的功能:

  1. 锂电池充放电管理,5V输出
  2. 按键开机,MCU 控制关机(自己给自己切断电源)
  3. 5V升压

整体电路图如下,可以看到分成四部分:锂电池充放电(第一部分),一键开机和MCU关机(第二部分)、5V升压(第三部分)和锂电池座(第四部分):

首先介绍第一部分,核心是 IP5306模块,接口部分定义如下:

引脚功能介绍
1USBINPin1是 USBIN ,连接 MCU ,设置为 INPUT_PULLUP,当USB充电时,会被拉低;当没有充电时会设置为高。从而MCU通过读取这个GPIO 能够得知当前是否有正在进行充电。IP5306 没有反映当前充电状态的引脚,所使用这个设计来获得充电状态;
2OUT1Pin2 是5V输出。当没有对外供电时,这里有4V 左右的电压输出;当外部插入取电时,或者SW2按钮按下时,这里会有5V输出;
3GND 
4BATIN连接电池正极输入;
5GND 
6BAT_ADC一个分压输出,MCU 的 ADC 能够获得当前的电池电压信息

此外这部分的 SW2是一个按钮,短按可以让 IP5306输出5V,再次按下会切断输出,如果负载<50ma,那么 45s之后也会停止输出

接下来是第二部分:

这部分根据【参考1】而来,很好用。接口定义如下:

引脚功能介绍
1IN2输入(第一部分输出的OUT1 可以接入这里)
2OUT2控制后的输出
3IN2同上 IN2
4OUT2同上 OUT2
5CTRL输出控制脚,初始时MCU 需要通过 CTRL对这里输入一个高电平,当需要断电时CTRL输入低电平随即切断Pin2的输出
6GND 
   

这部分也带有一个按键,按下之后 Pin2 即可输出(需要按的稍微长一些,保证MCU 的 CTRL能够输出高电平)

第三部分,基于MT3608 芯片的5V升压设计,具体芯片 DataSheet可以在【参考2】看到,这个也是也是来自开源广场别人的设计(不过忘记是哪篇了,找了一下没找到),接口定义如下:

引脚功能介绍
1IN3电源输入,例如输入3.3V
2OUT3电源输出,5V
3IN3同上IN3
4OUT3同上OUT3
5GND 
6GND 

简单功耗测量,测试方法是在电池串联万用表测量电流。5V对ESP32 S3 板【参考3】输出时,电流在90ma左右;MCU 切断供电后,电流在5ma左右;经过45s后IP5306自动断电后电流在0.04ma左右。

成品

 工作视频在:

上述主要芯片除了电容电阻,其余都是购买自立创商城,有兴趣的朋友可以实验。

参考:

  1. https://oshwhub.com/armxu/kai-ji-zi-dong-guan-ji-dian-lu
  2. https://atta.szlcsc.com/upload/public/pdf/source/20161110/1478743351706.pdf
  3. https://mc.dfrobot.com.cn/thread-315546-1-1.html

SPI NOR 芯片测试板

为了测试 SPI NOR, 做了一个测试的小板,一面可以使用 SOP8 的SPI NOR, 一面可以使用 SOP16 的SPI NOR。对于目前我们使用的 SOP8 的 SPI NOR 来说,有8个引脚:

其中的 WP# 是写保护,低有效(拉低的时候无法写入);HOLD# 是暂停操作,比如,当前正在写入,HOLD# 拉低之后暂停写入,拉高后继续写入而无需重新发送写入命令。

SPINOR 测试板PCB
SPINOR 测试板成品

电路图和PCB下载:

STB下载