DFRobot TinkerNode NB-IoT 开发板安装指南(支持包的安装)

为了让你的开发板能够在 Arduino IDE中跑起来,需要在“开发板管理器”中安装这个板子的开发包。具体操作首先请参考下文:


https://mc.dfrobot.com.cn/thread-303693-1-1.html

如果你碰到下面这样的错误无法继续

”下载 http://downloads.arduino.cc/packages/package_index.json 时出错“,那么请参考本文的方法:

1.同样的打开首选项,将附加开发板管理器网址修改为

http://download.dfrobot.top/TinkerNode-NB-IoT/package_TinkerNode-NB-IoT_index.json

2.点击下面的直接编辑链接打开对应的目录,在我这边是

“C:\Users\Ziv2013\AppData\Local\Arduino15”

3.打开 preferences.txt 文件,找到下面两行:

target_package=arduino

target_platform=avr

修改为

target_package=TinkerNode_NB-IoT

target_platform=esp32

同时,将 package_TinkerNode-NB-IoT_index.json 文件丢在这个目录下

4.重启 Arduino ,打开“开发板管理器”,同样的你会遇到上面相同的错误,只是这次,出现错误之后输入 ti 能够在列表中看到需要的板子

选择安装,耐心等待即可:

耗时比较长,还经常出现不动的情况,可以用任务管理器Kill Javaw.exe 然后再次启动Arduino ,重复上面的操作可以继续下载(有断点续传)。当然,如果还是太慢无法下载完整的话,推荐直接联系 DFRobot 客服让他们直接提供离线文件。

Arduino UcgLib 快速显示

最近在做一个热成像仪,需要将传感器的数据快速显示到屏幕上。屏幕是我之前试验过的使用 ILI9341 主控 240×320分辨率的LCD。对应使用 Ucg 库来驱动之。遇到的问题是显示速度太慢。起初我使用ucg.drawPixel() 通过绘制点的方式来实现绘图。编写一个简单的代码来进行测试:

#include <SPI.h>
#include "Ucglib.h"

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/ 9, /*cs=*/ 10, /*reset=*/ 8,(HardwareSerial*)&Serial);

void setup(void) {
    Serial.begin(115200);
  delay(3000);
  ucg.begin(UCG_FONT_MODE_TRANSPARENT,&Serial);
  ucg.clearScreen();
  Serial.print("Starting");
}
void loop(void)
{
  ucg.setColor(0xFF,0,0);
  long int starttime=millis();
  for (int i=0;i<320;i++)
  for (int j=0;j<240;j++)
    {  
       ucg.drawPixel(j,i);
    }
  Serial.println(millis()-starttime);    
 }

可以看到,绘制  240×320的图像需要27.366秒。

经过阅读ILI9341 的 Spec,大致了解一下显示原理:将颜色信息写入主控的 RAM 中即可驱动屏幕实现显示。DrawPixel 函数应该是有额外的开销,所以导致速度非常缓慢。查看代码,具体实现绘制点的代码在ucg_dev_ic_ili9486.c 文件中:

  case UCG_MSG_DRAW_PIXEL:
    if (ucg_clip_is_pixel_visible(ucg) != 0)
    {
      uint8_t c[3];
      ucg_com_SendCmdSeq(ucg, ucg_ili9486_set_pos_seq);
      c[0] = ucg->arg.pixel.rgb.color[0];
      c[1] = ucg->arg.pixel.rgb.color[1];
      c[2] = ucg->arg.pixel.rgb.color[2];
      ucg_com_SendRepeat3Bytes(ucg, 1, c);
      ucg_com_SetCSLineStatus(ucg, 1); /* disable chip */
    }
return 1;

其中会将ucg_pgm_uint8_t ucg_ili9486_set_pos_seq结构体定义的数据发送出去:

const ucg_pgm_uint8_t ucg_ili9486_set_pos_seq[] =
    {
        UCG_CS(0), /* enable chip */
        UCG_C11(0x036, 0x008),
        UCG_C10(0x02a), UCG_VARX(8, 0x01, 0), UCG_VARX(0, 0x0ff, 0), UCG_A2(0x001, 0x03f), /* set x position */
        UCG_C10(0x02b), UCG_VARY(8, 0x01, 0), UCG_VARY(0, 0x0ff, 0), UCG_A2(0x001, 0x0df), /* set y position */
        UCG_C10(0x02c),                                                                    /* write to RAM */
        UCG_DATA(),                                                                        /* change to data mode */
        UCG_END()};

为了简单起见,直接用逻辑分析仪抓取发送的 SPI数据(实际上在整个过程中,Ucg Lib 无需使用 MISO Pin来接受屏幕的返回数据,完全用 MOSI 即可达成所有操作)。抓取结果如下:

我设定的是对 x=02,y=03 写入一个颜色为(F0,F2,F3)的点

36 08 //Memory Access Control(36)
2A 00 02 00 EF//Column Address Set( 2A) 
2B 00 03 01 3F //Page Address Set (2Bh)
2C F0 F2 F3 //Memory Write(2Ch)

根据 Spec ,2A 命令后面给出 x 的位置 02, 然后 00 EF  是 239 (屏幕 x方向范围 [0,239]); 2B 命令后面给出 y 的位置 03, 然后 01 3F  是 319 (屏幕 y方向范围 [0,329])。之后的 2Ch 表示开始对 Ram 填写。顺便说一下前面结构体中UCG_C11 这样宏的意思: UCG_Cmn 中,m表示后面 command 的数量,n 表示数据的个数。因为这个屏幕通过一个C/D Pin 来切换当前的命令是命令还是数据。通过这样的定义,可以知道如何切换这个C/D Pin。

有了上面的知识,接下来改进优化速度。

第一项是减少数据量,我们知道他们需要什么数据,直接发送而不再使用 Ucg 的结构体避免额外开销:

void loop(void)
{
  ucg.setColor(0xFF,0,0);
  long int starttime=millis();
  ucg.drawPixel(0,0);
    
  for (int i=0;i<320;i++)
  for (int j=0;j<240;j++)
    {  
     SPI.transfer (0);//主机SPI发送
     SPI.transfer (0xff);//主机SPI发送
     SPI.transfer (0);//主机SPI发送
     
    }
  Serial.println(millis()-starttime);    
}

这样测试下来绘制一帧花费时间 0.469秒。

第二项优化是 spi 的速度问题。示波器测量显示,上面代码默认配置为 8Mhz 的 SPI Clock,已经是 Uno 上最高的速度了。因此,对于 Uno 的板子来说已经是最快的速度了,如果用其他板子可以考虑是否达到 spi 的最高速度。

第三项是数据量的问题。前面对于每一个 Pixel 我们会发送三个Byte的 RGB 信息。实际上其中只有 18bits 是有效的

此外,还有一种16bits的模式:

这种模式下每次传输2Bytes即可。因此,如果能切换到这个模式下,那么对每个Pixel 只要2个Byte 即可,能节省33%。切换的命令是 3Ah ,我们需要将 DBI 设置为 5h 即可。

直接在ucg_dev_ic_ili9341.c 文件中添加:

const ucg_pgm_uint8_t ucg_ili9341_set_pos_seq[] = 
{
  UCG_CS(0),					/* enable chip */
  UCG_C11(ILI9341_PIXFMT, 0x55),
  UCG_C11( 0x036, 0x008),
  UCG_C10(0x02a),	UCG_VARX(0,0x00, 0), UCG_VARX(0,0x0ff, 0), UCG_A2(0x000, 0x0ef),					/* set x position */
  UCG_C10(0x02b),	UCG_VARY(8,0x01, 0), UCG_VARY(0,0x0ff, 0), UCG_A2(0x001, 0x03f),		/* set y position */
  UCG_C10(0x02c),							/* write to RAM */
  UCG_DATA(),								/* change to data mode */
  UCG_END()
};

修改代码再次试验

void loop(void)
{
  ucg.setColor(0xFF,0,0);
  long int starttime=millis();
  ucg.drawPixel(0,0);
    
  for (int i=0;i&lt;320;i++)
  for (int j=0;j&lt;240;j++)
    {  
     SPI.transfer (0);//主机SPI发送
     SPI.transfer (0x0F);//主机SPI发送
     //SPI.transfer (0xFF);//主机SPI发送
    }
  Serial.println(millis()-starttime);    
  
}

屏幕会变成绿色,运行时间 320ms。和前面相比减小1/3的时间开销。但是总体颜色数量少了如果设计上需要丰富的颜色层次那么这种模式是不适合的。

原本我打算将这个放置在对屏幕初始化的地方,但是一直有问题,后来忽然悟道UcgLib 里面对于我这个屏幕使用的都是3byte的颜色模式。如果初始化为 2Byte , 那么很多函数是无法工作的。因此,直接修改 DrawPixel 函数这里是最简单的试验方式。

如果你使用 Teensy 3.1 那么还可以使用 ILI9341_t3 的 Library,作者针对 Teensy 做了进一步的优化,速度更快。但是我在试验中发现可能是因为初始化缺少了一些必要指令,这个库有时候无法正常显示。

关于几个硬盘省电技术的介绍

最近看了一下关于 SATA 省电方面的技术文档,总结一下遇到的名词术语。

首先是经常能在文档上看到的 Partial 和 Slumber。简单的说这两种都是 SATA PHY 休眠的状态。

Partial: 功耗大约75mW, 比Standby状态功耗约200mW低了一多半, 从Partial状态退回Phy Rdy的时间不能超过10us.

Slumber: 功耗大约25mW, 比Partial状态功耗更低,从Slumber状态退回Phy Rdy的时间也相对较长,但不能超过10ms。

可以看到Partial和Slumber的差别在于后者睡得更深功耗更低,同时回来越慢。

Host与Device均可以发起请求 (HIPM/DIPM),进入Partial/Slumber。

  • HIPM = Host Initiated Link Power Management 
  • DIPM = Device Initiated Link Power Management

HOST  能够发起是因为HOST知道何时有读取写入请求,后面没有操作即可要求“睡一会”;DEVICE 同样能够发起是因为设备自己知道对于上面的命令什么时候能够完成操作,完成要求之后马上要求“睡一会”来省电。

此外,现在的PCH上还能看到DEVSLP Pin。在Partial/Slumber的省电模式下,硬盘都必须让自己的传输电路保持在工作状态,以便在SATA Host需要的时候能把它唤醒,这样的话硬盘需要消耗一定电力随时响应唤醒。DevSlp就是把这个传输电路完全关掉,然后专门加了一个低速的Pin来负责接收唤醒通知。

从上图可以看到,打开 DEVSLP支持后功耗降低到了5mW左右,而Exit Latency进一步加长20ms级别。

再进一步,虽然DEVSLP 告诉硬盘彻底休息,但是VCC仍然存在已久会有一些电源消耗。这时候,再引入RTD D3Cold 的概念。通常是通过某个 GPIO来实现的。当确定硬盘可以进入D3 Cold 时,主机会通过拉GPIO的方式彻底切断硬盘供电,这样硬盘就完全不需要消耗任何电力了。

我个人不喜欢 RTD3 的设计,因为从实践上来说,在断电上电过程中经常会遇到各种问题,很可能是设备本身不支持。例如:某个设备上电100ms后才能正常工作,而系统设计刚好是100ms左右即开始对其进行访问,卡在这样的点上之后很容易出现试验几百次才会fail一次的情况也让Debug变得非常困难。

从上面也可以看出,省电技术对于醒来的时间有着严格的要求。微软本身是没有打算在普通机械硬盘上实现 Modern StandBy 功能的,因为对于带有机械部件的设备来说这样的要求有些强人所难。

如果你需要进行这方面的试验,推荐使用 http://www.lab-z.com/chdevslp/ 介绍的TxBench 检查设备已经相应功能已经打开。

有兴趣的朋友推荐继续阅读,本文内容来自下面文章

1. https://mp.weixin.qq.com/s?__biz=MzIwNTUxNDgwNg==&mid=2247484118&idx=1&sn=7ff7934ae45c6f3e92567f863faf0dc1&chksm=972ef38fa0597a99b556e2181a2d12a17c353fc2f8aedc1e733a9495a23515c77b92c365003b&scene=21#wechat_redirect SATA系列专题之五:Link Power Management解析

2. http://www.ssdfans.com/blog/2016/10/01/sata-devslp%E6%98%AF%E4%BB%80%E4%B9%88%EF%BC%9F/  SATA DevSlp是什么?

3. https://sata-io.org/sites/default/files/documents/SATADevSleep-and-RTD3-WP-037-20120102-2_final.pdf

如果有任何错,欢迎在留言中指出。

202006024 补充:

DEVSLP 和 RTD3 是独立的,即便当前不支持 DEVSLP也可以实现 RTD3 断电功能。特别注意的是:进入 MS 之后3分钟才会使用 RTD3 切断硬盘供电,因此在测试的时候尽量时间长一些。 另外,断电需要请 HW 告知测试点,如果有可能使用带有 LED 的 SATA2M.2 转接板会更容易观察到测试结果。

在 Arduino Leonardo上使用SD卡

这次试验使用的 SD卡模块如下:

https://gd1.alicdn.com/imgextra/i4/31295113/O1CN01Xdd9Xs1ndnceTidLs_!!31295113.jpg

需要注意的是:SD卡使用的是 3.3V电平,所以不要直接把Arduino 接在SD卡上有可能导致损坏。

控制接口:共6个引脚(GND、VCC、MISO、MOSI、SCK、CS),GND为地,VCC为供电电源,MISO、MOSI、SCK为SPI总线,CS为片选信号脚;

对于Leonardo 来说,SPI 通讯接口在 ICSP 上,定义如下:

因此,需要将二者按照定义连接在一起,SD 卡模块上的CS接至D4 Pin。之后,运行 Example 中的CardInfo即可:

/*
  SD card test

 This example shows how use the utility libraries on which the'
 SD library is based in order to get info about your SD card.
 Very useful for testing a card when you're not sure whether its working or not.

 The circuit:
  * SD card attached to SPI bus as follows:
 ** MOSI - pin 11 on Arduino Uno/Duemilanove/Diecimila
 ** MISO - pin 12 on Arduino Uno/Duemilanove/Diecimila
 ** CLK - pin 13 on Arduino Uno/Duemilanove/Diecimila
 ** CS - depends on your SD card shield or module.
 		Pin 4 used here for consistency with other Arduino examples


 created  28 Mar 2011
 by Limor Fried
 modified 9 Apr 2012
 by Tom Igoe
 */
// include the SD library:
#include <SPI.h>
#include <SD.h>

// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root;

// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
// MKRZero SD: SDCARD_SS_PIN
const int chipSelect = 4;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("\nInitializing SD card...");

  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card inserted?");
    Serial.println("* is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    return;
  } else {
    Serial.println("Wiring is correct and a card is present.");
  }

  // print the type of card
  Serial.print("\nCard type: ");
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:
      Serial.println("SD1");
      break;
    case SD_CARD_TYPE_SD2:
      Serial.println("SD2");
      break;
    case SD_CARD_TYPE_SDHC:
      Serial.println("SDHC");
      break;
    default:
      Serial.println("Unknown");
  }

  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    return;
  }


  // print the type and size of the first FAT-type volume
  uint32_t volumesize;
  Serial.print("\nVolume type is FAT");
  Serial.println(volume.fatType(), DEC);
  Serial.println();

  volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
  volumesize *= volume.clusterCount();       // we'll have a lot of clusters
  volumesize *= 512;                            // SD card blocks are always 512 bytes
  Serial.print("Volume size (bytes): ");
  Serial.println(volumesize);
  Serial.print("Volume size (Kbytes): ");
  volumesize /= 1024;
  Serial.println(volumesize);
  Serial.print("Volume size (Mbytes): ");
  volumesize /= 1024;
  Serial.println(volumesize);


  Serial.println("\nFiles found on the card (name, date and size in bytes): ");
  root.openRoot(volume);

  // list all files in the card with date and size
  root.ls(LS_R | LS_DATE | LS_SIZE);
}


void loop(void) {

}

运行结果如下:

Windows下打开HIPM和DIPM 的方法

Microsoft在设计 ModernStandy 功能的时候没有想过在普通的机械硬盘(HDD)上使用这个功能。于是,正常情况下,如果当前系统有HDD的话是无法进入MS的。这时候需要手工打开 DIPM 和 HIPM 。具体方法如下:

1.打开 Power Options ->Edit Plan Settings

2.编辑注册表Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\0012ee47-9041-4b5d-9b77-535fba8b1442\0b2d69d7-a2a1-449c-9680-f91c70521c60将这个位置的 Attributes 设置为 2

3.再次打开Power Options ->Edit Plan Settings 可以看到目前出现选项可以选择当前的模式。

与之相反,如果你想提高当前硬盘的性能,可以考虑关闭 DIPM和 HIPM。

本文参考 https://mywindowshub.com/add-ahci-link-power-management-hipmdipm-power-options-windows-10/

CP2102 USB转TTL 串口分析

CP210x是Silicon Labs 公司出品的一系列USB转串口芯片。目前这一系列有:CP2101, CP2102, CP2103, CP2104, CP2105, 和CP2108。其中不同版本差别在下图可以看到【参考1】:

最近我拿到一个 CP2102 的USB转串口卡,发现Windows10可以通过Windows Update直接安装驱动无需额外下载。另外,它的官方网站提供了大量的资料和参考给人感觉很好。我的目标是使用 Arduino 来直接驱动,因此还需要进行一些额外的研究。非常遗憾,之前介绍的USBlyser抓包结果让人看起来一头雾水,最后只能使用USB逻辑分析仪抓包。一端通过 Arduino 按照 9600,8,None,1,None 模式发送。另外一端使用串口工具读取。数据是单独的 A  2秒后发送“string from www.lab-z.com” 字符串。对照网站提供的AN571可以解读全部数据。

1.打开/关闭串口

2.查询CTS/RTS等等线的状态

设备返回状态信息

3.查询 Serial Status

返回一个19Bytes的结构体

4.设置通讯参数

0x0800 : 1 Stop bit, None Parity, Data bits=8

5.设置流控制

发送一个 20Bytes 的数值,上面发送多次的原因是前两次被NAK 了,第三次才成功。

6.设置特殊控制字符(实际上我们不需要)

7.设置波特率

这个 0x01C200 是 115200 的波特率。紧接着又设置了一次,这次是 9600

8.设置 Modem 握手方式(我们没有用),这里用了 0x0200, 设置了 RTS

随后又设置用 DTS

参考:

1. https://www.silabs.com/documents/public/application-notes/an976-cp2102-3-4-9-to-cp2102n-porting-guide.pdf

Step to UEFI (213)EFI 的WinPE 格式研究

这一系列文章并不是按照“由浅入深”的原则排列的,更多的是按照“任务驱动”的方式编写的。就是按照:提出问题,解决问题,为什么能解决问题,是否还有其他解决问题的方法,原理探究的方式进行排列的。这种方式可以帮助你快速掌握编程技术,当然要想实现这个目标更重要的是要亲手操作研读代码。

前面的文章中介绍过 EFI 文件格式的一些简单知识,这里会对一只EFI 做完整的分析,标明每一个字节的含义。

首先,我们选择实验的目标是 \AppPkg\Applications\Hello

#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
  )
{
  Print(L"Hello there fellow Programmer.\n");
  Print(L"Welcome to the world of EDK II.\n");

  return(0);
}

这个代码功能很简单,就是显示两行字符串。生成的代码为 8,160 (0x1FE0)bytes大小。

特别的,要在对应的INF文件中加入下面的语句保证生成 COD文件。

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

接下来使用 SFF 工具直接分析 EFI:

1.从 0 到 0x3C 是一个 Dos Header。这个只是作为兼容性的结构存在并没有任何功能。

这个结构可以在\MdePkg\Include\IndustryStandard\PeImage.h 看到定义

///
/// PE images can start with an optional DOS header, so if an image is run
/// under DOS it can print an error message.
///
typedef struct {
  UINT16  e_magic;    ///< Magic number.
  UINT16  e_cblp;     ///< Bytes on last page of file.
  UINT16  e_cp;       ///< Pages in file.
  UINT16  e_crlc;     ///< Relocations.
  UINT16  e_cparhdr;  ///< Size of header in paragraphs.
  UINT16  e_minalloc; ///< Minimum extra paragraphs needed.
  UINT16  e_maxalloc; ///< Maximum extra paragraphs needed.
  UINT16  e_ss;       ///< Initial (relative) SS value.
  UINT16  e_sp;       ///< Initial SP value.
  UINT16  e_csum;     ///< Checksum.
  UINT16  e_ip;       ///< Initial IP value.
  UINT16  e_cs;       ///< Initial (relative) CS value.
  UINT16  e_lfarlc;   ///< File address of relocation table.
  UINT16  e_ovno;     ///< Overlay number.
  UINT16  e_res[4];   ///< Reserved words.
  UINT16  e_oemid;    ///< OEM identifier (for e_oeminfo).
  UINT16  e_oeminfo;  ///< OEM information; e_oemid specific.
  UINT16  e_res2[10]; ///< Reserved words.
  UINT32  e_lfanew;   ///< File address of new exe header.
} EFI_IMAGE_DOS_HEADER;

使用 SFF 工具可以方便的看到每个项目释义:

可以看到大多数信息都是 00,从前面的实验也可以知道,这是编译过程中被我们的工具擦掉的。

2.接下来是 NT Header, 分为两个部分,一个是 File Header 另一个是 Optional Header.

这个结构同样在  \MdePkg\Include\IndustryStandard\PeImage.h 有定义,可以看到其中是有2部分的。

///
/// @attention
/// EFI_IMAGE_HEADERS64 is for use ONLY by tools.
///
typedef struct {
  UINT32                      Signature;
  EFI_IMAGE_FILE_HEADER       FileHeader;
  EFI_IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} EFI_IMAGE_NT_HEADERS64;

2.1 先看一下 File Header,从 0xBC到 0xCE.

///
/// COFF File Header (Object and Image).
///
typedef struct {
  UINT16  Machine;
  UINT16  NumberOfSections;
  UINT32  TimeDateStamp;
  UINT32  PointerToSymbolTable;
  UINT32  NumberOfSymbols;
  UINT16  SizeOfOptionalHeader;
  UINT16  Characteristics;
} EFI_IMAGE_FILE_HEADER;

其中的 Characteristics 提供了一些基本信息:

2.2 Optional Header 从 0xD0到0x1B4,其中有一些比较重要的信息

比如,这里给出了AddressOfEntryPoint 就是EFI文件的代码入口。

下面是其中的 DataDirectory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES] 定义:

///
/// Optional Header Standard Fields for PE32+.
///
typedef struct {
  ///
  /// Standard fields.
  ///
  UINT16                    Magic;
  UINT8                     MajorLinkerVersion;
  UINT8                     MinorLinkerVersion;
  UINT32                    SizeOfCode;
  UINT32                    SizeOfInitializedData;
  UINT32                    SizeOfUninitializedData;
  UINT32                    AddressOfEntryPoint;
  UINT32                    BaseOfCode;
  ///
  /// Optional Header Windows-Specific Fields.
  ///
  UINT64                    ImageBase;
  UINT32                    SectionAlignment;
  UINT32                    FileAlignment;
  UINT16                    MajorOperatingSystemVersion;
  UINT16                    MinorOperatingSystemVersion;
  UINT16                    MajorImageVersion;
  UINT16                    MinorImageVersion;
  UINT16                    MajorSubsystemVersion;
  UINT16                    MinorSubsystemVersion;
  UINT32                    Win32VersionValue;
  UINT32                    SizeOfImage;
  UINT32                    SizeOfHeaders;
  UINT32                    CheckSum;
  UINT16                    Subsystem;
  UINT16                    DllCharacteristics;
  UINT64                    SizeOfStackReserve;
  UINT64                    SizeOfStackCommit;
  UINT64                    SizeOfHeapReserve;
  UINT64                    SizeOfHeapCommit;
  UINT32                    LoaderFlags;
  UINT32                    NumberOfRvaAndSizes;
  EFI_IMAGE_DATA_DIRECTORY  DataDirectory[EFI_IMAGE_NUMBER_OF_DIRECTORY_ENTRIES];
} EFI_IMAGE_OPTIONAL_HEADER64;

3.紧接着是Section Headers[x]

定义在 \MdePkg\Include\IndustryStandard\PeImage.h

///
/// Section Table. This table immediately follows the optional header.
///
typedef struct {
  UINT8 Name[EFI_IMAGE_SIZEOF_SHORT_NAME];
  union {
    UINT32  PhysicalAddress;
    UINT32  VirtualSize;
  } Misc;
  UINT32  VirtualAddress;
  UINT32  SizeOfRawData;
  UINT32  PointerToRawData;
  UINT32  PointerToRelocations;
  UINT32  PointerToLinenumbers;
  UINT16  NumberOfRelocations;
  UINT16  NumberOfLinenumbers;
  UINT32  Characteristics;
} EFI_IMAGE_SECTION_HEADER;

我们这次分析的EFI 文件中各个节如下:

.text  执行代码的节;

.rdata 保存常量数据的节;

.data 保存数据的节,这个对应C语言中以初始化的全局变量数据;

XXXX 一个名称全部为空的节;

.pdata和 .xdata都存放的是异常处理相关的内容。

4.随后是Relocation Directory

对应在  \MdePkg\Include\IndustryStandard\PeImage.h 有如下定义:

///
/// Relocation format.
///
typedef struct {
  UINT32  VirtualAddress;
  UINT32  SymbolTableIndex;
  UINT16  Type;
} EFI_IMAGE_RELOCATION;

5.下面是 Debug  Directory 在  \MdePkg\Include\IndustryStandard\PeImage.h 有如下定义

///
/// Debug Directory Format.
///
typedef struct {
  UINT32  Characteristics;
  UINT32  TimeDateStamp;
  UINT16  MajorVersion;
  UINT16  MinorVersion;
  UINT32  Type;
  UINT32  SizeOfData;
  UINT32  RVA;           ///< The address of the debug data when loaded, relative to the image base.
  UINT32  FileOffset;    ///< The file pointer to the debug data.
} EFI_IMAGE_DEBUG_DIRECTORY_ENTRY;

Section Headers 后面就是紧密排列着的每个 Section 的内容了。

WDTF的安装

如果需要自动测试 Modern Standby 那么需要安装 WDTF (Windows Device Testing Framework)【参考1】, 这个事情在之前的 ZVIRTUALBATTERY工具上有提到过【参考1】,当时我将一个 WDTF 安装包集成到了这个工具中,在安装工具的时候会自动安装。

实际上微软的推荐是需要安装和当前版本相匹配的 WDTF。因此这里就介绍一下如何单独安装。

1.检查当前 Windows 版本,命令  WinVer

2.根据这个版本,找对应的 WDK,通常是 ISO格式

3.可以选择直接挂接,比如下面直接把ISO 虚拟成一个光驱

4.使用命令  E:\Installers>msiexec /i “Windows Driver Testing Framework (WDTF) Runtime Libraries-x64_en-us.msi” 即可安装

5.保险起见,可以到已经安装程序中查看是否有如下程序

参考:

1. https://docs.microsoft.com/en-us/windows-hardware/drivers/wdtf/

2. http://www.lab-z.com/zvb/

=========================================================

2025年4月22日 更新的离线 WDTF 安装包

Step to UEFI (212)WinPE 格式:RDATA 节的作用

前面的文章中提到了 .rdata 是用于保存常量数据的节,这里对这个位置进行研究。

首先从  \Build\AppPkg\DEBUG_VS2015x86\X64\AppPkg\Applications\Hello\Hello\OUTPUT\Hello.map 看到 Section 分布:

Start         Length     Name                   Class
 0001:00000000 000012f7H .text$mn                CODE
 0002:00000000 000006f4H .rdata                  DATA
 0002:000006f4 00000110H .rdata$zzzdbg           DATA
 0003:00000000 00000020H .data                   DATA
 0003:00000020 00000020H .bss                    DATA
 0004:00000000 000000a8H .pdata                  DATA
 0005:00000000 00000084H .xdata                  DATA

对应的,在下面有这个段对应偏移的内容:

比如,其中有下面这样一条:

0002:00000690       mHexStr                    0000000000001c50     BasePrintLib:PrintLibInternal.ob

我们查看 \MdePkg\Library\BasePrintLib\PrintLibInternal.c有如下定义:

GLOBAL_REMOVE_IF_UNREFERENCED CONST CHAR8 mHexStr[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

对应代码:

/**
  Internal function that convert a number to a string in Buffer.

  Print worker function that converts a decimal or hexadecimal number to an ASCII string in Buffer.

  @param  Buffer    Location to place the ASCII string of Value.
  @param  Value     The value to convert to a Decimal or Hexadecimal string in Buffer.
  @param  Radix     Radix of the value

  @return A pointer to the end of buffer filled with ASCII string.

**/
CHAR8 *
BasePrintLibValueToString (
  IN OUT CHAR8  *Buffer,
  IN INT64      Value,
  IN UINTN      Radix
  )
{
  UINT32  Remainder;

  //
  // Loop to convert one digit at a time in reverse order
  //
  *Buffer = 0;
  do {
    Value = (INT64)DivU64x32Remainder ((UINT64)Value, (UINT32)Radix, &Remainder);
    *(++Buffer) = mHexStr[Remainder];
  } while (Value != 0);

  //
  // Return pointer of the end of filled buffer.
  //
  return Buffer;
}

结合代码可以出这个是用来将十进制数值转化为十六进制字符串的,对于一般的打印处理是会用到的。

使用 CFF 打开 EFI 文件,查看 .rdata Section(红色框中),可以在其中看到定义的字符串:

如果把上面的 0x34修改为0x46,再次运行 Hello.efi 就会用不同的值显示出来。修改之前运行结果如下:

修改之后运行的结果:

因此,可以确定在 .rdata 中存放了mHexStr[]用于十进制对十六进制显示的转换。这就是 .rdata存放的常量的作用。