
简介面向嵌入式开发者的STM32F4与AD7606高精度串行采集实现方案基于SPI总线驱动16位多通道ADC完成模拟信号采集适合具备一定STM32基础、需要掌握SPI通信或模数转换应用的读者。压缩包共181个文件约4.35MB核心代码以49个h头文件和48个c源文件为主并附带Keil工程配置、编译生成的hex/axf/map文件、文本说明与清理脚本可直接打开工程进行编译和烧录验证。已有2279人学习下载。内容覆盖SPI引脚与时钟配置、AD7606工作模式设置、数据接收解析及电压值换算等关键环节并带有LCD显示与基本外设初始化代码能够帮助读者快速理解串行采集链路与寄存器操作方式。对工业数据采集、仪器仪表等场景的项目移植也具有参考价值。 做工业数据采集或者多通道模拟量采集的项目几乎绕不开AD7606这颗芯片。8通道、16位、同步采样性能参数放在今天依然能打很多电力监测、振动分析、测试测量设备里都能看到它。但真正用起来不少人会在接口方式上纠结一下标准并行接法速度快是快可16根数据线一接MCU的引脚瞬间被吃掉一大半。我这次在STM32F4平台上的做法是改用SPI串行采集只用了四五根线就把8个通道全部读出来了实际跑起来稳定性和精度都满足项目需求。这篇东西适合正在调AD7606、或者准备在STM32F4上做多通道同步采样的朋友。我会从方案选型、硬件接线、CubeMX配置、驱动代码到常见的坑完整捋一遍最后附上我自己的排查经验尽量让大家照着做就能跑通。1. 为什么用SPI读AD7606方案选型与设计思路1.1 并行采集的痛点和串行方案的价值AD7606原本的设计里并行接口是性能最完整的形态。16根数据线D0~D15直接接到MCU的GPIO上配合CS、RD、BUSY、CONVST这些控制脚一次转换结束之后一个读周期就能把所有通道的数据拉回来吞吐率拉满没问题。但这样算一下账16条数据线加上控制线轻轻松松占掉20个引脚。F4系列虽然引脚不算少但一个实际产品里往往还要接屏幕、SD卡、传感器、按键、通信接口GPIO很快就捉襟见肘。SPI串行模式最大的好处就是省引脚。整个采集链路只需要SCLK、MISO、CS三根SPI相关线再加上BUSY和CONVST两根控制线一共五根。剩下的引脚可以留给其他功能这对于多外设系统来说非常关键。代价是读取时间变长8个通道每个16位一共128个时钟周期才能读完一次。按照SPI时钟5.25MHz来算一次读取大约需要24.4微秒稳定跑几十kSPS的采样率足够用了。如果对采样率要求没那么极限比如做传感器数据采集、电力参数监测、音频特征提取这类应用串行方案的性价比非常高。1.2 为什么是SPI而不是IIC或并口可能有人会问为什么不用IIC答案先摆在前面AD7606这颗芯片压根就没有IIC接口。这从硬件上就决定了只能选SPI或者并行。那如果外接转接芯片强行用IIC呢IIC协议本身有地址、应答、起始停止等额外开销常用速率也就100k~400k就算跑1Mbps的高速模式传8个通道128位数据也要一百多微秒效率远不如SPI。SPI在这类ADC读取场景里的优势是非常明显的。它是全双工、无帧协议主机只需要产生时钟数据随时钟边沿自动移出不需要额外的命令交互。对比一下就说清楚了对比项SPIIIC信号线数量4根SCK、MOSI、MISO、CS2根SCL、SDA通信速率取决于外设可到几十Mbps标准模式100k高速模式3.4M寻址方式靠CS片选无地址机制带7位/10位从机地址数据格式全双工无帧协议开销有ACK、起始停止位等额外开销适用场景高速数据流、大块数据搬运低速传感器配置、多设备总线共享AD7606本来就是纯从设备不需要地址寻址SPI简直是量身定做。并行接口虽然速度最快但引脚占用实在太夸张除非性能瓶颈卡在串行读取时间上否则综合来看SPI是性价比最高的方案。1.3 AD7606串行时序的关键点BUSY、CS、SCLK之间的关系AD7606的串行时序和普通SPI从设备有个明显的区别它不需要主机发送命令主机只需要在合适的时机拉低CS然后给SCLK时钟就行数据会从DOUT引脚按通道1到通道8的顺序逐位移出。完整的时序流程是这样的CONVST引脚产生一个上升沿触发一次所有通道的同步采样同时BUSY引脚拉高表示正在转换。转换完成后BUSY拉低这时候才允许把CS拉低开始读数据。CS拉低后DOUT在SCLK的驱动下按照MSB在前的方式依次输出每个通道的16位转换结果。FRSTDATA引脚会在这段数据流的第一个通道窗口输出高电平用来辅助定位通道起始位置。SPI工作模式上AD7606数据手册的典型时序是SCLK空闲为高电平数据在下降沿更新、上升沿稳定所以主机应该在第二个边沿采样数据也就是SPI Mode3对应CPOL1、CPHA1。有些板子上用Mode2也能读到数据本质上是边沿匹配的问题但按手册来最稳妥就用Mode3。2. 硬件连接与电路设计要点2.1 引脚分配与最小接线我这次用的是STM32F407VET6SPI2外设。AD7606这边的连接方式如下如果你的板子引脚不同对应改一下就行AD7606引脚接STM32F4引脚说明DOUTAPB14SPI2_MISO串行数据输出SCLKPB13SPI2_SCKSPI时钟CSPB12GPIO输出软件控制片选BUSYPA0GPIO输入转换状态指示CONVSTPA1GPIO输出启动转换RESETPA2GPIO输出硬件复位RANGEPA3GPIO输出输入量程选择OS0~OS2GND关闭过采样VDRIVE3.3V接口电平AVCC5V模拟供电COMAGND模拟地参考点这里面有个特别值得注意的引脚VDRIVE。它决定AD7606数字接口的电平标准必须和STM32F4的IO电平保持一致。STM32F4大部分IO是3.3V供电所以VDRIVE接3.3V最稳妥。如果VDRIVE接了5V虽然F4很多引脚号称5V容忍但不是所有引脚都保证而且电平不匹配容易造成读数据不稳定没必要冒这个险。模拟输入部分AD7606是单端输入相对于COM引脚测量。V1到V8这8个通道输入范围由RANGE引脚决定接低电平是±5V接高电平是±10V。测量双极性信号时模拟输入可以直接接但要注意别超过量程否则内部钳位电路会动作影响精度。2.2 容易踩坑的硬件细节VDRIVE、RANGE、OS、复位和参考电压硬件上有个细节经常被人忽略RANGE和OS这几个引脚的配置状态是在复位过程中被锁存的。也就是说如果你想修改量程或者过采样倍率要先改好这几个引脚的电平然后再给一次复位脉冲芯片才会按新配置工作。我习惯把RANGE、OS配置好后在初始化代码里主动拉一次RESET确保配置生效。过采样引脚OS0~OS2在初步调试阶段建议全部接GND关闭过采样。原因有两个一是串行模式下过采样会改变数据输出的节奏和滤波特性刚开始调SPI通路时变量越少越好二是先把基本的数据读取跑通后面想提升精度再拉高OS引脚重新复位这样问题定位会清晰很多。RESET引脚不要悬空。有些开发板图省事直接用RC复位也能工作但用GPIO控制最方便。上电后先拉低再拉高完成一次明确的复位。参考电压方面AD7606内置参考源REFIN/REFOUT引脚上接一个10uF左右的低ESR电容到AGND就行不需要外部基准源。这个电容摆放位置尽量靠近芯片引脚对转换精度有直接影响。另外SCLK、CS这些数字信号线上做产品的话建议串一个33Ω左右的电阻可以抑制信号反射和振铃。开发板上可以省略但做PCB时最好保留。3. 软件实现从CubeMX配置到读取函数3.1 CubeMX配置SPI参数Mode3、16位数据宽度、软件NSS软件部分我直接用STM32CubeMX生成初始化代码再用HAL库写驱动。SPI2的配置参数如下Mode选择Full-Duplex Master硬件NSS选择Disable用软件控制CSData Size选16 BitsFirst Bit选MSB FirstClock Polarity选HighClock Phase选2 Edge对应Mode3BaudRate Prescaler选8SPI2挂在APB1上时钟42MHz分频后得到5.25MHz的SCLK。SCLK频率为什么要留余量AD7606的串行时钟上限保守控制在10MHz以内比较安全5.25MHz在满足读取速度的同时还留出了抗干扰裕量。SPI时钟并不是越快越好尤其在PCB走线比较长、或者模拟输入信号很小的场合过高的时钟容易把数字噪声耦合到模拟端。配置完成后生成的SPI初始化代码如下hspi2.Instance SPI2; hspi2.Init.Mode SPI_MODE_MASTER; hspi2.Init.Direction SPI_DIRECTION_2LINES; hspi2.Init.DataSize SPI_DATASIZE_16BIT; hspi2.Init.CLKPolarity SPI_POLARITY_HIGH; hspi2.Init.CLKPhase SPI_PHASE_2EDGE; hspi2.Init.NSS SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_8; hspi2.Init.FirstBit SPI_FIRSTBIT_MSB; hspi2.Init.TIMode SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi2.Init.CRCPolynomial 10; HAL_SPI_Init(hspi2);数据宽度选16位这样HAL库的Receive函数一次就能接收一个通道的完整数据不用手动拼字节省事很多。CubeMX生成的GPIO初始化里PA0、PA1、PA2、PA3、PB12分别配置成输入或输出模式引脚标签自己定义好就行。3.2 驱动代码启动转换、等待BUSY、读取8通道驱动代码的核心逻辑就三步发CONVST脉冲启动转换等BUSY拉低拉低CS之后连续读8个16位数据。为了方便复用我用宏把GPIO操作封装了一下#define AD7606_CS_L() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) #define AD7606_CS_H() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) #define AD7606_CONVST_L() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET) #define AD7606_CONVST_H() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET) #define AD7606_BUSY_READ() HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) #define AD7606_RST_L() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET) #define AD7606_RST_H() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET)初始化函数里先配置量程再复位一次确保RANGE和OS的设置被锁存void AD7606_Init(void) { // 默认配置RANGE拉低使用±5V量程 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET); // 复位脉冲锁存RANGE/OS配置 AD7606_RST_L(); HAL_Delay(1); AD7606_RST_H(); HAL_Delay(1); }启动转换的函数很简单给CONVST一个低脉冲即可void AD7606_StartConv(void) { AD7606_CONVST_L(); for (volatile int i 0; i 10; i); // 短延时确保低电平脉宽满足要求 AD7606_CONVST_H(); }读取函数是核心。这里要注意读之前先判断BUSY如果还在转换中就直接返回不强行读取。CS拉低后用HAL_SPI_Receive连续收8个16位数据全部收完再拉高CS。有一个容易被坑的点当SPI数据宽度配置为16位时HAL_SPI_Receive的Size参数指的是16位数据的个数所以填8才是读8个通道填16会只读一个通道就返回剩下的全是垃圾数据。uint8_t AD7606_ReadAll(uint16_t *adc_data) { // 转换未完成直接返回 if (AD7606_BUSY_READ() GPIO_PIN_SET) { return 0; } // BUSY刚拉低时稍微等一下让输出数据稳定 for (volatile int i 0; i 100; i); AD7606_CS_L(); HAL_SPI_Receive(hspi2, (uint8_t *)adc_data, 8, 10); AD7606_CS_H(); return 1; }如果要做连续采样建议把BUSY引脚接到外部中断下降沿触发时置一个标志位主循环检测到标志再调用AD7606_ReadAll不要在主循环里死等BUSY。这样CPU利用率高很多采样间隔也更均匀。3.3 数据换算与验证方法AD7606输出的原始数据是二进制补码16位。RANGE接低电平、量程±5V时电压换算公式电压值 (int16_t)原始码 * 5.0 / 32768.0如果RANGE接高电平、量程±10V就把5.0换成10.0。调试时我习惯先用串口打印原始码值而不是直接转电压。原因很简单先确认读取链路是否正确再确认数据是否符合预期。打印原始码用十六进制格式最直观比如printf(CH1:0x%04X CH2:0x%04X\r\n, adc_data[0], adc_data[1]);然后给某个通道接一个已知的稳定电压比如用电阻分压得到1.25V理论上±5V量程下对应的码值应该是1.25 / 5 * 32768 8192左右。如果实际值在这个附近说明硬件和软件链路基本没问题。4. 常见问题与排查技巧实录4.1 读回全0或全FF先查这些地方读回来全是0先看器件是不是真的上电了AVCC和VDRIVE电压对不对RESET有没有被拉低。确认供电正常后用示波器看CS拉低期间SCLK是否正常输出DOUT在SCLK驱动下有没有数据波形。如果DOUT一直平基本可以确定器件没进入串行输出状态回头查CS时序和供电。读回来全是FF通常是SPI空闲时读到的高电平大概率是CS时序没对比如还没拉低CS就开始读或者CS拉低后SCLK没有正常产生MISO线上保持高电平。另外SPI模式不对也会导致采到的数据全错试着把CPOL和CPHA换一下看看。HAL_SPI_Receive的Size参数填错也是一个很常见的低级错误。数据宽度是16位时Size必须填8很多人下意识填16结果函数读了一个通道就返回CS提前拉高后面的数据全是0xFFFF。这个坑我见过不止一次。4.2 通道数据错位、乱跳怎么解决读到的8个通道数值都对但顺序对不上比如通道2的数据跑到通道1的位置上这种基本是首通道定位问题。AD7606在每次CS拉低后的第一个数据窗口FRSTDATA引脚会输出高电平可以用来对齐通道顺序。如果没接FRSTDATA可以在每次CS拉低前确保上一次读取完整读了8个通道让芯片内部的数据指针回到起始位置。如果仍然错位可以把FRSTDATA接到一个GPIO输入在读取时判断这个引脚状态来定位首个通道。数值乱跳要分情况看。如果是模拟输入引脚悬空AD7606的高输入阻抗会导致读数随机漂移这属于正常现象把未用通道接到AGND就好了。如果是接了稳定信号但数值仍然跳考虑电源和地的问题AVCC和AGND要处理好不能用数字电源直接供电。4.3 数据跳动大、波形失真瓶颈通常在硬件数据整体看起来合理但噪声明显偏大或者正弦波测试时波形毛刺很多优先怀疑电源和PCB布局。AD7606的AVCC如果和电机、继电器、数码管这些大电流器件共用电源采样数据会非常难看。解决办法是AVCC用单独的LDO供电AGND单点接地SPI信号线尽量远离模拟输入走线。还有就是SCLK时钟频率太高数字信号串扰进模拟输入。这时候可以尝试把SPI Prescaler从8改成16SCLK降到2.625MHz看数据是否明显改善。如果改善明显说明是数字干扰问题要么降速要么改PCB布局。4.4 调试心得先看波形再查代码调这类ADC我一直建议的顺序是先示波器再代码。把探头夹到SCLK、CS、DOUT、BUSY这四个信号上看一眼时序心里就有底了。重点检查几个点CONVST上升沿后BUSY有没有立即拉高BUSY高电平持续时间大概在4微秒左右这是转换时间CS拉低后DOUT是否跟着SCLK逐位输出8个通道读完CS拉高后DOUT是否回到高阻状态。这四个点只要都符合软件基本没什么问题。如果示波器上根本看不到波形先查供电和复位再怀疑代码。另外分享一个调试小技巧如果硬件SPI怎么调都不出数据别死磕直接用GPIO软件模拟三线SPI读一次试试。就是把SCK、DOUT、CS三个引脚都用普通GPIO控制按AD7606的时序手动翻转电平读取数据。这个方法虽然慢但能帮你快速区分是SPI外设配置问题还是硬件连接问题。我之前有一块板子就是PCB上DOUT走线断了硬件SPI查了半天没头绪换软件模拟SPI一读发现数据根本没有这才定位到是硬件问题。我在实际做这个项目时还特意把RESET、RANGE、OS几个引脚都引到了排针上调试的时候可以随时跳线改配置不用反复飞线。对于这种硬件配置靠引脚电平锁存的芯片这个习惯能省不少事。如果后续想把采样率提上去可以把BUSY下降沿接到定时器触发或者DMA请求做成乒乓缓冲采样间隔能做得更均匀。但基础的SPI串行读取通路按照上面的方法调通是没什么问题的。本文还有配套的精品资源点击获取