Windows 版本对照表

来自 https://docs.microsoft.com/zh-cn/windows/release-information/

目前常见的版本有 19H1(1903) 19H2(1909) 20H1(2004) 20H1(。

所有的日期都按照 ISO 8601 格式列出:YYYY-MM-DD)上次更新时间: 2021-01-04

半年频道

Version服务选项上市日期OS build最后修订日期终止服务:家庭版、教育版、专业教育版、专业工作站版和 IoT 核心版服务终止日期:企业版、教育版和 IoT 企业版
20H2半年频道2020-10-2019042.6852020-12-082022-05-102023-05-09Microsoft 建议使用
2004半年频道2020-05-2719041.6852020-12-082021-12-142021-12-14
1909半年频道2019-11-1218363.12562020-12-082021-05-112022-05-10
1809半年频道2019-03-2817763.16372020-12-08服务终止2021-05-11
1809半年频道(定向)2018-11-1317763.16372020-12-08服务终止2021-05-11
1803半年频道2018-07-1017134.19022020-12-08服务终止2021-05-11
1803半年频道(定向)2018-04-3017134.19022020-12-08服务终止2021-05-11

下面是2个已经终止的版本

Version服务选项上市日期OS build最后修订日期终止服务:家庭版、教育版、专业教育版、专业工作站版和 IoT 核心版服务终止日期:企业版、教育版和 IoT 企业版
1709半年频道2018-01-1816299.19372020-06-18服务终止2020-10-13
1709半年频道(定向)2017-10-1716299.19372020-06-18服务终止2020-10-13

企业版和 IoT 企业版 LTSB/LTSC 版本

Version服务选项上市日期OS build最后修订日期主要支持结束日期外延支持结束日期
1809长期服务频道 (LTSC)2018-11-1317763.12942020-06-162024-01-092029-01-09
1607长期服务分支 (LTSB)2016-08-0214393.37552020-06-182021-10-122026-10-13
1507 (RTM)长期服务分支 (LTSB)2015-07-2910240.186092020-06-182020-10-132025-10-14

Windows 10 发行历史记录

在 Windows 10 更新历史记录上了解有关 Windows 10 更新内容的更多信息。


 Version 20H2 (OS build 19042)

要将设备运行的 Windows 10 版本 2004 升级为 Windows 10 版本 20H2,可通过 更新支持包 来加速此流程。有关详细信息,请参阅KB4562830

OS build上市日期服务选项知识库文章
19042.6852020-12-08半年频道KB 4592438
19042.6622020-11-30半年频道KB 4586853
19042.6312020-11-19半年频道KB 4594440
19042.6302020-11-10半年频道KB 4586781
19042.6102020-10-29半年频道KB 4580364
19042.5722020-10-20半年频道

 Version 2004 (OS build 19041)

OS build上市日期服务选项知识库文章
19041.3312020-06-18半年频道KB 4567523
19041.3292020-06-09半年频道KB 4557957
19041.2642020-05-27半年频道

 Version 1909 (OS build 18363)

要将设备运行的 Windows 10 版本 1903 升级为 Windows 10 版本 1909,可通过更新支持包来加速此流程。有关详细信息,请参阅KB4517245

OS build上市日期服务选项知识库文章
18363.9042020-06-16半年频道KB 4567512
18363.9002020-06-09半年频道KB 4560960
18363.8362020-05-12半年频道KB 4556799
18363.8152020-04-21半年频道KB 4550945
18363.7782020-04-14半年频道KB 4549951
18363.7532020-03-30半年频道KB 4554364
18363.7522020-03-24半年频道KB 4541335
18363.7202020-03-12半年频道KB 4551762
18363.7192020-03-10半年频道KB 4540673
18363.6932020-02-27半年频道KB 4535996
18363.6572020-02-11半年频道KB 4532693
18363.6282020-01-28半年频道KB 4532695
18363.5922020-01-14半年频道KB 4528760
18363.5352019-12-10半年频道KB 4530684
18363.4762019-11-12半年频道KB 4524570

 Version 1903 (OS build 18362)

OS build上市日期服务选项知识库文章
18362.9042020-06-16半年频道KB 4567512
18362.9002020-06-09半年频道KB 4560960
18362.8362020-05-12半年频道KB 4556799
18362.8152020-04-21半年频道KB 4550945
18362.7782020-04-14半年频道KB 4549951
18362.7532020-03-30半年频道KB 4554364
18362.7522020-03-24半年频道KB 4541335
18362.7202020-03-12半年频道KB 4551762
18362.7192020-03-10半年频道KB 4540673
18362.6932020-02-27半年频道KB 4535996
18362.6572020-02-11半年频道KB 4532693
18362.6282020-01-28半年频道KB 4532695
18362.5922020-01-14半年频道KB 4528760
18362.5352019-12-10半年频道KB 4530684
18362.4762019-11-12半年频道KB 4524570
18362.4492019-10-24半年频道KB 4522355
18362.4182019-10-08半年频道KB 4517389
18362.3882019-10-03半年频道KB 4524147
18362.3872019-09-26半年频道KB 4517211
18362.3572019-09-23半年频道KB 4522016
18362.3562019-09-10半年频道KB 4515384
18362.3292019-08-30半年频道KB 4512941
18362.2952019-08-13半年频道KB 4512508
18362.2672019-07-26半年频道KB 4505903
18362.2392019-07-09半年频道KB 4507453
18362.2072019-06-27半年频道KB 4501375
18362.1752019-06-11半年频道KB 4503293
18362.1452019-05-29半年频道KB 4497935
18362.1162019-05-21半年频道KB 4505057

 Version 1809 (OS build 17763)

OS build上市日期服务选项知识库文章
17763.12942020-06-16半年频道 • 半年频道(定向) • LTSCKB 4567513
17763.12822020-06-09半年频道 • 半年频道(定向) • LTSCKB 4561608
17763.12172020-05-12半年频道 • 半年频道(定向) • LTSCKB 4551853
17763.11922020-04-21半年频道 • 半年频道(定向) • LTSCKB 4550969
17763.11582020-04-14半年频道 • 半年频道(定向) • LTSCKB 4549949
17763.11322020-03-30半年频道 • 半年频道(定向) • LTSCKB 4554354
17763.11312020-03-17半年频道 • 半年频道(定向) • LTSCKB 4541331
17763.10982020-03-10半年频道 • 半年频道(定向) • LTSCKB 4538461
17763.10752020-02-25半年频道 • 半年频道(定向) • LTSCKB 4537818
17763.10392020-02-11半年频道 • 半年频道(定向) • LTSCKB 4532691
17763.10122020-01-23半年频道 • 半年频道(定向) • LTSCKB 4534321
17763.9732020-01-14半年频道 • 半年频道(定向) • LTSCKB 4534273
17763.9142019-12-10半年频道 • 半年频道(定向) • LTSCKB 4530715
17763.8642019-11-12半年频道 • 半年频道(定向) • LTSCKB 4523205
17763.8322019-10-15半年频道 • 半年频道(定向) • LTSCKB 4520062
17763.8052019-10-08半年频道 • 半年频道(定向) • LTSCKB 4519338
17763.7752019-10-03半年频道 • 半年频道(定向) • LTSCKB 4524148
17763.7742019-09-24半年频道 • 半年频道(定向) • LTSCKB 4516077
17763.7402019-09-23半年频道 • 半年频道(定向) • LTSCKB 4522015
17763.7372019-09-10半年频道 • 半年频道(定向) • LTSCKB 4512578
17763.7202019-08-17半年频道 • 半年频道(定向) • LTSCKB 4512534
17763.6782019-08-13半年频道 • 半年频道(定向) • LTSCKB 4511553
17763.6522019-07-22半年频道 • 半年频道(定向) • LTSCKB 4505658
17763.6152019-07-09半年频道 • 半年频道(定向) • LTSCKB 4507469
17763.5932019-06-26半年频道 • 半年频道(定向) • LTSCKB 4509479
17763.5922019-06-18半年频道 • 半年频道(定向) • LTSCKB 4501371
17763.5572019-06-11半年频道 • 半年频道(定向) • LTSCKB 4503327
17763.5292019-05-21半年频道 • 半年频道(定向) • LTSCKB 4497934
17763.5042019-05-19半年频道 • 半年频道(定向) • LTSCKB 4505056
17763.5032019-05-14半年频道 • 半年频道(定向) • LTSCKB 4494441
17763.4752019-05-03半年频道 • 半年频道(定向) • LTSCKB 4495667
17763.4392019-05-01半年频道 • 半年频道(定向) • LTSCKB 4501835
17763.4372019-04-09半年频道 • 半年频道(定向) • LTSCKB 4493509
17763.4042019-04-02半年频道 • 半年频道(定向) • LTSCKB 4490481
17763.3792019-03-12半年频道 • 半年频道(定向) • LTSCKB 4489899
17763.3482019-03-01半年频道(定向) • LTSCKB 4482887
17763.3162019-02-12半年频道(定向) • LTSCKB 4487044
17763.2922019-01-22半年频道(定向) • LTSCKB 4476976
17763.2532019-01-08半年频道(定向) • LTSCKB 4480116
17763.1952018-12-19半年频道(定向) • LTSCKB 4483235
17763.1942018-12-11半年频道(定向) • LTSCKB 4471332
17763.1682018-12-05半年频道(定向) • LTSCKB 4469342
17763.1342018-11-13半年频道(定向) • LTSCKB 4467708
17763.1072018-11-13半年频道(定向) • LTSCKB 4464455
17763.552018-10-09半年频道(定向) • LTSCKB 4464330
17763.12018-10-02半年频道(定向) • LTSC

 Version 1803 (OS build 17134)

OS build上市日期服务选项知识库文章
17134.15532020-06-16半年频道 • 半年频道(定向)KB 4567514
17134.15502020-06-09半年频道 • 半年频道(定向)KB 4561621
17134.14882020-05-12半年频道 • 半年频道(定向)KB 4556807
17134.14562020-04-21半年频道 • 半年频道(定向)KB 4550944
17134.14252020-04-14半年频道 • 半年频道(定向)KB 4550922
17134.14012020-03-30半年频道 • 半年频道(定向)KB 4554349
17134.13992020-03-17半年频道 • 半年频道(定向)KB 4541333
17134.13652020-03-10半年频道 • 半年频道(定向)KB 4540689
17134.13452020-02-25半年频道 • 半年频道(定向)KB 4537795
17134.13042020-02-11半年频道 • 半年频道(定向)KB 4537762
17134.12762020-01-23半年频道 • 半年频道(定向)KB 4534308
17134.12462020-01-14半年频道 • 半年频道(定向)KB 4534293
17134.11842019-12-10半年频道 • 半年频道(定向)KB 4530717
17134.11302019-11-12半年频道 • 半年频道(定向)KB 4525237
17134.10992019-10-15半年频道 • 半年频道(定向)KB 4519978
17134.10692019-10-08半年频道 • 半年频道(定向)KB 4520008
17134.10402019-10-03半年频道 • 半年频道(定向)KB 4524149
17134.10392019-09-24半年频道 • 半年频道(定向)KB 4516045
17134.10092019-09-23半年频道 • 半年频道(定向)KB 4522014
17134.10062019-09-10半年频道 • 半年频道(定向)KB 4516058
17134.9842019-08-19半年频道 • 半年频道(定向)KB 4512509
17134.9502019-08-13半年频道 • 半年频道(定向)KB 4512501
17134.9152019-07-16半年频道 • 半年频道(定向)KB 4507466
17134.8852019-07-09半年频道 • 半年频道(定向)KB 4507435
17134.8602019-06-26半年频道 • 半年频道(定向)KB 4509478
17134.8582019-06-18半年频道 • 半年频道(定向)KB 4503288
17134.8292019-06-11半年频道 • 半年频道(定向)KB 4503286
17134.7992019-05-21半年频道 • 半年频道(定向)KB 4499183
17134.7662019-05-19半年频道 • 半年频道(定向)KB 4505064
17134.7652019-05-14半年频道 • 半年频道(定向)KB 4499167
17134.7532019-04-25半年频道 • 半年频道(定向)KB 4493437
17134.7062019-04-09半年频道 • 半年频道(定向)KB 4493464
17134.6772019-03-19半年频道 • 半年频道(定向)KB 4489894
17134.6482019-03-12半年频道 • 半年频道(定向)KB 4489868
17134.6192019-02-19半年频道 • 半年频道(定向)KB 4487029
17134.5902019-02-12半年频道 • 半年频道(定向)KB 4487017
17134.5562019-01-15半年频道 • 半年频道(定向)KB 4480976
17134.5232019-01-08半年频道 • 半年频道(定向)KB 4480966
17134.4722018-12-19半年频道 • 半年频道(定向)KB 4483234
17134.4712018-12-11半年频道 • 半年频道(定向)KB 4471324
17134.4412018-11-27半年频道 • 半年频道(定向)KB 4467682
17134.4072018-11-13半年频道 • 半年频道(定向)KB 4467702
17134.3762018-10-24半年频道 • 半年频道(定向)KB 4462933
17134.3452018-10-09半年频道 • 半年频道(定向)KB 4462919
17134.3202018-09-26半年频道 • 半年频道(定向)KB 4458469
17134.2862018-09-17半年频道 • 半年频道(定向)KB 4464218
17134.2852018-09-11半年频道 • 半年频道(定向)KB 4457128
17134.2542018-08-30半年频道 • 半年频道(定向)KB 4346783
17134.2282018-08-14半年频道 • 半年频道(定向)KB 4343909
17134.1912018-07-24半年频道 • 半年频道(定向)KB 4340917
17134.1672018-07-16半年频道 • 半年频道(定向)KB 4345421
17134.1652018-07-10半年频道 • 半年频道(定向)KB 4338819
17134.1372018-06-26半年频道(定向)KB 4284848
17134.1122018-06-12半年频道(定向)KB 4284835
17134.832018-06-05半年频道(定向)KB 4338548
17134.812018-05-23半年频道(定向)KB 4100403
17134.482018-05-08半年频道(定向)KB 4103721

 Version 1709 (OS build 16299)

OS build上市日期服务选项知识库文章
16299.19372020-06-18半年频道 • 半年频道(定向)KB 4567515
16299.19322020-06-09半年频道 • 半年频道(定向)KB 4561602
16299.18682020-05-12半年频道 • 半年频道(定向)KB 4556812
16299.18062020-04-14半年频道 • 半年频道(定向)KB 4550927
16299.17762020-03-30半年频道 • 半年频道(定向)KB 4554342
16299.17752020-03-17半年频道 • 半年频道(定向)KB 4541330
16299.17472020-03-10半年频道 • 半年频道(定向)KB 4540681
16299.17172020-02-25半年频道 • 半年频道(定向)KB 4537816
16299.16862020-02-11半年频道 • 半年频道(定向)KB 4537789
16299.16542020-01-23半年频道 • 半年频道(定向)KB 4534318
16299.16252020-01-14半年频道 • 半年频道(定向)KB 4534276
16299.15652019-12-10半年频道 • 半年频道(定向)KB 4530714
16299.15082019-11-12半年频道 • 半年频道(定向)KB 4525241
16299.14812019-10-15半年频道 • 半年频道(定向)KB 4520006
16299.14512019-10-08半年频道 • 半年频道(定向)KB 4520004
16299.14212019-10-03半年频道 • 半年频道(定向)KB 4524150
16299.14202019-09-24半年频道 • 半年频道(定向)KB 4516071
16299.13922019-09-23半年频道 • 半年频道(定向)KB 4522012
16299.13872019-09-10半年频道 • 半年频道(定向)KB 4516066
16299.13652019-08-16半年频道 • 半年频道(定向)KB 4512494
16299.13312019-08-13半年频道 • 半年频道(定向)KB 4512516
16299.12962019-07-16半年频道 • 半年频道(定向)KB 4507465
16299.12682019-07-09半年频道 • 半年频道(定向)KB 4507455
16299.12392019-06-26半年频道 • 半年频道(定向)KB 4509477
16299.12372019-06-18半年频道 • 半年频道(定向)KB 4503281
16299.12172019-06-11半年频道 • 半年频道(定向)KB 4503284
16299.11822019-05-28半年频道 • 半年频道(定向)KB 4499147
16299.11502019-05-19半年频道 • 半年频道(定向)KB 4505062
16299.11462019-05-14半年频道 • 半年频道(定向)KB 4499179
16299.11272019-04-25半年频道 • 半年频道(定向)KB 4493440
16299.10872019-04-09半年频道 • 半年频道(定向)KB 4493441
16299.10592019-03-19半年频道 • 半年频道(定向)KB 4489890
16299.10292019-03-12半年频道 • 半年频道(定向)KB 4489886
16299.10042019-02-19半年频道 • 半年频道(定向)KB 4487021
16299.9672019-02-12半年频道 • 半年频道(定向)KB 4486996
16299.9362019-01-15半年频道 • 半年频道(定向)KB 4480967
16299.9042019-01-08半年频道 • 半年频道(定向)KB 4480978
16299.8472018-12-19半年频道 • 半年频道(定向)KB 4483232
16299.8462018-12-11半年频道 • 半年频道(定向)KB 4471329
16299.8202018-11-27半年频道 • 半年频道(定向)KB 4467681
16299.7852018-11-13半年频道 • 半年频道(定向)KB 4467686
16299.7552018-10-18半年频道 • 半年频道(定向)KB 4462932
16299.7262018-10-09半年频道 • 半年频道(定向)KB 4462918
16299.6992018-09-26半年频道 • 半年频道(定向)KB 4457136
16299.6662018-09-17半年频道 • 半年频道(定向)KB 4464217
16299.6652018-09-11半年频道 • 半年频道(定向)KB 4457142
16299.6372018-08-30半年频道 • 半年频道(定向)KB 4343893
16299.6112018-08-14半年频道 • 半年频道(定向)KB 4343897
16299.5792018-07-24半年频道 • 半年频道(定向)KB 4338817
16299.5512018-07-16半年频道 • 半年频道(定向)KB 4345420
16299.5472018-07-10半年频道 • 半年频道(定向)KB 4338825
16299.5222018-06-21半年频道 • 半年频道(定向)KB 4284822
16299.4922018-06-12半年频道 • 半年频道(定向)KB 4284819
16299.4612018-05-21半年频道 • 半年频道(定向)KB 4103714
16299.4312018-05-08半年频道 • 半年频道(定向)KB 4103727
16299.4022018-04-23半年频道 • 半年频道(定向)KB 4093105
16299.3712018-04-10半年频道 • 半年频道(定向)KB 4093112
16299.3342018-03-22半年频道 • 半年频道(定向)KB 4089848
16299.3092018-03-13半年频道 • 半年频道(定向)KB 4088776
16299.2512018-03-05半年频道 • 半年频道(定向)KB 4090913
16299.2482018-02-13半年频道 • 半年频道(定向)KB 4074588
16299.2142018-01-31半年频道 • 半年频道(定向)KB 4058258
16299.2012018-01-18半年频道 • 半年频道(定向)KB 4073291
16299.1942018-01-17半年频道 • 半年频道(定向)KB 4073290
16299.1922018-01-03半年频道 • 半年频道(定向)KB 4056892
16299.1252017-12-12半年频道 • 半年频道(定向)KB 4054517
16299.982017-11-30半年频道(定向)KB 4051963
16299.642017-11-14半年频道(定向)KB 4048955
16299.192017-10-17半年频道(定向)KB 4043961

 Version 1703 (OS build 15063) – 服务终止

OS build上市日期服务选项知识库文章
15063.21082019-10-08半年频道 • CBKB 4520010
15063.20792019-10-03半年频道 • CBKB 4524151
15063.20782019-09-24半年频道 • CBKB 4516059
15063.20462019-09-23半年频道 • CBKB 4522011
15063.20452019-09-10半年频道 • CBKB 4516068
15063.20212019-08-17半年频道 • CBKB 4512474
15063.19882019-08-13半年频道 • CBKB 4512507
15063.19552019-07-16半年频道 • CBKB 4507467
15063.19282019-07-09半年频道 • CBKB 4507450
15063.18982019-06-26半年频道 • CBKB 4509476
15063.18972019-06-18半年频道 • CBKB 4503289
15063.18682019-06-11半年频道 • CBKB 4503279
15063.18392019-05-28半年频道 • CBKB 4499162
15063.18082019-05-19半年频道 • CBKB 4505055
15063.18052019-05-14半年频道 • CBKB 4499181
15063.17852019-05-02半年频道 • CBKB 4502112
15063.17842019-04-25半年频道 • CBKB 4493436
15063.17462019-04-09半年频道 • CBKB 4493474
15063.17162019-03-19半年频道 • CBKB 4489888
15063.16892019-03-12半年频道 • CBKB 4489871
15063.16592019-02-19半年频道 • CBKB 4487011
15063.16312019-02-12半年频道 • CBKB 4487020
15063.15962019-01-15半年频道 • CBKB 4480959
15063.15632019-01-08半年频道 • CBKB 4480973
15063.15082018-12-19半年频道 • CBKB 4483230
15063.15062018-12-11半年频道 • CBKB 4471327
15063.14782018-11-27半年频道 • CBKB 4467699
15063.14462018-11-13半年频道 • CBKB 4467696
15063.14182018-10-18半年频道 • CBKB 4462939
15063.13872018-10-09半年频道 • CBKB 4462937
15063.13562018-09-20半年频道 • CBKB 4457141
15063.13242018-09-11半年频道 • CBKB 4457138
15063.12922018-08-30半年频道 • CBKB 4343889
15063.12662018-08-14半年频道 • CBKB 4343885
15063.12352018-07-24半年频道 • CBKB 4338827
15063.12092018-07-16半年频道 • CBKB 4345419
15063.12062018-07-10半年频道 • CBKB 4338826
15063.11822018-06-21半年频道 • CBKB 4284830
15063.11552018-06-12半年频道 • CBKB 4284874
15063.11122018-05-17半年频道 • CBKB 4103722
15063.10882018-05-08半年频道 • CBKB 4103731
15063.10582018-04-17半年频道 • CBKB 4093117
15063.10292018-04-10半年频道 • CBKB 4093107
15063.9942018-03-22半年频道 • CBKB 4088891
15063.9662018-03-13半年频道 • CBKB 4088782
15063.9362018-03-08半年频道 • CBKB 4092077
15063.9362018-02-22半年频道 • CBKB 4077528
15063.9092018-02-13半年频道 • CBKB 4074592
15063.8772018-01-17半年频道 • CBKB 4057144
15063.8502018-01-03半年频道 • CBKB 4056891
15063.7862017-12-12半年频道 • CBKB 4053580
15063.7292017-11-22半年频道 • CBKB 4055254
15063.7262017-11-14半年频道 • CBKB 4048954
15063.6752017-11-02半年频道 • CBKB 4049370
15063.6742017-10-10半年频道 • CBKB 4041676
15063.6322017-09-25半年频道 • CBKB 4040724
15063.6082017-09-12半年频道 • CBKB 4038788
15063.5402017-08-08半年频道 • CBKB 4034674
15063.5022017-08-01半年频道 • CBKB 4032188
15063.4832017-07-11半年频道 • CBKB 4025342
15063.4472017-06-27CBKB 4022716
15063.4132017-06-13CBKB 4022725
15063.3322017-05-26CBKB 4020102
15063.2962017-05-09CBKB 4016871
15063.2502017-04-25CBKB 4016240
15063.1382017-04-11CBKB 4015583

 Version 1607 (OS build 14393)

OS build上市日期服务选项知识库文章
14393.37552020-06-18CBB • CB • LTSBKB 4567517
14393.37502020-06-09CBB • CB • LTSBKB 4561616
14393.36862020-05-12CBB • CB • LTSBKB 4556813
14393.36592020-04-21CBB • CB • LTSBKB 4550947
14393.36302020-04-14CBB • CB • LTSBKB 4550929
14393.35952020-03-17CBB • CB • LTSBKB 4541329
14393.35642020-03-10CBB • CB • LTSBKB 4540670
14393.35422020-02-25CBB • CB • LTSBKB 4537806
14393.35042020-02-11CBB • CB • LTSBKB 4537764
14393.34742020-01-23CBB • CB • LTSBKB 4534307
14393.34432020-01-14CBB • CB • LTSBKB 4534271
14393.33842019-12-10CBB • CB • LTSBKB 4530689
14393.33262019-11-12CBB • CB • LTSBKB 4525236
14393.33002019-10-15CBB • CB • LTSBKB 4519979
14393.32742019-10-08CBB • CB • LTSBKB 4519998
14393.32432019-10-03CBB • CB • LTSBKB 4524152
14393.32422019-09-24CBB • CB • LTSBKB 4516061
14393.32062019-09-23CBB • CB • LTSBKB 4522010
14393.32042019-09-10CBB • CB • LTSBKB 4516044
14393.31812019-08-17CBB • CB • LTSBKB 4512495
14393.31442019-08-13CBB • CB • LTSBKB 4512517
14393.31152019-07-16CBB • CB • LTSBKB 4507459
14393.30852019-07-09CBB • CB • LTSBKB 4507460
14393.30562019-06-27CBB • CB • LTSBKB 4509475
14393.30532019-06-18CBB • CB • LTSBKB 4503294
14393.30252019-06-11CBB • CB • LTSBKB 4503267
14393.29992019-05-23CBB • CB • LTSBKB 4499177
14393.29722019-05-19CBB • CB • LTSBKB 4505052
14393.29692019-05-14CBB • CB • LTSBKB 4494440
14393.29412019-04-25CBB • CB • LTSBKB 4493473
14393.29082019-04-25CBB • CB • LTSBKB 4499418
14393.29062019-04-09CBB • CB • LTSB

Step to UEFI (215)AudioPkg 测试

GitHub 上的John Davis开发出了能够在 UEFI Shell下播放声音的驱动AudioPkg。具体项目可以在 https://github.com/Goldfish64/AudioPkg 看到(从19年开始我就开始关注他的项目,但是一直没有实验成功。现在他这个项目完全 Move 到另外的项目中了)。偶然之间我发现需要将当前系统的 Audio 设置为 (非SST。区别上简单的说就是 Intel 做出来一个 HD-Audio 的标准,然后又改进出来另外一套称作 SST 的标准,在 SST中大量使用 DSP ,更节省 CPU 更节省电力。唯一的问题是相比前者外界不知道如何实现只能通过Intel 驱动来实现播放)。

AudioPkg在 EDK2 201903 下面使用 build -a X64 -p AudioPkg\AudioPkg.dsc 会遇到如下错误:

c:\buildbs\stable201903\AudioPkg\Platform\BootChimeDxe\BootChimeDxe.c(163): error C2220: warning treated as error - no 'object' file generated
c:\buildbs\stable201903\AudioPkg\Platform\BootChimeDxe\BootChimeDxe.c(163): warning C4244: 'function': conversion from 'UINTN' to 'UINT8', possible loss of data
c:\buildbs\stable201903\AudioPkg\Application\BootChimeCfg\BootChimeCfg.c(415): error C2220: warning treated as error - no 'object' file generated
c:\buildbs\stable201903\AudioPkg\Application\BootChimeCfg\BootChimeCfg.c(415): warning C4244: 'function': conversion from 'UINTN' to 'UINT8', possible loss of data
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Vc\bin\x86_amd64\cl.exe"' : return code '0x2'
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Vc\bin\x86_amd64\cl.exe"' : return code '0x2'

解决方法是通过在出现问题代码对应的INF 中添加编译开关忽略之:

[BuildOptions]
MSFT:*_*_X64_CC_FLAGS          = /wd4054 /wd4055 /wd4152

最终修改后的代码可以在这里下载:

编译后的 X64 EFI 可以在这里下载:

再次强调:如果你发现实体机Shell 下无法运行,很可能是因为 Setup 中选择了 Intel SST 导致的,请修改为非Intel SST 的项目。

测试的视频:

获得 ModernStandby ResumeTime 的方法

微软大力推广 MS的一个原因是这个功能能够让系统更快的从睡眠状态醒来,因此,需要有一种方法来测量这个时间。

在微软的网站上有介绍使用 Windows Performance Analyzer的方法【参考1】,这里用实例介绍一下。对于WPA 的安装,可以在【参考4】看到。

1.到 WPA 的安装目录下使用 wpr -start Power 开始记录

wpr 开始记录和停止记录的命令

2.使用 Power Button 让系统进入MS,推荐十五分钟以上,然后使用  wpr -stop MSA.etl 生成记录文件“msa.etl”

WPA 记录结果

3.双机打开这个文件后,展开 System Activitiy,在右侧寻找 “Microsoft-Windows-PDC”项目:

WPA 查看Microsoft-Windows-PDC

4.在下面可以找到 “ExitFromConnectedStandbyStart”和 “ExitFromConnectedStandbEnd ” 两项:

“ExitFromConnectedStandbyStart”和 “ExitFromConnectedStandbEnd ”

5.其中给出的时间差值就是 Resume 花费的时间,这里是 1967.822568100 – 1967.029685499=0.792882601秒。

差值就是我们需要的 Resume time
  1. https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-resume-performance
  2. https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/using-windows-performance-analyzer-to-analyze-modern-standby-issues#capture-a-wpa-trace
  3. https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/modern-standby-wi-fi-connected-scenarios
  4. http://www.lab-z.com/wpainst/

ESP32 SPI 速度测试

使用DFRobot 的 FireBeetle 测试 SPI速度,代码在\DFRobot_FireBeetle-ESP32\0.0.9\libraries\SPI\examples  下面:

/* The ESP32 has four SPi buses, however as of right now only two of
 * them are available to use, HSPI and VSPI. Simply using the SPI API 
 * as illustrated in Arduino examples will use HSPI, leaving VSPI unused.
 * 
 * However if we simply intialise two instance of the SPI class for both
 * of these buses both can be used. However when just using these the Arduino
 * way only will actually be outputting at a time.
 * 
 * Logic analyser capture is in the same folder as this example as
 * "multiple_bus_output.png"
 * 
 * created 30/04/2018 by Alistair Symonds
 */
#include <SPI.h>

static const int spiClk = 1000000; // 1 MHz

//uninitalised pointers to SPI objects
SPIClass * vspi = NULL;
SPIClass * hspi = NULL;

void setup() {
  //initialise two instances of the SPIClass attached to VSPI and HSPI respectively
  vspi = new SPIClass(VSPI);
  hspi = new SPIClass(HSPI);
  
  //clock miso mosi ss

  //initialise vspi with default pins
  //SCLK = 18, MISO = 19, MOSI = 23, SS = 5
  vspi->begin();
  //alternatively route through GPIO pins of your choice
  //hspi->begin(0, 2, 4, 33); //SCLK, MISO, MOSI, SS
  
  //initialise hspi with default pins
  //SCLK = 14, MISO = 12, MOSI = 13, SS = 15
  hspi->begin(); 
  //alternatively route through GPIO pins
  //hspi->begin(25, 26, 27, 32); //SCLK, MISO, MOSI, SS

  //set up slave select pins as outputs as the Arduino API
  //doesn't handle automatically pulling SS low
  pinMode(5, OUTPUT); //VSPI SS
  pinMode(15, OUTPUT); //HSPI SS

}

// the loop function runs over and over again until power down or reset
void loop() {
  //use the SPI buses
  vspiCommand();
  hspiCommand();
  delay(100);
}

void vspiCommand() {
  byte data = 0b01010101; // junk data to illustrate usage

  //use it as you would the regular arduino SPI API
  vspi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
  digitalWrite(5, LOW); //pull SS slow to prep other end for transfer
  vspi->transfer(data);  
  digitalWrite(5, HIGH); //pull ss high to signify end of data transfer
  vspi->endTransaction();
}

void hspiCommand() {
  byte stuff = 0b11001100;
  
  hspi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
  digitalWrite(15, LOW);
  hspi->transfer(stuff);
  digitalWrite(15, HIGH);
  hspi->endTransaction();
}

其中的 spiClk 指定的是SPI 通讯速度,最高可以设置为 80000000(80M),示波器测量如下:

40M 波形测试如下

https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/spi_master.html

Secure Boot 导致的蓝屏问题

最近在 Debug 一个问题,在刷坏了一个主板之后我更换了同型号的另外的主板。开机之后出现了无法进入系统的问题。

BSOD Message

试验无法进入安全模式。接下使用之前的 WinPe 盘【参考1】启动,查看上图提到的 SrtTrail.txt 内容如下:

Startup Repair diagnosis and repair log
---------------------------
Last successful boot time: ‎5/‎19/‎2020 11:39:04 PM (GMT)
Number of repair attempts: 1

Session details
---------------------------
System Disk = \Device\Harddisk0
Windows directory = C:\Windows
AutoChk Run = 0
Number of root causes = 1

Test Performed: 
---------------------------
Name: Check for updates
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: System disk test
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Disk failure diagnosis
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Disk metadata test
Result: Completed successfully. Error code =  0x0
Time taken = 141 ms

Test Performed: 
---------------------------
Name: Disk metadata test
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Target OS test
Result: Completed successfully. Error code =  0x0
Time taken = 16 ms

Test Performed: 
---------------------------
Name: Volume content check
Result: Completed successfully. Error code =  0x0
Time taken = 31 ms

Test Performed: 
---------------------------
Name: Boot manager diagnosis
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: System boot log diagnosis
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Event log diagnosis
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Internal state check
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Check for installed LCU
Result: Completed successfully. Error code =  0x0
Time taken = 4515 ms

Test Performed: 
---------------------------
Name: Check for installed driver updates
Result: Completed successfully. Error code =  0x0
Time taken = 750 ms

Test Performed: 
---------------------------
Name: Check for pending package install
Result: Completed successfully. Error code =  0x0
Time taken = 1625 ms

Test Performed: 
---------------------------
Name: Boot status test
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Test Performed: 
---------------------------
Name: Setup state check
Result: Completed successfully. Error code =  0x0
Time taken = 94 ms

Test Performed: 
---------------------------
Name: Registry hives test
Result: Completed successfully. Error code =  0x0
Time taken = 469 ms

Test Performed: 
---------------------------
Name: Windows boot log diagnosis
Result: Completed successfully. Error code =  0x0
Time taken = 0 ms

Root cause found: 
---------------------------
Boot critical file c:\windows\system32\drivers\msdmfilt.sys is corrupt.

Repair action: File repair
Result: Failed. Error code =  0x57
Time taken = 1063 ms

---------------------------
---------------------------

从上面可以看到是因为 msdmfilt.sys 导致的,搜索了一下这个文件是 WTDF 的文件。没有办法进入安全模式也就没有办法卸载。忽然想起来可能是因为BIOS版本不同导致的。尝试启动到 Shell 来刷BIOS。但是又遇到数字签名不对的问题。这时候我意识到安装 WDTF 的时候 Secure Boot 是Disabled的,更换主板后默认是 Enabled。于是,进入 Setup 设置为 Dsiabled,问题就解决了。

参考:

1. http://www.lab-z.com/newghost/ Ghost 替代者,新的全盘备份工具

2.https://softwaretested.com/windows/how-to-fix-srttrail-txt-bsod-error-on-windows-10/ How to Fix SrtTrail.txt BSOD Error on Windows 10

TinkerNode 恢复方法

因为 TinkerNode 内置U盘功能,所以在某些状况下U盘出现问题会导致系统被拖慢,开发板无法上传等等情况。遇到这样的问题可以通过下面的几个方法尝试修复:

1.Arduino IDE 上传 Blinker 或者一个空的文件;

2.使用 sptool.exe –chip esp32 –port 端口号 –baud 512000 erase_flash尝试烧写

例如:sptool.exe –chip esp32 –port COM43 –baud 512000 erase_flash

3.使用 Arduino IDE 下载 ESP32 支持包选择低波特率烧写。

Step to UEFI (214)Ps2键盘的LED 分析

之前研究过Shell下如何控制USB 键盘上的 LED【参考1】,这次研究如何实现 PS2 键盘的 LED 控制。

和 USB 键盘一样,PS2 键盘同样需要实现Shell 下键盘 LED 的同步。意思是如果系统中有一个 USB 键盘和一个PS2键盘,当USB键盘 Num 键按下后,键盘LED状态也要同步到PS2 键盘上。

PS2键盘

对应的代码可以在\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c 的 UpdateStatusLights 函数中看到。根据代码基本想法如下在这个头文件中有如下定义\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2Keyboard.h:

typedef struct {
  UINTN                               Signature;

  EFI_HANDLE                          Handle;
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL      ConIn;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL   ConInEx;

  EFI_EVENT                           TimerEvent;

  UINT32                              DataRegisterAddress;
  UINT32                              StatusRegisterAddress;
  UINT32                              CommandRegisterAddress;

  BOOLEAN                             LeftCtrl;
  BOOLEAN                             RightCtrl;
  BOOLEAN                             LeftAlt;
  BOOLEAN                             RightAlt;
  BOOLEAN                             LeftShift;
  BOOLEAN                             RightShift;
  BOOLEAN                             LeftLogo;
  BOOLEAN                             RightLogo;
  BOOLEAN                             Menu;
  BOOLEAN                             SysReq;

  BOOLEAN                             CapsLock;
  BOOLEAN                             NumLock;
  BOOLEAN                             ScrollLock;

  BOOLEAN                             IsSupportPartialKey;
  //
  // Queue storing key scancodes
  //
  SCAN_CODE_QUEUE                     ScancodeQueue;
  EFI_KEY_QUEUE                       EfiKeyQueue;
  EFI_KEY_QUEUE                       EfiKeyQueueForNotify;

  //
  // Error state
  //
  BOOLEAN                             KeyboardErr;

  EFI_UNICODE_STRING_TABLE            *ControllerNameTable;

  EFI_DEVICE_PATH_PROTOCOL            *DevicePath;
  //
  // Notification Function List
  //
  LIST_ENTRY                          NotifyList;
  EFI_EVENT                           KeyNotifyProcessEvent;
} KEYBOARD_CONSOLE_IN_DEV;

先找到系统中的全部  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL ,检查 Signature 确定后再使用下面的宏即可获得KEYBOARD_CONSOLE_IN_DEV结构体

#define KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a)  CR (a, KEYBOARD_CONSOLE_IN_DEV, ConIn, KEYBOARD_CONSOLE_IN_DEV_SIGNATURE)
#define TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS(a) \
  CR (a, \
      KEYBOARD_CONSOLE_IN_DEV, \
      ConInEx, \
      KEYBOARD_CONSOLE_IN_DEV_SIGNATURE \
      )

之后即可用UpdateStatusLights() 函数实现更改LED 状态。

最终完整代码如下:

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

#include  "Ps2Keyboard.h"

extern EFI_BOOT_SERVICES        *gBS;
extern EFI_SYSTEM_TABLE         *gST;
extern EFI_RUNTIME_SERVICES     *gRT;

EFI_GUID gEfiSimpleTextInputExProtocolGuid = 
  {0xdd9e7534, 0x7762, 0x4698, 
      { 0x8c, 0x14, 0xf5, 0x85, 0x17, 0xa6, 0x25, 0xaa } };
                       
UINTN
EFIAPI
MicroSecondDelay (
  IN      UINTN                     MicroSeconds
  )
{
  gBS->Stall(MicroSeconds);
  return MicroSeconds;
}

/**
  Write data register.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

**/
VOID
KeyWriteDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  IoWrite8 (ConsoleIn->DataRegisterAddress, Data);
}

/**
  Read data register .

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return return the value

**/
UINT8
KeyReadDataRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )

{
  return IoRead8 (ConsoleIn->DataRegisterAddress);
}

/**
  Read status register.

  @param ConsoleIn  Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return value in status register

**/
UINT8
KeyReadStatusRegister (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  return IoRead8 (ConsoleIn->StatusRegisterAddress);
}

/**
  wait for a specific value to be presented on
  8042 Data register by keyboard and then read it,
  used in keyboard commands ack

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Value     the value wanted to be waited.

  @retval EFI_TIMEOUT Fail to get specific value in given time
  @retval EFI_SUCCESS Success to get specific value in given time.

**/
EFI_STATUS
KeyboardWaitForValue (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Value
  )
{
  UINT8   Data;
  UINT32  TimeOut;
  UINT32  SumTimeOut;
  UINT32  GotIt;

  GotIt       = 0;
  TimeOut     = 0;
  SumTimeOut  = 0;

  //
  // Make sure the initial value of 'Data' is different from 'Value'
  //
  Data = 0;
  if (Data == Value) {
    Data = 1;
  }
  //
  // Read from 8042 (multiple times if needed)
  // until the expected value appears
  // use SumTimeOut to control the iteration
  //
  while (1) {
    //
    // Perform a read
    //
    for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
      if (KeyReadStatusRegister (ConsoleIn) & 0x01) {
        Data = KeyReadDataRegister (ConsoleIn);
        break;
      }

      MicroSecondDelay (30);
    }

    SumTimeOut += TimeOut;

    if (Data == Value) {
      GotIt = 1;
      break;
    }

    if (SumTimeOut >= KEYBOARD_WAITFORVALUE_TIMEOUT) {
      break;
    }
  }
  //
  // Check results
  //
  if (GotIt == 1) {
    return EFI_SUCCESS;
  } else {
    return EFI_TIMEOUT;
  }

}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  write key to keyboard

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV
  @param Data      value wanted to be written

  @retval EFI_TIMEOUT   The input buffer register is full for putting new value util timeout
  @retval EFI_SUCCESS   The new value is sucess put into input buffer register.

**/
EFI_STATUS
KeyboardWrite (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn,
  IN UINT8                   Data
  )
{
  UINT32  TimeOut;
  UINT32  RegEmptied;

  TimeOut     = 0;
  RegEmptied  = 0;

  //
  // wait for input buffer empty
  //
  for (TimeOut = 0; TimeOut < KEYBOARD_TIMEOUT; TimeOut += 30) {
    if ((KeyReadStatusRegister (ConsoleIn) & 0x02) == 0) {
      RegEmptied = 1;
      break;
    }

    MicroSecondDelay (30);
  }

  if (RegEmptied == 0) {
    return EFI_TIMEOUT;
  }
  //
  // Write it
  //
  KeyWriteDataRegister (ConsoleIn, Data);

  return EFI_SUCCESS;
}

//
//\MdeModulePkg\Bus\Isa\Ps2KeyboardDxe\Ps2KbdCtrller.c
//
/**
  Show keyboard status lights according to
  indicators in ConsoleIn.

  @param ConsoleIn Pointer to instance of KEYBOARD_CONSOLE_IN_DEV

  @return status of updating keyboard register

**/
EFI_STATUS
UpdateStatusLights (
  IN KEYBOARD_CONSOLE_IN_DEV *ConsoleIn
  )
{
  EFI_STATUS  Status;
  UINT8       Command;

  //
  // Send keyboard command
  //
  Status = KeyboardWrite (ConsoleIn, 0xed);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);

  //
  // Light configuration
  //
  Command = 0;
  if (ConsoleIn->CapsLock) {
    Command |= 4;
  }

  if (ConsoleIn->NumLock) {
    Command |= 2;
  }

  if (ConsoleIn->ScrollLock) {
    Command |= 1;
  }

  Status = KeyboardWrite (ConsoleIn, Command);

  if (EFI_ERROR (Status)) {
    return Status;
  }

  KeyboardWaitForValue (ConsoleIn, 0xfa);
  return Status;
}


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS    Status;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL       *ConIn;
  KEYBOARD_CONSOLE_IN_DEV              *ConsoleIn;
  UINTN         HandleIndex, HandleCount;
  EFI_HANDLE    *DevicePathHandleBuffer = NULL;
  UINTN         i;
  //
  //Get all the Handles that have SimpleTextInputEx Protocol
  //
  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiSimpleTextInputExProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);
  //
  //Open SimpleTextInputEx Protocol on each device
  //
  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
    Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiSimpleTextInputExProtocolGuid,
                      (VOID**)&ConIn);
    if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open ConIn fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }
    
    //Get KEYBOARD_CONSOLE_IN_DEV by SimpleTextInputEx Protocol    
    ConsoleIn = TEXT_INPUT_EX_KEYBOARD_CONSOLE_IN_DEV_FROM_THIS (ConIn);
    
    //Check the signature if it's what we want    
    if (ConsoleIn->Signature==KEYBOARD_CONSOLE_IN_DEV_SIGNATURE) {
            // Turn Off all LEDs
            ConsoleIn->CapsLock=0;
            ConsoleIn->NumLock=0;
            ConsoleIn->ScrollLock=0;
            UpdateStatusLights(ConsoleIn);
                    
            for (i=0;i<20;i++) {
                    ConsoleIn->NumLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->NumLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->CapsLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->CapsLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    
                    ConsoleIn->ScrollLock=1;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                    ConsoleIn->ScrollLock=0;
                    UpdateStatusLights(ConsoleIn);
                    MicroSecondDelay(300000UL);
                }
   } //if (ConsoleIn->Signature
  } //for (HandleIndex = 0;

  gBS->FreePool(DevicePathHandleBuffer);  
  return Status;
}

完整代码和X64 EFI 下载(因为含有 IO 代码,所以必须在实体机上运行)

工作的视频:

参考:

  1. https://www.lab-z.com/stu120uk/  UEFI 下控制USB键盘 LED
  2. http://www-ug.eecg.toronto.edu/msl/nios_devices/datasheets/PS2%20Keyboard%20Protocol.htm The PS/2 Keyboard Interface
  3. https://wiki.osdev.org/PS/2_Keyboard PS/2 Keyboard

Arduino 上通过串口控制CH376

CH37X 系列是南京沁恒出品的一系列USB控制类的芯片,主要用途是用作 USB Host 实现 USB 设备的控制。具体列表如下【参考1】:

上面不同型号主要差别在于提供的接口,目前支持三种对单片机的接口,分别是并口 SPI和串口。此外就是是否硬件集成文件系统。如果集成了,那么可以通过简单的命令来实现文件系统级别的操作,否则需要主控芯片自己来实现,这对于主控的内存要求比较高,整体程序也会复杂得多。

从上面可以看到,CH378 是当前最强的,但是因为封装更加负责以及比较新的原因(缺少资料),所以市面上最常见的还是 CH376,淘宝上的价格在20以内,应该也是比较容易接受的。这次试验就是基于 CH376 模块,特别注意是下面这种带有一个 2X3跳线的:

这款电路图如下:

这个模块默认情况下使用串口通讯,跳线顺序如下:

P_S: GND TXD(CH376) TXD

    S:  RXD  GND  TXD

这个跳线决定初始时串口通讯速度,意思是如果能够通讯,那么可以通过修改寄存器的方式修改波特率从而实现更高的速度。

  跳线指南【参考2】

这次试验就是如何进行串口通讯 ,P_S 位置跳线是 TXD(CH376)和 TXD 短路。试验使用的是Arduino Leonardo,因为他带有一个额外的硬件串口(软件串口通常在 115200波特率下接收会有问题)。接线如下:

Arduino LeonardoCH376模块
5VVCC
GNDGND
Pin0 RXTXD
Pin1 TXRXD

测试代码是库中自带的 basicUsageHwSerial测试程序:

/*------------------------------------------------------------------------------------------------------------------
 *    Author: György Kovács                                                                                         |
 *    Created: 28 Mar 2019                                                                                          |
 *    Description: Basic usage of CH376 with hardware serial                                                        |
 *    Thanks for the idea to Scott C , https://arduinobasics.blogspot.com/2015/05/ch376s-usb-readwrite-module.html  |
 *------------------------------------------------------------------------------------------------------------------
 */


#include <Ch376msc.h>

//..............................................................................................................................
// Leave the default jumper settings for the baud rate (9600) on the CH376, the library will set it up the chosen speed(HW serial only)
Ch376msc flashDrive(Serial1, 115200); // Ch376 object with hardware Serial1 on arduino mega baudrate: 9600, 19200, 57600, 115200
//..............................................................................................................................
 // buffer for reading
char adatBuffer[255];// max length 255 = 254 char + 1 NULL character
//..............................................................................................................................
// strings for writing to file
char adat[]="Vivamus nec nisl molestie, blandit diam vel, varius mi. Fusce luctus cursus sapien in vulputate.\n";
char adat2[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis efficitur ac est eu pharetra. \n";
//..............................................................................................................................
unsigned long totSect = 0;
unsigned long freeSect = 0;
byte percentg = 0;
byte tmpCommand; //used to store data coming from serial port
boolean readMore;
static char helpString[]= {"h:Print this help\n\n1:Create\n2:Append\n3:Read\n4:Read date/time\n"
            "5:Modify date/time\n6:Delete\n7:List dir\n8:Print free space"
            "\n9:Open/Create folder(s)/subfolder(s)"};

void setup() {
  Serial.begin(115200);
  flashDrive.init();
  printInfo(helpString);
}

void loop() {
	if(flashDrive.checkIntMessage()){
		if(flashDrive.getDeviceStatus()){
			Serial.println(F("Flash drive attached!"));
		} else {
			Serial.println(F("Flash drive detached!"));
		}
	}
  if(Serial.available()){
    tmpCommand = Serial.read();                      //read incoming bytes from the serial monitor
    if(((tmpCommand > 48)&&(tmpCommand < 58)) && !flashDrive.driveReady()){ // if the data is ASCII 1 - 9 and no flash drive are attached
       printInfo("Attach flash drive first!");
      tmpCommand = 10; // change the command byte
    }
     switch (tmpCommand) {

      case 49: //1
        printInfo("COMMAND1: Create and write data to file : TEST1.TXT");    // Create a file called TEST1.TXT
          flashDrive.setFileName("TEST1.TXT");  //set the file name
          flashDrive.openFile();                //open the file

          for(int a = 0; a < 20; a++){          //write text from string(adat) to flash drive 20 times
            flashDrive.writeFile(adat, strlen(adat)); //string, string length
          }
          flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 50: //2
        printInfo("COMMAND2: Append data to file: TEST1.TXT");               // Append data to the end of the file.
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        if(flashDrive.openFile() == ANSW_USB_INT_SUCCESS){               //open the file
        	flashDrive.moveCursor(CURSOREND);     //if the file exist, move the "virtual" cursor at end of the file, with CURSORBEGIN we actually rewrite our old file
        	//flashDrive.moveCursor(flashDrive.getFileSize()); // is almost the same as CURSOREND, because we put our cursor at end of the file
        }
        for(int a = 0; a < 20; a++){          //write text from string(adat) to flash drive 20 times
        	if(flashDrive.getFreeSectors()){ //check the free space on the drive
        		flashDrive.writeFile(adat2, strlen(adat2)); //string, string length
        	} else {
        		printInfo("Disk full");
        	}
        }
        flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 51: //3
        printInfo("COMMAND3: Read File: TEST1.TXT");                         // Read the contents of this file on the USB disk, and display contents in the Serial Monitor
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.openFile();                //open the file
        readMore = true;
                //read data from flash drive until we reach EOF
        while(readMore){ // our temporary buffer where we read data from flash drive and the size of that buffer
        	readMore = flashDrive.readFile(adatBuffer, sizeof(adatBuffer));
        	Serial.print(adatBuffer);          //print the contents of the temporary buffer
        }
        flashDrive.closeFile();               //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 52: //4
        printInfo("COMMAND4: Read File date/time: TEST1.TXT");      // Read the date and time of file, default 2004.01.01 - 00:00:00
        flashDrive.setFileName("TEST1.TXT");            //set the file name
        flashDrive.openFile();                          //open the file
                //print informations about the file
          Serial.println(flashDrive.getFileName());
          Serial.print(flashDrive.getYear());
          Serial.print("y\t");
          Serial.print(flashDrive.getMonth());
          Serial.print("m\t");
          Serial.print(flashDrive.getDay());
          Serial.print("d\t");
          Serial.print(flashDrive.getHour());
          Serial.print("h\t");
          Serial.print(flashDrive.getMinute());
          Serial.print("m\t");
          Serial.print(flashDrive.getSecond());
          Serial.println('s');
        flashDrive.closeFile();                         //at the end, close the file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 53: //5
        printInfo("COMMAND5: Modify File date/time: TEST1.TXT");    // Modify the file date/time and save
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.openFile();                //open the file

          flashDrive.setYear(2019);
          flashDrive.setMonth(12);
          flashDrive.setDay(19);
          flashDrive.setHour(03);
          flashDrive.setMinute(38);
          flashDrive.setSecond(42);

          flashDrive.saveFileAttrb();           //save the changed data
        flashDrive.closeFile();               //and yes again, close the file after when you don`t use it
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 54: //6
        printInfo("COMMAND6: Delete File: TEST1.TXT");                       // Delete the file named TEST1.TXT
        flashDrive.setFileName("TEST1.TXT");  //set the file name
        flashDrive.deleteFile();              //delete file
        printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 55: //7
        printInfo("COMMAND7: List directory");                          //Print all file names in the current directory
          while(flashDrive.listDir()){ // reading next file
            if(flashDrive.getFileAttrb() == ATTR_DIRECTORY){//directory
              Serial.print('/');
              Serial.println(flashDrive.getFileName()); // get the actual file name
            } else {
              Serial.print(flashDrive.getFileName()); // get the actual file name
              Serial.print(" : ");
              Serial.print(flashDrive.getFileSize()); // get the actual file size in bytes
              Serial.print(" >>>\t");
              Serial.println(flashDrive.getFileSizeStr()); // get the actual file size in formatted string
            }
          }
          printInfo("Done!");
        break;
//*****************************************************************************************************************************************************
      case 56: //8
    	  totSect = flashDrive.getTotalSectors(); // get the total sector number
    	  freeSect = flashDrive.getFreeSectors(); // get the available sector number
    	  percentg = map(freeSect,totSect,0,0,100); 		// convert it to percentage (0-100)
    	  Serial.print("Disk size in bytes: ");
    	  /*if the sector number is more than 8388607 (8388607 * 512 = 4294966784 byte = 4Gb (fits in a 32bit variable) )
    	    							 e.g. 8388608 * 512 = 4294967296 byte (32bit variable overflows) */
    	  if(totSect > 8388607){
    		  Serial.print(">4Gb");
    	  } else {
        	  Serial.print(totSect * SECTORSIZE);
    	  }
    	  Serial.print("\tFree space in bytes: ");
    	  if(freeSect > 8388607){
    		  Serial.print(">4Gb");
    	  } else {
        	  Serial.print(freeSect * SECTORSIZE);
    	  }
    	  Serial.print(F("\tDisk usage :"));
    	  Serial.print(percentg);
    	  Serial.print(F("%"));
    	  switch (flashDrive.getFileSystem()) { //1-FAT12, 2-FAT16, 3-FAT32
			case 1:
				Serial.println(F("\tFAT12 partition"));
				break;
			case 2:
				Serial.println(F("\tFAT16 partition"));
				break;
			case 3:
				Serial.println(F("\tFAT32 partition"));
				break;
			default:
				Serial.println(F("\tNo valid partition"));
				break;
		}
    	 break;
//*****************************************************************************************************************************************************
      case 57: //9
        switch(flashDrive.cd("/DIR1/DIR2/DIR3",1)){
          case ERR_LONGFILENAME: //0x01
            Serial.println(F("Directory name is too long"));
          break;

          case ANSW_USB_INT_SUCCESS: //0x14
          Serial.println(F("Directory created successfully"));
          break;

          case ANSW_ERR_OPEN_DIR: //0x41
          Serial.println(F("Directory opened successfully"));
          break;

          case ANSW_ERR_MISS_FILE: //0x42
          Serial.println(F("Directory doesn't exist"));
          break;

          case ANSW_ERR_FOUND_NAME: //0x43
          Serial.println(F("File exist with the given name"));
          break;

          default:

          break;
        }
      break;
//*****************************************************************************************************************************************************
      case 104: //h
    	  printInfo(helpString);
        break;
      default:
        break;
    }//end switch

  }//endif serial available

}//end loop

//Print information
void printInfo(char info[]){
  char * infoPtr = info;
  int infoLength = 0;
    while(*infoPtr){
      infoPtr++;
      infoLength++;
      if(infoLength > 40) break;
    }
    Serial.print(F("\n\n"));
    for(int a = 0; a < infoLength; a++){
      Serial.print('*');
    }
   Serial.println();
   Serial.println(info);
   for(int a = 0; a < infoLength; a++){
      Serial.print('*');
    }
   Serial.print(F("\n\n"));
}

运行结果:插拔U盘有提示,输入 h 显示菜单,可以进行U盘读写等等文件操作

最后吐槽一下:CH37X  系列推出十多年了,很多年前我也入手过,但是不得不说这货太难用了,官网提供的资料看起来很全,但上手之后会发现缺少核心部分。现在能用是因为南京沁恒后来公布了一些具体 COMMAND (之前应该只有大客户才能拿到),这样玩家才有机会应用在 Arduino 上。相比之下,国外的芯片资料一直都很全。这也是为什么很多产品在第一版设计时不会考虑国产方案的原因。希望未来国产芯片在这方面能有所改观。

参考:

  1. http://www.wch.cn/products/category/1.html
  2. https://github.com/djuseeq/Ch376msc

查看 PMC Firmware版本的工具

很多时候,我们需要得知当前系统中的 PMC Firmware版本。最简单的方法是进入 Setup 查看。但是不幸的是很多时候BIOS会主动隐藏这个选项。因此,编写一个 Shell 下的工具。用户可以直接获得当前系统 PMC版本号:

显示PMC版本的 Shell 工具

当然,除此之外还可以使用 MEInfo.efi (在 CSME Release Package 中)看到 PMC 的版本信息:

MEINFO 显示PMC版本信息