STM32学习笔记(14)——ADC初步应用
Posted Mount256
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32学习笔记(14)——ADC初步应用相关的知识,希望对你有一定的参考价值。
STM32学习笔记(14)——ADC初步应用
一、ADC 中断实验
【实现功能】使用 ADC 的一个通道进行测量,并通过中断读取测量结果。
【基本思路】本次实验我们使用 ADC2 的通道 11,查看电路原理图可知,通道 11 对应的是 PC1 口。
结合之前我们所学的内容,因为使用的是是单通道测量,因此工作模式应选择独立模式;连续扫描模式是用于多通道扫描的,因此不使用;由于需要不停地重复采集,因此应使用连续转换模式;不使用外部触发源,而是使用软件触发方式;最后,测量数据使用右对齐方式,以方便程序读取。
除此之外,我们还需要配置 ADC 转换时钟、通道 11 的转换顺序、采样时间,最后开启中断。别忘了每次测量之前,应该对 ADC 进行一次校准。
每测量完一次,便会产生 EOC 中断标志。在中断服务函数内部,即可读取测量结果,并且记得要清除中断标志位,为下一次中断做准备。
【编程要点】
- 初始化 GPIO:PC1 引脚;
- 初始化 ADC 结构体;
- 配置 NVIC 结构体;
- 配置 ADC 时钟;
- 配置 ADC 规则通道(转换顺序+采样时间);
- 使能 ADC 中断、使能 ADC;
- 校准 ADC,并等待校准完成;
- 软件触发 ADC 开始工作;
- 中断服务函数读取测量结果,并清除中断标志位。
部分代码如下:
1. adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
#define ADC_PORT GPIOC
#define ADC_PORT_CLK RCC_APB2Periph_GPIOC
#define ADC_PIN GPIO_Pin_1
#define ADC_x ADC2
#define ADC_CLK RCC_APB2Periph_ADC2
#define ADC_CHANNEL ADC_Channel_11
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
void ADC_Init_Config(void);
#endif /* __ADC_H */
2. adc.c
#include "adc.h"
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 复用输入模式
GPIO_InitStructure.GPIO_Pin = ADC_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1; // 参与转换的通道数为1
ADC_Init(ADC_x, &ADC_InitStructure);
RCC_ADCCLKConfig(RCC_PCLK2_Div8); // f(ADC) = PCLK2 / 8,其中PCLK2 = 72MHz
// 配置ADC规则通道的参数,最后两个参数分别是:规则通道的转换顺序 和 采样时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);
ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE); // 开启EOC中断
ADC_Cmd(ADC_x, ENABLE); // 使能ADC,但此时未使能触发方式,ADC不工作
ADC_StartCalibration(ADC_x); // ADC开始校准
while(ADC_GetCalibrationStatus(ADC_x) == SET); // 等待ADC校准完成
ADC_SoftwareStartConvCmd(ADC_x, ENABLE); // 软件触发ADC使其开始工作
}
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 ADC_Init_Config(void)
{
ADC_Mode_Config();
ADC_GPIO_Config();
ADC_NVIC_Config();
}
3. stm32f10x_it.c
#include "stm32f10x_it.h"
#include "adc.h"
__IO uint16_t ADC_ConversionValue = 0;
void ADC_IRQHandler(void)
{
if( ADC_GetITStatus(ADC_x, ADC_IT_EOC) == SET ) // 如果 EOC 中断标志位为 1
{
ADC_ConversionValue = ADC_GetConversionValue(ADC_x); // 读取测量数据
}
ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC); // 清除 EOC 中断标志位
}
4. main.c
#include "stm32f10x.h"
#include "usart.h"
#include "adc.h"
extern uint16_t ADC_ConversionValue;
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
int main(void)
{
float ADC_Result;
ADC_Init_Config();
USART_Config();
printf("\\r\\n开始测试!!!\\r\\n");
while(1)
{
ADC_Result = (float)ADC_ConversionValue / 4096 * 3.30; // 提示:最小分辨率为 3.30V / 2^12
printf("\\r\\n测试结果:%f V (0x%04X)\\r\\n", ADC_Result, ADC_ConversionValue); // 通过串口显示测量结果
Delay(0xffffee);
}
}
大家只需要用杜邦线将 PC1 引脚与待测量引脚连接在一起就行了,数据会在上位机上显示。
二、ADC_DMA 实验
很多情况下,我们不使用中断读取测量数据,主要原因是从中断发起到响应需要一定的滞后时间,并且频繁的响应会拖慢 CPU 的运行速度。使用 DMA 请求则可以解决这个问题。
以下我们分为两个部分:单通道测量和多通道测量。
1. ADC_DMA 单通道实验
【实现功能】使用 DMA 存储 单通道ADC 测量的数据。继续使用 通道11。
【基本思路】回顾一下我们之前所学的 DMA 知识:STM32 有 2 个 DMA ,DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。DMA1 的通道 1 对应的是 ADC1,DMA2 的通道 5 对应的是 ADC3,没有专门的通道对应至 ADC2。 因此我们选择使用 ADC1。
现在来解决一下 DMA 的结构体配置问题:
- 由于数据来源于 ADC_DR 寄存器,因此外设基地址(PeripheralBaseAddr) 应为 ADC_DR 寄存器;
- 我们用一个变量来存储测量结果,因此存储基地址(MemoryBaseAddr) 应为该变量地址;
- DMA 的传输方向显然是从外设到存储(DMA_DIR_PeripheralSRC) ;
- 显然,由于我们使用的是单通道 ADC,因此均不开启外设和存储基地址的增量模式(PeripheralInc & MemoryInc) ;
- 因为在单通道情况下,只使用 ADC_DR 寄存器的低 16 位,所以外设的数据宽度(PeripheralDataSize)和存储的数据宽度(MemoryDataSize) 均为 16 位,即半字(Half Word);
- 每次采集传输数据只有 1 个,因此传输数据个数(BufferSize) 为 1;
- 因为是不断采集,工作模式选择循环模式(DMA_Mode_Circular);
在配置完 DMA 后,需要通过函数 ADC_DMACmd 开启 ADC_DMA 请求。
【编程要点】
- 初始化 GPIO:PC1 引脚;
- 初始化 ADC 结构体;
- 初始化 DMA 结构体;
- 配置 ADC 时钟;
- 配置 ADC 规则通道(转换顺序+采样时间);
- 使能 ADC、使能 ADC_DMA;
- 校准 ADC,并等待校准完成;
- 软件触发 ADC 开始工作。
部分代码如下:
(1)adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
#define ADC_PORT GPIOC
#define ADC_PORT_CLK RCC_APB2Periph_GPIOC
#define ADC_PIN1 GPIO_Pin_1
#define ADC_PIN2 GPIO_Pin_2
#define ADC_PIN3 GPIO_Pin_3
#define ADC_PIN4 GPIO_Pin_4
#define ADC_x ADC1
#define ADC_CLK RCC_APB2Periph_ADC1
#define ADC_CHANNEL1 ADC_Channel_11
#define ADC_CHANNEL2 ADC_Channel_12
#define ADC_CHANNEL3 ADC_Channel_13
#define ADC_CHANNEL4 ADC_Channel_14
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL DMA1_Channel1
#define ADC_IRQ ADC1_2_IRQn
#define ADC_IRQHandler ADC1_2_IRQHandler
void ADC_Init_Config(void);
#endif /* __ADC_H */
(2)adc.c
#include "adc.h"
__IO uint16_t ADC_ConversionValue = 0;
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(ADC_PORT_CLK, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = ADC_PIN1;
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(ADC_CLK, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC_x, &ADC_InitStructure);
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_Cmd(ADC_x, ENABLE);
ADC_DMACmd(ADC_x, ENABLE); // 使能ADC_DMA请求
ADC_StartCalibration(ADC_x); // 开始校准
while(ADC_GetCalibrationStatus(ADC_x) == SET); // 等待校准完成
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
}
static void ADC_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC_x->DR)); // 外设基地址为ADC数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (&ADC_ConversionValue); // 存储器基地址为变量地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输源为外设(数据寄存器)
DMA_InitStructure.DMA_BufferSize = 1; // 每次传输1个数据
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 不使用增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; // 不使用增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 数据宽度:16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 数据宽度:16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
DMA_Cmd(ADC_DMA_CHANNEL, ENABLE);
}
void ADC_Init_Config(void)
{
ADC_Mode_Config();
ADC_GPIO_Config();
ADC_DMA_Config();
}
2. ADC_DMA 多通道实验
现在我们将上面程序修改一下,变成可以同时测量 4 个通道的功能。需要修改的地方其实很少,其实只要理解了结构体每一项的功能,就知道要修改哪里了。
首先当然需要使能 4 个 GPIO(PC1、PC2、PC3、PC4)了,很简单,这里就不放出代码了。然后需要定义一个数组用于存放 4 个测量结果:
__IO uint16_t ADC_ConversionValue[4] = {0, 0, 0, 0};
(1)ADC 通道数、规则通道的配置修改
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 多通道扫描,开启连续扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 4; // 使用 4 个通道
// 分别对 4 个规则通道配置转换顺序和采样时间
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL1, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL2, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL3, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL4, 4, ADC_SampleTime_55Cycles5);
(2)DMA 部分修改
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (ADC_ConversionValue); // 存储基地址为数组的首地址
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储基地址选择为增量模式
DMA_InitStructure.DMA_BufferSize = 4; // 传输 4 个数据
三、双 ADC 规则同步实验
【实现功能】使用 ADC1 和 ADC2(不能使用 ADC3)的同步规则模式,同时测量两个引脚(PC1 和 PC2)的电压值。
【基本思路】回顾一下同步规则模式:ADC1 和 ADC2 同时转换一个规则通道组,其中 ADC1 为主,ADC2 为从,ADC1 转换的结果放在ADC_DR低16位,ADC2 转换的结果放在ADC_DR的高16位。
ADC1 测量 PC1 引脚,ADC2 测量 PC2 引脚,每个 ADC 都是单通道测量。由于 ADC2 没有 DMA 功能,所以不需要对 ADC2 使能 DMA 功能。ADC1 采用软件触发方式,ADC2 采用外部触发方式,触发源是 ADC1。
【编程要点】
- 初始化 GPIO:PC1 和 PC2 引脚;
- 初始化 ADC 结构体;
- 初始化 DMA 结构体;
- 配置 ADC1/2 时钟;
- 配置 ADC1/2 规则通道(转换顺序+采样时间);
- 使能 ADC1/2、使能 ADC1_DMA;
- 校准 ADC1/2,并等待校准完成;
- 外部触发 ADC2、软件触发 ADC1 开始工作。
1. adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
/*******双ADC规则同步实验*******/
#define ADC_PORT GPIOC
#define ADC_PORT_CLK RCC_APB2Periph_GPIOC
#define ADC_PIN1 GPIO_Pin_1
#define ADC_PIN2 GPIO_Pin_2
#define ADC_PIN3 GPIO_Pin_3
#define ADC_PIN4 GPIO_Pin_4
#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL DMA1_Channel1
void ADC_Init_Config(void);
#endif /* __ADC_H */
2. adc.c
#include "adc.h"
__IO uint32_t ADC_ConversionValue = 0;
static void ADC_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // PC1
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // PC2
GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}
static void ADC_Mode_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC2, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET);
ADC_RegularChannelConfig(ADC2, ADC_Channel_12, 1, ADC_SampleTime_55Cycles5);
ADC_Init(ADC2, &ADC_InitStructure);
ADC_Cmd(ADC2, ENABLE);
// ADC_DMACmd(ADC2, ENABLE); // ADC2与DMA不相连
ADC_StartCalibration(ADC2);
while(ADC_GetCalibrationStatus(ADC2) == SET);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
ADC_ExternalTrigConvCmd (ADC2, ENABLE);
}
static void ADC_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) (&(ADC1->DR));
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) (&ADC_ConversionValue);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = 1; // 每次采集 1 个数据
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
DMA_Cmd(ADC_DMA_CHANNEL, ENABLE);
}
void ADC_Init_Config(void)
{
ADC_Mode_Config();
ADC_GPIO_Config();
ADC_DMA_Config();
}
3. main.c
#include "stm32f10x.h"
#include "usart.h"
#include "adc.h"
extern uint32_t ADC_ConversionValue;
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
int main(void)
{
float ADC_Result1 = 0, ADC_Result2 = 0;
ADC_Init_Config();
USART_Config();
printf("\\r\\nADC_DMA开始测试!!!\\r\\n");
while(1)
{
ADC_Result1 = (float)( (ADC_ConversionValue & 0xFFFF0000) >> 16) / 4096 * 3.30; // 高16位数据处理
ADC_Result2 = (float) STM32学习笔记(12)——定时器初步应用