Modern Standby简介

本文是根据作者对于 Modern Standby 的理解写成的,在某些概念上会和定义有冲突,希望读者特别注意如果有冲突以官方文档为准。

CPU 作为PC的大脑通常是最大的耗电大户。与之类似,人脑重量只占体重的2%,却会消耗整个身体所需能量的17%和氧气的20%。所以, CPU省电是整个PC系统降低功耗的关键。随着笔记本电脑的普及,如何降低功耗延长待机时间成为一个突出需求。在进一步介绍之前需要提及一下之前的休眠。在ACPI 规范中定义了一些 PC 省电的状态。比如下图所示的S1 S3 和S4,当处于 S0 状态时,CPU处于工作状态:

如果能让CPU 在S0 的时候尽量降低功耗,那么可以节省出很多电力。此外,人们希望省电不要影响操作体验。为此,Intel 和微软携手一起推出了 Modern Standby。据说这个概念来源自手机:对于用户来说,按下按键锁屏,屏幕黑掉之后系统会做一些动作来省电。具体的操作并不需要用户操心,相比PC用户,Legacy Sleep需要用户操作选择S1 S3 或者S4;手机用户按下按钮马上屏幕可以亮起来继续之前的使用。于是,Intel 和微软也希望能在PC上实现类似功能。比如,用户使用Windows平板电脑按下电源关闭屏幕就开始省电,再次按下就马上恢复使用,这样就能做到在不影响客户体验的情况下节省电力。

除了Modern Standby(缩写 MS,还有些人会缩写成 MSB),该技术还可以被称作:Connected Standby (缩写 CS),S0ix或者 Sleep S0。这里特别提一下用 Connected Standby称呼 Modern Standby 功能并不恰当。Modern Standby包括了2种,一种是 Connected Standby,最主要的意思是睡眠的时候仍然联网,比如:收邮件。进入CS 之后后台仍然能够收邮件,唤醒之后马上就能看到新邮件。还有一种是 Disconnected Standby,就是睡眠的时候断网。很明显,因为有联网的操作,经常让系统睡的不踏实,人们也不知道睡眠的时候 Windows 究竟在后台干嘛。甚至很多时候 Windows后台升级操作会严重影响休眠,表面看着睡下去,但是实际上 CPU 并没有睡下去依然在工作。

此外,微软和 Intel 对于 Modern Standby 理解上有差别。前者以关闭屏幕为标志,屏幕一灭就是开始睡;后者是以 SLPS0 信号拉低(Assert)为标志的。所以在测试的时候也经常发现 Software 报告的 Sleep 会高于 Hardware Sleep 的情况。

SLPS0 是存在 CPU上的实体线路,与之类似的是主板上的 Sleep S3 信号,还有Sleep S4 信号。CPU 通过SLPS0告诉外围设备:我现在睡 Modern Standby了。比如,EC看到了这个信号被拉低就知道系统进入 MS,可以做一些相应动作。

在前期的研发过程中,示波器测量SLPS0 信号是最准确的判定是否进入 MS 的方法。使用示波器,可以看到正常情况下 SLPS0 信号是每隔一段时间起来一次的。这就是CPU 起来看看是否有需要他处理的事件,如果有的话处理一下,处理完成继续睡,如果没有继续睡。还有一种特殊情况,比如 Skype 能够在MS的状态下随时接收呼叫,这个功能是网卡配合,将一些特殊的 Pattern 注册到网卡中。当系统进入 MS 后,网卡保持工作,这个工作不需要CPU参与;如果发生呼叫,远端服务器会将这个 Pattern 发送到网卡上,网卡发现匹配后唤醒CPU来进行处理。

从BIOS 的角度来说 Connected Standby 调试很困难,因为基本上所有的软件调试都要依赖CPU 来处理,CPU 睡了也没人处理。另外,CPU 进入 Connected Standby 之后 CPU并没有准确的停止位置。比如:Sleep S1 我们能在 ACPI 中拦截到;Sleep S4 我们知道CPU 是关掉了。但是对于Connected Standby 没人知道当下 CPU 的 EIP 指向何处。产用的调试方法是运行 Intel 专用调试软件 Powerhouse Mountain抓取Log,但是对于一些硬件问题这种方法无能为力;更复杂的问题需要使用 Intel 专用设备来调试。

QEMU 的重新编译(上)

QEMU 是功能强大的虚拟机,支持了大量的CPU 同时可以直接使用 EDK2 作为启动BIOS。之前我介绍过如何编译VirtualBox 的代码以便修改它使用的ASL Code,这次介绍一些如何重新编译 QEMU,具体方法如下(方法来自https://my.oschina.net/ejoyc/blog/1587798):

我是在 VirtualBox 的虚拟机中测试的,操作系统是Win10 16299。

[1] 安装msys2
    打开msys2官网http://www.msys2.org/下载x64版的msys2, 安装到目录c:\msys64
    Python环境也是需要的, 安装Python2.7.12到目录D:\Python27

[2] 更新源
    进入目录c:\msys64\etc\pacman.d
    在mirrorlist.msys的前面插入
        Server = http://mirrors.ustc.edu.cn/msys2/msys/$arch
    在mirrorlist.mingw32的前面插入
        Server = http://mirrors.ustc.edu.cn/msys2/mingw/i686
    在mirrorlist.mingw64的前面插入
        Server = http://mirrors.ustc.edu.cn/msys2/mingw/x86_64

[3] 更新msys2
    pacman -Syu

  这里会有一次异常,具体现象是出现一个错误提示然后就不动了。直接叉掉窗口之后重新启动虚拟机再次执行上面这个命令,等待完成即可。
    pacman -Su

[4] 准备编译环境

       将代码放在 c:\msys64\home 目录下
    通过“MSYS2 MinGW 64-bit”打开msys2终端: 

       Cd..   

       这步之后会进入 home 目录下   
    PATH=/c/Python27:/c/Python27/DLLs:$PATH
    pacman -S base-devel git
    pacman -S mingw-w64-x86_64-binutils mingw-w64-x86_64-crt-git
    pacman -S mingw-w64-x86_64-headers-git mingw-w64-x86_64-gcc-libs
    pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-gdb mingw-w64-x86_64-make
    pacman -S mingw-w64-x86_64-tools-git mingw-w64-x86_64-pkg-config
    pacman -S mingw-w64-x86_64-winpthreads-git mingw-w64-x86_64-libwinpthread-git
    pacman -S mingw-w64-x86_64-winstorecompat-git mingw-w64-x86_64-libmangle-git
    pacman -S mingw-w64-x86_64-pixman mingw-w64-x86_64-SDL2 mingw-w64-x86_64-glib2
    pacman -S mingw-w64-x86_64-capstone

上述命令中除了第一个其他都能很快完成,当然也和你的网络有很大关系。当你不确定是否卡死的时候,可以用任务管理器查看 CPU占用率确定。

[5] 编译QEMU

下载最新的 Qemu 我用的是qemu-4.2.0.tar

  tar xvf qemu-4.2.0.tar  (原文提供的是tar.xy 文件,使用这个命令tar xvJf qemu-2.10.1.tar.xz)
   cd ./qemu-2.10.1.tar
   mkdir build
    cd build
     ../configure –prefix=/qemu –target-list=x86_64-softmmu –enable-sdl –enable-tools
    make
    make install

我们只需要 X64的虚拟机,所以target-list 指定 x86_64-softmmu 即可。

[6] 使用qemu
   此时qemu就编译完成并安装到c:\msys2\qemu目录中。这里生成的 QEMU 没有办法直接运行,还需要打开 msys2 MinGW 64-bit 然后到生成的 qemu 目录中,用 ./qemu-system-x86_64w.exe 即可以运行。

整体来说上面的方法比较麻烦,但是操作下来还是比较顺利的。

上面所需的工具我都放在网盘中了,文件如下:

qemu-w64-setup-20200201.exe   这是QEMU网站提供的官方 Windows 64位安装程序

python-2.7.12.amd64.msi       Python 2.7  

msys2-x86_64-20190524.exe     MSYS2 安装文件 

qemu-4.2.0.tar                QEMU 4.0.0 源代码 

分享链接  https://pan.baidu.com/s/1_7pAiRKDZEstrccGWZBYeQ 密码: vpzr

WinDBG 调试 Notepad

WinDBG 有两种模式,一种是用来调试Kernel 的,比如,前面介绍过的双机通过USB3 互联,然后从一端调试另外一端;另外一种是用来调试User Mode的Application,比如,在本机打开WinDBG调试本机当前的 NotePad。

我原来的理解是,如果双机互联之后都能够调试Kernel Mode,那么调试远端机器上的Application应该手到擒来。但是多次试验都没有成功,后来咨询了一下天杀,他表示这条路不通如果需要调试Application 必须老老实实的用UserMode。

这次WinDBG试验的目标是调整Notepad 打开文件对话框,使得原来无法聚焦在编辑界面的对话框可以聚焦。意思是打开 Open对话框时无法再点击后面的主程序界面进行编辑。 本文根据 https://www.codeproject.com/Articles/1276860/Hacking-with-Windbg-Part-1-Notepad 翻译。

这次介绍的是用 WinDBG 来调试本机的 Notepad (记事本):

  1. 打开Notepad.exe,打开WinDBG, File -> Start debugging  -> Attach to process 选择 notepad.exe

Attach之后会自动停下来:

2.使用 x notepad!* 可以查看这个进程的 symbols,如下

3.查看一下带有 open 字样的函数,可以看到 notepad!ShowOpenSaveDialog ,这个可能性很大

5.在这个函数上设置断点,命令  bp notepad!ShowOpenSaveDialog,之后使用 bl 命令查看确定断点已经下好

6.使用 g 命令, 然后使用 Notepad file->open 调出对话框。这时会触发中断

7.此时再使用 u 反编译,u notepad!ShowOpenSaveDialog

同样,可以使用 uf 来一次性反编译整个notepad!ShowOpenSaveDialog

再使用 k 命令【参考1】查看堆栈信息,当前已经在 notepad!ShowOpenSaveDialog 中

7.接下来使用 ub 命令,我们前面使用过 u命令来反编译指定位置和之后的命令。这里ub这个命令是用来反编译指定位置当前指令之前的汇编代码,b这里就是代表向后查看(backward)的意思。【参考2】

              可以看到在调用 notepad!ShowOpenSaveDialog 之前有给参数赋值的命令。使用下面的命令直接修改指令,然后再次使用ub 检查,可以看到已经修改完成

0:008> a 00007ff6`ce9c2263
00007ff6`ce9c2263 xor ecx,ecx
DBGHELP: SharedUserData - virtual symbol module
00007ff6`ce9c2265 nop
00007ff6`ce9c2266 

8.使用 g 命令让 notepad 运行起来,这时当打开文件对话框仍然可以在编辑区域输入内容。

       总结:上面展示了几个 WinDBG 的基本操作,不过第7步的操作在我看起来有些莫名其妙,notepad!ShowOpenSaveDialog是Notepad 自定义的函数,不知道作者是如何得出 ecx 中是对话框参数的结论的,中间似乎缺少必要的推理过程。

参考:

1. https://blog.csdn.net/chenyujing1234/article/details/7743460#t11

15. k     命令用来显示当前线程的堆栈,如下

0:018> k

跟d命令一样,k后面也可以跟很多后缀,比如kb kp,kn,kv,kl等,这些后缀控制了显示的格式和信息。

栈指令k[b|p|P|v]

这四条指令显示的内容类似,但是每个指令都有特色;

KB显示三个参数;

Kp显示所有的参数,但需要Full Symbols或Private PDBSymbols支持。KP与Kp相似,只是KP将参数换行显示了;

Kv用于显示FPO和调用约定;

KD,用于显示Stack的Dump,在跟踪栈时比较有用。

这些指令区分大小。

2. https://www.cnblogs.com/developersupport/p/windbgcommand-u.html

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 的内容了。