ADC简介
ADC,全称:Analog-to-Digital Converter,指模拟/数字转换器
ADC的特性参数
- 分辨率
- 表示ADC能辨别的最小模拟量,用二进制位数表示,比如:8、10、12、16位等
- 转换时间
- 表示完成一次A/D转换所需要的时间,转换时间越短,采样率就可以越高
- 精度
- 最小刻度基础上叠加各种误差的参数,精度受ADC性能、温度和气压等影响
- 量化误差
- 用数字量近似表示模拟量,采用四舍五入原则,此过程产生的误差为量化误差
STM32各系列ADC的主要特性
ADC工作原理
结构框图
①参考电压/模拟部分电压
②输入通道
③转换序列
④触发源
⑤转换时间
⑥数据寄存器
⑦中断
输入通道
ADC1 | IO | ADC2 | IO | ADC3 | IO |
通道0 | PA0 | 通道0 | PA0 | 通道0 | PA0 |
通道1 | PA1 | 通道1 | PA1 | 通道1 | PA1 |
通道2 | PA2 | 通道2 | PA2 | 通道2 | PA2 |
通道3 | PA3 | 通道3 | PA3 | 通道3 | PA3 |
通道4 | PA4 | 通道4 | PA4 | 通道4 | PF6 |
通道5 | PA5 | 通道5 | PA5 | 通道5 | PF7 |
通道6 | PA6 | 通道6 | PA6 | 通道6 | PF8 |
通道7 | PA7 | 通道7 | PA7 | 通道7 | PF9 |
通道8 | PB0 | 通道8 | PB0 | 通道8 | PF10 |
通道9 | PB1 | 通道9 | PB1 | 通道9 | 连接内部VSS |
通道10 | PC0 | 通道10 | PC0 | 通道10 | PC0 |
通道11 | PC1 | 通道11 | PC1 | 通道11 | PC1 |
通道12 | PC2 | 通道12 | PC2 | 通道12 | PC2 |
通道13 | PC3 | 通道13 | PC3 | 通道13 | PC3 |
通道14 | PC4 | 通道14 | PC4 | 通道14 | 连接内部VSS |
通道15 | PC5 | 通道15 | PC5 | 通道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
数据寄存器
中断
中断事件 | 事件标志 | 使能控制位 |
规则通道转换结束 | EOC | EOCIE |
注入通道转换结束 | JEOC | JEOCIE |
设置了模拟看门狗状态位 | AWD | AWDIE |
溢出(F1没有) | OVR | OVRIE |
DMA请求(只适用于规则组):
规则组每个通道转换结束后,除了可以产生中断外,还可以产生DMA请求,我们利用DMA及时把转换好的数据传输到指定的内存里,防止数据被覆盖。
单次和连续转换
CONT位 | 0 | 1 |
转换模式 | 单次转换模式 | 连续转换模式 |
转换组/转换模式 | 单次转换模式(只触发一次转换) | 连续转换模式(自动触发下一次转换) 注意:只有规则组才能触发该模式 |
规则组 | 转换结果被储存在ADC_DR EOC(转换结束)标志位被置1 如果设置了EOCIE位,则产生中断 然后ADC停止 | 转换结果被储存在ADC_DR EOC(转换结束)标志位被置1 如果设置了EOCIE位,则产生中断 |
注入组 | 转换结果被储存在ADC_DRJx JEOC(转换结束)标志位被置1 如果设置了JEOCIE位,则产生中断 然后ADC停止 | 转换结果被储存在ADC_DRJx JEOC(转换结束)标志位被置1 如果设置了JEOCIE位,则产生中断 自动注入:将JAUTO位置1 |
扫描模式
SCAN位 | 0 | 1 |
扫描模式 | 关闭扫描模式 | 使用扫描模式 |
关闭扫描模式 | 使用扫描模式 |
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规则序列寄存器 1(ADC_SQRx)
ADC规则数据寄存器 (ADC_DR)
单通道ADC采集
普通模式
相关HAL库函数介绍
函数 | 主要寄存器 | 主要功能 |
HAL_ADC_Init() | CR1、CR2 | 配置ADC工作参数 |
HAL_ADCEx_Calibration_Start() | CR2 | ADC校准 |
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() | CR2 | ADC校准 |
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);
}
Comments NOTHING