STM32基础教程——ADC多通道

目录

前言

技术实现

连线图

代码实现 

内容要点 

ADC基本结构配置

获取ADC数据

实验结果 

问题记录 


前言

ADC(Analog-Digital Converter模拟-数字转换器,ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁.12位ADC是一种逐次逼近型模拟数字转换器。它有多达18个通道,可测量16个外部和两个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐对齐方式存储在16位数据寄存器中。

逐次逼近型ADC通过二进制搜索算法逐步确定输入模拟信号的数值,具体步骤如下:

  1. 初始化
    • 逐次逼近寄存器(SAR)将最高有效位(MSB)设为1,其余位设为0,形成一个初始猜测值。
    • 该值通过内部DAC(数模转换器)转换为模拟电压,并与输入信号进行比较。
  2. 比较与调整
    • 比较器将DAC输出的电压与输入模拟信号进行比较:
      • 若DAC电压 < 输入电压 → 保持该位为1,继续测试下一位(次高位)。
      • 若DAC电压 > 输入电压 → 将该位清零(设为0),继续测试下一位。
    • 重复上述步骤,逐位确定每一位的值(从MSB到LSB),直到所有位完成判断。
  3. 输出结果
    • 最终,SAR寄存器中保存的二进制数值即为输入模拟信号的数字表示。

ADC的输入时钟不得超过14MHz,它是由PCLK2经分频产生。

ADC主要特征:

        ● 12位分辨率
        ● 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
        ● 单次和连续转换模式
        ● 从通道0到通道n的自动扫描模式
        ● 自校准
        ● 带内嵌数据一致性的数据对齐
        ● 采样间隔可以按通道分别编程
        ● 规则转换和注入转换均有外部触发选项
        ● 间断模式
        ● 双重模式(带2个或以上ADC的器件)
        ● ADC转换时间:
        ─
        STM32F103xx增强型产品:时钟为56MHz时为1μs(时钟为72MHz为1.17μs)
        ─
        STM32F101xx基本型产品:时钟为28MHz时为1μs(时钟为36MHz为1.55μs)
        ─
        STM32F102xxUSB型产品:时钟为48MHz时为1.2μs
        ─
        STM32F105xx和STM32F107xx产品:时钟为56MHz时为1μs(时钟为72MHz为1.17μs)
        ● ADC供电要求:2.4V到3.6V
        ● ADC输入范围:VREF- ≤ VIN ≤ VREF+
        ● 规则通道转换期间有DMA请求产生。

ADC功能描述:

        

        

技术实现

连线图

代码实现 

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"						//延时函数
#include "OLED.h"
#include "AD.h"


uint16_t AD0,AD1,AD2,AD3;
uint16_t AD_Value;						//AD转换值	
float Voltage;

int main(void)
{
	/*
		OLED初始化
	*/
	OLED_Init();
	AD_Init();

	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	while(1)
	{	
		//单通道非扫描模式
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);
	}
}

AD.h

#ifndef AD_H
#define AD_H

#include "stm32f10x.h"

void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);

#endif

AD.c 

#include "AD.h"

/**
 * @brief  AD Initialization 
 * @param  None
 * @retval None
 * @note   Initialize AD basic structure
 */
void AD_Init(void)
{
    //开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);

    //配置ADC时钟 12MHz
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AIN;                         //模拟输入
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

    //初始化ADC
    ADC_InitTypeDef ADC_InitStruct;
    ADC_InitStruct.ADC_Mode                 = ADC_Mode_Independent;
    ADC_InitStruct.ADC_ScanConvMode         = DISABLE;                  //非扫描模式
    ADC_InitStruct.ADC_ContinuousConvMode   = DISABLE;                  //单次转换
    ADC_InitStruct.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_None;//软件触发,非外部触发
    ADC_InitStruct.ADC_DataAlign            = ADC_DataAlign_Right;      //数据右对齐
    ADC_InitStruct.ADC_NbrOfChannel         = 1;                        //序列1
    ADC_Init(ADC1,&ADC_InitStruct);
    
    //关闭ADC电源
    ADC_Cmd(ADC1,DISABLE);
	
	//维持ADC处于断电状态至少两个周期
	Delay_us(1);

    //ADC校准
    ADC_ResetCalibration(ADC1);                                         //复位校准,将ADC_CR2寄存器中的RSTCAL位置1,初始化校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC1));                         //等待复位校准完成,校准寄存器被初始化后RSTAL位由硬件清零
    ADC_StartCalibration(ADC1);                                         //AD校准,将ADC_CR2寄存器中的CAL位置1,开始校准
    while(ADC_GetCalibrationStatus(ADC1));                              //等待AD校准完成,校准完成后CAL位由硬件清零

	//开启ADC电源
	ADC_Cmd(ADC1,ENABLE);
}

/**
 * @brief  获取AD转换的结果
 * @param  None
 * @retval None
 * @note   启动转换,返回AD转换的结果
 */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
    ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//选择规则组的输入通道
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);                              //软件触发AD转换
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));                       //等待AD转换完成,读取SR寄存器EOC位的状态,转换完成后硬件设置
    return ADC_GetConversionValue(ADC1);                                //返回获取的ADC转换结果,读取DR寄存器会自动清除EOC标志位
}

 OLED部分代码参照文章《STM32基础教程——OLED显示》http://【STM32基础教程 ——OLED显示 - CSDN App】https://blog.csdn.net/2301_80319641/article/details/145837521?sharetype=blog&shareId=145837521&sharerefer=APP&sharesource=2301_80319641&sharefrom=link

内容要点 

ADC基本结构配置

//开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);

实现AD转换要选择GPIO端口作为AD通道,ADC1和GPIO同属APB2外设,故应先开启APB2外设时钟。

//配置ADC时钟 12MHz
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

RCC_PCLK2_Div6: ADC clock = PCLK2/6 ,PCLK为72MHz,计算后ADC时钟为12MHz。

ADC时钟要求不得超过14MHz,当系统时钟为72MHz时,RCC_ADCCLKConfig()的参数分频因子只能选择RCC_PCLK2_Div6或RCC_PCLK2_Div8

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AIN;                         //模拟输入
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

由于进行AD转换,GPIO的输入量为模拟量,故应将GPIO的输入模式配置为模拟输入模式, 由于使用AD转换通道0,通道1,通道2,通道3,这里GPIO配置PA0、PA1、PA2、PA3引脚。AD通道与GPIO引脚对应见下图:

//选择规则组的输入通道
    ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);

 配置ADC输入通道,配置为ADC通道1,将其置于转换序列1,采样周期设置为55.5个采样周期,

AD转换时间为68/14/1000000,即4.86us,即最大采样频率为205.8kHz.

STM32 ADC的总转换时间为:TcoNv=采样时间+12.5个ADC周期

//初始化ADC
    ADC_InitTypeDef ADC_InitStruct;
    ADC_InitStruct.ADC_Mode                 = ADC_Mode_Independent;		//独立ADC模式
    ADC_InitStruct.ADC_ScanConvMode         = DISABLE;                  //非扫描模式
    ADC_InitStruct.ADC_ContinuousConvMode   = DISABLE;                  //单次转换
    ADC_InitStruct.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_None;//软件触发,非外部触发
    ADC_InitStruct.ADC_DataAlign            = ADC_DataAlign_Right;      //数据右对齐
    ADC_InitStruct.ADC_NbrOfChannel         = 1;                        //序列1
    ADC_Init(ADC1,&ADC_InitStruct);

初始化ADC,ADC模式有双ADC模式(包含多种模式)和单ADC模式,这里使用单ADC模式,同时转换模式设置为单次转换,非扫描模式(即转换序列只进行一次转换,并只转换指定的转换序列)。本实验使用软件触发AD转换的模式,不使用AD的硬件触发。数据对齐方式使用右对齐(右对齐不会改变数据的大小,使用方便,但精度不如数据左对齐,左对齐对高位操作方便,但数据读取不如右对齐方式。)。

//关闭ADC电源,且超过两周期以上
    ADC_Cmd(ADC1,DISABLE);

	//维持ADC处于掉电状态并且持续两周期,满足校准要求,一个周期约为71.4ns,两个周期约为142.8ns
	Delay_us(1);

AD转换后要进行校准,关闭ADC电源且维持至少两个周期,满足AD转换的要求。

//ADC校准
    ADC_ResetCalibration(ADC1);                                         //复位校准,将ADC_CR2寄存器中的RSTCAL位置1,初始化校准寄存器
    while(ADC_GetResetCalibrationStatus(ADC1));                         //等待复位校准完成,校准寄存器被初始化后RSTAL位由硬件清零
    ADC_StartCalibration(ADC1);                                         //AD校准,将ADC_CR2寄存器中的CAL位置1,开始校准
    while(ADC_GetCalibrationStatus(ADC1));

ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差 。先启用复位校准,复位校准寄存器,复位校准完成后会将ADC_CR2 寄存器中的RSTCAL位置0,标志校准寄存器已经初始化完成。在启用AD转换,当AD转换完成后,应急那会将CAL位置零。启动AD转换可以消除误差,确保数据的稳定性。

//开启ADC电源
    ADC_Cmd(ADC1,ENABLE);

启动AD转换

获取ADC数据

/**
 * @brief  获取AD转换的结果
 * @param  None
 * @retval None
 * @note   启动转换,返回AD转换的结果
 */
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
    ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//选择规则组的输入通道
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);                              //软件触发AD转换
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));                       //等待AD转换完成,读取SR寄存器EOC位的状态,转换完成后硬件设置
    return ADC_GetConversionValue(ADC1);                                //返回获取的ADC转换结果,读取DR寄存器会自动清除EOC标志位
}

每次软件触发AD转换前,调用ADC_RegularChannelConfig()函数向其传递参数ADC_Channel选择对应的规则组的输入通道,然后使用软件触发的方式触发AD转换,AD转换完成后硬件会将ADC_SR寄存器的EOC位置1,使用 while()循环等待AD转换,完成后读取ADC_DR寄存器的值,将ADC的值返回。由于ADC_DR寄存器的低半字为ADC1规则组ADC的数据,ADC_GetConversionValue()将ADC_DR寄存器的值强转为了uint16_t,自动截断高半字。

 数据读取与显示

//单通道非扫描模式
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);

要显示对应的AD值之前分别调用AD_GetValue()函数,向其传递对应通道的参数并将返回值存储在变量中,以供OLED显示。 

实验结果 

旋转电位器课观察到OLED上ADC0数值变化,遮挡光敏电阻可以观察到OLED上ADC1的数值变化,用手捏住热敏电阻可以观察到OLED上ADC2的数值变变化,将手放在反射式红外上方可以观察到OLED上ADC3数值变化

AD多通道

问题记录 

1.江科大教学视频中对AD校准未严格遵守“启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值