SAI这个外设,在我刚开始做STM32应用时一直把它归类在“音频专用件”里——无非就是I2S时序、左右声道、播放WAV文件。直到一次项目里要同时采集8通道24位同步ADC的数据,才发现SAI被严重低估了。Sony的PlayStation里SAI能干的事,远比“放歌”要广得多。这次直接拿STM32的SAI接口去对接AD7768这种高速Σ-Δ ADC,实现多通道连续采集,配合一套还算讲究的PCB布局,效果超出预期。这篇就把整套思路记录下来,从外设原理、时序匹配、代码配置到布局要点,一次性说透。
SAI不走音频走数据,本质上是在利用它的同步串口框架做TDM传输。这个思路一旦打开,很多“音频接口只能干音频”的印象就该被纠正了。这篇文章既适合正在做数据采集、振动监测、电力分析、多通道同步测量的朋友,也适合想深入理解STM32 SAI外设本质的人。
1. SAI接口为什么能扛起AD7768这种高速ADC
1.1 SAI的本质是一条“通用同步时分串行总线”
很多人一看到SAI全称是Serial Audio Interface,就默认它只能接音频Codec。但从芯片内部结构看,SAI就是一个带帧同步信号和位时钟的同步移位寄存器统。它的核心资源是四样东西:
- 位时钟(CK),决定每一位数据的节拍
- 帧同步信号(FS),标记一帧数据的起始
- 串行数据线(SD),把数据一位一位挪出去或挪进来
- 可编程的帧格式,包括帧长、时隙数、时隙宽度、帧同步偏移
这个框架和音频里的I2S、TDM没有本质区别,但放到工业采集场景里,它恰恰是一种硬件级的TDM(时分复用)总线。AD7768这种多通道ADC的输出接口,正好也是这种“一帧多时隙”的结构。
对比SPI,SPI通常会有一个片选信号CS,一个CS拉低周期里只能连续传输一串数据;虽然STM32的SPI也能靠软件控制CS实现连续多字节,但每字节的边界、帧同步的精确控制都不够用。对比FMC并行总线,FMC适合接并口ADC或SRAM,但在同步串行ADC面前就有点杀鸡用牛刀,还白白占用大量引脚。
SAI真正合适的场景,是多通道、连续、等间隔的数据流传输。AD7768这种8通道ADC,每次转换完成后输出一帧32位×8通道的数据,接收端必须精准地知道一帧从哪里开始,每个通道数据落在哪个时段内,这正是SAI的TSEL、SLOTR、FRCR寄存器所描述的模型。
1.2 AD7768的数字接口为什么天生一副“音频脸”
AD7768是ADI旗下24位8通道同步采样Σ-Δ ADC,最高支持每通道256 kSPS采样率。它的数字输出接口引脚包括DCLK、FSYNC、DOUTx、DRDY,其中DCLK是位时钟,FSYNC是帧同步,DOUTx是串行数据线。你看这个结构,是不是和SAI的CK、FS、SD几乎一一对应?
AD7768通过配置可以工作在主模式或从模式。主模式下,ADC产生DCLK和FSYNC,外部设备作为从机接收数据;从模式下,外部控制器提供DCLK和FSYNC。无论哪种模式,输出的数据格式都是一帧内依次排列8个通道的数据,与SAI的多时隙帧结构完全兼容。
这里有个非常经典的对应关系:AD7768的FSYNC对应SAI的FS,DCLK对应SAI的CK,DOUT0对应SAI的SD。把AD7768配成主模式,STM32 SAI配成从模式,就能天然地实现硬件同步数据流接收,不需要CPU在中断里频繁判断数据边界。
有个容易被忽略的细节:AD7768的FSYNC极性、DCLK采样沿都可以通过寄存器配置,这给了设计者很大的兼容余地。而这些参数配置必须与STM32 SAI端的帧同步极性、位时钟极性严格匹配,否则会出现“数据错位半拍”这种极其隐蔽的问题。
1.3 实测吞吐量心里要有数:单线SAI能接多高速率
SAI毕竟不是专门为超高带宽设计的接口,使用前要对吞吐量做个简单估算。假设AD7768跑在48 kSPS、8通道全部开启、输出24位数据,采用单线DOUT0输出,那么数据率就是:
48 kSPS × 8通道 × 24位 = 9.216 Mbps
这个速率对STM32H7的SAI来说毫无压力。如果采样率拉到192 kSPS,数据率变成:
192 kSPS × 8通道 × 24位 ≈ 36.864 Mbps
这种情况下单线SAI就有点紧张了。STM32H7的SAI位时钟上限通常可以跑到上百MHz,但还要考虑APB2总线和内部时钟树的实际配置,超过一定频率后时钟裕量、信号完整性都会出问题。此时通常有两个选择:把AD7768切换到2线或4线输出模式,或者降低采样率。我在项目里一般以24.576 Mbps为单线SAI的“心理安全线”,超过就考虑2线模式。
2. 接线之前,先把帧同步、槽位、时钟沿这三件事对齐
2.1 主从模式的选择:让ADC做时钟主,SAI做从
我强烈建议在实际方案里让AD7768做主时钟源,STM32 SAI配成从模式。原因很简单:ADC的采样时钟由自身MCLK决定,如果外部主机提供位时钟和帧同步,任何一个时钟相位抖动、毛刺都可能直接污染转换结果。反过来,让ADC自己产生DCLK和FSYNC,它能保证输出数据与内部转换时刻严格同步,采集链路最干净。
SAI配置成从模式时,位时钟CK和帧同步FS都来自外部引脚。在CubeMX里只需要把SAI模式改成Slave,然后设置好帧格式参数就行。AD7768主模式下输出的DCLK频率由MCLK与分频系数决定,一般选用2.048 MHz、4.096 MHz、12.288 MHz这类常见音频系统主时钟倍数,表格后面会给具体搭配。
2.2 帧长度、时隙宽度:8通道塞进一个SAI帧
SAI的帧结构比想象中灵活。FRCR寄存器里的FRL字段定义了帧长度,注意这个字段的值是“半帧长度减1”,实际支持最小8位、最大256位的一帧。一帧内最多可以分成16个时隙,每个时隙的宽度可以是数据宽度、16位或32位。
AD7768在单线输出模式下一帧恰好是8通道×24位 = 192位。把SAI帧长设为192位,每个时隙24位,8个时隙刚好一一对应8个通道。但我更推荐把帧长设为256位、每个时隙32位,只使用其中的低24位。原因是SAI的SLOTR寄存器对于32位时隙的处理最规整,DMA搬运时按32位对齐,后续用位操作提取数据也方便,而且不易出现时隙边界错位。
下面这段是CubeMX生成的SAI初始化核心片段,我在H7系列上实测可直接使用:
/* SAI1_Block_A */ hsai1.Instance = SAI1_Block_A; hsai1.Init.AudioMode = SAI_MODEMASTER; // 从模式改为 SAI_MODESLAVE hsai1.Init.Synchro = SAI_SYNCHRONOUS; hsai1.Init.OutputDrive = SAI_DISABLE; hsai1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE; hsai1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_QF; hsai1.Init.AudioFrequency = 48000; // 实际频率由外部时钟决定 hsai1.Init.MonoStereoMode = SAI_STEREOMODE; hsai1.Init.CompandingMode = SAI_NOCOMPANDING; hsai1.Init.TriState = SAI_TRISTATEMANAGEMENT;这里尤其要注意NoDivider和AudioFrequency,从模式下这两个参数只是供内部计算参考,真正起决定作用的是外部输入的DCLK频率,配置不对不影响接收,但会影响你在调试时对时序的误判。
2.3 时隙使能与数据宽度:别把通道数据搞错位
SAI的SLOTR寄存器支持16个时隙,每个时隙都可以独立使能。AD7768单线模式只有8个通道数据,因此可以只使能SLOTR[15:0]中的前8个槽位,后续槽位自动填充为0或忽略。
数据宽度在DATALEN字段里设置,这里要和AD7768的24位输出严格一致。如果SAI数据宽度设成16位,ADC输出的24位数据就会被迫截断;设成24位时,每个时隙正好容纳一个通道的完整数据。
经验之谈:如果数据总是偏移一个固定值,先检查时隙使能位,而不是查DMA。我之前遇到数据总差一帧的问题,重新检查SLOTR才发现高位槽位被误使能了,导致后面的通道数据整体前移。
2.4 宽窄线模式:吞吐量不够时的进阶玩法
AD7768的输出模式不止单线一种,它支持1线、2线和4线输出。2线模式下DOUT0和DOUT1并行输出,前4个通道走DOUT0,后4个通道走DOUT1,相当于吞吐翻倍。4线模式吞吐再翻一倍。
这对STM32来说是个考验,因为一个SAI块只有一条SD引脚。好在STM32H7有两个SAI块,SAI1和SAI2,可以分别接两条数据线。我就做过一个方案,AD7768跑2线模式,DOUT0接SAI1的SD,DOUT1接SAI2的SD,每路接收4个通道数据,配合一个硬件同步触发,最终做到每通道256 kSPS满速率采集,DCLK接近50 MHz,依然稳定。
如果要用4线模式,就需要外部逻辑或两片STM32配合,复杂度会直线上升,除非项目对采样率有硬性要求,否则不推荐。
3. 从CubeMX到寄存器:让SAI真正“吃”进AD7768的数据流
3.1 CubeMX里的几个关键参数别乱填
CubeMX虽然帮你生成了初始化代码,但对SAI这种外设,很多默认值并不适合工业采集场景。下面这张表是我每次配置SAI时都会逐一核对的条目:
| 参数 | 建议配置 | 原因 |
|---|---|---|
| Audio Mode | PCM/DSP | 支持TDM多时隙帧结构 |
| Frame Length | 256 bits | 容纳8通道×32位时隙 |
| Frame Sync Active | 可选高或低 | 需与AD7768 FSYNC极性一致 |
| Frame Sync Offset | 1 bit | AD7768 FSYNC后一个DCLK开始输出数据 |
| Data Size | 24 bits | 匹配AD7768精度 |
| Slot Size | 32 bits | DMA对齐,提取方便 |
| Clock Polarity | 取决于ADC输出沿 | 保证采样数据稳定 |
尤其值得强调的是Frame Sync Offset。SAI支持帧同步信号与数据第一位之间的偏移设置,如果AD7768在FSYNC下降沿后一个DCLK上升沿开始输出第一位数据,那SAI的FSOFF就设为1 bit。偏移设置错了,看起来数据能收到,但每帧整体提前或滞后一个位时钟,多通道数据会整体错位。
3.2 AD7768这边的寄存器配置顺序
AD7768的接口配置通过SPI完成,上电后先配置电源模式、抽取率,再配置输出接口模式。下面是一段简化配置序列,实际项目中需要根据你的采样率要求调整寄存器值:
// 伪代码:AD7768 通过 SPI 配置 ad7768_spi_write(0x00, 0x01); // 电源模式寄存器:正常功耗模式 ad7768_spi_write(0x02, 0x00); // 抽取率设置:根据 MCLK 和采样率计算 ad7768_spi_write(0x03, 0x00); // 接口控制:单线模式、FSYNC 极性、DCLK 沿选择 ad7768_spi_write(0x04, 0x80); // 通道使能:8通道全开 ad7768_spi_write(0x05, 0x00); // 数字滤波器配置AD7768的DCLK频率配置也很关键。它内部会对MCLK进行分频产生DCLK,DCLK必须足够快,保证在一帧时间内把所有通道数据搬完。比如8通道48 kSPS、24位数据,单线模式下DCLK至少要大于9.216 MHz,通常留20%裕量,配到12.288 MHz。
3.3 DMA环形缓冲与数据重排:接收只是第一步
SAI外设自带FIFO,配合DMA将数据直接搬运到内存,能做到完全无CPU干预的连续采集。我习惯用DMA循环模式,配置一个环形缓冲区,每次传输完成中断后计算当前写入位置,按帧处理。
// 伪代码:DMA环形缓冲接收,帧提取处理 #define FRAME_SIZE 8 // 8通道 #define BUFFER_SIZE (FRAME_SIZE * 4) // 每个通道32位时隙 uint32_t rx_buffer[BUFFER_SIZE]; uint32_t channel_data[FRAME_SIZE]; // DMA传输完成或半传输中断 void on_dma_half_transfer(void) { process_frame((uint32_t *)rx_buffer); } void process_frame(uint32_t *buf) { for (int i = 0; i < FRAME_SIZE; i++) { uint32_t raw = buf[i]; // 提取低24位有效数据并做符号扩展 int32_t sample = (int32_t)(raw << 8) >> 8; channel_data[i] = sample; } // 更新解析指针,处理环形缓冲边界 }这里有个非常实用的技巧:环形缓冲区的解析要同时处理“半传输”和“全传输”两个中断,才能避免指针越界和数据拼接错误。很多新手只处理全传输中断,结果缓冲区前半段数据被覆盖后才发现错误。
3.4 从乱码到稳定数据:沿捕获和FIFO阈值的调优过程
我在调试时遇到过数据乱码问题,后来发现是SAI的FIFO阈值设置不当。SAI FIFO深度在不同系列上不同,STM32H7上通常为8个字。阈值设得太高,DMA搬运不及时可能溢出;设得太低,又会频繁触发中断。
调优顺序是:先用逻辑分析仪抓到DCLK、FSYNC和SD三路波形,确认时序帧结构正确之后,再动软件。如果没有逻辑分析仪,可以用GPIO翻转法在传输完成中断里翻转一个引脚,示波器量一下中断频率是否符合预期,快速定位问题在哪一层。
4. PCB布局指南:AD7768的模拟性能,很容易被数字布线毁掉
4.1 三个“供电域”必须分清楚
AD7768有模拟电源AVDD1、AVDD2和数字电源DVDD,还有高精度基准电压输入。在PCB布局时必须把它们当成三个独立的电力域来设计,否则电源噪声直接耦合到转换结果中。
我见过很多第一次做数据采集板的朋友,把所有电源都接到同一个LDO输出,然后寄希望于磁珠可以隔离。实际上磁珠对高频噪声的隔离能力有限,更可靠的做法是:
- 模拟电源AVDD1/AVDD2由独立的低噪声LDO供电,输出端至少各放2个100 nF电容加1个10 μF钽电容
- 数字电源DVDD可以与MCU供电同源,但必须通过磁珠或小电阻隔离,并在AD7768的DVDD引脚就近放置100 nF电容
- 基准电压REF输入单独走线,基准源放在距离AD7768 REF引脚不超过5mm的位置
一个反常识的点是:AD7768对电源噪声的敏感度,远高于很多人的直觉。实测中AVDD上叠加6 mVpp的开关电源纹波,有效位数能掉0.8位,这在精密测量项目里是致命的。所以开关电源纹波大时,必须给ADC供电加LC滤波而不是只加电容。
4.2 时钟线、数据线、地平面的处理原则
SAI接口接AD7768时,数字信号线有MCLK、DCLK、FSYNC、DOUTx这几条。布局时遵循以下原则:
- MCLK引脚走线尽可能短,晶振或时钟源距离AD7768的MCLK引脚不超过10mm,走线不要打过孔,更不能在晶振下方走其他高速数字线
- DCLK、FSYNC、DOUT这几条线保持等长,长度差控制在2mm以内,避免建立保持时间裕量不足
- 每条数字信号线靠近输出端串联22Ω~33Ω电阻,抑制振铃和过冲,同时降低EMI
- 数字信号线不得跨越模拟区域的参考平面缝隙,否则回流电流会切割地平面,引入严重噪声
地平面处理的选择,当前主流做法是不对模拟地和数字地做物理分割,而是通过完整的地平面和合理的器件分区来保证信号回流路径最短。如果强行把AGND和DGND分成两块,数字信号回流电流可能被迫绕过缝隙,反而形成一个大环天线,高频噪声更严重。我做过两块对比板,分割地的方案在采样数据上明显出现了周期性噪声尖峰。
4.3 前端RC抗混叠滤波的设计与布局位置
AD7768是Σ-Δ ADC,内部虽然有数字抽取滤波器,但前端仍然建议加一阶RC抗混叠滤波,这几乎是Σ-Δ ADC的标准配置。RC截止频率通常设置在采样率附近的1/10到1/20,例如48 kSPS采样时,截止频率约2.4 kHz到4.8 kHz。电阻阻值通常在几欧到几十欧之间,电容选择C0G材质,容值在1 nF到10 nF之间。
这里有个布局关键点:RC滤波的电容必须紧贴ADC的模拟输入引脚放置,不是靠近连接器,而是靠近ADC本身。因为RC网络的低阻抗路径是高频噪声泄放到地的通道,如果走线过长,电感效应会让电容在几十MHz时失去旁路作用。
差分输入时,两个输入端的RC参数必须完全对称,电阻和电容都使用相同型号,布局上做到镜像对称。不对称的RC会让共模噪声转化成差模噪声,直接影响ADC信噪比。
4.4 叠层设计与整体布局顺序建议
四层板是AD7768采集板的底线配置,我建议采用如下叠层:
| 层 | 功能 | 关键走线 |
|---|---|---|
| 顶层 | 信号与器件 | AD7768、RC电路、时钟电路、SAI信号线 |
| 第二层 | 完整地平面 | 严禁分割,为所有信号提供回流路径 |
| 第三层 | 电源平面 | 模拟电源、数字电源分区,但共地 |
| 底层 | 信号与去耦 | 去耦电容、低速控制线、SPI配置线 |
布局顺序有讲究,先放基准源,再放AD7768,然后是SAI连接器,最后才是MCU电源模块。ADC的位置应该尽量靠近板边,让模拟输入连接器到ADC输入引脚的走线最短。MCU放在另一侧,数字电源和地线朝MCU方向走,避免数字噪声回流穿过模拟区。
5. 调试实录:我在SAI+AD7768方案里踩过的四个坑
5.1 帧同步极性反了:所有数据看起来能收到,但全是0x800000
第一次调通SAI接收时,DMA正常触发,数据也能搬进缓冲区,但所有通道读出来都是0x800000附近,像是某个固定的中间偏置值。排查了很久,最后发现是AD7768的FSYNC极性配置和SAI的FS极性不一致。
AD7768的FSYNC默认是低有效,即在帧开始时拉低,而STM32 SAI的Frame Sync Active如果设成高有效,就会把帧同步信号识别成反相,导致SAI把数据的中间比特当成了帧头。解决办法是交叉检查两种组合:要么把AD7768寄存器第3位设为高有效,要么把SAI的Frame Sync Active改成低有效。用逻辑分析仪对比两者波形,一眼就能看出错在哪。
5.2 槽位偏移半拍:盲调DMA不如抓波形
另一次问题表现为前7个通道数据正确,第8个通道数据永远是0,把DMA缓冲区和代码查了个遍也没发现问题。最后用逻辑分析仪抓FSYNC和SD,发现FSYNC下降沿之后,SAI从第一个DCLK上升沿就开始采第一位数据,但AD7768实际从下降沿后第二个上升沿才开始输出有效数据,相当于整体偏移了半拍到一拍。
在SAI配置里把FSOFF从1 bit改为2 bit,数据立刻全部正确。这个经验是:遇到通道数据错位或某个通道恒为0,先别急着改DMA,优先检查帧同步偏移和数据对齐方式。
5.3 电源纹波6mV带走了0.8位有效分辨率
有一块采集板在实测中动态范围始终达不到手册标称值,噪声基底明显偏高。用近场探头扫了一圈,发现噪声集中在开关频率附近,顺藤摸瓜锁定了DCDC输出端到ADC供电网络之间的薄弱点。
后来在DCDC输出后端增加了π型LC滤波器,转折频率设在1.5kHz左右,同时在AD7768的AVDD1引脚旁增加了一颗10nF+100nF并联电容组合,实测有效位数从16.2位提升到17位以上。这个教训说明,PCB布局指南里强调的电源去耦,不是纸上谈兵,而是直接关系到你能从ADC里真正拿到多少性能。
5.4 分割地的板子总有机身噪声抖动
我在对比测试时做过两块布局完全不同的底板,一块采用完整地平面,另一块把AGND和DGND分成了两块并在单点连接。单点连接的板子在低频段表现差异不大,但采样率提升到100 kSPS以上时,FFT结果里出现了一簇一簇的毛刺,间隔正好与DCLK频率相关。
这说明数字返回电流被迫绕行,在地平面缝隙处形成了辐射环路。改用完整地平面加分区布局后,同样的芯片、同样的固件,毛刺几乎消失。从那以后,我的数据采集板一律采用完整地平面,靠器件分区而不是物理切割来处理模拟数字混合信号。
最后说点我自己的体会
SAI接口做AD7768采集,这套组合最迷人的地方在于“用现成的外设干了一件它原本没打算干的事”,而且干得还不差。很多工程师对STM32外设的印象停留在手册描述的“典型应用”,但芯片厂商给典型应用只是因为市场需要,不代表外设的能力边界就在那里。遇到多通道同步采集需求时,SAI天然支持帧同步和多时隙,比SPI省心得多。
对于准备复刻这套方案的朋友,我的建议是:先别急着上高速采样率,把48 kSPS、8通道、24位这条链路从头到尾调通,确认每一帧数据对应关系完全正确之后,再逐步拉升采样率,扩到2线模式。PCB布局方面,优先保证地平面完整、模拟电源独立、RC滤波贴近引脚这三点,已经能拿回大部分性能。一个稳定的采集系统,从来不是单片机的功劳,而是接口、时钟、电源、布局一起协作的结果。