STM32 SAI 通讯原理与 TDM 应用
一、SAI 通讯原理基础
1.1 SAI 概述
SAI(Serial Audio Interface)串行音频接口是 STM32 系列单片机中专门为音频和高精度传感器应用设计的高性能外设。它提供了两个完全独立的音频子块 (SAI_A 和 SAI_B),每个子块都可以独立配置为主机或从机,支持全双工通信。
SAI 相比传统 I2S 接口的核心优势:
- 支持TDM 时分复用模式,最多可连接 16 个设备
- 更灵活的帧和时隙配置
- 支持 8/10/16/20/24/32 位数据宽度
- 内置 FIFO 缓冲区 (8 个 32 位字)
- 支持 DMA 传输,减轻 CPU 负担
1.2 SAI 核心架构
每个 SAI 子块包含以下关键组件:
- 时钟生成单元:产生串行时钟 (SCK) 和帧同步信号 (FS)
- 帧同步单元:生成和检测帧同步信号
- 数据串行化 / 反串行化单元:并行数据与串行数据转换
- FIFO 缓冲区:8 个 32 位深度,用于数据缓存
- DMA 接口:支持高速数据传输
- 中断控制器:提供多种中断源
1.3 SAI 工作模式详解
SAI 支持五种主要工作模式:
| 模式 | 描述 | 典型应用 |
|---|---|---|
| Free 协议模式 | 最灵活的模式,可配置为任何自定义协议 | TDM 接口传感器、自定义音频协议 |
| I2S 模式 | 标准 I2S 协议,支持 Philips、MSB、LSB 对齐 | 立体声麦克风、音频编解码器 |
| PCM 模式 | 脉冲编码调制模式,支持短帧和长帧 | 单声道麦克风、电话系统 |
| AC'97 模式 | 音频编解码器接口 | 传统音频 Codec |
| SPDIF 模式 | 索尼 / 飞利浦数字接口 | 数字音频传输 |
1.4 TDM 模式核心原理
TDM(Time Division Multiplexing)时分复用模式是 SAI 最强大的功能,它允许在同一根串行数据线上传输多个通道的数据。
TDM 工作机制:
- 数据传输以帧 (Frame)为单位
- 每个帧包含多个时隙 (Slot)
- 每个时隙对应一个物理设备 (传感器 / 麦克风)
- 帧同步信号 (FS) 在每个帧开始时触发
- 数据在串行时钟 (SCK) 的边沿被采样
- 每个设备只在自己的时隙内发送 / 接收数据
TDM 关键参数:
- 帧长度 (FRL):一个帧包含的总位数
- 时隙数量 (NBSLOT):一个帧包含的时隙数 (最多 16 个)
- 时隙大小 (SLOTSZ):每个时隙的位数 (8/16/32 位)
- 帧同步活动长度 (FSALL):FS 信号有效持续的位数
- 帧同步偏移 (FSOFF):FS 信号与第一个数据位的偏移量
二、SAI 关键寄存器详解
2.1 全局配置寄存器 (SAI_GCR)
地址偏移: 0x00 复位值: 0x00000000- SYNCOUT[1:0](位 1-0): 同步输出选择
- 00: 无同步输出
- 01: SAI_A 作为同步主
- 10: SAI_B 作为同步主
- SYNCIN[1:0](位 3-2): 同步输入选择
- 00: 无同步输入
- 01: 同步于 SAI_A
- 10: 同步于 SAI_B
2.2 子块配置寄存器 1 (SAI_xCR1)
地址偏移: 0x04 + 0x0400 * x (x=A/B) 复位值: 0x00000040- MODE[1:0](位 1-0): 模式选择
- 00: 主发送器 (Master Transmitter)
- 01: 主接收器 (Master Receiver)
- 10: 从发送器 (Slave Transmitter)
- 11: 从接收器 (Slave Receiver)
- PRTCFG[1:0](位 3-2): 协议配置
- 00: Free 协议 (用于 TDM)
- 01: SPDIF 协议
- 10: AC'97 协议
- 11: 保留
- DS[3:0](位 7-4): 数据大小
- 0000: 8 位
- 0001: 10 位
- 0010: 16 位
- 0011: 20 位
- 0100: 24 位
- 0101: 32 位
- LSBFIRST(位 8): 最低位优先
- 0: MSB 优先 (默认)
- 1: LSB 优先
- CKSTR(位 9): 时钟极性
- 0: 数据在 SCK 下降沿发送,上升沿采样
- 1: 数据在 SCK 上升沿发送,下降沿采样
- SYNCEN[1:0](位 11-10): 同步使能
- 00: 异步模式
- 01: 内部同步
- 10: 外部同步
- MONO(位 12): 单声道模式
- 0: 立体声
- 1: 单声道
- SAICLK[2:0](位 18-16): SAI 时钟源选择
- 000: PLLSAI1_P
- 001: PLLSAI2_P
- 010: PLL3_P
- 011: I2S_CKIN
- 100: HSI
- MCKDIV[5:0](位 23-19): 主时钟分频系数
- 0-63: 分频值
2.3 子块配置寄存器 2 (SAI_xCR2)
地址偏移: 0x08 + 0x0400 * x (x=A/B) 复位值: 0x00000000- FTH[2:0](位 2-0): FIFO 阈值
- 000: FIFO 空
- 001: 1/4 FIFO
- 010: 1/2 FIFO
- 011: 3/4 FIFO
- 100: FIFO 满
- FFLUS(位 3): FIFO 刷新
- 0: 无操作
- 1: 刷新 FIFO
- TRIS(位 4): 三态管理
- 0: 数据引脚在非活动时隙保持驱动
- 1: 数据引脚在非活动时隙进入高阻
- MUTE(位 5): 静音使能
- 0: 正常操作
- 1: 发送静音值
2.4 帧配置寄存器 (SAI_xFRCR)
地址偏移: 0x0C + 0x0400 * x (x=A/B) 复位值: 0x00000007- FRL[7:0](位 7-0): 帧长度
- 0-255: 帧长度 = FRL + 1 位
- FSALL[6:0](位 14-8): 帧同步活动长度
- 0-127: FS 有效长度 = FSALL + 1 位
- FSDEF(位 16): 帧同步定义
- 0: FS 是开始信号
- 1: FS 是通道识别信号
- FSPOL(位 17): 帧同步极性
- 0: FS 低电平有效
- 1: FS 高电平有效
- FSOFF(位 18): 帧同步偏移
- 0: FS 与第一个数据位同时开始
- 1: FS 比第一个数据位提前一个 SCK 周期
2.5 时隙配置寄存器 (SAI_xSLOTR)
地址偏移: 0x10 + 0x0400 * x (x=A/B) 复位值: 0x00000000- FBOFF[4:0](位 4-0): 第一个位偏移
- 0-31: 第一个数据位的偏移量
- SLOTSZ[1:0](位 7-6): 时隙大小
- 00: 与数据大小 (DS) 相同
- 01: 16 位
- 10: 32 位
- 11: 保留
- NBSLOT[3:0](位 11-8): 时隙数量
- 0-15: 时隙数量 = NBSLOT + 1
- SLOTEN[15:0](位 31-16): 时隙使能
- 每一位对应一个时隙,1 表示使能
2.6 数据寄存器 (SAI_xDR)
地址偏移: 0x14 + 0x0400 * x (x=A/B) 复位值: 0x00000000- 32 位读写寄存器,用于发送和接收数据
- 写入数据会被放入发送 FIFO
- 读取数据会从接收 FIFO 中取出
2.7 状态寄存器 (SAI_xSR)
地址偏移: 0x18 + 0x0400 * x (x=A/B) 复位值: 0x00000008- OVRUDR(位 0): 溢出 / 下溢标志
- 接收模式: 1 表示 FIFO 满时又收到数据 (溢出)
- 发送模式: 1 表示 FIFO 空时需要发送数据 (下溢)
- FREQ(位 3): FIFO 请求标志
- 1 表示 FIFO 达到阈值
- FLVL[2:0](位 9-7): FIFO 级别
- 000: FIFO 空
- 001: FIFO 中有 1-2 个字
- 010: FIFO 中有 3-4 个字
- 011: FIFO 中有 5-6 个字
- 100: FIFO 中有 7-8 个字
三、STM32CubeMX SAI 配置步骤
3.1 系统时钟配置
SAI 对时钟精度要求很高,必须正确配置:
- 在 "Clock Configuration" 中找到 SAI 时钟源
- 通常选择PLLSAI1_P或PLL3_P作为 SAI 时钟源
- 配置 PLL 分频系数,使 SAI 内核时钟为49.152MHz(48kHz 采样率) 或45.1584MHz(44.1kHz 采样率)
- 确保 MCLK 频率 = 采样率 × 256 (标准音频时钟)
3.2 SAI 基本配置
- 在 "Connectivity" 中找到 SAI 并启用
- 选择要使用的子块 (SAI_A 或 SAI_B)
- 配置模式(主发送 / 主接收 / 从发送 / 从接收)
- 配置协议(Free 协议用于 TDM)
- 配置数据大小(通常 24 位用于 MEMS 传感器)
- 配置时钟极性(根据设备手册)
- 配置帧同步参数
- 配置时隙参数
- 配置FIFO 阈值
- 启用DMA(推荐) 或中断
3.3 DMA 配置
- 点击 "Add" 添加 DMA 通道
- 方向选择Peripheral to Memory(接收) 或Memory to Peripheral(发送)
- 优先级选择High或Very High
- 数据宽度选择Word(32 位)
- 启用循环模式(Circular Mode)
- 启用内存增量模式(Memory Increment)
- 禁用外设增量模式(Peripheral Increment)
四、TDM 接口 MEMS 加速度计读写实现
4.1 硬件连接
以ADXL355-TDM三轴加速度计为例:
- SAI_SCK → ADXL355_SCLK
- SAI_FS → ADXL355_FSYNC
- SAI_SD → ADXL355_SDO
- SAI_MCLK → ADXL355_MCLK (可选)
- VCC → 3.3V
- GND → GND
注意:ADXL355-TDM 支持最多 4 个传感器共享同一总线,每个传感器占用一个时隙。
4.2 STM32CubeMX 配置
- 启用 SAI,选择SAI1 Block A
- 模式:Master Receive(主接收)
- Protocol:Free Protocol(自由协议)
- Data Size:24 Bits
- First Bit:MSB First
- Clock Strobing Edge:Falling Edge(数据在下降沿发送,上升沿采样)
- Synchronization:Asynchronous(异步)
- MCLK Output:Enabled(如果需要提供主时钟)
- Frame Configuration:
- Frame Length:31(32 位 / 帧)
- Frame Sync Active Length:0(1 位有效)
- Frame Sync Definition:Start of Frame
- Frame Sync Polarity:Active High
- Frame Sync Offset:No Offset
- Slot Configuration:
- First Bit Offset:0
- Slot Size:32 Bits
- Number of Slots:3(4 个时隙)
- Slot Enable:0x000F(使能前 4 个时隙)
- FIFO Threshold:4 FIFO
- DMA 配置:添加 DMA 通道,循环模式,32 位数据宽度
4.3 完整代码实现
#include "stm32h7xx_hal.h" #include <stdio.h> // 全局变量 SAI_HandleTypeDef hsai_BlockA1; DMA_HandleTypeDef hdma_sai1_a; #define ACCEL_CHANNELS 4 #define ACCEL_BUFFER_SIZE ACCEL_CHANNELS * 2 // 双缓冲 uint32_t accel_tdm_buffer[ACCEL_BUFFER_SIZE]; int32_t accel_data[ACCEL_CHANNELS][3]; // [通道][X/Y/Z] uint8_t accel_data_ready = 0; // SAI接收完成回调函数 void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { if(hsai->Instance == SAI1_Block_A) { accel_data_ready = 1; } } // 初始化SAI TDM加速度计 void SAI_TDM_Accel_Init(void) { // 启动DMA循环接收 HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*)accel_tdm_buffer, ACCEL_BUFFER_SIZE); } // 解析加速度数据 void Parse_Accel_Data(void) { if(!accel_data_ready) return; accel_data_ready = 0; // 处理每个通道 for(int ch=0; ch<ACCEL_CHANNELS; ch++) { // ADXL355输出20位有符号数,左对齐在32位时隙中 // 数据格式: [20位数据][12位0] int32_t raw_data = (int32_t)(accel_tdm_buffer[ch] << 4) >> 12; // 分解X/Y/Z轴数据(ADXL355在一个时隙内连续发送X/Y/Z) // 注意:不同传感器的数据格式可能不同,请参考手册 accel_data[ch][0] = raw_data & 0xFFFFF; // X轴 accel_data[ch][1] = (raw_data >> 20) & 0xFFFFF; // Y轴 accel_data[ch][2] = (raw_data >> 40) & 0xFFFFF; // Z轴 // 符号扩展 if(accel_data[ch][0] & 0x80000) accel_data[ch][0] |= 0xFFF00000; if(accel_data[ch][1] & 0x80000) accel_data[ch][1] |= 0xFFF00000; if(accel_data[ch][2] & 0x80000) accel_data[ch][2] |= 0xFFF00000; // 转换为g值(ADXL355 ±8g范围: 4μg/LSB) float x_g = accel_data[ch][0] * 0.000004f; float y_g = accel_data[ch][1] * 0.000004f; float z_g = accel_data[ch][2] * 0.000004f; printf("Channel %d: X=%.3fg, Y=%.3fg, Z=%.3fg\r\n", ch, x_g, y_g, z_g); } } // 主函数 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SAI1_Init(); SAI_TDM_Accel_Init(); while(1) { Parse_Accel_Data(); HAL_Delay(10); } }五、TDM 接口 MEMS 麦克风拾音实现
5.1 硬件连接
以INMP441-TDM数字 MEMS 麦克风为例:
- SAI_SCK → INMP441_SCK
- SAI_FS → INMP441_WS
- SAI_SD → INMP441_SD
- SAI_MCLK → INMP441_MCLK (可选)
- VCC → 3.3V
- GND → GND
- L/R → GND (左声道) 或 VCC (右声道)
注意:INMP441-TDM 支持最多 8 个麦克风共享同一总线,每个麦克风占用一个时隙。
5.2 STM32CubeMX 配置
- 启用 SAI,选择SAI1 Block A
- 模式:Master Receive(主接收)
- Protocol:Free Protocol(自由协议)
- Data Size:24 Bits
- First Bit:MSB First
- Clock Strobing Edge:Rising Edge(数据在上升沿发送,下降沿采样)
- Synchronization:Asynchronous(异步)
- MCLK Output:Enabled
- Frame Configuration:
- Frame Length:255(256 位 / 帧)
- Frame Sync Active Length:0(1 位有效)
- Frame Sync Definition:Start of Frame
- Frame Sync Polarity:Active High
- Frame Sync Offset:No Offset
- Slot Configuration:
- First Bit Offset:0
- Slot Size:32 Bits
- Number of Slots:7(8 个时隙)
- Slot Enable:0x00FF(使能前 8 个时隙)
- FIFO Threshold:8 FIFO
- DMA 配置:添加 DMA 通道,循环模式,32 位数据宽度
5.3 完整代码实现
#include "stm32h7xx_hal.h" #include <stdio.h> #include <string.h> // 全局变量 SAI_HandleTypeDef hsai_BlockA1; DMA_HandleTypeDef hdma_sai1_a; #define MIC_CHANNELS 8 #define AUDIO_SAMPLE_RATE 48000 #define AUDIO_BUFFER_SIZE 1024 // 每个通道的采样点数 #define TOTAL_BUFFER_SIZE MIC_CHANNELS * AUDIO_BUFFER_SIZE uint32_t mic_tdm_buffer[TOTAL_BUFFER_SIZE]; int32_t audio_buffer[MIC_CHANNELS][AUDIO_BUFFER_SIZE]; uint8_t audio_buffer_ready = 0; uint8_t audio_buffer_half = 0; // 1: 前半部分, 2: 后半部分 // SAI半接收完成回调函数 void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai) { if(hsai->Instance == SAI1_Block_A) { audio_buffer_half = 1; audio_buffer_ready = 1; } } // SAI接收完成回调函数 void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { if(hsai->Instance == SAI1_Block_A) { audio_buffer_half = 2; audio_buffer_ready = 1; } } // 初始化SAI TDM麦克风 void SAI_TDM_Mic_Init(void) { // 启动DMA循环接收 HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*)mic_tdm_buffer, TOTAL_BUFFER_SIZE); } // 处理音频数据 void Process_Audio_Data(void) { if(!audio_buffer_ready) return; audio_buffer_ready = 0; uint32_t *src_ptr; uint32_t start_idx; uint32_t end_idx; // 确定处理哪一部分缓冲区 if(audio_buffer_half == 1) { src_ptr = mic_tdm_buffer; start_idx = 0; end_idx = AUDIO_BUFFER_SIZE / 2; } else { src_ptr = mic_tdm_buffer + TOTAL_BUFFER_SIZE / 2; start_idx = AUDIO_BUFFER_SIZE / 2; end_idx = AUDIO_BUFFER_SIZE; } // 解析TDM数据 for(int sample=0; sample < AUDIO_BUFFER_SIZE/2; sample++) { for(int ch=0; ch<MIC_CHANNELS; ch++) { // INMP441输出24位有符号数,左对齐在32位时隙中 // 数据格式: [24位数据][8位0] int32_t raw_sample = (int32_t)(src_ptr[sample * MIC_CHANNELS + ch] << 8) >> 8; // 存储到音频缓冲区 audio_buffer[ch][start_idx + sample] = raw_sample; } } // 音频处理示例:计算每个通道的RMS值 for(int ch=0; ch<MIC_CHANNELS; ch++) { int64_t sum_sq = 0; for(int i=start_idx; i<end_idx; i++) { sum_sq += (int64_t)audio_buffer[ch][i] * audio_buffer[ch][i]; } float rms = sqrtf((float)sum_sq / (AUDIO_BUFFER_SIZE / 2)); // 转换为dBFS(满量程分贝) float dbfs = 20.0f * log10f(rms / 8388608.0f); printf("Channel %d RMS: %.1f dBFS\r\n", ch, dbfs); } } // 主函数 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SAI1_Init(); SAI_TDM_Mic_Init(); while(1) { Process_Audio_Data(); } }六、SAI 各种模式完整应用示例
6.1 I2S 模式 (立体声麦克风)
CubeMX 配置:
- 模式: Master Receive
- 协议: I2S Philips
- 数据大小: 16 Bits
- 标准: I2S Philips
- 采样率: 44100 Hz
- 时钟极性: Low
代码实现:
#define I2S_BUFFER_SIZE 1024 uint16_t i2s_stereo_buffer[I2S_BUFFER_SIZE]; int16_t left_channel[I2S_BUFFER_SIZE/2]; int16_t right_channel[I2S_BUFFER_SIZE/2]; void I2S_Stereo_Init(void) { HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*)i2s_stereo_buffer, I2S_BUFFER_SIZE); } void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { // 分离左右声道 for(int i=0; i<I2S_BUFFER_SIZE/2; i++) { left_channel[i] = i2s_stereo_buffer[2*i]; right_channel[i] = i2s_stereo_buffer[2*i+1]; } }6.2 PCM 模式 (单声道麦克风)
CubeMX 配置:
- 模式: Master Receive
- 协议: PCM
- 数据大小: 16 Bits
- 标准: PCM Short
- 采样率: 16000 Hz
- 时钟极性: High
代码实现:
#define PCM_BUFFER_SIZE 512 uint16_t pcm_mono_buffer[PCM_BUFFER_SIZE]; void PCM_Mono_Init(void) { HAL_SAI_Receive_DMA(&hsai_BlockA1, (uint8_t*)pcm_mono_buffer, PCM_BUFFER_SIZE); } void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { // 处理PCM数据 for(int i=0; i<PCM_BUFFER_SIZE; i++) { int16_t sample = (int16_t)pcm_mono_buffer[i]; // 处理采样点... } }6.3 全双工模式 (音频编解码器)
CubeMX 配置:
- SAI_A: Master Transmit
- SAI_B: Master Receive
- 协议: I2S Philips
- 数据大小: 16 Bits
- 采样率: 44100 Hz
- 同步: SAI_B 同步到 SAI_A
代码实现:
#define AUDIO_BUFFER_SIZE 1024 uint16_t tx_buffer[AUDIO_BUFFER_SIZE]; uint16_t rx_buffer[AUDIO_BUFFER_SIZE]; void SAI_FullDuplex_Init(void) { // 先启动发送,再启动接收 HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*)tx_buffer, AUDIO_BUFFER_SIZE); HAL_SAI_Receive_DMA(&hsai_BlockB1, (uint8_t*)rx_buffer, AUDIO_BUFFER_SIZE); } void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai) { // 填充发送缓冲区 for(int i=0; i<AUDIO_BUFFER_SIZE; i++) { tx_buffer[i] = 0; // 静音 } } void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai) { // 处理接收数据 memcpy(tx_buffer, rx_buffer, AUDIO_BUFFER_SIZE * 2); // 回环 }6.4 主从同步模式 (多 SAI 同步)
CubeMX 配置:
- SAI1_A: Master Transmit (同步主)
- SAI2_A: Slave Receive (同步从)
- 全局配置: SAI1 SYNCOUT=01, SAI2 SYNCIN=01
代码实现:
void SAI_Sync_Init(void) { // 先启动从设备,再启动主设备 HAL_SAI_Receive_DMA(&hsai2_BlockA1, (uint8_t*)rx_buffer, BUFFER_SIZE); HAL_SAI_Transmit_DMA(&hsai1_BlockA1, (uint8_t*)tx_buffer, BUFFER_SIZE); }七、常见问题与解决方案
7.1 数据丢失 / 溢出问题
现象:SAI_SR 寄存器的 OVRUDR 位被置 1解决方案:
- 提高 DMA 优先级到最高
- 增加 FIFO 阈值 (推荐使用 FIFO 满)
- 使用双缓冲或循环缓冲
- 避免在中断中执行耗时操作
- 检查 SAI 时钟频率是否正确
7.2 数据错位 / 乱序问题
现象:接收到的数据与预期不符解决方案:
- 检查帧同步极性和偏移
- 确认时钟极性 (CKSTR) 设置正确
- 检查数据大小和时隙大小是否匹配
- 确认第一个位偏移 (FBOFF) 设置正确
- 检查硬件连接是否正确
7.3 符号扩展问题
现象:负数显示为很大的正数解决方案:
// 24位数据符号扩展 int32_t sample_24 = (int32_t)(raw_data << 8) >> 8; // 20位数据符号扩展 int32_t sample_20 = (int32_t)(raw_data << 12) >> 12; // 16位数据符号扩展 int16_t sample_16 = (int16_t)raw_data;7.4 时钟同步问题
现象:音频有杂音或周期性失真解决方案:
- 使用高精度的外部晶振
- 配置 PLLSAI 专门用于 SAI 时钟
- 确保主从设备使用相同的时钟源
- 避免在运行时修改 SAI 配置
八、最佳实践总结
- 时钟配置:优先使用 PLLSAI 作为 SAI 时钟源,确保时钟精度
- DMA 传输:始终使用 DMA 传输 SAI 数据,避免 CPU 轮询
- 双缓冲机制:使用半传输和全传输中断实现双缓冲
- FIFO 配置:FIFO 阈值设置为 FIFO 满,减少 DMA 请求次数
- 数据处理:在主循环中处理数据,避免在中断中处理
- 错误处理:定期检查 SAI 状态寄存器,处理溢出和错误
- 硬件设计:SAI 信号线尽量短,避免干扰,使用差分信号
