STM32笔记(十三)---ADC
ADC-电压采集
文章目录
一、ADC简介
以STM32f103 系列为例,3 个 ADC,精度为 12 位,每个 ADC 最多有 16 个外部通道。其中ADC1 和 ADC2 都有 16 个外部通道, ADC3 根据 CPU 引脚的不同通道数也不同,一般都有8 个外部通道。 ADC 的模式非常多,功能非常强大,具体的我们在功能框图中分析每个部分的功能。
ADC功能框图
图1 单个ADC功能框图
-
①电压输入范围
ADC 输入范围为: VREF- ≤ VIN ≤ VREF+。由 VREF-、 VREF+ 、 VDDA 、 VSSA、这四个外部引脚决定。 在设计原理图的时候一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0~3.3V。
如果想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量。
-
②输入通道
确定好 ADC 输入电压之后,那么电压怎么输入到 ADC?这里我们引入通道的概念,STM32 的 ADC 多达 18 个通道,其中外部的 16 个通道就是框图中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。这 16 个通道对应着不同的 IO 口, 具体是哪一个 IO 口可以从手册查询到。其中 ADC1/2/3 还有内部通道: ADC1 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。ADC3 的模拟通道 9、 14、 15、 16 和 17 连接到了内部的 VSS。
图2 STM32F103VET6 ADC 通道
外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16路,注入通道最多有 4 路。那这两个通道有什么区别?在什么时候使用?
规则通道
规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道。
注入通道
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。
- ③转换顺序
规则序列
规则序列寄存器有 3 个,分别为 SQR3、 SQR2、 SQR1。 SQR3 控制着规则序列中的第一个到第六个转换,对应的位为: SQ1[4:0]~SQ6[4:0],第一次转换的是位 4:0 SQ1[4:0],如果通道 16 想第一次转换,那么在 SQ1[4:0]写 16 即可。 SQR2 控制着规则序列中的第 7 到第12 个转换,对应的位为: SQ7[4:0]~SQ12[4:0],如果通道 1 想第 8 个转换,则 SQ8[4:0]写 1即可。 SQR1 控制着规则序列中的第 13 到第 16 个转换,对应位为: SQ13[4:0]~SQ16[4:0],如果通道 6 想第 10 个转换,则 SQ10[4:0]写 6 即可。具体使用多少个通道,由 SQR1 的位L[3:0]决定,最多 16 个通道。
图3 规则序列寄存器
注入序列
注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[2:0]决定。如果 JL 的 值小于 4 的话,则 JSQR 跟 SQR 决定转换顺序的设置不一样,第一次转换的不是 JSQR1[4:0],而是 JCQRx[4:0] , x = (4-JL),跟 SQR 刚好相反。如果 JL=00(1 个转换),那么转换的顺序是从 JSQR4[4:0]开始,而不是从 JSQR1[4:0]开始。当 JL 等于 4 时,跟 SQR 一样。
图4 注入序列寄存器
-
④触发源
ADC 转换可以由ADC 控制寄存器 2: ADC_CR2 的 ADON 这个位来控制,写 1 的时候开始转换,写 0 的时候停止转换,这个是最简单也是最好理解的开启 ADC 转换的控制方式。
除了这种庶民式的控制方法, ADC 还支持触发转换,这个触发包括内部定时器触发和外部 IO 触发。触发源有很多,具体选择哪一种触发源,由 ADC 控制寄存器 2:ADC_CR2 的EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。 EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要**,则由ADC 控制寄存器 2:ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位来**。其中 ADC3 的规则转换和注入转换的触发源与 ADC1/2 的有所不同,在框图上已经表示出来。 -
⑤转换时间
ADC 时钟
ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大是 14M,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,注意这里没有 1 分频。一般我们设置 PCLK2=HCLK=72M。
采样时间
ADC 使用若干个 ADC_CLK 周期对输入的电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置, ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5个周期,这里说的周期就是 1/ADC_CLK。 ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为: Tconv = 采样时间 +12.5 个周期。 当 ADCLK = 14MHZ (最高),采样时间设置为 1.5 周期(最快),那么总的转换时间(最短) Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。
一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。 -
⑥中断
转换结束中断
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断
服务程序。
模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTR 和 ADC_HTR 设置。例如我们设置高阈值是 2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。
DMA 请求
规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据直接存储在内存里面。要注意的是只有 ADC1 和 ADC3 可以产生 DMA 请求。有关DMA 请求需要配合《STM32F10X-中文参考手册》 DMA 控制器这一章节来学习。一般我们在使用 ADC 的时候都会开启 DMA 传输。电压转换
模拟电压经过 ADC 转换后,是一个 12 位的数字值,如果通过串口以 16 进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。
我们一般在设计原理图的时候会把 ADC 的输入电压范围设定在: 0~3.3v,因为 ADC是 12 位的,那么 12 位满量程对应的就是 3.3V, 12 位满量程对应的数字值是: 2^12。数值0 对应的就是 0V。 如果转换后的数值为 X , X 对应的模拟电压为 Y,那么会有这么一个等式成立: 2^12 / 3.3 = X / Y, => Y = (3.3 * X ) / 2^12。
二、ADC独立模式单通道中断采集实验
- 初始 ADC 用到的 GPIO;
- 设置 ADC 的工作参数并初始化;
- 设置 ADC 工作时钟;
- 设置 ADC 转换通道顺序及采样时间;
- 配置使能 ADC 转换完成中断,在中断内读取转换完数据;
- 使能 ADC;
- 使能软件触发 ADC 转换。
- 头文件相关宏定义
/*************ADC GPIO相关宏定义**************/
#define ADC_GPIO_APBxCLock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
/*************ADC 相关宏定义**************/
//选择ADC1
#define ADC_APBxCLock_FUN RCC_APB2PeriphClockCmd
#define ADC_CLK RCC_APB2Periph_ADC1
#define ADCx ADC1
//根据ADC引脚图 PC1为通道11
#define ADC_CHANNEL ADC_Channel_11
// ADC 中断相关宏定义
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
- ADC配置
__IO uint16_t ADC_ConvertedValue;
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打开ADC IO端口时钟
ADC_GPIO_APBxCLock_FUN(ADC_GPIO_CLK, ENABLE);
// 配置ADC IO引脚模式为模拟输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = ADC_PIN;
// ADC为模拟输入,无需配置速度
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 打开ADC时钟
ADC_APBxCLock_FUN(ADC_CLK,ENABLE);
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 只使用一个 ADC,属于独立模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 转换通道 1 个
ADC_InitStructure.ADC_NbrOfChannel = 1;
// 禁止扫描模式,多通道才要,单通道不需要
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_Init(ADCx, &ADC_InitStructure);
//配置ADC-CLK为6分频 72/6=12M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置 ADC 通道转换顺序为 1,第一个转换,采样时间为 55.5 个时钟周期
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
//开启ADC转换结束中断,在中断服务函数中读取转换值☆
ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);//EOC--规则通道
//开启ADC,并开始转换
ADC_Cmd(ADCx, ENABLE);
//初始化ADC校准寄存器
ADC_ResetCalibration(ADCx);
//等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx));
/***********ADC校准开启(不开启也可)**********/
//ADC开始校准
ADC_StartCalibration(ADCx);
//等待校准完成
while(ADC_GetCalibrationStatus(ADCx));
//由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
static void ADC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
ADC_NVIC_Config();
}
- ‘main函数’
extern __IO uint16_t ADC_ConvertedValue;
float ADC_ConvertedValueLocal;
int main(void)
{
USART_Config();
//ADC初始化
ADCx_Init();
printf("\r\n ----这是一个ADC实验(DMA传输)----\r\n");
while(1)
{
ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;
printf("\r\n The current AD value = 0x%04X \r\n",
ADC_ConvertedValue);
printf("\r\n The current AD value = %f V \r\n",
ADC_ConvertedValueLocal);
printf("\r\n\r\n");
//简易延时
Delay(0xffffee);
}
}
三、ADC独立模式多通道DMA采集实验
多通道与单通道ADC,DMA传输区别
-
IO口定义
-
DMA存储器地址与地址增量
-
DMA缓冲区数量
-
ADC转化通道数量
-
ADC扫描模式
-
ADC转化顺序与采样时间
-
ADC相关宏定义
/*************ADC GPIO相关宏定义**************/
#define ADC_GPIO_APBxCLock_FUN RCC_APB2PeriphClockCmd
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
/*************ADC 相关宏定义**************/
//选择ADC1
#define ADC_APBxCLock_FUN RCC_APB2PeriphClockCmd
#define ADC_CLK RCC_APB2Periph_ADC1
#define ADCx ADC1
//C1为通道11
#define ADC_CHANNEL ADC_Channel_11
// ADC 中断相关宏定义
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
// ADC1 对应 DMA1 通道 1, ADC3 对应 DMA2 通道 5, ADC2 没有 DMA 功能
#define ADC_DMA_CHANNEL DMA1_Channel1
#define ADC_DMA_APBxClock_FUN RCC_AHBPeriphClockCmd
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
// 转换通道个数
#define CHANNELNUM 5
#define ADC_PIN1 GPIO_Pin_0
#define ADC_CHANNEL1 ADC_Channel_10
#define ADC_PIN2 GPIO_Pin_1
#define ADC_CHANNEL2 ADC_Channel_11
#define ADC_PIN3 GPIO_Pin_3
#define ADC_CHANNEL3 ADC_Channel_13
#define ADC_PIN4 GPIO_Pin_4
#define ADC_CHANNEL4 ADC_Channel_14
#define ADC_PIN5 GPIO_Pin_5
#define ADC_CHANNEL5 ADC_Channel_15
- ADC相关配置
__IO uint16_t ADC_ConvertedValue[CHANNELNUM] = {0,0,0,0,0};
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 打开ADC IO端口时钟
ADC_GPIO_APBxCLock_FUN(ADC_GPIO_CLK, ENABLE);
// 配置 ADC IO 引脚模式
GPIO_InitStructure.GPIO_Pin = ADC_PIN1|ADC_PIN2|ADC_PIN3|ADC_PIN4|ADC_PIN5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
// ADC为模拟输入,无需配置速度
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADCx_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// DMA的时钟
ADC_DMA_APBxClock_FUN(ADC_DMA_CLK, ENABLE);
// 打开ADC时钟
ADC_APBxCLock_FUN(ADC_CLK,ENABLE);
/*****************DMA模式配置*******************/
// 复位 DMA 控制器
DMA_DeInit(ADC_DMA_CHANNEL);
// 配置 DMA 初始化结构体
// 外设基址为: ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&( ADCx->DR ));
// 存储器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
// 数据源来自外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 缓冲区大小,应该等于数据目的地的大小
DMA_InitStructure.DMA_BufferSize = CHANNELNUM;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据大小为半字,即两个字节
DMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_HalfWord;
// 内存数据大小也为半字,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个 DMA 通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止存储器到存储器模式,因为是从外设到存储器
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 初始化 DMA
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
// 使能 DMA 通道
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
/*****************ADC模式配置*******************/
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 只使用一个 ADC,属于独立模式
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
// 转换通道 1 个
ADC_InitStructure.ADC_NbrOfChannel = CHANNELNUM;
// 扫描模式,多通道才要,单通道不需要
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_Init(ADCx, &ADC_InitStructure);
//配置ADC-CLK为6分频 72/6=12M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 配置 ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADCx, ADC_CHANNEL5, 5, ADC_SampleTime_55Cycles5);
//ADC_DMA请求开启
ADC_DMACmd(ADCx, ENABLE);
//开启ADC,并开始转换
ADC_Cmd(ADCx, ENABLE);
//初始化ADC校准寄存器
ADC_ResetCalibration(ADCx);
//等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx));
/***********ADC校准开启(不开启也可)**********/
//ADC开始校准
ADC_StartCalibration(ADCx);
//等待校准完成
while(ADC_GetCalibrationStatus(ADCx));
//由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADCx, ENABLE);
}
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
}
- main函数
extern __IO uint16_t ADC_ConvertedValue[CHANNELNUM];
float ADC_ConvertedValueLocal[CHANNELNUM];
int main(void)
{
USART_Config();
//ADC初始化
ADCx_Init();
printf("\r\n ----这是一个ADC实验(DMA传输)----\r\n");
while(1)
{
ADC_ConvertedValueLocal[0] =(float)ADC_ConvertedValue[0]/4096*3.3;
ADC_ConvertedValueLocal[1] =(float)ADC_ConvertedValue[1]/4096*3.3;
ADC_ConvertedValueLocal[2] =(float)ADC_ConvertedValue[2]/4096*3.3;
ADC_ConvertedValueLocal[3] =(float)ADC_ConvertedValue[3]/4096*3.3;
ADC_ConvertedValueLocal[4] =(float)ADC_ConvertedValue[4]/4096*3.3;
printf("\r\n CH1 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
printf("\r\n CH2 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
printf("\r\n CH3 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
printf("\r\n CH2 value = %f V \r\n",ADC_ConvertedValueLocal[3]);
printf("\r\n CH3 value = %f V \r\n",ADC_ConvertedValueLocal[4]);
printf("\r\n\r\n");
Delay(0xffffee);
}
}
四、双重 ADC 同步规则模式采集实验
表格 1 双 ADC 模式的各种模式汇总
模式 简要说明
同步注入模式 | ADC1 和 ADC2 同时转换一个注入通道组,其中 ADC1 为主, ADC2 为 从。 转换的数据存储在每个 ADC 接口的 ADC_JDRx 寄存器中。 |
---|---|
同步规则模式 | ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主, ADC2 为 从。 ADC1 转换的结果放在 ADC1_DR 的低 16 位, ADC2 转换的结果放 在 ADC1_DR 的高十六位。 |
快速交叉模式 | ADC1 和 ADC2 交替采集一个规则通道组( 通常为一个通道)。当 ADC2 触发之后, ADC1 需要等待 7 个 ADCCLK 之后才能触发。 |
慢速交叉模式 | ADC1 和 ADC2 交替采集一个规则通道组( 只能为一个通道)。当 ADC2 触发之后, ADC1 需要等待 14 个 ADCCLK 之后才能触发。 |
交替触发模式 | ADC1 和 ADC2 轮流采集注入通道组,当 ADC1 所有通道采集完毕之后再采集 ADC2 的通道,如此循环。跟交叉采集不一样。 |
混合的规则/注入同步模式 | 规则组同步转换被中断,以启动注入组的同步转换。分开两个模式来理解就可以了,区别就是注入组可以中断规则组的转换。 |
混 合 的 同 步 规则+交替触发模式 | 规则组同步转换被中断,以启动注入组交替触发转换。分开两个模式理解就可以了,区别就是注入组可以中断规则组的转换。 |
混合同步注入交叉模式 | 交叉转换可以被同步注入模式中断。 这种情况下,交叉转换被中断,注入转换被启动。 |
选取同步规则模式为例。同步规则模式是 ADC1 和 ADC2 同时转换一个规则通道组, ADC1 是主, ADC2 是从, ADC1 转换的结果放在 ADC1_DR 的低 16位, ADC2 转换的结果放在 ADC1_DR 的高十六位。并且必须开启 DMA 功能。
外部触发来自 ADC1 的规则组多路开关(由 ADC1_CR2 寄存器的 EXTSEL[2:0]选择), 它同时给 ADC2 提供同步触发。为了简单起见, ADC1 选择软件触发, ADC2必须选择外部触发,这个外部触发来自于 ADC1 的规则组多路开关。为了实验的简单起见,实验中选取 ADC1 和 ADC2 各采集一个通道 。
图5 双重 ADC 同步规则模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c44vynwp-1594090383268)(C:\Users\Milliwhat\AppData\Roaming\Typora\typora-user-images\image-20200706225440547.png)]
编程要点:
-
初始化 ADC GPIO;
-
初始化 DMA 配置;
-
初始化 ADC 参数;
-
读取 ADC 采集的数据,并打印出来校正;
- ADC相关宏定义
// 双模式时,ADC1和ADC2转换的数据都存放在ADC1的数据寄存器,
// ADC1的在低十六位,ADC2的在高十六位
// 双ADC模式的第一个ADC,必须是ADC1
#define ADCx_1 ADC1
#define ADCx_1_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_1_CLK RCC_APB2Periph_ADC1
#define ADCx_1_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_1_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADCx_1_PORT GPIOC
#define ADCx_1_PIN GPIO_Pin_1
#define ADCx_1_CHANNEL ADC_Channel_11
// 双ADC模式的第二个ADC,必须是ADC2
#define ADCx_2 ADC2
#define ADCx_2_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_2_CLK RCC_APB2Periph_ADC2
#define ADCx_2_GPIO_APBxClock_FUN RCC_APB2PeriphClockCmd
#define ADCx_2_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADCx_2_PORT GPIOC
#define ADCx_2_PIN GPIO_Pin_4
#define ADCx_2_CHANNEL ADC_Channel_14
#define NOFCHANEL 1
// ADC1 对应 DMA1通道1,ADC3对应DMA2通道5,ADC2没有DMA功能
#define ADC_DMA_CHANNEL DMA1_Channel1
- ADC相关配置
__IO uint32_t ADC_ConvertedValue[NOFCHANEL] = {0};
/**
* @brief ADC GPIO 初始化
* @param 无
* @retval 无
*/
static void ADCx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// ADCx_1 GPIO 初始化
ADCx_1_GPIO_APBxClock_FUN ( ADCx_1_GPIO_CLK, ENABLE );
GPIO_InitStructure.GPIO_Pin = ADCx_1_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(ADCx_1_PORT, &GPIO_InitStructure);
// ADCx_2 GPIO 初始化
ADCx_1_GPIO_APBxClock_FUN ( ADCx_2_GPIO_CLK, ENABLE );
GPIO_InitStructure.GPIO_Pin = ADCx_2_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(ADCx_2_PORT, &GPIO_InitStructure);
}
/**
* @brief 配置ADC工作模式
* @param 无
* @retval 无
*/
static void ADCx_Mode_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 打开DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 打开ADC时钟
ADCx_1_APBxClock_FUN ( ADCx_1_CLK, ENABLE );
ADCx_2_APBxClock_FUN ( ADCx_2_CLK, ENABLE );
/* ------------------DMA模式配置---------------- */
// 复位DMA控制器
DMA_DeInit(ADC_DMA_CHANNEL);
// 配置 DMA 初始化结构体
// 外设基址为:ADC 数据寄存器地址
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&( ADCx_1->DR ));
// 存储器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
// 数据源来自外设
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 缓冲区大小,应该等于数据目的地的大小
DMA_InitStructure.DMA_BufferSize = NOFCHANEL;
// 外设寄存器只有一个,地址不用递增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 存储器地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据大小
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Word;
// 内存数据大小,跟外设数据大小相同
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止存储器到存储器模式,因为是从外设到存储器
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 初始化DMA
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
// 使能 DMA 通道
DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);
/* ----------------ADCx_1 模式配置--------------------- */
// 双ADC的规则同步
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;
// 扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道个数
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADCx_1, &ADC_InitStructure);
// 配置ADC时钟N狿CLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADCx_1, ADCx_1_CHANNEL, 1,
ADC_SampleTime_239Cycles5);
// 使能ADC DMA 请求
ADC_DMACmd(ADCx_1, ENABLE);
/* ----------------ADCx_2 模式配置--------------------- */
// 双ADC的规则同步
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;
// 扫描模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ;
// 连续转换模式
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
// 不用外部触发转换,软件开启即可
ADC_InitStructure.ADC_ExternalTrigConv =
ADC_ExternalTrigConv_None;
// 转换结果右对齐
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// 转换通道个数
ADC_InitStructure.ADC_NbrOfChannel = NOFCHANEL;
// 初始化ADC
ADC_Init(ADCx_2, &ADC_InitStructure);
// 配置ADC时钟为PCLK2的8分频,即9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
// 配置ADC 通道的转换顺序和采样时间
ADC_RegularChannelConfig(ADCx_2, ADCx_2_CHANNEL, 1,
ADC_SampleTime_239Cycles5);
/* 使能ADCx_2的外部触发转换 */
ADC_ExternalTrigConvCmd(ADC2, ENABLE);
/* ----------------ADCx_1 校准--------------------- */
// 开启ADC ,并开始转换
ADC_Cmd(ADCx_1, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADCx_1);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_1));
// ADC开始校准
ADC_StartCalibration(ADCx_1);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_1));
/* ----------------ADCx_2 校准--------------------- */
// 开启ADC ,并开始转换
ADC_Cmd(ADCx_2, ENABLE);
// 初始化ADC 校准寄存器
ADC_ResetCalibration(ADCx_2);
// 等待校准寄存器初始化完成
while(ADC_GetResetCalibrationStatus(ADCx_2));
// ADC开始校准
ADC_StartCalibration(ADCx_2);
// 等待校准完成
while(ADC_GetCalibrationStatus(ADCx_2));
// 由于没有采用外部触发,所以使用软件触发ADC转换
ADC_SoftwareStartConvCmd(ADCx_1, ENABLE);
}
/**
* @brief ADC初始化
* @param 无
* @retval 无
*/
void ADCx_Init(void)
{
ADCx_GPIO_Config();
ADCx_Mode_Config();
}
- main函数
// ADC1转换的电压值通过MDA方式传到SRAM
extern __IO uint32_t ADC_ConvertedValue[NOFCHANEL];
// 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal[NOFCHANEL*2];
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
uint16_t temp0=0 ,temp1=0;
// 配置串口
USART_Config();
// ADC 初始化
ADCx_Init();
printf("\r\n ----这是一个双ADC规则同步采集实验----\r\n");
printf("欢迎使用野火STM32开发板\n");
while (1)
{
// 取出ADC1数据寄存器的高16位,这个是ADC2的转换数据
temp0 = (ADC_ConvertedValue[0]&0XFFFF0000) >> 16;
// 取出ADC1数据寄存器的低16位,这个是ADC1的转换数据
temp1 = (ADC_ConvertedValue[0]&0XFFFF);
ADC_ConvertedValueLocal[0] =(float) temp0/4096*3.3;
ADC_ConvertedValueLocal[1] =(float) temp1/4096*3.3;
printf("\r\n ADCx_1 value = %f V \r\n",
ADC_ConvertedValueLocal[1]);
printf("\r\n ADCx_2 value = %f V \r\n",
ADC_ConvertedValueLocal[0]);
printf("\r\n\r\n");
Delay(0xffffee);
}
}
下一篇: STM32笔记(九)---串口通信