
1. 项目缘起与整体方案拆解1.1 为什么选AD7606搭配STM32做数据采集做过电力监测、工业振动分析或者多路传感器阵列的朋友大概率都绕不开一个需求同时采集多路模拟信号而且对精度和同步性有硬要求。市面上常见的MCU内置ADC12位分辨率、通道间轮询采样遇到需要8通道同步、16位精度的场景就力不从心了。AD7606这颗芯片就是冲着这个痛点来的——8通道同步采样、16位逐次逼近型ADC、内置模拟输入钳位保护、二阶抗混叠滤波器单颗芯片就能搞定一台小型数据采集设备的前端。我手头这个项目是做一台便携式多路信号记录仪需要同时监测8路0-5V的传感器输出采样率要求不低于10kSPS每通道数据通过串口实时上传到上位机做波形显示。选型阶段对比过几种方案外挂多颗单通道ADC成本高且同步困难用MCU内置ADC加多路模拟开关切换则牺牲了同步性。AD7606的并行接口读取速度快但会占用大量IO口在STM32F103这类资源有限的芯片上不太划算。最终敲定用SPI接口读取虽然速度比并行慢一些但引脚占用少配合DMA可以做到不占用CPU资源整体性价比最高。这里要说明一点AD7606原生支持并行、串行和字节三种接口模式SPI属于串行模式的一种。通过硬件配置PAR/SER/BYTE SEL引脚选择串行模式后再用SPI时序去读转换结果就行。很多人第一次接触会疑惑AD7606的SPI和标准SPI协议是不是完全一样答案是基本兼容但有几个细节需要特别注意后面会详细展开。1.2 整体架构与数据流设计整个系统的数据流是这样的AD7606在CONVST引脚上升沿触发8通道同步采样转换完成后BUSY引脚拉低表示数据就绪。STM32通过SPI接口依次读取8个通道的16位转换结果每次读取需要两个字节。为了不阻塞主循环我用DMA把SPI接收数据直接搬运到内存缓冲区转换完成中断里只做标志位处理和缓冲区切换。这个架构的核心考量是实时性与CPU占用率的平衡。如果不用DMA每读一个通道就要进一次中断或者轮询等待8个通道下来CPU基本被占满根本没法做其他事情。用DMA之后SPI的时钟和数据搬运完全由硬件完成CPU只需要在DMA传输完成中断里处理数据即可。实测下来10kSPS采样率下CPU占用率不到5%留给串口上传和数据处理的空间非常充裕。STM32CubeMX在这个项目里扮演的是“硬件抽象层生成器”的角色。以前配SPI和DMA要翻参考手册一个个算寄存器值现在图形化点选就能生成初始化代码省下来的时间可以专注在业务逻辑上。不过CubeMX生成的代码只是起点AD7606的时序控制、数据解析、校准逻辑还是得自己写。下面我就按实际开发顺序把每个环节拆开来讲。2. 硬件连接与CubeMX关键配置2.1 引脚分配与硬件设计要点先看硬件连接。AD7606和STM32F103C8T6的连线如下表所示AD7606引脚STM32引脚功能说明CONVST A/BPA8转换启动上升沿触发BUSYPA9转换状态指示高电平表示正在转换RESETPA10复位高电平有效CSPA4SPI片选低电平有效SCLKPA5SPI时钟MISOPA6数据输出AD7606到STM32MOSIPA7数据输入STM32到AD7606本项目中未使用FRSTDATAPB0第一通道数据指示可选OS0/OS1/OS2GND过采样设置全接地表示无过采样RANGEGND量程选择接地为±5V接VCC为±10VPAR/SER/BYTE SELGND接口模式选择接地为并行接VCC为串行这里有个容易踩坑的地方PAR/SER/BYTE SEL引脚必须接高电平才能进入串行模式。我第一次调试时忘了这个引脚结果SPI读出来全是0xFF排查了半天才发现是接口模式没配对。另外RANGE引脚决定输入量程接GND时是±5V接VCC时是±10V要根据传感器输出范围提前确定。注意AD7606的模拟输入范围是±5V或±10V如果传感器输出是0-3.3V单极性信号需要加一个偏置电路把信号抬到以0V为中心的范围否则负半周会被钳位。电源部分也不能马虎。AD7606的AVCC建议用5V供电DVCC用3.3V与STM32电平匹配。模拟电源和数字电源之间要用磁珠或者0欧电阻隔离模拟地和数字地单点连接。我在第一版PCB上忽略了这一点结果采集到的数据噪声很大后来在AVCC引脚旁边加了10uF钽电容和0.1uF陶瓷电容去耦噪声立刻降下来了。2.2 CubeMX中SPI与DMA的配置细节打开STM32CubeMX新建工程选择STM32F103C8T6。先配置时钟树外部晶振8MHzPLL倍频到72MHzAPB2总线72MHzAPB1总线36MHz。SPI1挂在APB2上时钟源就是72MHz。SPI1的配置参数如下ModeFull-Duplex Master虽然只用MISO但全双工模式配置最简单Data Size8 BitsAD7606每次传输16位分两个字节读Clock Polarity (CPOL)HighClock Phase (CPHA)2 EdgeNSSSoftware用普通GPIO控制片选方便灵活控制时序Baud Rate Prescaler8SPI时钟72MHz/89MHzFirst BitMSB First这里重点说CPOL和CPHA的选择。AD7606的SPI时序要求是时钟空闲为高电平数据在第二个边沿采样对应CPOLHigh、CPHA2Edge。这个配置如果搞反了读出来的数据会整体移位或者完全错误。我建议先用示波器抓一下SCLK和MISO的波形确认时序匹配后再继续调试。波特率预分频选8SPI时钟9MHz。AD7606的SCLK最高支持23MHz读取模式9MHz留了足够余量同时避免高速下信号完整性变差。如果你用杜邦线连接建议再降一档到4.5MHz长线传输时更稳定。DMA配置在SPI1的DMA Settings标签页里添加DMA RequestSPI1_RXChannelDMA1 Channel2DirectionPeripheral To MemoryPriorityHighModeNormal每次转换触发一次DMA传输传完8个通道后重新配置提示AD7606一次转换产生8个通道数据每个通道16位即2字节总共16字节。DMA缓冲区大小设为16传输宽度设为Byte这样一次DMA请求就能把8通道数据全部搬完。GPIO配置方面PA8配置为GPIO_OutputCONVSTPA9配置为GPIO_InputBUSYPA10配置为GPIO_OutputRESETPA4配置为GPIO_OutputCS。初始电平CONVST低RESET低CS高。生成代码时选择MDK-ARM或者STM32CubeIDE根据你的开发环境来。我习惯用VSCode加PlatformIO但CubeMX生成的代码结构是通用的移植起来不麻烦。3. AD7606驱动实现与SPI时序控制3.1 复位与初始化流程AD7606上电后必须执行一次复位否则内部寄存器状态不确定。复位时序很简单RESET引脚拉高至少50ns然后拉低等待至少25ns后就可以开始转换了。实际代码里我延时1ms确保稳定。void AD7606_Reset(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_10, GPIO_PIN_RESET); HAL_Delay(1); }复位之后建议做一次“空转”转换即触发一次CONVST但不读取数据让芯片内部电路进入稳定状态。这个技巧是我从AD7606数据手册的“上电序列”章节里悟出来的实测能减少首次采样的异常值。初始化还包括过采样设置。OS0/OS1/OS2三个引脚全接地表示无过采样此时采样率最高。如果需要降低噪声可以设置过采样倍数但会牺牲吞吐率。我的项目对速度要求高所以选择无过采样靠硬件滤波和软件平均来改善信噪比。3.2 转换触发与BUSY等待策略AD7606的转换启动靠CONVST引脚上升沿。给一个至少25ns的高脉冲芯片就开始转换。转换期间BUSY引脚变高转换完成后变低。8个通道同步转换总转换时间约4us无过采样时。等待BUSY变低有两种策略轮询和中断。轮询简单但浪费CPU中断效率高但需要额外引脚。我一开始用轮询后来改成用BUSY下降沿触发外部中断在中断里启动DMA读取。不过STM32F103的EXTI不支持下降沿触发DMA所以实际做法是在中断里手动启动SPI DMA传输。void AD7606_StartConversion(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); __NOP(); __NOP(); __NOP(); // 约30ns延时 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET); } void EXTI9_5_IRQHandler(void) // BUSY接PA9 { if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_9) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_9); AD7606_ReadData_DMA(); } }这里有个细节BUSY下降沿触发中断后要尽快启动SPI读取因为AD7606在下次CONVST之前数据有效但如果你用很高的采样率读取窗口会很窄。10kSPS时周期100us转换4us读取16字节在9MHz SPI下约14us时间余量充足。3.3 SPI读取时序与数据拼接AD7606的SPI读取时序和标准SPI略有不同。CS拉低后第一个SCLK下降沿输出DB15最高位之后每个下降沿依次输出下一位。8个通道的数据连续输出每个通道16位。所以总共需要128个SCLK周期。用STM32的SPI读取时发送16个字节的哑数据0x00同时接收16个字节。接收到的数据按大端格式拼接void AD7606_ParseData(uint8_t *buf, int16_t *channels) { for(int i 0; i 8; i) { channels[i] (int16_t)((buf[i*2] 8) | buf[i*2 1]); } }注意AD7606输出的是二进制补码格式正负量程对应-32768到32767。如果RANGE接GND±5V实际电压值 channels[i] / 32768.0 * 5.0。这个换算关系要记牢不然算出来的电压全是错的。实操心得SPI读取时CS必须在整个128个SCLK周期内保持低电平。如果CS中途拉高AD7606会复位输出移位寄存器后续数据全乱。我建议用DMA传输完成中断来拉高CS确保时序准确。4. DMA传输与数据缓冲设计4.1 DMA配置与双缓冲机制前面在CubeMX里已经配好了SPI1_RX的DMA现在要在代码里启动它。每次BUSY下降沿触发后调用以下函数uint8_t spi_rx_buf[16]; int16_t adc_channels[8]; void AD7606_ReadData_DMA(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS低 HAL_SPI_Receive_DMA(hspi1, spi_rx_buf, 16); } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi-Instance SPI1) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS高 AD7606_ParseData(spi_rx_buf, adc_channels); // 数据处理或存入环形缓冲区 } }这个方案在10kSPS下工作稳定但如果你要跑到50kSPS以上单缓冲可能来不及处理。这时候可以上双缓冲定义两个16字节缓冲区DMA传输完成中断里切换缓冲区主循环处理另一个。STM32F103的DMA不支持自动双缓冲需要手动切换。uint8_t spi_rx_buf[2][16]; volatile uint8_t active_buf 0; void AD7606_ReadData_DMA(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Receive_DMA(hspi1, spi_rx_buf[active_buf], 16); } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); uint8_t ready_buf active_buf; active_buf ^ 1; // 处理spi_rx_buf[ready_buf] }双缓冲的关键是处理速度要快于采样速度。如果主循环处理一帧数据的时间超过采样周期缓冲区还是会溢出。我的做法是在中断里只做数据搬运和标志置位复杂计算放到主循环并且用环形缓冲区做二级缓冲。4.2 定时器触发采样与精确时序用软件延时触发CONVST精度不够10kSPS下误差可能到几百Hz。更可靠的方式是用定时器PWM或者定时器中断来触发。我用TIM2的更新中断每100us触发一次转换void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim-Instance TIM2) { AD7606_StartConversion(); } }TIM2挂在APB1上时钟36MHz预分频36-1自动重装载值100-1得到100us周期即10kHz。这个精度比软件延时高两个数量级而且不占用CPU。注意定时器中断里只触发转换不要做SPI读取。SPI读取要等BUSY下降沿放在EXTI中断里。两个中断优先级要设置合理TIM2中断优先级低于EXTI避免转换触发打断数据读取。4.3 数据校准与软件滤波AD7606出厂时已经校准过但实际电路中由于基准电压源精度、PCB走线阻抗等因素还是会有零点偏移和增益误差。我在项目里做了两点校准短接模拟输入到地采集1000个样本取平均得到零点偏移输入标准5V参考电压采集1000个样本取平均计算增益系数。float offset[8] {0}; float gain[8] {1.0}; float AD7606_GetVoltage(uint8_t ch) { return (adc_channels[ch] - offset[ch]) * gain[ch] / 32768.0f * 5.0f; }软件滤波方面如果信号带宽不高可以做滑动平均或者一阶低通滤波。我的项目信号最高频率1kHz采样率10kSPS用16点滑动平均后噪声明显降低而且相位延迟可以接受。#define FILTER_LEN 16 int32_t filter_buf[8][FILTER_LEN] {0}; uint8_t filter_idx 0; void AD7606_Filter(void) { for(int ch 0; ch 8; ch) { filter_buf[ch][filter_idx] adc_channels[ch]; int32_t sum 0; for(int i 0; i FILTER_LEN; i) sum filter_buf[ch][i]; adc_channels[ch] sum / FILTER_LEN; } filter_idx (filter_idx 1) % FILTER_LEN; }这个滤波在10kSPS下每帧计算量约128次加法STM32F103在72MHz下耗时不到10us完全跟得上。5. 常见问题排查与实战避坑指南5.1 SPI读数据全为0或0xFF的排查思路这是新手最常遇到的问题我至少遇到过三次每次原因都不一样。整理成排查表现象可能原因排查方法全0xFFPAR/SER/BYTE SEL引脚未接高万用表测引脚电平全0x00CS未拉低或SCLK无输出示波器抓CS和SCLK波形数据错位CPOL/CPHA配置错误对照AD7606时序图调整只有第一通道有数CS提前拉高检查DMA传输完成前CS状态数据跳变剧烈模拟电源噪声大示波器看AVCC纹波我第一次调试时SPI读出来全是0xFF查了半天以为是SPI配置问题最后发现是PAR/SER/BYTE SEL引脚悬空了。AD7606内部有弱上拉悬空时默认高电平进入串行模式但悬空引脚容易受干扰还是老老实实接VCC靠谱。第二次遇到数据错位读出来的值整体左移了一位。原因是CPHA设成了1Edge改成2Edge后正常。这个问题的本质是AD7606在SCLK下降沿输出数据STM32要在下一个上升沿采样对应CPHA2Edge。5.2 BUSY信号异常与转换失败处理BUSY信号一直为高说明AD7606没有完成转换。可能原因有CONVST脉冲太窄小于25ns、RESET未正确执行、芯片供电异常。我的做法是在启动转换后加一个超时检测HAL_StatusTypeDef AD7606_WaitBusy(uint32_t timeout) { uint32_t tick HAL_GetTick(); while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9) GPIO_PIN_SET) { if(HAL_GetTick() - tick timeout) return HAL_TIMEOUT; } return HAL_OK; }如果超时执行一次复位并重新初始化。这个保护机制在电磁干扰强的工业现场特别有用我有个项目在变频器旁边工作偶尔会出现BUSY卡死加了超时复位后连续运行一周没再出问题。5.3 高采样率下的数据丢帧与DMA溢出当采样率超过20kSPS时SPI读取时间加上中断处理时间可能超过采样周期导致DMA还没传完下一次转换就开始了。解决办法有三个提高SPI时钟到18MHz、减少中断处理时间、用双缓冲。我实测STM32F103在72MHz下SPI 9MHz读取16字节约14us中断响应加处理约5us总共19us。10kSPS周期100us绰绰有余但50kSPS周期20us就非常紧张了。如果非要跑50kSPS建议换STM32F4系列SPI时钟可以到42MHz读取时间缩短到3us以内。避坑技巧DMA传输完成中断里不要调用HAL_Delay或者做浮点运算这些操作耗时不可控。所有耗时操作放到主循环中断里只做标志置位和缓冲区切换。5.4 模拟输入保护与抗混叠滤波AD7606内置了模拟输入钳位保护可以承受±16.5V的过压但这不代表可以随便接。如果传感器输出可能超过量程建议外加电阻分压或者TVS管。我在一个电机电流检测项目里因为电机反电动势导致输入电压瞬间冲到15V虽然AD7606没坏但采集数据全饱和了。后来加了BAV99双二极管钳位到5V和GND问题解决。抗混叠滤波方面AD7606内置二阶巴特沃斯滤波器截止频率约22kHz无过采样时。如果信号带宽低于这个值可以直接用如果信号包含高频噪声建议外加RC低通。我的项目信号最高1kHz在输入端加了1kΩ电阻和100nF电容组成RC滤波截止频率1.6kHz有效抑制了高频干扰。6. 性能实测与优化经验分享6.1 实测数据与精度评估用信号发生器输入1kHz正弦波幅度4Vpp偏置0V采集10000个样本做FFT分析。实测结果SNR约85dBTHD约-90dBENOB约13.8位。这个成绩对于16位ADC来说还有提升空间主要瓶颈在基准电压源的噪声和PCB布局。后来我把基准电压源从MCU的3.3V换成ADR4212.5V噪声0.1Hz-10Hz仅1.5uVppENOB提升到14.5位。所以如果你对精度要求高基准电压源的投资不能省。优化措施SNR(dB)ENOB(位)初始方案8213.3加RC滤波8513.8换基准电压源8814.5加屏蔽罩8914.76.2 CPU占用率与实时性优化用DMA方案后10kSPS下CPU占用率实测约4.7%。具体分布TIM2中断0.3%EXTI中断1.2%SPI DMA完成中断2.1%主循环数据处理1.1%。如果关掉滑动平均滤波占用率降到3.2%。优化建议如果CPU资源紧张可以把数据解析和滤波放到DMA完成中断里做但要注意中断执行时间不能超过采样周期的50%。我的做法是在中断里只做数据拼接和存入环形缓冲区滤波和上传在主循环做。6.3 从F103到F4的移植经验后来因为项目需要更高采样率我把代码移植到了STM32F407。主要改动SPI时钟从9MHz提到21MHzDMA从DMA1换到DMA2中断向量表重新映射。CubeMX重新生成代码后AD7606驱动层几乎不用改只需要调整SPI句柄和DMA通道。移植后50kSPS下CPU占用率约8%SPI读取时间从14us降到6us时间余量充足。如果你也在考虑升级平台F4系列是性价比很高的选择而且CubeMX对F4的支持很完善。6.4 几个容易被忽略的细节最后分享几个我在实际项目中踩过的坑。第一AD7606的CONVST脉冲宽度数据手册写最小25ns但实际测试发现低于50ns时偶尔会转换失败建议用__NOP()延时或者硬件RC延时确保100ns以上。第二SPI的MISO引脚要配置为上拉输入否则在CS切换瞬间可能产生毛刺。第三如果多个AD7606级联要注意FRSTDATA引脚的用法它指示第一个通道数据是否就绪级联时可以用来同步。还有一点关于CubeMX的生成的SPI初始化代码里NSS引脚如果配置为SoftwareCubeMX会自动把NSS引脚设为GPIO输出并置高。但如果你用普通GPIO控制CS记得在CubeMX里把NSS设为Disable否则会多占用一个引脚。这个项目从选型到稳定运行花了大约两周时间其中调试SPI时序占了三分之一。现在回头看如果一开始就用示波器抓波形对照数据手册能省下至少三天。所以我的建议是嵌入式调试示波器比printf好用一百倍。