STM32 DAC播放音乐(已测试成功)
先上演示效果
STM32DAC
DAC(Digital-to-Analog Converter)是一种将数字信号转换为模拟信号的器件,能够根据数字量生成相应比例的电压值。STM32系列微控制器通常配备8位或12位DAC模块。
在介绍音乐播放功能前,有必要简要说明几种常见的音频格式:
- MP3:采用有损压缩的音频格式
- WAV:无损音频格式,标准WAV文件采用与CD相同的参数(44.1kHz采样频率,16位量化)
- PCM:直接将模拟音频信号转换为数字脉冲序列的记录方式,由未经编码或压缩的1/0数字信号组成
还需要关注一下采样率和位深度
采样率很好理解,把一秒分成很多帧,采样率越高声音越细致。
位深度可以理解为声音的高低大小,深度越大,声音越精准。例如8bit对应256个层级。
前置准备
本次使用的是STM32F407VET6 512KB FLASH
我这里使用16KHz 8Bit PCM格式的音频数据,8Bit的数据对应一个Byte,16KHz采样,1秒种占用存储空间就是16K Byte,理论上可以存储512K/16K = 32秒的音频。那本次就截取30s的音频。
然后通过以下python脚本把对应的WAV文件转成PCM。(在线的转换很少能指定8bit和任意采样率,为了高可复制性,直接python去做)
import wave
import numpy as np
import os
import glob
def wav_to_pcm(input_file, output_file, target_sample_rate=16000, target_bits=8, target_channels=1):
"""
将WAV文件转换为指定格式的PCM文件
参数:
input_file: 输入的WAV文件路径
output_file: 输出的PCM文件路径
target_sample_rate: 目标采样率 (默认16000Hz)
target_bits: 目标位深度 (默认8bit)
target_channels: 目标声道数 (默认1声道)
"""
try:
# 打开WAV文件
with wave.open(input_file, 'rb') as wav_in:
# 获取原始音频参数
original_channels = wav_in.getnchannels()
original_sample_width = wav_in.getsampwidth()
original_sample_rate = wav_in.getframerate()
original_frames = wav_in.getnframes()
print(f"处理文件: {os.path.basename(input_file)}")
print(f" 原始声道数: {original_channels}")
print(f" 原始采样宽度: {original_sample_width * 8} bit")
print(f" 原始采样率: {original_sample_rate} Hz")
print(f" 原始时长: {original_frames / original_sample_rate:.2f} 秒")
# 读取音频数据
audio_data = wav_in.readframes(original_frames)
# 将字节数据转换为numpy数组
if original_sample_width == 1:
# 8bit无符号
audio_array = np.frombuffer(audio_data, dtype=np.uint8)
elif original_sample_width == 2:
# 16bit有符号
audio_array = np.frombuffer(audio_data, dtype=np.int16)
elif original_sample_width == 4:
# 32bit有符号
audio_array = np.frombuffer(audio_data, dtype=np.int32)
else:
raise ValueError(f"不支持的采样宽度: {original_sample_width}")
# 重塑数组以处理多声道
if original_channels > 1:
audio_array = audio_array.reshape(-1, original_channels)
# 转换为单声道 (取平均值)
audio_array = np.mean(audio_array, axis=1)
# 重采样到目标采样率
if original_sample_rate != target_sample_rate:
# 简单的线性插值重采样
ratio = target_sample_rate / original_sample_rate
new_length = int(len(audio_array) * ratio)
indices = np.linspace(0, len(audio_array) - 1, new_length)
audio_array = np.interp(indices, np.arange(len(audio_array)), audio_array)
# 转换为8bit无符号格式
if target_bits == 8:
# 归一化到0-255范围
if audio_array.max() != audio_array.min():
audio_array = ((audio_array - audio_array.min()) / (
audio_array.max() - audio_array.min()) * 255).astype(np.uint8)
else:
# 处理静音或恒定值的情况
audio_array = np.full(len(audio_array), 128, dtype=np.uint8)
print(f" 转换后: {target_channels}声道, {target_bits}bit, {target_sample_rate}Hz")
print(f" 转换后时长: {len(audio_array) / target_sample_rate:.2f} 秒")
# 写入PCM文件
with open(output_file, 'wb') as pcm_out:
pcm_out.write(audio_array.tobytes())
print(f" ✓ 转换完成: {os.path.basename(output_file)}")
except Exception as e:
print(f" ✗ 转换失败: {str(e)}")
return False
return True
def batch_convert_wav_to_pcm(directory=".", target_sample_rate=16000, target_bits=8, target_channels=1):
"""
批量转换目录下的所有WAV文件为PCM格式
参数:
directory: 工作目录 (默认为当前目录)
target_sample_rate: 目标采样率
target_bits: 目标位深度
target_channels: 目标声道数
"""
# 获取目录下所有WAV文件
wav_files = glob.glob(os.path.join(directory, "*.wav"))
wav_files.extend(glob.glob(os.path.join(directory, "*.WAV")))
if not wav_files:
print(f"在目录 '{directory}' 中没有找到WAV文件")
return
print(f"找到 {len(wav_files)} 个WAV文件:")
for wav_file in wav_files:
print(f" - {os.path.basename(wav_file)}")
print(f"\n开始批量转换...")
print(f"目标格式: {target_channels}声道, {target_bits}bit, {target_sample_rate}Hz")
print("-" * 50)
success_count = 0
total_count = len(wav_files)
for wav_file in wav_files:
# 生成输出文件名 (在同一目录下)
base_name = os.path.splitext(os.path.basename(wav_file))[0]
output_file = os.path.join(directory, f"{base_name}_16k_8bit_mono.pcm")
# 执行转换
if wav_to_pcm(wav_file, output_file, target_sample_rate, target_bits, target_channels):
success_count += 1
print() # 空行分隔
print("-" * 50)
print(f"批量转换完成!")
print(f"成功: {success_count}/{total_count}")
if success_count < total_count:
print(f"失败: {total_count - success_count} 个文件")
def main():
import argparse
parser = argparse.ArgumentParser(description='批量将WAV文件转换为16KHz 8Bit 单声道PCM格式')
parser.add_argument('-d', '--directory', default='.', help='工作目录 (默认为当前目录)')
parser.add_argument('--sample-rate', type=int, default=16000, help='目标采样率 (默认16000Hz)')
parser.add_argument('--bits', type=int, default=8, help='目标位深度 (默认8bit)')
parser.add_argument('--channels', type=int, default=1, help='目标声道数 (默认1声道)')
args = parser.parse_args()
# 检查目录是否存在
if not os.path.exists(args.directory):
print(f"错误: 目录 '{args.directory}' 不存在")
return
# 执行批量转换
batch_convert_wav_to_pcm(
args.directory,
args.sample_rate,
args.bits,
args.channels
)
if __name__ == "__main__":
main()
把WAV文件放在和python一个目录下,然后点击运行即可生成对应的PCM文件。
然后通WinHex软件打开,复制为C源码格式

然后把数组名字改成这个:const unsigned char data,并作为一个.h文件在项目中

环境配置
打开CUBEMX,配置基本的时钟 debug 168MHZ.然后激活DAC,OUT1单声道,并且触发源选择TIM6。

然后DMA配置为Normal,数据宽度选Byte(因为我们传的8bit)

然后配置TIM6,TIM6对应APB1 84MHZ,ARR配置为5249,即为16khz。那么这样就相当于TIM6以16khz的频率更新,无需配置中断,很方便。

就这样生成代码即可。
代码编写
需要注意的STM32DAC一次传的大小只有16位 对应65535。那么音乐肯定没办法一空气放完,那我们就分开来。
/* USER CODE BEGIN PV */
// 分块传输相关变量
static uint32_t current_offset = 0; // 当前数据偏移
static uint32_t total_length = 480000; // 总数据长度
static uint32_t block_size = 65535; // 每块大小(DMA最大传输计数)
static uint32_t block_count = 0; // 当前块计数
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// 启动定时器,为DAC提供触发信号
HAL_TIM_Base_Start(&htim6);
// 启动DAC的DMA传输,播放PCM音频数据
// 开始第一块数据传输
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)(data + current_offset), block_size, DAC_ALIGN_8B_R);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
// 检查DMA传输是否完成
if (HAL_DAC_GetState(&hdac) == HAL_DAC_STATE_READY)
{
// 计算下一块数据的偏移和大小
current_offset += block_size;
block_count++;
// 检查是否还有更多数据需要传输
if (current_offset < total_length)
{
// 计算当前块的实际大小
uint32_t remaining = total_length - current_offset;
uint32_t current_block_size = (remaining > block_size) ? block_size : remaining;
// 启动下一块数据传输
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)(data + current_offset), current_block_size, DAC_ALIGN_8B_R);
}
else
{
// 所有数据已传输完成,重新开始
current_offset = 0;
block_count = 0;
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)data, block_size, DAC_ALIGN_8B_R);
}
}
}
/* USER CODE END 3 */
另外提一嘴,声音的质感和运放以及喇叭都有关系。编者这次用的是淘宝2块钱的5128D类功放模块,结合8bit的采样,音质只能说就那样吧。下期文章我们直接从SD卡中解析完整的完全的音频进行播放。
更多推荐
所有评论(0)