最近我的一台电脑出现 Win+ L 无法锁屏的问题。一番搜索下来有很多文章介绍在注册表中有一个控制的位置。但是我根据文章无法找到对应的位置,后来使用搜索功能在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 找到。
DisableLockWorkstation = 1 表示禁止锁屏功能,0 表示可以锁屏。
所以,出现同样问题时,不妨先查看上面的那个注册表位置。
最近我的一台电脑出现 Win+ L 无法锁屏的问题。一番搜索下来有很多文章介绍在注册表中有一个控制的位置。但是我根据文章无法找到对应的位置,后来使用搜索功能在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon 找到。
DisableLockWorkstation = 1 表示禁止锁屏功能,0 表示可以锁屏。
所以,出现同样问题时,不妨先查看上面的那个注册表位置。
我需要Python处理一个类似下面的 CSV文件:

于是编写代码先尝试读取之:
import csv
with open('Datax.CSV', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
print(row['Process Name'])
奇怪的事情发生了:我可以读取除了第一个”Time of Day”之外的所有 row 的值。当我写出来 print(row[‘Time of Day’]) 的时候会出现下面的错误:
C:\Users\Administrator\AppData\Local\Programs\Python\Python38>go2.py
Traceback (most recent call last):
File "C:\Users\Administrator\AppData\Local\Programs\Python\Python38\go2.py", l
ine 5, in <module>
print(row['Time of Day'])
KeyError: 'Time of Day'
测试数据文件在这里,代码在这里。有兴趣的朋友在看解释之前可以尝试自己解决一下。

接下来我各种猜测,默认是因为空格等等,都不是Root Cause。后来用代码将取得的 Key 打印一下:

惊奇的发现第一个是 ‘\ufeff”Time of Day”‘。用十六进制软件打开数据文件查看:

在文件头上有 EF BB BF ,这是CSV 文件的 Unicode 头。就是它引起了我们奇怪的问题。
知道了原因之后,可以通过尝试构造出一个相同的Key 来解决:
import csv
with open('Datax.CSV', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
print(row["\ufeff\"Time of Day\""])
运行正常:

结论:这是因为数据文件的 Unicode 头导致的……. 话说 Python 处理数据还真是方便,虽然调试和编写耗时不短,但是代码长度大大出乎我的意料。
本文是根据作者对于 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 是功能强大的虚拟机,支持了大量的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 有两种模式,一种是用来调试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 (记事本):

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
为了让你的开发板能够在 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 客服让他们直接提供离线文件。
最近在做一个热成像仪,需要将传感器的数据快速显示到屏幕上。屏幕是我之前试验过的使用 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<320;i++)
for (int j=0;j<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。
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 转接板会更容易观察到测试结果。
这次试验使用的 SD卡模块如下:

需要注意的是: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) {
}
运行结果如下:

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/