资讯详情

STM32F103驱动AD7606:并行读取与采样率优化实践

📅 2026/9/11 9:25:53 | 华诺云谱 👁 阅读
STM32F103驱动AD7606:并行读取与采样率优化实践
简介压缩包内是一套基于STM32F103正点原子战舰V3开发板的AD7606高精度ADC采集工程适合嵌入式初学者与需要多通道同步采样的开发者参考。程序基于Keil5环境编写核心包含STM32F103的初始化配置、AD7606驱动及SPI串行通信实现可完成16位、8通道模拟信号的同步采集与数据处理。压缩包共173个文件以C源码、H头文件、O目标文件和调试工程配置为主辅以HEX烧录文件与MAP映射表整体约3.55MB目录结构清晰便于直接打开工程查看调用关系。当前已有3506人学习使用工程内还附带LCD显示、定时器、ADC、I2C和CAN等标准外设驱动便于对照学习STM32外设编程。通过这套代码读者可以快速理清AD7606与STM32F103的接线与通信流程掌握多通道高精度数据采集的初始化、读取和数据处理方法并在此基础上迁移到其他类似ADC项目中。1. STM32F103 读 AD7606一个“能用”但经常跑不满速的采集需求把“基于 STM32F103 的 AD7606 采集程序”拆开背后是一个很具体的场景用一片 72MHz 的 M3 同时采集 8 路模拟量而且每路的采样时刻必须严格对齐。电力监测要看三相电压电流的相位差振动分析要看多通道之间的时间一致性普通 SPI 接口的逐通道转换 ADC 做不了这件事AD7606 这类 8 通道同步采样 ADC 才是常规选择。正点原子开发板上常见的例程大多用 GPIO 模拟并行时序能采到数据但很多人改完引脚、换完通道读出来要么全 0xFFFF要么 8 个通道错位要么采样率提不上去。问题往往不在芯片本身而在没分清 AD7606 的并行、串行、字节三种读取方式对时序的不同要求。这篇按一线工程师会用的顺序写先讲接口选型和接线再给最小工程代码接着讨论采样率和 DMA 的边界最后收在几个真正影响数据质量的细节上。新手照步骤能跑通已经写过驱动的人也能从排错部分找到自己踩过的坑。2. AD7606 与 STM32F103 的接口选型并行、串行还是 FSMC2.1 AD7606 的三种读取方式与速率差异AD7606 是 5V 供电的 16 位 8 通道同步采样 ADC内部自带输入箝位保护、一阶抗混叠滤波器和参考电压模拟前端省掉不少运放电路。数字读取接口有三种16 位并行、串行 SPI 和 8 位字节模式。并行模式下一根 RD 下降沿取一路数据8 个下降沿读完 V1 到 V8串行模式需要 SCLK 逐位移出引脚省到 3 根但速度明显变慢字节模式是为老式 8 位 MCU 准备的对 F103 意义不大。对 STM32F103 来说并行模式通常是首选。F103 的 GPIO 数量足够DB[15:0] 加 CS、RD、BUSY、CONVST 总共不到 20 根线而且并行读取不需要等一串时钟读 8 路数据只要 8 次总线访问延迟可预期。如果引脚紧正点原子最小系统板上要同时挂 LCD 屏、外部 Flash 和按键才需要考虑串行模式省引脚。串行模式适合引脚受限或速率要求不高的场合但要注意它的每通道吞吐率会受 SCLK 频率限制使用中如果还同时跑 RTOS 任务调度读取延迟会直接影响有效采样率。2.2 正点原子板卡上最常用的接线方案拿到正点原子战舰或精英板最稳妥的做法是直接沿用例程里的 GPIO 模拟时序接线也就是把 DB[15:0] 接到一组连续的 GPIO 端口上比如 GPIOE 的低 16 位CS、RD、BUSY、CONVST 各用一个普通 IO。这颗芯片的数字输出电平由 VDRIVE 引脚决定VDRIVE 接 3.3V 时 DB 引脚输出 3.3V 逻辑不用考虑 5V 电平兼容问题可以直接连 F103 的 GPIO。一种常见的接法是STM32F103 引脚AD7606 引脚说明PE0 ~ PE15DB0 ~ DB1516 位并行数据PB0CS片选低有效PB1RD读信号下降沿取数PB2BUSY转换状态输出PB3CONVST A / B启动转换双通道需接一起3.3VVDRIVE数字接口电平5VAVCC / VCC模拟与数字电源V1 到 V8 接模拟输入输入范围有两种通过 RANGE 引脚选择 ±5V 或 ±10V默认拉低为 ±5V。采集 0~5V 单极性信号时需要把输入加上 2.5V 偏置否则负半周会测量不到。这一点在接入传感器前就要确认例程里往往不会替你想好。2.3 过采样率 OS[2:0] 引脚配置表AD7606 内置数字过采样滤波器通过 OS2、OS1、OS0 三个引脚的电平组合控制。过采样本质是把多次转换结果做均值滤波能压低噪声、提升有效位数但代价是输出速率下降。无过采样时每通道最大吞吐率约为 200kSPS开启过采样后实际输出速率等于最大吞吐率除以过采样倍率。OS2OS1OS0过采样率每通道输出速率000关闭200kSPS0012 倍100kSPS0104 倍50kSPS0118 倍25kSPS10016 倍12.5kSPS10132 倍6.25kSPS11064 倍3.125kSPS注意 111 这个组合在手册里是保留状态不要在代码里配进去。开启过采样后转换时间会变长BUSY 高电平持续时间不再是最初那几微秒而是按倍率拉长代码里等待 BUSY 变低的逻辑不需要改但计算实际采样周期时一定要把 OSR 算进去否则用串口调试助手观察到的数据率会和你预期的对不上。提示过采样滤波适合低频缓慢变化的模拟量比如温度、压力、电池电压。如果要采 5kHz 以上的振动信号过采样倍数超过 4 之后输出速率会降到 50kSPS 以下且滤波器会引入明显的群延迟这时反而应该关闭过采样靠外部硬件滤波。3. 搭建最小采集工程从正点原子模板到并行读取跑通3.1 工程拷贝与引脚冲突检查正点原子 F103 例程基于标准外设库 V3.5工程结构通常是 HARDWARE 目录下挂外设驱动、SYSTEM 目录放 delay/sys/usart。写 AD7606 驱动时先从例程里复制一个最小的跑马灯工程把 SYSTEM 三个文件夹复制过来再新建ad7606.c和ad7606.h这样 delay 和串口初始化都不用自己从零写。做拷贝后第一件事是查引脚冲突。正点原子战舰板上 LCD 占用了 FSMC 的 PD0~PD15 和一组控制线如果你把 AD7606 数据线也接到 PD 口上两个外设会互相干扰。精英板相对宽松但也要确认按键、LED、外部中断占用的引脚没有和你选的 AD7606 引脚重叠。查法很简单打开正点原子原理图 PDF搜索你计划用到的引脚编号顺手在代码注释里写清楚每个引脚的作用。有一个容易踩的坑是 PA11 和 PA12 这两个引脚。它们除了做普通 GPIO还复用为 USB 的 D-/D如果板卡设计时把 USB 座和你的 AD7606 控制线挤在一起初始化顺序不对会导致 USB 枚举失败。还有 F103 在 PA11 上有一个已知的设计注意点用作输入且外部无强驱动时加一个 10k 下拉电阻更稳排查数据漂移时可以留意一下。DAP 下载失败也经常和这个区域有关引脚被复用成 JTAG 功能时先确认 SWJ 配置是否被改过。3.2 GPIO 模拟并行读时序的最小代码AD7606 的并行读取时序并不复杂CONVST 给一个低脉冲启动转换BUSY 变高转换完成后 BUSY 拉低此时把 CS 拉低RD 每拉低一次就有一路 16 位数据出现在 DB 总线上按 V1 到 V8 的顺序依次读走。初始化代码只需要把用到的引脚配置为推挽输出或浮空输入。// 引脚宏定义按自己实际接线修改 #define AD7606_CS_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_0) #define AD7606_CS_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_0) #define AD7606_RD_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_1) #define AD7606_RD_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_1) #define AD7606_CONVST_LOW() GPIO_ResetBits(GPIOB, GPIO_Pin_3) #define AD7606_CONVST_HIGH() GPIO_SetBits(GPIOB, GPIO_Pin_3) static void AD7606_GPIO_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE); // 数据线 PE0~PE15 设为浮空输入读取外部总线电平 gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOE, gpio); // 控制线 PB0~PB3 推挽输出 gpio.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_3; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, gpio); // 默认状态CS 高、RD 高、CONVST 高 AD7606_CS_HIGH(); AD7606_RD_HIGH(); AD7606_CONVST_HIGH(); }控制线配置成 50MHz 推挽输出是为了让 CONVST 和 RD 的上升沿、下降沿足够陡减少建立时间不足导致的误触发。数据线用浮空输入因为 AD7606 在 RD 下降沿之后会主动驱动 DB 总线浮空输入不存在内部上拉和芯片输出打架的问题。如果你读出来的某个通道固定是 0x0000 或 0xFFFF先检查数据线有没有虚焊再看 RD 时序里等待时间是否够。3.3 8 通道循环读取与 BUSY 判断读取函数要做两件事启动转换、等 BUSY 结束后循环读 8 路数据。有人用延时替代 BUSY 判断这在环境噪声小、时序固定时能跑但换一片 AD7606 或改了过采样配置后转换时间会变化延时算不准就容易读到上一轮的数据。只要接线允许一定要把 BUSY 引脚接出来。// 读取一次 8 通道数据结果存入 adc_buf void AD7606_Read8Ch(uint16_t *adc_buf) { uint8_t i; // 启动转换 AD7606_CONVST_LOW(); delay_us(1); // 低电平至少保持 25ns这里放宽到 1us AD7606_CONVST_HIGH(); // 等待 BUSY 变低表示转换完成 while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) Bit_SET); // 依次读取 8 路数据 AD7606_CS_LOW(); for (i 0; i 8; i) { AD7606_RD_LOW(); // RD 下降沿后数据总线更新 delay_us(1); // 等待数据建立 adc_buf[i] GPIO_ReadInputData(GPIOE); // 读取 16 位并行数据 AD7606_RD_HIGH(); } AD7606_CS_HIGH(); }BUSY 是只读输入不能当输出配置初始化时保持浮空输入即可。GPIO_ReadInputDataBit 读的是 IDR 寄存器不受输出配置影响。读取顺序严格按 V1、V2……V8对应 AD7606 芯片手册里的通道顺序。如果你发现数据整体左移了一个通道比如 V2 的数据出现在 buf[0]通常是启动转换到第一次 RD 之间间隔太短数据总线还没稳定成 V1 的值把 CS 拉低前的延时从 1us 加到 3us 再试。代码里的delay_us(1)是一个折中值F103 主频 72MHz 时 1us 足够 AD7606 的数据建立时间手册典型值几十纳秒级别。RD 低电平保持时间也不要太短GPIO 翻转本身有几纳秒到几十纳秒的延迟留 1us 余量换稳定性对最高 200kSPS 的吞吐率影响可以忽略。4. 采样率、DMA 与实时性从“能读”到“读得快”4.1 过采样配置对输出速率和有效位的影响在实际项目中很多人打开例程后第一个问题是怎么调采样率。AD7606 本身没有采样率寄存器采样率完全由 CONVST 脉冲的频率决定。也就是说控制采样率的上位逻辑在 MCU 端。用 GPIO 翻转加延时的方式产生 CONVST软件延时的抖动会造成采样间隔不均匀进而让频谱分析里出现杂散分量。目标采样率推荐触发方式抖动水平适用场景1kSPS 以下GPIO 延时翻转较大温度、压力等缓变信号1kSPS ~ 20kSPS定时器 PWM 触发很小电机振动、电流波形20kSPS 以上定时器 PWM 或外部信号很小电力暂态、高频振动过采样倍数也要和采样率联动。软件运行主循环里先启动转换、等 BUSY、再读数这个过程本身要占用几百个时钟周期如果目标采样率高于 50kSPS建议把读取动作拆到中断或 DMA 里避免主循环里的其他任务阻塞采集流程。一个常见配置是过采样 8 倍、目标输出 25kSPS这时 CONVST 频率为 25kHz每次转换期间 MCU 有约 40us 空闲足够做通道标定或数据打包。4.2 用定时器 PWM 触发 CONVST让采样率稳定下来的常规做法是定时器输出 PWM 接到 CONVST 引脚。F103 的定时器时钟来自 APB172MHz 主频下定时器预分频器设置为 72 减 1得到 1MHz 计数时钟再设置自动重载寄存器 ARR就能精确控制 CONVST 频率。这样得到的采样率由硬件时钟决定不随主循环代码变动而漂移。void TIM3_PWM_Init(uint16_t arr, uint16_t psc) { GPIO_InitTypeDef gpio; TIM_TimeBaseInitTypeDef tim; TIM_OCInitTypeDef oc; // 初始化引脚TIM3 CH1 默认映射到 PA6 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); gpio.GPIO_Pin GPIO_Pin_6; gpio.GPIO_Mode GPIO_Mode_AF_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio); // 72MHz 分频后得到 1MHz 计数时钟 tim.TIM_Prescaler psc; tim.TIM_CounterMode TIM_CounterMode_Up; tim.TIM_Period arr; tim.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, tim); // PWM 模式 1输出一个短低脉冲作为 CONVST 下降沿 oc.TIM_OCMode TIM_OCMode_PWM1; oc.TIM_OutputState TIM_OutputState_Enable; oc.TIM_Pulse arr / 2; oc.TIM_OCPolarity TIM_OCPolarity_Low; TIM_OC1Init(TIM3, oc); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_Cmd(TIM3, ENABLE); }调用示例TIM3_PWM_Init(99, 71)计数时钟为 1MHzARR 为 99PWM 频率为 1MHz 除以 100即 10kHz对应 10kSPS 采样率。PWM 极性配置成TIM_OCPolarity_Low后输出低电平宽度正好是 PWM 脉宽AD7606 手册要求 CONVST 低电平至少 25ns这里脉冲宽度 50us完全满足。注意 psc 参数是 71 而不是 72因为定时器计数从 0 开始实际分频系数是 psc 加 1。PWM 触发取代 GPIO 翻转之后主循环里只做 BUSY 等待和读取。如果你用 ADS 系 ADC 习惯了 SPI 读取这里会明显感受到差异AD7606 一旦启动转换8 个通道的模拟采样在同一个时刻完成读取只是把转换结果搬回来不存在通道间的时间偏移。4.3 DMA 读 FSMC 的边界与可行性能不能用 DMA 把 8 通道数据直接搬进内存代码上可做但要知道 F103 的 DMA 触发机制对 FSMC 支持有限。FSMC 不是 DMA 的外设触发源不能像 SPI、串口那样由硬件事件自动发起传输。常见的绕法是配置 DMA 为 memory-to-memory 模式把源地址指向 FSMC 的地址空间软件触发一次传输DMA 会以最快的总线速度连续读 8 个字。// 以 memory-to-memory 方式从 FSMC 地址连续读 8 个 16 位数据 DMA_InitTypeDef dma; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma.DMA_PeripheralBaseAddr (uint32_t)0x60000000; // FSMC Bank1 dma.DMA_MemoryBaseAddr (uint32_t)adc_buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize 8; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; // 始终访问同一地址 dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; dma.DMA_Mode DMA_Mode_Normal; dma.DMA_Priority DMA_Priority_High; dma.DMA_M2M DMA_M2M_Enable; // memory-to-memory 模式 DMA_Init(DMA1_Channel1, dma); DMA_Cmd(DMA1_Channel1, ENABLE);这里的 0x60000000 是 FSMC Bank1 的起始地址前提是 AD7606 的数据线连接到了 FSMC 的 D0~D15RD 接到了 FSMC 的 NOE且 CS 固定拉低。每次 DMA 对 0x60000000 发起读访问FSMC 都会对外设产生一次完整的 NOR/SRAM 读时序效果等同手动拉低 RD 再读数据。要注意DMA_M2M_Enable模式下DMA 通道不监听外设请求是一次性传输想要连续采集需要每次转换完成后再触发一次。另一个限制是 memory-to-memory 模式的总线优先级较高如果同时有串口中断收发DMA 占总线时间过长会影响中断响应所以这个方案更适合转换速率超过 50kSPS 的高吞吐场合低速采集完全没必要。5. 真正影响采集质量的 4 个细节5.1 CONVST 低电平脉宽不是越宽越好AD7606 手册给出 CONVST 低电平最小保持时间约 25ns但很多例程里用 GPIO 翻转脉宽做到几十微秒甚至上百微秒。这样做的隐患在于CONVST 高电平到下一次低电平时芯片内部采样保持电路会重新跟踪输入信号脉宽过长会让采样时刻和 CONVST 下降沿之间的关系变得不精确。用定时器 PWM 时把脉宽设置在 1us 到 10us 之间既能保证触发可靠又不会拖慢采样节拍。5.2 VDRIVE 与 5V 供电的电压关系AD7606 的 AVCC 和 VCC 供电范围是 4.75V 到 5.25V而 VDRIVE 决定数字接口输出高电平。VDRIVE 如果悬空芯片上电后 DB 总线可能输出 5V 电平直接连 F103 的 3.3V GPIO 虽然大多数引脚是 5V 容忍但总线和长走线时的串扰会明显变差。把 VDRIVE 接 3.3V数字输出以 3.3V 为高电平整个数字链路工作在同一个电平域数据读取更稳定。5.3 基准电压去耦与外部基准注入内部基准默认输出 2.5VREFIN/REFOUT 引脚上必须并联去耦电容典型值 100nF 加 10uF电容位置要尽量靠近引脚。用外部高精度基准时需要把 REFIN/REFOUT 引脚与内部基准断开在电路上设计成可配置的选项不能直接把外部基准怼到 REFIN 上内部基准会对外部源形成负载导致基准值被拉低。判断基准是否正常可以在不上电时量 REFIN/REFOUT 对地电阻正常应呈高阻如果接近几十欧姆说明内部基准和外部源有冲突。5.4 用 BUSY 和 CS 抓时序验证真实采样率写完之后不要只看数据用逻辑分析仪抓四个信号CONVST、BUSY、CS、RD。验证点有三项CONVST 频率和设定值误差是否在 1% 以内BUSY 高电平持续时间是否随过采样倍数按比例变化每次 CONVST 下降沿之后第一个 RD 下降沿到最后一个 RD 下降沿之间是否有数据总线变化。看到 RD 下降沿与 BUSY 下降沿的间隔稳定在一个固定值说明转换已经完成此时启动下一次 CONVST 才安全。稳定之后再用串口调试助手把八个通道的波形打印出来通道间不应有可观察到的相位差这样整个采集链路才算真正跑通。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。