一 环境
这次使用的MAX98357A芯片来做实验,接收的I2S数据,连接模拟喇叭进行播放。
这个芯片的数据手册:MAX98357A Datasheet(PDF) - Maxim Integrated Products
然后主控也升级了,这次没用之前的老朋友raspberry pico。主要是因为pico没有I2S口,这次换了一个ESP32 S3,也是现在比较强的终端板,后面可能还会用这个做一些视频类的试验。
首先还是在ESP32 S3烧写Micropython的环境,这个遇到不少问题,搞了两三个小时。
安装烧写工具倒是简单,就是
pip install esptool
之后去micropython网站下载bin。后面烧写的时候是两个U口要换着用,也还好。不过烧完遇到不少麻烦。不管是GPT还是国内技术网,说烧写清一色是这样。
esptool.py --chip esp32s3 --port COM3 --baud 460800 write_flash -z 0x1000 firmware.bin
但是这样烧进去就会
之前没遇到过这样的问题,一下就懵圈了,百度了无数篇贴文,都说把解释器换成ESP8266就行了,然后又说版本不能下最新,要随机找之前的版本试。。。但是我试了N次,都不行。。。
无奈只有换成谷歌,发现这个帖子:
Espressif ESP32-S3-WROOM-1 + Micropython --- invalid header: 0xffffffff - Stack Overflow
原来是烧写地址要改成-z 0,也就是从0起始位置开始烧。。。
esptool.py --chip esp32s3 --port COM3 --baud 460800 write_flash -z 0 firmware.bin
之后立马就OK了。然后搭建环境。环境如下。ESP32用I2S连MAX98357小板,然后MAX98357小板连喇叭。
- 3.3V -> Vin
- GND -> GND
- GPIO18 -> BCLK
- GPIO17 -> LRCLK
- GPIO5 -> DIN
喇叭是在PDD上买的一个5块钱。参数书4欧3W,查到的MAX98357最大可以推动4欧8W。所以应该是没问题。
算下来数字功放芯片MAX98357差不多11块,还有这个小喇叭5块,另外线还要自己出,有一些排线还要自己焊,整体下来这个试验成本就要20块。。。所以说搞硬件确实花钱啊,不过反过来说这样也才有一些门槛。。像软件没有门槛,一台电脑就能下场,现在行情已经彻底乱了。。。
二 代码
这次做了两个试验,一个简单的正弦波,一个是播放wav。
1 正弦波
from machine import I2S, Pin
# 设置I2S配置,使用替代引脚
i2s = I2S(0,
sck=Pin(18), # BCLK
ws=Pin(17), # LRCLK
sd=Pin(5), # DIN
mode=I2S.TX, # 发送模式
bits=16, # 每个样本的位数
format=I2S.MONO, # 单声道
rate=44100, # 采样率
ibuf=20000) # 缓冲区大小
import math
import array
# 生成一个简单的正弦波音频数据
def generate_sine_wave(frequency, sample_rate, amplitude, duration):
samples = sample_rate * duration
sine_wave = array.array("h", (0 for _ in range(samples)))
for i in range(samples):
sine_wave[i] = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
return sine_wave
# 生成1kHz的正弦波
sine_wave = generate_sine_wave(1000, 44100, 32767, 1) # 2秒的1kHz正弦波
print("Test I2S.")
# 播放音频数据
while True:
i2s.write(sine_wave)
wav_file.close()
其实这个代码很典型了, bits=16, # 每个样本的位数 rate=44100, # 采样率ibuf=20000) # 缓冲区大
这里也解释了为什么之前在上层要配置这些。尤其是bit和采样率。
在这里,我也做了试验,主要是修改generate_sine_wave(1000, 44100, 32767, 1)。确实不同的频率发出来的声音不一样。
2 播放wav
from machine import I2S, Pin
import ustruct
# 打开WAV文件
wav_file = open('house_lo.wav', 'rb')
# 读取WAV文件头
wav_header = wav_file.read(44)
# 正确解析WAV文件头
header = ustruct.unpack('<4sI4s4sIHHIIHH4sI', wav_header)
chunk_id, chunk_size, format, subchunk1_id, subchunk1_size, audio_format, channels, sample_rate, byte_rate, block_align, bits_per_sample, subchunk2_id, subchunk2_size = header
print(f'Channels: {channels}, Sample Rate: {sample_rate}, Bits Per Sample: {bits_per_sample}')
def convert_8bit_to_16bit(data):
# 将8位音频数据转换为16位
converted = bytearray()
for byte in data:
# 扩展到16位: 中心点为128,扩展至32768
converted.extend(ustruct.pack('<h', (byte - 128) << 8))
return converted
# 更新I2S的采样率
#i2s.sample_rate(sample_rate)
i2s = I2S(0, sck=Pin(18), ws=Pin(17), sd=Pin(5), mode=I2S.TX, bits=16, format=I2S.MONO, rate=11025, ibuf=20000)
#i2s = I2S(0, sck=Pin(18), ws=Pin(17), sd=Pin(5), mode=I2S.TX, bits=bits_per_sample, format=I2S.MONO if channels == 1 else I2S.STEREO, rate=sample_rate, ibuf=20000)
# 播放WAV文件数据
while True:
audio_data = wav_file.read(1024)
if not audio_data:
break
audio_data_16bit = convert_8bit_to_16bit(audio_data)
i2s.write(audio_data_16bit)
# 关闭WAV文件
wav_file.close()
在这里有几个值得注意的。
首先是找wav文件,不能大的,而且采样率波特率都要合格,最后是Windows系统里面的house_lo.wav,这个文件76K,节奏明快,播放的时候还可以随着起舞。很是有趣。
然后也是wav文件,不同的文件,对应的采样率位数这些都不同,首先都要在wav文件文件头里面解析这部分,然后这些都要在启动I2S的时候设置进去,这些都是动态的。
最后是位数,ESP32的I2S不支持16位,所以这里用了个函数转换成16位。
三 I2S协议
1 I2S协议
整体抓包的图
从图上可以看到BCLK,也就是最下面那个是时钟。LRCLK也就是中间那个是左右声道选择,有点类似SPI片选那根线,DIN也就是最上面那个,就是数据线。
看协议介绍,SCK的时钟频率 = 声道数 * 采样频率 * 采样位数。
我这边是BCLK=11025×16×1=176400Hz。不过这里有点稍微奇怪,我在代码中设置的其实是单声道I2S.MONO,但是从波形上看还是双声道,ESP32是把第一个声道给复制了一遍,所以最后的BCLK=11025×16×2=352800Hz。
下面看看每一个Data
看一下DIN的图,看起来是时钟线上升沿作为有效位,这个和I2C一样,一次有16个bit,2个byte。这里的数据是1100 0101 0000 0000,也就是上面解析出来的0xC500。这里也就是对应着上面的16位采样。
2 音频采样
下面是科普:
什么是音频采样?
音频信号在原始状态下是模拟信号,表现为随时间连续变化的电压波形。为了将这些模拟信号存储或处理为数字形式,需要将其转换为一系列离散的数字值。这一过程称为采样。
采样率:每秒钟对模拟信号进行采样的次数。采样率越高,音频的时间分辨率越高,能够更精确地捕捉声音的细节。常见的采样率有44.1 kHz(CD质量)、48 kHz、96 kHz等。
位深度(Bit Depth):每个采样点使用的位数。位深度决定了每个采样值的精度,即信号的动态范围(能够表达的最小和最大振幅之间的差异)。16位采样表示每个采样点使用16位二进制数来表示。
16位采样意味着每个音频样本点可以有 2162^{16}216(即65,536)个不同的可能值。这些值用于表示音频信号在该时间点的振幅(音量)。
- 更高的位深度意味着更大的动态范围。16位音频的动态范围大约是96 dB,这意味着它可以表示从非常安静到非常响亮的声音细节。
- 音频质量:16位采样被广泛用于CD音质的音频格式。在这种设置下,音频能够很好地平衡文件大小和音质。
从这里来看,声音就是一个波形。采样率就是采样次数,越多这个波形越准确。然后采样就是这个点的精度。越多就是越准。
3 PCM编码
里面用的实际就是PCM。解析Header头的动作,在上面2.2可以看到。
PCM(Pulse Code Modulation,脉冲编码调制)是一种用于表示模拟信号的数字编码方法。它是数字音频的基础编码方式,广泛应用于各种音频格式和传输标准中。以下是对PCM格式的详细介绍:
PCM的基本原理
PCM的基本原理包括三个主要步骤:采样、量化和编码。
采样(Sampling):
- 将连续的模拟信号在固定的时间间隔内进行采样,得到一系列离散的样本。
- 采样频率(Sampling Rate)决定了每秒钟采样的次数,常见的采样频率有44.1kHz(CD音质)、48kHz、96kHz等。
量化(Quantization):
- 将每个采样值转换为最接近的离散数字值。
- 量化级数(Quantization Levels)决定了每个样本的精度,通常用位深度(Bit Depth)表示,如16位、24位等。
编码(Encoding):
- 将量化后的数字值编码为二进制数据,便于存储和传输。
PCM格式的特性
- 无损编码:PCM是一种无损编码方法,能够精确地表示原始模拟信号。
- 高保真度:由于PCM能够精确地表示原始信号,因此它具有很高的音频保真度。
- 通用性:PCM广泛应用于CD、DVD、数字电话、音频流媒体等领域。
PCM数据的表示
PCM数据通常以二进制形式存储和传输。以下是一个简单的PCM数据表示示例:
采样频率:44.1kHz
位深度:16位
声道数:2(立体声)
在这种情况下,每秒钟有44,100个样本,每个样本由16位表示,每个样本包含两个声道的数据(左声道和右声道)。
PCM数据的存储格式
PCM数据可以存储在多种文件格式中,最常见的是WAV文件格式。以下是一个WAV文件的PCM数据结构示例:
RIFF Header
ChunkID: "RIFF"
ChunkSize: 36 + SubChunk2Size
Format: "WAVE"
fmt Subchunk
Subchunk1ID: "fmt "
Subchunk1Size: 16
AudioFormat: 1 (PCM)
NumChannels: 2 (立体声)
SampleRate: 44100
ByteRate: 176400 (SampleRate * NumChannels * BitsPerSample/8)
BlockAlign: 4 (NumChannels * BitsPerSample/8)
BitsPerSample: 16
data Subchunk
Subchunk2ID: "data"
Subchunk2Size: NumSamples * NumChannels * BitsPerSample/8
Data: PCM音频数据
PCM的应用
- CD音频:CD音频使用44.1kHz的采样频率和16位的位深度。
- 专业音频:专业音频录制和制作通常使用更高的采样频率(如96kHz)和更高的位深度(如24位)。
- 电话通信:数字电话系统通常使用8kHz的采样频率和8位的位深度。
PCM的优缺点
优点:
- 高保真度:能够精确地表示原始模拟信号。
- 无损编码:没有数据压缩,保留了所有原始信息。
- 通用性:广泛应用于各种音频设备和系统。
缺点:
- 数据量大:由于没有压缩,PCM数据量较大,存储和传输成本较高。
- 不适合低带宽传输:在带宽受限的情况下,PCM可能不适合直接传输。
总结
PCM是一种基础的数字音频编码方法,通过采样、量化和编码将模拟信号转换为数字数据。它具有高保真度和无损编码的优点,广泛应用于各种音频系统和设备中。尽管数据量较大,但其高保真度使其成为高质量音频应用的首选。
四 小结和参考
好吧,I2S协议实际上本身也没有太多的内容,我觉得比较独特的还是时钟要根据参数来计算。其它还是声音本身的内容,后面有时间再看看吧。。。