1.ADC(模数转换器)原理:
把温度、压力、湿度或者声音等,对这些模拟信号通过对应的传感器器件转换成具有对应关系的电信号(一般是电压信号)。把电信号经过一系列处理转换成数字信号。从而反求出温度、压力、湿度或者声音对应的模拟信号大小。
2.ADC的重要名词
类似占空比是PWM是一个专用名词,所以ADC也有两个比较重要的名词。
1.分辩率:指数字量变化一个最小量时模拟信号的变化量,定义为满刻度与2n的比值。通常用数字信号的位数来表示。比如:12位的ADC(2的12次方=4095),那么4095就是分辨率。如果测量的电压范围是(0~3.3v)模拟量。那么对应的数字量(0~4095)。
实例一:STM32F407芯片有3个12位的ADC,参考电压是3.3V。 那么,有一个外部为1.25V的模拟量电压信号,经过ADC转换后对应的数字量d是多少? 12位ADC -> 212=4096;这样0~3.3V(参考电压),对应的ADC输出为:0~4095 那么,1.25/d=3.3/4095 -> d=1552(取整)。
2.ADC的参考电压:将模拟电压值转换为数字值的电压基准。由于数字信号本身不具有实际意义,仅仅表示一个相对大小,故任何一个模数转换器都需要一个参考模拟量作为转换的标准。比较常见的参考标准为最大的可转换信号大小,即模数转换器可进行转换的电压范围为:0V到REF(引脚输入的参考电压),输出的数字量则表示输入信号相对于参考信号的大小。
3.ADC的工作过程
由四部分组成模拟部分(如参考电压、比较器、可控积分器等)、采样保持部分、量化、编码。
1.模拟部分(输入模拟的电压信号)
2.采样保持部分:用一个比模拟信号快快的频率(可以根据香农定律确定采样频率)每隔一定的时间间隔,抽取信号的一个瞬时幅度值.
采样电路的要求:
1.在采样时间内,信号维持不变,以提供足够的转换时间。(在电路中用一个电容,因为电容有充放电作用,可以维持一段时间)
2.采样后保持原信号特征。
3. 香农定律:采样频率大于2倍输入信号频率的最大值
3.量化:采样后在时间上离散的样值序列,但每个样值的幅度仍然是一个连续的模拟量。所以量化就是对每个样值的幅度进行离散。
为了产生量化编码,在设计(或选择)AD器件时,首先应确定最小量化单位,即单位数字量所代表的模拟量。 如量化单位用△表示,量化过程为: 把要转换的模拟量除△,得: ①整数部分,用二进制表示,即得转换数字量。 ②余数部分,即量化误差。
误差处理:四舍五入——误差小。 只舍不入——误差大。
量化单位越小→转换位数越多→量化误差越小。
4.编码:采样、量化后的信号变成了一串幅度分级的脉冲信号,这串脉冲的包络代表了模拟信号,它本身也还不是数字信号,而是一种十进制信号,需要把它转换成数字编码脉冲。下面是转换的过程。
4.ADC的类型
并行比较型,逐次逼近型,双积分型,V/F转换型。
一般在STM32内部都是逐次逼近型(听名字就是一步一步逼近实际值(即转换的数字量大小)。类型不同精度与速度可能会不同。不是硬件设计也不需要去了解。
5.ADC的主要技术指标(只介绍两个重要的)
1.分辩率(Resolution): 指数字量变化一个最小量时模拟信号的变化量,定义为满刻度与2n的比值。分辩率又称精度,通常以数字信号的位数来表示。
2.转换速率(Conversion Rate): 是指完成一次从模拟转换到数字的AD转换所需的时间的倒数。采样时间则是另外一个概念,是指两次转换的间隔。为了保证转换的正确完成,采样速率(Sample Rate)必须小于或等于转换速率。
6.STM32F407内部ADC的介绍(看手册就行)
STM32F407内部集成3个有最高12位ADC(ADC1、ADC2、ADC3),它们是逐次逼近型模数转换器。
主要特性: 可配置的转换精度:6位,8位,10位,12位 转换电压范围: 0 ~ VREF+(一般接到3.3V电源,不能超过STM32芯片电源电压) 19个转换通道:16个外部通道(IO引脚) + 3个内部通道(温度传感器、内部电压参考、电池供电监测)。
采样时间可配置 扫描方向可配置 多种转换模式:单次(转换一次就结束),连续
数据存放对齐方式可配置:左对齐,右对齐(ADC的结果存储在一个左对齐或右对齐的 16 位数据寄存器中)
启动转换方式可配置:软件触发,硬件触发。(就是启动触发的方式)
可设置上下门限的模拟看门狗 DMA功能 在转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断。
7.ADC功能框图:
1.ADC的参考电压: 1.8<= 参考电压<= 3.3v
2.输入通道:有16个内部通道,3个内部通道。(具体的对应的引脚看数据手册)
3.注入通道与规则通道
规则通道(只能最多16个):规规矩矩按照顺序来转换(可按任意顺序在任意通道上完成)
注入通道(只能最多4个):注入可以理解为插队。它是一种在规则通道转换的时候强行插入要转换的一种。这点和中断有点像,当规则通道转换中,有注入通道插队,那么得先转换注入通道的,然后才倒回来转换规则通道。
4.启动触发的触发方式:
分为软件触发与硬件触发(内部定时器触发和外部IO触发)。如果选择外部触发事件还可以选择控制触发极性,可以有4种状态:禁止触发检测、上升沿检测、下降沿检测以及上升沿和下降沿均检测。
5.ADC的时钟
先查手册ADC挂载在哪个总线:如果挂载在PCLK2(84MHZ),但是这里有一个要求就是分频后的频率不能大于36MHZ。如果我4分频那么就是21MHz(没有大于36MHZ)。
采样时间:ADC需要若干个ADC_CLK周期完成对输入的电压进行采样。
(转换时间)TCONV=采样时间+12个周期
每个通道可以分别设置成不同的时间进行采样。其中采样的周期最小是3个,想要通过设置达到 最快的采样,就设置周期为3个,周期等于1/ADC_CLK。 因为ADC总的转换时间和ADC的输入时钟以及采样时间有关,所以其计算公式为: TCONV=采样时间+12个周期 其中12个周期是采集12位AD时间是固定的。当ADC_CLK=30MHz,ADC为3周期,那么总的转换时间为:TCONV=3+12=15个周期=0.5us。
6.数据寄存器
注入通道每个通道都有一个数据寄存器。规则通道只有一个,那么当一个通道采集完放到数据寄存器。如果不去读取。当采集下一个通道完成时就会产生溢出(有溢出中断)。用DMA传输就不会当一个通道转换完成就自动传输了。
7.模拟看门狗
如果ADC转换的模拟电压低于阈值下限或高于阈值上限(通过配置寄存器设置下限,上限),则AWD模拟看门狗状态位会置1。这些阈值在ADC_HTR和ADC_LTR的16位寄存器的12个最低有效位中进行编程。可以使用ADC_CR1寄存器中的AWDIE位使能中断。 阈值与ADC_CR2寄存器中的ALIGN位的所选对齐方式无关。在对齐之前,会将模拟电压与阈值上限和下限进行比较。
8.ADC中断
当模拟看门狗状态位和溢出状态位分别置1时,规则组和注入组在转换结束时可能会产生中断。可以使用单独的中断使能位以实现灵活性。
8.总结:
这是ADC的基本介绍,其实还有许多高级用法用的相对少。
9.实战
1.单通道采集
1.cubeMX的配置:
2.代码
ADC.h文件
#ifndef __ADC_H__
#define __ADC_H__
#include "stm32f4xx_hal.h"
// 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
/********************ADC输入通道(引脚)配置**************************/
#define ADCx_RCC_CLK_ENABLE() __HAL_RCC_ADC1_CLK_ENABLE()
#define ADCx_RCC_CLK_DISABLE() __HAL_RCC_ADC1_CLK_DISABLE()
#define ADCx ADC1
// PA3
#define ADC_GPIO_ClK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define ADC_GPIO GPIOA
#define ADC_GPIO_PIN GPIO_PIN_3
#define ADC_CHANNEL ADC_CHANNEL_3
/* 扩展变量 ------------------------------------------------------------------*/
extern ADC_HandleTypeDef hadcx;
/* 函数声明 ------------------------------------------------------------------*/
void MX_ADCx_Init(void);
#endif /* __ADC_H__ */
ADC.C
#include "adc/bsp_adc.h"
void MX_ADCx_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
hadcx.Instance = ADCx;
hadcx.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; //4分频,84/4=21MHz
hadcx.Init.Resolution = ADC_RESOLUTION_12B; //ADC分辨率12位
hadcx.Init.ScanConvMode = DISABLE; //失能扫描模式
hadcx.Init.ContinuousConvMode = ENABLE; //使能连续转换模式不采用单次
hadcx.Init.DiscontinuousConvMode = DISABLE;//失能不连续转换模式
hadcx.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;//外设触发边沿极性
hadcx.Init.ExternalTrigConv = ADC_SOFTWARE_START;//软件触发
hadcx.Init.DataAlign = ADC_DATAALIGN_RIGHT;//右对齐
hadcx.Init.NbrOfConversion = 1;//规则序列转换通道数目
hadcx.Init.DMAContinuousRequests = DISABLE;//DMA连续请求失能
hadcx.Init.EOCSelection = ADC_EOC_SINGLE_CONV;//单通道采样完成就进入中断
HAL_ADC_Init(&hadcx);
/* 单个配置采样通道 */
sConfig.Channel = ADC_CHANNEL; // 选择通道
sConfig.Rank = 1; //转换序列排序
sConfig.SamplingTime = ADC_SAMPLETIME_56CYCLES; //采样时间
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
}
/**
* 函数功能: ADC外设初始化配置
* 输入参数: hadc:AD外设句柄类型指针
* 返 回 值: 无
* 说 明: 该函数被HAL库内部调用
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hadc->Instance==ADCx)
{
/* 外设时钟使能 */
ADCx_RCC_CLK_ENABLE();
/* AD转换通道引脚时钟使能 */
ADC_GPIO_ClK_ENABLE();
/* AD转换通道引脚初始化 */
GPIO_InitStruct.Pin = ADC_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; //模拟输入(一个要是模拟输入)
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉(否则会有影响)
HAL_GPIO_Init(ADC_GPIO, &GPIO_InitStruct);
/* 外设中断优先级配置和使能中断 */
HAL_NVIC_SetPriority(ADC_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
}
}
main.c
#include "stm32f4xx_hal.h"
#include "string.h"
#include "usart/bsp_debug_usart.h"
#include "adc/bsp_adc.h"
#define COVER_BUFFER_SIZE ((uint32_t) 100)
float ADC_ConvertedValueLocal; // 保存采集到的数字量转换成模拟量的值
__IO uint32_t ADC_ConvertedValue; //保存采集到的数字量
int32_t ADC_Aver_Value[COVER_BUFFER_SIZE]; // 保存采集100次的ADC值
uint16_t times=0; // 记录采集的次数
__IO uint8_t finish_flag=0; // 采集100次的完成标志
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
__HAL_RCC_PWR_CLK_ENABLE(); //使能PWR时钟
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); //设置调压器输出电压级别1
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 外部晶振,8MHz
RCC_OscInitStruct.HSEState = RCC_HSE_ON; //打开HSE
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; //打开PLL
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; //PLL时钟源选择HSE
RCC_OscInitStruct.PLL.PLLM = 8; //8分频MHz
RCC_OscInitStruct.PLL.PLLN = 336; //336倍频
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; //2分频,得到168MHz主时钟
RCC_OscInitStruct.PLL.PLLQ = 7; //USB/SDIO/随机数产生器等的主PLL分频系数
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟:168MHz
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB时钟: 168MHz
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // APB1时钟:42MHz
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // APB2时钟:84MHz
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
HAL_RCC_EnableCSS(); // 使能CSS功能,优先使用外部晶振,内部时钟源为备用
// HAL_RCC_GetHCLKFreq()/1000 1ms中断一次
// HAL_RCC_GetHCLKFreq()/100000 10us中断一次
// HAL_RCC_GetHCLKFreq()/1000000 1us中断一次
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); // 配置并启动系统滴答定时器
/* 系统滴答定时器时钟源 */
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* 系统滴答定时器中断优先级配置 */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
int main(void)
{
uint8_t i=0;
int32_t ADC_value=0;
/* 复位所有外设,初始化Flash接口和系统滴答定时器 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化串口并配置串口中断优先级 */
MX_DEBUG_USART_Init();
printf("----这是一个ADC单通道电压采集实验-----\n");
/* ADC 初始化 */
MX_ADCx_Init();
/* 启动AD转换并使能AD中断(启动转换完成与溢出中断) */
HAL_ADC_Start_IT(&hadcx);
/* 无限循环 */
while (1)
{
if(finish_flag==1)
{
for(i=0;i<COVER_BUFFER_SIZE;i++)
{
/* 将数据累加至ADC_value变量 */
ADC_value+=ADC_Aver_Value[i];
}
ADC_value=ADC_value/COVER_BUFFER_SIZE; // 采集100的平均值 (采集到的数字量)
/* 3.3为AD转换的参考电压值,stm32的AD转换为12bit,2^12=4095,
即当输入为3.3V时,AD转换结果为4096 */
ADC_ConvertedValueLocal =(double)ADC_value*3.3/4095; // 把采集到的数字量转模拟量(范围0~3.3v)
printf("AD转换原始值 = 0x%04X \r\n", ADC_value);
printf("计算得出电压值 = %f V \r\n",ADC_ConvertedValueLocal);
finish_flag=0;
ADC_value=0;
HAL_Delay(1000); // 延时1s在采集
}
}
}
/**
* 函数功能: AD转换结束回调函数
* 输入参数: hadc:AD设备类型句柄
* 返 回 值: 无
* 说 明: 无
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
ADC_ConvertedValue=0;
ADC_ConvertedValue=HAL_ADC_GetValue(&hadcx); // 读取数据寄存器的值
times++;
if(times==COVER_BUFFER_SIZE)
{
finish_flag=1;
times=0;
}
ADC_Aver_Value[times]=ADC_ConvertedValue; // 采集完成一个ADC放到数组里直到采集100次标志采集完成
}
总结:在转换完成中断里读取数据存储器的值
2.单通道(DMA传输)
cubeMX DMA的配置(用DMA要查表对应ADC在DMA的哪个数据流通道)
代码:
ADC.h
#ifndef __ADC_H__
#define __ADC_H__
/********************ADC输入通道(引脚)配置**************************/
#define ADCx_RCC_CLK_ENABLE() __HAL_RCC_ADC1_CLK_ENABLE()
#define ADCx_RCC_CLK_DISABLE() __HAL_RCC_ADC1_CLK_DISABLE()
#define DMAx_RCC_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE()
#define ADCx ADC1
#define ADCx_DMA_IRQx DMA2_Stream0_IRQn
#define ADCx_DMA_IRQx_Handler DMA2_Stream0_IRQHandler
#define DMAx__Stream_x DMA2_Stream0
#define DMAx__CHANNEL_x DMA_CHANNEL_0 // DMA2数据流0
#define ADC_GPIO_ClK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define ADC_GPIO GPIOA
#define ADC_GPIO_PIN GPIO_PIN_3
#define ADC_CHANNEL ADC_CHANNEL_3
/* 扩展变量 ------------------------------------------------------------------*/
extern ADC_HandleTypeDef hadcx; // ADC初始化结构体
extern DMA_HandleTypeDef hdma_adcx; // DMA初始化结构体
/* 函数声明 ------------------------------------------------------------------*/
void MX_ADCx_Init(void);
void MX_DMA_Init(void) ;
ADC.c
#include "adc/bsp_adc.h"
#include "usart/bsp_debug_usart.h"
ADC_HandleTypeDef hadcx;
DMA_HandleTypeDef hdma_adcx;
void MX_ADCx_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
hadcx.Instance = ADCx;
hadcx.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;//4分频,84/4=21MHz
hadcx.Init.Resolution = ADC_RESOLUTION_12B;//12位分辨率
hadcx.Init.ScanConvMode = DISABLE;//扫描模式失能
hadcx.Init.ContinuousConvMode = ENABLE;//连续转换使能
hadcx.Init.DiscontinuousConvMode = DISABLE;//非连续采集转换失能
hadcx.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;//外部触发源的极性
hadcx.Init.ExternalTrigConv = ADC_SOFTWARE_START;//软件触发
hadcx.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
hadcx.Init.NbrOfConversion = 1;//规则通道转换数
hadcx.Init.DMAContinuousRequests = ENABLE;//DMA连续请求使能
hadcx.Init.EOCSelection = ADC_EOC_SINGLE_CONV;//采样完成进入中断
HAL_ADC_Init(&hadcx);
/* 配置采样通道 */
sConfig.Channel = ADC_CHANNEL;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;//采样时间
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
}
void MX_DMA_Init(void)
{
DMAx_RCC_CLK_ENABLE();
/* 外设中断优先级配置和使能中断 */
HAL_NVIC_SetPriority(ADCx_DMA_IRQx, 0, 0);
HAL_NVIC_EnableIRQ(ADCx_DMA_IRQx);
}
/**
* 函数功能: ADC外设初始化配置
* 输入参数: hadc:AD外设句柄类型指针
* 返 回 值: 无
* 说 明: 该函数被HAL库内部调用
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hadc->Instance==ADCx)
{
/* 外设时钟使能 */
ADCx_RCC_CLK_ENABLE();
/* AD转换通道引脚时钟使能 */
ADC_GPIO_ClK_ENABLE();
/* AD转换通道引脚初始化 */
GPIO_InitStruct.Pin = ADC_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;//模拟输入
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_GPIO, &GPIO_InitStruct);
hdma_adcx.Instance = DMAx__Stream_x;
hdma_adcx.Init.Channel = DMAx__CHANNEL_x;
hdma_adcx.Init.Direction = DMA_PERIPH_TO_MEMORY; //DMA方向:外设->内存
hdma_adcx.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址不变
hdma_adcx.Init.MemInc = DMA_MINC_ENABLE; //内存地址自增
hdma_adcx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; //传输数据半字16位
hdma_adcx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; //接收数据半字16位
hdma_adcx.Init.Mode = DMA_CIRCULAR; //循环模式
hdma_adcx.Init.Priority = DMA_PRIORITY_HIGH; // 优先级
hdma_adcx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // FIFO模式
HAL_DMA_Init(&hdma_adcx);
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adcx); // DMA要与ADC连接起来(这个函数很关键)
}
}
main.c
#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "adc/bsp_adc.h"
#define COVER_BUFFER_SIZE ((uint32_t) 100)
float ADC_ConvertedValueLocal;
// AD转换结果值
uint32_t ADC_ConvertedValue;
uint32_t DMA_Transfer_Complete_Count=0; //记录DAM转换的次数
uint32_t ADC_Add_DATA[COVER_BUFFER_SIZE];
uint16_t counts=0;
uint8_t finish_flag=0;
uint32_t g_count = 0;
int main(void)
{
uint8_t i=0;
int32_t ADC_value=0;
/* 复位所有外设,初始化Flash接口和系统滴答定时器 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化串口并配置串口中断优先级 */
MX_DEBUG_USART_Init();
printf("----这是一个ADC单通道电压采集实验-----\n");
MX_DMA_Init();
/* ADC 初始化 */
MX_ADCx_Init();
/* 启动AD转换并使能DMA传输和中断 */
HAL_ADC_Start_DMA(&hadcx,&ADC_ConvertedValue,1); // 参数1:ADC句柄结构体 参数2:保存数据 参数3:长度
/* 无限循环 */
while (1)
{
if(finish_flag==1)
{
for(i=0;i<COVER_BUFFER_SIZE;i++)
{
/* 将数据累加至ADC_value变量 */
ADC_value+=ADC_Add_DATA[i];
}
ADC_value=ADC_value/COVER_BUFFER_SIZE; // 求平均值
/* 3.3为AD转换的参考电压值,stm32的AD转换为12bit,2^12=4096,
即当输入为3.3V时,AD转换结果为4096 */
ADC_ConvertedValueLocal =(double)ADC_value*3.3/4096;
printf("AD转换原始值 = 0x%04X \r\n", ADC_value);
printf("计算得出电压值 = %f V \r\n",ADC_ConvertedValueLocal);
printf("完成的次数 %u V \r\n",g_count);
finish_flag=0;
ADC_value=0;
HAL_Delay(1000);
}
}
}
/**
* 函数功能: ADC转换完成回调函数
* 输入参数: hadc:ADC外设设备句柄
* 返 回 值: 无
* 说 明: 无
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) // 当触发DMA 把外设的数据传输到存储器
{
g_count ++;
ADC_Add_DATA[DMA_Transfer_Complete_Count]=ADC_ConvertedValue;
DMA_Transfer_Complete_Count++;
if(DMA_Transfer_Complete_Count==COVER_BUFFER_SIZE)
{
DMA_Transfer_Complete_Count=0;
finish_flag=1;
}
}
3.多通道(DMA)传输 要设置扫描模式
当多通道采集时,因为ADC转换需要时间。采集不能太快。每个通道的采集时间要求快就需要用DMA传输。如果时间要求不高。当第一个通道采集完。读取数据后,采集第二个通道。如果太快可能第一个通道都没读取完数据然后第二个通道有采集完。就会溢出。
多通道轮询方式dome(不采用DMA传输)
//开启ADC1
HAL_ADC_Start(&hadc1);
//等待ADC转换完成,超时为100ms
HAL_ADC_PollForConversion(&hadc1,100);
//判断ADC是否转换成功
if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1),HAL_ADC_STATE_REG_EOC)){
//成功读取值
return HAL_ADC_GetValue(&hadc1);
}
cubeMX的配置
代码
ADC.h
#ifndef __ADC_H__
#define __ADC_H__
/********************ADC输入通道(引脚)配置**************************
// 采集多个通道
#define ADCx_RCC_CLK_ENABLE() __HAL_RCC_ADC3_CLK_ENABLE()
#define ADCx_RCC_CLK_DISABLE() __HAL_RCC_ADC3_CLK_DISABLE()
#define DMAx_RCC_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE()
#define DMAx_Streamn_IRQn DMA2_Stream0_IRQn
#define DMAx_Streamn_IRQHandler DMA2_Stream0_IRQHandler
#define DMAx_Streamn DMA2_Stream0
#define DMA_CHANNEL DMA_CHANNEL_2
#define ADCx ADC3
#define ADC_GPIO_ClK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE();
#define ADC_GPIO_PIN1 GPIO_PIN_6
#define ADC_GPIO1 GPIOF
#define ADC_CHANNEL1 ADC_CHANNEL_4
#define ADC_GPIO_PIN2 GPIO_PIN_7
#define ADC_GPIO2 GPIOF
#define ADC_CHANNEL2 ADC_CHANNEL_5
#define ADC_GPIO_PIN3 GPIO_PIN_8
#define ADC_GPIO3 GPIOF
#define ADC_CHANNEL3 ADC_CHANNEL_6
#define ADC_GPIO_PIN4 GPIO_PIN_9
#define ADC_GPIO4 GPIOF
#define ADC_CHANNEL4 ADC_CHANNEL_7
#define ADC_GPIO_PIN5 GPIO_PIN_10
#define ADC_GPIO5 GPIOF
#define ADC_CHANNEL5 ADC_CHANNEL_8
#define ADC_GPIO_PIN6 GPIO_PIN_3
#define ADC_GPIO6 GPIOF
#define ADC_CHANNEL6 ADC_CHANNEL_9
#define ADC_GPIO_PIN7 GPIO_PIN_4
#define ADC_GPIO7 GPIOF
#define ADC_CHANNEL7 ADC_CHANNEL_14
#define ADC_GPIO_PIN8 GPIO_PIN_5
#define ADC_GPIO8 GPIOF
#define ADC_CHANNEL8 ADC_CHANNEL_15
#define ADC_CHANNEL_NUMBER 8
/* 扩展变量 ------------------------------------------------------------------*/
extern ADC_HandleTypeDef hadcx;
extern DMA_HandleTypeDef hdma_adcx;
/* 函数声明 ------------------------------------------------------------------*/
void MX_ADCx_Init(void);
void MX_DMA_Init(void) ;
ADC.C
#include "adc/bsp_adc.h"
#include "usart/bsp_debug_usart.h"
/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
ADC_HandleTypeDef hadcx;
DMA_HandleTypeDef hdma_adcx;
*/
void MX_ADCx_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
hadcx.Instance = ADCx;
hadcx.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;//4分频,84/4=21MHz
hadcx.Init.Resolution = ADC_RESOLUTION_12B;//ADC 12位分辨率
hadcx.Init.ScanConvMode = ENABLE;//扫描模式使能
hadcx.Init.ContinuousConvMode = ENABLE;//连续转换使能
hadcx.Init.DiscontinuousConvMode = DISABLE;//非连续转换失能
hadcx.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;//外部触发
hadcx.Init.ExternalTrigConv = ADC_SOFTWARE_START;//软件触发
hadcx.Init.DataAlign = ADC_DATAALIGN_RIGHT;//数据右对齐
hadcx.Init.NbrOfConversion = ADC_CHANNEL_NUMBER;//规则通道8个
hadcx.Init.DMAContinuousRequests = ENABLE;//DMA连续请求使能
hadcx.Init.EOCSelection = ADC_EOC_SINGLE_CONV;//采样完成进入中断
HAL_ADC_Init(&hadcx);
// 配置每个采样通道采样时间都可以单独配置,并且一个通道可以多次采集
sConfig.Channel = ADC_CHANNEL1;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL2;
sConfig.Rank = 2;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL3;
sConfig.Rank = 3;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL4;
sConfig.Rank = 4;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL5;
sConfig.Rank = 5;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL6;
sConfig.Rank = 6;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL7;
sConfig.Rank = 7;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
sConfig.Channel = ADC_CHANNEL8;
sConfig.Rank = 8;
HAL_ADC_ConfigChannel(&hadcx, &sConfig);
}
void MX_DMA_Init(void)
{
DMAx_RCC_CLK_ENABLE();
/* 外设中断优先级配置和使能中断 */
HAL_NVIC_SetPriority(DMAx_Streamn_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMAx_Streamn_IRQn);
}
/**
* 函数功能: ADC外设初始化配置
* 输入参数: hadc:AD外设句柄类型指针
* 返 回 值: 无
* 说 明: 该函数被HAL库内部调用
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(hadc->Instance==ADCx)
{
/* 外设时钟使能 */
ADCx_RCC_CLK_ENABLE();
/* AD转换通道引脚时钟使能 */
ADC_GPIO_ClK_ENABLE();
/* AD转换通道引脚初始化 */
GPIO_InitStruct.Pin = ADC_GPIO_PIN1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_GPIO1, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN2;
HAL_GPIO_Init(ADC_GPIO2, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN3;
HAL_GPIO_Init(ADC_GPIO3, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN4;
HAL_GPIO_Init(ADC_GPIO4, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN5;
HAL_GPIO_Init(ADC_GPIO5, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN6;
HAL_GPIO_Init(ADC_GPIO6, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN7;
HAL_GPIO_Init(ADC_GPIO7, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC_GPIO_PIN8;
HAL_GPIO_Init(ADC_GPIO8, &GPIO_InitStruct);
hdma_adcx.Instance = DMAx_Streamn;
hdma_adcx.Init.Channel = DMA_CHANNEL;
hdma_adcx.Init.Direction = DMA_PERIPH_TO_MEMORY;//外设到内存
hdma_adcx.Init.PeriphInc = DMA_PINC_DISABLE;//外设地址自增失能
hdma_adcx.Init.MemInc = DMA_MINC_ENABLE;//存储器地址自增使能
hdma_adcx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adcx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adcx.Init.Mode = DMA_CIRCULAR;//循环模式
hdma_adcx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_adcx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_adcx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL;
hdma_adcx.Init.MemBurst = DMA_MBURST_SINGLE;
hdma_adcx.Init.PeriphBurst = DMA_PBURST_SINGLE;
HAL_DMA_Init(&hdma_adcx);
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adcx);
}
}
main.c
#include "stm32f4xx_hal.h"
#include "usart/bsp_debug_usart.h"
#include "adc/bsp_adc.h"
#define COVER_BUFFER_SIZE ((uint32_t) 100)
float ADC_ConvertedValueLocal[ADC_CHANNEL_NUMBER];
// AD转换结果值
uint32_t ADC_ConvertedValue[ADC_CHANNEL_NUMBER];
uint32_t DMA_Transfer_Complete_Count=0;
uint32_t ADC_Add_DATA[COVER_BUFFER_SIZE*ADC_CHANNEL_NUMBER];
uint16_t counts=0;
uint8_t finish_flag=0;
int main(void)
{
uint8_t i=0,j=0;
int32_t ADC_value[ADC_CHANNEL_NUMBER]={0};
/* 复位所有外设,初始化Flash接口和系统滴答定时器 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化串口并配置串口中断优先级 */
MX_DEBUG_USART_Init();
printf("----这是一个ADC多通道电压采集实验-----\n");
MX_DMA_Init();
/* ADC 初始化 */
MX_ADCx_Init();
/* 启动AD转换并使能DMA传输和中断 */
HAL_ADC_Start_DMA(&hadcx,ADC_ConvertedValue,ADC_CHANNEL_NUMBER);
/* 无限循环 */
while (1)
{
if(finish_flag==1)
{
for(uint8_t y=0;y<ADC_CHANNEL_NUMBER;y++)
{
for(i=0;i<COVER_BUFFER_SIZE;i++)
{
/* 将数据累加至ADC_value变量 */
ADC_value[y]+=ADC_Add_DATA[COVER_BUFFER_SIZE*y+i];
}
}
for(j=0;j<ADC_CHANNEL_NUMBER;j++)
{
ADC_value[j]=ADC_value[j]/COVER_BUFFER_SIZE;
/* 3.3为AD转换的参考电压值,stm32的AD转换为12bit,2^12=4096,
即当输入为3.3V时,AD转换结果为4096 */
ADC_ConvertedValueLocal[j] =(double)ADC_value[j]*3.3/4096;
printf("CH%d value -> %fV\n",j,ADC_ConvertedValueLocal[j]);
ADC_value[j]=0;
}
finish_flag=0;
printf("\n");
HAL_Delay(1000);
}
}
}
/**
* 函数功能: ADC转换完成回调函数
* 输入参数: hadc:ADC外设设备句柄
* 返 回 值: 无
* 说 明: 无
*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) // 采集完8个通道才进中断
{
/* 因为有8个通道每个通道采集 */
for(uint8_t x=0;x<ADC_CHANNEL_NUMBER;x++)
{
ADC_Add_DATA[COVER_BUFFER_SIZE*x+DMA_Transfer_Complete_Count]=ADC_ConvertedValue[x];
}
DMA_Transfer_Complete_Count++;
if(DMA_Transfer_Complete_Count==COVER_BUFFER_SIZE)
{
DMA_Transfer_Complete_Count=0;
finish_flag=1;
}
}
总结:代码里有很好的注释,多看代码注释。