1. SAI 简介

SAI(Serial Audio Interface,串行音频接口) 在 STM32 中被广泛用于连接外部音频设备。通过 SAI,MCU 可以与放大器、ADC、DAC、音频处理器等外部音频芯片进行高效的数据交互。

本文将介绍 STM32 F7 系列 SAI 的主要功能及其典型使用方法。

1.1 SAI 内部结构

图片来源: STM 32 手册
  • 双子模块架构: SAI 由两个相互独立的音频子模块组成,每个子模块都带有专用的时钟发生器。
  • 移位寄存器: 每个子模块包含一个32位移位寄存器,用于音频数据的串行输入与输出。
  • FIFO 缓存: 每个子模块内置 FIFO,既可由 CPU 直接访问,也可通过 DMA 实现高效传输,降低 CPU 负担。
  • I/O 管理: 每个子模块可控制4根专用信号线:
    • FS_x:帧同步
    • SCK_x:位时钟
    • SD_x:串行数据
    • MCLK_x:主时钟

1.2 工作模式:

每个子模块既可作为发送器(Transmitter),也可作为接收器(Receiver),并支持主模式(Master) 和从模式(Slave):

  • 主模式:子模块自行生成 SCK_x 和 FS_x 信号。
  • 从模式:子模块使用来自外部或另一子模块的 SCK_x 和 FS_x 信号。

⚠️ 特殊情况:在 AC’97 协议模式下,即使子模块作为从机使用外部时钟,FS 信号仍然由 SAI 输出。

1.3 支持的协议模式

  • Free Protocol Mode(可配置 I²S、PCM、TDM 等协议)
    • I2S 

      I²S 是一种用于在集成电路组件间传输数字音频信号的串行接口协议,通常用于 MCU、DAC、ADC 或音频处理器之间。其特点是将音频信号以脉冲编码调制(PCM)形式进行传输,支持立体声(左右声道)数据。

      信号线:

      • 串行时钟 (SCK),又称 位时钟 (BCLK)
      • 字选择 (WS),又称左右声道时钟 (LRCLK) 或帧同步 (FS)。
        • WS = 0:左声道
        • WS = 1:右声道
      • 串行数据 (SD)
    • TDM

      • 通过时间划分时隙,将多路信号复用到同一物理链路上。
      • 每帧划分为多个 Slot(时隙)。
      • 每个通道(声道)分配固定 Slot,数据在对应 Slot 中发送。
  • S/PDIF 接口模式(符合 IEC 60958 标准)
  • AC’97 模式(兼容 Audio Codec '97 标准)

1.4 帧同步信号

  • 用于标识一帧音频数据的起始位置
  • I²S 协议:表示左右声道切换(L/R Clock)
  • TDM/PCM 协议:标记帧开始,帮助接收端正确区分 Slot

1.5 Slot配置

  • Slot:音频帧的基本单元,承载实际音频数据
  • 每帧由若干 Slot 构成,STM32 SAI 最多支持 16 个 Slot
  • Slot 大小可独立配置(16bit、24bit、32bit 等)
  • TDM 模式:不同通道数据依次放入不同 Slot
  • I²S/PCM 模式:常用两个 Slot(左/右声道)

2. 实验

2.1 实验准备

必备开发软件:STM32CubeIDE、STM32CubeMX

实验目标:掌握SAI基本操作,通过BSP软件包实现 Audio Record & Play。

硬件需求:STM32 开发板(如 STM32F746G-DISCO)

2.2 实验步骤

  • 打开 STM32CubeMX,选择目标芯片或开发板,创建新工程。

  • 配置 PLLM 为 25,以产生 1 MHz 时钟用于 SAI 时钟源。 

  • 生成代码并导入 STM32CubeIDE.

2.3 实验代码

  • 从下载的板子软件包里找到需要的BSP文件,添加到工程里: 

  • 设置工程属性,将 BSP 头文件目录添加到包含路径中。 

  • 从下载的板子软件包里找到以下hal driver文件,添加到工程的hal driver目录下:

stm32fxx_hal_i2s.h
stm32fxx_hal_sai.h
stm32fxx_hal_sai_ex.h
stm32fxx_hal_sdram.h
stm32fxx_hal_tim.h
stm32fxx_hal_tim_ex.h
stm32fxx_hal_uart.h
stm32fxx_hal_uart_ex.h
stm32fxx_hal_ll_fmc.h
stm32fxx_hal_i2s.c
stm32fxx_hal_sai.c
stm32fxx_hal_sai_ex.c
stm32fxx_hal_sdram.c
stm32fxx_hal_tim.c
stm32fxx_hal_tim_ex.c
stm32fxx_hal_uart.c
stm32fxx_hal_uart_ex.c
stm32fxx_hal_ll_fmc.c
  • stm32f7xx_hall_conf.h中启用以下宏定义
#define HAL_SDRAM_MODULE_ENABLED
#define HAL_I2S_MODULE_ENABLED
#define HAL_SAI_MODULE_ENABLED
#define HAL_TIM_MODULE_ENABLED
#define HAL_UART_MODULE_ENABLED

  • 修改Flash LD文件配置SDRAM:
    MEMORY
    {
        RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 320K
        FLASH    (rx)    : ORIGIN = 0x8000000,   LENGTH = 1024K
        SD_RAM (xrw)   : ORIGIN = 0xC0000000,    LENGTH = 8192K
    }

   .sd_ram :
   {
       *(.SD_RAM);
   } >SD_RAM
  • 包含头文件与定义全局变量:
#include "stm32746g_discovery_audio.h"
#include "stm32746g_discovery.h"
#define PCM_BUFFER_SIZE            (1024)
#define RECORD_BUFFER_SIZE         (409600)
#define VOLUME_LEVEL               (70)

static uint16_t pcm_buffer[PCM_BUFFER_SIZE];
__attribute__((section(".SD_RAM"))) uint16_t record_buffer[RECORD_BUFFER_SIZE]; // 把录音buffer放在sdram
static uint32_t record_buffer_size = 0;
uint32_t play_size = 0;
  • 实现回调函数:

// 录音 buffer 完整回调
void BSP_AUDIO_IN_TransferComplete_CallBack(void) {

	memcpy(record_buffer + record_buffer_size, pcm_buffer + PCM_BUFFER_SIZE / 2, PCM_BUFFER_SIZE);

	record_buffer_size += PCM_BUFFER_SIZE / 2;

	if(record_buffer_size >= RECORD_BUFFER_SIZE)
	{
		BSP_AUDIO_IN_Stop(CODEC_PDWN_SW);
	}
}

// 录音 buffer 半满回调
void BSP_AUDIO_IN_HalfTransfer_CallBack(void) {

	memcpy(record_buffer + record_buffer_size, pcm_buffer, PCM_BUFFER_SIZE);

	record_buffer_size += PCM_BUFFER_SIZE / 2;

	if(record_buffer_size >= RECORD_BUFFER_SIZE)
	{
		BSP_AUDIO_IN_Stop(CODEC_PDWN_SW);
	}
}

// 播放 buffer 完整回调
void BSP_AUDIO_OUT_TransferComplete_CallBack(void)
{
    memcpy(pcm_buffer + PCM_BUFFER_SIZE/2, record_buffer + play_size, PCM_BUFFER_SIZE);
    play_size+=PCM_BUFFER_SIZE/2;

    if(play_size >= RECORD_BUFFER_SIZE)
    {
    	BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
    }
}

// 播放 buffer 半满回调
void BSP_AUDIO_OUT_HalfTransfer_CallBack(void)
{
    memcpy(pcm_buffer, record_buffer + play_size, PCM_BUFFER_SIZE);
    play_size+=PCM_BUFFER_SIZE/2;

    if(play_size >= RECORD_BUFFER_SIZE)
    {
    	BSP_AUDIO_OUT_Stop(CODEC_PDWN_SW);
    }
}
  • 初始化与录放音:
    // 初始化sdram
    BSP_SDRAM_Init();

    // 初始化音频输入
    if(BSP_AUDIO_IN_Init(DEFAULT_AUDIO_IN_FREQ, // 音频采样频率
                         DEFAULT_AUDIO_IN_BIT_RESOLUTION, // 每个采样点的数据位数
                         DEFAULT_AUDIO_IN_CHANNEL_NBR // 声道数
                         ) == AUDIO_OK)
    {
        // 开始录音
        BSP_AUDIO_IN_Record((uint16_t *) (pcm_buffer), PCM_BUFFER_SIZE);
    }

    // 等待录音完成
    while(record_buffer_size < RECORD_BUFFER_SIZE){};

    // 初始化音频输出
    if(BSP_AUDIO_OUT_Init(OUTPUT_DEVICE_BOTH, VOLUME_LEVEL , DEFAULT_AUDIO_IN_FREQ) == AUDIO_OK)
    {
        // 初始化播放buffer
        memcpy(pcm_buffer, record_buffer, PCM_BUFFER_SIZE*2);
        play_size += PCM_BUFFER_SIZE;

        // 配置两个slot
        BSP_AUDIO_OUT_SetAudioFrameSlot(CODEC_AUDIOFRAME_SLOT_02);

        // 开始播放
        BSP_AUDIO_OUT_Play((uint16_t*)pcm_buffer, PCM_BUFFER_SIZE*2);
    }
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐