一、什么是ADC?
全称:Analog-to-Digital Converter,指模拟/数字转换器。
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。


12 位 ADC 是一种逐次逼近型模拟数字转换器 (0~4095(2^12))。它有多达 18 个通道 ,可测量 16 个外部和 2 个内部信号源 。各通道的 A/D 转换可以单次、连续、扫描或间断模式 执行。 ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。
ADC 的输入时钟不得超过 14MHz,它是由 PCLK2 经分频产生。
STM32F103C8T6 ADC资源:ADC1、ADC2,10 个外部输入通道
二、ADC工作原理(逐次逼近型)


三、ADC框图


规则组/注入组

四、转换顺序
每个 ADC 规则通道只有一个数据寄存器 ,16个通道一起共用这个寄存器 ,所以需要指定规则转换通道的转换顺序。
**规则通道中的转换顺序由三个寄存器控制:SQR1、SQR2、SQR3,它们都是32位寄存器。**SQR寄存器控制着转换通道的数目和转换顺序,只要在对应的寄存器位SQx中写入相应的通道,这个通道就是第x个转换。

和规则通道转换顺序的控制一样,注入通道的转换也是通过注入寄存器来控制,只不过只有一个JSQR寄存器 来控制,控制关系如下:

注入序列的转换顺序是从JSQx[ 4 : 0 ](x=4-JL[1:0])开始。只有当JL=4的时候,注入通道的转换顺序才会按照JSQ1、JSQ2、JSQ3、JSQ4的顺序执行。
五、触发转换方法
-
通过向控制寄存器 ADC-CR2 的 ADON 位写 1 来开启 ADC ,再将 SWSTART 位置 1 ,启动规则通道转换
-
也可以通过外部事件(如定时器)进行转换。

六、转化时间
计算最小的转换时间?
ADC 是挂载在 APB2 总线(PCLK2)上的,经过分频器得到 ADC 时钟(ADCCLK),最高 14 MHz。

72MHZ/2 = 36 MHZ > MAX14MHZ (不行)
72MHZ/4 = 18 MHZ > MAX14MHZ (不行)
72MHZ/6 = 12 MHZ < MAX14MHZ (行)
经过分频器得到 ADC 时钟(ADCCLK)的时钟频率为12MHZ
转换时间=采样时间+12.5个周期

12.5个周期是固定的,一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M, 采样周期设置为 1.5 个周期,
转换时间 = 采样时间 (最小如上图所示:1.5个周期)+12.5个周期 = 14个周期/12MHZ = 1.17us
算出最短的转换时间为 1.17us。


七、ADC寄存器及库函数介绍
7.1 ADC状态寄存器(ADC_SR)

7.2 ADC控制寄存器 1(ADC_CR1)


7.3 ADC控制寄存器 2(ADC_CR2)




7.4 ADC采样时间寄存器 1(ADC_SMPR1)

7.5 ADC采样时间寄存器 2(ADC_SMPR2)

7.6 ADC注入通道数据偏移寄存器x (ADC_JOFRx)(x=1..4)

7.7 ADC规则序列寄存器 1(ADC_SQR1)

7.8 ADC注入序列寄存器(ADC_JSQR)

7.9 ADC 注入数据寄存器x (ADC_JDRx) (x= 1..4)

7.10 ADC规则数据寄存器(ADC_DR)

八、常用的函数

九、ADC单通道采集实验
使用 ADC1 采集通道 1 的电压值,通道 1 连接光敏电阻传感器。

复制项目文件夹19-串口打印功能
重命名为43-ADC单通道采集实验

打开项目文件
加载文件
main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
adc_init();
printf("hello world!\r\n");
while(1)
{
//换算成电压的形式输出
printf("adc result: %f\r\n", (float)adc_get_result(ADC_CHANNEL_1) / 4096 * 3.3);
delay_ms(500);
}
}
adc.c
cpp
#include "adc.h"
ADC_HandleTypeDef adc_handle = {0};//ADC的句柄
//初始化ADC函数
void adc_init(void)
{
adc_handle.Instance = ADC1;//基地址
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;//要不要扫描?不需要扫描
adc_handle.Init.ContinuousConvMode = DISABLE;//连续模式?不连续转换模式
adc_handle.Init.NbrOfConversion = 1;//转换个数?1个
adc_handle.Init.DiscontinuousConvMode = DISABLE;//间断模式?不间断模式
adc_handle.Init.NbrOfDiscConversion = 0;//间断个数0
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;//触发方式:软件触发
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);//ADC校准
}
//初始化msp函数
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)//判断是不是ADC1
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//ADC时钟句柄
GPIO_InitTypeDef gpio_init_struct = {0};//GPIO的句柄
__HAL_RCC_ADC1_CLK_ENABLE();//打开ADC1的时钟
__HAL_RCC_GPIOA_CLK_ENABLE();//打开GPIO口的时钟
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
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频?6分频
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);//ADC时钟初始化函数
}
}
//通道配置的函数
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime)
{
//
ADC_ChannelConfTypeDef adc_ch_config = {0};
adc_ch_config.Channel = ch;//指定通道-外界传进来
adc_ch_config.Rank = rank;//序列-外界传进来
adc_ch_config.SamplingTime = stime;//取样时间-外界传进来
HAL_ADC_ConfigChannel(hadc, &adc_ch_config);//通道配置
}
//ADC获取转换结果
uint32_t adc_get_result(uint32_t ch)
{
//adc句柄,通道,指定的序列,采样时间
adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
HAL_ADC_Start(&adc_handle);//开启ADC
HAL_ADC_PollForConversion(&adc_handle, 10);//等待ADC转化完成
//(ADC1转化的结果放在DR寄存器的低16位,这里强转为uint16_t就可以直接获取,
//DR寄存器的高16位存放的是ACD2的转换结果)
return (uint16_t)HAL_ADC_GetValue(&adc_handle);//获取ADC转化的结果
}
adc,h
cpp
#ifndef __ADC_H__
#define __ADC_H__
#include "sys.h"
void adc_init(void);
uint32_t adc_get_result(uint32_t ch);
#endif
十、ADC单通道采集实验+DMA读取
使用 ADC1 采集通道 1 的电压值+DMA读取,通道 1 连接光敏电阻传感器。

复制项目文件43-ADC单通道采集实验
重命名为44-ADC单通道采集实验(DMA读取)
打开项目文件夹
main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
uint16_t adc_result = 0;
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
adc_dma_init((uint32_t *)&adc_result);
printf("hello world!\r\n");
while(1)
{
//换算成电压的形式输出
printf("adc result: %f\r\n", (float)adc_result / 4096 * 3.3);
delay_ms(500);
}
}
adc.c
cpp
#include "adc.h"
DMA_HandleTypeDef dma_handle = {0};//DMA的句柄
ADC_HandleTypeDef adc_handle = {0};//ADC的句柄
//初始化ADC函数
void adc_config(void)
{
adc_handle.Instance = ADC1;//基地址
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;//要不要扫描?不需要扫描
adc_handle.Init.ContinuousConvMode = ENABLE;//连续模式?连续转换模式
adc_handle.Init.NbrOfConversion = 1;//转换个数?1个
adc_handle.Init.DiscontinuousConvMode = DISABLE;//间断模式?不间断模式
adc_handle.Init.NbrOfDiscConversion = 0;//间断个数0
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;//触发方式:软件触发
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);//ADC校准
}
//初始化msp函数
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)//判断是不是ADC1
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//ADC时钟句柄
GPIO_InitTypeDef gpio_init_struct = {0};//GPIO的句柄
__HAL_RCC_ADC1_CLK_ENABLE();//打开ADC1的时钟
__HAL_RCC_GPIOA_CLK_ENABLE();//打开GPIO口的时钟
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
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频?6分频
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);//ADC时钟初始化函数
}
}
void dma_config(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;//通道1
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;//半字对齐
dma_handle.Init.MemInc = DMA_MINC_ENABLE;//内存增量失能模式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//半字对其
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;//内存增量失能模式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;//DMA优先级
dma_handle.Init.Mode = DMA_CIRCULAR;//模式:循环搬运模式
HAL_DMA_Init(&dma_handle);
//这行代码的作用是将ADC的句柄(adc_handle)与DMA的句柄(dma_handle)关联起来
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);//当ADC完成数据采集时,数据可以直接通过DMA传输到指定的内存地址,而不需要CPU的干预。
}
//通道配置的函数
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime)
{
//
ADC_ChannelConfTypeDef adc_ch_config = {0};
adc_ch_config.Channel = ch;//指定通道-外界传进来
adc_ch_config.Rank = rank;//序列-外界传进来
adc_ch_config.SamplingTime = stime;//取样时间-外界传进来
HAL_ADC_ConfigChannel(hadc, &adc_ch_config);//通道配置
}
//adc dma 初始化函数
void adc_dma_init(uint32_t *mar)
{
adc_config();
//adc句柄,通道,序列号,ADC采样的时间
adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
dma_config();
//启动adc,ADC转化完的数据通过DMA搬运出来,从ADC外设传输到存储器的长度
HAL_ADC_Start_DMA(&adc_handle, mar, 1);//mar是目标缓冲区的地址
}
adc.h
cpp
#ifndef __ADC_H__
#define __ADC_H__
#include "sys.h"
void adc_dma_init(uint32_t *mar);
#endif
十一、ADC多通道采集实验+DMA读取
使用 ADC1 采集通道 0 ~3 的电压值+DMA读取,通道 1 连接光敏电阻传感器。
复制项目文件44-ADC单通道采集实验(DMA读取)
重命名为:45-ADC多通道采集实验(DMA读取)
打开项目文件
main.c
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "uart1.h"
#include "adc.h"
uint16_t adc_result[4] = {0};
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
uart1_init(115200);
adc_dma_init((uint32_t *)&adc_result);
printf("hello world!\r\n");
while(1)
{
//换算成电压的形式输出
printf("通道0电压: %f\r\n", (float)adc_result[0] / 4096 * 3.3);
printf("通道1电压: %f\r\n", (float)adc_result[1] / 4096 * 3.3);
printf("通道2电压: %f\r\n", (float)adc_result[2] / 4096 * 3.3);
printf("通道3电压: %f\r\n\r\n", (float)adc_result[3] / 4096 * 3.3);
delay_ms(500);
}
}
adc.c
cpp
#include "adc.h"
DMA_HandleTypeDef dma_handle = {0};//DMA的句柄
ADC_HandleTypeDef adc_handle = {0};//ADC的句柄
//初始化ADC函数
void adc_config(void)
{
adc_handle.Instance = ADC1;//基地址
adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
adc_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;//要不要扫描?需要扫描
adc_handle.Init.ContinuousConvMode = ENABLE;//连续模式?连续转换模式
adc_handle.Init.NbrOfConversion = 4;//转换个数?4个通道数目
adc_handle.Init.DiscontinuousConvMode = DISABLE;//间断模式?不间断模式
adc_handle.Init.NbrOfDiscConversion = 0;//间断个数0
adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;//触发方式:软件触发
HAL_ADC_Init(&adc_handle);
HAL_ADCEx_Calibration_Start(&adc_handle);//ADC校准
}
//初始化msp函数
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC1)//判断是不是ADC1
{
RCC_PeriphCLKInitTypeDef adc_clk_init = {0};//ADC时钟句柄
GPIO_InitTypeDef gpio_init_struct = {0};//GPIO的句柄
__HAL_RCC_ADC1_CLK_ENABLE();//打开ADC1的时钟
__HAL_RCC_GPIOA_CLK_ENABLE();//打开GPIO口的时钟
gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;//引脚
gpio_init_struct.Mode = GPIO_MODE_ANALOG;//模式:虚拟量的输入
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;//外设的时钟选择:ADC
adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//分频?6分频
HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);//ADC时钟初始化函数
}
}
void dma_config(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
dma_handle.Instance = DMA1_Channel1;//通道1
dma_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
//内存相关配置
dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;//半字对齐
dma_handle.Init.MemInc = DMA_MINC_ENABLE;//内存增量失能模式
//外设相关配置
dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//半字对其
dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;//内存增量失能模式
dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM;//DMA优先级
dma_handle.Init.Mode = DMA_CIRCULAR;//模式:循环搬运模式
HAL_DMA_Init(&dma_handle);
//这行代码的作用是将ADC的句柄(adc_handle)与DMA的句柄(dma_handle)关联起来
__HAL_LINKDMA(&adc_handle, DMA_Handle, dma_handle);//当ADC完成数据采集时,数据可以直接通过DMA传输到指定的内存地址,而不需要CPU的干预。
}
//通道配置的函数
void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime)
{
//
ADC_ChannelConfTypeDef adc_ch_config = {0};
adc_ch_config.Channel = ch;//指定通道-外界传进来
adc_ch_config.Rank = rank;//序列-外界传进来
adc_ch_config.SamplingTime = stime;//取样时间-外界传进来
HAL_ADC_ConfigChannel(hadc, &adc_ch_config);//通道配置
}
//adc dma 初始化函数
void adc_dma_init(uint32_t *mar)
{
adc_config();
//adc句柄,通道,序列号,ADC采样的时间
adc_channel_config(&adc_handle, ADC_CHANNEL_0, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5);
adc_channel_config(&adc_handle, ADC_CHANNEL_1, ADC_REGULAR_RANK_2, ADC_SAMPLETIME_239CYCLES_5);
adc_channel_config(&adc_handle, ADC_CHANNEL_2, ADC_REGULAR_RANK_3, ADC_SAMPLETIME_239CYCLES_5);
adc_channel_config(&adc_handle, ADC_CHANNEL_3, ADC_REGULAR_RANK_4, ADC_SAMPLETIME_239CYCLES_5);
dma_config();
//启动adc,ADC转化完的数据通过DMA搬运出来,从ADC外设传输到存储器的长度
HAL_ADC_Start_DMA(&adc_handle, mar, 4);//mar是目标缓冲区的地址,4个通道
}
adc.h
cpp
#ifndef __ADC_H__
#define __ADC_H__
#include "sys.h"
void adc_dma_init(uint32_t *mar);
#endif