2025年2月更新,Step to UEFI 文章索引:
VirtualBox Windows 11 的 DSDT
最近在 VirtualBox 上进行ACPI 方面的试验,经过努力 Dump 出来 DSDT 的 ASL ,然后手工加入了一个自定义设备:
//LABZ_Debug_Start
Device (LABZ)
{
Name (_HID, EisaId ("LAB33D6") /* Intel Virtual Buttons Device */) // _HID: Hardware ID
Method (_STA, 0, Serialized) // _STA: Status
{
Return (0x0F)
}
}
//LABZ_Debug_End
有需要的朋友可以基于这个版本修改增加你需要的功能:
CH32V305 使用 UART3 的例子
根据 Ch32V307 的 “USART_Printf:串口打印调试例程” 修改而来。对于Ch32V305来说,UART3 在电路图如下位置:

Arduino 代码如下:
void zUSART_Printf_Init(uint32_t baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = baudrate;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
USART_Cmd(USART3, ENABLE);
}
void OutUart(char *buf, int size)
{
for(int i = 0; i < size; i++)
{
while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
USART_SendData(USART3, *buf++);
}
}
void setup() {
zUSART_Printf_Init(115200);
}
void loop() {
char sMsg[]="www.lab-z.com";
OutUart(sMsg,sizeof(sMsg));
delay(1000);
}

// USART3 缓冲区最大长度
#define RXBUFFER3SIZE 100
// USART3 缓冲区
u8 RxBuffer3[RXBUFFER3SIZE];
// USART3 缓冲区内数据长度
volatile u8 RxCnt3 = 0;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
void USART2_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
void USART3_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
/*********************************************************************
@fn USARTx_CFG
@brief Initializes the USART2 & USART3 peripheral.
@return none
*/
void USARTx_CFG(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
USART_InitTypeDef USART_InitStructure = {0};
NVIC_InitTypeDef NVIC_InitStructure = {0};
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/* USART3 TX-->B.10 RX-->B.11 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART3, ENABLE);
}
void setup() {
// 这次的测试是从 USART3 接收,然后将收到的数据从 USART1 送出
Serial.begin(115200);
Serial.println("Start");
USARTx_CFG();
Serial.println("Init Done");
}
void loop() {
if (RxCnt3!=0) {
for (int i=0;i<RxCnt3;i++) {
Serial.write(RxBuffer3[i]);
}
RxCnt3=0;
}
delay(1000);
}
/*********************************************************************
* @fn USART3_IRQHandler
*
* @brief This function handles USART3 global interrupt request.
*
* @return none
*/
void USART3_IRQHandler(void)
{
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)
{
RxBuffer3[RxCnt3++] = USART_ReceiveData(USART3);
}
}

Offline 安装 .Net3.5
1.挂载一个 Windows 镜像
2. CMD 窗口运行如下命令:
Dism /online /enable-feature /featurename:NetFX3 /All /Source:D:\sources\sxs /LimitAccess
关闭Chrom禁止加载本地文件的方法
在调试 JavaScript 的时候,默认情况下是无法加载本地的图片或者其他文件的。这样对于调试造成了一定困扰,一种给解决方法是启动一个本地的Http Server 然后即可调试;此外还可以通过关闭浏览器的安全设置来实现。根据【参考1】,可以在命令行下运行如下命令:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --allow-file-access-from-files --user-data-dir="C:\temp" --disable-web-security
在新开的窗口中打开你的 html 文件即可。
参考:
2025上海慕尼黑电子展参观记
2025年4月15日,慕尼黑上海电子展在上海新国际博览中心如期举行。

展览位于地铁花木路出口,我在上午十点到达的,提前已经注册好,查验二维码,刷身份证直接进入参观。

门口矗立着各家的广告牌,有 DigiKey 的,还有 TI 的。

还有WCH “含蓄”的广告牌。

含蓄之处在于特别强调了一款成本只要0.58的RISC-V 2.4G无线MCU/SoC,相信因为带有无线功能,可玩性会较高。

第一站去的是位于W5 展馆517展位的JLC嘉立创&中信华的展台。嘉立创集团在本次展会有2个展台,一个位于W5展馆,这是连接器、开关、线束线缆展区以及PCB展区;另外一个是位于N2展馆的619展台。前者属于嘉立创EDA和PCB部门,后者是立创商城部门。嘉立创集团打通了EDA到零售,对于我这样的DIY用户非常友好,使用EDA 设计可避免元件尺寸不合适导致无法使用的问题(这种问题在插接元件上经常会遇到,比如,同样都是USB插头,固定引脚的位置往往差别很大)。

近期立创商城正在进行招商,有兴趣的朋友可以联系他们看看能否成为他们的供应商共同成长。

第二站,前往了位于N5 展馆(半导体展区&人工智能联合展区) 509 的沁恒微的展台。这次带来的主要展品是各种基于RISC-V的MCU和SoC产品,融合了USB/蓝牙/以太网多种接口;此外还有很多Type-C 接口的设备,比如,桥接隔离设计,USB3.0 Hub,双 Type-C 显示设备等等。有USB和 Type-C 特殊设计需求的朋友不妨前往参观,并且可以和现场工作人员进行沟通。

ST展台前依然人头攒动,但是因为价格向上波动的可能,在选型时工程师需要三思。相同的性能可以考虑从国产MCU中进行选择。

一博科技,在去年的文章中介绍过,能够提供从PCB设计到制造一条龙服务的企业。我们是他的客户,多次与他们合作生产研发用途的笔记本主板。

一条龙的服务,能够帮助客户节省成本,缩短研发验证周期。特别的,如果你有更换 x86 SoC的需求也可以联系他们。比如,手上是 ES 的 SoC,拿到 QS SOC之后,可以找他们进行更换。方便后续的测试。

本次展览还看到 JBC 的展台,这是非常优秀的焊接品牌,很多大厂选择他们的设备作为焊接、修理的专业设备。

这是焊接台:

这是小型锡炉:

德州仪器(TI),在模拟领域仍然是庞然大物,地位暂时无人能够撼动:

作为BIOS工程师,少不了和 GigaDevice打交道,它家的SPINOR 芯片经过了时间的检验是很多主板厂商的首选。这次他们带来了车规级的 SPI NOR Flash 产品,使用 45-55nm工艺。

此外还有DDR3L、DDR4、LPDDR4X的产品(消费级)展示。和现场工作人员咨询得知,目前还没有DDR5系列产品。

作为经常DIY的玩家,我更倾向于国内厂商,因为沟通更加便利。比如,如果你要咨询 AD产品,遇到的第一个问题就是他们会先和你“盘道”,问你从哪里购买的元件,然后就以购买厂商不是许可供应商为由拒绝你。

高云是这几年出现的国产FPGA厂商

从拿到的手册来看,他家产品线日渐丰富,现场展示的有视频桥接芯片:

这次看到了 Murata(村田) 的展位,知道这个品牌的原因是这家公司为使用他们无源器件的客户提供了EMC免费测试服务。我们之前使用过他们这个服务,预约之后即可前往他们的公司进行测试。虽然他们不会提供标准的认证,但是这种EMC测试服务能够帮助客户节省金钱和时间。

除了无源器件,展会上Murata还带来了一些特别的产品,比如下面的透明ID标签:

有兴趣的朋友可以关注一下:


广州市星翼电子科技有限公司(正点原子)也过来参加本次展会:

展示了他们设计生产的很多设备:

这一款是目前最强的逻辑分析仪DL32(USB3.0接口,现场询价在一千左右),工作人员推荐个人用户选择更有性价比的DL16:

上海冠显光电(TDO)是专注于显示设备的公司,专注于0.32”至55”显示产品研发,能够提供显示屏以及转接芯片等等设备和方案。

MPS 这个品牌我多次在B站笔记本电脑维修大佬的口中听到:

展会上也有很多小型参展商,比如,下面这一家初看我还以为是做游戏手柄的,实际上是工业手柄。

如此多造型各异的手柄让我有着改装游戏的冲动:


在深圳鹈鹕科技(www.sztihu.com)的摊位现场看到了这种 POGOPIN 连接器。后面可以考虑这种连接器制作下载线以及充电头。

展会将会从4月15日到17日,有兴趣的朋友仍然有机会前往参观。
基于FireBeetle 2 实现 VGA视频输出
之前有使用 FireBeetle(ESP32)实现过 VGA 输出,这次基于 FireBeetle 2 ESP32-S3 的板卡来实现 VGA 的输出。
首先进行电路的设计,其中的Hsync和Vsync分别使用 IO1 和 IO2,颜色则通过 R-2R 来实现:

PCB 设计如下:

3D预览如下:

图中的USB母头是预留给外部供电的,比如,VGA-HDMI转接器通常需要外部供电,这种情况下可以从这个端口取电。
焊接之后插入 FireBeetle 2 的板子(注意USB接口方向和 VGA接口方向相反):

接下来使用来自 https://github.com/spikepavel/ESP32-S3-VGA 的库
运行如下代码
#include "VGA.h"
#include <FONT_9x16.h>
VGA vga;
int scale = 2;
void setup()
{
// r,r,r,r,r, g,g, g, g, g, g, b, b, b, b, b, h,v
const PinConfig pins(4,5,6,7,8, 9,10,11,12,13,14, 15,16,17,18,21, 1,2);
Mode mode = Mode::MODE_640x480x60;
if(!vga.init(pins, mode, 8, 3)) while(1) delay(1);
vga.start();
for(int y = 0; y < 480; y++)
for(int x = 0; x < 640; x++)
vga.dotdit(x, y, x, y, 255-x);
vga.setFont(FONT_9x16);
vga.start();
delay(5000);
}
void loop()
{
vga.clear(0);
delay(1000);
for(int count = 0; count < 100000; count++)
vga.dot(rand()%640, rand()%480, rand()%255);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.line(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.tri(rand()%640, rand()%480, rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillTri(rand()%640, rand()%480, rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.rect(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillRect(rand()%640, rand()%480, rand()%640, rand()%480, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.circle(rand()%640, rand()%480, rand()%100, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 5000; count++)
vga.fillCircle(rand()%640, rand()%480, rand()%50, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 10000; count++)
vga.ellipse(rand()%640, rand()%480, rand()%100, rand()%100, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
vga.fillEllipse(rand()%640, rand()%480, rand()%100, rand()%100, rand()%255);
vga.clear(0);
delay(1000);
vga.clear(0);
delay(1000);
for(int count = 0; count < 100000; count++)
vga.mouse(rand()%640, rand()%480);
vga.clear(0);
delay(1000);
for(int count = 0; count < 1000; count++)
{
static int c = 0;
static int d = 1;
c += d;
if (c == 0 || c == 255)
d = -d;
char text[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0
};
for (int i = 0; i < 256; i++)
text[i] = 33 + (i + (c >> 2));
vga.setCursor(8, 48);
vga.setTextColor(vga.rgb(c, 255 - c, 255), vga.rgb(0, c / 2, 127 - c / 2));
vga.print(text);
vga.setCursor(8, 148);
vga.print(text);
vga.setCursor(8, 248);
vga.print(text);
vga.setCursor(8, 348);
vga.print(text);
}
delay(4000);
}
实践测试发现,这个板卡在不同显示器上会有兼容性问题,可能和选择的分辨率及其参数有关系。
工作的测试视频【FireBeelte 2 ESP32-S3 VGA 输出测试】 https://www.bilibili.com/video/BV1yrzjY4EmG/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54
Three实现旋转地球效果的例子
本文根据 https://blog.csdn.net/m0_51180924/article/details/144498003 编写,实现了旋转地球的效果。
代码如下,如果想测试需要本地补充three.js,jquery.js和CCapture.all.min.js,在【参考1】文章末尾可以下载到(示例代码保存为 html 放入带有 JS文件的目录即可)
<html>
<head>
<meta charset="UTF-8">
<!-- <script src="http://threejs.org/build/three.js"></script>-->
<!-- <script src="http://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>-->
<script src="ThreeJS/three.js"></script>
<script src="ThreeJS/jquery.js"></script>
<script src="CCapture/CCapture.all.min.js"></script>
</head>
<body>
<center id="myContainer"></center>
<script>
//创建渲染器
var myRenderer = new THREE.WebGLRenderer({
antialias: false,
preserveDrawingBuffer: true
});
document.body.appendChild(myRenderer.domElement);
myRenderer.setPixelRatio(window.devicePixelRatio);
myRenderer.setSize(window.innerWidth, window.innerHeight);
$("#myContainer").append(myRenderer.domElement);
var myClock = new THREE.Clock();
var myCamera = new THREE.PerspectiveCamera(60,
window.innerWidth / window.innerHeight, 1, 10000);
myCamera.position.set(0, 100, 300);
myCamera.lookAt(new THREE.Vector3(0, 0, 0))
var myScene = new THREE.Scene();
myScene.background = new THREE.Color('white');
myScene.add(new THREE.AmbientLight(0xFFFFFF, 1.0));
//创建球体(地球)
var myGeometry = new THREE.SphereBufferGeometry(120, 64, 64);
var myMap = new THREE.TextureLoader().load('image/img007.png');
var myMaterial = new THREE.MeshPhongMaterial({
map: myMap
});
var myMesh = new THREE.Mesh(myGeometry, myMaterial);
myScene.add(myMesh);
let isAnimating = true; // 动画状态标志
// 初始化捕获器(以 WebM 格式为例)
const capturer = new CCapture({
format: 'webm',
framerate: 30,
verbose: true
});
// 开始捕获
capturer.start();
//渲染(旋转)球体(地球)
animate();
function animate() {
if (!isAnimating) return; // 停止动画时退出循环
requestAnimationFrame(animate);
var delta = myClock.getDelta();
myRenderer.render(myScene, myCamera);
//按照设置的角度增量实现绕y轴旋转地球
myMesh.rotation.y += delta / 5;
// 捕获当前帧
capturer.capture(myRenderer.domElement);
// 旋转超过一圈(2π)时停止
if (myMesh.rotation.y >= Math.PI * 2) {
isAnimating = false; // 停止动画
console.log("旋转完成!");
capturer.stop();
capturer.save();
}
}
</script>
</body>
</html>
最终完整的代码下载:
工作的视频
参考:
CCapture 录制 ThreeJs的例子
使用 ThreeJs 制作一个旋转的立方体,然后录制它沿着Z轴旋转一圈。代码如下:
<html>
<head>
<meta charset="UTF-8">
<!-- <script src="http://threejs.org/build/three.js"></script>-->
<!-- <script src="http://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>-->
<script src="ThreeJS/three.js"></script>
<script src="ThreeJS/jquery.js"></script>
<script src="CCapture//CCapture.all.min.js"></script>
</head>
<body>
<script>
// 初始化场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({
antialias: true,
preserveDrawingBuffer: true
});
// 设置渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建红色立方体
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.8
});
const cube = new THREE.Mesh(geometry, material);
// 添加绿色边框
const edges = new THREE.EdgesGeometry(geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({
color: 0x00ff00
}));
// 组合对象
const cubeWithEdges = new THREE.Group();
cubeWithEdges.add(cube);
cubeWithEdges.add(line);
scene.add(cubeWithEdges);
// 设置相机角度
camera.position.set(5, 5, 5); // 45°倾斜视角[^1]
camera.lookAt(0, 0, 0);
// 初始化捕获器(以 WebM 格式为例)
const capturer = new CCapture({
format: 'webm',
framerate: 30,
verbose: true
});
// 开始捕获
capturer.start();
let cap=true;
console.log("start");
animate();
// 动画循环
function animate() {
if (cubeWithEdges.rotation.z<Math.PI * 2) {
requestAnimationFrame(animate);
// Z轴旋转
cubeWithEdges.rotation.z += 0.01;
renderer.render(scene, camera);
// 捕获当前帧
capturer.capture(renderer.domElement);
} else {
if (cap==true) {
capturer.stop();
capturer.save();
cap=false;
}
}
}
</script>
</body>
</html>
上述代码生成的视频:
完整的代码和JS文件下载:
赤道,南北回归线KML文件分享
FireBeetle(ESP32)测试 HT513
HT513 是一款国产的D类单声道I2S 功放芯片。特别之处在于它支持通过 I2C 接口控制输出音量,因此可以在保证输出效果的情况下极大简化软件设计。
首先设计一个 HT513的功能板:

PCB设计如下:

焊接完成后,可以直接在面包板上使用。
这次测试DFRobot 的 FireBeetle 通过 I2S 接口发送音频数据给 HT513, 最终通过喇叭将音频播放出来:

HT513模块 | FireBeetle | 说明 | 说明 | FireBeetle | HT513模块 | |
GND | GND | 地 | 数字电源 | 3V3 | VCC33 | |
SD# | N/A | 接地关闭功放 | 地 | GND | GND | |
SDA | IO21 | I2C 数据 | 芯片错误 | N/A | FAULT | |
SCL | IO22 | I2C 时钟 | 模拟参考电压 | N/A | BYPASS | |
MCK | IO0 | I2S主时钟 | 功放负输出 | N/A | LOUTN 接喇叭 | |
BCLK | 14 | I2S串行时钟 | 地 | GND | GND | |
DIN | 18 | I2S数据 | 功率电源 | VCC | PVDD | |
LRCLK | 15 | 帧时钟 | 功放正输出 | N/A | LOUTP 接喇叭 |
测试使用 AudioTools 库(arduino-audio-tools-1.0.0)。特别注意: HT513 工作时需要 MCLK 信号,因此需要修改库文件 AudioConfig.h:
#define PWM_FREQENCY 30000
#define PIN_PWM_START 12
#define PIN_I2S_BCK 14
#define PIN_I2S_WS 15
#define PIN_I2S_DATA_IN 32
#define PIN_I2S_DATA_OUT 18
#define PIN_I2S_MCK 0
#define I2S_USE_APLL true
// Default Setting: The mute pin can be switched actovated by setting it to a gpio (e.g 23). Or you could drive the LED by assigning LED_BUILTIN
#define PIN_I2S_MUTE -1
#define SOFT_MUTE_VALUE 0
#define PIN_CS SS
#define PIN_ADC1 34
测试的代码是基于这个库自带的两个例程,一个是生成正弦波送至I2S接口(streams-generator-i2s),我们在这里测试评估 HT513音量调整的功能;另外一个是播放存储在 Flash 中的WAV文件(streams-memory_raw-i2)。在例子的寄存上增加对于 HT513初始化设定的代码。关键部分如下:
#define HT513_ADDR_L 0x6c
/**
@brief ht513写寄存器
@param addr 寄存器地址
@param val 要写的值
@retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.write(val);
Wire.endTransmission(true);
}
/**
@brief ht513读寄存器
@param addr 寄存器地址
@retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
uint8_t temp = 0;
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.endTransmission(true);
Wire.requestFrom(HT513_ADDR_L, (uint8_t)1);
temp = Wire.read();
return temp;
}
在Setup() 中加入下面的代码:
Wire.begin(21,22);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12,0b11110000);
// 设置数据格式为 I2S, 16Bits
HT513_WriteOneByte(0x13, 0b00110000);
// 调整音量
HT513_WriteOneByte(0x16, 0b10100000);
// 设置 SD 为LOW
HT513_WriteOneByte(0x12,0b11110100);
其中的HT513_WriteOneByte(0x16, XX) 是调整音量的代码:

换句话说,有了上面的代码就可以完整的发挥出 HT513的功能了。
最终完整的代码streams-generator-i2sHT513.ino 如下:
#include <Wire.h>
#include "AudioTools.h"
AudioInfo info(44100, 1, 16);
SineWaveGenerator<int16_t> sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000
GeneratedSoundStream<int16_t> sound(sineWave); // Stream generated from sine wave
I2SStream out;
StreamCopy copier(out, sound); // copies sound into i2s
#define HT513_ADDR_L 0x6c
/**
@brief ht513写寄存器
@param addr 寄存器地址
@param val 要写的值
@retval None
*/
void HT513_WriteOneByte(uint8_t addr, uint8_t val)
{
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.write(val);
int ack = Wire.endTransmission(true);
Serial.print("Ack ");
Serial.println(ack, HEX);
}
/**
@brief ht513读寄存器
@param addr 寄存器地址
@retval 读取到的寄存器值
*/
uint8_t HT513_ReadOneByte(uint8_t addr)
{
uint8_t temp = 0;
Wire.beginTransmission(HT513_ADDR_L);
Wire.write(addr);
Wire.endTransmission(false);
uint8_t bytesReceived = 0;
bytesReceived = Wire.requestFrom(HT513_ADDR_L, (uint8_t)1, true);
if (bytesReceived == 1) {
temp = Wire.read();
}
else {
Serial.println("Read Error ");
}
return temp;
}
// Arduino Setup
void setup(void) {
Wire.begin(21,22);
// Open Serial
Serial.begin(115200);
while (!Serial);
AudioLogger::instance().begin(Serial, AudioLogger::Info);
// start I2S
Serial.println("starting I2S...");
auto config = out.defaultConfig(TX_MODE);
config.copyFrom(info);
out.begin(config);
// Setup sine wave
sineWave.begin(info, N_B4);
Serial.println("started...");
int nDevices;
byte error, address;
Serial.println("Scanning...");
nDevices = 0;
for( address = 1; address < 127; address++ ) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
nDevices++;
}
else if (error==4) {
Serial.print("Unknow error at address 0x");
if (address<16) {
Serial.print("0");
}
Serial.println(address,HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
}
else {
Serial.println("done\n");
}
// 设置 SD 为LOW
HT513_WriteOneByte(0x12, 0b11110000);
// 设置数据格式为 I2S, 16Bits
HT513_WriteOneByte(0x13, 0b00110000);
// 调整音量
HT513_WriteOneByte(0x16, 0b01111111);
// 设置 SD 为HIGH
HT513_WriteOneByte(0x12, 0b11110100);
uint8_t Value = HT513_ReadOneByte(0x12);
Serial.println("++++++++++++++++");
Serial.println(Value, HEX);
}
// Arduino loop - copy sound to out
void loop() {
copier.copy();
}
本文提到的HT513 测试板电路图和 PCB:
工作的视频在: