基于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小跑堂
谢谢 楼主分享ADC注入采样的示例。
之前只是听说过 感谢分享!APM32F402的双ADC同步采样方案在实际应用中确实可以提高数据采集的准确性
两个ADC都使用Timer1的触发事件来触发
页:
[1]