目录
一.ADC简介
ADC(Analog-to-Digital Converter,模数转换器)是嵌入式开发中常用的功能之一,用于将模拟信号(如传感器的电压)转换为数字信号供MCU处理。STM32F1系列的ADC模块支持12位精度,具有多通道和多模式的特点,广泛应用于信号采集、控制等领域。
STM32的ADC是12位逐次逼近型ADC,转换时间为1us,输入电压的范围是0~3.3V,转换结果的数值范围是0~4095(即2^12),有12个输入通道,可测量10个外部(GPIO),和2个内部信号源(内部温度传感器 和 内部参考电压1.2V,不随供电电压的变化而变化,可以测量CPU的温度),有规则组和注入组两个转换单元,以及模拟看门狗自动监测输入电压范围,超出范围可直接通往NVIC申请中断。
二.ADC组成
2.1 逐次逼近型ADC结构
这里以8位ADC为例,STM32的ADC通常是12位的,但两者原理是一样的。
可以通过配置左下角的ADDA,ADDB,ADDC,然后给ALE端口一个锁存信号,就可以选择左上角的哪个INx通道信号输入比较器,经过DAC内部的加权电阻网络,不断输出电压与输入电压进行比较,直到DAC的输出电压与外部输入的电压近似相等,再将DAC的值通过三态锁存缓冲器输出。与此同时,产生EOC结束信号。
2.2 ADC模块总体结构
从图中可以发现,除了对通道的选择外,STM32还对转换的通道进行了分组:注入通道 和 规则通道。下表是他们的不同点:
重点注意事项:
- 注入组的优先级 比 规则组的优先级更高,可以打断规则组的采集。
- 注入组的四个通道 正好 对应四个数据寄存器,不会出现数据被别的通道覆盖的现象,但规则组则是16个通道共用一个数据寄存器,需要及时把数据移走,不然在进行多个规则组通道采样时,会出现数据覆盖现象。最好配合DMA来进行,至于什么是DMA?我们后续章节再进行讲解。
总结:
规则组适用于:定期采集多个通道的数据,处理量较大但实时性要求较低的情况。
注入组适用于:实时性要求高的关键信号采集,通常用于紧急处理场景。
三.ADC配置
3.1 时钟源的配置
参考手册的时钟树:
假设当经过AHB分频后的时钟频率为72MHz,则ADC预分频器只能进行 6/8分频,因为这里的ADC最大只能接受14HMz的时钟频率,为什么最大只能接收14HMz的时钟频率呢?还记得我们之前提到的,ADC的最短转换时间是1us吗?
ADC的转换时间计算公式为:
Tconv = 采样时间 + 12.5个周期;
当ADC的时钟频率为最大14HMz时,参考手册:
此时采样时间为1.5个周期,所以 Tconv = 采样时间 + 12.5个周期 = 1.5 + 12.5 = 14 个周期,
又因为时钟频率为14HMz,所以总时间为 T = 14 * (1/14 HMz),也就是1us。与ADC的最短转换时间1us不谋而合。
3.2 ADC触发源选择
3.2.1 软件触发
通过调用封装好的标准库函数,直接进行软件触发。
3.2.2 定时器定时触发
可以在定时器的中断里触发,但不推荐这种做法,因为会导致程序频繁的进入中断,会影响到程序的正常运行,更推荐的做法是:将定时器的更新事件映射到TRGO引脚,选择为触发ADC转换,这样就可以实现硬件层面的自动触发。
3.3.3 外部中断触发
通过外部中断,会产生一个脉冲信号,触发ADC转换,具体什么是外部中断,请参考外部中断EXTI篇STM32单片机快速入门——(外部)中断篇_stm32 hall外部中断初始化-CSDN博客
3.3 ADC转化模式
ADC总共有四种转换模式,分别是:
- 单次转换,非扫描模式
- 单次转换,扫描模式
- 连续转换,非扫描模式
- 连续转换,扫描模式
我们分开进行讲解:
- 转换
- 单次:只转换一次
- 连续:只要一开启转换,就会不断的进行。
- 非扫描:一次只转换一个通道
- 扫描:一次性可同时按顺序转换多个通道
进行排列组合,便可以得到以下模式:
3.4 ADC对齐模式
对齐模式有:
- 左对齐
- 右对齐
STM32F103C8T6芯片的ADC为12位的,但是数据寄存器为16位的,选择 左对齐 相当于把结果左位移了4位,将AD采集结果扩大了2^4倍。选择 右对齐 则读出的数据就是正常的原始数据。所以我们选择右对齐居多。
3.5 ADC校准
为什么要进行ADC的校准?
因为:ADC内部存在硬件偏差(Offset Error),导致零输入信号时转换结果并非0。校准可以修正这些偏差,使得零输入时输出接近真实值。温度、供电电压等环境因素会影响ADC性能,通过校准可以在不同条件下确保一致性
3.6 ADC配置流程
四.ADC代码示例
4.1 ADC单通道+单次转换
#include "stm32f10x.h"
void ADC_Config(void);
uint16_t ADC_Read(void);
int main(void)
{
uint16_t adc_value;
// 配置ADC
ADC_Config();
while (1)
{
// 读取ADC值
adc_value = ADC_Read();
}
}
void ADC_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO为模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置ADC参数
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道非扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 转换通道数量为1
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 5. 启用ADC并进行校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1); // 重置校准
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1); // 开始校准
while (ADC_GetCalibrationStatus(ADC1));
}
uint16_t ADC_Read(void)
{
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换完成
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 读取转换结果
return ADC_GetConversionValue(ADC1);
}
4.2 多通道连续转换(带DMA)
#include "stm32f10x.h"
#define ADC1_DR_Address ((uint32_t)0x4001244C)
uint16_t ADC_ConvertedValue[2];
void ADC_DMA_Config(void);
int main(void)
{
// 配置ADC和DMA
ADC_DMA_Config();
while (1)
{
// ADC_ConvertedValue中保存了两个通道的转换结果
uint16_t value0 = ADC_ConvertedValue[0];
uint16_t value1 = ADC_ConvertedValue[1];
}
}
void ADC_DMA_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// 1. 启用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 2. 配置GPIO为模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置DMA
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 2;
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);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 4. 配置ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 2; // 转换通道数量为2
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置ADC通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
// 6. 启用ADC DMA
ADC_DMACmd(ADC1, ENABLE);
// 7. 启用ADC并进行校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
// 8. 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}