尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

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

基于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采样率的逐次逼近型SARADC。它有几个关键引脚需要我们重点关注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通常没问题。我的连接方案如下同步时钟生成使用AT32的TMR1通道1PA8引脚配置为PWM输出模式1产生一个固定频率例如5MHz的方波。这个信号同时连接到两片AD9220的CLK引脚。数据就绪信号连接两片ADC的OTR引脚分别连接到MCU的两个外部中断引脚例如ADC1_OTR - PE0, ADC2_OTR - PE1。OTR的下跳沿将作为触发DMA传输的“请求源”。数据总线连接这是最占用引脚的部分。每片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位效率极高。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传输的触发信号。配置步骤如下配置GPIO为输入将连接OTR的PE0和PE1配置为浮空输入或上拉输入取决于OTR的驱动能力。配置外部中断EXTI将PE0和PE1连接到EXTI0和EXTI1中断线并配置为下降沿触发。关键一步配置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;配置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工作异常系统时钟初始化配置系统时钟、AHB、APB1、APB2总线时钟。GPIO初始化配置TMR1_CH1PA8为复用推挽输出。配置PA0-PA11、PB0-PB11为浮空输入用于ADC数据总线。配置PE0、PE1为浮空输入用于OTR信号。TMR1初始化按第3章配置产生5MHz PWM时钟。先不要启动CEN0。EXTI初始化配置EXTI0和EXTI1为事件模式下降沿触发。先不要使能。DMA初始化配置两个DMA流设置好外设地址GPIOA_IDR, GPIOB_IDR、内存地址、数据长度、循环模式等。最关键的是设置正确的触发请求源EXTI0/1。初始化完成后使能DMA流EN1此时DMA处于“等待触发”状态。缓冲区与变量初始化清零ADC数据缓冲区初始化缓冲区索引、状态标志等。启动序列这是一个关键步骤顺序错了会导致首批数据错误。第一步启动TMR1TMR1-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引脚有正常跳变。排查思路检查时钟首先用示波器确认TMR1输出的时钟是否准确到达两片ADC的CLK引脚。幅值、频率、占空比是否正常。检查OTR信号用示波器同时测量ADC的OTR引脚和CLK引脚。确认在CLK上升沿之后经过一段转换时间查AD9220手册OTR是否确实产生了下降沿。检查EXTI事件这是最隐蔽的一环。EXTI事件是否成功产生一个简单的验证方法是将EXTI配置为中断模式而不是事件模式并在中断服务函数里翻转一个测试引脚。如果测试引脚能随OTR跳变说明EXTI检测到了信号。如果没反应检查GPIO输入配置、EXTI线路映射、上下拉电阻配置OTR是开漏输出可能需要上拉。检查DMA触发源映射确认DMA_SxCR寄存器中的CHSEL字段是否设置正确对应到了EXTI线的请求编号。这个编号需要查芯片数据手册的DMA请求映射表非常容易配错。检查DMA使能顺序务必在使能EXTI事件之前就使能DMA流。如果顺序反了第一个OTR事件可能发生在DMA准备好之前从而被错过。6.2 问题二数据错位或跳动严重现象能采集到数据但数值不稳定或者两路信号看起来不同步。排查思路电源与地噪声高速ADC对电源极其敏感。务必使用高质量的LDO为模拟部分供电并在每个ADC的电源引脚附近放置足够容量的去耦电容如10uF钽电容 0.1uF陶瓷电容。模拟地和数字地单点连接。时钟抖动用示波器的余辉模式观察TMR输出的时钟看边沿是否有抖动。如果抖动大检查MCU的电源或者尝试降低输出时钟频率。也可以考虑使用定时器的“重复计数器”功能来产生更稳定的时钟。数据建立保持时间虽然我们用了DMA但从OTR下降沿到DMA实际读取GPIO数据仍有极短的延迟。需要确保这个延迟不会让ADC的数据输出发生变化。AD9220的数据在OTR变低后会保持稳定直到下一个CLK上升沿。只要我们的DMA在下一个CLK上升沿前完成读取即可。在5MHz时钟周期200ns下这个时间窗口是充裕的。但如果时钟频率很高如20MHz就需要仔细评估DMA响应时间和GPIO读取时间。缓冲区溢出如果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 问题三如何验证同步精度这是项目的终极验证。一个有效的方法是输入同源信号将同一个正弦波信号通过一个运放缓冲后同时输入到两片AD9220。采集数据运行程序采集一段数据。计算互相关对采集到的两路数字信号序列计算互相关函数。如果完全同步互相关函数的峰值应该出现在零延迟点。如果存在固定的时间偏差峰值会偏移偏移的样本点数乘以采样间隔就是同步误差。观察结果在我的实测中使用此方案两路信号的同步误差在±1个采样时钟周期200ns以内这主要取决于两片ADC芯片自身CLK到输出延迟tOD的一致性以及PCB布线等长带来的微小差异已经达到了硬件所能实现的极限精度。通过这套基于AT32 TMR输出时钟和DMA外部触发请求的方案我成功实现了两片AD9220的高精度、高实时性同步采样。整个系统运行稳定CPU占用率极低为后续复杂的实时信号处理算法留出了充足的计算资源。这个设计模式具有很强的通用性可以扩展到更多片ADC的同步或者与其他需要精密时钟同步的外设配合使用。
返回列表