
1. 为什么AD7606配STM32值得单独写一篇实战记录AD7606这颗芯片在电力监测、电机控制、振动采集这些场景里出镜率极高。8通道同步采样、16位分辨率、内置模拟前端和抗混叠滤波器单颗芯片就能顶掉过去一堆运放加多路开关加独立ADC的组合。但真正上手过的人都知道它的坑不在模拟部分而在数字接口——尤其是用SPI模式去读它的时候。我前后做过三个基于AD7606的采集项目前两次用的是并行接口第三次因为主控引脚紧张改走SPI结果在时序上卡了整整两天。翻遍中文资料大部分文章要么只讲并行模式要么SPI部分一笔带过真正把STM32CubeMX配置、HAL库代码、时序波形三者串起来讲清楚的少之又少。所以这篇就把我踩过的坑、验证过的配置、以及用逻辑分析仪抓出来的波形细节完整记录下来。这篇内容适合两类人一是第一次用AD7606做多通道同步采集的嵌入式工程师二是已经在用但SPI读数不稳定、偶尔丢数据、找不到原因的调试者。我会从CubeMX的引脚配置讲到DMA搬运从CONVST脉冲宽度讲到BUSY下降沿的采样时机尽量让每一步都有依据而不是照着填就行。需要提前说明的是AD7606的SPI模式和并行模式在时序逻辑上有本质区别。并行模式下你读的是数据总线时序相对宽松SPI模式下所有数据都串行移出对SCLK的极性和相位、CS的建立保持时间、以及两次读取之间的间隔都有更严格的要求。这也是为什么很多人并行模式跑得好好的一换SPI就出问题。2. AD7606的SPI接口到底特殊在哪里2.1 它不是标准SPI从机别拿常规套路套很多人第一次看AD7606数据手册时会下意识把它当成一颗普通的SPI ADC比如像MCP3208那样发命令、收数据。但AD7606完全不是这个逻辑。它的SPI接口本质上是只读移位寄存器——你不需要发送任何命令字只要把CS拉低然后在SCLK的驱动下芯片就会把转换结果从DOUTA或DOUTB引脚依次移出。这一点非常关键。如果你在CubeMX里把STM32配成SPI全双工主机HAL库会同时收发虽然发出去的数据AD7606根本不理会但收发过程本身会占用时间还可能因为TX FIFO的状态影响读取节奏。我建议直接用只读模式或者干脆用GPIO模拟SCLK来读后者在时序调试阶段特别有用。AD7606的SPI一次完整读取需要多少个时钟这取决于你读几个通道。8个通道全部读取每个通道16位总共128个SCLK。如果只读部分通道比如只读前4个那就是64个时钟。数据手册里明确写了DOUTA在CS下降沿后的第一个SCLK下降沿开始输出DB15之后每个SCLK下降沿输出下一位。所以SCLK的空闲电平、极性、相位都必须和这个逻辑对齐。2.2 双DOUT引脚设计带来的读取策略AD7606有两个数据输出引脚DOUTA和DOUTB。DOUTA输出通道1到通道4的数据DOUTB输出通道5到通道8的数据。这个设计其实是为了配合并行模式下的双字节读取但在SPI模式下它给了我们一个选择可以只用DOUTA分两次读也可以两个引脚同时接两个SPI接口并行读。我实际项目里用的是单DOUTA方案因为STM32F103只有一个SPI接口可用另一个被其他外设占了。读取流程是第一次CS拉低移出64个时钟读通道1-4CS拉高再拉低移出64个时钟读通道5-8。注意两次读取之间CS必须拉高否则移位寄存器不会复位到通道1的起始位置。如果你用的是STM32F4或F7系列有两个SPI那可以DOUTA接SPI1、DOUTB接SPI2同时启动两个DMA通道一次转换就能把8个通道全部读回来效率翻倍。但要注意两个SPI的SCLK必须同源或者严格同步否则两个移位寄存器的输出会错位。2.3 CONVST、BUSY、CS、SCLK四根线的配合逻辑AD7606的转换启动和读取是分离的。CONVST上升沿触发采样保持所有8个通道同时进入保持状态然后芯片内部开始逐次逼近转换。转换期间BUSY保持高电平转换完成后BUSY下降。你必须在BUSY下降之后才能开始读SPI否则读出来的是上一次的结果或者无效数据。这里有个容易忽略的细节CONVST的脉冲宽度。数据手册要求最小25ns但实际用STM32的GPIO输出时如果你用HAL_GPIO_WritePin直接拉高再拉低中间不加延时脉冲宽度可能只有几个纳秒AD7606根本识别不到。我一开始就吃了这个亏逻辑分析仪上看到CONVST是个极窄的毛刺BUSY纹丝不动。后来在拉高和拉低之间加了__NOP()空指令或者一个微秒级延时问题立刻解决。CS的作用是使能SPI移位输出。注意AD7606的CS不是标准SPI的片选它更像是输出使能。CS为高时DOUTA呈高阻态CS拉低后DOUTA才开始驱动。所以CS的下降沿到第一个SCLK下降沿之间需要满足建立时间数据手册给的是最小15nsSTM32的SPI在配置正确的情况下这个时间是够的但如果你用GPIO模拟就要注意在CS拉低后加一点延时再发SCLK。3. STM32CubeMX里的关键配置项逐个拆解3.1 时钟树与SPI速率的选择依据AD7606的SPI最高时钟频率是23MHzVDRIVE3.3V时看起来很高但实际用的时候我不建议一上来就跑满。原因有两个一是STM32F103的SPI在高速下波形质量会变差尤其是杜邦线连接的时候二是AD7606的SPI输出建立时间在高速下会吃掉裕量。我的做法是先降到1MHz左右调通确认数据正确后再逐步提高。在CubeMX里SPI的波特率分频系数是根据APB2时钟算的。假设系统时钟72MHzAPB2也是72MHz那SPI2挂在APB1上时钟36MHz。分频系数选32得到1.125MHz这个速率下时序非常宽松逻辑分析仪抓波形一目了然。等调通之后我把分频改成8SPI时钟4.5MHz实测读数稳定。再往上到9MHz就开始偶尔出现通道数据错位说明布线或者AD7606的驱动能力到了边界。所以速率不是越高越好够用就行。3.2 SPI模式与数据帧格式的设置AD7606的SPI时序对应的是CPOL0CPHA0还是CPOL1CPHA1这个问题我查了很久。数据手册的时序图显示SCLK空闲时为低电平数据在SCLK下降沿输出主机应该在上升沿采样。这对应的是CPOL0、CPHA0吗不对。仔细看SCLK空闲低电平第一个边沿是上升沿数据在下降沿变化。如果主机在上升沿采样那数据在采样之前就已经稳定了这符合CPHA0的定义——在第一个时钟边沿采样。但CPOL0意味着空闲低电平第一个边沿是上升沿采样也在上升沿所以是CPOL0CPHA0。等等我再确认一下。CPHA0的定义是数据在第一个时钟边沿被采样在第二个边沿变化。AD7606是在SCLK下降沿输出数据也就是第二个边沿变化主机在上升沿采样也就是第一个边沿采样。所以确实是CPOL0、CPHA0。但实际配置的时候我发现用CPOL0、CPHA0读出来的数据偶尔会错一位。后来用逻辑分析仪对比才发现STM32的SPI在CPHA0时MISO的采样点比理论值稍微提前了一点而AD7606在CS拉低后第一个SCLK下降沿才输出DB15如果CS和SCLK几乎同时动作第一位可能还没稳定就被采样了。解决办法是在CS拉低后加一个很小的延时再启动SPI传输或者改用CPOL0、CPHA1试试。我最终用的是CPOL0、CPHA0但在CS和SPI启动之间插了一个__NOP()。数据帧格式选16位MSB First。AD7606每个通道输出16位正好一个数据帧。如果你用HAL_SPI_Receive它会按你设定的数据宽度来收。我建议用DMA模式一次收8个通道就是8个半字效率最高。3.3 GPIO配置CONVST和BUSY的细节CONVST和BUSY用普通GPIO就行但有几个点要注意。CONVST配置为推挽输出初始电平低。BUSY配置为输入建议开启内部上拉因为AD7606的BUSY是开漏输出不是它是推挽输出但为了保险起见加上拉没坏处。RESET引脚也要接一个GPIO上电后给一个至少50ns的高脉冲复位AD7606。我见过有人忘了复位结果读出来的数据一直是0或者满量程查了半天以为是SPI问题其实是芯片没初始化。还有一点AD7606的RANGE引脚决定输入量程是±5V还是±10V这个根据你的传感器输出决定配置成GPIO输出或者直接接固定电平都行。如果接GPIO记得在初始化时设置正确。3.4 DMA的配置与搬运长度计算用DMA搬运SPI数据是标配但AD7606的读取长度不是随便设的。前面说了8个通道分两次读每次64个SCLK对应4个16位数据。所以DMA的搬运长度设为4数据宽度半字16位。在CubeMX里添加SPI的RX DMA通道模式选Normal不是Circular因为每次转换后你都要重新启动读取。优先级设中或高都行AD7606的读取不频繁的话中就够了。这里有个坑HAL_SPI_Receive_DMA在传输完成之后会调用回调函数但如果你在回调里直接启动下一次读取可能会因为SPI状态没完全复位而失败。我的做法是在回调里置一个标志位在主循环里检测标志位再启动下一次。或者用HAL_SPI_Receive_DMA之前先调用HAL_SPI_Abort确保状态干净。4. 从CubeMX到代码完整读取流程的实现4.1 初始化顺序与复位脉冲的生成上电后的初始化顺序很重要。我的做法是先初始化所有GPIO确保CONVST为低、RESET为低、CS为高。延时至少1ms让AD7606电源稳定。给RESET一个高脉冲宽度至少50ns我一般延时1us。拉低RESET再延时1ms等待芯片内部稳定。初始化SPI和DMA。进入主循环。RESET脉冲的生成代码很简单HAL_GPIO_WritePin(AD7606_RESET_GPIO_Port, AD7606_RESET_Pin, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(AD7606_RESET_GPIO_Port, AD7606_RESET_Pin, GPIO_PIN_RESET); delay_ms(1);别小看这个delay_us(1)如果你用HAL_Delay最小单位是1ms虽然也能用但复位脉冲太宽没必要。用DWT或者TIM做个微秒延时更合适。4.2 启动一次转换的完整时序启动转换的流程是拉高CONVST延时至少25ns我用1us。拉低CONVST。等待BUSY变高确认转换开始。等待BUSY变低确认转换完成。第3步很多人会省略但如果你不等BUSY变高就直接等它变低可能会因为BUSY还没起来就误判为转换完成。我的做法是加一个超时机制HAL_GPIO_WritePin(AD7606_CONVST_GPIO_Port, AD7606_CONVST_Pin, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(AD7606_CONVST_GPIO_Port, AD7606_CONVST_Pin, GPIO_PIN_RESET); uint32_t timeout 0; while (HAL_GPIO_ReadPin(AD7606_BUSY_GPIO_Port, AD7606_BUSY_Pin) GPIO_PIN_RESET) { if (timeout 100000) break; } timeout 0; while (HAL_GPIO_ReadPin(AD7606_BUSY_GPIO_Port, AD7606_BUSY_Pin) GPIO_PIN_SET) { if (timeout 100000) break; }BUSY的下降沿就是读取的起点。注意AD7606的转换时间大约是4us8通道同时转换所以BUSY高电平持续时间很短用轮询完全来得及。4.3 两次CS拉低读取8通道的代码实现读取部分我封装了一个函数传入一个8元素的int16_t数组void AD7606_ReadAllChannels(int16_t *data) { uint16_t buf[4]; // 读通道1-4 HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_RESET); __NOP(); __NOP(); HAL_SPI_Receive(hspi2, (uint8_t*)buf, 4, 100); HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_SET); data[0] (int16_t)buf[0]; data[1] (int16_t)buf[1]; data[2] (int16_t)buf[2]; data[3] (int16_t)buf[3]; // 读通道5-8 HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_RESET); __NOP(); __NOP(); HAL_SPI_Receive(hspi2, (uint8_t*)buf, 4, 100); HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_SET); data[4] (int16_t)buf[4]; data[5] (int16_t)buf[5]; data[6] (int16_t)buf[6]; data[7] (int16_t)buf[7]; }注意HAL_SPI_Receive的第三个参数是数据个数不是字节数。因为SPI配置成了16位数据宽度所以4个数据就是4个半字对应64个SCLK。如果你配的是8位数据宽度那就要传8但读出来的数据需要自己拼装。两次CS之间的间隔至少要留几个微秒让AD7606的移位寄存器复位。我实测间隔1us就够了但保险起见我用了10us。4.4 用DMA替代轮询的改造方法上面的代码用的是阻塞式HAL_SPI_Receive在低速下没问题但如果你要高速连续采集阻塞式会占用大量CPU时间。改成DMA的方式是volatile uint8_t spi_dma_done 0; void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi2) { spi_dma_done 1; } } void AD7606_ReadAllChannels_DMA(int16_t *data) { // 读通道1-4 HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_RESET); __NOP(); __NOP(); spi_dma_done 0; HAL_SPI_Receive_DMA(hspi2, (uint8_t*)data, 4); while (!spi_dma_done); HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_SET); // 读通道5-8 HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_RESET); __NOP(); __NOP(); spi_dma_done 0; HAL_SPI_Receive_DMA(hspi2, (uint8_t*)data[4], 4); while (!spi_dma_done); HAL_GPIO_WritePin(AD7606_CS_GPIO_Port, AD7606_CS_Pin, GPIO_PIN_SET); }DMA模式下数据直接搬到内存CPU只需要等标志位。如果你用RTOS可以在回调里发一个信号量任务里等信号量这样CPU利用率更高。5. SPI时序调试逻辑分析仪抓到的真实波形分析5.1 第一个坑CS和SCLK的相位关系我第一次抓波形的时候发现CS拉低之后SCLK的第一个下降沿来得太快导致DOUTA上的第一位数据还没稳定就被采样了。逻辑分析仪上看到DOUTA在CS下降沿后大约20ns才开始输出而SCLK的第一个下降沿在CS下降沿后10ns就来了。虽然数据手册说建立时间最小15ns但那是芯片保证的值实际布线带来的延迟可能让这个裕量消失。解决办法有两个一是在CS拉低后加延时再启动SPI二是在CubeMX里把SPI的NSS设置为软件模式手动控制CS这样可以在CS和SPI传输之间插入代码。我选的是第二种因为更可控。5.2 第二个坑BUSY下降沿到CS拉低的间隔BUSY下降沿表示转换完成但AD7606的内部寄存器更新还需要一点时间。数据手册里写的是BUSY下降后最小0ns就可以读但我实测发现如果BUSY一降就立刻拉低CS偶尔会读到上一次的数据。后来我在BUSY下降后加了1us延时问题消失。这个延时不是必须的但加上之后稳定性明显提升。我猜测是因为AD7606内部从转换完成到数据锁存到输出寄存器有一个很小的窗口虽然手册说0ns但实际芯片个体差异或者电源波动可能让这个窗口变长。5.3 第三个坑SCLK空闲电平和CPOL设置不匹配有一次我换了一颗AD7606同样的代码读出来全是0。查了半天发现是新芯片的VDRIVE引脚没接对导致SPI电平不匹配。但在这个过程中我发现如果CPOL设置错了SCLK空闲电平为高AD7606会误判为时钟活动DOUTA可能一直处于高阻态。所以每次换芯片或者换板子第一件事就是用逻辑分析仪确认SCLK空闲电平是低。如果不是检查CubeMX里的CPOL设置。5.4 用示波器看CONVST脉冲宽度的必要性CONVST脉冲宽度不够是最隐蔽的坑之一。因为STM32的GPIO翻转速度很快如果你用HAL_GPIO_WritePin连续拉高拉低中间没有延时脉冲宽度可能只有几纳秒。示波器上能看到一个尖峰但AD7606的输入滤波器可能把它滤掉导致转换根本没启动。我的做法是在CONVST拉高和拉低之间加一个__NOP()或者用TIM输出一个固定宽度的脉冲。如果你用TIM可以精确控制脉冲宽度比如1us这样最可靠。6. 多通道同步采样的数据对齐与校准6.1 8个通道的数据顺序确认AD7606的SPI输出顺序是固定的DOUTA先输出通道1的DB15到DB0然后通道2依此类推。所以第一次CS读到的4个半字依次是通道1、2、3、4第二次是通道5、6、7、8。这个顺序不能搞错否则你的数据会张冠李戴。我建议在代码里用一个联合体或者结构体来管理这8个通道避免数组下标写错typedef struct { int16_t ch1; int16_t ch2; int16_t ch3; int16_t ch4; int16_t ch5; int16_t ch6; int16_t ch7; int16_t ch8; } AD7606_Data_t;这样在读取的时候直接往结构体里填可读性更好。6.2 量程与LSB的计算AD7606的输出是16位二进制补码量程由RANGE引脚决定。如果RANGE接低量程是±5VLSB 10V / 65536 ≈ 152.6uV。如果RANGE接高量程是±10VLSB 20V / 65536 ≈ 305.2uV。转换公式是float voltage (float)raw_data * LSB;注意raw_data是int16_t直接乘LSB就行不需要先转成无符号。因为补码的符号位已经包含了正负信息。6.3 通道间串扰的实测与处理多通道同步采样最怕的是通道间串扰。我实测过如果某个通道输入满量程信号相邻通道会有大约1-2个LSB的串扰。这个水平在大多数应用里可以接受但如果你的信号很小比如毫伏级那就需要做软件校准。校准方法是所有通道接同一个已知信号读出一组数据计算每个通道相对于平均值的偏差然后在后续读数中减去这个偏差。这个偏差会随温度变化所以如果环境温度变化大需要定期重新校准。7. 常见问题排查与实战经验汇总7.1 读出来全是0或者全是0xFFFF这是最常见的问题。排查顺序是检查VDRIVE引脚电压是否和STM32的IO电平匹配。检查RESET脉冲是否给了。检查CONVST脉冲宽度是否够。用逻辑分析仪看DOUTA在CS拉低后是否有输出。检查SPI的CPOL和CPHA设置。如果DOUTA一直没输出大概率是CS没拉低或者芯片没复位。如果DOUTA有输出但数据不对检查SCLK的极性和采样边沿。7.2 数据偶尔错位一位错位一位通常是CS和SCLK的时序问题。AD7606在CS下降沿后的第一个SCLK下降沿输出DB15如果主机在第一个SCLK上升沿采样而CS和SCLK几乎同时动作DB15可能还没稳定。解决办法是在CS拉低后加一个短延时或者降低SPI速率。7.3 BUSY一直不下降BUSY一直高说明转换没完成。检查CONVST脉冲是否被AD7606识别到。如果CONVST脉冲太窄AD7606可能忽略它。用示波器确认CONVST的宽度至少25ns实际建议1us以上。7.4 DMA传输完成但数据没更新DMA传输完成回调触发了但数据缓冲区里的值没变。这通常是因为DMA的目标地址没设对或者SPI的RX DMA请求没使能。在CubeMX里检查DMA配置确保SPI的RX通道已经添加并且方向是外设到内存。7.5 高速读取时数据跳变SPI速率提高后数据跳变通常是信号完整性问题。检查SCLK和MISO的走线是否等长是否有串扰。如果用的是杜邦线尽量缩短长度或者在SCLK上串一个22欧姆的电阻。AD7606的SPI输出驱动能力有限长线会导致边沿变缓采样出错。8. 从单次采集到连续采集的架构演进单次采集跑通之后下一步通常是连续采集。连续采集有两种模式一种是定时器触发CONVST固定采样率另一种是软件触发按需采集。定时器触发的方式更常用。用一个TIM输出PWM连接到CONVST引脚设置好频率就是采样率。比如你要10kHz采样率TIM的PWM频率就设10kHz占空比设小一点保证脉冲宽度够。然后在BUSY下降沿中断里启动SPI读取。BUSY可以接到STM32的外部中断引脚下降沿触发。在中断服务函数里启动DMA读取读取完成后再等下一次BUSY。这样整个采集流程就是硬件驱动的CPU只需要处理数据。注意中断优先级的问题。如果BUSY中断和SPI DMA中断优先级冲突可能会导致数据丢失。我的做法是把BUSY中断优先级设得比DMA中断高确保BUSY下降沿能被及时响应。连续采集时还要考虑数据缓冲。如果采样率很高CPU来不及处理就需要用双缓冲或者环形缓冲。DMA搬到缓冲区A处理完A之后切换到B同时DMA搬到B。这样交替进行避免数据覆盖。我在一个振动采集项目里用了20kHz采样率8个通道SPI速率4.5MHz。每次转换后读8个通道需要大约30us20kHz的周期是50us所以时间裕量只有20us。这种情况下必须用DMA加双缓冲否则CPU稍微被其他中断打断就会丢数据。最后分享一个调试技巧在连续采集模式下如果发现数据偶尔出现尖峰先别怀疑AD7606用逻辑分析仪抓BUSY和CS的波形看看是不是读取时机不对。我遇到过因为BUSY中断被更高优先级中断延迟导致CS拉低时AD7606已经开始下一次转换读出来的数据混了两次转换的结果。把BUSY中断优先级提到最高之后问题解决。