资讯动态

STM32F103通过SPI驱动AD7606实现8通道同步采样

发布时间:2026/9/16 15:14:19 来源:尧图企业网站定制
简介面向使用STM32F103芯片与AD7606/AD7608模数转换器的嵌入式开发者这套驱动资源提供了完整的SPI通信模式源码与配套说明覆盖多通道十六位同步数据采集中涉及的接口初始化、引脚配置、时序控制、命令收发以及中断响应等关键环节。整个压缩包共包含567个文件体积约2.06MB。文件类型以HTML参考文档、PNG示意图、C语言源码、H头文件、汇编启动文件以及IAR/Keil工程配置文件为主其中46个C源文件和65个头文件构建了可直接编译的工程216个HTML文档则用于查阅寄存器与调用说明整体目录结构清晰便于学习与二次开发。该资料目前已有1307人学习下载。压缩包内还包含cstart_thumb2.asm等底层启动文件、stm32f10x系列标准外设库源文件以及PDF用户手册和示例工程详细展示了初始化流程、SPI数据读写函数和中断处理逻辑。通过学习开发者能够快速掌握STM32F103通过SPI驱动AD7606的完整方法并能方便地迁移到AD7608或其他同步采样系统中。1. 为什么在 STM32F103 上给 AD7606 写驱动而不是直接用现成库做 8 通道同步采样时AD7606 几乎是 STM32F103 项目里的首选 ADC——一颗芯片就能同时锁存 8 路模拟输入16 位分辨率最高 200kSPS 吞吐率。但很多人在驱动层就被卡住并行接口要占 16 根数据线和 LQFP48 的 F103 相比引脚完全不够用换成串行 SPI 后又因为 CONVST、BUSY、OS 这几个引脚的状态配合问题读出不稳定的数据。我实际把 AD7606 挂在 SPI1 上用 5 根信号线就完成了 8 通道读取配合标准外设库 V3.5 和 ST-LINK 调试器在电机驱动板上稳定运行。这篇就把这套驱动方案拆开讲时序怎么对初始化怎么配读回来的 16 位补码怎么换算成电压以及常见坑的定位方法。2. AD7606 的采样机制与接口时序并行总线 vs SPI2.1 AD7606 的关键引脚和内部工作原理AD7606 的真正常用引脚分三组模拟输入 V1~V8电源 AVCC/VDRIVE/GND数字接口 CONVST、BUSY、CS、SCLK、DB[15:0]。芯片内部每个输入通道都有独立的采样保持放大器因此所有通道的采样动作发生在同一个 CONVST 上升沿这是它与其他多路复用型 ADC 最大的区别。你关心的“同步采样”实际上就是靠这个上升沿完成的上升沿锁存模拟输入之后内部逐次逼近寄存器SAR对每个通道同时转换所有结果在约 3.2us 后同时就绪。数字接口上DB[15:0] 是并行数据总线配合 CS 和 RD 读取在串行模式下数据从 DOUTA 和 DOUTB 引脚逐位移出。F103 的 GPIO 资源通常不够并行总线使用尤其 LQFP48 封装下再要接按键或串口就会很紧张所以我会优先使用 SPI 读取。FSMC 当然也能把 AD7606 映射到外部内存地址但 F103 的 FSMC 一般服务于外部 RAM 和 TFT为 AD7606 单独配置一套时序性价比不高而且并行连接还容易因为信号线不等长导致高速读取误码。因此本文后面只讨论 SPI 单线输出模式。2.2 CONVST 与 BUSY 的时序关系一次完整的转换周期从 CONVST 拉高开始。手册要求 CONVST 高电平脉冲不小于 25ns实际用 GPIO 翻转时不需要额外延时因为 F103 的 GPIO 输出翻转本身就有几百纳秒。CONVST 上升沿锁存模拟输入随后 BUSY 拉高内部开始转换转换完成后 BUSY 拉低16 位结果已经锁存在移位寄存器里等待 SCLK 读出。以下是简化后的关键时间参数实际值以 AD7606 数据手册为准。参数手册要求我常用的配置CONVST 高电平宽度最小 25nsGPIO 直接输出不额外延时BUSY 高电平保持时间约 3.2us 或更长过采样时更长轮询等待或下降沿中断SCLK 最高频率25MHz18MHz72MHz 主频 4 分频CS 有效到 SCLK 第一个边沿设置保持时间等待 BUSY 拉低后再拉低 CS等待 BUSY 下降沿是最常见的同步方式轮询和外部中断都可以。下面是一个伪代码骨架展示触发转换和等待的过程// 触发一次转换并等待 BUSY 下降沿 AD7606_CONVST_HIGH(); // 拉高 CONVST上升沿锁存输入 AD7606_CONVST_LOW(); // 拉低触发内部转换 while (AD7606_BUSY_READ() SET); // BUSY 拉高期间等待 AD7606_CS_LOW(); // 转换完成后允许输出 read_8_channels(); // 读 8 个 16 位结果 AD7606_CS_HIGH(); // 释放片选这段代码中 CONVST 用普通 GPIO 模拟而不是 PWM因为 PWM 无法在上升沿之后精确控制拉低时机。BUSY 是推挽输出所以 F103 的输入方向不需要上拉或下拉但为了防止外部强干扰我会配成下拉输入。wait 循环里最好加超时计数如果 AD7606 没上电或 CONVST 没触发BUSY 会一直保持高电平没有超时会直接卡死主循环。2.3 SPI 模式下读出数据的位顺序串行模式下CS 拉低后SCLK 的每个下降沿把移位寄存器里的数据从 DOUTA 逐位移出最高位在前。由于 AD7606 在下降沿更新数据F103 需要在上升沿采样所以 SPI 的空闲极性为低CPOL0相位选择第二个边沿CPHA2Edge或第一个边沿都可能在不同板卡上工作原因是 CS 到第一个 SCLK 的建立时间不同。我会在初始化代码里先固定一组参数再用逻辑分析仪看波形如果发现读出数据整体右移一位或最高位变成符号位就把 CPOL 和 CPHA 同时翻转后重试。实际在标准库中我用的组合是SPI_CPOL_Low和SPI_CPHA_2Edge。SPI1 的波特率先用 4 分频也就是 18MHz如果波形质量不好就降到 9MHz。这个细节在调试章节会展开。3. STM32F103 驱动 AD7606最小系统与硬件连接要点3.1 最小系统搭建晶振、电源和复位STM32F103 最小系统通常包括 8MHz 晶振、两颗 20pF 负载电容、BOOT0 下拉到地、复位引脚上拉加 100nF 电容。如果只是想验证 AD7606 驱动用最小系统板就够但要注意电源布局AD7606 的模拟电源 AVCC 需要 5V而 STM32F103 是 3.3V两者不能共用同一路 LDO 输出。常见做法是用一块 5V 电源给 AD7606 的 AVCC 和模拟输入供电另外用低压差稳压器把 5V 转成 3.3V 给 F103 和 VDRIVE 供电。VDRIVE 必须接 3.3V因为它决定了 AD7606 数字引脚的输出高电平范围这样 F103 的 SPI 引脚可以直接连接 AD7606不需要额外电平转换。模拟地和数字地建议在电源入口处用一颗 0Ω 电阻单点连接。AD7606 的 AGND 和 DGND 引脚分开如果地平面分割不当SPI 时钟线的噪声会耦合进模拟输入表现为读数最低位跳动明显。实际项目中我会先把所有 DGND 接到数字地铜皮再把 AGND 单独走线到 ADC 附近最后通过磁珠或 0Ω 电阻合并到主地。3.2 引脚分配表与接线图下面是驱动代码里实际使用的引脚分配基于 STM32F103C8T6 的 LQFP48 封装。注意 CS 用了 SPI1 的硬件 NSS 引脚 PA4但在代码中把它当普通 GPIO 控制不使用硬件 NSS 自动管理。信号AD7606 引脚STM32F103 引脚说明CONVSTCONVST A/B 并联PA0普通推挽输出下降沿触发转换BUSYBUSYPA1输入转换期间高电平RESETRESETPA2输出低有效复位CSCSPA4软件控制片选低有效SCLKSCLKPA5SPI1_SCKDOUTADOUTA/RDPA6SPI1_MISO串行数据输出OS[2:0]OS0/OS1/OS2PB12/PB13/PB14过采样率配置可接 GPIORANGERANGE3.3V 或 GND选择 ±5V 或 ±10VAD7606 在串行单输出模式下只需要 DOUTADOUTB 和并行数据引脚悬空即可。OS[2:0] 如果直接接 3.3V 或 GND 也可以但我想在软件里动态切换过采样率所以接到了 GPIO。注意 RANGE 引脚不能悬空必须接高或接低否则内部逻辑不确定会导致量程出错。3.3 软件工程准备标准库 V3.5 与调试工具STM32F103 的驱动工程我一般从标准外设库 V3.5.0 起步而不是用 HAL。原因是寄存器级的 SPI 操作和 DMA 配置在标准库下更直接而且很多老项目都在 V3.5 上固化了。把 Libraries/CMSIS 和 Libraries/StdPeriph_Driver 拷进工程在预编译选项里加上USE_STDPERIPH_DRIVER, STM32F10X_MD启动文件选择startup_stm32f10x_md.s中密度或_hd.s高密度。如果用的是 C8T6选 md 即可。调试和下载工具方面ST-LINK 需要先安装 stlink 驱动否则 Keil 里会报No ST-LINK detected串口打印则依赖 CH340 或 CP2102 这类 USB 转 TTL 的驱动。另一个容易忽略的是在工程里加入延时函数比如一个简单的delay_us因为 AD7606 上电后需要一小段时间稳定复位后也需要等待。下面的复位函数就是为此准备的void AD7606_HardwareReset(void) { AD7606_RESET_LOW(); // 拉低进入复位状态 delay_us(20); // 复位脉冲保持 20us AD7606_RESET_HIGH(); // 释放复位芯片开始工作 delay_us(1); // 等待内部逻辑稳定 }复位引脚是低电平有效所以这里先拉低再拉高。复位脉冲的最小宽度通常 10ns 以上我给 20us 完全足够。释放复位后芯片默认处于待机状态OS 引脚电平在复位期间被锁存所以 OS 引脚的电平必须在复位释放前设置好。这一点容易漏很多人上电后直接拉高 RESET但 OS 引脚还是高阻导致过采样率随机。4. 使用标准库在 SPI 模式下配置并读取 AD7606 数据4.1 SPI1 和 GPIO 的初始化代码核心驱动分两步GPIO 管脚状态放在AD7606_GPIO_InitSPI 外设配置放在AD7606_SPI_Init。先看 GPIO 初始化这里把 CONVST、RESET、CS 设为推挽输出BUSY 设为输入SCK/MISO 设为复用推挽。void AD7606_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能 GPIOA、GPIOB 和 SPI1 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_SPI1, ENABLE); // CONVST - PA0, RESET - PA2, CS - PA4推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_2 | GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // BUSY - PA1下拉输入防止上电期间误触发 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPD; GPIO_Init(GPIOA, GPIO_InitStructure); // SCK - PA5, MISO - PA6复用推挽 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // OS[2:0] - PB12/PB13/PB14推挽输出先全部拉低 GPIO_InitStructure.GPIO_Pin GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_ResetBits(GPIOB, GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14); }这里把 OS 引脚也初始化了初始值全 0 对应无过采样。SPI_Speed_50MHz只代表 GPIO 输出翻转速率不是实际 SPI 时钟。BUSY 用下拉输入是因为 AD7606 推挽输出上电瞬间状态未知下拉后默认低电平避免第一次读取时误认为转换完成。SPI 初始化要特别注意 CPOL 和 CPHA。下面的配置对应 SCLK 空闲低电平第二个边沿采样数据大小 16 位void AD7606_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }SPI_BaudRatePrescaler_4在 72MHz 主频下得到 18MHz SCLK。AD7606 的 SCLK 上限是 25MHz但 18MHz 已经接近上限长杜邦线下容易产生过冲如果发现采样值不稳定把这行改成SPI_BaudRatePrescaler_8降到 9MHz 后大多能解决。SPI_NSS_Soft表示不使用硬件片选因此 PA4 完全由普通 GPIO 控制。4.2 启动转换并读取 8 通道结果接下来是驱动核心的读取函数。这个函数完成一次完整采样触发转换、等待 BUSY 结束、连续拉取 8 个 16 位结果。int16_t AD7606_ReadChannels(int16_t *buf, uint8_t len) { uint32_t timeout; if (len 8) return -1; // CONVST 输出一个下降沿触发所有通道同步采样 AD7606_CONVST_LOW(); AD7606_CONVST_HIGH(); AD7606_CONVST_LOW(); // 等待 BUSY 拉低表示转换完成 timeout 10000; while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) ! Bit_RESET) { if (--timeout 0) return -2; } // CS 拉低后SPI 连续读取 8 个 16 位数据 AD7606_CS_LOW(); for (uint8_t i 0; i 8; i) { uint16_t raw; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, 0x0000); // 发送空数据同时接收 MISO while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); raw SPI_I2S_ReceiveData(SPI1); buf[i] (int16_t)raw; // 补码转有符号数 } AD7606_CS_HIGH(); return 0; }AD7606_CONVST_HIGH/LOW对应 PA0 的GPIO_SetBits/ResetBitsAD7606_CS_LOW/HIGH对应 PA4。这里 CONVST 拉高再拉低之间没有延时由于 C 语句执行时间在百纳秒级满足手册的 25ns 最小脉冲宽度。如果之后发现触发不稳定在HIGH和LOW之间加一个delay_us(1)即可。SPI 的SendData和ReceiveData必须成对调用。标准库的SPI_I2S_SendData其实是往数据寄存器写值写完后硬件立刻开始发送同时 MISO 上的数据被采样到接收寄存器所以每次发送前要等待TXE标志发送后等待RXNE标志再读接收寄存器。这里发送的数据是无意义的 0x0000真正的目的是产生 SCLK 时序。返回值设计为 0 成功负值为错误码。-1表示传入缓冲区长度不足-2表示 BUSY 超时。在主循环里可以根据返回值打印错误信息避免出现全零数据时无从排查。4.3 关键参数配置量程、过采样和采样率AD7606 的量程由 RANGE 引脚决定过采样由 OS[2:0] 决定。下表是常用配置和对应特性RANGE 引脚模拟输入范围1 LSB 对应电压接 GND±5V10V / 65536 ≈ 152.6µV接 VDRIVE±10V20V / 65536 ≈ 305.2µVOS[2:0]过采样倍数最大吞吐率典型000无200kSPS0012 倍100kSPS0104 倍50kSPS0118 倍25kSPS10016 倍12.5kSPS10132 倍6.25kSPS11064 倍3.125kSPS在驱动代码里设置 OS 引脚很简单比如GPIO_SetBits(GPIOB, GPIO_Pin_12);代表 OS21。但要记住OS 引脚电平必须在释放复位前设置好否则过采样倍数不确定。采样率由 CONVST 的触发频率决定如果只是主循环里丢下降沿实际采样率会随着指令执行长度波动要精确 200kSPS 必须用定时器产生稳频的 CONVST 信号这部分在下一章细说。5. 驱动优化定时器触发 CONVSTBUSY 中断和 DMA 搬数据5.1 用定时器产生精确采样时钟软件触发 CONVST 适合读取直流或慢变信号但测量交流信号时需要等间隔采样否则重建波形会看到频率抖动。常见做法是把 TIM1 的 CH1 输出 PWM 波形接到 AD7606 的 CONVST 引脚用定时器周期决定采样率。比如要 200kSPS就把 PWM 频率设成 200kHz占空比 50%TIM1 的计数时钟是 72MHz所以周期寄存器值就是 360。void AD7606_Timer_Trigger_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); TIM_TimeBaseStructure.TIM_Period 360 - 1; // 200kHz 采样率 TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_Pulse 180 - 1; // 50% 占空比 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_CtrlPWMOutputs(TIM1, ENABLE); // 高级定时器必须开主输出 TIM_Cmd(TIM1, ENABLE); }这段代码把 TIM1_CH1 接到 PA8PA8 复用映射到 TIM1_CH1。如果板子上 PA8 被占用也可以把 CH1 重映射到其他端口但对于 C8T6 只能用 PA8。PWM 模式 1 下计数小于比较值时输出高电平因此 CONVST 的上升沿出现在计数归零时下降沿出现在比较匹配时。AD7606 需要的是下降沿触发所以用 PWM1 模式刚好合适。TIM_CtrlPWMOutputs(TIM1, ENABLE)是高级定时器特有的主输出使能漏掉这行 PWM 引脚不会有波形。采样率改起来也很方便在TIM_Period里填72000000 / target_sps - 1即可注意 target_sps 不要超过 200000。下表给出常用配置目标采样率TIM_Period 设置值72MHz 时钟是否支持 8 通道连续 DMA 读取200kSPS360-1是需 18MHz SCLK100kSPS720-1是可降 SPI 时钟50kSPS1440-1是稳定性更好10kSPS7200-1是几乎无压力5.2 BUSY 下降沿中断与 SPI 双 DMA 读取定时器触发转换后主循环去轮询 BUSY 会浪费大量 CPU。更好的方案是把 BUSY 接到一个外部中断输入在下降沿中断里启动 SPI 的 DMA 接收让外设直接把 8 个通道数据搬到内存。DMA 使用两路通道一路把内存中固定的 0x0000 循环发送到 SPI1_DR 以产生 SCLK另一路把 SPI1_DR 接收到的数据搬到 ADC 缓冲区。外设DMA 通道方向模式说明SPI1_TXDMA1_Channel2内存到外设循环模式发送 0产生 SCLKSPI1_RXDMA1_Channel3外设到内存普通模式接收 8 个通道数据TX DMA 的源地址要指向一个内容全部为 0 的uint16_t数组目标地址是SPI1-DR。RX DMA 的初始化如下void AD7606_DMA_Init(uint16_t *buf) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 8; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStructure); }注意 SPI1_RX 在 DMA1 上固定使用通道 3SPI1_TX 是通道 2切勿配错。DMA_Mode_Normal表示传输 8 个半字后自动停止要在下一次中断里重新启动。buf必须是uint16_t数组并且注意对齐。BUSY 的外部中断服务函数这样写void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) ! RESET) { AD7606_CS_LOW(); // 允许 AD7606 移出数据 DMA_Cmd(DMA1_Channel3, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel3, 8); DMA_Cmd(DMA1_Channel3, ENABLE); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_RX, ENABLE); EXTI_ClearITPendingBit(EXTI_Line1); } }为什么先DMA_Cmd(DISABLE)再重新使能因为 DMA 在 Normal 模式下传输结束后当前数据计数器变成 0必须把计数器重新设回 8 才能进行下一次传输。如果不做这一步第二次中断进来时 DMA 不会工作。TX 循环 DMA 在初始化后一直运行所以 SCLK 始终存在中断里只需要重新触发 RX DMA 就能抓住当前通道的数据。5.3 双缓冲区持续采样单缓冲区在 DMA 传输完成中断里做数据搬运时主循环如果想读同一份数据可能读了一半被 DMA 覆盖。常见的解法和串口双缓冲一样准备两个 8 数据的缓冲区DMA 只操作其中一个另一个给应用层读取两块交替使用。这个方案在 FreeRTOS 里也适用DMA 传输完成中断里把满缓冲区地址放到队列任务从队列取出后做滤波或傅里叶变换。注意 DMA 的源地址永远是SPI1-DR目标地址在切换时要通过DMA_Cmd(DISABLE)后修改DMA_MemoryBaseAddr再重新使能。这样在 200kSPS、8 通道的前提下CPU 只需要在每次 DMA 完成时处理一次数据占用率远低于逐位轮询。6. 验证驱动是否正常逻辑分析仪波形与串口数值检查6.1 用逻辑分析仪验证时序如果代码写完但读回来的数据不对第一步不是调算法而是用逻辑分析仪看 SCLK、CS、BUSY、DOUTA 四根线的实际时序。采样率要设到 100MHz 以上否则 18MHz 的 SCLK 抓不出细节。正常波形应该是CONVST 下降沿后 BUSY 立即拉高保持约 3.2us 后拉低CS 拉低后 DOUTA 输出 8 组 16 位脉冲每组之间有一个极短的空闲周期但 CS 在整个读取期间不能被拉高。如果看到 CS 在读取中间拉高检查主循环里是否有其他外设占用了 SPI1。现象可能原因调整方法读出数据整体右移一位SPI 采样沿不对把 CPOL/CPHA 同时取反DOUTA 只有一半脉冲CS 持续时间不够或 SCLK 频率过高降低 SPI 分频到 9MHzBUSY 一直为高CONVST 脉冲过窄或 AD7606 未复位检查 CONVST 是否真的翻转重新执行复位函数所有通道都是同一个值CS/DOUTA 接错或 MISO 没有断开其他设备用万用表量 PA6 和 DOUTA 之间通断6.2 用串口打印 8 通道电压值时序正常后把转换结果通过串口打印出来验证数值。CH340 驱动装好后在代码里重定向fputc然后用printf输出每个通道的有符号数和电压值。下面是最小验证程序// 串口1重定向printf int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; } void Print_Voltage(int16_t raw, float range) { float voltage raw * range / 32768.0f; // range 为 5.0 或 10.0 printf(raw%5d voltage%.3f V\r\n, raw, voltage); }这段浮点运算里的range对应量程的一半。如果 RANGE 接地量程是 ±5V所以满量程正数 32767 对应 5V计算时range传 5.0 即可。把 V1 输入端短接到 GND打印结果应该在 0 附近加一个 1.5V 干电池到 V1 与 GND 之间读到的电压值应该在 1.5V 左右误差几十毫伏以内。如果数值完全不对先用万用表确认输入电压真实值。6.3 常见坑SPI 相位、VDRIVE、PA11最后集中说几个容易踩的硬件和软件坑。SPI 相位错位是频率最高的坑。用逻辑分析仪看清 SCLK 与 DOUTA 变化沿的关系F103 必须在上升沿读取如果不是交换 CPOL/CPHA。VDRIVE 没接的时候AD7606 输出的高电平可能只有 1V 左右F103 识别不到表现为数据全是 0 或乱跳。VDRIVE 必须接到 3.3V并且与 F103 的 VDD 同源。复位释放前要设置好 OS[2:0] 和 RANGE否则过采样倍数和量程随机。我遇到过 32 倍过采样导致吞吐率只有 6.25kSPS 的案例排查半天才发现 OS 引脚悬空。如果 BUSY 或 CS 接到了 PA11/PA12在启用 USB 外设的工程里会出现上电后程序跑飞的问题。这个问题在 F103 的部分批次上有迹可循表现为 ST-LINK 下载正常但复位后卡死拔掉 USB 线才恢复。所以驱动 AD7606 时避开 PA11/PA12把它们留给 USB 专用引脚。一个有用的自检技巧把 AD7606 的 REFIN/REFOUT 引脚2.5V 基准输出用短跳线接到 V1 输入连续读取 100 次求平均值如果结果落在 16300 ± 100 范围内说明 SPI 相位、符号转换和量程配置都正确。这个范围对应 2.5V 电压在 ±5V 量程下的理论值 16384 附近偏差主要来自基准精度和输入噪声。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价