一、如何用过采样和求均值的方式提高ADC的分辨率?
(1)如何确定过采样率
根据要增加的分辨率位数计算过采样频率方程:
假如ADC原来的分辨率是12位的,如果想提高为13位的,那么过采样频率就是原来采样频率的四倍,也就是说,原来一个周期采样一次,现在需要采样4次。这个分辨率是有局限性的,不可能无限提高它。
(2) 如何求均值
举个例子:12位分辨率的ADC提高4位分辨率,采样频率就要提高256倍,即需要256次采集才能得到一次16位分辨率的数据。
然后将这256次采集结果求和,求和的结果再右移4位,就得到提高分辨率后的结果。
注意:提高N 位分辨率,需要 右移N位
二、实验介绍
1、功能描述 通过DMA读取数据
通过ADC1通道1(PA1)过采样实现16位分辨率采集电压,并显示ADC转换的数字量及换算后的电压值
2、确定最小刻度
VREF+ = 3.3V ---> 0V ≤ VIN ≤ 3.3V --->最小刻度 = 3.3 / 65536
3、确定转换时间
采样时间设置为最小值1.5个ADC时钟周期,可以得到转换时间为1.17us * 256
4、模式组合
连续转换模式、不扫描模式
三、编程实战
通过ADC1的通道1(PA1)采集电压,16位分辨率
dma.c源程序
#include "./BSP/DMA/dma.h"
#include <string.h>
extern ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma;
//配置ADC1的DMA1的通道1请求
void DMA_Init(void)
{
//开启DMA1时钟
__HAL_RCC_DMA1_CLK_ENABLE();
//配置DMA1通道 因为ADC1连接在DMA1的通道1 这个在使用手册可以查找到
hdma.Instance = DMA1_Channel1;
//外设到内存
hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
//内存为16位
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
//内存递增
hdma.Init.MemInc = DMA_MINC_ENABLE;
//循环搬运
//当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。
hdma.Init.Mode = DMA_CIRCULAR;
//外设16位
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
//外设地址不递增
hdma.Init.PeriphInc = DMA_PINC_DISABLE;
//通道1优先级为中
hdma.Init.Priority = DMA_PRIORITY_MEDIUM;
//配置DMA的通道1为外设到内存
HAL_DMA_Init(&hdma);
//将DMA句柄于ADC句柄连接起来 可以理解为将这个DMA句柄拷贝到ADC句柄里的DMA句柄上
__HAL_LINKDMA(&hadc,DMA_Handle,hdma);
}
//标志DMA数据搬运完
uint8_t state = 0;
void DMA1_Channel1_IRQHandler(void)
{
if(DMA1->ISR & (1 << 1))
{
state = 1;
//清除中断标志位
DMA1->IFCR |= (1 << 1);
}
DMA1->IFCR |= (1 << 0);
}
adc.c源程序
#include "./BSP/ADC/adc.h"
#include "string.h"
extern DMA_HandleTypeDef hdma;
uint16_t ADC_data[256];
ADC_HandleTypeDef hadc;
//配置ADC1的通道1 PA1进行
void ADC_Init(void)
{
hadc.Instance = ADC1;
//配置ADC连续转换模式 就是ADC转换完成一次后会自动下一次转换
hadc.Init.ContinuousConvMode = ENABLE;
//转换结果采用右对齐
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
//不开启间断模式
hadc.Init.DiscontinuousConvMode = DISABLE;
//ADC触发选用软件触发
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
//设置ADC转换数量的 SQR1的L位
hadc.Init.NbrOfConversion = 1;
//设置间断模式写转换一次转换数量
hadc.Init.NbrOfDiscConversion = 0;
//不开启扫描模式 因为就一个通道
hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;
//设置ADC为软件触发转换
HAL_ADC_Init(&hadc);
//开启ADC校准
HAL_ADCEx_Calibration_Start(&hadc);
ADC_ChannelConfTypeDef sConfig;
//配置通道1
sConfig.Channel = ADC_CHANNEL_1;
//通道1第一个转换
sConfig.Rank = ADC_REGULAR_RANK_1;
//采样周期采用1.5
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
//配置ADC通道一为第一个转换
HAL_ADC_ConfigChannel(&hadc, &sConfig);
memset((void*)ADC_data,0,256);
//这句话最好写 不然下面那个会把半传输中断也开开 这里就会开一个中断 因为下面那个函数回把ADC半传输回调函数复制
//这句话不写也行 但是中断里边要清除所有位 不然卡在中断
HAL_DMA_Start_IT(&hdma, (uint32_t)&ADC1->DR, (uint32_t)ADC_data, 256);
//开启DMA传输 这个函数里边会把中断都打开
HAL_ADC_Start_DMA(&hadc, (uint32_t* )ADC_data, 256);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
//开启ADC1时钟
__HAL_RCC_ADC1_CLK_ENABLE();
//开启GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Mode = GPIO_MODE_ANALOG;
GPIO_Init.Pin = GPIO_PIN_1;
GPIO_Init.Pull = GPIO_NOPULL;
//设置PA1为模拟输入模式
HAL_GPIO_Init(GPIOA,&GPIO_Init);
//使能DMA1通道1中断
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
//设置中断优先级
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,2,2);
}