ADC简介:有打moba游戏的别搞混了,这不是射手adc。在32中,ADC的全称为:Analog-to-Digital Converter,指模拟/数字转换器 也就是模拟-数字电路的转换器。其实通俗的来讲,它就是一个电压表。
目录
一.ADC原理
1.ADC框图
重点看红框内部的结构就行了,通过接触GPIO来将外接设备的模拟电压进行转换,变为数字逻辑电平,这就是它的主要功能。
2.通道
再经典不过的东西,TIM,DMA都有的东西。功能也和它们差不多,这里对应的自然就是GPIO。不同的通道固定只能监听固定的GPIO口。
这里唯一不同的点就是不同的ADC(比如ADC1和ADC2)同时监听PA0口并不会像其他外设一样出问题,完全可以同时监听(但本质上是间歇性监听,就是不断轮番监听)。另外一般在应用场景中ADC是要同时监听多个通道的。
3.规则组/注入组&转换顺序
规则组:如果把ADC看作一个测量电压的机器,那么规则组就是他的流水线。其上面按Rank摆放着一组通道(GPIO口),ADC就依次测量并且把数据放在寄存器中。
注入组:很简单,注入组看作中断执行的流水线即可。(假设正在执行规则组此时进来一组注入组则优先执行完注入组再回过头执行规则组。
转换顺序:由三个寄存器控制,看一眼图就行。
4.转换触发
大体来说其实就是硬件自动触发还是软件控制触发:
可以看到要么就是各种定时器或者外部中断触发要么就是软件触发。一般还是软件触发用的比较多也比较方便。
5.周期
首先ADC的是中原是PCLK,他的时钟源不能超过14Mhz。再应用中,通过选择采集时间来控制对应的周期:
采样时间随箭头增长。
6.转换模式
主要分为三种:
扫描模式:只循环测量规则组的第一个(不论组里配置了几个通道)
循环模式:测量完后继续从头重复测量
间歇模式:不用。
另外,当哪一种模式都不配置的时候,它就变为普通模式,就是你触发它一次它测量一次:
7.校验
这个看起来可有可无的东西其实再最终结果中产生的影响还是蛮大的:
1.一定要校验,否则电压测量会有约0.05的误差
2.必须再初始化后进行,不然数据特别不正常。(别问问就是作者吃过瘪了)
二.配置
1.ADC基本配置流程(代码)
ADC的流程和其他基本外设配置的流程也极度相似:
ADC基本初始化(数据对齐;搬运模式;转换触发)HAL_ADC_Init()
|
通道配置(通道选择;测量时间;规则通道Rank选择)HAL_ADC_ConfigChannel()
|
校验 HAL_ADCEx_Calibration_Start()
|
经典Msp配置(对应GPIO口配置;时钟使能;分频数选择;对应IO口时钟来源)HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
|
软件触发 HAL_ADC_Start(&adc_init);
|
等待测量结束返回对应值 HAL_ADC_PollForConversion(&adc_init,10);
#include "adc.h"
ADC_HandleTypeDef adc_init = {0};
ADC_ChannelConfTypeDef adc_channel_init = {0};
void ADC_INIT(){
//ADC基本模式配置
adc_init.Instance = ADC1;
adc_init.Init.ContinuousConvMode = DISABLE; //连续转变模式配置
adc_init.Init.DiscontinuousConvMode = DISABLE; //间断转变模式配置
adc_init.Init.DataAlign = ADC_DATAALIGN_RIGHT; //测得数据对齐模式
adc_init.Init.ExternalTrigConv = ADC_SOFTWARE_START; //触发模式
adc_init.Init.NbrOfConversion = 1; //转变数量
adc_init.Init.NbrOfDiscConversion = 0; //间断模式转变量
adc_init.Init.ScanConvMode = DISABLE; //扫描模式
//ADC通道配置
adc_channel_init.Channel = ADC_CHANNEL_1; //通道选择
adc_channel_init.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //测量时间
adc_channel_init.Rank = ADC_REGULAR_RANK_1; //规则通道位置配置
HAL_ADC_Init(&adc_init);
HAL_ADC_ConfigChannel(&adc_init,&adc_channel_init);
HAL_ADCEx_Calibration_Start(&adc_init); //校验,否则电压测量会有约
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc){
if(hadc->Instance == ADC1){
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio_init;
gpio_init.Mode = GPIO_MODE_ANALOG;
gpio_init.Pin = GPIO_PIN_1;
//ADC时钟配置
RCC_PeriphCLKInitTypeDef PeriphClkInit;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; //ADC时钟分频
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; //外设时钟选择
HAL_GPIO_Init(GPIOA,&gpio_init);
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
}
uint32_t value_get(){
HAL_ADC_Start(&adc_init); //软件启动转换
HAL_ADC_PollForConversion(&adc_init,10); //等待转换结束
return (uint16_t)HAL_ADC_GetValue(&adc_init); //返回
}
2.进阶用法:ADC与DMA结合使用
流程和上面基本一样,只是有以下区别:
1.转换模式调为循环模式 adc_init.Init.ContinuousConvMode = ENABLE;
2.通道初始化需要同时初始化多个 adc_channel_init.Channel = ADC_CHANNEL_2;
3.ADC初始化转化数量需要更改 adc_init.Init.NbrOfConversion = 2
4.Msp引脚初始化需要增加 gpio_init.Pin = GPIO_PIN_1|GPIO_PIN_2;
5.数据对齐方式需要更改
dma_init.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
这个对齐方式是位了对应ADC的精度,配合后续传输和返回值的处理,后面会详细解释。
ADC部分:
#include "adc.h"
ADC_HandleTypeDef adc_init = {0};
ADC_ChannelConfTypeDef adc_channel_init = {0};
void ADC_INIT(){
//ADC基本模式配置
adc_init.Instance = ADC1;
adc_init.Init.ContinuousConvMode = ENABLE; //连续转变模式配置
adc_init.Init.DiscontinuousConvMode = DISABLE; //间断转变模式配置
adc_init.Init.ScanConvMode = DISABLE; //扫描模式配置
adc_init.Init.DataAlign = ADC_DATAALIGN_RIGHT; //测得数据对齐模式
adc_init.Init.ExternalTrigConv = ADC_SOFTWARE_START; //触发模式
adc_init.Init.NbrOfConversion = 2; //转变数量
adc_init.Init.NbrOfDiscConversion = 0; //间断模式转变量
//扫描模式
//ADC通道配置
adc_channel_init.Channel = ADC_CHANNEL_1; //通道选择
adc_channel_init.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //测量时间
adc_channel_init.Rank = ADC_REGULAR_RANK_1; //规则通道位置配置
HAL_ADC_Init(&adc_init);
HAL_ADC_ConfigChannel(&adc_init,&adc_channel_init);
adc_channel_init.Channel = ADC_CHANNEL_2; //通道选择
adc_channel_init.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; //测量时间
adc_channel_init.Rank = ADC_REGULAR_RANK_2; //规则通道位置配置
HAL_ADC_ConfigChannel(&adc_init,&adc_channel_init);
HAL_ADCEx_Calibration_Start(&adc_init); //校验,否则电压测量会有约0.05的误差
//注意:
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc){
if(hadc->Instance == ADC1){
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef gpio_init;
gpio_init.Mode = GPIO_MODE_ANALOG;
gpio_init.Pin = GPIO_PIN_1|GPIO_PIN_2;
//ADC时钟配置
RCC_PeriphCLKInitTypeDef PeriphClkInit;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6; //ADC时钟分频
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC; //外设时钟选择
HAL_GPIO_Init(GPIOA,&gpio_init);
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
}
DMA的配置则并不难,不多赘述,上代码:
#include "dma.h"
DMA_HandleTypeDef dma_init = {0};
extern ADC_HandleTypeDef adc_init;
void DMA_INIT(){
__HAL_RCC_DMA1_CLK_ENABLE();
dma_init.Instance = DMA1_Channel1;
dma_init.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //千万千万千万注意,M和P千万千万不能对应错啊
dma_init.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
dma_init.Init.MemInc = DMA_MINC_ENABLE; //内存递增使能
dma_init.Init.PeriphInc = DMA_PINC_DISABLE;
dma_init.Init.Direction = DMA_PERIPH_TO_MEMORY;
dma_init.Init.Priority = DMA_PRIORITY_MEDIUM;
dma_init.Init.Mode = DMA_CIRCULAR; //循环模式
HAL_DMA_Init(&dma_init);
__HAL_LINKDMA(&adc_init,DMA_Handle,dma_init);
}
最后主函数:
这里唯一要讲的地方就是红框内的部分,你很有可能会问:为什么不干脆定义成uint32还非要等返回接收的时候进行强转呢?
原因很简单,但不知道就很难。 首先主要是由于区别之一的字节对齐问题,ADC的精度是12位,按照字节对齐则正好占两个字节也就是uint16。但ADC的DMA地址是连续的,也就是如果该数据超过这一字节那么就把下个字节也拿来用了。所以如果一开始你就用uint32,那么一个缓存中就会存入两个通道的数据。像这样: