STM32基础教程——AD单通道

目录

前言

技术实现

连线图

代码实现 

内容要点 

ADC基本结构配置

读取ADC数据

电压值计算 

实验结果 

问题记录 


前言

ADCAnalog-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 AD_Value;						//AD转换值	
float Voltage;

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

	OLED_ShowString(1,1,"ADValue:");
	OLED_ShowString(2,1,"Voltage:0.00V");

	while(1)
	{
		AD_Value = AD_GetValue();
		Voltage = (float)AD_Value / 4095 * 3.3;
		OLED_ShowNum(1,9,AD_Value,5);
		OLED_ShowNum(2,9,Voltage,1);
		OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);
	}
}

AD.h

#ifndef AD_H
#define AD_H

#include "stm32f10x.h"

void AD_Init(void);
uint16_t AD_GetValue(void);

#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_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

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

    //初始化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_Cmd(ADC1,DISABLE);

	//维持ADC处于掉电状态并且持续两周期,满足校准要求,一个周期约为71.4ns,两个周期约为142.8ns
	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(void)
{
    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显示 - 优快云 App】https://blog.youkuaiyun.com/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_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

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

//初始化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(void)
{
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);                              //软件触发AD转换
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));                       //等待AD转换完成,读取SR寄存器EOC位的状态,转换完成后硬件设置
    return ADC_GetConversionValue(ADC1);                                //返回获取的ADC转换结果,读取DR寄存器会自动清除EOC标志位
}

使用软件触发的方式触发AD转换,AD转换完成后会将ADC_SR寄存器的EOC位置1,使用 while()循环等待AD转换,完成后读取ADC_DR寄存器的值,将ADC的值返回。由于ADC_DR寄存器的低半字为ADC1规则组ADC的数据,ADC_GetConversionValue()将ADC_DR寄存器的值强转为了uint16_t,自动截断高半字。

电压值计算 

        AD_Value = AD_GetValue();
		Voltage = (float)AD_Value / 4095 * 3.3;

实验结果 

旋转电位器,可以观察到OLED上显示的经转化后的电压数字量(电压值)在改变

问题记录 

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

### STM32 ADC采集教程及实例代码 #### 一、STM32多路ADC采集基础 对于想要了解如何利用STM32实现多通道模拟到数字转换(ADC)的开发者来说,掌握其内部集成的ADC模块是非常重要的。该模块允许同时从多个输入端口读取电压水平并将其转化为相应的数值表示形式[^1]。 ```c // 初始化配置部分省略... HAL_ADC_Start(&hadc); for (uint8_t i = 0; i < CHANNEL_COUNT; ++i){ HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY); adcValue[i] = HAL_ADC_GetValue(&hadc); } HAL_ADC_Stop(&hadc); ``` 这段C语言编写的程序展示了怎样启动一次性的连续模式下的ADC采样过程,并获取指定数量通道的结果存储于数组`adcValue[]`之中。这里假设已经完成了必要的初始化工作以及定义好了全局变量CHANNEL_COUNT来指示参与测量的具体数目。 #### 二、基于STM32的声音信号采集实践 当涉及到音频处理领域时,则可以借助STM32强大的定时器中断特性配合外部麦克风传感器完成对环境音量变化趋势的有效捕捉。此过程中同样离不开精准可靠的ADC支持作为底层硬件支撑[^2]。 ```c void TIM2_IRQHandler(void) { if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET && __HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET){ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // LED Toggle for debug purpose. if(HAL_OK == HAL_ADC_Start(&hadc)){ if(HAL_OK == HAL_ADC_PollForConversion(&hadc, 10)) soundLevel = HAL_ADC_GetValue(&hadc); HAL_ADC_Stop(&hadc); } __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); } } ``` 上述函数片段说明了每当发生周期性计数溢出事件触发IRQ服务例程执行期间会尝试开启单次转换请求并将最终得到的数据赋给soundLevel变量保存起来以便后续分析使用。 #### 三、提升数据准确性——STM32搭配专用ADC芯片方案 为了满足某些特殊应用场景下更高的分辨率要求或是更快的速度表现,还可以考虑引入诸如AD7799这样的高性能独立型ADC器件并与MCU建立通信连接共同协作达成目标。这种方式不仅拓宽了系统的灵活性同时也提高了整体性能指标[^3]。 ```c SPI_HandleTypeDef hspi; /* ... */ static void AD7799_ReadRegister(uint8_t regAddr,uint8_t *pData,uint16_t size){ uint8_t txData[2]; memset(pData,0,size); /* Select the device */ HAL_GPIO_WritePin(NSS_PORT,NSS_PIN,GPIO_PIN_RESET); /* Send register address to read from */ txData[0]=regAddr | 0x80; // Set MSB high for reading operation HAL_SPI_Transmit(&hspi,(uint8_t*)txData,sizeof(txData),HAL_MAX_DELAY); /* Read data back into buffer */ HAL_SPI_Receive(&hspi,pData,size,HAL_MAX_DELAY); /* Deselect the device */ HAL_GPIO_WritePin(NSS_PORT,NSS_PIN,GPIO_PIN_SET); } ``` 在此处给出了一段针对特定型号(即AD7799)的操作接口设计思路,它采用串行外设接口(SPI)协议来进行寄存器级别的交互访问从而实现了对外部设备状态查询等功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值