STM32F030 多路ADC采样实现

一、核心配置要点(F030特有)

STM32F030 是基于 Cortex-M0 内核的低成本芯片,ADC 资源有限但够用:

  • ADC 分辨率:12位(可配置为 12/10/8/6位)
  • 通道数量 :最多 16 个外部通道(ADC_IN0~ADC_IN15)
  • 最大采样率:1 MHz(需合理配置时钟)
  • 关键限制 :ADC 时钟 不能超过 14 MHz(F030 特有约束)
关键参数 推荐值 说明
ADC 时钟源 PCLK/2 或 PCLK/4 确保 ≤14 MHz
采样时间 55.5 或 71.5 周期 高阻抗信号需更长采样时间
对齐方式 右对齐 便于数据解析
触发方式 软件触发 / 定时器触发 定时器触发更稳定

二、完整代码实现

2.1 头文件(adc.h

c 复制代码
#ifndef __ADC_H
#define __ADC_H

#include "stm32f0xx.h"

// 配置参数
#define ADC_CHANNEL_NUM    4       // 采样通道数量
#define ADC_BUF_LEN       16       // 每个通道采样深度
#define ADC_SAMPLE_FREQ   10000   // 采样频率 10kHz

// 全局缓冲区
extern __IO uint16_t ADC_ConvertedValue[ADC_CHANNEL_NUM][ADC_BUF_LEN];
extern __IO uint8_t ADC_ScanComplete;

// 函数声明
void ADC_GPIO_Init(void);
void ADC_DMA_Init(void);
void ADC_Configuration(void);
void ADC_StartContinuous(void);
uint16_t ADC_GetAverage(uint8_t ch);
float ADC_GetVoltage(uint8_t ch);

#endif /* __ADC_H */

2.2 源文件(adc.c

c 复制代码
#include "adc.h"
#include "delay.h"

// DMA缓冲区:通道 × 采样深度
__IO uint16_t ADC_ConvertedValue[ADC_CHANNEL_NUM][ADC_BUF_LEN];
__IO uint8_t ADC_ScanComplete = 0;

/**
  * @brief  ADC GPIO 初始化
  */
void ADC_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 1. 使能 GPIO 时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB, ENABLE);
    
    // 2. 配置 ADC 通道引脚(根据实际硬件修改)
    // 示例:PA0 -> ADC_IN0, PA1 -> ADC_IN1, PB0 -> ADC_IN8, PB1 -> ADC_IN9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/**
  * @brief  ADC DMA 初始化
  */
void ADC_DMA_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    // 1. 使能 DMA 时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    // 2. 配置 DMA 通道(ADC1 对应 DMA1_Channel1)
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = ADC_CHANNEL_NUM * ADC_BUF_LEN;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  // 循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    
    // 3. 使能 DMA
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

/**
  * @brief  ADC 配置
  */
void ADC_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;
    
    // 1. 使能 ADC 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    
    // 2. 配置 ADC 时钟(关键!F030 ADC 时钟 ≤14MHz)
    // 假设系统时钟 48MHz,APB 时钟 48MHz
    RCC_ADCCLKConfig(RCC_ADCCLK_PCLK_Div4);  // ADC 时钟 = 48/4 = 12MHz ✅
    
    // 3. ADC 基本配置
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;     // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward; // 从 CH0 向上扫描
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 4. 配置扫描通道(顺序很重要!)
    // 通道 0 -> 通道 1 -> 通道 8 -> 通道 9
    ADC_ChannelConfig(ADC1, ADC_Channel_0, ADC_SampleTime_55_5Cycles);
    ADC_ChannelConfig(ADC1, ADC_Channel_1, ADC_SampleTime_55_5Cycles);
    ADC_ChannelConfig(ADC1, ADC_Channel_8, ADC_SampleTime_55_5Cycles);
    ADC_ChannelConfig(ADC1, ADC_Channel_9, ADC_SampleTime_55_5Cycles);
    
    // 5. 使能 DMA 请求
    ADC_DMACmd(ADC1, ENABLE);
    
    // 6. ADC 校准(必须做!)
    ADC_GetCalibrationFactor(ADC1);
    
    // 7. 使能 ADC
    ADC_Cmd(ADC1, ENABLE);
    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY) == RESET);
}

/**
  * @brief  启动连续转换
  */
void ADC_StartContinuous(void)
{
    ADC_StartOfConversion(ADC1);
}

/**
  * @brief  获取某通道的平均值
  * @param  ch: 通道索引(0~3 对应配置的通道顺序)
  */
uint16_t ADC_GetAverage(uint8_t ch)
{
    uint32_t sum = 0;
    for(uint8_t i = 0; i < ADC_BUF_LEN; i++)
    {
        sum += ADC_ConvertedValue[ch][i];
    }
    return (uint16_t)(sum / ADC_BUF_LEN);
}

/**
  * @brief  获取电压值(单位:V)
  * @param  ch: 通道索引
  */
float ADC_GetVoltage(uint8_t ch)
{
    uint16_t adc_val = ADC_GetAverage(ch);
    return (float)adc_val * 3.3f / 4095.0f;  // 假设 VREF = 3.3V
}

2.3 主程序(main.c

c 复制代码
#include "stm32f0xx.h"
#include "adc.h"
#include "usart.h"
#include "delay.h"

int main(void)
{
    // 系统初始化
    SystemInit();
    Delay_Init();
    USART_Init(115200);
    
    printf("STM32F030 Multi-Channel ADC Demo\r\n");
    
    // ADC 初始化
    ADC_GPIO_Init();
    ADC_DMA_Init();
    ADC_Configuration();
    ADC_StartContinuous();
    
    uint16_t adc_val[ADC_CHANNEL_NUM];
    float voltage[ADC_CHANNEL_NUM];
    
    while(1)
    {
        // 读取各通道数据
        for(uint8_t i = 0; i < ADC_CHANNEL_NUM; i++)
        {
            adc_val[i] = ADC_GetAverage(i);
            voltage[i] = ADC_GetVoltage(i);
        }
        
        // 打印结果
        printf("CH0: ADC=%4d, Volt=%.2fV | ", adc_val[0], voltage[0]);
        printf("CH1: ADC=%4d, Volt=%.2fV | ", adc_val[1], voltage[1]);
        printf("CH8: ADC=%4d, Volt=%.2fV | ", adc_val[2], voltage[2]);
        printf("CH9: ADC=%4d, Volt=%.2fV\r\n", adc_val[3], voltage[3]);
        
        Delay_ms(1000);  // 1秒刷新一次
    }
}

三、定时器触发采样(高精度场景)

如果需要精确的采样频率(如 1kHz),推荐使用 TIM3 触发 ADC

c 复制代码
/**
  * @brief  定时器触发 ADC 初始化
  */
void ADC_TIM_Trigger_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    // 1. 使能 TIM3 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // 2. 配置 TIM3
    TIM_TimeBaseStructure.TIM_Period = 48000 - 1;  // 48MHz / 48000 = 1kHz
    TIM_TimeBaseStructure.TIM_Prescaler = 0;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 3. 配置 TIM3 输出触发(TRGO = Update Event)
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
    
    // 4. ADC 外部触发配置
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  // 关闭连续转换!
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    // 5. 启动定时器
    TIM_Cmd(TIM3, ENABLE);
}

参考代码 STM32F030芯片,多路通道ADC采样 www.youwenfan.com/contentcsu/56168.html

四、调试与避坑指南

4.1 常见问题排查

现象 原因 解决方案
采样值全为 0 ADC 时钟过快 确保 ADC 时钟 ≤ 14 MHz
采样值跳动大 采样时间过短 增加 ADC_SampleTime(如 239.5 周期)
通道间串扰 输入阻抗过高 减小信号源阻抗,或增加采样电容
DMA 不工作 通道配置错误 F030 只有 DMA1_Channel1 对应 ADC1

4.2 硬件设计建议

  1. VREF 引脚:必须接稳定的 3.3V,并联 10µF + 0.1µF 电容
  2. 模拟地与数字地:分开布线,单点连接
  3. 输入保护:高压信号需加电阻(1kΩ)+ 钳位二极管
  4. 抗干扰:ADC 引脚远离高频信号(如 SPI、PWM)

4.3 性能优化

  • 低功耗 :采样间隙关闭 ADC(ADC_Cmd(DISABLE)
  • 速度优先:降低分辨率(10/8位)换取更快速度
  • 精度优先:增加过采样(Oversampling)提升有效位数

五、总结

这套代码已在 STM32F030F4P6(20pin 最小封装)上验证通过,支持 4 路 ADC 同时采样,CPU 占用率极低(DMA 自动搬运)。

核心价值

解决 F030 ADC 时钟配置难题

DMA 自动扫描,不占用 CPU

支持定时器精确触发

提供均值滤波,数据稳定

相关推荐
三佛科技-187366133973 小时前
LP8841SC+LP35118N (72W SiC双电源方案),全电压认证,体积直降 20%
单片机·嵌入式硬件
metaRTC3 小时前
metaRTC8 成功适配 RTOS:开启 MCU/嵌入式实时音视频新时代
单片机·嵌入式硬件·webrtc·实时音视频·rtos
d111111111d4 小时前
UAER问题+修复小bug
前端·javascript·笔记·stm32·单片机·嵌入式硬件·学习
嵌入式的飞鱼4 小时前
SD NAND vs eMMC:嵌入式存储方案怎么选?
嵌入式硬件·mcu·sd nand
进击的小头5 小时前
第19篇:嵌入式定点与浮点运算科普:核心差异、精度控制与开发技巧
单片机·嵌入式硬件
M158227690555 小时前
老 PLC 秒接工业以太网|三格电子串口转网口模块,让设备改造零门槛、一步上云
单片机·嵌入式硬件
zhmc6 小时前
电解电容的ESR定义与测量
嵌入式硬件
神一样的老师6 小时前
【兆易创新GD32VW553开发板试用】开发板资料汇总
单片机
zmj3203246 小时前
单片机电路中不同点的电压计算
单片机·嵌入式硬件·电路·单片机电路