简介
ADC(Analog-to-digital converters,模数转换器)的作用是将外部的模拟信号转变为数字信号。
STM32F103的ADC使用的是12位逐次逼近型的模数转换器,共有18个通道,可测量16个外部和2个内部信号。ADC框图如下:
这个框图展示了ADC的18个输入源、注入组与规则组的结构、可触发的中断请求和DMA请求等。要注意的是ADC的输入范围是0~3.3V,如果超过3.3V可能会导致芯片烧毁。
代码实践
我们通过句柄ADC_HandleTypeDef来学习如何使用ADC,下面给出一个初始化实例
ADC_HandleTypeDef g_adc_handler = {0};
GPIO_InitTypeDef gpio_init_struct = {0};
g_adc_handler.Instance = ADC1;
g_adc_handler.Init.DataAlign = ADC_DATAALIGN_RIGHT;
g_adc_handler.Init.ScanConvMode = ADC_SCAN_DISABLE;
g_adc_handler.Init.ContinuousConvMode = DISABLE;
g_adc_handler.Init.NbrOfConversion = 1;
g_adc_handler.Init.DiscontinuousConvMode = DISABLE;
g_adc_handler.Init.NbrOfDiscConversion = 0;
g_adc_handler.Init.ExternalTrigConv = ADC_SOFTWARE_START;
HAL_ADC_Init(&g_adc_handler); /* 初始化ADC */
HAL_ADCEx_Calibration_Start(&g_adc_handler); /* 校准ADC */
/* 设置AD采集通道对应IO引脚工作模式 */
gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN; /* ADC通道对应的IO引脚 */
gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟 */
HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct);
Instance 配置使用的ADCx,这里使用的是ADC1。
Init是ADC_InitTypeDef,用来保存初始化ADC的参数。
DataAlign 数据对齐方式,由于ADC的DR数据寄存器是16位的,而分辨率是12位,即只用到16位中的12位,所以要选择数据是左对齐还是右对齐,一般是选择右对齐ADC_DATAALIGN_RIGHT。
ScanConvMode 扫描转换模式,当使能启用扫描模式时,ADC会采集每一个激活的ADC通道;当失能时,仅采集一个ADC通道。
ContinuousConvMode 连续模式,当连续模式被使能时,ADC就会自动进行一轮又一轮的采集。也就是持续采集,不需要等待触发信号。
NbrOfConversion 指定转换通道数,每使用一个ADC转换通道,就加一。
DiscontinuousConvMode 间断转换模式,一种特殊的转换模式,用于在特定条件下按组(分组)进行多通道采样。每次触发仅转换一个组内的通道。
NbrOfDiscConversion 指定间断转换通道数,每使用一个ADC转换通道,就加一。
ExternalTrigConv外部触发配置,可以设置不同定时器来作为ADC的触发信号,这里使用的软件触发ADC_SOFTWARE_START。
配置完句柄后调用HAL_ADC_Init()即可完成对ADC的初始化,函数HAL_ADCEx_Calibration_Start()是为了校准ADC,在官方的注释中,该函数的执行是可选的。
下面看看ADC的时钟配置,通过官方注释我们了解到,除了使能ADC的时钟外,还需要配置ADC的时钟。代码如下:
/* 使能ADC1时钟 */
__HAL_RCC_ADC1_CLK_ENABLE();
/* 设置ADC时钟 */
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC; /* ADC外设时钟 */
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6; /* 分频因子6时钟为72M/6=12MHz */
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); /* 设置ADC时钟 */
这里主要配置的是ADC的分频因子,可以调整ADC的频率。
之后还需要配置ADC通道。
/* 配置ADC通道 */
ADC_ChannelConfTypeDef adc_ch_conf = {0};
adc_ch_conf.Channel = ADC_ADCX_CHY; /* 通道 */
adc_ch_conf.Rank = ADC_REGULAR_RANK_1; /* 序列 */
adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; /* 采样时间,设置最大采样周期:239.5个ADC周期 */
HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf); /* 通道配置 *
ADC支持轮询和DMA方式获取数据。
轮询方式代码如下:
HAL_ADC_Start(&g_adc_handler); /* 开始转换 */
HAL_ADC_PollForConversion(&g_adc_handler, 10); /* 等待常规组转换完成 */
uint16_t adc_value = HAL_ADC_GetValue(&g_adc_handler);
DMA方式如下:
DMA_HandleTypeDef g_dma_adc_handle = {0}; /* 定义要搬运ADC数据的DMA句柄 */
uint32_t buffer[10];
__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
/* 初始化DMA */
g_dma_adc_handle.Instance = ADC_ADCX_DMACx; /* 设置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; /* 外设数据长度:16位 */
g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */
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与adc联系起来 */
/* 配置DMA数据流请求中断优先级 */
HAL_NVIC_EnableIRQ(ADC_ADCX_DMACx_IRQn);
HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, buffer, 10); /* 启动DMA,并开启中断 */
HAL_ADC_Start_DMA(&g_adc_dma_handle, &buffer, 10); /* 开启ADC,通过DMA传输结果 */
ADC采集的数据会被DMA输入到buffer中,可以设置一个DMA传输结束标志,当执行DMA中断处理函数时,对标志置一表示传输完成。
最后,还可以对ADC读取到的数据进行处理。由于分辨率是12位的,所以ADC采集到的数据范围在0 - 4095之间,我们可以将其转为0 - 3.3V电压,方便查看。
uint32_t result = buffer[i] * (3.3 / 4096)