news 2026/8/12 19:41:37

基于AT32F403A与AD9220实现双通道ADC高精度同步采样方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于AT32F403A与AD9220实现双通道ADC高精度同步采样方案

1. 项目缘起:当双通道同步采样成为刚需

在嵌入式信号采集领域,我们常常会遇到一个看似简单、实则棘手的需求:如何让两个独立的ADC(模数转换器)实现真正意义上的同步采样?这里的“同步”,指的是两个ADC的采样时钟边沿在时间上完全对齐,并且转换启动信号也严格同步,从而确保采集到的两路信号在时间轴上具有可比性。这个需求在电力系统谐波分析、电机控制、振动监测、声学阵列等场景下几乎是刚需。

最近我在一个工业振动监测项目中就遇到了这个问题。项目需要同时采集来自两个正交安装的加速度传感器的信号,用于分析设备的振动模态。传感器用的是AD9220,这是一款经典的12位、20MSPS高速ADC。最初,我尝试用MCU的两个SPI接口分别去控制两片AD9220,用软件触发启动转换。结果发现,由于软件指令执行存在微秒级的抖动,以及SPI总线调度延迟,两个通道的采样时刻根本无法对齐,导致后续的相位分析完全失真。

问题的核心在于,AD9220的采样由外部时钟(CLK)驱动,而转换结果的读取则依赖于MCU提供的读时钟(通常由SPI的SCK模拟)。要实现同步,必须确保两片ADC的CLK是同源、同相的,并且启动转换的时机要精确可控。经过一番选型和评估,我最终选用了AT32F403A这款MCU,并决定利用其高级定时器(TMR)的输出比较功能来产生高精度的同步时钟,再结合DMA(直接存储器访问)来无延迟地搬运数据,从而彻底解决同步难题。这套方案不仅精度高,而且几乎不占用CPU资源,下面我就把具体的实现思路、关键配置和踩过的坑分享给大家。

2. 硬件架构与核心芯片选型解析

在动手写代码之前,理清硬件连接和芯片特性是成功的第一步。这个方案的核心是让MCU的一个硬件资源同时驱动两片ADC,并高效地取回数据。

2.1 主角登场:AT32F403A与AD9220

我选择的MCU是雅特力科技的AT32F403A,它基于ARM Cortex-M4内核,主频高达240MHz。我之所以看中它,除了性能强劲外,更关键的是其丰富且灵活的高级定时器(TMR)和DMA控制器。特别是TMR1/TMR8,支持多种输出模式,可以非常方便地生成指定频率和占空比的时钟信号。

传感器端的ADC是ADI公司的AD9220。这是一款单通道、12位分辨率、最高20MSPS采样率的逐次逼近型(SAR)ADC。它有几个关键引脚需要我们重点关注:

  • CLK (Pin 1):转换时钟输入。ADC在此时钟的上升沿启动采样与转换。这是实现同步的生命线,必须由MCU提供精准、同步的时钟。
  • OTR (Pin 21):输出数据就绪信号(低电平有效)。当一次转换完成,数据锁存到输出寄存器后,此引脚会拉低,提示外部控制器可以读取数据了。
  • D11-D0 (Pin 9-20):12位并行数据输出总线。
  • SENSE (Pin 28):模式选择。接地表示选择并行输出模式。

AD9220的工作流程很简单:在CLK上升沿,它开始对模拟输入进行采样并启动转换;经过固定的转换时间后,数据准备好,OTR引脚变低;外部控制器需要在下一个CLK上升沿到来之前,将数据读走。

2.2 同步采样的硬件连接图

要让两片AD9220(以下简称ADC1和ADC2)同步,硬件上必须确保它们的CLK来自同一个源,并且相位一致。最直接的方法就是用MCU的一个引脚同时驱动两片ADC的CLK。但这里有个细节:ADC的CLK输入有电容负载,直接并联到同一个GPIO可能会造成时钟边沿变缓,在高速时(比如10MHz以上)可能导致时序问题。稳妥的做法是,使用一个缓冲器(如74HC125)来驱动,或者确保MCU的GPIO驱动能力足够强(AT32的GPIO驱动能力不错,在10MHz以内直接驱动两片ADC通常没问题)。

我的连接方案如下:

  1. 同步时钟生成:使用AT32的TMR1通道1(PA8引脚)配置为PWM输出模式1,产生一个固定频率(例如5MHz)的方波。这个信号同时连接到两片AD9220的CLK引脚。
  2. 数据就绪信号连接:两片ADC的OTR引脚分别连接到MCU的两个外部中断引脚(例如ADC1_OTR -> PE0, ADC2_OTR -> PE1)。OTR的下跳沿将作为触发DMA传输的“请求源”。
  3. 数据总线连接:这是最占用引脚的部分。每片AD9220有12位并行数据线,两片就是24位。我选择使用MCU的GPIO端口A和端口B的高低位来分别承接。例如:
    • ADC1_D[11:0] -> PA[11:0]
    • ADC2_D[11:0] -> PB[11:0] 这样连接,当同时读取GPIOA和GPIOB的输入数据寄存器时,就能一次性获取两片ADC的共24位数据(实际上各12位),效率极高。
  4. DMA数据流:配置两个DMA数据流(或通道),分别服务于端口A和端口B的数据搬运。它们的触发源就分别对应上面两个OTR引脚产生的外部中断事件。

注意:在PCB布局时,连接到同一片ADC数据总线的GPIO引脚最好属于MCU的同一个端口,并且物理上连续。这样在使用DMA从GPIOx->IDR(输入数据寄存器)搬运数据时,效率最高,也避免了位拼接的麻烦。如果不得不使用不连续的引脚,则需要通过软件进行位操作,会稍微复杂一些。

3. 核心引擎:TMR输出时钟的精确配置

同步的基石是一个精准、稳定的公共时钟源。AT32的高级定时器(TMR1/8)是完成这项任务的绝佳选择。

3.1 定时器工作模式选择与参数计算

我们的目标是用TMR1的通道1输出一个5MHz的方波时钟。为什么是5MHz?因为AD9220在5MHz时钟下,采样率就是5MSPS,对于很多中频振动信号已经足够。当然,你可以根据需求调整,但不要超过ADC的最高额定时钟频率(AD9220是20MHz)。

TMR输出时钟的本质是一个PWM波。我们需要计算三个关键参数:定时器时钟源频率(TMR_CLK)、自动重装载值(ARR)和比较捕获值(CCR)。

首先,需要确定TMR1的输入时钟。AT32F403A的APB2总线时钟(APB2_CLK)在系统时钟为240MHz时,通常是120MHz。TMR1挂载在APB2上,当APB预分频系数不为1时,定时器时钟是APB时钟的2倍,因此TMR1_CLK = 240 MHz

接下来计算ARR和CCR值,以产生5MHz方波(占空比50%):

  • 期望的PWM频率PWM_Freq = 5 MHz
  • 定时器计数频率TMR_CLK = 240 MHz
  • 则计数周期Period = TMR_CLK / PWM_Freq = 240 / 5 = 48
  • 在向上计数模式下,ARR = Period - 1 = 47
  • 对于50%占空比,CCR = (ARR + 1) / 2 = 24

因此,配置TMR1->ARR = 47TMR1->CCR1 = 24

3.2 关键寄存器配置与代码实现

配置TMR1输出PWM,需要操作以下几个关键寄存器:

// 1. 使能TMR1时钟 RCC->APB2ENR |= RCC_APB2ENR_TMR1EN; // 2. 配置时基单元 TMR1->PSC = 0; // 预分频器为0,即不分频 TMR1->ARR = 47; // 自动重装载值,决定PWM频率 TMR1->CNT = 0; // 计数器清零 // 3. 配置通道1为PWM模式1输出 // 输出比较模式:PWM模式1 TMR1->CCMR1 |= (0b110 << TMR_CCMR_OC1M_Pos); // 输出极性:高电平有效 TMR1->CCER |= TMR_CCER_CC1E; // 设置比较值,决定占空比 TMR1->CCR1 = 24; // 4. 使能自动重装载预装载(ARPE)和主输出(MOE,针对高级定时器) TMR1->CCMR1 |= TMR_CCMR_OC1PE; TMR1->BDTR |= TMR_BDTR_MOE; // 高级定时器必须开启主输出 // 5. 启动定时器 TMR1->CR1 |= TMR_CR1_CEN;

配置完成后,用示波器测量PA8引脚,应该能看到一个非常干净的5MHz、3.3V方波。这个信号将作为两片AD9220的“心跳”。

实操心得:务必使用示波器验证输出时钟的质量。检查频率是否准确、幅值是否达到标准逻辑电平(3.3V)、上升/下降沿是否陡峭(一般应小于10ns)。不理想的时钟边沿是导致ADC采样误差甚至失败的常见原因。如果边沿过缓,可以考虑降低时钟频率,或者如前所述,增加一个时钟缓冲芯片。

4. 数据搬运管家:DMA请求的巧妙触发

时钟同步解决了采样时刻对齐的问题,接下来要解决数据搬运的实时性问题。如果使用CPU轮询或中断来读取24位并行数据,在5MSPS的速率下,CPU根本忙不过来,且会引入不可控的延迟。DMA是唯一的解决方案。

但如何触发DMA传输呢?我们需要一个与数据就绪严格同步的信号。AD9220的OTR引脚正是为此而生。

4.1 将外部中断映射为DMA请求源

AT32的DMA控制器功能强大,其请求源不仅可以来自外设(如SPI、UART的TX/RX),还可以来自外部中断线(EXTI)。这正是我们需要的:将ADC的OTR引脚下降沿产生的外部中断事件,作为DMA传输的触发信号。

配置步骤如下:

  1. 配置GPIO为输入:将连接OTR的PE0和PE1配置为浮空输入(或上拉输入,取决于OTR的驱动能力)。
  2. 配置外部中断(EXTI):将PE0和PE1连接到EXTI0和EXTI1中断线,并配置为下降沿触发。
  3. 关键一步:配置EXTI事件映射到DMA:这不是标准库函数直接提供的,需要操作特定寄存器。在AT32中,EXTI的EMR(事件屏蔽寄存器)和RTSR(上升沿触发选择寄存器)/FTSR(下降沿触发选择寄存器)共同决定了一个事件是否产生。当EXTI事件产生时,它可以触发中断,也可以触发事件。DMA请求关联的是事件,而非中断。 我们需要使能对应EXTI线的事件功能,并选择下降沿。
    // 使能EXTI0线的事件功能,下降沿触发 EXTI->EMR |= EXTI_EMR_EM0; EXTI->FTSR |= EXTI_FTSR_TR0; // 同理配置EXTI1 EXTI->EMR |= EXTI_EMR_EM1; EXTI->FTSR |= EXTI_FTSR_TR1;
  4. 配置DMA流:我们需要两个DMA流(例如DMA1_Stream0和Stream1)。
    • 外设地址:对于从GPIO读数,外设地址就是GPIO端口输入数据寄存器的地址,如(uint32_t)&GPIOA->IDR(uint32_t)&GPIOB->IDR
    • 存储器地址:指向我们定义的两个内存缓冲区adc1_buffer[]adc2_buffer[]
    • 数据宽度:外设和存储器都设置为半字(16位),因为GPIOx_IDR是32位寄存器,但我们只需要低16位(实际用了12位)。设置16位可以一次性搬运。
    • 传输模式:外设到存储器。
    • 循环模式:使能,这样当缓冲区填满后,DMA会自动回到开头重新开始,实现连续采集。
    • 外设增量:禁止(外设地址固定为GPIOA_IDR)。
    • 存储器增量:使能(每次传输后,存储器地址指向下一个数组元素)。
    • 触发源:这是核心!需要将DMA流的触发源配置为“EXTI线0”和“EXTI线1”。在AT32的DMA请求映射表中,可以查到EXTI线对应的请求编号。例如,EXTI0可能映射到DMA请求DMA_REQUEST_EXTI0。你需要查阅具体的《参考手册》来确认。
    // 假设DMA_REQUEST_EXTI0的值为x DMA1_Stream0->CR &= ~DMA_SxCR_CHSEL; // 先清除通道选择 DMA1_Stream0->CR |= (x << DMA_SxCR_CHSEL_Pos); // 设置触发源为EXTI0 // 使能DMA流 DMA1_Stream0->CR |= DMA_SxCR_EN;

4.2 双缓冲与数据对齐技巧

为了确保数据搬运不丢失,并且方便后续处理,我采用了“双缓冲”机制和位对齐操作。

双缓冲:我定义了两个大的缓冲区,例如每个2048点。DMA配置为循环模式,当它填满半个缓冲区(1024点)时,产生一个半传输完成中断(HTIE),填满整个缓冲区时产生传输完成中断(TCIE)。在中断服务程序里,我只需切换指针,去处理已经填满的那半个缓冲区,而DMA继续向另外半个缓冲区写入数据。这样实现了采集与处理的并行,避免了数据覆盖。

数据对齐:AD9220输出12位数据,我们按16位(半字)搬运。数据存储在GPIOx_IDR的低12位。在后续处理时,需要将读取到的16位数据与0x0FFF进行“与”操作,以屏蔽掉无用的高4位。

uint16_t raw_data = adc1_buffer[i] & 0x0FFF; // 获取有效的12位ADC值

对于两片ADC的数据,由于是同步采集的,adc1_buffer[i]adc2_buffer[i]就是同一时刻两路信号的采样值,保证了严格的同步性。

5. 系统集成与软件流程设计

硬件和驱动配置好后,需要一套清晰的软件流程来管理整个采集系统。

5.1 初始化序列

系统的初始化必须遵循一定的顺序,否则可能导致时钟或DMA工作异常:

  1. 系统时钟初始化:配置系统时钟、AHB、APB1、APB2总线时钟。
  2. GPIO初始化
    • 配置TMR1_CH1(PA8)为复用推挽输出。
    • 配置PA0-PA11、PB0-PB11为浮空输入(用于ADC数据总线)。
    • 配置PE0、PE1为浮空输入(用于OTR信号)。
  3. TMR1初始化:按第3章配置,产生5MHz PWM时钟。先不要启动(CEN=0)
  4. EXTI初始化:配置EXTI0和EXTI1为事件模式,下降沿触发。先不要使能
  5. DMA初始化:配置两个DMA流,设置好外设地址(GPIOA_IDR, GPIOB_IDR)、内存地址、数据长度、循环模式等。最关键的是设置正确的触发请求源(EXTI0/1)。初始化完成后使能DMA流(EN=1),此时DMA处于“等待触发”状态。
  6. 缓冲区与变量初始化:清零ADC数据缓冲区,初始化缓冲区索引、状态标志等。
  7. 启动序列:这是一个关键步骤,顺序错了会导致首批数据错误。
    • 第一步:启动TMR1(TMR1->CR1 |= TMR_CR1_CEN;)。此时同步时钟开始输出,但ADC尚未开始转换(因为需要第一个CLK上升沿)。
    • 第二步:短暂延时(至少一个时钟周期),确保时钟稳定。
    • 第三步:使能EXTI事件(EXTI->EMR |= ...)。一旦使能,下一个OTR下降沿就会触发DMA请求。
    • 此时,系统就进入了全自动采集状态。ADC在时钟驱动下开始转换,转换完成则OTR变低触发EXTI事件,EXTI事件立即触发DMA请求,DMA将GPIO端口的数据搬运到内存。整个过程无需CPU干预。

5.2 主循环与数据处理

主循环变得非常简洁,CPU被解放出来进行更复杂的信号处理:

while(1) { // 检查DMA半传输/传输完成标志 if(dma_half_buffer_ready_flag) { process_buffer(half_buffer_index); // 处理前半部分或后半部分缓冲区 dma_half_buffer_ready_flag = 0; // 可以在此进行FFT、滤波、特征提取等算法 } // 其他系统任务,如通信、状态监测等 system_idle_task(); }

6. 实测调试与常见问题排查

理论很美好,但调试过程总会遇到各种问题。以下是我在实现过程中遇到的一些典型问题及解决方法。

6.1 问题一:DMA毫无动静,缓冲区全是0

  • 现象:程序运行后,ADC缓冲区始终为0,示波器看OTR引脚有正常跳变。
  • 排查思路
    1. 检查时钟:首先用示波器确认TMR1输出的时钟是否准确到达两片ADC的CLK引脚。幅值、频率、占空比是否正常。
    2. 检查OTR信号:用示波器同时测量ADC的OTR引脚和CLK引脚。确认在CLK上升沿之后,经过一段转换时间(查AD9220手册),OTR是否确实产生了下降沿。
    3. 检查EXTI事件:这是最隐蔽的一环。EXTI事件是否成功产生?一个简单的验证方法是,将EXTI配置为中断模式(而不是事件模式),并在中断服务函数里翻转一个测试引脚。如果测试引脚能随OTR跳变,说明EXTI检测到了信号。如果没反应,检查GPIO输入配置、EXTI线路映射、上下拉电阻配置(OTR是开漏输出,可能需要上拉)。
    4. 检查DMA触发源映射:确认DMA_SxCR寄存器中的CHSEL字段是否设置正确,对应到了EXTI线的请求编号。这个编号需要查芯片数据手册的DMA请求映射表,非常容易配错。
    5. 检查DMA使能顺序:务必在使能EXTI事件之前就使能DMA流。如果顺序反了,第一个OTR事件可能发生在DMA准备好之前,从而被错过。

6.2 问题二:数据错位或跳动严重

  • 现象:能采集到数据,但数值不稳定,或者两路信号看起来不同步。
  • 排查思路
    1. 电源与地噪声:高速ADC对电源极其敏感。务必使用高质量的LDO为模拟部分供电,并在每个ADC的电源引脚附近放置足够容量的去耦电容(如10uF钽电容 + 0.1uF陶瓷电容)。模拟地和数字地单点连接。
    2. 时钟抖动:用示波器的余辉模式观察TMR输出的时钟,看边沿是否有抖动。如果抖动大,检查MCU的电源,或者尝试降低输出时钟频率。也可以考虑使用定时器的“重复计数器”功能来产生更稳定的时钟。
    3. 数据建立保持时间:虽然我们用了DMA,但从OTR下降沿到DMA实际读取GPIO数据,仍有极短的延迟。需要确保这个延迟不会让ADC的数据输出发生变化。AD9220的数据在OTR变低后会保持稳定,直到下一个CLK上升沿。只要我们的DMA在下一个CLK上升沿前完成读取即可。在5MHz时钟(周期200ns)下,这个时间窗口是充裕的。但如果时钟频率很高(如20MHz),就需要仔细评估DMA响应时间和GPIO读取时间。
    4. 缓冲区溢出:如果DMA搬运速度跟不上ADC产出数据的速度,会导致缓冲区被覆盖,数据错乱。计算一下:5MSPS,每个数据16位(2字节),那么数据率是10MB/s。AT32的DMA在系统总线不拥堵的情况下,搬运一个16位数据通常只需1-2个AHB时钟周期(约4-8ns),远小于采样间隔200ns,所以带宽完全足够。但如果CPU频繁访问被DMA使用的内存区域(尤其是通过Cache),可能会与DMA争抢总线带宽,造成偶尔的延迟。可以考虑将ADC缓冲区放在DTCM(如果芯片有)或者非Cacheable的内存区域。

6.3 问题三:如何验证同步精度?

这是项目的终极验证。一个有效的方法是:

  1. 输入同源信号:将同一个正弦波信号通过一个运放缓冲后,同时输入到两片AD9220。
  2. 采集数据:运行程序,采集一段数据。
  3. 计算互相关:对采集到的两路数字信号序列计算互相关函数。如果完全同步,互相关函数的峰值应该出现在零延迟点。如果存在固定的时间偏差,峰值会偏移,偏移的样本点数乘以采样间隔就是同步误差。
  4. 观察结果:在我的实测中,使用此方案,两路信号的同步误差在±1个采样时钟周期(200ns)以内,这主要取决于两片ADC芯片自身CLK到输出延迟(tOD)的一致性,以及PCB布线等长带来的微小差异,已经达到了硬件所能实现的极限精度。

通过这套基于AT32 TMR输出时钟和DMA外部触发请求的方案,我成功实现了两片AD9220的高精度、高实时性同步采样。整个系统运行稳定,CPU占用率极低,为后续复杂的实时信号处理算法留出了充足的计算资源。这个设计模式具有很强的通用性,可以扩展到更多片ADC的同步,或者与其他需要精密时钟同步的外设配合使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/12 19:37:38

科源制药子公司亮相2026西普会 共探AI时代产业增长新路径

8月12日&#xff0c;第十九届健康产业生态大会&#xff08;2026西普会&#xff09;在海南博鳌正式启幕。作为医药零售领域极具影响力的行业盛会&#xff0c;本届大会以“走进AI时代——健康产业的范式革命与路径选择”为年度主题&#xff0c;聚焦AI技术在科研、制造、医疗、营销…

作者头像 李华
网站建设 2026/8/12 19:37:18

深入解析Ubuntu软件包管理:apt、apt-get与dpkg的核心原理与实战指南

1. 项目概述&#xff1a;从“装软件”到理解系统基石在Linux世界里&#xff0c;尤其是Ubuntu及其衍生发行版中&#xff0c;安装、卸载、更新软件是每个用户从入门到精通都无法绕开的日常操作。新手们常常会接触到几个听起来相似却又让人困惑的命令&#xff1a;apt、apt-get、dp…

作者头像 李华
网站建设 2026/8/12 19:35:10

中国决策智能平台有哪些?代表厂商能力表现解析

内容来源&#xff1a;《2026爱分析DataAI厂商全景报告》发布时间&#xff1a;2026年7月发布机构&#xff1a;爱分析ifenxi关键词&#xff1a;决策智能平台、代表厂商、DataAI、AI产业研究、AI行业分析摘要&#xff1a;决策智能平台是连接数据资产与经营管理的重要应用层。爱分析…

作者头像 李华
网站建设 2026/8/12 19:33:18

全网详解 CTF 安全竞赛:赛事介绍、全部考点、参赛技术要求汇总

在网络安全领域&#xff0c;CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;是检验技术实力的 “试金石”&#xff0c;也是白帽黑客成长的 “练兵场”。对于刚接触网络安全的新手来说&#xff0c;CTF 既神秘又充满吸引力 —— 它不像传统考试那样侧重理论&…

作者头像 李华
网站建设 2026/8/12 19:26:11

输入只有经过应用、反馈和修正,才能转化为能力。

知识进入大脑&#xff0c;只是完成了“储存”&#xff1b;经过现实使用、错误暴露、持续调整&#xff0c;才完成了“能力化”。第一层&#xff1a;输入只是“原材料” 学习输入&#xff1a; 包括&#xff1a; 书籍&#xff1b;课程&#xff1b;文章&#xff1b;视频&#xff1b…

作者头像 李华
网站建设 2026/8/12 19:25:20

PKC 第 072 个开关:不领私聊利是的位置、验证方法与风险边界

&#x1f525; 个人主页&#xff1a; 杨利杰YJlio ❄️ 个人专栏&#xff1a; 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》 《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》 《超简单&#xff1a;用Python让Excel飞起来》…

作者头像 李华