一、核心配置要点(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 硬件设计建议
- VREF 引脚:必须接稳定的 3.3V,并联 10µF + 0.1µF 电容
- 模拟地与数字地:分开布线,单点连接
- 输入保护:高压信号需加电阻(1kΩ)+ 钳位二极管
- 抗干扰:ADC 引脚远离高频信号(如 SPI、PWM)
4.3 性能优化
- 低功耗 :采样间隙关闭 ADC(
ADC_Cmd(DISABLE)) - 速度优先:降低分辨率(10/8位)换取更快速度
- 精度优先:增加过采样(Oversampling)提升有效位数
五、总结
这套代码已在 STM32F030F4P6(20pin 最小封装)上验证通过,支持 4 路 ADC 同时采样,CPU 占用率极低(DMA 自动搬运)。
核心价值 :
解决 F030 ADC 时钟配置难题
DMA 自动扫描,不占用 CPU
支持定时器精确触发
提供均值滤波,数据稳定