STM32F103驱动AD7606:同步采样8通道ADC的时序与实现 📅 发布时间:2026/9/17 1:36:57 👁 浏览次数: 简介这份资源是一份基于STM32F103驱动AD7606/AD7608同步ADC的完整工程与说明文档面向嵌入式开发者、单片机学习者以及需要高精度多通道数据采集的工程师。资料涵盖SPI接口初始化、GPIO配置、片选控制、数据传输、中断处理与电源管理等关键环节并结合V4-120b例程演示了8通道16位同步采样实现适合参考移植到实际项目中。压缩包共567个文件约2.06MB主要包含HTML文档、PNG图例、H/C源码文件、汇编启动文件、IAR与Keil工程配置以及PDF说明等类型覆盖源码、配置脚本和阅读材料便于按需查阅。目前已有1307人学习下载。通过该资源读者可快速掌握STM32F103与AD7606/AD7608的SPI通信配置思路获得可直接编译调试的参考代码和详细的函数说明能够有效缩短数据采集系统的开发周期。1. 拆解stm32f103驱动AD7606的时序、接线与命令参数工程现场的信号采集从振动监测到电网谐波分析经常碰到一个共性问题多个通道的数据必须严格在同一时刻被抓取而普通ADC的“轮流采样”天然就带时间差。用stm32f103做主控把AD7606这颗8通道16位同步采样ADC驱动起来就是为了消除这个时间差让8路模拟量真正“同步”地进入控制器。所谓同步不是软件上先后读几次寄存器而是由硬件在同一个转换脉冲沿上完成所有通道的采样保持再依次把结果搬走。这个差异决定了这套驱动代码的时序骨架和大部分调试方向。这颗ADC是ADI的经典型号16位分辨率、8通道、双极性输入吞吐率最高200kSPS片内还带2.5V基准和过采样数字滤波。对stm32f103来说SPI接口是最省IO的选择并行接口则是追求极致吞吐的手段。无论走哪条路驱动AD7606的核心都绕不开三件事复位时序、转换信号控制、数据读取时序。任何一个环节对不上轻则采集波形毛刺重则读出全是0xFFFF或0x0000而且这类问题在示波器上非常隐蔽因为看起来“有转换、有响应”。本文面向已经跑通stm32f103最小系统、会用标准库或HAL库点灯的人按“先理解时序约束再写配置代码最后处理真实世界的问题”这条主线来写。新手的落点在于能看着数据手册的连接图把SPI模式和引脚初始化代码写对读回来的数值能和电压对上。老手的落点在于采样率怎么往上顶、DMA怎么接、多片级联怎么分时、过采样参数对有效位数的实际影响这些在datasheet里写得含蓄的细节下面都会讲到。2. stm32f103与AD7606的接口选型SPI模式和并行模式取舍以及引脚分配要点2.1 为什么同步采样对stm32f103方案如此关键stm32f103的片上ADC是多通道逐次逼近型一个ADC模块同一时刻只能采一路。如果你用它的ADC1去轮流采集三相电压和电流得到的六个通道数据看似步调一致实际上每一路之间都隔着几十微秒的采样间隔。对于工频50Hz的信号采样间隔造成的相位误差会直接污染功率计算和相角分析。AD7606的价值恰恰是“锁存”住同一时刻的8个通道模拟量后续无论你用SPI还是并口把它们搬回MCU这些数据都代表同一个时间断面。2.2 并口模式 vs SPI模式从IO占用、速率的维度做选择AD7606提供了三种数据读取方式并行字节/字读取、串行SPI读取、并行字节轮流读取。在stm32f103上真正常用的只有前两种。对比维度并行模式SPI模式数据线占用DB0-DB15共16根或分高低字节8根1根MISO控制线占用CS、RD、BUSY、CONVST_A/B、RESET、OS0-2与并行相同典型读取速率约100ns/次读操作16位字读更快受SPI时钟限制1MHz-9MHz可调代码复杂度直接读GPIO无时序协议需配置SPI模式、处理字节序适合场景200kSPS全速采集、多片并行绝大多数中低速采样、IO紧张如果你的工程只需要几十kSPS以下的采样率SPI模式能省下十几根GPIO而且stm32f103的硬件SPI有9MHz波特率上限对AD7606来说仍足够覆盖其串行时钟上限。并行模式的优势在高吞吐场景比如你要以100kSPS以上连续记录8个通道SPI的位搬运会明显挤占CPU时间让stm32f103忙于伺候数据搬移而无暇做运算或缓存管理。2.2.1 我推荐的IO分配方案SPI模式为例常见做法是stm32f103作为SPI主机AD7606作为SPI从机。需要注意AD7606的全双工特性即使只读数据MOSI也要配上因为它的寄存器配置如果使用串行模式下的回读功能偶尔需要MOSI发送命令。省掉MOSI的后果是一旦你后续想配置过采样或回读状态只能重焊飞线。// 引脚定义按标准库的风格组织 #define AD7606_RST_PORT GPIOA #define AD7606_RST_PIN GPIO_Pin_4 #define AD7606_CVA_PORT GPIOA #define AD7606_CVA_PIN GPIO_Pin_5 #define AD7606_CVB_PORT GPIOA #define AD7606_CVB_PIN GPIO_Pin_6 #define AD7606_CS_PORT GPIOA #define AD7606_CS_PIN GPIO_Pin_7 // BUSY引脚用于判断转换是否完成 #define AD7606_BUSY_PORT GPIOB #define AD7606_BUSY_PIN GPIO_Pin_0这个分配方案的逻辑是把控制类信号放在PA4-PA7这些带施密特触发的引脚上GPIO翻转速度快BUSY引脚选择带外部中断功能的PB0这样在等待转换完成时可以进入低功耗模式或者用中断方式减少CPU轮询开销。SPI引脚直接用硬件的PA5/PA6/PA7不要用软件模拟SPI原因后面会说明。2.3 复位和启动时序第一次上电最容易踩的坑AD7606是上电自动复位的但这个“自动”有条件电源稳定时间必须够长。很多开发板用LDO给模拟电源供电LDO启动慢如果代码在上电后立即发CONVSTADC可能处于未知状态读出数据全乱。稳妥做法是在初始化函数里显式执行一次硬件复位RESET引脚置高至少50ns再拉低。stm32f103的GPIO翻转速度在几十ns级直接控制即可满足。void AD7606_Reset(void) { GPIO_SetBits(AD7606_RST_PORT, AD7606_RST_PIN); // 此处的延时至少保证50nsstm32f103主频72MHz时一个空循环约几ns for (volatile int i 0; i 10; i); GPIO_ResetBits(AD7606_RST_PORT, AD7606_RST_PIN); // 复位后等待至少25ms让内部基准稳定 Delay_Ms(25); }复位后的25ms等待经常被省略结果是前几百次采样的数据缓慢漂移。AD7606内部基准从建立到精度稳定确实需要时间batch产线测试的标准流程也是上电后至少等25ms再开始转换。如果你用外部基准源这个等待可以缩短但代码里保留它没有任何坏处。3. 用stm32f103标准库初始化AD7606的SPI、GPIO和定时器完整可抄配置3.1 SPI2接口配置为什么选SPI2而不是SPI1SPI1和SPI2在stm32f103上有APB2和APB1两条总线SPI1挂APB2最高72MHzSPI2挂APB1最高36MHz。按数据手册AD7606的SCLK最高可以到约20MHz串行模式SPI1的9MHz波特率上限反而更接近这个规格。但很多工程师的stm32f103方案里SPI1已经给了其他外设比如W5500或显示屏因此SPI2成了自然选择。如果你SPI2的引脚PB13/PB14/PB15没有和现有功能冲突用SPI2是合理的。注意AD7606数据手册中串行读取对SCLK空闲电平有要求。手册规定串行模式下SCLK空闲应该为低电平数据在SCLK下降沿移出上升沿被主机锁存。由此得到SPI配置的关键参数CPOL0空闲低CPHA1第二个边沿采样。这个组合实际上是对应SPI的Mode 1和AD7606的串行时序严格匹配。void AD7606_SPI_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; // 使能SPI2相关时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); // PB13SCK, PB14MISO, PB15MOSI gpio.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; gpio.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽SPI外设接管 gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, gpio); // 配置SPI2为主机模式 spi.SPI_Direction SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode SPI_Mode_Master; spi.SPI_DataSize SPI_DataSize_8b; // 8位模式AD7606数据分高低字节 spi.SPI_CPOL SPI_CPOL_Low; // 空闲时钟为低 spi.SPI_CPHA SPI_CPHA_2Edge; // 第二个边沿采样即Mode 1 spi.SPI_NSS SPI_NSS_Soft; // 软件管理片选 spi.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_8; // 36MHz/84.5MHz spi.SPI_FirstBit SPI_FirstBit_MSB; // AD7606先输出最高位 SPI_Init(SPI2, spi); SPI_Cmd(SPI2, ENABLE); }3.1.1 这段代码里几个值得细看的参数SPI_BaudRatePrescaler_8是刻意选的值。SPI2的工作时钟是36MHz预分频8得到4.5MHz的SCLKAD7606在4.5MHz下能够稳定驱动而且stm32f103作为主机读取数据时硬件FIFO和移位寄存器不会因为太快而丢数据。如果你想提高读取速度可以试预分频4得到9MHz但那需要确保PCB走线短、寄生电容低否则SCLK边沿过冲可能导致采样错位。SPI_DataSize_8b是因为AD7606在串行模式下每次最多移出16位一个通道的数据而stm32f103的SPI外设要发送和接收。你完全可以把数据宽度设为16位一次读回但在DMA搬运场景下8位模式配合数组按字节组织更灵活。读到高字节和低字节后手动拼接反而更容易排查字节序问题。3.2 GPIO初始化控制引脚和BUSY引脚的配置代码所有控制引脚必须配置为推挽输出且初始电平要符合AD7606的静态要求CONVST_A/B拉低、CS拉高、RESET拉低。这个初始电平顺序特别容易写反导致上电瞬间误触发一次转换。void AD7606_GPIO_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // 控制引脚推挽输出 gpio.GPIO_Pin AD7606_RST_PIN | AD7606_CVA_PIN | AD7606_CVB_PIN | AD7606_CS_PIN; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); // BUSY引脚上拉输入用于查询转换状态 gpio.GPIO_Pin AD7606_BUSY_PIN; gpio.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOB, gpio); // 初始电平CS高CONVST低RESET低 GPIO_SetBits(GPIOA, AD7606_CS_PIN); GPIO_ResetBits(GPIOA, AD7606_CVA_PIN); GPIO_ResetBits(GPIOA, AD7606_CVB_PIN); GPIO_ResetBits(GPIOA, AD7606_RST_PIN); }BUSY引脚配置为上拉输入是因为AD7606的BUSY输出是推挽结构MCU侧不需要上拉。但如果你飞线调试线缆较长时外部干扰可能把BUSY电平拉高导致代码永远等不到转换结束而卡死。此时在代码里加一个超时判断比改硬件更稳妥。3.3 软件模拟SPI和硬件SPI的读取差异3.3.1 为什么优先用硬件SPI而不是GPIO模拟时序网上的stm32f103 AD7606例程里软件模拟SPI占了相当大比例原因是AD7606的串行读取时序看起来很简单拉低CS循环16次每次拉高SCLK再读MISO。问题是GPIO模拟方式的SCLK频率完全取决于循环指令周期通常在500kHz到2MHz之间而这只是理论值。在开启中断、又刚好有定时器溢出的情况下模拟时序会被中断服务函数插入SCLK高电平时间忽长忽短AD7606虽然不至于出错但你的采样间隔时间戳会抖动导致波形分析时出现叔频干扰。硬件SPI则没有这个毛病SCLK由外设时钟分频产生与CPU执行状态无关。即便中断打乱了代码流SPI外设仍然按既定波特率移位读到的数据字节序稳定。因此除非你只是验证AD7606能不能出数否则不要用软件模拟SPI来做生产级驱动。4. 核心驱动流程从触发转换到读回8通道数据的完整代码4.1 单次转换模式下的命令时序CONVST脉冲、BUSY等待、CS拉低读取AD7606的转换触发很简单给CONVST_A和CONVST_B一个上升沿即可两个引脚可以短接在一起使用。这个上升沿必须在CS为高时产生否则ADC会把CS的状态误解为正在进行串行读操作。典型流程是确保CS为高CONVST拉低保持至少25ns再拉高等待BUSY引脚从高变低BUSY变低后拉低CS准备读取连续读取8个通道的数据全部读完拉高CSuint16_t adc_samples[8]; void AD7606_StartConversion(void) { // 保证CS为高避免串行读操作和转换触发冲突 GPIO_SetBits(GPIOA, AD7606_CS_PIN); // CONVST_A/B同时产生上升沿 GPIO_ResetBits(GPIOA, AD7606_CVA_PIN); GPIO_ResetBits(GPIOA, AD7606_CVB_PIN); // 此处的空循环至少保证25ns的低电平脉宽 for (volatile int i 0; i 5; i); GPIO_SetBits(GPIOA, AD7606_CVA_PIN); GPIO_SetBits(GPIOA, AD7606_CVB_PIN); // 等待BUSY变低表示转换完成 uint32_t timeout 0; while (GPIO_ReadInputDataBit(GPIOB, AD7606_BUSY_PIN) 1) { timeout; if (timeout 100000) { break; // 超时保护防止ADC异常时死循环 } } } void AD7606_ReadAllChannels(void) { // CS拉低使能串行输出 GPIO_ResetBits(GPIOA, AD7606_CS_PIN); // 每个通道占用两个字节先高后低 for (int ch 0; ch 8; ch) { uint16_t highByte, lowByte; SPI_I2S_SendData(SPI2, 0x00); // 发送任意字节读取移位寄存器的数据 while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) RESET); highByte SPI_I2S_ReceiveData(SPI2); SPI_I2S_SendData(SPI2, 0x00); while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) RESET); lowByte SPI_I2S_ReceiveData(SPI2); adc_samples[ch] (highByte 8) | lowByte; } // 读取完毕CS拉高 GPIO_SetBits(GPIOA, AD7606_CS_PIN); }4.1.1 highByte和lowByte的拼接顺序为什么不能反AD7606串行输出时同一通道的16位数据按照MSB-first顺序先移出位15到位8再移出位7到位0。你收到的高字节在时间上先到因此在stm32f103的小端模式下用(highByte 8) | lowByte拼接得到的是正确数值。如果把顺序搞反读出的数据会变成原值得字节交换比如原本0x1234变成0x3412数值上表现为规律性乱跳而不是固定偏置这种错误最迷惑人。另外一个容易忽略的点每次发送的0x00字节无实际意义纯粹是为了触发SPI外设产生SCLK时钟。AD7606不需要MOSI数据就能输出但这个驱动要求主机时钟必须运行否则数据无法移出。发送0x00不会影响ADC的输出状态。4.2 连续转换模式和DMA配合提高stm32f103的数据吞吐效率单次转换模式适合周期性采样比如每秒采1000次每次采完8个通道后去执行其他任务。但如果你的应用要求连续不间断采几十毫秒甚至几秒的数据比如故障录波、冲击响应分析每次都用轮询方式读SPI就有问题stm32f103在读取期间被完全占用无法并行处理缓存或外部通信。解决办法是把SPI接收接到DMA上。stm32f103的SPI2可以触发DMA1的通道4Rx和通道5Tx。你不需要DMA持续发送数据只要DMA把数据搬到内存里SPI硬件自动完成时钟生成。实现思路是配置DMA1的Rx通道接收缓冲区指定为一个16字节数组8通道x2字节启动DMA传输拉低CSSPI连续产生时钟直到DMA计数归零拉高CSDMA完成中断里处理数据#define AD7606_DMA_BUF_SIZE 16 uint8_t dma_rx_buf[AD7606_DMA_BUF_SIZE]; volatile uint8_t dma_complete 0; void AD7606_DMA_Init(void) { DMA_InitTypeDef dma; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma.DMA_PeripheralBaseAddr (uint32_t)(SPI2-DR); dma.DMA_MemoryBaseAddr (uint32_t)dma_rx_buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; // 从外设读取 dma.DMA_BufferSize AD7606_DMA_BUF_SIZE; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; dma.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; dma.DMA_Mode DMA_Mode_Normal; dma.DMA_Priority DMA_Priority_High; dma.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel4, dma); DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE); DMA_Cmd(DMA1_Channel4, ENABLE); } void AD7606_ReadAllChannels_DMA(void) { dma_complete 0; DMA_Cmd(DMA1_Channel4, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel4, AD7606_DMA_BUF_SIZE); DMA_Cmd(DMA1_Channel4, ENABLE); GPIO_ResetBits(GPIOA, AD7606_CS_PIN); // SPI连续发送空字节产生SCLK时钟驱动数据移出 for (int i 0; i AD7606_DMA_BUF_SIZE; i) { SPI_I2S_SendData(SPI2, 0x00); while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) RESET); } while (dma_complete 0); // 等待DMA接收完成 GPIO_SetBits(GPIOA, AD7606_CS_PIN); // 从dma_rx_buf中解析出8个通道的数据 for (int ch 0; ch 8; ch) { uint16_t highByte dma_rx_buf[ch * 2]; uint16_t lowByte dma_rx_buf[ch * 2 1]; adc_samples[ch] (highByte 8) | lowByte; } }4.2.1 DMA版本的坑发送循环和接收DMA的匹配这段代码里有三个容易出问题的地方。第一发送循环中while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) RESET);是为了确保发送数据寄存器有空间如果去掉连续写入可能丢失数据导致SCLK时钟数量不足数据移位不完整。第二DMA的BufferSize是16字节对应8个通道的完整数据DMA完成标志位在最后一个字节进入内存后置位。第三如果你把DMA接收缓冲区大小设为8字节那么只能读4个通道而且后续通道数据会停留在移位寄存器里下次读的时候产生错位。4.3 转换时间计算与采样率上限的换算AD7606的单次转换时间典型值为4us这意味着理论上最高可以到200kSPS。但在stm32f103之上实际能达到的采样率远低于这个值瓶颈在SPI读取时间上。SPI工作4.5MHz时读取一个通道16位数据需要约3.6us8个通道就是28.8us加上转换时间单轮总耗时约33us对应约30kSPS吞吐率。如果你把SPI提高到9MHz读取时间缩短到约14.2us总耗时约18us对应55kSPS。如果你的应用只需要10kSPS则SPI预分频16约2.25MHz也已经够用反而能降低SCLK边沿的EMI辐射。这个换算关系应该在项目初期就估算好避免画完板子后发现采样率根本达不到指标。5. 过采样配置、字节序陷阱和BUSY超时stm32f103驱动AD7606的边界情况处理5.1 OS0-OS2三个引脚数字滤波和有效位数的取舍AD7606的过采样引脚OS0、OS1、OS2可以在硬件上直接配置也可以通过软件写入SPI寄存器如果开启了串行回读功能。三个引脚组合决定了内部数字滤波器的抽取率从无滤波到64倍过采样。OS2OS1OS0过采样倍率输出数据速率内部采样率200kSPS时典型应用0001x200kSPS高带宽信号0012x100kSPS一般噪声抑制0104x50kSPS工频采样0118x25kSPS振动分析10016x12.5kSPS传感器慢变信号10132x6.25kSPS高精度直流测量11064x3.125kSPS对噪声最敏感场景过采样不是简单重复采样它通过内置数字低通滤波器将高频噪声平均掉相当于提高了有效位数。工程上常见的误用是把OS引脚全接高电平期望得到最高精度结果是数据速率降到3.125kSPS频谱分析时带宽不够用。正确的选法是先确定信号带宽再反推过采样率。比如工频50Hz信号分析到50次谐波需要2.5kHz带宽过采样16倍输出12.5kSPS已经远远够用噪声抑制效果还比1倍好得多。5.1.1 在stm32f103中动态切换过采样率的两种做法如果你的硬件设计把OS引脚接在GPIO上可以运行时随时改变滤波强度。这在多模式仪器中很有用低速高精度模式和高速捕获模式需要不同的过采样设置。void AD7606_SetOversample(uint8_t os2, uint8_t os1, uint8_t os0) { if (os2) GPIO_SetBits(GPIOB, AD7606_OS2_PIN); else GPIO_ResetBits(GPIOB, AD7606_OS2_PIN); if (os1) GPIO_SetBits(GPIOB, AD7606_OS1_PIN); else GPIO_ResetBits(GPIOB, AD7606_OS1_PIN); if (os0) GPIO_SetBits(GPIOB, AD7606_OS0_PIN); else GPIO_ResetBits(GPIOB, AD7606_OS0_PIN); // 切换后需要一次复位以使滤波器重新同步 AD7606_Reset(); }每次切换过采样设置后执行复位是必要的因为内部滤波器需要一个建立周期。如果不复位切换后输出的头几个样本会受到上一档滤波器状态的污染表现为明显的过渡跳变。另一个做法是硬件把OS引脚直接绑到VCC或GND代码层面不做任何切换。这能节省GPIO代价是你必须在设计阶段确定滤波需求后续改不了。对于量产产品我倾向于硬件固定减少固件变量和测试工作量。5.2 字节序和数据符号AD7606双极性输入的补码解析AD7606支持±10V和±5V双极性输入输出格式是二进制补码。0V输入对应0x000010V对应0x7FFF-10V对应0x8000。stm32f103驱动里的uint16_t类型可以正确保存原始数据但要换算成实际电压时必须做有符号处理。double AD7606_ConvertToVoltage(int16_t raw, float vref, float range) { // vref通常为2.5Vrange为输入量程5.0或10.0 // AD7606的增益固定为1满量程对应范围为±range double voltage (double)raw * range / 32768.0; return voltage; }这里的代码逻辑是将原始16位数据强制类型转换为int16_t再除以32768乘上量程。对于5V量程满量程是±5V数字量0x7FFF对应约4.9998V0x8000对应-5V。如果你忘记做有符号转换用uint16_t直接计算负半波会变成一个大正数波形看起来像被整流了一样这在示波器显示时非常奇特容易误判为硬件故障。注意range参数的取值AD7606的RANGE引脚控制输入量程接低电平时为±5V接高电平时为±10V。代码里的range要与硬件设置一致否则电压换算会差一倍。一个可靠的调试方法是把输入对地短接读取原始值如果接近0x0000说明接线和SPI时序正确输入一个已知电压源比如1.000V读取后换算看是否等于1.000V。5.3 BUSY超时处理与电源上电顺序的实战经验BUSY引脚在转换期间为高电平转换完成后拉低。如果你发现程序卡在等待BUSY的循环里第一件事不是查SPI而是量一下RESET引脚电平。AD7606的复位向量落后于电源上电如果你的AD7606_Reset()在main函数里调用之前GPIO初始化错了引脚ADC会一直处于复位状态BUSY永远不变低。另一个常见现象电压测量值整体偏大或偏小但线性度很好。这通常不是AD7606的问题而是输入前端运放的增益误差或分压电阻精度问题。AD7606内部已经集成了2.5V基准和增益电阻外部不需要加运放缓冲直接接传感器信号即可。如果你在输入端额外加了一级跟随器要确保它的失调电压不影响整体精度。// 带超时的BUSY等待版本适合实际工程 uint8_t AD7606_WaitForBusyLow(uint32_t timeout_us) { uint32_t tick 0; while (GPIO_ReadInputDataBit(GPIOB, AD7606_BUSY_PIN) 1) { Delay_Us(1); tick; if (tick timeout_us) { return 1; // 超时返回错误 } } return 0; // 正常完成 }这个带超时的版本返回错误码调用方可以在超时后做错误处理比如置一个全局错误标志、重新复位AD7606或者上报给上位机。在实际工业环境中ADC偶尔受到强电磁干扰导致转换失败是有可能的超时重试比死等更可靠。6. 多片AD7606级联的数据合并技巧和校验策略6.1 级联模式下stm32f103的片选策略共用控制线独立数据线如果系统需要16通道同步采样比如电能质量分析仪采集三相电压和三相电流一颗AD7606的8个通道不够用。级联第二颗AD7606是常见做法。stm32f103的GPIO数量有限多片级联时控制线可以共享数据读取则需要分开片选。具体设计是RESET、CONVST_A/B连接在一起同时触发两颗ADC转换每颗ADC的BUSY引脚分别接两个GPIOCS引脚各自独立。这样做的核心逻辑是保证所有通道的转换在同一个时刻发起消除片间相位差。如果CONVST不同步两颗ADC的采样时刻会错开导致电压和电流的相位关系失真后续算功率因素会得到一个错误结果。读数据时先选中AD7606_U读完8个通道再选中AD7606_I再读8个通道。因为转换已经同步完成读取顺序不影响时间对准。void AD7606_ReadDualChannels(uint16_t *samples_u, uint16_t *samples_i) { // 启动转换共用CONVST GPIO_SetBits(GPIOA, AD7606_CS_U_PIN); GPIO_SetBits(GPIOA, AD7606_CS_I_PIN); GPIO_ResetBits(GPIOA, AD7606_CVA_PIN); GPIO_ResetBits(GPIOA, AD7606_CVB_PIN); for (volatile int i 0; i 5; i); GPIO_SetBits(GPIOA, AD7606_CVA_PIN); GPIO_SetBits(GPIOA, AD7606_CVB_PIN); // 等待所有BUSY都变低 while (GPIO_ReadInputDataBit(GPIOB, AD7606_BUSY_U_PIN) 1); while (GPIO_ReadInputDataBit(GPIOB, AD7606_BUSY_I_PIN) 1); // 读第一颗 AD7606_ReadChannelBlock(samples_u, AD7606_CS_U_PIN); // 读第二颗 AD7606_ReadChannelBlock(samples_i, AD7606_CS_I_PIN); }这段代码里两个片选引脚在读取前同时置高是为了确保在转换启动时没有片选被拉低。然后依次拉低读取数据。务必注意BUSY等待必须同时等两个引脚都变低不能只等一颗就开读否则第二颗可能还没完成转换读取的数据是上轮残留值。6.2 用CRC或冗余通道做数据校验什么场景真的需要单颗AD7606是硬件并行ADC数据出错概率很低真正需要校验的场景是长距离飞线传输或电磁环境恶劣。如果你一定要校验有三种做法硬件上飞出一路已知电压接某个ADC通道软件上检测该通道读数是否在合理区间内软件上对8个通道数据求和观察和是否在某范围内波动或者在SPI数据帧之后附加CRC字节。AD7606本身没有CRC输出所以CRC是上位机层面的自定义协议。// 简单校验第8通道接一个已知电压比如1.25V // 在±5V量程下对应数字量约为0x2000 int8_t AD7606_CheckReferenceChannel(void) { uint16_t raw adc_samples[7]; if (raw 0x1F00 raw 0x2100) { return 0; // 正常 } else { return -1; // 异常参考通道漂移 } }接参考电压做校验在批量产线里有实际意义它能发现ADC坏道、接触不良、前端放大电路故障甚至电源噪声引起的读数漂移。1.25V处于±5V量程的中间位置换算成数字量是0x2000附近允许一定范围内波动。如果你的参考源是电阻分压从VREF引脚取电温度漂移可能会让检测窗口内数值偏移所以窗口要留够余量。6.3 实际项目中的采样率与数据处理流水线设计stm32f103的主频只有72MHz指令执行速度有限。如果你在中断里做了太多数据处理比如浮点运算会导致中断时间过长错过下一次采样触发。常见做法是把采样和数据运算分开定时器中断触发转换读完原始数据存进一个环形缓冲区主循环里再从缓冲区取数据做FFT或滤波。#define SAMPLE_RING_SIZE 2048 uint16_t sample_ring[SAMPLE_RING_SIZE][8]; volatile uint16_t ring_write_index 0; volatile uint16_t ring_read_index 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); AD7606_StartConversion(); AD7606_ReadAllChannels(); // 写入环形缓冲区 for (int ch 0; ch 8; ch) { sample_ring[ring_write_index][ch] adc_samples[ch]; } ring_write_index (ring_write_index 1) % SAMPLE_RING_SIZE; } }这种架构下ADC的采样率实际上由定时器决定而不是AD7606本身。比如定时器配置为10kHz触发中断每次中断里完成一次转换和读取约耗时33us占CPU时间约33%剩下的时间留给主循环做其他事。如果采样率提到20kHz中断耗时占比上升到66%主循环可能被饿死这时你需要考虑用DMA来搬运数据或者降低采样率。在stm32f103上10kHz的8通道全采集精度比较舒服超过这个速率就要做性能和功耗的权衡了。本文还有配套的精品资源点击获取