Peixu 发表于 2025-5-21 01:37

基于APM32F402的多通道 ADC 同步采样系统设计与实现

本帖最后由 Peixu 于 2025-5-30 11:59 编辑

APM32F402 系列 MCU 的双 ADC 同步采样方案,通过注入通道机制实现三路模拟信号的高精度同步采集。

一、系统设计:
实现 ADC1 和 ADC2 的同步触发,消除通道间采样时间差
利用注入通道的高优先级特性,确保关键信号优先处理
配置定时器触发源,实现精确的采样周期控制
优化中断处理流程,提高系统实时响应能力

二、硬件架构详解
2.1 核心芯片选型
本文选用极海半导体 APM32F402 系列 MCU,该芯片具备以下关键特性:
双 ADC 模块,支持同步采样模式
高达 120MHz 的系统主频,提供强大运算能力
丰富的定时器资源,支持多种触发模式
12 位 ADC 分辨率,采样率可达 1MHz

2.2 引脚分配与信号连接



模拟输入部分:
PA2 (ADC1_CH2):连接第 1 路模拟信号源
PA3 (ADC2_CH3):连接第 2 路模拟信号源
PA4 (ADC2_CH4):连接第 3 路模拟信号源

触发控制部分:
TMR1_TRGO:定时器 1 的触发输出,连接至 ADC 触发输入

通信接口部分:
PB10 (USART3_TX):串口发送,用于调试信息输出
PB11 (USART3_RX):串口接收,可扩展为上位机命令接收

三、软件实现详解
3.1 双 ADC 同步采样配置
软件设计的核心是配置双 ADC 的同步工作模式,实现三路信号的同步采集。关键代码如下:
void ADC_Init(void)
{
    // 1. 使能GPIO和ADC时钟
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1 | RCM_APB2_PERIPH_ADC2);

    // 2. 配置模拟输入引脚
    GPIO_Config_T GPIO_ConfigStruct;
    GPIO_ConfigStructInit(&GPIO_ConfigStruct);
    GPIO_ConfigStruct.mode = GPIO_MODE_ANALOG;
    GPIO_ConfigStruct.pin = GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4;
    GPIO_Config(GPIOA, &GPIO_ConfigStruct);

    // 3. 配置ADC1
    ADC_Config_T ADC_ConfigStruct;
    ADC_Reset(ADC1);
    ADC_ConfigStructInit(&ADC_ConfigStruct);
    ADC_ConfigStruct.mode = ADC_MODE_INJEC_SIMULT;// 注入同步模式
    ADC_ConfigStruct.scanConvMode = ENABLE;         // 扫描模式
    ADC_ConfigStruct.continuousConvMode = DISABLE;// 单次转换模式
    ADC_ConfigStruct.externalTrigConv = ADC_EXT_TRIG_CONV_NONE;
    ADC_ConfigStruct.dataAlign = ADC_DATA_ALIGN_RIGHT;
    ADC_ConfigStruct.nbrOfChannel = 3;
    RCM_ConfigADCCLK(RCM_PCLK2_DIV_6);// ADCCLK = 120MHz/6 = 20MHz
   
    // 4. 配置ADC1注入通道
    ADC_ConfigInjectedSequencerLength(ADC1, 1);
    ADC_ConfigInjectedChannel(ADC1, ADC_CHANNEL_2, 1, ADC_SAMPLETIME_13CYCLES5);
    ADC_ConfigExternalTrigInjectedConv(ADC1, ADC_EXT_TRIG_INJEC_CONV_TMR1_TRGO);
   
    // 5. 配置ADC2注入通道
    ADC_Reset(ADC2);
    ADC_Config(ADC2, &ADC_ConfigStruct);
    ADC_ConfigInjectedSequencerLength(ADC2, 2);
    ADC_ConfigInjectedChannel(ADC2, ADC_CHANNEL_3, 1, ADC_SAMPLETIME_13CYCLES5);
    ADC_ConfigInjectedChannel(ADC2, ADC_CHANNEL_4, 2, ADC_SAMPLETIME_13CYCLES5);
    ADC_ConfigExternalTrigInjectedConv(ADC2, ADC_EXT_TRIG_INJEC_CONV_TMR1_TRGO);

    // 6. 使能中断并启动ADC
    ADC_EnableInterrupt(ADC1, ADC_INT_INJEOC);
    ADC_EnableInterrupt(ADC2, ADC_INT_INJEOC);
    NVIC_EnableIRQRequest(ADC1_2_IRQn, 0, 0);
   
    // 7. ADC校准与启动
    ADC_Enable(ADC1);
    ADC_ResetCalibration(ADC1);
    while (ADC_ReadResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_ReadCalibrationStartFlag(ADC1));
   
    ADC_Enable(ADC2);
    ADC_ResetCalibration(ADC2);
    while (ADC_ReadResetCalibrationStatus(ADC2));
    ADC_StartCalibration(ADC2);
    while (ADC_ReadCalibrationStartFlag(ADC2));
   
    // 8. 启动注入转换
    ADC_EnableSoftwareStartInjectedConv(ADC1);
    ADC_EnableSoftwareStartInjectedConv(ADC2);
}
3.2 定时器触发配置
定时器 TMR1 配置为中心对齐模式,生成周期性触发信号:
void TMR_Init(void)
{
    // 1. 使能定时器和GPIO时钟
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_TMR1);
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA | RCM_APB2_PERIPH_GPIOB);

    // 2. 配置PWM输出引脚(可用于电机控制等应用)
    GPIO_Config_T gpioConfig;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    gpioConfig.mode = GPIO_MODE_AF_PP;
    gpioConfig.pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;// PWM输出引脚
    GPIO_Config(GPIOA, &gpioConfig);

    // 3. 配置定时器基本参数
    TMR_BaseConfig_T tmrBaseConfig;
    tmrBaseConfig.countMode = TMR_COUNTER_MODE_CENTERALIGNED1;// 中心对齐模式
    tmrBaseConfig.clockDivision = TMR_CLOCK_DIV_1;
    tmrBaseConfig.period = 999;// 周期值
    tmrBaseConfig.division = 11999;// 预分频值
    tmrBaseConfig.repetitionCounter = 1;// 重复计数器
    TMR_ConfigTimeBase(TMR1, &tmrBaseConfig);

    // 4. 配置PWM输出模式(示例)
    TMR_OCConfig_T tmrOCConfig;
    tmrOCConfig.mode = TMR_OC_MODE_PWM2;
    tmrOCConfig.outputState = TMR_OC_STATE_ENABLE;
    tmrOCConfig.outputNState = TMR_OC_NSTATE_ENABLE;
    tmrOCConfig.pulse = 500;// 占空比50%
    TMR_ConfigOC1(TMR1, &tmrOCConfig);
    TMR_ConfigOC2(TMR1, &tmrOCConfig);
    TMR_ConfigOC3(TMR1, &tmrOCConfig);

    // 5. 配置触发输出(关键步骤)
    TMR_SelectOutputTrigger(TMR1, TMR_TRGO_SOURCE_UPDATE);// 使用更新事件作为触发源

    // 6. 配置死区时间(用于互补PWM输出)
    TMR_BDTConfig_T TIM_BDTRStruct;
    TIM_BDTRStruct.deadTime = 0x1D;// 约2μs死区时间
    TIM_BDTRStruct.BRKState = TMR_BRK_STATE_DISABLE;
    TMR_ConfigBDT(TMR1, &TIM_BDTRStruct);

    // 7. 使能定时器和PWM输出
    TMR_EnablePWMOutputs(TMR1);
    TMR_Enable(TMR1);
}

3.3 中断处理机制
ADC 转换完成后触发中断,在中断服务函数中读取采样值并进行处理:
void ADC1_2_IRQHandler(void)
{
    // 1. 标记中断进入(用于调试)
    GPIO_SetBit(GPIOC, GPIO_PIN_13);

    // 2. 处理ADC1注入通道转换完成事件
    if (ADC_ReadIntFlag(ADC1, ADC_INT_INJEOC) == SET)
    {
      uint16_t adc1_value = ADC_ReadInjectedConversionValue(ADC1, ADC_INJEC_CHANNEL_1);
      float voltage = (float)adc1_value / 4095 * 3.3;// 转换为电压值
      printf("ADC1 (PA2) Data: %d, Voltage: %.3f V\r\n", adc1_value, voltage);
      ADC_ClearIntFlag(ADC1, ADC_INT_INJEOC);// 清除中断标志
    }

    // 3. 处理ADC2注入通道转换完成事件
    if (ADC_ReadIntFlag(ADC2, ADC_INT_INJEOC) == SET)
    {
      uint16_t adc2_ch3 = ADC_ReadInjectedConversionValue(ADC2, ADC_INJEC_CHANNEL_1);
      uint16_t adc2_ch4 = ADC_ReadInjectedConversionValue(ADC2, ADC_INJEC_CHANNEL_2);
      printf("ADC2 (PA3) Data: %d, Voltage: %.3f V\r\n", adc2_ch3, adc2_ch3*3.3/4095);
      printf("ADC2 (PA4) Data: %d, Voltage: %.3f V\r\n", adc2_ch4, adc2_ch4*3.3/4095);
      ADC_ClearIntFlag(ADC2, ADC_INT_INJEOC);// 清除中断标志
    }

    // 4. 标记中断处理完成
    GPIO_ResetBit(GPIOC, GPIO_PIN_13);
}
四、测试结果
定时器 TMR1 的计数频率:120MHz / (11999+1) = 10kHz
实际采样频率:10kHz × 2 = 20kHz
每个 ADC 通道的采样时间:1/20kHz = 50μs

#申请原创#
@21小跑堂

发光的梦 发表于 2025-9-9 09:03

谢谢 楼主分享ADC注入采样的示例。
之前只是听说过

瞌睡虫本虫 发表于 2025-10-1 18:34

感谢分享!APM32F402的双ADC同步采样方案在实际应用中确实可以提高数据采集的准确性

分形梦想家 发表于 2025-10-3 09:15

两个ADC都使用Timer1的触发事件来触发
页: [1]
查看完整版本: 基于APM32F402的多通道 ADC 同步采样系统设计与实现