在 KablyLake/AmberLake的PCH上存在着3个UART,默认情况下使用 UART2(第三个UART,INTEL RVP上使用的就是这个Port作为UEFI Debug Log 输出)。为此,编写一个 UEFI Shell Application 能够让三个 UART 分别发送 “This is PCH UART0/1/2”。在通过串口连接出去的机器上可以收到。这样能够方便的确认硬件连接以及配置是否工作正常。
运行结果:

工具下载(无 SourceCode):
春节这几天趁着有空再进一步研究了一下 ESP32 S2 的 USB 玩法。线路连接方法和之前介绍的相同【参考1】,特别注意,这次没有连接 ESP32S2 的5V和 USB 端口的5V, 这是防止 USB 端口上的5V和板子上的5V不同导致的电流倒灌。首先测试 HID的例子:

烧写代码后设备管理器中可以看到多出的 HID 设备。

我手上的开发板是ESP32-S2-Saola-1R,引脚入下图:

调试方法:

这样,可以在 Arduino 串口监视器中看到 Debug 信息,比如插入时有如下信息:

接下来研究一下 MSD (MassStorageDevice,U盘)的例子,在Example->ESP32 TinyUSB->MSC。这个例子需要使用1.5MB的 PSRAM ,对于我入手的板子来说 Flash 是4MB ,PSRAM 是2MB,完全能够满足要求。此外,需要 Enable PSRAM,否则会出现不断重启的问题:

烧写之后系统中就会出现如下的硬盘。

测试发现最新版本的 ESPTinyUSB 库似乎有问题,下面是我这边正常使用的老版本的库:
参考:
之前看到过很多人用一些词组来证明中文的简洁和高效,比如:人们看到“炮闩”,通过“闩”就能猜到它的用途。这让我想起最近网上的一个笑话:“老师在群里发消息说明天开始一元二次方程。有家长问。能不能一元三次?打个折?” 缺少必要知识,就无法理解“元”的含义。 “一元二次方程” 的英文名是“quadratic equation with one unknown”,从上面大抵能看明白这个方程中有一个未知数以及它的二次方。所以,懂和不懂是取决于知识的储备和语言文字并没有太多关系。
| 缩写 | 原始词组 | 解释 |
| ACM | Authenticated Code Module | 用于验证当前密钥之类的模块,放在 BIOS 中的,这个可以看作是 BootGuard 功能的一部分(换句话说,如果想 Enabled BootGuard 功能,除了在 FIT 中 Enable BootGuard,还要保证BIOS中有这个模块。某些情况下,IBV Enabled Debug 功能后,因为 Size 的原因会关闭 ACM ,这样做出来的Debug BIOS 无法在 Fuse 后,并且Enable BootGuard的板子上运行。现象会是:上电之后马上掉电)。 |
| BtG | BootGuard | BootGuard 在【参考1】有介绍。 |
| BUP | Bring-Up | 主板上电过程,有时候指新打出来板子然后Porting BIOS 的过程。 |
| CSE | Converged Security Engine | 系统上的 CSME 系统 |
| dTPM | Discrete TPM | 分立式TPM。对应有一颗专用芯片,通常使用 SPI 接口和PCH 相连。Intel PTT 相当于将一个 dTPM 集成到SOC 中。 |
| EK | Endorsement Key | 签署密钥,或背书密码。它是一个TPM平台的不可迁移的解密密钥,它是一个2048bit的RSA密钥对。它生成于平台的生产过程中,代表着每个平台的真实身份,每个平台都拥有唯一的一个。在确定平台所有者时,用于解密所有者的授权数据,还有解密与生成AIK相关的数据。签署密钥从不用作数据加密和签名。签署密钥的主要功能是生成身份证明密钥(AIK)和建立TPM平台的所有者,由TPM的所有者来生成存储根密钥SRK,使用SRK来加密、存储其他的密钥。【参考2】 |
| FIPS | Federal Information Processing Standard | 美国联邦信息处理标准 |
| FIT | Flash Image Tool | 有时缩写为 FITC, 是一个用于设置 IFWI 的工具,由Intel 提供,跟随 CSME 分发。 |
| FPF | Field Programmable Fuses | 可编程熔丝位。在 PCH 中有一些只能编写一次的熔丝位。可以理解成类似保险丝一样的东西,需要的时候通过编程能够将这个位置写死。比如,Enable PTT之后通过设置对应的熔丝位会使得这个功能无法再次 Disabled。 |
| fTPM | Firmware TPM | 固件模拟TPM。通过在 CPU 上运行的代码来模拟实现 TPM 的功能。为了增加安全性,相关代码是放在 Protected Execution Environment (PEE) 来执行的。 |
| IBB | Initial Boot Block | 这是 UEFI 中的概念,用于校验 BIOS 其余部分。 |
| PEE | Protected Execution Environment | 受保护运行环境,能够独立于普通CPU 运行环境来运行代码。 |
| PRTC | Protected Real Time Clock | 受保护的时钟,能够抵御 Hammering 和 Replay 攻击。对于这两种攻击介绍如下【参考4】 重放攻击(Replay Attacks)又称重播攻击、回放攻击,是指攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程,破坏认证的正确性。重放攻击可以由发起者,也可以由拦截并重发该数据的敌方进行。攻击者利用网络监听或者其他方式盗取认证凭据,之后再把它重新发给认证服务器。重放攻击在任何网络通过程中都可能发生,是计算机世界黑客常用的攻击方式之一。 重放操作 一个电子商务网站,要求客户对电子订单签名以防止非授权用户下订单。攻击者如要冒充某位客户下订单,最好可以获得他的私钥,如果不成功,攻击者可以监听这位顾客的通信,将顾客以前发送的订单记录下来,然后他就可以直接将这些订单发给网站了。因为这些订单的确是合法客户签名过的,如果网站没有一种识别重放订单的机制,它就会不加犹豫地接收这些订单。 |
| PTP | Platform TPM Profile | |
| RBE | ROM Boot Extension | |
| RNG | Random Number Generator | 随机数生成器 |
| ROT | Root of Trust | 根信任节点 |
| RPMB | Replay Protected Memory Block | 防重放攻击内存块, 将PTT数据存放在独立的 NVM 区域。 |
| RPMC | Replay Protection Monotonic Counters | 防重放攻击单调递增计数器。这个计数器只能增加不能减小,通过这样的方式来对抗重放攻击。 |
| RTC | Real Time Clock | |
| RTM | Root of Trust for Measurement | |
| RTR | Root of Trust for Reporting | |
| RTS | Root of Trust for Storage | |
| TCG | Trusted Computing Group | 可信计算组织。TCG组织制定了TPM(Trusted Platform Module)的标准,很多安全芯片都是符合这个规范的。而且由于其硬件实现安全防护,正逐渐成为PC,尤其是便携式PC的标准配置。 |
| TPM | Trusted Platform Module | 可信平台模块。 |
| VSC | Virtual Smart Cards | 虚拟智能卡。常见的公交卡就是一种智能卡。可以看到它能够实现身份验证,支付等等的功能。同样的,虚拟智能卡也能实现这样的功能。可以想象下面的应用场景:通过 TPM 实现了 Virtual Smart Cards 的功能,然后 Windows 就可以访问企业内部的敏感资源。比如,你将一份文档发送到企业内部打印机上,之前需要刷胸卡来完成打印,现在有了 VSC 可以通在打印机上刷笔记本电脑的方式完成打印。 |
可以看到,上面的很多词,如果不是专业人员,无论是英文还是中文都是需要多加解释的。再比如:“有一个机械中特别重要的装置,叫活塞,英文是piston。不过在中国,这个词最早不叫活塞,而叫“鞲鞴”。来,眼睛别晕,先扶着墙把晚饭吐出来,然后把这个字复制到word里,放大三倍字号。这个词,念“勾背”。据李文、戴吾三两位考证,是中国第一艘火轮船的制造者徐寿发明的,徐寿1871年在《汽机发轫》首次把piston翻成鞲鞴。别看这词冷僻古怪又麻烦,还真是有典故的。“鞲”字意义是革制的皮套,引申成皮制的鼓风机,也就是风箱;“鞴”字就更精妙了,它的意思是:水受压而喷涌而出。唐代皮日休的《通玄子栖宾亭记》:“源内橐籥鞴出琉璃液。”一鼓一压,正是活塞工作之象。徐寿文化水平太高,用这两个字来译piston,真是用典贴切,古今妙译里,也排得上号,就是他妈太麻烦了……所以后来大家普遍都使用“活塞”这个更浅显的词,鞲鞴则只留存在专业领域。”【参考3】
参考:
1.https://www.lab-z.com/btg/ Boot Guard 简介
2.https://www.cnblogs.com/embedded-linux/p/6716740.html TPM Key相关概念
3.https://www.zhihu.com/question/24449484/answer/27850454 作者:马伯庸
4.https://baike.baidu.com/item/%E9%87%8D%E6%94%BE%E6%94%BB%E5%87%BB 重放攻击
5.https://docs.microsoft.com/en-us/windows/security/identity-protection/virtual-smart-cards/virtual-smart-card-overview
6.https://baike.baidu.com/item/%E6%99%BA%E8%83%BD%E5%8D%A1 智能卡
智能卡(Smart Card) :内嵌有微芯片的塑料卡(通常是一张信用卡的大小)的通称。一些智能卡包含一个微电子芯片,智能卡需要通过读写器进行数据交互。智能卡配备有CPU、RAM和I/O,可自行处理数量较多的数据而不会干扰到主机CPU的工作。智能卡还可过滤错误的数据,以减轻主机CPU的负担。适应于端口数目较多且通信速度需求较快的场合。 卡内的集成电路包括中央处理器CPU、可编程只读存储器EEPROM、随机存储器RAM和固化在只读存储器ROM中的卡内操作系统COS(Chip Operating System)。卡中数据分为外部读取和内部处理部分。
智能卡(Smart Card) :内嵌有微芯片的塑料卡(通常是一张信用卡的大小)的通称。一些智能卡包含一个微电子芯片,智能卡需要通过读写器进行数据交互。智能卡配备有CPU、RAM和I/O,可自行处理数量较多的数据而不会干扰到主机CPU的工作。智能卡还可过滤错误的数据,以减轻主机CPU的负担。适应于端口数目较多且通信速度需求较快的场合。 卡内的集成电路包括中央处理器CPU、可编程只读存储器EEPROM、随机存储器RAM和固化在只读存储器ROM中的卡内操作系统COS(Chip Operating System)。卡中数据分为外部读取和内部处理部分。
某些情况下,我们需要在没有源代码的情况下需要对BIOS ROM 进行直接修改。本文就以修改 OVMF.FD 为例介绍这个过程。
首先介绍一下这次修改的目标,使用下面的命令生成 Debug Log:
qemu-system-x86_64 -bios "ovmf.fd" -debugcon file:debug.log -global isa-debugcon.iobase=0x402
其中有下面这样一个错误:
SecCoreStartupWithStack(0xFFFCC000, 0x820000)
LABZ report checksum error!
Register PPI Notify: DCD0BE23-9586-40F4-B643-06522CED4EDE
Install PPI: 8C8CE578-8A3D-4F1C-9935-896185C32DD3
Install PPI: 5473C07A-3DCB-4DCA-BD6F-1E9689E7349A
The 0th FV start address is 0x00000820000, size is 0x000E0000, handle is 0x820000
Register PPI Notify: 49EDB1C1-BF21-4761-BB12-EB0031AABB39
Register PPI Notify: EA7CA24B-DED5-4DAD-A389-BF827E8F9B38
Install PPI: B9E0ABFE-5979-4914-977F-6DEE78C278A6
我们的目标就是通过修改 OVMF.FD 来去掉这个错误。
使用 UEFITool 打开 OVMF.FD ,使用搜索功能查找错误字符串,可以看到是位于 SecMain 中。这个文件没有压缩,对于我们搜索来说会很方便。

为了研究代码,还需要使用这个工具将SecMain 释放出来:

释放出来的文件是以 “MZ”开头的:

使用 IDA 来分析解压出来的这个文件,特别注意,因为代码中含有64Bit的指令,必须使用 IDA-64 才能得到结果。


我们需要找到相关的代码,在菜单中选择 View->Open subviews->Strings

先用Ctrl+F 找到前面提到的字符串,在字符串上双击即可跳转到对应的代码:

新版本的 IDA 默认使用图形化反编译结果,看起来并不是很直接,右键切换到 Text View

这里就可以看的很清楚,比较 rax 和 rdx ,如果不相等就输出字符串,如果相等就跳过:

使用 Hex View 查看,74 0F就是 jz short loc_FFFCCE37 这个跳转语句,将这条语句修改为 jmp short loc_FFFCCE37 就不会输出错误提示了。

修改方法是:在 OVMF中搜索 FD FF FF 48 3B C2 74 0F (整个文件中只有一处),找到后将 74 修改为改成 EB (JMP)。这样修改后再次使用 QEMU 启动修改后的 OVMF.FD ,可以在 Debug.log 中看到之前的字符串已经消失。
本文使用的,修改之前的 OVMF 可以在这里下载:
这个信息是在 Omvf/Sec/SecMain.c 中加入如下代码生成的:
//
// Find PEI Core entry point
//
Status = PeCoffLoaderGetEntryPoint ((VOID *) (UINTN) PeiCoreImageBase, (VOID**) PeiCoreEntryPoint);
if (EFI_ERROR (Status)) {
*PeiCoreEntryPoint = 0;
}
//LABZ_Debug_Start
if (((EFI_PHYSICAL_ADDRESS) FindImageBase -(EFI_PHYSICAL_ADDRESS) FindAndReportEntryPoints)!=0x0) {
DEBUG ((EFI_D_ERROR, "LABZ report checksum error!\n"));
}
//LABZ_Debug_End
return;
}
对于现在的电脑来说,USB是外部通讯的不二选择,通过这个接口可以引出键盘鼠标等等外部设备。这次尝试使用国产的 CH55X 系列单片机来做一个 USB 提醒器,通过这个设备能够做到LED 发光提示还有蜂鸣器声音提示。
首先介绍一下 CH55X 系列芯片是南京沁恒(WCH)出品的带有USB功能的系列单片机【参考1】,提起这家公司的名称大多数人会感到陌生,但是如果提起CH340/CH341芯片大家都会非常熟悉,这款低成本的USB串口转换芯片就是这家公司的产品。这次设计用到的 CH552 和 CH551/CH554 是同一个系列。他们主要的差别是:CH551 是最低端的,工作频率是 24Mhz,FLASH 有 10K (可以看作是单片机的 ROM),内存是 512+256字节,只能做 USB Device;CH552比前者配置稍微高了一些,FLASH空间有16K,内存有1K+256字节,只能做 USB Device;CH554比前面都高级,FLASH 和 内存和 CH552 相同,但是可以当作 USB Host 使用能够实现解析USB 键盘鼠标这样设备的工作。可以看出,相比 Atmel 的 32U4 (Arduino Leonardo),主要缺点是内存太小。但是胜在价格便宜,引脚简单(这样容易焊接)。在立创商城上面的芯片价格如下(CH552/4 不同封装引脚数量不同,所以价格是在一个范围内)
CH551G 2.475元
CH552 2.7-2.78元
Ch554 5.43-7.03元
32u4 23.51元
因此,如果CH55X 系列芯片能够满足你的需求,能够大大降低成品的成本。同时, CH55X 系列单片机对于外围元件要求极低,无须晶振,只要合适的贴片电容即可工作起来。
这次设计使用CH552G芯片是 SOP16 封装,同样的 CH554 也有SOP16封装可以直接替换(CO-Layout)

电路设计如下:

这个芯片使用C1/C2 两个 0.1uF 的电容即可工作,5V 供电,从 VCC/VDD 引脚进入。上图中 V33 经过 R1 (10K)连接到 USB+引脚上,其中的 PAD1无须上键在这里作为开关使用用于控制芯片进入 Bootloader Mode。
板子上还有一个 WS2812B MINI(3.5×3.5mm) 用于提供灯光提示,同样的这个元件无须外围配合,直接用CH55X P1.5即可控制:

外围有一个蜂鸣器,因为CH55X 引脚提供电流有限,所以使用S8050这个三极管扩流,同时外加R3用于限流,主要是避免声音太大。CH55X P3.4 Pin 用于控制蜂鸣器开关。因为该引脚是 PWM 引脚,因此这里即可以使用有源蜂鸣器和无源蜂鸣器。有源蜂鸣器的含义是“内部有震荡源”,类似于电动机,有了供电就能发声;与其相反,无源蜂鸣器含义是“内部没有震荡源”,因此需要用引脚电流变换来驱动它。对于有源蜂鸣器,P3.4 当作普通的GPIO 即可;对于无源蜂鸣器,需要设定为 PWM 然后给他设置占空比和频率(默认即可)。

PCB 设计如下:

切换为 3D 模式预览如下(这次设计的 Symbol使用的都是嘉立创的,所以自带了 3D 模型,非常直观):


拿到手的 PCB和焊接后的照片如下:

接下来就开始软件设计了。这个芯片原本设计使用 C51 来进行变成,用户可以通过 Keil 这样的集成开发环境进行编程,但是对于我们来说C51还是过于复杂。这里使用 Github上的一个开源项目:ch55xduino【参考2】,能够让我们像开发 Arduino 一样为 CH551/2/4 进行开发。
首先介绍一下项目的安装:和其他的所有的第三方板卡一样, 在 Preferences –> Additional Boards Manager URLs 中加入这个板卡的地址https://raw.githubusercontent.com/DeqingSun/ch55xduino/ch55xduino/package_ch55xduino_mcs51_index.json

之后打开 Boards Manager

搜索这个项目

Install 安装之后在 Boards Manager 中能够看到这个板卡,编译时,CH551使用 CH551 Board, CH552/4 使用 CH552 Board

接下来就是如何编译一个程序,可以在 Example 中看到专用的例子,比如下面就是关于 USB 设备的例子:

打开一个例子尝试编译,完成后就需要做上传的准备工作了。刚拿到手 ,板子上没有Bootloader,需要短接板子背面 PAD 处(电路图中是0.1uF电容,实际上不上件预留为空),这样V33会通过10K 电阻后进入 D+ Pin,板子会进入 Bootloader Mode。


属性如下:

使用 Zadig 给他安装一个驱动,勾选 Edit

重命名为 USB Module

安装之后:

编译后会自动搜索名为 “USB Module”的设备并且上传

需要注意的是:如果你的代码没有实现 USB 串口,那么只能短接PAD 让板子进入 BootLoader Mode再次上传。
了解了上面的知识,即可着手设计代码实现提醒器的功能。我们设计了2种提醒方式:LED 和蜂鸣器,分别通过2个命令进行设置,形式如下:
完整代码如下:
#define BEEPERPIN 34
#define LEDPIN 15
#define TX(LedColor) {\
if (((LedColor)&0x80)==0) {\
XdigitalWriteFast(1,5,HIGH);\
(LedColor)=(LedColor)<<1;\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
}else{\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
XdigitalWriteFast(1,5,HIGH);\
(LedColor)=(LedColor)<<1;\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
XdigitalWriteFast(1,5,LOW);\
}\
}
#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif
#include "src/userUsbCdc/USBCDC.h"
void setup() {
// 设置蜂鸣器Pin 为Low (停止发声)
pinMode(BEEPERPIN,OUTPUT);
digitalWrite(BEEPERPIN,LOW);
// 设置LED 控制Pin
pinMode(LEDPIN,OUTPUT);
digitalWrite(LEDPIN,LOW);
USBInit();
}
byte Status=0;
byte RValue,GValue,BValue;
byte THigh=0,TLow=0;
unsigned long CounterDown=0xFFFFFFFF;
void loop() {
while (USBSerial_available()) {
// 接收来自USB串口的字符
char serialChar = USBSerial_read();
if ((serialChar == '[')&&(Status==0)) { Status=1; continue;}
if ((serialChar == 'c')&&(Status==1)) {Status=2; continue;}
if (Status==2) {RValue=serialChar; Status=3; continue;}
if (Status==3) {GValue=serialChar; Status=4; continue;}
if (Status==4) {BValue=serialChar; Status=5; continue;}
if (Status==5) {
if (serialChar == ']') {Status=6;continue;}
else {Status=0; continue;}
}
if ((serialChar == 'b')&&(Status==1)) {Status=7; continue;}
if (Status==7) {THigh=serialChar; Status=8; continue;}
if (Status==8) {TLow=serialChar; Status=9; continue;}
if (Status==9) {
if (serialChar == ']') {Status=10; continue;}
else {
// 如果最后收到的字符不是 [ 那么需要重新开始
Status=0; continue;
}
}
}
// 根据收到的 cRGB 设置 LED 颜色
if (Status==6) {
USBSerial_println_s("RGB");
USBSerial_println_i(RValue);
USBSerial_println_i(GValue);
USBSerial_println_i(BValue);
USBSerial_flush();
// 设置 WS2812 颜色, 特别注意这里和时许非常相关,具体数值都是示波器测量取得
//Send Green value from Bit7 to 0
TX(GValue);TX(GValue);TX(GValue);TX(GValue);TX(GValue);TX(GValue);TX(GValue);TX(GValue);
//Send Red value from Bit7 to 0
TX(RValue);TX(RValue);TX(RValue);TX(RValue);TX(RValue);TX(RValue);TX(RValue);TX(RValue);
//Send Blue value from Bit7 to 0
TX(BValue);TX(BValue);TX(BValue);TX(BValue);TX(BValue);TX(BValue);TX(BValue);TX(BValue);
// 重新设置为状态 0
Status=0;
}
// 根据设置的延时开始倒计时
if (Status==10) {
if ((THigh==0)&&(TLow==0)) {
digitalWrite(BEEPERPIN,LOW);
}
// 设置触发时间
else CounterDown=(THigh*256+TLow)*1000UL+millis();
Status=0;
}
// 如果当前处于状态 0 并且设置的触发时间小于当前时间,那么就开始响
if ((Status==0)&&(CounterDown<millis())) {
// 对应 Pin 拉高,蜂鸣器开始发声
digitalWrite(BEEPERPIN,HIGH);
// 拉高之后会一直发声
CounterDown=0xFFFFFFFF;
}
}
特别提一句:WS2812 有很多版本,彼此之间在时序上有差别。比如,下面两种从Datasheet看就是有差别的,前者的代码(CH55xDuino)在后者(这次设计使用的)上会有问题:
| WS2812D-F8 | WS2812B-Mini | |
| T0H | 400±150ns | 300±80ns |
| T0L | 850±150ns | 790±210ns |
| T1H | 850±150ns | 790±210ns |
| T1L | 400±150ns | 300±80ns |
| RES | >50us | >280us |
所以,我在代码上重写了 WS2812 相关部分,因为对时序要求很高,所以最好使用汇编语言,但是因为我对 C51汇编很陌生,于是只能写成宏的方式。有兴趣的朋友可以尝试修改CH55xduino的库文件。
BOM 如下,不包括PCB 在10元左右,可以看到这个芯片在制作 USB 相关设备方面很有竞争力:

虽然CH55X系列芯片有诸多好处,但是还存在如下缺点:
参考:
========================================
因为 GitHub 在访问上可能存在的问题,所以我在这里放一个 json 文件,就是说你可以在 Preferences –> Additional Boards Manager URLs 中使用下面这个地址:
http://www.lab-z.com/wp-content/uploads/2021/05/package_ch55xduino_mcs51_index.json
| Req Id | Title | Location | Link for Job Description |
| NS532 | Platform Applications Engineer, Senior Staff | Shanghai | https://jobs.jobvite.com/amperecomputing/job/o9s6cfwD |
| NS627 | Platform Applications Engineer (Software), Principal | Shanghai / Shenzhen | https://jobs.jobvite.com/amperecomputing/job/oVPbefwT |
| NS660 | Platform Applications Engineer (Software), Staff | Shanghai | https://jobs.jobvite.com/amperecomputing/job/oKFvefwS |
| NS703 | Platform Applications Engineer (Hardware) Staff | Shanghai | https://jobs.jobvite.com/amperecomputing/job/othIefwq |
| NS771 | Senior Recruiter | Shanghai | https://jobs.jobvite.com/amperecomputing/job/o5g5efwo |
| NS764 | Field Marketing Manager | Shanghai | https://jobs.jobvite.com/amperecomputing/job/oCO2efwq |
| NS775 | Sales Operations, Senior Manager | Shanghai | https://jobs.jobvite.com/amperecomputing/job/oPt6efwm |
| NS687 | Field Applications Engineer, Staff | Shanghai / Beijing | https://jobs.jobvite.com/amperecomputing/job/oTVDefwp |
| NS631 | Platform Applications Engineer (Software) Staff | Beijing | https://jobs.jobvite.com/amperecomputing/job/oWheefwp |
| NS650 | Platform Applications Engineer, Senior Staff | Taipei | https://jobs.jobvite.com/amperecomputing/job/oD3sefw6 |
| NS642 | Staff ATE Hardware Engineer | Taiwan – Virtual | https://jobs.jobvite.com/amperecomputing/job/oVXqefwg |
| NS724 | Supplier Quality Engineer, Staff | Taiwan – Virtual | https://jobs.jobvite.com/amperecomputing/job/oolNefwu |
| NS725 | Document Controller / Technical Writer, Staff | Taiwan – Virtual | https://jobs.jobvite.com/amperecomputing/job/oplNefwv |
| NS726 | Customer Quality Engineer, Staff | Taiwan – Virtual | https://jobs.jobvite.com/amperecomputing/job/oqlNefww |
| NS750 | System Level Test (SLT) Engineer, Staff | Taiwan – Virtual | https://jobs.jobvite.com/amperecomputing/job/o6uXefwv |
有兴趣的朋友可以直接联系 jun.chen@amperecomputing.com
Processor power management technologies are defined in the ACPI specification and are divided into two categories or states:【参考1】
简单的说, P-states 是 CPU 醒着的时候降低功耗(好比上班摸鱼,玩玩手机放松一下,但是随时都是醒着的),C-states 就是CPU 睡着了。
从下面的示意图可以看的更清晰




更多推荐的资料:
https://www.cnblogs.com/apnpc/p/13780146.html 【CPU】 C-State, C-模式 是什么?
https://zhuanlan.zhihu.com/p/25675639 CPU省电的秘密(二):CStates
参考:
1.https://docs.microsoft.com/en-us/previous-versions/windows/desktop/xperf/p-states-and-c-states
2.https://lenovopress.com/lp0632.pdf
3.https://www.thomas-krenn.com/en/wiki/Processor_P-states_and_C-states
=====================================================
2025年5月
玩过 Arduino 的朋友可能会注意到,再编译的时候可以设定当前单片机的电压和频率。比如,3.3V供电的时候只能使用 8Mhz 的主频,但是如果 5V 供电时可以使用 16Mhz的频率。如果在 3.3V 时强制使用 16Mhz 的主频,那么很可能会遇到不稳定等等奇怪的问题。
对于 CPU 来说同样如此,较低的频率只能使用较低的频率。因此,可以制作一张表格,标注不同电压对应的频率,当需要省电时,切换到较低电压后可以切换到较低的频率。这就是 P-State 省电的原理。
声音,是物体振动产生的,通常情况下通过空气将震动传递到耳朵我们就能听到了声音。常见的喇叭就是一种将电能转换为机械能的器件。
FireBeetle 核心是 ESP32-WROOM-32D, 主控频率高达240Mhz, Flash Rom 有 16MB。这里介绍一种直接通过它来播放声音的方法。用这种方法可以直接通过喇叭来播放音乐,但是因为 ESP32 输出功率有限,直接推动喇叭产生的声音很小。为此,这里使用“Gravity: 带功放喇叭模块”来实现更大的声音输出。

基本原理是将数据存储在 FireBeetle 的Flash 上,然后通过 ESP32 的DAC 直接输出之。
具体步骤如下:
第一步,将音乐转化为8位单声道。我是用 Golden Wave ,将罗大佑的 “恋曲1990.mp3“转为 8000Hz 8 Bits的Wav 格式。



注意,这里必须重新采样为 8000Hz。直接另存为的话实际上并没有设置为 8000Hz。


重新加载处理后的 8000Hzm 8Bits Wave 音频数据
第二步,使用Bin2C 将这个 Wav 生成C 语言的头文件。命令如下:
bin2c.exe -o audio.h 19908bits.wav
实际上数据中包含了 wav的文件头,但是因为数据量不大,对播放几乎没有影响,所以这里也没有额外处理。

这样,我们就有了这个歌曲的数据。特别需要注意的有下面两点:
1.需要设置编译模式为 3MB APP/9MB FATFS 模式

2.原始的 Wav 最好不要超过 2.7MB(2,831,155.2Bytes, 0x2B3333Bytes),否则会超过最大程序的限制。这次我使用的 WAV 是 2,527,766 字节的,编译后结果如下:
Sketch uses 2746845 bytes (87%) of program storage space. Maximum is 3145728 bytes.
Global variables use 15372 bytes (4%) of dynamic memory, leaving 312308 bytes for local variables. Maximum is 327680 bytes.
第三步,我们需要一些基本的测试。首先,编写一个循环,使用 dacwrite() 输出数值,输出2000000次花费了11102ms ,也就是说一次 dac 输出需要花费 0.005551ms。前面提到使用的 Wav采样率是 8000Hz。这样一个周期是 1/8000=0.125ms。 因此,为了重建一个声音,我们需要 0.125/0.005551=22.5184次 dacwrite。
第四步,编写代码:
#include "audio\SoundData.h"
void setup() {
}
void loop() {
for (unsigned int i=0;i<2527766;i++) {
for (int j=0;j<21;j++) dacWrite(25,WarOfWorldsWav[i]);
}
}
我们将音频数据放在audio目录下的SoundData.h文件中,这样每次Arduino 打开ino 文件的时候并不会一起打开音频数据,否则因为音频数据很大,会耽误很多时间,有时候甚至会导致Arduino 编译器崩溃。另外,和前面提到的22次 dacwrite 不同,代码使用的是21次,因为for循环有一些开销,所以实际测量下来22次有一点点慢。
第五步,Gravity: 带功放喇叭模块,上面有3个线,VCC和GND 连接到 FireBeetle上,信号输入Pin 连接到 FireBeetle D2 (IO25)。
下载代码后即可播放出音乐了。
很明显,上述方法足够简单,能够存储 2.7*1024*1024/8000=354秒的音频。在要求不高的场合下完全能够满足要求。

8Bits Wave 格式的歌曲:
完整的代码和数据:
工作的视频:
https://www.bilibili.com/video/BV1Df4y1x7WF?share_source=copy_web
前面介绍了FireBeetle 通过 DAC 来播放音频,除此之外,还可以使用 PWM 方式来播放音频。
关于 PWM动力老男孩在“Arduino系列教程之 – PWM的秘密(上)”【参考1】有介绍,对于我们来说,能用到的就是下面这一段:
PWM是用占空比不同的方波,来模拟“模拟输出”的一种方式。靠,这个太拗口了,简而言之就是电脑只会输出0和1,那么想输出0.5怎么办呢?于是输出01010101….,平均之后的效果就是0.5了。早这么说就了然了嘛。

比如,当前最高电压是5V,如果输出50%的PWM信号,可以当作 2.5V 的信号输出。对于 ESP32来说,有对 PWM 的直接支持【参考2】。
Arduino core for the ESP32 并没有一般 Arduino 中用来输出 PWM 的 analogWrite(pin, value) 方法,取而代之的 ESP32 有一个 LEDC ,设计是用来控制 LED 。
ESP32 的 LEDC 总共有16个路通道(0 ~ 15),分为高低速两组,高速通道(0 ~ 7)由80MHz时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动。
对于我们来说,用到的函数有下面3个:
ledcSetup(uint8_t channel, double freq, uint8_t resolution_bits)
分别设定使用的通道(Channel),PWM 的频率, PWM 的分辨率。比如,我们设定 1Hz 的频率,然后分辨率为4Bit,那么就可以设置从 0 到15,一共16个PWM值。可以看出,分辨率越高,可以细分出更多的 PWM值。
ledcWrite(uint8_t channel, uint32_t duty)
对通道设定当前的占空比(duty)
ledcAttachPin(uint8_t pin, uint8_t channel)
将 LEDC 通道绑定到指定 IO 口上
这样,我们就得到了一个和之前 DAC 很像的代码:
#include "audio\SoundData.h"
int freq = 8000*256; // 频率
int channel = 0; // 通道
int resolution = 8; // 分辨率
const int led = 25;
void setup() {
ledcSetup(channel, freq, resolution); // 设置通道
ledcAttachPin(led, channel); // 将通道与对应的引脚连接‘
Serial.begin(115200);
}
void loop() {
for (unsigned int i=0;i<2527766;i++) {
ledcWrite(channel, WarOfWorldsWav[i]);
delayMicroseconds(120);
}
}
完整的代码和数据下载:
前面提到了,PWM 支持更高的分辨率,因此,我们可以尝试播放16Bits 的音频。最简单的想法,直接将频率设定为 8000Hz,然后分辨率为16位。但是实际测试下来这样无法工作,经过研究,频率和分辨率之间有一定的限制关系【参考3】。在 8000Hz 下能够达到最高分辨率是 13bits。最终实验表明使用 12Bits 分辨率 8000Hz 可以接收,再高噪音会较大。此外,16bits 的 WAV 和 8Bits 的还有一个很大的区别在于:前者是有符号数值,后者是无符号数值。比如:0x8001 实际上表示的是 -1。因此代码中取出数值后需要加上 0x8000 再做处理。因此代码中取出数值后需要加上 0x8000 再做处理。另外,因为 16Bits 相对于 8Bits 数据量是直接翻倍了,导致无法在 Flash 中放下全部文件,为此,这次实验用到的16Bits音频数据只是截取了部分歌曲。
参考:
The maximum PWM frequency with the currently used ledc duty resolution of 10 bits in PWM module is 78.125KHz.
The duty resolution can be lowered down to 1 bit in which case the maximum frequency is 40 MHz, but only the duty of 50% is available.
For duty resolution of 8 buts, the maximal frequency is 312.5 kHz.
The available duty levels are (2^bit_num)-1, where bit_num can be 1-15.
The maximal frequency is 80000000 / 2^bit_num
In my MicroPython implementation, I'm currently working on enabling user selectable and/or automatic duty resolution and higher maxumum frequencies.
4.手册上有描述

当出现 Windows 中热键冲突的时候,你可以使用 OpenArk 来揪出罪魁祸首。比如,运行之后,可以在“系统热键”中看到当前系统中所有的热键:

这是开源软件,可以在下面的地址下载到: