在嵌入式系统中,利用STM32系列微控制器实现音频播放是一个常见而又具有挑战性的任务。常见的播放音频的方式包括:
TIM+PWM方式: 使用定时器(TIM)和脉冲宽度调制(PWM)技术来实现音频输出。通过定时器生成一定频率的PWM信号,控制扬声器或驱动电路的工作,从而产生模拟音频输出。这种方法简单高效,适用于低功耗应用和简单音频需求。
DAC+运放放大器到喇叭: 使用数字模拟转换器(DAC)将数字音频信号转换为模拟信号,然后通过运放(放大器)放大至喇叭驱动的电平。这种方法提供高保真度的音频输出,适用于需要良好音质和音量的应用,如音乐播放和语音提示。
IIS+语音解码芯片: 使用IIS(Inter-IC Sound)接口连接专用的语音解码芯片(如VS1053),通过解码器处理和解压音频数据,然后将模拟音频信号输出到喇叭或耳机。这种方式适用于需要播放多种音频格式(如MP3、WAV等)和复杂音频处理的应用场景。
本文主要介绍如何利用DAC+运算放大器到喇叭的方式,用到的有STM32F103RCT6的DAC(数字模拟转换器)、DMA(直接存储器访问)和TIM(定时器)模块,结合HAL库(Hardware Abstraction Layer),来实现音乐的数字转模拟输出,从而实现音频播放功能。
前期准备
在开始之前,需要进行一些准备工作:
硬件准备
STM32F103RCT6最小开发板:某宝买的
功放模块:某宝买的
4Ω3W喇叭:某宝买的
开发工具的下载与安装
STM32CubeMX的下载和安装:
STM32CubeMX是STMicroelectronics提供的用于STM32微控制器系列的图形化配置工具,可以帮助快速生成初始化代码和配置文件。
Adobe Audition的下载和安装:
Adobe Audition是一款音频编辑软件,用于编辑、处理和转换音频文件格式(可将MP3文件进行编辑并保存为WAVE文件)。
WinHex的下载和安装:
WinHex是一款十六进制编辑器,用于查看和编辑二进制文件(可将GoldWave保存的.WAV文件导出为C语言的数组)。
注:该压缩包的后缀为.exe,这不是应用程序,是一个压缩格式,正常解压即可使用
WAVE(.wav)文件格式的介绍
Wave文件格式主要是用来存储音频PCM数据的。PCM数据是脉冲编码调制(Pulse Code Modulation)的音频数据,它是一种将模拟信号转换为数字信号的编码方式,PCM中的声音数据没有被压缩,。PCM数据的主要过程包括采样、量化和编码,其中采样是将模拟信号在时间上离散化,量化是将采样值按分层单位四舍五入取整量化,而编码则是将量化后的样值转换成对应的二进制码。
详细介绍可看这篇文章:WAVE(.wav)文件格式
使用STM32CubeMX生成DAC + DMA + TIM程序
一、DAC简介
DAC(Digital-to-Analog Converter),即数字/模拟转换模块,故名思议,它的作用就是把输入的数字编码,转换成对应的模拟电压输出,它的功能与 ADC 相反。在常见的数字信号系统中,大部分传感器信号被化成电压信号,而 ADC 把电压模拟信号转换成易于计算机存储、处理的数字编码,由计算机处理完成后,再由 DAC 输出电压模拟信号,该电压模拟信号常用来驱动某些执行器件,使人类易于感知。如音频信号的采集及还原就是这样一个过程。
STM32 具有片上 DAC 外设,它的分辨率可配置为 8 位或 12 位的数字输入信号,具有两个 DAC 输出通道,这两个通道互不影响,每个通道都可以使用 DMA 功能,都具有出错检测能力,可外部触发。
二、DAC通道选择
在 STM32 中具有 2 个这样的 DAC 部件,每个 DAC 有 1 个对应的输出通道连接到特定的引脚,即:PA4-通道 1,PA5-通道 2,为避免干扰,使用 DAC 功能时,DAC 通道引脚需要被配置成模拟输入功能(AIN)。
三、新建工程
1、打开STM32CubeMX软件,点击”新建工程“
2、选择MCU和封装
3、配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz,修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器,SYS 设置,选择 Debug 为 Serial Wire
四、DAC1配置
1、配置DAC1
在 Analog 中选择 DAC 设置,并选择 OUT1 Configuration 通道1
或者在右边图找到 PA4 引脚,选择 DAC_OUT1
具体配置参数如下
OUT1/2 Configuration:
对应两个输出通道。
External Trigger:
外部中断EXTI9 触发 就是使用外部中断来触发DAC。
Output Buffer:
DAC输出缓存。
DAC 集成了 2 个输出缓存,DAC输出缓存是一个内置于DAC模块中的运算放大器,用于缓冲DAC的输出信号。启用输出缓存可以改善信号驱动能力,降低输出阻抗,并提高转换速度和稳定性,无需外部运放即可直接驱动外部负载。每个 DAC 通道输出缓存可以通过设置 DAC_CR 寄存器的 BOFFx 位来使能或者关闭。如果带载能力还不行,后面就接一个电压跟随器,选择运放一定要选择电流大的型号。
使能输出缓冲后,DAC 输出的最小电压为 0.2V,最大电压为 VREF±0.2,而未使能输出缓冲则输出可达到0V。
Trigger:
选择DAC的触发方式
Timer 2/4/5/6/7/8 Trigger Out event 定时器触发,利用这种方式可以输出特定的波形。在这里我们选择定时器2。
Software trigger 软件触发,在本模式下,向 DAC_SWTRIGR 寄存器写入配置即可触发信号进行转换。
Wave generation mode:Disable(不使用波形发生器)。
2、配置DMA
点击 DMA Settings 添加 DAC_CH1 对应 DMA2 的通道3。DMA模式选择循环模式,方向选为内存到外设。
Priority:
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
Mode:
Normal 表示单次传输,传输一次后终止传输。
Circular 表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。
Increment Address:
Peripheral 表示外设地址自增。
Memory 表示内存地址自增。
Data Width:
Byte 一个字节。
Half Word 半个字,等于两字节。
Word 一个字,等于四字节。
五、TIM2配置
1、配置TIM2
在 Timers 中选择 TIM2 设置,时钟源 Clock Source 选择内部时钟 Internal Clock。
在 Parameter Settings 进行具体参数配置。
Prescaler(时钟预分频数):0 驱动计数器的时钟 CK_CNT = CK_INT(即72MHz)/(0+1) = 72MHz 即不分频
Counter Mode(计数模式):Up(向上计数模式)
Counter Period(自动重装载值):9000-1
auto-reload-preload(自动重装载):Disable(不使能)
TRGO Parameters(触发输出):Update Event(更新事件) 在定时器的定时时间到达的时候输出一个信号(如:定时器更新产生TRGO信号来触发DAC的同步转换)
2、代码生成
输入项目名和项目路径(项目名称和路径不要包含中文),选择应用的 IDE 开发环境 MDK-ARM V5。
每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码。
使用Adobe Audition和WinHex生成音频波形数据表
一、将MP3文件转换成Wav文件
用 Adobe Audition 打开一个MP3音频文件,选择一段区域。右键,存储选区为。
设置参数,然后导出
格式:Wave PCM
采样类型:8000 Hz,单声道,8位
去除包含标记和其他元数据,这样生成的文件更小
采样频率越高,文件越大,后面生成的波形数据表会更大,根据上面TIM2的参数配置:
TIM2的定时器频率:f = Tclk/[(psc+1) * (cnt+1)] = 72MHz/9000 = 8KHz。
定时器时钟Tclk:72MHz
预分频器psc:0
自动重装载值cnt:8999
所以我们采样频率选择 8000 Hz,位深选择 8位。
二、将生成的Wav文件生成音频波形数据表
用WinHex打开刚保存的Wav文件,并将其生成16进制数组并复制
生成后的16进制数组如下:
unsigned AnsiChar data[46606] = {
0x52, 0x49, 0x46, 0x46, 0x06, 0xB6, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20,
0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x40, 0x1F, 0x00, 0x00, 0x40, 0x1F, 0x00, 0x00,
0x01, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61, 0xE2, 0xB5, 0x00, 0x00, 0x81, 0x80, 0x82, 0x81,
0x7E, 0x81, 0x81, 0x80, 0x7E, 0x81, 0x82, 0x81, 0x80, 0x82, 0x7F, 0x81, 0x7F, 0x80, 0x82, 0x7D,
0x7E, 0x82, 0x7F, 0x81, 0x80, 0x83, 0x7D, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x80, 0x81,
0x81, 0x7E, 0x80, 0x80, 0x7E, 0x80, 0x80, 0x82, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7E, 0x82,
0x7F, 0x7E, 0x82, 0x81, 0x82, 0x7E, 0x81, 0x82, 0x7D, 0x7E, 0x83, 0x82, 0x7F, 0x7E, 0x82, 0x81,
0x7F, 0x7C, 0x80, 0x82, 0x80, 0x81, 0x82, 0x81, 0x80, 0x7F, 0x81, 0x80, 0x7F, 0x7F, 0x82, 0x7F,
0x80, 0x7F, 0x7E, 0x80, 0x81, 0x81, 0x7F, 0x81, 0x82, 0x7F, 0x80, 0x80, 0x82, 0x80, 0x7F, 0x7F,
0x82, 0x7E, 0x7E, 0x81, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7E, 0x80, 0x82, 0x81, 0x80, 0x81,
......(数组太大不贴上来了)
};
注意注意,特别重要,DMA计数寄存器通常是16位的,因此DMA的最大计数值为65535。DMA的计数值指的是在一次DMA传输操作中需要传输的数据单元数,这些数据单元的大小取决于数据对齐方式,可以是字节(byte)、半字(halfword)或字(word)。每传输一个数据单元,DMA控制器会将计数值减1,直到达到0时传输完成。因此,数组大小不要超过65535个数据单元,否则DMA无法一次性传输全部数据,可能导致音乐播放中断或数据传输不完整的问题。
我理解的DAC+DMA+TIM的工作流程:DAC的触发源为TIM中断,因此每次TIM触发中断时,会触发DAC模块。DAC在接收到TIM中断后,会根据DMA传输的新数据生成相应的模拟电压输出。DMA自动从内存中获取下一个数据并将其传输到DAC,以实现连续更新模拟输出的功能。
将音频数据移植到STM32程序中并通过DAC输出音频信号
打开上面使用STM32CubeMX生成的STM32程序,将生成的16进制数组复制到main.c如下位置,同时修改unsigned AnsiChar为const uint8_t
修改音频数据,将Wav文件协议头删除掉,留下音频数据,Wav协议具体看上面WAVE(.wav)文件格式的介绍,如下图,将红框中的协议头删除
同时,数组大小也需要改变,协议头的大小为44个字节,所以数组大小也需要更改大小,如下
在main函数中
添加 HAL_TIM_Base_Start() 函数,启动定时器。
添加 HAL_DAC_Start_DMA()函数,启动 DAC 的 DMA 输出。
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_DAC_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim2);
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)data, sizeof(data), DAC_ALIGN_8B_R);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
观察波形数据
将程序烧录到STM32F103RCT6开发板中,用示波器测量PA4引脚,波形如下
观察波形和上面截取的MP3文件,可以看到两个波形是一样的,至此我们的DAC已正确输出音频信号,接下来只需将功放模块和喇叭正确连接上即可播放音乐。
播放音乐
将STM32F103RCT6开发板和功放模块和喇叭按下图进行连接,连接完成之后对STM32F103RCT6开发板进行上电,就可以听到喇叭播放音乐。
STM32+功放+喇叭播放音乐
注意事项
用户代码要加在 USER CODE BEGIN N 和 USER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
总结
本文详细介绍了如何在STM32系列微控制器上实现音频播放,涵盖了从硬件准备、工具安装、音频文件处理到代码实现的全过程。以上内容仅作为个人开发时的学习总结,欢迎各位大佬对本文提供的代码和方法提出改进意见和建议,共同进步。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/wcynbl/article/details/140127338
|
|