
简介面向STM32F429等单片机开发者这份工程以GPIO模拟SPI方式驱动AD7606八通道16位同步ADC省去硬件SPI的时序限制适合多路模拟量同步采集场景。压缩包共685个文件约4.85MB以441个c源文件和165个h头文件为主体覆盖外设库、驱动层与应用层另含汇编启动文件、编译工程文件、Hex固件及说明文档目录结构清晰。工程提供bsp_spi_InitAD7606、Demo_spi_AD7606等函数完成GPIO配置、量程选择、定时器轮询与菜单显示可直接编译烧录验证。代码分层合理、注释较全便于移植到其他STM32平台或改为中断、DMA方式读取。目前已有233人学习下载适合正在调试AD7606采样或研究模拟SPI时序的开发者参考。1. 8通道同步ADC靠一根DOUT线读出GPIO模拟SPI够用AD7606 内部并不是一个 ADC 加模拟开关分时切换而是 8 个独立的 16 位逐次逼近 ADC 共用一组控制信号在同一个 CONVST 上升沿把所有通道同时保持并转换。这一条决定了它的读法和其他“8通道 ADC”完全不同转换结果需要从 DOUT 线上按通道顺序串行移出而不是先选通道再读数据。因此即使是 GPIO 模拟 SPI也能完整拿到 8 个通道在同一时刻的电压值代价只是速率上不去。对电力三相、振动监测、电机相电流这类需要严格同步采样的场景AD7606 是常见选择用 GPIO 模拟串行接口则可以在不依赖 STM32 硬件 SPI 重映射、不受引脚冲突限制的情况下快速把驱动跑起来。下文以 STM32F429 工程为背景把引脚配置、模拟时序、读取代码和移植注意点逐段拆开。2. AD7606串行时序与GPIO模拟SPI的极性选择2.1 为什么读数据必须关注下降沿而不是上升沿AD7606 的串行接口在 CS 拉低后每次 SCLK 的下降沿会把下一位数据送到 DOUT 引脚上。主机如果按常规 SPI Mode 0空闲低、上升沿采样读取第一次读到的其实还是上一个时钟周期留下的旧电平整体会错一位。所以模拟 SPI 时先把极性理解对比抄代码更重要。常见做法是让 SCLK 空闲为高在循环里每次先把 SCLK 拉低让 AD7606 更新 DOUT 位等待一个短延时后再读取引脚电平读完后把 SCLK 拉高进入下一位。这个过程对应 SPI Mode 1CPOL0, CPHA1总线空闲时 SCLK 为低数据在下降沿改变、上升沿稳定。如果用的是中断方式驱动 SCLK也要保证下降沿和读数之间有足够的时间窗口不要在下沿之后立刻采样。把这个极性关系反过来的代码也能跑但容易出现首通道最高位丢失或者整体错一位的问题。排查时先在逻辑分析仪上量 SCLK 下降沿与 DOUT 变化点的时间差如果变化点比下降沿晚了几百纳秒就说明是延时不够不是极性配置问题。2.2 FRSTDATA 与 CS 的帧对齐AD7606 串行模式下还有一个 FRSTDATA 输出它在 CS 下降沿后、第一个通道的数据开始输出时拉高读完第一个通道后拉低。接上一根线可以用它确认帧头避免抖动导致通道错位。在 GPIO 模拟方案里如果只关心连续读 8 个 16 位结果FRSTDATA 也可以不接做法是每次 CS 拉低后严格读 8 个通道读满即一帧。工程里为了简化布线通常就采用后者。但要注意 CS 脉冲的宽度两个 SCLK 周期之间 CS 保持低的时间不能太短读完 8 通道后将 CS 拉高AD7606 才会结束这一次读取。用 GPIO 模拟时没有硬件 CS 管理必须自己在代码里保证 CS 只在整帧读取过程中为低。2.3 GPIO 分配与量程、过采样引脚AD7606 除了 SCLK、CS、DOUT还需要 CONVST、BUSY、RESET、RANGE、OS0~OS2 这几个控制引脚。CONVST 用于触发同步转换BUSY 用于查询转换是否完成RESET 做上电复位RANGE 选择 ±5V 或 ±10V 输入量程OS[2:0] 配置过采样倍率。功能推荐GPIO方向初始电平说明SCLK推挽输出高空闲高下降沿更新 DOUTCS推挽输出高读整帧时拉低DOUT输入-AD7606 串行数据输出BUSY输入-转换期间为高CONVST推挽输出低上升沿触发同步转换RESET推挽输出低上电后给一个高脉冲RANGE推挽输出按量程1 ±10V0 ±5VOS[2:0]推挽输出000无过采样OS 引脚组合决定过采样倍率000 表示关闭过采样001~110 对应 2 到 64 倍。过采样倍率越高转换时间越长适合噪声大的现场信号做高速同步采样时保持 000 即可。RANGE 不能按 ±5V 和 0~10V 理解AD7606 只有 ±5V 和 ±10V 两种双极性量程正负满量程由 RANGE 引脚决定。2.4 为什么不用硬件SPI外设STM32 的硬件 SPI 在速率上有优势但把它接到 AD7606 会遇到两个问题一是 SCLK 空闲极性和相位需要配置为 Mode 1部分库函数封装下很容易配成 Mode 0 或 Mode 3导致数据整体错位二是硬件 SPI 的 CS 引脚如果使用 NSS 硬件管理在连续读 8 个 16 位数据时很难精确控制在整帧结束后才释放 CS而 AD7606 又不支持像普通 SPI Flash 那样的连续地址读。GPIO 模拟 SPI 反而把时钟和片选完全掌握在循环里每读一位都是一次电平翻转调试时可以直接量 SCLK 和 DOUT 的对应关系。代价是 128 位数据需要 128 次循环以几百 kHz 的采样率来看足够用。如果同时跑 ADC 采样和 UI 刷新可以把读取函数放到定时器中断里中断里只做电平翻转和数据移位不要做浮点换算换算放到主循环。3. STM32 F429 GPIO模拟SPI读取AD7606完整驱动实现3.1 初始化函数与引脚时钟工程里用 bsp_spi_InitAD7606() 完成所有 GPIO 配置写成独立函数的好处是换板子时只改这一个函数。下面以 HAL 库为例实际源码包可能是标准外设库风格结构一致。void bsp_spi_InitAD7606(void) { GPIO_InitTypeDef gpio; __HAL_RCC_GPIOE_CLK_ENABLE(); /* AD7606 控制线挂在 GPIOE */ gpio.Pin AD7606_SCLK_PIN | AD7606_CS_PIN | AD7606_CONVST_PIN | AD7606_RESET_PIN | AD7606_RANGE_PIN | AD7606_OS0_PIN | AD7606_OS1_PIN | AD7606_OS2_PIN; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_NOPULL; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOE, gpio); gpio.Pin AD7606_DOUT_PIN | AD7606_BUSY_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_NOPULL; /* AD7606 推挽输出不需要内部上拉 */ HAL_GPIO_Init(GPIOE, gpio); AD7606_SCLK_HIGH(); AD7606_CS_HIGH(); AD7606_CONVST_LOW(); AD7606_RESET_LOW(); AD7606_OS_ALL_LOW(); }逻辑说明所有控制引脚先配置为推挽输出DOUT 和 BUSY 配置为输入。注意 DOUT 不要开内部上拉AD7606 的输出级是推挽结构加内部上拉反而会增加线缆上的电流对边沿没有好处。初始化结束时把 SCLK 置高是为了让第一拍拉低时产生有效的下降沿把第一个 bit 更新到 DOUT 上。RANGE 和 OS 引脚的电平在初始化后设置不能留成浮空。3.2 单通道16位读取与整帧读取核心读位函数如下它把 SCLK 的下降沿、DOUT 采样和位移封装到一个循环里static uint16_t AD7606_ReadOneChannel(void) { uint16_t val 0; for (uint8_t i 0; i 16; i) { AD7606_SCLK_LOW(); /* 下降沿AD7606 更新 DOUT */ __NOP(); __NOP(); /* 等待数据稳定具体延时按主频调整 */ if (AD7606_DOUT_READ()) { val | (0x8000U i); /* 高位先出按位拼装 */ } AD7606_SCLK_HIGH(); /* 拉高准备下一个下降沿 */ } return val; } void AD7606_Read8Ch(uint16_t *buf) { AD7606_CS_LOW(); for (uint8_t ch 0; ch 8; ch) { buf[ch] AD7606_ReadOneChannel(); /* 通道顺序固定 CH1~CH8 */ } AD7606_CS_HIGH(); }逻辑说明AD7606 高位先出所以第一个读到的 bit 放到 0x8000 位之后依次右移。SCLK 每次拉低都会让 AD7606 把下一位放到 DOUT 上读取动作发生在低电平窗口内SCLK 拉高只是补全时钟周期。两次 __NOP() 在 180MHz 主频下大约是 11ns 级别的延时对于 AD7606 的数据输出延迟来说偏短稳妥做法是换成几个空的 for 循环或者直接用 while 加变量延时。模拟 SPI 跑在 1MHz 左右时一位时钟周期约 1μsGPIO 翻转函数的开销已经占掉大部分时间不需要额外精细延时。3.3 转换触发与等待 BUSY在读取之前需要触发一次转换。AD7606 的 CONVST 上升沿启动所有通道的同步采样BUSY 引脚在转换期间保持高电平转换完成后拉低。void AD7606_StartConv(void) { AD7606_CONVST_HIGH(); __NOP(); AD7606_CONVST_LOW(); /* 上升沿已经触发转换 */ uint32_t timeout 10000; while (AD7606_BUSY_READ() timeout--) { /* 等待 BUSY 拉低超时保护避免死循环 */ } }逻辑说明CONVST 拉高的时间至少要满足数据手册中的最小脉冲宽度GPIO 操作本身已经足够重点在 BUSY 等待。用 timeout 而不是裸等是因为如果 AD7606 没有正常供电、复位或者焊接有问题BUSY 可能永远不拉低裸等会卡死整个系统。超时退出后还要清一次 DOUT 上的残留数据避免把上一次转换结果当成新的数据。3.4 Demo_spi_AD7606 主循环组织工程里的 Demo_spi_AD7606() 展示了一个最小可运行结构关键是把触发、读取、换算、显示分层void Demo_spi_AD7606(void) { g_tAD7606.Range 1; /* ±10V 量程 */ bsp_spi_InitAD7606(); bsp_StartAutoTimer(0, 500); /* 软件定时器500ms 触发一次 */ while (1) { bsp_Idle(); /* 喂狗等空闲任务 */ if (bsp_CheckTimer(0)) { AD7606_StartConv(); /* 同步转换 */ AD7606_Read8Ch(g_tAD7606.ADC_Value); /* 读取 8 通道 */ for (uint8_t i 0; i 8; i) { g_tAD7606.Volt[i] AD7606_CalVolt( g_tAD7606.ADC_Value[i], g_tAD7606.Range); } } } }逻辑说明软件定时器由 bsp.c 里的 SysTick 驱动500ms 只是演示值实际需要把 ADC 数据按更高频率采集时可以把 bsp_StartAutoTimer 的参数改小或者改用定时器 PWM 直接触发 CONVST。Range 字段在整个驱动里贯穿量程配置AD7606_CalVolt 换算时要用它来区分 ±10V 和 ±5V。电压换算公式如下注意原码编码的零点不在 0x0000float AD7606_CalVolt(uint16_t raw, uint8_t range) { float full (range 1) ? 10.0f : 5.0f; return ((int32_t)raw - 0x8000) * full / 32768.0f; }参数说明AD7606 的输出编码以 0x8000 为零点正电压对应大于 0x8000负电压小于 0x8000。0x8000 是 16 位有符号数的 0 位置减掉它再乘满量程系数就得到实际电压。注意 32768 是半量程计数不能用 65536否则半量程电压会偏一半。4. 换 MCU 板卡时怎么移植、常见故障怎么查4.1 引脚宏与时钟树的移植点把工程从 F429 挪到 F407、F103 或者 GD32F4 上时首先要改的是引脚宏定义。GPIO 模拟 SPI 的全部硬件依赖只有 GPIO驱动本身不涉及 SPI 外设的初始化所以移植量很小。#define AD7606_SCLK_PIN GPIO_PIN_0 #define AD7606_CS_PIN GPIO_PIN_1 #define AD7606_DOUT_PIN GPIO_PIN_2 #define AD7606_BUSY_PIN GPIO_PIN_3 #define AD7606_CONVST_PIN GPIO_PIN_4 #define AD7606_RESET_PIN GPIO_PIN_5 #define AD7606_RANGE_PIN GPIO_PIN_6 #define AD7606_OS0_PIN GPIO_PIN_7 #define AD7606_OS1_PIN GPIO_PIN_8 #define AD7606_OS2_PIN GPIO_PIN_9逻辑说明上面的宏里 DOUT 和 BUSY 是输入其余是输出引脚分散在多个 GPIO 端口时还需要分别使能对应端口的时钟。F429 启动文件默认把系统时钟配置到 168MHz 或 180MHz具体取决于工程里的 SystemInit 实现代码里不要再调用一遍时钟初始化。移植到 HSE 晶振不是 8MHz 的板子时启动文件里的 PLL 参数要同步改否则表面上看 GPIO 翻转正常实际频率和预期差一大截SPI 时序也会偏移。4.2 常见故障表现象可能原因排查方向读到的值全为 0CS 没拉低、DOUT 方向配错示波器量 CS 和 DOUT 电平确认初始化顺序读到的值全为 0xFFFFSCLK 极性反了读到的是空闲电平检查 SCLK 空闲是否置高下降沿是否产生8 个通道数据整体错位CS 低电平期间多读或少读了一拍用 FRSTDATA 信号对照第一通道输出位置数值在某个点固定不变输入悬空或接线断把通道引脚直接接 GND看读数是否回 0x8000数据跳动大供电不稳、模拟输入未加 RC 滤波AD7606 模拟输入前端加 RC 低通电源加磁珠4.3 电平匹配问题AD7606 的数字接口电平由 VDRIVE 引脚决定不是由 AVCC 决定。VDRIVE 接 3.3V 时DOUT 高电平为 3.3V可以直接连 STM32 的输入SCLK、CS、CONVST 从 STM32 输出 3.3V 高电平也满足 AD7606 的 VIH 要求。如果 VDRIVE 接 5VDOUT 的高电平接近 5VSTM32F429 大部分 IO 兼容 5V但仍建议串一个 1kΩ 电阻或者做分压避免总线上的电平毛刺损坏引脚。RANGE 引脚的电平要在转换前确定不要在上电后再频繁切换OS 引脚的上下拉电阻要选对方向内部没有强上拉悬空等于逻辑不定。工程里这三个引脚都直接用 GPIO 输出驱动是稳定做法。4.4 读不到数据时优先看CS帧长度模拟 SPI 最容易遗漏的点是 CS 的低电平持续时间和读取位数的匹配。AD7606 串行模式下 CS 拉低到第一个数据位出现需要一段固定时间如果 CS 拉低后立即读 SCLK 下降沿第一通道最高位可能还没出现在 DOUT 上。遇到首通道丢最高位的情况在 CS 拉低后加一小段延时或者多做一次空时钟然后把第一个读到的高位丢弃都能解决但前者更符合手册时序。另外每读完 8 个通道必须把 CS 拉高。有些移植代码会在 for 循环里频繁拉高 CS结果把一帧数据切成了 8 段读出来永远只有第一个通道有效。判断标志就是 FRSTDATA或者用逻辑分析仪看 CS 低电平期间是否有连续的 128 个 SCLK 脉冲。5. 编码表自检、DSP滤波接入和更快触发方式5.1 零点与满量程自检驱动跑通后先做静态测试不要直接接传感器。将 8 个通道全部短接到 GND正常读数应在 0x8000 附近波动波动范围与电源噪声和 PCB 布局有关一般不超过几十个 LSB。再把某个通道接到稳定的 3.3V 电压用万用表先量准输入电压然后对照编码表验证换算关系。输入电压±10V 量程理论编码换算后的显示值-10V0x0000-10.00000V0x800003.3V约 0xAA3D3.300010V0xFFFF9.999710V 时编码 0xFFFF 对应的浮点计算值为 9.9997V这是 16 位量化的正常结果不是驱动错误。如果读数偏离理论值超过 1%先检查输入分压电阻精度再看 VREF 基准是否接对了电容。5.2 把源码包里的 DSP 库用起来源码包里已经包含 arm_common_tables.c、arm_dct4_init_f32.c 等 CMSIS-DSP 文件说明 DSP 库是完整编译进来的。对 AD7606 这样自带模拟抗混叠滤波的 ADC最简单有效的是对每个通道做滑动平均或者一阶低通。8 通道数据直接用 arm_mean_f32 每轮求平均适合把采样率提高到一定值之后做降采样不建议在 500ms 一次的慢速演示里用那只是验证工程链接没问题。float32_t ch_buf[16]; float32_t mean_val; arm_mean_f32(ch_buf, 16, mean_val);逻辑说明arm_mean_f32 的第一个参数是待平均数组第二个是点数第三个是输出地址。AD7606 的原始数据先转成 float 存入每个通道的 16 点缓冲区再调用它省掉手写循环。做 FIR 滤波时用 arm_fir_f32 需要先分配状态缓冲区状态缓冲区必须按 4 字节对齐CMSIS-DSP 的滤波函数对内存对齐有要求。5.3 更高采样率触发方式如果 500ms 的软件定时器不满足需求不要靠调小 bsp_StartAutoTimer 参数硬扛。软件定时器的精度依赖主循环响应主循环里一旦有串口打印或者 UI 刷新50ms 间隔就会飘。常见做法是用定时器输出 PWM 接到 AD7606 的 CONVST转换完成后 BUSY 下降沿触发 EXTI 中断在中断里读数据这样采样间隔由硬件定时器保证抖动很小。void EXTI4_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line4) ! RESET) { AD7606_Read8Ch(adc_cur_buf); EXTI_ClearITPendingBit(EXTI_Line4); } }逻辑说明BUSY 下降沿表示一次转换结束此时进入中断读取 8 通道数据读完后主循环只负责处理数据。需要注意中断里不能做浮点换算16 位原始数据拷贝是安全的换算放到主循环或者 DSP 库函数里处理。这样改完之后AD7606 的采样率上限就不再受模拟 SPI 的 GPIO 循环限制而是由转换时间和中断响应时间共同决定。本文还有配套的精品资源点击获取