STM32F1-ADC

发布于 2023-05-10  558 次阅读


ADC简介

ADC,全称:Analog-to-Digital Converter,指模拟/数字转换器

ADC的特性参数

  • 分辨率
    • 表示ADC能辨别的最小模拟量,用二进制位数表示,比如:8、10、12、16位等
  • 转换时间
    • 表示完成一次A/D转换所需要的时间,转换时间越短,采样率就可以越高
  • 精度
    • 最小刻度基础上叠加各种误差的参数,精度受ADC性能、温度和气压等影响
  • 量化误差
    • 用数字量近似表示模拟量,采用四舍五入原则,此过程产生的误差为量化误差

STM32各系列ADC的主要特性

ADC工作原理

结构框图

①参考电压/模拟部分电压

②输入通道

③转换序列

④触发源

⑤转换时间

⑥数据寄存器

⑦中断

输入通道

ADC1IOADC2IOADC3IO
通道0PA0通道0PA0通道0PA0
通道1PA1通道1PA1通道1PA1
通道2PA2通道2PA2通道2PA2
通道3PA3通道3PA3通道3PA3
通道4PA4通道4PA4通道4PF6
通道5PA5通道5PA5通道5PF7
通道6PA6通道6PA6通道6PF8
通道7PA7通道7PA7通道7PF9
通道8PB0通道8PB0通道8PF10
通道9PB1通道9PB1通道9连接内部VSS
通道10PC0通道10PC0通道10PC0
通道11PC1通道11PC1通道11PC1
通道12PC2通道12PC2通道12PC2
通道13PC3通道13PC3通道13PC3
通道14PC4通道14PC4通道14连接内部VSS
通道15PC5通道15PC5通道15连接内部VSS
通道16连接内部温度传感器通道16连接内部VSS通道16连接内部VSS
通道17连接内部Vrefint通道17连接内部VSS通道17连接内部VSS

转换序列

A/D转换被组织为两组:规则组(常规转换组)注入组(注入转换组)

规则组最多可以有16个转换,注入组最多有4个转换

优先级:注入组>规则组第1个转换的通道>规则组第2个转换的通道

注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始

触发源

ADON 位触发转换
当 ADC_CR2 寄存器的 ADON 位为 1 时,再独立给 ADON 位写 1(其它位不能一起改变,这是为了防止误触发),这时会启动转换。这种控制 ADC 启动转换的方式非常简单。
外部触发转换
另一种方法是通过外部事件触发转换,例如定时器捕获、 EXTI 线和软件触发,可以分为规则组外部触发和注入组外部触发。

规则组外部触发使用方法是将 EXTTRIG 位置 1,并且通过 EXTSET[2:0]位选择规则组启动转换的触发源。如果 EXTSET[2:0]位设置为 111,那么可以通过 SWSTART 为启动 ADC 转换,相当于软件触发。

注入组外部触发使用方法是将 JEXTTRIG 位置 1,并且通过 JEXTSET[2:0]位选择注入组启动转换的触发源。如果 JEXTSET[2:0]位设置为 111,那么可以通过 JSWSTART 为启动 ADC 转换,相当于软件触发。

转换时间

ADC时钟

转换时间

ADC转换时间: TCONV = 采样时间 + 12.5个周期

采样时间可通过 ADC_SMPR1 和 ADC_SMPR2 寄存器中的 SMPx[2:0]位设置, x=0~17。
ADC_SMPR1 控制的是通道 0~9, ADC_SMPR2 控制的是通道 10~17。每个输入通道都支持通过编程来选择不同的采样时间,采样时间可选的范围如下:

  • SMP = 000: 1.5 个 ADC 时钟周期
  • SMP = 001: 7.5 个 ADC 时钟周期
  • SMP = 010: 13.5 个 ADC 时钟周期
  • SMP = 011: 28.5 个 ADC 时钟周期
  • SMP = 100: 41.5 个 ADC 时钟周期
  • SMP = 101: 55.5 个 ADC 时钟周期
  • SMP = 110: 71.5 个 ADC 时钟周期
  • SMP = 111: 239.5 个 ADC 时钟周期

举个例子:ADC时钟频率为12MHz时,ADC最短的转换时间是多少?

TCONV = 采样时间 + 12.5个周期 = 1.5个周期 + 12.5个周期 = 14个周期 = (1/12000000)∗14 s = 1.17us

数据寄存器

中断

中断事件事件标志使能控制位
规则通道转换结束EOCEOCIE
注入通道转换结束JEOCJEOCIE
设置了模拟看门狗状态位AWDAWDIE
溢出(F1没有)OVROVRIE

DMA请求(只适用于规则组):

规则组每个通道转换结束后,除了可以产生中断外,还可以产生DMA请求,我们利用DMA及时把转换好的数据传输到指定的内存里,防止数据被覆盖。

单次和连续转换

CONT01
转换模式单次转换模式连续转换模式
转换组/转换模式单次转换模式(只触发一次转换)连续转换模式(自动触发下一次转换) 注意:只有规则组才能触发该模式
规则组转换结果被储存在ADC_DR EOC(转换结束)标志位被置1 如果设置了EOCIE位,则产生中断 然后ADC停止转换结果被储存在ADC_DR EOC(转换结束)标志位被置1 如果设置了EOCIE位,则产生中断
注入组转换结果被储存在ADC_DRJx JEOC(转换结束)标志位被置1 如果设置了JEOCIE位,则产生中断 然后ADC停止转换结果被储存在ADC_DRJx JEOC(转换结束)标志位被置1 如果设置了JEOCIE位,则产生中断 自动注入:将JAUTO位置1

扫描模式

SCAN01
扫描模式关闭扫描模式使用扫描模式
关闭扫描模式使用扫描模式
ADC只转换ADC_SQRx或ADC_JSQR选中的第一个通道进行转换ADC会扫描所有被ADC_SQRx或ADC_JSQR选中的所有通道

具体例子:

单次转换模式(不扫描)使用ADC单通道,并要求进行一次转换
单次转换模式(扫描)使用ADC多通道,并要求所有通道都转换一次就停止
连续转换模式(不扫描)使用ADC单通道,并要求对该通道连续转换
连续转换模式(扫描)使用ADC多通道,并要求所有通道都转换一次后,自动启动下一轮转换

ADC相关寄存器

ADC控制寄存器 1(ADC_CR1)

ADC控制寄存器 2(ADC_CR2)

ADC采样时间寄存器1(ADC_SMPRx)

ADC_SMPR1
ADC_SMPR2

ADC规则序列寄存器 1(ADC_SQRx)

ADC_SQR1
ADC_SQR2
ADC_SQR3

ADC规则数据寄存器 (ADC_DR)

单通道ADC采集

普通模式

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_ADC_Init()CR1、CR2配置ADC工作参数
HAL_ADCEx_Calibration_Start()CR2ADC校准
HAL_ADC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_RCCEx_PeriphCLKConfig()RCC_CFGR设置扩展外设时钟,如:ADC、RTC等
HAL_ADC_ConfigChannel()SQRx、SMPRx配置ADC相应通道的相关参数
HAL_ADC_Start()CR2启动A/D转换
HAL_ADC_PollForConversion()SR等待规则通道转换完成
HAL_ADC_GetValue()DR获取规则通道A/D转换结果

1.配置工作参数

ADC_HandleTypeDef g_adc_handle;

/* ADC单通道 */
void adc_init(void)
{
    ADC_ChannelConfTypeDef adc_ch_conf;

    g_adc_handle.Instance = ADC1;
    g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 设置数据的对齐方式 */ 
    g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 扫描模式 */ 
    g_adc_handle.Init.ContinuousConvMode = DISABLE;/* DISABLE开启单次转换模式或者ENABLE连续转换模式 */ 
    g_adc_handle.Init.NbrOfConversion = 1;/* 设置转换通道数目 */ 
    g_adc_handle.Init.DiscontinuousConvMode = DISABLE;/* 是否使用规则通道组间断模式 */ 
    g_adc_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数 */ 
    g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* ADC 外部触发源选择 */ 
    HAL_ADC_Init(&g_adc_handle);/*配置ADC工作参数*/
    
    HAL_ADCEx_Calibration_Start(&g_adc_handle);/*ADC校准*/
    
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* ADC 转换顺序 */ 
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;/* ADC 采样周期,采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us*/ 
    HAL_ADC_ConfigChannel(&g_adc_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
}

2.MSP初始化

/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC1)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_ADC1_CLK_ENABLE();

        gpio_init_struct.Pin = GPIO_PIN_1;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct); 
        
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/*选择要配置的时钟源*/
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/*设置ADC分频系数*/
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/*配置ADC时钟源以及分频系数*/
    }
}

3.获得ADC转换结果

/* 获得ADC转换后的结果函数 */
uint32_t adc_get_result(void)
{
    HAL_ADC_Start(&g_adc_handle);/*启动A/D转换*/
    HAL_ADC_PollForConversion(&g_adc_handle, 10);/*等待规则通道转换完成*/
    return (uint16_t)HAL_ADC_GetValue(&g_adc_handle);/*获取规则通道A/D转换结果*/
}

main函数

  while(1)
  {
	adcx = adc_get_result();
	temp = (float)adcx * (3.3 / 4096);              /* 获取计算后的带小数的实际电压值,比如3.1111 */
	adcx = temp;    /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
	OLED_ShowNum(10,10,adcx,1,8);/* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
	temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
	temp *= 1000;   /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
	adcx = temp;/* 赋值整数部分给adcx变量,因为adcx为u16整形 */
	OLED_ShowNum(30,10,adcx,4,8);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111 */

  }

DMA读取

相关函数库

函数主要寄存器主要功能
HAL_ADC_Init()CR1、CR2配置ADC工作参数
HAL_ADCEx_Calibration_Start()CR2ADC校准
HAL_ADC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_RCCEx_PeriphCLKConfig()RCC_CFGR设置扩展外设时钟,如:ADC、RTC等
HAL_ADC_ConfigChannel()SQRx、SMPRx配置ADC相应通道的相关参数
HAL_DMA_Start_IT()CCRx启动DMA、开启传输完成中断
HAL_ADC_Start_DMA()CR2触发ADC转换、使用DMA传输数据

1.配置工作参数

DMA_HandleTypeDef g_dma_adc_handle;
ADC_HandleTypeDef g_adc_dma_handle;

/* ADC单通道 */
void adc_dma_init(uint32_t mar)
{
	__HAL_RCC_DMA1_CLK_ENABLE();
	
    g_dma_adc_handle.Instance = DMA1_Channel1;/* DMA通道*/
    g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 外设到存储器模式 */
    g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//ADC数据寄存器独立模式时是低16位有效的,所以用半字
    g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    g_dma_adc_handle.Init.Mode = DMA_NORMAL;/* 外设流控模式,内存到内存不支持循环模式 */
    g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;/* 中等优先级 */
    HAL_DMA_Init(&g_dma_adc_handle);
	__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);/* 关联 DMA 句柄,hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler,如下图1.1所示*/
		
    ADC_ChannelConfTypeDef adc_ch_conf;
    g_adc_dma_handle.Instance = ADC1;
    g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 设置数据的对齐方式 */ 
    g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 扫描模式 */ 
    g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;/* DISABLE开启单次转换模式或者ENABLE连续转换模式 */ 
    g_adc_dma_handle.Init.NbrOfConversion = 1;/* 设置转换通道数目 */ 
    g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;/* 是否使用规则通道组间断模式 */ 
    g_adc_dma_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数 */ 
    g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* ADC 外部触发源选择 */ 
    HAL_ADC_Init(&g_adc_dma_handle);/*配置ADC工作参数*/
    
	HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);/*ADC校准*/
    
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* ADC 转换顺序 */ 
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;/* ADC 采样周期,采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us*/ 
    HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 1);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
	
    HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);//长度为零,CNDTR为零,即先不传输,后面可以自己控制传输触发条件
    HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
}

2.MSP初始化

/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC1)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_ADC1_CLK_ENABLE();
 
        gpio_init_struct.Pin = GPIO_PIN_1;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct); 
        
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/*选择要配置的时钟源*/
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/*设置ADC分频系数*/
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/*配置ADC时钟源以及分频系数*/
    }
}

3.中断服务函数

uint8_t g_adc_dma_sta;//DMA传输完成标志位
/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & (1<<1))//判断在通道有没有传输完成事件(TC),即判断DMA传输完成标志位
    {
        g_adc_dma_sta = 1;//DMA传输完成标志位
        DMA1->IFCR |= 1 << 1;//清除DMA_ISR寄存器中的对应TCIF标志,即清除DMA通道传输完成标志即
    }
//	if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC1))//获取TC位,即发送完成位的状态
//	{
//		g_adc_dma_sta = 1;//DMA传输完成标志位
//		__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC1);//清除TC标志位
//	}
}

4.使能传输函数

/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
    ADC1->CR2 &= ~(1 << 0);//失能ADC
    
    DMA1_Channel1->CCR &= ~(1 << 0);//失能DMA
    while (DMA1_Channel1->CCR & (1 << 0));//等待失能DMA,即等待CCRx位0为0
    DMA1_Channel1->CNDTR = cndtr;//重装CNDTR的值,使能传输

    DMA1_Channel1->CCR |= 1 << 0;//使能DMA

    ADC1->CR2 |= 1 << 0;//使能ADC
    ADC1->CR2 |= 1 << 22;//启动A/D转换
    
//    __HAL_ADC_DISABLE(&g_adc_dma_handle);
//    
//    __HAL_DMA_DISABLE(&g_dma_adc_handle);
//    while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
//    DMA1_Channel1->CNDTR = cndtr;
//    __HAL_DMA_ENABLE(&g_dma_adc_handle);
//    
//    __HAL_ADC_ENABLE(&g_adc_dma_handle);
//    HAL_ADC_Start(&g_adc_dma_handle);
}

mian函数

#define ADC_DMA_BUF_SIZE        100         /* ADC DMA采集 BUF大小 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */
uint16_t i;
uint16_t adcx;
uint32_t sum;
float temp;

	adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
	adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */

  while(1)
  {
//	  OLED_ShowNum(30,10,66,8,8);  
	 if (g_adc_dma_sta == 1)
        {
			/* 计算DMA 采集到的ADC数据的平均值 */
            sum = 0;
            for (i = 0; i < ADC_DMA_BUF_SIZE; i++)   /* 累加 */
            {
                sum += g_adc_dma_buf[i];
            }
            adcx = sum / ADC_DMA_BUF_SIZE;           /* 取平均值 */
            temp = (float)adcx * (3.3 / 4096);                  /* 获取计算后的带小数的实际电压值,比如3.1111 */
            adcx = temp;                                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
            OLED_ShowNum(1,10,adcx,1,8);      /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
            temp -= adcx;                                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
            temp *= 1000;                                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
            OLED_ShowNum(10,10,temp,3,8);  /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
            g_adc_dma_sta = 0;                                  /* 清除DMA采集完成状态标志 */
            adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动下一次ADC DMA采集 */
        }

   
        delay_ms(100);
  }

多通道ADC采集

多通道采集相比单通道采集,需要将ADC设置为扫描模式,并设置好对应的通道个数即可。

1.配置工作参数

DMA_HandleTypeDef g_nch_dma_adc_handle;
ADC_HandleTypeDef g_nch_adc_dma_handle;

/* ADC单通道 */
void adc_dma_init(uint32_t mar)
{
	__HAL_RCC_DMA1_CLK_ENABLE();
	
    g_nch_dma_adc_handle.Instance = DMA1_Channel1;/* DMA通道*/
    g_nch_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 外设到存储器模式 */
    g_nch_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_nch_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    g_nch_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//ADC数据寄存器独立模式时是低16位有效的,所以用半字
    g_nch_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    g_nch_dma_adc_handle.Init.Mode = DMA_NORMAL;/* 外设流控模式,内存到内存不支持循环模式 */
    g_nch_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;/* 中等优先级 */
    HAL_DMA_Init(&g_nch_dma_adc_handle);
	__HAL_LINKDMA(&g_nch_adc_dma_handle, DMA_Handle, g_nch_dma_adc_handle);/* 关联 DMA 句柄,hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler,如下图1.1所示*/
		
    ADC_ChannelConfTypeDef adc_ch_conf;
    g_nch_adc_dma_handle.Instance = ADC1;
    g_nch_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 设置数据的对齐方式 */ 
    g_nch_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;/* 扫描模式 */ 
    g_nch_adc_dma_handle.Init.ContinuousConvMode = ENABLE;/* DISABLE开启单次转换模式或者ENABLE连续转换模式 */ 
    g_nch_adc_dma_handle.Init.NbrOfConversion = 4;/* 设置转换通道数目 */ 
    g_nch_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;/* 是否使用规则通道组间断模式 */ 
    g_nch_adc_dma_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数 */ 
    g_nch_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* ADC 外部触发源选择 */ 
    HAL_ADC_Init(&g_nch_adc_dma_handle);/*配置ADC工作参数*/
    
	HAL_ADCEx_Calibration_Start(&g_nch_adc_dma_handle);/*ADC校准*/
	
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* ADC 转换顺序 */ 
	adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;/* ADC 采样周期,采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us*/
	HAL_ADC_ConfigChannel(&g_nch_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	adc_ch_conf.Channel = ADC_CHANNEL_2;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_2;/* ADC 转换顺序 */ 
    HAL_ADC_ConfigChannel(&g_nch_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	adc_ch_conf.Channel = ADC_CHANNEL_3;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_3;/* ADC 转换顺序 */ 
    HAL_ADC_ConfigChannel(&g_nch_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	adc_ch_conf.Channel = ADC_CHANNEL_4;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_4;/* ADC 转换顺序 */ 
    HAL_ADC_ConfigChannel(&g_nch_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 1);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
	
    HAL_DMA_Start_IT(&g_nch_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);//长度为零,CNDTR为零,即先不传输,后面可以自己控制传输触发条件
    HAL_ADC_Start_DMA(&g_nch_adc_dma_handle, &mar ,0);
}

2.MSP初始化

/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC1)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_ADC1_CLK_ENABLE();
 
        gpio_init_struct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct); 
        
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/*选择要配置的时钟源*/
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/*设置ADC分频系数*/
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/*配置ADC时钟源以及分频系数*/
    }
}

3.中断服务函数

uint8_t g_adc_dma_sta;//DMA传输完成标志位
/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & (1<<1))//判断在通道有没有传输完成事件(TC),即判断DMA传输完成标志位
    {
        g_adc_dma_sta = 1;//DMA传输完成标志位
        DMA1->IFCR |= 1 << 1;//清除DMA_ISR寄存器中的对应TCIF标志,即清除DMA通道传输完成标志即
    }
//	if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC1))//获取TC位,即发送完成位的状态
//	{
//		g_adc_dma_sta = 1;//DMA传输完成标志位
//		__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC1);//清除TC标志位
//	}
}

4.使能DMA传输

/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
    ADC1->CR2 &= ~(1 << 0);//失能ADC
    
    DMA1_Channel1->CCR &= ~(1 << 0);//失能DMA
    while (DMA1_Channel1->CCR & (1 << 0));//等待失能DMA,即等待CCRx位0为0
    DMA1_Channel1->CNDTR = cndtr;//重装CNDTR的值,使能传输

    DMA1_Channel1->CCR |= 1 << 0;//使能DMA

    ADC1->CR2 |= 1 << 0;//使能ADC
    ADC1->CR2 |= 1 << 22;//启动A/D转换
    
//    __HAL_ADC_DISABLE(&g_adc_dma_handle);
//    
//    __HAL_DMA_DISABLE(&g_dma_adc_handle);
//    while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
//    DMA1_Channel1->CNDTR = cndtr;
//    __HAL_DMA_ENABLE(&g_dma_adc_handle);
//    
//    __HAL_ADC_ENABLE(&g_adc_dma_handle);
//    HAL_ADC_Start(&g_adc_dma_handle);
}

main函数

#define ADC_DMA_BUF_SIZE        50*4         /* ADC DMA采集 BUF大小 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];   /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta;               /* DMA传输状态标志, 0,未完成; 1, 已完成 */
uint8_t g_adc_dma_ch;//DMA传输通道标志位
    uint16_t i,j;
    uint16_t adcx;
    uint32_t sum;
    float temp;

	adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
	adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */

while(1)
{
	if (g_adc_dma_sta == 1)
	{
		for(j=0;j<4;j++)
		{
			/* 计算DMA 采集到的ADC数据的平均值 */
            sum = 0;
            for (i = 0; i < 50; i++)   /* 累加 */                                                                                           
            {
                sum += g_adc_dma_buf[i*4+j];	/*取出采集值并求和,12341234...1234*/
            }
			if(j == 2){		//如果是通道2采集的值
			adcx = sum / ADC_DMA_BUF_SIZE/4;           /* 取平均值 */
			temp = (float)adcx * (3.3 / 4096);                  /* 获取计算后的带小数的实际电压值,比如3.1111 */
			adcx = temp;                                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
			OLED_ShowNum(1,1,adcx,1,8);      /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
			temp -= adcx;                                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
			temp *= 1000;                                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
			OLED_ShowNum(10,1,temp,3,8);  /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
			g_adc_dma_sta = 0;                                  /* 清除DMA采集完成状态标志 */                                                                  
			adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动下一次ADC DMA采集 */
			}
			   
        }
}

	  
//	  OLED_ShowNum(30,10,66,8,8);  
        delay_ms(100);
  }

提高ADC分辨率

使用过采样和求均值的方式提高ADC的分辨率

确定过采样率

根据要增加的分辨率位数计算过采样频率方程:

fos= 4w⋅ fs

fos是过采样频率,w是希望增加的分辨率位数,fs 是初始采样频率要求

求均值

举个例子:12位分辨率的ADC提高4位分辨率,采样频率就要提高4的4次方倍即256倍

即需要256次采集才能得到一次16位分辨率的数据

然后将这256次采集结果求和,求和的结果再右移4位,就得到提高分辨率后的结果

提高N 位分辨率,需要 右移N位

实现代码

需要将采样周期设置为1.5个ADC时钟周期,采样速度则更快

uint8_t g_adc_dma_sta;//DMA传输完成标志位

DMA_HandleTypeDef g_dma_adc_handle;
ADC_HandleTypeDef g_adc_dma_handle;

/* ADC单通道 */
void adc_dma_init(uint32_t mar)
{
	__HAL_RCC_DMA1_CLK_ENABLE();
	
    g_dma_adc_handle.Instance = DMA1_Channel1;/* DMA通道*/
    g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 外设到存储器模式 */
    g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//ADC数据寄存器独立模式时是低16位有效的,所以用半字
    g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    g_dma_adc_handle.Init.Mode = DMA_NORMAL;/* 外设流控模式,内存到内存不支持循环模式 */
    g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;/* 中等优先级 */
    HAL_DMA_Init(&g_dma_adc_handle);
	__HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);/* 关联 DMA 句柄,hdmatx 是外设句柄结构体的成员变量,在这里实际就是 UART1_Handler,如下图1.1所示*/
		
    ADC_ChannelConfTypeDef adc_ch_conf;
    g_adc_dma_handle.Instance = ADC1;
    g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 设置数据的对齐方式 */ 
    g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 扫描模式 */ 
    g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;/* DISABLE开启单次转换模式或者ENABLE连续转换模式 */ 
    g_adc_dma_handle.Init.NbrOfConversion = 1;/* 设置转换通道数目 */ 
    g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;/* 是否使用规则通道组间断模式 */ 
    g_adc_dma_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数 */ 
    g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* ADC 外部触发源选择 */ 
    HAL_ADC_Init(&g_adc_dma_handle);/*配置ADC工作参数*/
    
	HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);/*ADC校准*/
    
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* ADC 转换通道*/ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* ADC 转换顺序 */ 
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;/* ADC 采样周期,采样时间1.5个ADC时钟周期为例*/ 
    HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);/*配置ADC相应通道的相关参数*/
	
	HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 1);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
	
    HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);//长度为零,CNDTR为零,即先不传输,后面可以自己控制传输触发条件
    HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar ,0);
}

/* ADC MSP初始化函数 */
void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
    if(hadc->Instance == ADC1)
    {
        GPIO_InitTypeDef gpio_init_struct;
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_ADC1_CLK_ENABLE();
 
        gpio_init_struct.Pin = GPIO_PIN_1;
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct); 
        
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/*选择要配置的时钟源*/
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/*设置ADC分频系数*/
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/*配置ADC时钟源以及分频系数*/
    }
}

/* ADC DMA采集中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
    if (DMA1->ISR & (1<<1))//判断在通道有没有传输完成事件(TC),即判断DMA传输完成标志位
    {
        g_adc_dma_sta = 1;//DMA传输完成标志位
        DMA1->IFCR |= 1 << 1;//清除DMA_ISR寄存器中的对应TCIF标志,即清除DMA通道传输完成标志即
    }
//	if(__HAL_DMA_GET_FLAG(&g_dma_handle,DMA_FLAG_TC1))//获取TC位,即发送完成位的状态
//	{
//		g_adc_dma_sta = 1;//DMA传输完成标志位
//		__HAL_DMA_CLEAR_FLAG(&g_dma_handle,DMA_FLAG_TC1);//清除TC标志位
//	}
}

/* 使能一次ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
    ADC1->CR2 &= ~(1 << 0);//失能ADC
    
    DMA1_Channel1->CCR &= ~(1 << 0);//失能DMA
    while (DMA1_Channel1->CCR & (1 << 0));//等待失能DMA,即等待CCRx位0为0
    DMA1_Channel1->CNDTR = cndtr;//重装CNDTR的值,使能传输

    DMA1_Channel1->CCR |= 1 << 0;//使能DMA

    ADC1->CR2 |= 1 << 0;//使能ADC
    ADC1->CR2 |= 1 << 22;//启动A/D转换
    
//    __HAL_ADC_DISABLE(&g_adc_dma_handle);
//    
//    __HAL_DMA_DISABLE(&g_dma_adc_handle);
//    while (__HAL_DMA_GET_FLAG(&g_dma_adc_handle, __HAL_DMA_GET_TC_FLAG_INDEX(&g_dma_adc_handle)));
//    DMA1_Channel1->CNDTR = cndtr;
//    __HAL_DMA_ENABLE(&g_dma_adc_handle);
//    
//    __HAL_ADC_ENABLE(&g_adc_dma_handle);
//    HAL_ADC_Start(&g_adc_dma_handle);
}

main函数

#define ADC_OVERSAMPLE_TIMES    256                         /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
#define ADC_DMA_BUF_SIZE        ADC_OVERSAMPLE_TIMES*10  /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */

	uint32_t i;
	uint32_t adcx;
	uint32_t sum;
	float temp;
	adc_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */
	adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */
while(1)
  {
//	  OLED_ShowNum(30,10,66,8,8);  
	 if (g_adc_dma_sta == 1)
        {
			/* 计算DMA 采集到的ADC数据的平均值 */
            sum = 0;
            for (i = 0; i < ADC_DMA_BUF_SIZE; i++)   /* 累加 */
            {
                sum += g_adc_dma_buf[i];
            }
            adcx = sum /(ADC_DMA_BUF_SIZE /ADC_OVERSAMPLE_TIMES); /* 取平均值 */
            adcx >>= 4;   /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */
			OLED_ShowNum(40,1,adcx,8,8);      /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
            temp = (float)adcx * (3.3 / 65536);                  /* 获取计算后的带小数的实际电压值,比如3.1111 */
            adcx = temp;                                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
            OLED_ShowNum(1,10,adcx,1,8);      /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
            temp -= adcx;                                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
            temp *= 1000;                                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
            OLED_ShowNum(10,10,temp,3,8);  /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
            g_adc_dma_sta = 0;                                  /* 清除DMA采集完成状态标志 */
            adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动下一次ADC DMA采集 */
        }

   
        delay_ms(100);
  }
  • alipay_img
  • wechat_img
想法不去做终究就只是想法
最后更新于 2023-05-14