概述
声音的录制和播放主要用到了DFSDM和SAI两个外设。
-
DFSDM是将外部sigma-delta调制器的高速数字比特流,通过数字滤波和抽取得到高分辨率PCM数据的硬件模块。
-
SAI(Serial Audio Interface,串行音频接口) 在 STM32 中被广泛用于连接外部音频设备。通过 SAI,MCU 可以与放大器、ADC、DAC、音频处理器等外部音频芯片进行高效的数据交互。

在录音流程中,数字麦克风采集到的 PDM 数据 送入 DFSDM 模块,由 DFSDM 转换为 PCM 数据流。应用层通过 DMA + 回调函数 的方式,将转换后的 PCM 数据拷贝到用户指定的录音缓冲区,从而完成音频录制。
在播放流程中,应用层同样通过 DMA + 回调机制,将待播放的 PCM 数据填充到播放缓冲区,由 SAI 外设 按音频时序输出到外部音频设备,实现语音播放。
代码实现
添加依赖代码
- BSP文件

- wm8994:音频Codec
- audio:音频录制与播放的BSP实现
- sdram:初始化/配置SDRAM
- HAL文件
DFSDM 与 SAI 外设可通过 CubeMX 启用,以自动生成对应 HAL 文件。

由于 音频初始化完全由 BSP 完成,在 CubeMX 的 Project Manager 中不勾选生成初始化代码。
如果 BSP 还依赖其它 HAL 模块(如 UART、FMC),可通过相同方式添加,或直接拷贝对应 HAL 文件。
录音代码
缓冲区与状态定义
#define PCM_BUFFER_SIZE 1024
#define RECORD_BUFFER_SIZE 409600
#define SCRATCH_BUFF_SIZE 512
/* 录音状态机定义 */
#define RECORD_STATE_IDLE 0 // 空闲
#define RECORD_STATE_RECORDING 1 // 正在录音
#define RECORD_STATE_COMPLETED 2 // 录音完成
/* DMA 使用的音频数据缓冲区 */
static uint16_t pcm_buffer[PCM_BUFFER_SIZE * 2];
/* 最终录音数据缓存,放在 SDRAM 中 */
static __attribute__((section(".SD_RAM"))) uint16_t record_buffer[RECORD_BUFFER_SIZE];
/* DFSDM 使用的 Scratch buffer */
static int32_t Scratch[SCRATCH_BUFF_SIZE];
/* 当前已录入的样本数(单位:uint16_t) */
static uint32_t record_buffer_size = 0;
/* 当前录音状态 */
static uint8_t record_state = RECORD_STATE_IDLE;
半传输完成回调
void BSP_AUDIO_IN_HalfTransfer_CallBack(void)
{
/* 将 DMA buffer 前半部分拷贝到录音 buffer */
memcpy(record_buffer + record_buffer_size,
pcm_buffer,
PCM_BUFFER_SIZE * sizeof(uint16_t));
/* 更新已录入样本数 */
record_buffer_size += PCM_BUFFER_SIZE;
/* 如果录音 buffer 已满,停止录音 */
if(record_buffer_size >= RECORD_BUFFER_SIZE)
{
BSP_AUDIO_IN_Stop();
record_state = RECORD_STATE_COMPLETED;
}
}
全传输完成回调
void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
/* 将 DMA buffer 后半部分拷贝到录音 buffer */
memcpy(record_buffer + record_buffer_size,
pcm_buffer + PCM_BUFFER_SIZE,
PCM_BUFFER_SIZE * sizeof(uint16_t));
/* 更新已录入样本数 */
record_buffer_size += PCM_BUFFER_SIZE;
/* 如果录音 buffer 已满,停止录音 */
if(record_buffer_size >= RECORD_BUFFER_SIZE)
{
BSP_AUDIO_IN_Stop();
record_state = RECORD_STATE_COMPLETED;
}
}
启动录音
uint8_t audio_record(void)
{
uint8_t status;
/* 清空录音计数 */
record_buffer_size = 0;
/* 设置状态为录音中 */
record_state = RECORD_STATE_RECORDING;
/* 初始化音频输入参数:采样率、位宽、通道数 */
status = BSP_AUDIO_IN_Init(DEFAULT_AUDIO_IN_FREQ,
DEFAULT_AUDIO_IN_BIT_RESOLUTION,
DEFAULT_AUDIO_IN_CHANNEL_NBR);
if(status != AUDIO_OK)
{
return STATUS_AUDIO_INIT_FAILURE;
}
/* 分配 DFSDM 使用的 Scratch buffer */
status = BSP_AUDIO_IN_AllocScratch(Scratch, SCRATCH_BUFF_SIZE);
if(status != AUDIO_OK)
{
return STATUS_FAILURE;
}
/* 启动 DMA 录音,DMA 会循环写入 pcm_buffer */
status = BSP_AUDIO_IN_Record((uint16_t *)pcm_buffer,
PCM_BUFFER_SIZE * 2);
if(status != AUDIO_OK)
{
return STATUS_AUDIO_RECORD_FAILURE;
}
return STATUS_SUCCESS;
}
获取录音结果
uint8_t audio_record_buffer_get(AUDIO_DATA *audio_data)
{
if(!audio_data)
{
return STATUS_AUDIO_NULL_PTR;
}
/* 只有在录音完成状态下才允许读取数据 */
if(record_state == RECORD_STATE_COMPLETED)
{
audio_data->data_ptr = record_buffer;
audio_data->size = record_buffer_size;
return STATUS_SUCCESS;
}
return STATUS_FAILURE;
}
播放代码
缓冲区与状态定义
#include "main.h"
#define PCM_BUFFER_SIZE 1024
#define RECORD_BUFFER_SIZE 409600
#define SCRATCH_BUFF_SIZE 512
#define RECORD_STATE_IDLE 0 // 空闲
#define RECORD_STATE_RECORDING 1 // 正在录音
#define RECORD_STATE_COMPLETED 2 // 录音完成
/* DMA 使用的音频数据缓冲区 */
static uint16_t pcm_buffer[PCM_BUFFER_SIZE * 2];
/* 最终录音数据缓存,放在 SDRAM 中
* 用于保存完整录音结果
*/
static __attribute__((section(".SD_RAM")))
uint16_t record_buffer[RECORD_BUFFER_SIZE];
/* DFSDM 使用的 Scratch buffer */
static int32_t Scratch[SCRATCH_BUFF_SIZE];
/* 当前已录入的样本数(单位:uint16_t) */
static uint32_t record_buffer_size = 0;
/* 当前录音状态 */
static uint8_t record_state = RECORD_STATE_IDLE;
半传输完成回调
void BSP_AUDIO_IN_HalfTransfer_CallBack(void)
{
/* 将 DMA buffer 前半部分拷贝到录音 buffer */
memcpy(record_buffer + record_buffer_size,
pcm_buffer,
PCM_BUFFER_SIZE * sizeof(uint16_t));
/* 更新已录入样本数 */
record_buffer_size += PCM_BUFFER_SIZE;
/* 如果录音 buffer 已满,停止录音 */
if(record_buffer_size >= RECORD_BUFFER_SIZE)
{
BSP_AUDIO_IN_Stop();
record_state = RECORD_STATE_COMPLETED;
}
}
全传输完成回调
```c
void BSP_AUDIO_IN_TransferComplete_CallBack(void)
{
/* 将 DMA buffer 后半部分拷贝到录音 buffer */
memcpy(record_buffer + record_buffer_size,
pcm_buffer + PCM_BUFFER_SIZE,
PCM_BUFFER_SIZE * sizeof(uint16_t));
/* 更新已录入样本数 */
record_buffer_size += PCM_BUFFER_SIZE;
/* 如果录音 buffer 已满,停止录音 */
if(record_buffer_size >= RECORD_BUFFER_SIZE)
{
BSP_AUDIO_IN_Stop();
record_state = RECORD_STATE_COMPLETED;
}
}
启动录音
uint8_t audio_record(void)
{
uint8_t status;
/* 清空录音计数 */
record_buffer_size = 0;
/* 设置状态为录音中 */
record_state = RECORD_STATE_RECORDING;
/* 初始化音频输入参数:采样率、位宽、通道数 */
status = BSP_AUDIO_IN_Init(DEFAULT_AUDIO_IN_FREQ,
DEFAULT_AUDIO_IN_BIT_RESOLUTION,
DEFAULT_AUDIO_IN_CHANNEL_NBR);
if(status != AUDIO_OK)
{
return STATUS_AUDIO_INIT_FAILURE;
}
/* 分配 DFSDM 使用的 Scratch buffer */
status = BSP_AUDIO_IN_AllocScratch(Scratch, SCRATCH_BUFF_SIZE);
if(status != AUDIO_OK)
{
return STATUS_FAILURE;
}
/* 启动 DMA 录音,DMA 会循环写入 pcm_buffer */
status = BSP_AUDIO_IN_Record((uint16_t *)pcm_buffer,
PCM_BUFFER_SIZE * 2);
if(status != AUDIO_OK)
{
return STATUS_AUDIO_RECORD_FAILURE;
}
return STATUS_SUCCESS;
}
获取录音结果
uint8_t audio_record_buffer_get(AUDIO_DATA *audio_data)
{
if(!audio_data)
{
return STATUS_AUDIO_NULL_PTR;
}
/* 只有在录音完成状态下才允许读取数据 */
if(record_state == RECORD_STATE_COMPLETED)
{
audio_data->data_ptr = record_buffer;
audio_data->size = record_buffer_size;
return STATUS_SUCCESS;
}
return STATUS_FAILURE;
}
添加中断服务函数
void AUDIO_SAIx_DMAx_IRQHandler(void)
{
HAL_DMA_IRQHandler(haudio_out_sai.hdmatx);
}
void AUDIO_DFSDM_DMAx_LEFT_IRQHandler(void)
{
HAL_DMA_IRQHandler(haudio_in_dfsdm_leftfilter.hdmaReg);
}
void AUDIO_DFSDM_DMAx_RIGHT_IRQHandler(void)
{
HAL_DMA_IRQHandler(haudio_in_dfsdm_rightfilter.hdmaReg);
}
SDRAM 链接脚本配置
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 2048K
SD_RAM (xrw) : ORIGIN = 0xC0000000, LENGTH = 8192K
}
.sd_ram :
{
*(.SD_RAM);
} >SD_RAM