ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 SPI驱动AD7606:8通道同步采样与DMA读取实战

STM32 SPI驱动AD7606:8通道同步采样与DMA读取实战 1. 项目缘起与整体方案拆解AD7606 这颗芯片在电力监控、电机控制、振动采集这些场景里出镜率极高8 通道同步采样、16 位分辨率、最高 200kSPS 的吞吐率基本上把多路模拟量同时抓这件事做到了一个很舒服的性价比区间。但真到动手把它接到 STM32 上很多人会卡在同一个地方CubeMX 里 SPI 配置看着都填了代码也生成了结果读回来的数据要么全 0要么全 0xFFFF要么通道之间串得乱七八糟。问题往往不在 AD7606 本身而在于采样时序、转换启动、SPI 读取这三件事没有对齐。我这次做的项目是用 STM32F103 系列通过 SPI 接口驱动 AD7606实现 8 通道同步采样采样率目标定在 50kSPS 左右数据通过 DMA 搬到内存缓冲区主循环只负责打包和上传。选 SPI 而不是并行接口理由很直接并行接口要占用 16 根数据线加若干控制线F103 的 IO 本来就紧张而且并行布线在双层板上很容易出问题SPI 只需要 4 根线SCLK、MISO、CS、BUSY引脚省下来可以干别的。代价是读取速度受限于 SPI 时钟但 50kSPS 这个量级SPI 跑 10MHz 以上完全够用。整体方案的核心思路可以概括成一句话用定时器触发 CONVST 启动转换用 BUSY 下降沿或者轮询判断转换完成再用 SPI 把 8 个通道的数据一次性读回来。这里有个关键点容易被忽略——AD7606 的 8 个通道数据是在同一个转换周期内锁存的SPI 读取的时候是按通道顺序依次移出所以只要转换启动是同步的读回来的就是同一时刻的采样值。这也是同步采样的真正含义不是 SPI 读得快就叫同步。提示AD7606 的 CONVST 上升沿触发转换BUSY 变高表示转换进行中BUSY 变低表示转换完成、数据可读。这两个信号的时序关系是整个项目的地基后面所有调试都围绕它展开。方案选型上还有几个岔路口需要交代清楚。第一用硬件 SPI 还是软件模拟 SPI我选硬件 SPI因为 F103 的 SPI1 支持 DMA配合起来 CPU 占用极低。第二用硬件片选还是软件片选AD7606 的 CS 需要在读取期间保持低电平硬件 NSS 在某些配置下会在字节间拉高导致读取中断所以我用软件片选手动控制 GPIO。第三用轮询 BUSY 还是外部中断50kSPS 意味着 20us 一个周期中断太频繁会拖垮系统我最终用定时器触发 轮询 BUSY 的方式简单可靠。2. AD7606 关键引脚与时序细节解析2.1 引脚功能与连接方式先把 AD7606 的引脚按功能分组理一遍这样接线的时候不会乱。模拟输入侧有 8 个通道 V1 到 V8每个通道有对应的 AGND数字侧关键信号包括 CONVST A/B、BUSY、CS、SCLK、DOUTA/DOUTB、RESET、RANGE、OS 等。我这次用的是串行模式所以只接 DOUTA 一根数据线DOUTB 悬空或者接地都行具体看芯片手册的串行模式说明。引脚方向作用我的接法CONVST A/B输入启动转换上升沿有效接 PA8定时器触发BUSY输出转换状态高转换中接 PA9轮询读取CS输入片选低有效接 PA4软件控制SCLK输入SPI 时钟接 PA5SPI1_SCKDOUTA输出串行数据输出接 PA6SPI1_MISORESET输入复位接 PA10上电复位一次RANGE输入量程选择接 GND选 ±5VOS输入过采样设置接 GND不过采样这里有个细节值得说CONVST A 和 CONVST B 在串行模式下可以短接在一起用一个信号同时触发这样 8 个通道就是严格同步启动的。如果你分开控制A 组和 B 组之间会有微小的时间差虽然对多数应用影响不大但既然叫同步采样能短接就短接。2.2 转换与读取的时序关系AD7606 的完整工作周期分两段转换阶段和读取阶段。转换阶段从 CONVST 上升沿开始BUSY 拉高芯片内部逐次逼近8 个通道并行转换耗时典型值 4us 左右跟过采样设置有关。BUSY 拉低后数据就锁存在输出寄存器里这时候才能开始 SPI 读取。读取阶段的关键是CS 拉低后第一个 SCLK 下降沿开始移出数据每个通道 16 位8 个通道共 128 个 SCLK。注意 AD7606 是在 SCLK 下降沿更新数据、上升沿被采样所以 STM32 这边要配成 SPI 模式 2CPOL1, CPHA1或者模式 3具体要看你的采样沿。我实测下来用 CPOL1、CPHA1 最稳也就是空闲时 SCLK 为高第二个边沿采样。注意很多人读 AD7606 读不出来就是因为 SPI 模式配错了。AD7606 手册里写的是数据在 SCLK 下降沿变化主机应该在上升沿采样对应 STM32 的 CPOL0、CPHA1模式 1或者 CPOL1、CPHA1模式 3。我建议先用模式 3 试不行再换模式 1这两个是最常见的正确配置。时序上还有一个坑CS 拉低到第一个 SCLK 之间需要至少 25ns 的建立时间SCLK 最后一个下降沿到 CS 拉高之间也需要保持时间。STM32 的 SPI 硬件在 DMA 模式下CS 是软件控制的所以要在启动 DMA 之前拉低 CSDMA 传输完成中断里再拉高 CS这样时序最干净。2.3 过采样与量程的取舍AD7606 支持过采样通过 OS 引脚或者寄存器配置可以把多个采样结果平均降低噪声。过采样倍数越高转换时间越长BUSY 高电平持续时间越久。我这次目标 50kSPS如果开 4 倍过采样转换时间会从 4us 涨到十几 us50kSPS 的周期是 20us留给读取的时间就不够了。所以最终选择不过采样靠后端软件做滑动平均来降噪。量程方面RANGE 接 GND 选 ±5V接 VDRIVE 选 ±10V。我的传感器输出是 ±5V 范围所以接 GND。这里要注意量程选择影响的是模拟输入范围不影响 SPI 读取的 16 位数据格式读回来的都是补码形式正负满量程对应 ±32768。3. STM32CubeMX 配置全流程3.1 时钟与调试接口配置打开 CubeMX先选芯片型号我用的是 STM32F103C8T6。第一步配 RCCHSE 选 Crystal/Ceramic Resonator这样可以用外部 8MHz 晶振。然后进 Clock Configuration把 PLL 倍频到 72MHzAPB2 保持 72MHzSPI1 挂在 APB2 上APB1 分频到 36MHz。SPI1 的时钟源是 APB2所以 SPI 波特率预分频器是基于 72MHz 来算的。调试接口我保留了 SWDPA13 和 PA14 不占用方便下载和调试。这里提醒一句如果你用的是 F103 的最小系统板有些板子 PA13/PA14 被复用成别的功能了配置前先确认原理图。3.2 SPI1 参数配置在 Connectivity 里选 SPI1Mode 选 Full-Duplex Master虽然 AD7606 只往外发数据但 STM32 这边配成全双工也没问题MISO 照样能收。Hardware NSS Signal 选 Disable因为我们用软件片选。参数配置如下Clock Polarity: HighCPOL1Clock Phase: 2 EdgeCPHA1Data Size: 16 BitsFirst Bit: MSB FirstPrescaler: 872MHz / 8 9MHzCRC Calculation: DisabledNSS Pulse Mode: DisabledTI Mode: Disabled9MHz 的 SPI 时钟读 128 位需要约 14us加上转换时间 4us总共 18us刚好卡在 20us 周期内。如果你要更高的采样率可以把预分频改成 4SPI 跑到 18MHz但要注意 AD7606 的 SCLK 最高频率是 23MHz 左右别超了。提示Data Size 选 16 Bits 很关键。AD7606 每个通道就是 16 位用 16 位帧读DMA 搬 8 次就正好是 8 个通道。如果你选 8 位就得搬 16 次还得自己拼字节麻烦且容易错。3.3 DMA 配置在 SPI1 的 DMA Settings 里点 Add选 SPI1_RXMode 选 Circular 或者 Normal 都行。我选 Normal因为每次转换完读一次读完在中断里重新启动。Data Width 选 Half Word16 位跟 SPI 的 Data Size 对齐。Priority 选 High。DMA 的 Circular 模式适合连续采集但 AD7606 是转换一次读一次的节奏用 Normal 模式配合手动重启更符合逻辑。如果你要做连续高速采集可以用 Circular但要注意缓冲区覆盖的问题。3.4 GPIO 与定时器配置GPIO 需要配这几个PA4GPIO_Output初始 High作为 CSPA8GPIO_Output 或者定时器通道作为 CONVSTPA9GPIO_Input作为 BUSYPA10GPIO_Output作为 RESETCONVST 我用 TIM1 的 PWM 输出这样触发频率精确可控。TIM1 挂在 APB2 上72MHz预分频设 71得到 1MHz 计数频率ARR 设 19就是 20us 周期对应 50kHz。PWM 模式选 PWM1占空比设很小比如 CCR1这样 CONVST 就是一个窄脉冲上升沿触发转换。BUSY 用轮询方式读在定时器中断或者主循环里判断。如果用中断可以把 BUSY 接到 EXTI但 50kHz 的中断频率对 F103 来说有点吃力所以我用主循环轮询 状态机的方式。4. 代码实现与核心环节剖析4.1 初始化与复位流程上电后的初始化顺序很重要。先初始化 HAL 库、系统时钟、GPIO、SPI、DMA、定时器然后给 AD7606 一个复位脉冲。RESET 引脚拉高至少 50ns 再拉低芯片内部寄存器恢复到默认状态。复位后建议延时 1ms 再开始第一次转换让内部电路稳定。void AD7606_Init(void) { HAL_GPIO_WritePin(AD_CS_GPIO_Port, AD_CS_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(AD_RESET_GPIO_Port, AD_RESET_Pin, GPIO_PIN_HIGH); HAL_Delay(1); HAL_GPIO_WritePin(AD_RESET_GPIO_Port, AD_RESET_Pin, GPIO_PIN_LOW); HAL_Delay(1); HAL_GPIO_WritePin(AD_RESET_GPIO_Port, AD_RESET_Pin, GPIO_PIN_HIGH); HAL_Delay(1); }复位完成后启动 TIM1 的 PWM 输出CONVST 开始周期性触发。第一次转换启动后BUSY 会拉高等它拉低就可以读了。4.2 转换完成判断与数据读取主循环里用一个状态机来管理等待 BUSY 拉低拉低后拉低 CS启动 DMA 接收DMA 完成中断里拉高 CS处理数据。这里的关键是 DMA 接收的字节数要设对8 个通道 × 16 位 16 字节也就是 8 个半字。uint16_t adc_buf[8]; void AD7606_Read(void) { HAL_GPIO_WritePin(AD_CS_GPIO_Port, AD_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Receive_DMA(hspi1, (uint8_t *)adc_buf, 8); } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { HAL_GPIO_WritePin(AD_CS_GPIO_Port, AD_CS_Pin, GPIO_PIN_SET); adc_ready 1; } }注意 HAL_SPI_Receive_DMA 的第三个参数是数据个数不是字节数。因为 SPI 配的是 16 位所以传 8 就是 8 个半字正好 16 字节。这个参数搞错是新手最常见的错误之一传成 16 就会多读 8 个半字数据全乱。4.3 数据格式转换与校准AD7606 读回来的是 16 位补码范围 -32768 到 32767对应模拟输入的负满量程到正满量程。以 ±5V 量程为例转换公式是电压 (原始值 / 32768.0) × 5.0如果原始值是 32767对应约 5V如果是 -32768对应约 -5V。实际使用中零点可能有偏移建议做一次校准短接某个通道到 AGND读一批数据求平均把这个平均值作为零点偏移后续所有数据减去它。float AD7606_ToVoltage(int16_t raw) { return (raw / 32768.0f) * 5.0f; }这里有个细节adc_buf 是 uint16_t 类型转成 int16_t 才能正确解释负数。直接拿 uint16_t 去算负值会变成很大的正数这是很多人踩过的坑。4.4 定时器触发与采样率控制TIM1 的 PWM 输出直接驱动 CONVST所以采样率就是 PWM 频率。我设的 50kHz实际测试时用示波器看 CONVST 和 BUSY 的波形确认 BUSY 高电平时间不超过 20us 减去读取时间。如果 BUSY 还没拉低就开始下一次转换数据会出错。如果你需要动态调整采样率可以改 TIM1 的 ARR 寄存器。比如要 10kHzARR 设 7199要 100kHzARR 设 719。但 100kHz 时周期只有 10us转换 4us 加读取 14us 就超了所以 SPI 时钟得往上提或者减少读取的通道数。5. SPI 时序调试实战技巧5.1 用示波器抓波形定位问题调试 SPI 最有效的手段就是示波器没有之一。把 CS、SCLK、MISO、BUSY 四根线同时接上触发设在 CS 下降沿看整个读取过程。正常的波形应该是CS 拉低SCLK 开始输出 128 个脉冲MISO 在每个 SCLK 上升沿稳定数据依次移出。如果 MISO 一直是高电平说明 AD7606 没在输出数据可能是 CS 没拉低、SCLK 没出来、或者芯片没复位。如果 MISO 有数据但全是 0xFFFF可能是 SPI 模式配错采样沿不对。如果数据只有前几个通道对、后面全乱可能是 DMA 长度设错或者 CS 提前拉高了。提示没有示波器的话可以用逻辑分析仪几十块钱的那种就够用。抓一次波形比盲调半天代码有效得多。5.2 SPI 模式与采样沿的确认方法AD7606 的时序图里数据在 SCLK 下降沿更新主机在上升沿采样。对应 STM32 的 SPI 模式CPOL0、CPHA1 是模式 1CPOL1、CPHA1 是模式 3。这两个模式都是上升沿采样区别在于空闲电平。我实测模式 3 更稳因为空闲时 SCLK 为高第一个下降沿来得快建立时间更充裕。如果你不确定用哪个模式可以写个小测试固定读第一个通道把两种模式都试一遍看哪个读出来的数据稳定且符合预期。AD7606 的第一个通道数据在 CS 拉低后的前 16 个 SCLK 移出如果模式对了这个值应该跟输入电压对应。5.3 DMA 传输中的常见异常DMA 传输最常见的异常是只收到一半数据或者数据错位。原因通常是 DMA 的 Data Width 跟 SPI 的 Data Size 不匹配。SPI 配 16 位DMA 也要配 Half Word如果 DMA 配成 Byte就会把 16 位拆成两个字节搬数据全乱。另一个坑是 DMA 的 Normal 模式下传输完成后要手动重新启动否则第二次读不到数据。我在 DMA 完成中断里拉高 CS 后会在主循环里判断 adc_ready 标志处理完数据再启动下一次读取。如果你用 Circular 模式就不用重启但要注意缓冲区覆盖。5.4 采样率与读取时间的平衡50kSPS 时周期 20us转换 4us读取 14us余量只有 2us。这个余量很紧如果 SPI 时钟有抖动或者中断延迟就可能超时。我的做法是把 SPI 预分频从 8 改成 4SPI 跑到 18MHz读取时间降到 7us余量就充裕了。AD7606 的 SCLK 最高 23MHz18MHz 完全在范围内。如果你要更高的采样率比如 100kSPS周期 10us转换 4us读取必须压到 6us 以内SPI 得跑到 21MHz 以上接近极限了。这时候可以考虑用并行接口或者换更高主频的 STM32。6. 常见问题速查与避坑经验6.1 数据全 0 或全 0xFFFF 的排查这个问题我遇到过三次每次原因都不一样。第一次是 CS 没拉低AD7606 根本没被选中第二次是 SPI 模式配错采样沿不对读到的都是无效位第三次是 RESET 引脚没接好芯片一直处于复位状态。排查顺序建议是先确认 CS 波形再确认 SCLK 有没有输出最后确认 SPI 模式。现象可能原因排查方法全 0CS 未拉低、SPI 未启动示波器看 CS 和 SCLK全 0xFFFFSPI 模式错误、MISO 悬空换 SPI 模式试检查接线数据错位DMA 长度或位宽错误检查 DMA 配置通道串扰CS 提前拉高、读取不完整看 CS 和 SCLK 数量6.2 BUSY 信号异常的定位BUSY 一直高说明转换没完成可能是 CONVST 没触发、芯片没供电、或者 RESET 没释放。BUSY 一直低说明转换没启动CONVST 可能没接对或者定时器没输出。用示波器同时看 CONVST 和 BUSY正常的波形是 CONVST 上升沿后 BUSY 拉高几个 us 后 BUSY 拉低。如果 BUSY 抖动厉害可能是电源噪声或者地线干扰。AD7606 的模拟地和数字地要分开走最后单点共地。去耦电容要靠近芯片引脚100nF 和 10uF 并联。6.3 采样精度不达标的改善精度问题通常来自三个方面参考电压噪声、模拟输入阻抗、PCB 布局。AD7606 内置参考电压但如果你用外部参考要确保参考源干净。模拟输入如果直接接传感器要注意传感器的输出阻抗太高的话会影响采样精度必要时加缓冲运放。软件上可以做滑动平均比如连续采 16 次求平均噪声能降 4 倍。但滑动平均会降低有效采样率要权衡。我这次用 8 次平均50kSPS 降到 6.25kSPS 的有效输出率对多数监控场景够用了。6.4 CubeMX 生成代码的注意事项CubeMX 生成的代码里SPI 和 DMA 的初始化顺序是固定的但 AD7606 的复位和定时器启动需要手动加到 main 函数里。我建议在 MX_SPI1_Init 之后、MX_TIM1_Init 之前加 AD7606_Init这样复位完成后定时器才开始触发时序最干净。另外CubeMX 重新生成代码会覆盖用户代码所以自己的函数要写在 USER CODE BEGIN 和 END 之间。我习惯把 AD7606 相关的函数单独放一个 .c 文件CubeMX 只生成初始化业务逻辑自己管理这样重新生成不会丢代码。7. 实测数据与性能评估7.1 采样率与 CPU 占用实测在 72MHz 主频下50kSPS 采样时CPU 占用大约 15%主要是 DMA 中断和数据处理。如果把采样率提到 100kSPSCPU 占用涨到 30% 左右还在可接受范围。SPI 跑 18MHz 时读取 8 通道耗时约 7us转换 4us总周期 11us100kSPS 的 10us 周期就有点紧张了实测会丢数据。我用定时器打时间戳的方式测过从 BUSY 拉低到 DMA 完成平均 7.2us最大 8.1us抖动主要来自中断响应延迟。如果对时序要求极严可以把 DMA 中断优先级设到最高减少延迟。7.2 通道一致性与串扰测试8 个通道同时接同一个信号源读回来的数据最大偏差在 ±3LSB 以内一致性不错。通道间串扰测试是给一个通道加满量程信号其他通道接地读其他通道的数据偏差在 ±2LSB 以内基本可以忽略。这里要提一句AD7606 的通道间隔离做得不错但前提是 PCB 布局要合理。模拟输入走线要远离数字信号尤其是 SCLK 和 CONVST 这种高频信号。我第一版板子没注意SCLK 走线从模拟输入下面穿过串扰明显后来改版把数字和模拟分区问题就解决了。7.3 长时间运行的稳定性连续跑了 72 小时采样数据没有出现跳变或者丢帧。温度方面AD7606 表面温度比环境高 10 度左右在正常范围内。电源电流稳定在 20mA 左右没有异常波动。长时间运行最怕的是 DMA 溢出或者缓冲区覆盖。我用的是 Normal 模式加手动重启每次读取前检查 adc_ready 标志确保上一次数据已经处理完。如果处理不过来就丢弃本次数据避免缓冲区错乱。8. 项目扩展与优化方向8.1 从 8 通道扩展到多片级联如果需要更多通道可以用多片 AD7606 级联。SPI 支持菊花链模式DOUTA 接下一片的 DINCS 共用SCLK 共用这样一片读完自动读下一片。但 AD7606 的菊花链需要配置具体看手册。另一种方式是每片独立 CSSTM32 分时读取简单但占用更多 IO。8.2 结合 FreeRTOS 的任务划分如果项目复杂度上来了可以上 FreeRTOS。把 AD7606 的读取放在一个高优先级任务里数据处理放在低优先级任务通过队列传递数据。注意 SPI 和 DMA 的中断优先级要配好FreeRTOS 的 configMAX_SYSCALL_INTERRUPT_PRIORITY 要设对否则中断里调用 RTOS API 会出问题。8.3 数据上传与上位机对接采集到的数据可以通过串口或者 USB 上传到上位机。我用的是串口115200 波特率每帧打包 8 个通道的数据加时间戳上位机用 Python 解析。如果要更快的上传速度可以用 USB CDC 或者以太网但 F103 没有内置以太网得外接模块。提示串口上传时要注意数据帧的同步建议加帧头和校验避免上位机解析错位。我用的是 0xAA 0x55 帧头加 CRC16 校验实测很稳。8.4 低功耗场景的优化如果是电池供电的场景可以在两次采样之间让 STM32 进 Sleep 模式定时器唤醒。AD7606 也有待机模式通过 STBY 引脚控制。但要注意唤醒时间Sleep 唤醒很快几个时钟周期对采样率影响不大。最后分享一个我在这个项目里踩过的坑AD7606 的 CONVST 脉冲宽度不能太窄手册要求最小 25ns我一开始用 TIM1 的 CCR 设 1在 72MHz 下脉冲宽度约 14ns结果偶尔触发失败。后来把 CCR 改成 3脉冲宽度约 42ns就稳定了。这种细节手册里写了但容易忽略实际调试时如果遇到偶发问题不妨回头查查时序参数。
返回列表