赞
踩
本文使用芯片是 STM32F103ZET6
ADC的作用是将模拟信号转换为数字信号,以便微控制器能够理解和处理。在STM32微控制器中,ADC可以实现读取外部传感器的模拟数据的功能,如温度传感器、光敏电阻、压力传感器等。
在STM32系列微控制器中,ADC模块通常包括以下特性和功能:
ADC模块的模拟电源引脚,通常连接到MCU的模拟电源线路。VDDA提供ADC模块所需的模拟电源电压,确保ADC的正常工作和准确的模拟信号转换。
ADC模块的模拟地引脚,通常连接到MCU的模拟地线路。
ADC模块的正参考电压引脚,通常连接到外部提供的正参考电压源。VREF+确定了ADC转换过程中的上限电压参考点,通常对应于模拟输入信号的最大量程值。在ADC转换过程中,被测量的模拟信号会与VREF+进行比较,以确定其对应的数字量。
开发板将VREF+和VDDA都连接了MCU供电引脚,测量上限是3.3V。
ADC模块的负参考电压引脚,通常连接到外部提供的负参考电压源或地。VREF-确定了ADC转换过程中的下限电压参考点,通常对应于模拟输入信号的最小量程值。
开发板将VREF-连接了接地引脚,这样测量下限是0V。
需要测量的电压值需要调制到VREF-、VREF+范围内进行测量,本文使用开发板测量范围是0~3.3V 。
通道 n | ADC1 | ADC2 | ADC3 |
---|---|---|---|
通道0 | PA0 | PA0 | PA0 |
通道1 | PA1 | PA1 | PA1 |
通道2 | PA2 | PA2 | PA2 |
通道 3 | PA3 | PA3 | PA3 |
通道4 | PA4 | PA4 | PF6 |
通道5 | PA5 | PA5 | PF7 |
通道6 | PA6 | PA6 | PF8 |
通道7 | PA7 | PA7 | PF9 |
通道8 | PB0 | PB0 | PF10 |
通道9 | PB1 | PB1 | |
通道10 | PC0 | PC0 | PC0 |
通道11 | PC1 | PC1 | PC1 |
通道12 | PC2 | PC2 | PC2 |
通道13 | PC3 | PC3 | PC3 |
通道14 | PC4 | PC4 | |
通道15 | PC5 | PC5 | |
通道16 | 内部温度传感器 | ||
通道17 | 内部参考电压VREF |
在STM32微控制器的ADC模块中,分成了两种不同类型的通道:规则通道和注入通道,它们适应于不同的采集场景,功能框图如下图所示:
规则通道是ADC模块中用于常规数据采样的通道。它们允许用户配置ADC以便按照预定的顺序对这些通道进行连续或单次采样。规则通道通常用于周期性地采集传感器数据或监测模拟信号。一般情况下,规则通道的采样是通过ADC的常规转换触发进行的。
注入通道是一种高优先级的ADC通道,用于快速响应于外部事件并进行数据采样。注入通道允许用户将某些通道配置为在触发器触发时进行一次或多次采样,而不影响正在进行的规则通道转换。这种功能使得注入通道特别适合于采集突发事件或需要高优先级响应的应用,比如实时监控或故障检测。
ADC工作频率一般要求14MHz以下,ADC工作在的APB2总线的72M需要分频,一般分频因子设置为6。
ADC 要完成输入电压采样需要若干个ADC_CLK周期 , 最小采样周期为1.5个。
T
c
o
n
v
=
采样时间
+
12.5
个周期
T_{conv}=采样时间+12.5个周期
Tconv=采样时间+12.5个周期
当ADC_CLK=14MHz时,设置1.5个采样周期 , 则Tconv=1.5+12.5=14个周期=1us。
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void GPIO_Configuration(void) {
// 初始化 GPIO 变量
GPIO_InitTypeDef GPIO_InitStructure;
// 使能 GPIO 时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 配置引脚模式为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // 假设使用 PA0 引脚作为模拟输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; // 模拟输入模式
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 不使用上拉/下拉
GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化 GPIOA
}
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
其中结构体介绍:
ADC_TypeDef定义的 ADC 外设的类型,用于表示具体的 ADC 控制器。
typedef struct
{
__IO uint32_t SR; // ADC 状态寄存器
__IO uint32_t CR1; // ADC 控制寄存器1
__IO uint32_t CR2; // ADC 控制寄存器2
__IO uint32_t SMPR1; // ADC 采样时间寄存器1
__IO uint32_t SMPR2; // ADC 采样时间寄存器2
__IO uint32_t JOFR1; // ADC 规则通道数据偏移寄存器1
__IO uint32_t JOFR2; // ADC 规则通道数据偏移寄存器2
__IO uint32_t JOFR3; // ADC 规则通道数据偏移寄存器3
__IO uint32_t JOFR4; // ADC 规则通道数据偏移寄存器4
__IO uint32_t HTR; // ADC 规则通道高阈值寄存器
__IO uint32_t LTR; // ADC 规则通道低阈值寄存器
__IO uint32_t SQR1; // ADC 规则通道序列1寄存器
__IO uint32_t SQR2; // ADC 规则通道序列2寄存器
__IO uint32_t SQR3; // ADC 规则通道序列3寄存器
__IO uint32_t JSQR; // ADC 注入通道序列寄存器
__IO uint32_t JDR1; // ADC 注入通道数据寄存器1
__IO uint32_t JDR2; // ADC 注入通道数据寄存器2
__IO uint32_t JDR3; // ADC 注入通道数据寄存器3
__IO uint32_t JDR4; // ADC 注入通道数据寄存器4
__IO uint32_t DR; // ADC 规则通道数据寄存器
} ADC_TypeDef;
typedef struct
{
uint32_t ADC_Mode; // ADC 工作模式
FunctionalState ADC_ScanConvMode; // ADC扫描(多通道)或单次(单通道)模式选择
FunctionalState ADC_ContinuousConvMode; // 单次转换或者连续转换选择
uint32_t ADC_ExternalTrigConvEdge; // 外部触发信号
uint32_t ADC_DataAlign; // 转换结果数据对齐方式
uint8_t ADC_NbrOfChannel; // ADC采集通道数
}ADC_InitTypeDef;
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC_Cmd(ADC1, ENABLE);
// 校准
ADC_StartCalibration(ADC1);
// 等待复位校准完成
while(ADC_GetResetCalibrationStatus(ADC1));
// 等待校准结束
while(ADC_GetCalibrationStatus(ADC1));
//设置规则序列通道及采样周期
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,uint8_t Rank,uint8_t ADC_SampleTime);
// 软件触发开启转换
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 获取结果
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// 获取AD转换状态信息
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
本示例从ADC1_IN1输入模拟信号进行转换(PA1引脚)。
使用开发板原理图如下:
ADC1上使用了一个LM358运放,虽然一些简单情况下可以直接通过MCU端口进行ADC采集, 但使用运放会更好,原因如下:
#include "adc_utils.h"
/**
* @brief ADCx初始化端口
*/
void ADCx_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
// 模拟输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// 速度50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 设置分频因子为6
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// ADC1配置
ADC_InitTypeDef ADC_InitStructure;
// 单通道模式
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;
// 完成初始化
ADC_Init(ADC1, &ADC_InitStructure);
// 使能ADC1
ADC_Cmd(ADC1, ENABLE);
// 复位校准
ADC_ResetCalibration(ADC1);
// 等待复位校准完成
while(ADC_GetResetCalibrationStatus(ADC1));
// 开始校准
ADC_StartCalibration(ADC1);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADC1));
// 软件触发ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
/**
* @param ch 通道
* @param time 读取次数
* @brief 读取ADC值
*/
u16 Get_Adc(u8 ch, u8 time){
u8 i;
// 规则通道的规则序列进行设定,采样周期最长
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);
u16 value = 0;
for(i = 0; i < time; i++){
// 每次都用软件触发转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换结束
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 返回转换结果
value += ADC_GetConversionValue(ADC1);
}
return value / time;
}
#include "gpio_utils.h"
#include "stm32f10x.h"
#include "sys_tick_utils.h"
#include "usart_utils.h"
#include "stdio.h"
#include "adc_utils.h"
#include "led_utils.h"
// 主函数
int main(void)
{
u8 i;
u16 value;
GPIO_Configuration(); // 调用GPIO配置函数
// tick 初始化
sys_tick_init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
USART3_Init(9600);
printf("starting...");
ADCx_Init();
while (1) // 无限循环
{
i++;
if(i%20==0){
led_flash(0);
}
if(i%50==0){
value = Get_Adc(ADC_Channel_1, 20);
printf("value=%d\r\n", value);
printf("votage=%f\r\n", (float)value*(3.3/4096));
}
delay_ms(10);
}
}
调节开发板上的AD1电位器,可以看到串口输出的电压发生变化 。
本文学习资源来自普中stm32教程
本文代码开源地址:
https://gitee.com/xundh/stm32_arm_learn
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。