1. 项目缘起与整体设计思路1.1 为什么选AD7606搭配STM32做数据采集做过电力监测、振动分析或者多路传感器阵列的朋友应该都有体会8通道同步采样、16位精度、200kSPS吞吐率这几个指标叠在一起选型范围一下子就窄了。AD7606恰好卡在这个甜点位上——片内集成8路16位ADC、模拟输入钳位保护、二阶抗混叠滤波器还有灵活的数字接口。它支持并行、串行和并行字节三种模式其中串行模式只需要几根线就能跟MCU通信特别适合引脚资源紧张的STM32F103这类芯片。我这次的项目背景是做一个多路电压电流同步采集模块采样率要求每通道不低于20kSPS精度要求有效位在14位以上。AD7606的串行模式在200kSPS下每通道数据是16位8通道轮流读出SPI时钟需要跑到16MHz以上才能跟上。STM32F103的SPI1最高18MHz刚好够用。如果换成并行模式8根数据线加控制线F103的IO口根本不够分而且PCB走线也麻烦。所以串行SPI方案是性价比最高的选择。这里要澄清一个常见误区AD7606的“SPI接口”并不是标准四线SPI它没有MOSI主机输出从机输入这根线因为AD7606只往外吐数据不需要MCU给它写配置。它的串行接口实际上是三线制SCLK、DOUTA或DOUTB、CS再加上一个转换启动信号CONVST和忙信号BUSY。所以你在CubeMX里配置SPI的时候其实只用到了SCK和MISO两根线MOSI可以留着不用或者复用成其他功能。1.2 整体方案框图和信号流整个数据采集链路的信号流是这样的MCU定时器产生PWM或者GPIO翻转产生CONVST脉冲AD7606收到上升沿后同时采样8个通道并启动转换转换期间BUSY拉高转换完成后BUSY拉低。MCU检测到BUSY下降沿后拉低CS然后连续发送8组16个SCLK脉冲每个通道的数据在SCLK下降沿从DOUTA移出MCU在上升沿采样。读完8个通道后拉高CS等待下一次CONVST。这里有个关键点AD7606的串行数据输出是在SCLK的下降沿更新所以MCU必须在上升沿采样才能保证建立时间。STM32的SPI配置成模式1CPOL0CPHA1或者模式2CPOL1CPHA1都可以但要注意AD7606要求SCLK空闲时为低电平所以CPOL0更自然。我实测下来CPOL0、CPHA1最稳数据在第一个边沿上升沿被采样正好对应AD7606在下降沿更新数据的行为。方案的整体设计思路是用TIM2产生一个固定频率的PWM来触发CONVST这样采样率就由定时器频率决定非常精确。BUSY信号接到EXTI外部中断在中断里启动SPI DMA接收把8个通道的数据一次性搬进内存缓冲区。主循环只负责处理数据不参与时序控制。这样做的好处是CPU占用率极低而且采样间隔严格由硬件定时器保证不会因为中断延迟而抖动。1.3 关键参数计算与选型依据先算采样率。假设TIM2的APB1时钟是72MHz预分频器设为72-1自动重装载值设为100-1那么PWM频率就是72MHz / 72 / 100 10kHz。也就是说CONVST每100微秒来一个脉冲采样率10kSPS。AD7606的转换时间是固定的在内部基准开启时大约4微秒所以100微秒的周期完全够用。再算SPI时钟。AD7606的串行读取需要8个通道 × 16位 128个SCLK周期。如果SPI时钟是18MHz那么128个周期需要128 / 18MHz ≈ 7.1微秒。加上CS建立时间和保持时间总共不到10微秒。而CONVST周期是100微秒所以读取时间只占10%余量非常充足。如果你要把采样率提到50kSPS周期变成20微秒读取时间还是7微秒左右仍然够用。但再往上就要考虑DMA传输时间和中断响应时间了。AD7606的基准电压我选的是内部基准2.5V这样模拟输入范围是±5VRANGE引脚接地或者±10VRANGE接高。工业上常用±10V因为传感器输出经常是0-10V或者±10V。如果你用内部基准REFIN/REFOUT引脚要接一个10微法的钽电容到地这个电容的位置要尽量靠近芯片引脚否则基准噪声会直接影响采集精度。2. STM32CubeMX配置细节全解析2.1 时钟树和调试接口配置打开CubeMX新建工程选STM32F103C8T6或者你实际用的型号。第一步先把RCC里的HSE改成Crystal/Ceramic Resonator然后在Clock Configuration里把HCLK拉到72MHz。注意APB1的最大频率是36MHz但定时器时钟是APB1的两倍所以TIM2的时钟是72MHz。这个细节很多人会搞混以为APB1分频后定时器也只有36MHz实际上CubeMX会自动把定时器时钟乘以2。调试接口选Serial Wire也就是SWD只占用PA13和PA14两个引脚。如果你用ST-Link下载调试这个必须开否则第二次下载就可能锁芯片。我踩过的坑是第一次用CubeMX生成代码时忘了开SWD结果程序跑起来后SWD引脚被复用成普通IO再也连不上调试器最后只能用BOOT0拉高通过串口擦除。所以这个选项一定要在生成代码前确认。2.2 SPI外设参数设置在Connectivity里选SPI1Mode选Full-Duplex Master或者Half-Duplex Master都可以因为AD7606不需要MOSI。但CubeMX里如果选Half-DuplexMISO引脚会变成双向配置起来反而麻烦。我建议直接选Full-Duplex Master然后把MOSI引脚配置成GPIO_Output或者干脆不分配这样最省事。参数配置如下Frame Format选MotorolaData Size选16 BitsClock Polarity选LowClock Phase选1 Edge或者2 Edge都行我习惯选1 Edge也就是CPHA1。NSS选Software因为AD7606的CS需要手动控制不能用硬件NSS。Prescaler选8分频这样SPI时钟是72MHz / 8 9MHz。如果你要跑更快可以选4分频得到18MHz但要注意PCB走线质量线太长或者没有阻抗匹配的话18MHz下波形会有振铃。Baud Rate Prescaler的计算很简单SPI时钟 APB2时钟 / Prescaler。APB2时钟是72MHz所以Prescaler8时SPI时钟是9MHzPrescaler4时是18MHz。AD7606的SCLK最高支持23MHz在5V供电时所以18MHz是安全的。但如果你用3.3V供电SCLK最高只有17MHz左右这时候就要选8分频。CRC Calculation选DisabledFirst Bit选MSB First这个跟AD7606的时序一致。AD7606在CS下降沿后第一个SCLK下降沿输出DB15所以是MSB先出。2.3 DMA和中断配置SPI1的DMA配置在DMA Settings标签页里。点Add选SPI1_RXMode选Circular或者Normal都行。我选Normal因为每次转换完都要重新启动DMA。Data Width选Half Word因为SPI数据是16位的。Priority选High保证DMA请求能及时响应。NVIC里要开三个中断DMA1_Channel2_IRQnSPI1_RX的DMA通道、EXTI0_IRQnBUSY信号接在PA0上、TIM2_IRQn如果要用定时器中断触发CONVST的话。优先级分组选NVIC_PriorityGroup_2抢占优先级和子优先级各2位。DMA中断优先级设0EXTI设1TIM2设2。这样DMA最高保证数据不丢EXTI次之及时响应BUSY下降沿TIM2最低因为CONVST的定时要求没那么苛刻。这里有个细节STM32F103的SPI1_RX对应DMA1_Channel2SPI1_TX对应DMA1_Channel3。我们只用RX所以只开Channel2就行。DMA模式选Normal因为每次读8个通道就128个字节不需要循环。Memory地址自增Peripheral地址不自增这个CubeMX会自动配好。2.4 GPIO和定时器配置PA0接BUSY配置成GPIO_InputPull-up。因为AD7606的BUSY是推挽输出不需要上拉但配上拉可以防止悬空时误触发。PA1接CONVST配置成GPIO_Output初始电平Low。PA4接CS配置成GPIO_Output初始电平High。这三个引脚在CubeMX里手动配置不要分配到SPI的NSS上。TIM2配置成PWM Generation CH1Prescaler设71Counter Period设99Pulse设50。这样PWM频率是72MHz / 72 / 100 10kHz占空比50%。PWM输出引脚选PA2或者你实际用的引脚。注意TIM2的CH1默认在PA0上但PA0已经被BUSY占了所以要在Pinout视图里把TIM2_CH1重映射到PA2或者PA3。STM32F103的TIM2_CH1可以重映射到PA15但PA15默认是JTDI需要先禁用JTAG。我建议直接用PA2不需要重映射。如果你不想用PWM触发也可以用定时器中断在中断里翻转CONVST引脚。但PWM的好处是完全由硬件产生没有中断延迟采样间隔更精确。我实测PWM触发的采样间隔抖动在纳秒级而中断翻转的抖动在微秒级。3. 代码实现与关键环节剖析3.1 AD7606驱动层封装先定义一个结构体来管理AD7606的状态typedef struct { SPI_HandleTypeDef *hspi; GPIO_TypeDef *cs_port; uint16_t cs_pin; GPIO_TypeDef *convst_port; uint16_t convst_pin; GPIO_TypeDef *busy_port; uint16_t busy_pin; uint16_t adc_data[8]; volatile uint8_t conversion_done; } AD7606_HandleTypeDef;初始化函数里把CS拉高CONVST拉低然后启动TIM2的PWM输出。注意AD7606上电后需要一次复位就是把RESET引脚拉高至少50纳秒再拉低。如果你没接RESET引脚可以在初始化时发一次软件复位命令但AD7606没有软件复位寄存器所以最好还是接上RESET。读取数据的函数这样写void AD7606_ReadData(AD7606_HandleTypeDef *hadc) { HAL_GPIO_WritePin(hadc-cs_port, hadc-cs_pin, GPIO_PIN_RESET); HAL_SPI_Receive_DMA(hadc-hspi, hadc-adc_data, 8); }这里用HAL_SPI_Receive_DMA一次读8个半字也就是16个字节。DMA传输完成后会触发DMA1_Channel2的中断在中断回调里把CS拉高然后置位conversion_done标志。注意HAL_SPI_Receive_DMA的第三个参数是数据长度单位是半字所以写8就是读8个16位数据。如果你写16它会读16个半字也就是32个字节那就错了。这个参数很容易搞混我见过有人写16结果读出来全是0就是因为多读了一倍。3.2 BUSY中断与DMA启动时序BUSY信号接在PA0上配置成下降沿触发。在EXTI0的中断回调里启动SPI DMA接收void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin GPIO_PIN_0) { AD7606_ReadData(hadc7606); } }这里有个时序问题BUSY下降沿表示转换完成但AD7606的数据手册要求CS在BUSY下降后至少等待25纳秒才能拉低。STM32的中断响应时间大约1微秒所以这个等待时间自然就满足了。但如果你用轮询方式检测BUSY就要手动加延时。DMA传输完成回调里拉高CSvoid HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); hadc7606.conversion_done 1; } }注意CS拉高的时机很关键。AD7606要求最后一个SCLK下降沿后CS至少保持低电平10纳秒DMA传输完成中断触发时最后一个SCLK已经发完了所以直接拉高CS是安全的。但如果你在DMA传输完成前就拉高CS最后一个通道的数据可能还没移出来。3.3 数据拼接与电压换算AD7606输出的16位数据是二进制补码格式最高位是符号位。换算成电压的公式是float voltage (int16_t)adc_data[i] * VREF / 32768.0f;如果VREF是2.5VRANGE是±5V那么满量程是5VLSB是5V / 32768 ≈ 0.1526毫伏。如果RANGE是±10V满量程是10VLSB是0.3052毫伏。注意AD7606的输出码是二进制补码所以要先强制转换成int16_t再乘否则当成uint16_t处理会得到错误的正负号。我实测下来用内部基准2.5V时实际基准电压可能在2.49V到2.51V之间所以如果你要绝对精度最好用外部基准比如ADR421或者REF5025。外部基准的初始精度可以做到0.05%温漂3ppm/°C比内部基准好一个数量级。3.4 定时器触发与采样率控制TIM2的PWM输出直接接到CONVST引脚这样每个PWM周期产生一个转换启动脉冲。PWM的占空比设50%是为了保证CONVST的高电平时间足够长AD7606要求CONVST高电平至少25纳秒。10kHz下周期是100微秒50%占空比就是50微秒高电平远远够用。如果你要动态调整采样率可以在运行时修改TIM2的ARR寄存器__HAL_TIM_SET_AUTORELOAD(htim2, new_arr);比如你要从10kSPS切换到20kSPS把ARR从99改成49就行。但要注意SPI的读取时间必须小于新的采样周期。20kSPS下周期是50微秒SPI读取需要7微秒左右加上中断响应和DMA传输总共大约10微秒余量还是够的。4. 常见问题与排查技巧实录4.1 数据全为0或者全为0xFFFF这是最常见的问题通常有三个原因。第一是CS没有正确拉低AD7606的DOUTA一直处于高阻态MCU读到的就是悬空电平。用示波器看CS引脚确认在SPI传输期间是低电平。第二是SPI的CPOL/CPHA配置错误导致采样边沿不对。AD7606要求CPOL0CPHA1如果你配成CPOL0、CPHA0数据会在第一个边沿被采样但那时候数据还没建立好。第三是DMA长度设错了比如设成16而不是8多读出来的数据就是0。排查方法先用HAL_SPI_Receive阻塞方式读一次不用DMA看看数据对不对。如果阻塞方式能读到正确数据那就是DMA配置的问题。如果阻塞方式也读不到那就是SPI参数或者硬件连接的问题。4.2 采样值跳动很大如果采集的是稳定直流信号但读数跳动超过几个LSB通常是基准噪声或者电源噪声引起的。AD7606的REFIN/REFOUT引脚要接10微法钽电容加0.1微法陶瓷电容两个电容并联位置尽量靠近芯片。AVCC引脚也要接0.1微法去耦电容每个电源引脚一个。另一个原因是模拟输入没有加RC滤波。AD7606内部有二阶抗混叠滤波器截止频率大约是22kHz但如果你采集的信号频率很低可以在输入端再加一个RC低通截止频率设在你关心的最高频率的2倍左右。比如你关心的是50Hz工频信号RC截止频率设100Hz就行。4.3 BUSY中断丢失或者误触发BUSY信号是推挽输出理论上不会误触发。但如果你的PCB走线太长或者没有加串联电阻可能会因为反射导致误触发。我建议在BUSY线上串一个100欧姆的电阻靠近MCU端放置。另外EXTI的触发边沿要设成下降沿不要设成双边沿否则转换开始时BUSY上升沿也会触发中断。如果BUSY中断丢失通常是中断优先级配置有问题。DMA中断优先级如果比EXTI高DMA传输完成中断会打断EXTI中断导致EXTI中断服务函数执行不完整。我建议DMA优先级设0EXTI设1这样DMA不会打断EXTI但EXTI可以打断DMA。实际上DMA传输完成中断很短不会影响EXTI的响应。4.4 SPI时钟太快导致数据错误STM32F103的SPI1在18MHz下如果PCB走线超过10厘米或者没有做阻抗匹配SCLK波形会有明显的振铃和过冲。AD7606的SCLK输入是高阻抗CMOS输入对过冲很敏感过冲超过VDD0.3V就可能损坏芯片或者导致误触发。解决方法在SCLK线上串一个33欧姆到100欧姆的电阻靠近MCU端放置。这个电阻跟走线电容构成RC低通可以抑制振铃。另外SCLK走线要尽量短最好跟DOUTA走线等长减少 skew。如果实在跑不到18MHz就降到9MHz采样率降到10kSPS对大多数应用来说也够用了。4.5 常见问题速查表现象可能原因排查方法解决方案数据全0CS未拉低示波器看CS引脚检查GPIO配置和代码逻辑数据全0xFFFFDOUTA悬空万用表测DOUTA电压检查AD7606供电和CS信号数据跳动大基准噪声示波器看REFIN引脚加10uF0.1uF去耦电容BUSY中断丢失优先级配置错误调试器看NVIC寄存器DMA优先级设0EXTI设1SPI时钟振铃走线太长示波器看SCLK波形串33欧姆电阻缩短走线采样率不准定时器时钟错误示波器看CONVST频率确认APB1定时器时钟倍频5. 实操心得与进阶优化5.1 用DMA双缓冲实现连续采集如果你要做连续不间断采集单缓冲DMA会在传输完成中断里花时间处理数据这段时间SPI是空闲的。用双缓冲DMA可以解决这个问题DMA传输完成一半时触发中断你处理前半部分数据同时DMA继续传输后半部分。STM32F103的DMA支持半传输中断在CubeMX里把DMA的Mode设成Circular然后开Half Transfer中断就行。具体做法是定义一个长度为16的缓冲区DMA循环填充。半传输中断触发时前8个数据已经就绪你处理这8个传输完成中断触发时后8个数据就绪你处理那8个。这样CPU处理数据的时间可以跟DMA传输时间重叠实现无缝连续采集。5.2 硬件片选与软件片选的取舍AD7606的CS可以用硬件NSS控制也可以用软件GPIO控制。硬件NSS的优点是时序由SPI外设自动控制不需要CPU干预。但STM32的硬件NSS有个问题NSS引脚在每次传输结束后会自动拉高而AD7606要求CS在整个8通道读取期间保持低电平。所以硬件NSS不适合AD7606必须用软件GPIO控制CS。软件片选的缺点是每次传输前后都要手动拉低拉高CS增加了几条指令的开销。但在10kSPS下这点开销可以忽略不计。我实测软件片选和硬件片选在9MHz SPI时钟下数据传输时间只差不到1微秒。5.3 过采样与数字滤波AD7606支持过采样模式通过OS0、OS1、OS2三个引脚设置过采样倍数最高可以到64倍。过采样可以显著提高有效位数但会降低吞吐率。比如你设64倍过采样200kSPS的吞吐率会降到3.125kSPS但有效位数可以从14位提高到16位。如果你不需要那么高的采样率过采样是个很好的选择。设置方法是把OS0、OS1、OS2接到GPIO上在初始化时配置成需要的倍数。注意过采样模式下BUSY的高电平时间会变长因为AD7606要做多次转换然后求平均。64倍过采样时BUSY高电平大约256微秒所以CONVST周期要大于这个时间。5.4 PCB布局布线要点AD7606是混合信号芯片PCB布局要特别注意模拟地和数字地的分割。我通常把AD7606放在模拟区STM32放在数字区两个地平面在AD7606下方单点连接。AD7606的AGND和DGND引脚都要接到模拟地然后在芯片下方用0欧姆电阻或者磁珠连接到数字地。模拟输入走线要远离数字信号线特别是SCLK和CONVST这种高频信号。如果实在避不开就在中间加一条地线做隔离。AD7606的模拟输入引脚内部有钳位保护但外部还是要加TVS管防止工业现场的浪涌电压损坏芯片。5.5 校准与误差补偿AD7606的增益误差和偏移误差可以通过软件校准来补偿。校准方法是在输入端接一个已知的精确电压比如0V和满量程电压读取AD7606的输出码然后计算增益和偏移的修正系数。具体步骤先短接输入端到地读100次取平均得到偏移码Offset。然后输入一个精确的参考电压Vref_cal读100次取平均得到满量程码FullScale。增益系数Gain (FullScale - Offset) / (Vref_cal / LSB)。实际电压 (adc_code - Offset) / Gain * LSB。这样可以把偏移误差和增益误差都补偿掉有效位数可以提高1到2位。我在实际项目中用这个方法把AD7606的有效位数从13.5位提高到了15.2位效果非常明显。校准系数存在STM32的Flash里每次上电自动加载不需要重复校准。