你好,我是吴老师。
这份是给你自己读的——不用等我讲,你一段一段往下看,看到让你停下来的地方,就照着做一遍。看不懂的整段复制给 AI,加一句"请一次只解释一个概念,讲完问我一个小问题"。
读完之后,你应该能:说出扬声器发声的完整链条;解释"播放声音 = 按固定节奏送数字";说出 DAC 和采样率是干什么的;亲手把 440Hz 的声音改成 880Hz,并预测听感变化;说清"边播边动"为什么要把长任务切成小块。
用时:60–90 分钟。准备:板子、数据线、已经接入板卡 Skill 的 WorkBuddy。
一、从"看见"到"听见"
前八课,我们让板子做了很多事:灯变色、屏幕画画、动画跑起来。它们全都有一个共同点——都是让你看的。
今天做一件新事:让板子发出声音。
先想一个问题:板子上没有嘴巴,它怎么"说话"?
答案看起来简单:有喇叭。但这只是第一层。第二层问题才是真的:喇叭凭什么发出"滴——"的一声?
再往深一层:板子里没有声音,只有 0 和 1。你让 AI"播放一个声音",那些 0 和 1 是怎么变成你耳朵里听到的声音的?
今天这节课的目标很小:让板子响一声"滴",然后你改一个数值,让音调变高变低。
你会发现,声音一点都不神秘——它只是"被送得很快的数字"。
二、先让它响起来
请使用课程的 esp32s3-touchboard-hardware-dev Skill,
让板子通过扬声器播放一个 440Hz 的"滴"声,持续 0.8 秒,
音量用中等偏低的音量,播完就停。
播放前先检查板卡的音频芯片(ES8311)和功放初始化方式。
帮我新建程序并烧录。
烧录完,先别急着调来调去。把你听到的写下来:
| 我听到的现象 | 勾选 |
|---|---|
| 一声清晰的"滴" | |
| 声音很轻 | |
| 完全没有声音 | |
| 有咔哒杂音 |
三、声音是什么:一面看不见的鼓
声音不是"东西",是振动。
想象一面鼓:你推一下鼓面,它往下凹;弹回来时挤压前面的空气;空气再推下一层空气。这一层推一层的波动传到耳朵,撞上耳膜,你就"听到"了。
喇叭就是一面"电动的鼓":电压快速变化,纸盆就跟着快速振动,把空气推出一层层波。
| 名字 | 通俗说法 | 决定什么 |
|---|---|---|
| 频率 | 纸盆一秒来回推几次 | 音调(推得快 = 声音尖) |
| 振幅 | 推得多用力 | 响度(推得狠 = 声音大) |
| 波形 | 推的节奏长什么样 | 音色(钢琴 vs 小提琴) |
频率单位是 Hz,意思是"每秒多少次"。440Hz = 每秒推 440 次。
你肯定玩过这个实验:拨橡皮筋,绷得越紧,声音越尖——那就是频率变了。
亲手转一转这三个旋钮——边拖边听,看看耳朵听到的和眼睛看到的怎么对上:
⚠️ 比喻有边界:喇叭不是简单"推空气"。它里面有个线圈,靠电流在磁场里受力推动纸盆。这门课不钻这个,你记住链条就行:电压变 → 纸盆动 → 空气推 → 耳朵听。
四、板子里没有声音,只有数字
纸盆需要"快速变化的电压"才动。ESP32-S3 只会算数字,怎么造出会变化的电压?
靠一个小东西:DAC(数模转换器)。它只干一件事——把一个数字变成对应的电压。
数字大 → 电压高;数字小 → 电压低。
于是播放声音变成了一件简单的事:
按固定节奏送数字 → DAC 把数字变成电压 → 电压推纸盆 → 声音出来。
这块板上,DAC 不在 ESP32 里,而在另一颗芯片 ES8311 里。它叫音频"编解码芯片"(codec)。今天只用它一半的本事(DAC,把数字变电压);第 12 课我们会用到它另一半(ADC,把电压变数字)。
五、采样率:每秒送几个数字
"按固定节奏"——多快?这个速度叫采样率。
16000Hz(16kHz)采样率 = 每秒送 16000 个数字。
快与慢,先拿耳朵量一量
先猜一猜,再实验。同一批 440Hz 的数字,用三种速度送出去,听起来会怎样?
| 我改了什么 | 我预测 | 实际听到 |
|---|---|---|
| A 正常送(每秒 16000 个) | ||
| B 每个数字送两遍(送得慢了) | ||
| C 隔一个送一个(送得快了) |
把这段发给 AI:
请做一个对照实验,用同一批 440Hz 的声音数据,三种送法各播一遍:
A 正常送;
B 每个数字连送两遍;
C 每隔一个数字送一个。
每种之间停 1 秒供我对比,循环两遍。其余什么都不改。帮我烧录。
你会听到:A 正常,B 变沉,C 变尖。
💡 第一个重要发现:同一批数字,送得快音调就高,送得慢音调就低。 数字一个都没改,只是送的速度变了。
但请注意一个细节:时长也跟着变了
再听一遍,这回别只听音高,注意每个音持续了多久:
| 送法 | 音调 | 时长 |
|---|---|---|
| A 正常送 | 正常 | 正常 |
| B 每个送两遍 | 变低 | 变长(拉成两倍) |
| C 隔一取一 | 变高 | 变短(缩成一半) |
音调和时长,永远一起变。 这不是巧合——B 就是"这盘磁带放慢一倍",C 就是"放快一倍"。老式录音机快没电时,歌声会又沉又慢,就是这个道理。
想深一层:那能不能"音调变低、但时长不变"?
先自己想,再往下看。
如果你手里只有一个办法——改变送数字的速度——那做不到,因为音调和时长被绑死了:慢下来,两个一起变。
那真实世界里的音频软件是怎么做到的?(比如把一首歌升调但速度不变、或者变声器把你的声音变粗但语速不变。)
这需要一个更聪明的算法,它的思路大致是:
- 把声音切成一小段一小段(比如每段 20 毫秒);
- 挪动这些小段之间的位置——要让声音变长,就重复一些段;要让声音变短,就丢掉一些段;
- 但每一小段内部的波形保持原样不动。
这样时长变了,而每段内部的振动快慢没变——音调就保住了。
这个算法今天不用你写,你只要知道一件事: "变调"和"变速"本来是绑在一起的,想要拆开它们,就得动脑筋。
而这正是本课的名字"让板子开口"背后真正的功夫——板子只会送数字, 但怎么送、送什么,全是人想出来的。
采样率到底是什么
聊完"送得快慢",我们再回到采样率。它其实是另一个问题:
采样率 = 每秒为这段声音记录多少个点。 它决定的是——这段录音最高能还原到多高的音。
为什么?因为一个波至少要两个点才能画出来(一个表示"上去",一个表示"下来")。所以:
能还原的最高音 ≈ 采样率 ÷ 2
16000Hz 采样 → 最高能还原 8000Hz 的音
8000Hz 采样 → 最高只能还原 4000Hz 的音
人的说话声,主要能量在 3000Hz 以下。所以 16kHz 采样够用——电话、语音助手常用它,是为省数据、又够听清,不是因为音调。
⚠️ 这里最容易搞混,一句话分清:
你动的旋钮 变的是什么 送数字的快慢 音调(快=尖)—— 上面 A/B/C 实验玩的就是它 采样率的高低 能还原多高(上限 = 采样率 ÷ 2)、以及音质 一句话记住:"送数字的快慢"是播放这一端的事,"采样率"是记录那一端的事。 两个是分开的旋钮,别混成一个。
自己拖一拖采样率——点越来越稀的时候,还原出来的波还像原来那个音吗?
拖到最左边会发生什么? 波"变形"了——不再是你原来那个音。 这就是采样率不够的后果:录回来的东西变了样。
再感受一遍那句话:拖滑杆的时候,音调本身一次都没变(我们没动播放速度), 变的只是"这段录音最高能还原到多高的音"。采样率 ≠ 音调。
六、一条完整的声音流水线
把学过的东西连起来:
你生成数字(正弦波采样)
↓ 把数字装进缓冲区
按节奏送出(采样率,比如 16000/秒)
↓
ES8311 的 DAC:数字 → 电压
↓
功放放大电压
↓
扬声器纸盆振动
↓
空气一层层推
↓
你的耳朵
用中文伪代码写同一件事:
准备"滴"声(440Hz,0.8 秒):
一共 0.8 × 16000 = 12800 个数字
第 i 个数字 = 正弦波在 i/16000 秒那一刻的高度
播放:
把这些数字按 16000 个/秒的节奏送出去
正弦波那行公式看不懂没关系,它只是"算出一个会上下起伏的数列"。真正要记住的是:数字攒好,按节奏送出,声音就有。
七、动手:拖一根滑杆,听音调滑上去
到这一步,你已经听过 440Hz 和 880Hz 两个音了。
但改一次频率就要重烧一次程序,一次只能听两个点——太慢了,而且你会以为音调是从"这个音"跳到"那个音"。
现在我们换个玩法:给板子装一根滑杆,手指按住它左右拖,频率跟着手指走。拖的时候你会听到一个连续变化的声音。
先把这段发给 AI:
请做一块"频率滑杆":屏幕中间画一根横向滑杆,
手指按住它左右拖动,就改变正在播放的正弦波频率。
频率范围 220 到 880 Hz,屏幕上要实时显示当前的频率数字;
滑杆下方画一个波形图,把这个频率的波形画出来。
一边拖着一边要有声音,不能卡顿。
请用中文注释,烧录前核对端口。
这题有一个隐藏难点,先想一想:如果程序是"把整段声音全算好、播完才返回", 那你拖着手指的时候,板子根本没空去看你的手指——声音会卡、滑杆也会卡。 想想第 8 课学的触摸,和这节课的播放,它们怎么才能同时干活?
(提示:想一想下一节要讲的"把长任务切成小块"。)
烧录后做三件事,边做边记:
| 我做的 | 我听到的 | 我看到的 |
|---|---|---|
| 从最左边慢慢拖到最右边 | (音调怎么变?) | 波形怎么变? |
| 停在中间,记住这个声音 | ||
| 再拖到最右边,和中间比 |
停一下,看两个地方:
- 耳朵:那句话你听出来了吗——音调不是"跳"的,是"滑"的。手指在滑杆上挪一点点,音调就变一点点。这才是音调的真相。
- 眼睛:盯着下面的波形。频率越高,波形越密(同样宽度里,波的数量越多)。
💡 这两个是同一件事:耳朵听到的"尖",就是眼睛看到的"密"。 频率就是"一秒振动几次",振动次数多了,画出来自然就密。
顺便验证一个数字
前面的表格里说过:880 是 440 的两倍,听起来高一个八度。
现在你能验证它了——先拖到 440 停住,记住那个音;再拖到 880。
如果你家里有钢琴、电子琴或者音乐 App,按一下中音 Do(C)和高音 Do,对比一下: 是不是"同一个音,只是高了一层"?
这就是八度:频率翻倍 = 高八度。音乐里的音高就是频率,不神秘。
八、动手:把《两只老虎》"翻译"成频率表
单音会了、滑杆玩过了。现在做一件真正像"编曲"的事:把一首歌,变成一张频率表。
先看一个慢镜头。你哼两句《两只老虎》:
两 只 老 虎 ,
两 只 老 虎 ,
跑 得 快 ~ ,
你有没有注意到——有些字唱得长,有些字唱得短?"虎"字短促,而"快~"要拖长。所以一首歌里有两个东西在变:
| 变的 | 对应什么 |
|---|---|
| 音高低 | 频率(Hz)—— 上一节滑杆拖出来的那个 |
| 音长短 | 时长(毫秒)—— 这个音唱多久 |
旋律 = 一个一个的"频率 + 时长",排成一队。
第一步:先把工具箱摆出来
要"翻译"一首歌,得先有对照表。下面这三张表是你的原料,不是答案——照着查就行。
工具箱 ①:音符 → 频率
| 音符 | 唱名 | 频率 |
|---|---|---|
| Do | 1 | 262 Hz |
| Re | 2 | 294 Hz |
| Mi | 3 | 330 Hz |
| Fa | 4 | 349 Hz |
| Sol | 5 | 392 Hz |
| La | 6 | 440 Hz |
工具箱 ②:《两只老虎》前四句的曲谱——每个字唱哪个音、哪个字要拖长
两 只 老 虎 → Do Re Mi Do (四个字一样长)
两 只 老 虎 → Do Re Mi Do (四个字一样长)
跑 得 快 ~ → Mi Fa Sol ("快~"要拖长!)
跑 得 快 ~ → Mi Fa Sol ("快~"要拖长!)
工具箱 ③:时长用"份"来数
| 什么样的字 | 算几份 | 举例 |
|---|---|---|
| 短字 | 1 份 | 两、只、老、虎、跑、得 |
| 拖长的字 | 4 份 | 快~ |
所以"快~"听起来比别的字长得多——数一数就知道长几倍。
第二步:把三张表拼起来(这步是你的活)
音高照着曲谱抄就行。但"1 份到底等于多少毫秒",得你来定——这就是你的编曲。
先自己填,再发给 AI:
| 第几个字 | 唱什么 | 频率 Hz | 几份 | 时长(毫秒) |
|---|---|---|---|---|
| 1 | 两 | 262 | 1 | |
| 2 | 只 | 1 | ||
| 3 | 老 | 1 | ||
| 4 | 虎 | 1 | ||
| 5~8 | (和 1~4 一模一样,自己抄) | |||
| 9 | 跑 | 1 | ||
| 10 | 得 | 1 | ||
| 11 | 快~ | 4 | ||
| 12~14 | (和 9~11 一模一样) |
第 1 行我替你填好了,照着形式往下补。
然后发给 AI:
请让板子按顺序播放《两只老虎》的前四句,一共 14 个音:
两 只 老 虎,两 只 老 虎,跑 得 快~,跑 得 快~
(音高是 Do Re Mi Do / Do Re Mi Do / Mi Fa Sol / Mi Fa Sol)
一个「短字」算 ___ 毫秒,那个拖长的「快~」算 ___ 倍(参考 4 倍)。
音与音之间不留空,音量中等偏低。帮我烧录。
先别急着问 AI 要"正确的《两只老虎》"——你自己定的那个速度才叫编曲。 填得跟原曲不一样、听起来怪,那正好说明你听出来了差别,下一轮再改。 "我改了什么、听起来怎么变了",比"标准答案"值钱。
想好了再点开:我们填的那一版
**这不是标准答案。**我们把"1 份"定成 250 毫秒,于是:
| 字 | 频率 Hz | 几份 | 时长 |
|---|---|---|---|
| 两 / 只 / 老 / 虎 | 262 / 294 / 330 / 262 | 1 | 250 |
| 两 / 只 / 老 / 虎 | 262 / 294 / 330 / 262 | 1 | 250 |
| 跑 / 得 | 330 / 349 | 1 | 250 |
| 快~ | 392 | 4 | 1000 |
| 跑 / 得 | 330 / 349 | 1 | 250 |
| 快~ | 392 | 4 | 1000 |
速度是你的选择:250 改成 150,整首歌会变快(像快板);改成 500,就慢吞吞的(像哀乐)。 其余都不用动。
⚠️ 一个容易踩的坑:如果你把前两个音都填成 262Hz, 板子会连着播两个一模一样的音,听起来像一个长音,你会以为程序坏了。 遇到这种情况先检查你的表,别急着改代码。
最后一句提醒:抄来的旋律不是你的作品,你选出来的频率和节奏才是。 做完这一节,你就亲手把一首歌翻译成了板子能读懂的数字——第 16 课的音乐盒, 用的就是这同一个套路。
九、作品二:两只老虎(旋律 + 画面)
第八节你做的是"只有声音"。现在把它升级成一个能给人看的完整作品:
循环播放《两只老虎》,屏幕上两只小老虎跟着眨眼睛,按 BOOT 能暂停。
这一节和前面最大的不同是:声音和画面要同时进行。
先自己想一个问题:如果程序"播放一个音,等它放完,再播下一个音"——那播放的时候,屏幕还能眨眼吗?
答案是不能。因为播放的代码一直占着 CPU,画面根本没机会更新。这就是为什么之前的 audioPlaySine() 播完才返回。
所以作品二藏着一个新技术:边播边动。
先看现象,再研究它是怎么做到的。把这段发给 AI:
请使用课程的 esp32s3-touchboard-hardware-dev Skill,
让板子通过扬声器循环播放《两只老虎》的旋律,
同时在屏幕上画出两只卡通老虎(蓝天草地背景),老虎的眼睛定时眨一下。
短按 BOOT 键可以暂停/继续播放。
音高用正弦波实时生成,不要用蜂鸣器、不要读 TF 卡。
播放前先检查板卡的音频芯片(ES8311)和功放初始化方式。帮我新建程序并烧录。
烧录后观察三件事,填在下面:
| 我观察的 | 现象 |
|---|---|
| 旋律对不对 | |
| 老虎眨眼和音乐同时发生吗 | |
| 按 BOOT 会怎样 |
为什么这次能"边播边动"
回到第五节的流水线:播放 = 按节奏送数字。
之前的写法是"把整段旋律的数字全算好,一次性送完,才返回"。作品二换了思路:
每次只算一小段(160 个立体声帧),送出去,马上返回去干别的。
下一轮 loop 再来算下一小段。只要每一小段都及时接上,声音就是连续的;而两次"送数字"之间,CPU 是空闲的——眨眼动画就挤在这个空隙里画。
看下面两条时间轴——红条是 CPU 忙着送声音,黑缝是它空着。小人儿只有在黑缝里才眨得动眼。
换成"大块"试试:块越大,缝越少,眨眼次数就掉下来了。 这就是"把长任务切成小块"的全部意义——不是让 CPU 变快,是给它留出空隙。
顺便回想上一节的
demo06滑杆:它拖起来为什么不卡?用的是同一个办法。
用一个比喻:不是"一口气把话说完才做别的事",而是"说一个词,趁换气的间隙瞄一眼屏幕"。
| 写法 | 特点 | 适合 |
|---|---|---|
阻塞式(audioPlaySine) | 简单,播完才返回 | 单音、门铃这种短音 |
| 非阻塞(作品二用的) | 每次只送一小块就返回 | 旋律 + 动画、游戏音效等要同屏的场景 |
💡 这是本课第二个重要发现:让声音和画面同时工作,关键不是"同时做两件事",而是"把长任务切成小块,块与块之间留出空隙"。 你以后做游戏(第 29–33 课)时,音效和画面并存靠的就是这个思路。
旋律是怎么记下来的
《两只老虎》的谱子,在程序里存成了一张表:
每个音 = (唱第几个音, 唱多长)
{1,1} = do 唱一个八分音符
{5,4} = sol 唱四个八分音符(也就是二分音符,听起来长一倍)
前面数字是"第几个音":1~6 分别对应 do re mi fa sol la。后面数字是时长,单位是"八分音符"。
所以 4 就代表"这个音顶四个八分音符那么长"。你听《两只老虎》里"跑得快~"那个"快"字拖得长,就是长时值。
改谱子不需要动程序逻辑——只改这张表就行。试试看:
请把旋律换成别的儿歌(比如 ___,你自己挑一首),
每个音的时长按 ___ 毫秒算一个「短字」,
屏幕上的画面保持不变,其余都不改。帮我烧录。
旋律只有 6 个音够不够用?想想你挑的这首歌需要哪几个音,如果超出 do~la,你要改哪里?
画面部分
老虎是用基本图形拼出来的:圆形当脑袋、半圆当耳朵、椭圆当肚皮,眼睛是两个小黑点。
眨眼 = 每过一段时间,把黑点擦掉,换成一条横线,再等一小会儿变回黑点。
想想这和第六课(让画面动起来)有什么关系——是不是同一个套路:擦掉旧的,画上新的。
最后一个挑战:
请让老虎跟着节奏动起来:
每播完一个音符,老虎的身体轻轻跳一下。
旋律和画面其他部分都不改。帮我烧录。
这道题的关键是:画面怎么知道"刚播完一个音符"? 提示——程序里得有个变量记着这件事。
十、故障排查:没声音怎么办
按顺序自查,别乱试:
| 现象 | 先检查 |
|---|---|
| 完全没有声音 | 串口有没有报 ES8311 初始化失败?功放使能了吗? |
| 声音很小 | 音量数值调高(只要不刺耳) |
| 咔哒杂音 | 缓冲区清零了吗?播放完等送完再退出? |
| 播一半断 | 大缓冲区要用 PSRAM,别开在栈上 |
把现象如实告诉 AI,一次让它教你查一件事。
十一、自检小测
先自己想,再点开对答案。
1. 扬声器为什么会动?按顺序说一遍。
电压变 → 纸盆动 → 空气被推 → 耳朵听到。
喇叭是一面"电动的鼓":电流通过线圈,在磁场里受力,推着纸盆快速振动, 把空气一层一层推出去。
2. "播放声音"在程序里到底是什么操作?
按固定节奏,把一串数字送出去。
数字送给 ES8311 里的 DAC,DAC 把每个数字变成对应的电压, 电压推纸盆——声音就出来了。
一句话:播放 = 按节奏送数字。
3. DAC 是干什么的?在这块板上它住在哪颗芯片里?
DAC = 数模转换器,只做一件事:把一个数字变成对应的电压。 数字大 → 电压高;数字小 → 电压低。
它不在 ESP32 里面,而是住在音频芯片 ES8311 里(ES8311 叫"编解码芯片")。
4. "送数字的快慢"和"采样率",这两个旋钮各决定什么?
这是本课最容易混的地方,一句话分清:
| 旋钮 | 决定 | 属于哪一端 |
|---|---|---|
| 送数字的快慢 | 音调(快=尖,慢=沉)。时长会跟着一起变 | 播放这一端("变速") |
| 采样率 | 能还原多高的音(上限 = 采样率 ÷ 2)、音质 | 记录这一端 |
动画里 A/B/C 三种送法玩的是第一个:数字没改,只是送得快慢变了,音调就变了。 而"能不能只变调不变速"——用这个办法做不到,音调和时长是绑在一起的。
5. 频率 440 和 880,你听到的差别是什么?
880 听起来比 440 高一个八度(就是 do 到高音 do 的关系)。
因为 880 正好是 440 的两倍——频率翻倍 = 高八度,音乐里的音高就是频率。
拖滑杆的时候你应该听出来了:音调是**"滑"**上去的,不是一格一格跳的。
6. 你编的旋律里,每个音用了几个数字?怎么验证播对了?
每个音的采样个数 = 时长(秒)× 采样率。
比如 0.4 秒的音:0.4 × 16000 = 6400 个数字。
怎么验证:把屏上显示的音名(do/re/mi…)和耳朵听到的对照—— 对上就是对了。更严格的话,用第 11 课滑杆拖到那个频率,比一比音高是不是一样。
十二、还能玩什么
| 方向 | 想法 | 继续想 |
|---|---|---|
| 摩斯电码练习器 | 短音 0.1s、长音 0.3s | 长短边界谁来定? |
| 倒计时蜂鸣 | 最后 5 秒每秒"滴",最后拉长 | 声音和屏幕怎么分工? |
| 门铃 | 按 BOOT 响"叮咚" | 两声间隔多久才像门铃? |
| 小乐器 | 用第 8 课触摸当琴键 | 触摸坐标怎么变成频率? |
想做"板子听懂我说话"?那要把声音收进来——第 12 课,麦克风在等你。