一、实验简介
正常单通道ADC采集顺序是先开启ADC采集,然后等待ADC转换完成,也就是判断EOC位置1,然后再读取数据寄存器的值。
如果配置了DMA功能,在EOC位被硬件置1后,自动产生DMA请求,然后DMA进行数据搬运。
1,功能描述 通过DMA读取数据
通过ADC1通道1(PA1)采集电位器的电压,并显示ADC转换的数字量及换算后的电压值
2、确定最小刻度
VREF+ = 3.3V ---》 0V ≤ VIN ≤ 3.3V ---》最小刻度 = 3.3 / 4096 ,F1的分辨率是12位的,也就是把3.3V分为4096份。F4/F7/H7还可以自己配置分辨率,例如H7可以把分辨率配置为16位的,也就是把3.3V进行65536等分。
3,确定转换时间
采样时间239.5个ADC时钟周期为例,可以得到转换时间为21us。例如配置为最长的采样时间239.5个采样周期,那么采样时间就是239.5 + 12.5 = 252个时钟周期。配置ADC的时钟的12M,则转换时间为252 * (1 / 12000000) = 21us,采样时间设置的越大,准确度越高,设置的越小,准确度越低。
4、模式组合
由于使用了DMA搬运,所以使用连续转换模式、不使用扫描模式
二、单通道ADC采集实验配置步骤
1、HAL_DMA_Init()函数,初始化DMA
2、__HAL_LINKDMA()宏定义,将DMA和ADC句柄联系起来
3、HAL_ADC_Init()函数,用于初始化ADC,配置ADC工作参数。
4、HAL_ADCEx_Calibration_Start()函数,用于ADC校准的。
5、HAL_ADC_MspInit()函数, 配置NVIC、CLOCK、GPIO
6、HAL_ADC_ConfigChannel()函数,配置ADC相应通道相关参数
7、HAL_NVIC_SetPriority()、HAL_NVIC_EnableIRQ()函数,使能DMA数据流传输完成中断
8、DMAx_Channely_IRQHandler()函数,编写DMA数据流中断服务函数
9、HAL_DMA_Start_IT()函数,启动DMA,开启传输完成中断
10、HAL_ADC_Start_DMA()函数,触发ADC转换,DMA传输数据
三、 实验程序
1、寄存器版本
dma.c源程序
#include "./BSP/DMA/dma.h"
#include <string.h>
uint16_t ADC_data[20];
//配置ADC1的DMA1的通道1请求
void DMA_Init(void)
{
//开启DMA1时钟
RCC->AHBENR |= (1 << 0);
//MEM2MEM 设置为非存储器到存储器模式
DMA1_Channel1->CCR &= ~(1 << 14);
//PL 设置通道优先级为中
DMA1_Channel1->CCR |= (1 << 12);
//MSIZE 设置存储器数据宽度为16位
DMA1_Channel1->CCR |= (1 << 10);
//PSIZE 设置外设数据宽度为16位
DMA1_Channel1->CCR |= (1 << 8);
//MINC 设置存储器增量模式
DMA1_Channel1->CCR |= (1 << 7);
//PINC 设置外设不增量模式
DMA1_Channel1->CCR &= ~(1 << 6);
//CIRC 不执行循环模式
//DMA1_Channel1->CCR &= ~(1 << 5);
DMA1_Channel1->CCR |= (1 << 5);
//DIR 从外设读取
DMA1_Channel1->CCR &= ~(1 << 4);
//设置传输数量
DMA1_Channel1->CNDTR = 20;
//设置DMA的外地址
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
//清空ADC_data
memset((void*)ADC_data,0,20);
DMA1_Channel1->CMAR = (uint32_t)ADC_data;
//EN 开启DMA 这个必须放在最后 不然CNDTR寄存器不能写入
DMA1_Channel1->CCR |= (1 << 0);
}
adc.c源程序
#include "./BSP/ADC/adc.h"
//配置ADC1的通道1 PA1进行
void ADC_Init(void)
{
//开启ADC1时钟
RCC->APB2ENR |= (1 << 9);
//关闭扫描模式
ADC1->CR1 &= ~(1 << 8);
//开启外部触发转换ADC
ADC1->CR2 |= (1 << 20);
//EXTSEL 设置软件触发ADC转换
ADC1->CR2 |= (7 << 17);
//ALIGN 数据右对齐
ADC1->CR2 &= ~(1 << 11);
//DMA 开启DMA转换
ADC1->CR2 |= (1 << 8);
//CONT 开启连续转换模式
ADC1->CR2 |= (1 << 1);
//设置ADC1通道1转换时间为239.5个周期
ADC1->SMPR2 |= (7 << 3);
//L 设置总共1一个转换通道
ADC1->SQR1 |= (1 << 20);
//设置ADC第一个转换为通道1
ADC1->SQR3 |= (1 << 0);
//EXTTRIG 开启GPIOA时钟
RCC->APB2ENR |= (1 << 2);
//设置PA1为输入模式
GPIOA->CRL &= ~(0XF << 4);
//ADON 开启ADC功能
ADC1->CR2 |= (1 << 0);
//RSTCAL 初始化ADC校准寄存器
ADC1->CR2 |= (1 << 3);
//等待ADC校准寄存器初始化完毕
while(ADC1->CR2 & (1 << 3));
//开启ADC校准
ADC1->CR2 |= (1 << 2);
//等待ADC校准完成
while(ADC1->CR2 & (1 << 2));
//SWSTART 开启规则组转换 软件触发
ADC1->CR2 |= (1 << 22);
}
2、库函数版本
dam.c源程序
#include "./BSP/DMA/dma.h"
#include <string.h>
extern ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma;
//配置ADC1的DMA1的通道1请求
void DMA_Init(void)
{
//开启DMA1时钟
__HAL_RCC_DMA1_CLK_ENABLE();
//配置DMA1通道 因为ADC1连接在DMA1的通道1 这个在使用手册可以查找到
hdma.Instance = DMA1_Channel1;
//外设到内存
hdma.Init.Direction = DMA_PERIPH_TO_MEMORY;
//内存为16位
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
//内存递增
hdma.Init.MemInc = DMA_MINC_ENABLE;
//循环搬运
//当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。
hdma.Init.Mode = DMA_NORMAL;
//外设16位
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
//外设地址不递增
hdma.Init.PeriphInc = DMA_PINC_DISABLE;
//通道1优先级为中
hdma.Init.Priority = DMA_PRIORITY_MEDIUM;
//配置DMA的通道1为外设到内存
HAL_DMA_Init(&hdma);
//将DMA句柄于ADC句柄连接起来 可以理解为将这个DMA句柄拷贝到ADC句柄里的DMA句柄上
__HAL_LINKDMA(&hadc,DMA_Handle,hdma);
}
//标志DMA数据搬运完
uint8_t state = 0;
void DMA1_Channel1_IRQHandler(void)
{
if(DMA1->ISR & (1 << 1))
{
state = 1;
//清除中断标志位
DMA1->IFCR |= (1 << 1);
}
DMA1->IFCR |= (1 << 0);
}
adc.c源程序
#include "./BSP/ADC/adc.h"
#include "string.h"
extern DMA_HandleTypeDef hdma;
uint16_t ADC_data[20];
ADC_HandleTypeDef hadc;
//配置ADC1的通道1 PA1进行
void ADC_Init(void)
{
hadc.Instance = ADC1;
//配置ADC连续转换模式 就是ADC转换完成一次后会自动下一次转换
hadc.Init.ContinuousConvMode = ENABLE;
//转换结果采用右对齐
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
//不开启间断模式
hadc.Init.DiscontinuousConvMode = DISABLE;
//ADC触发选用软件触发
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
//设置ADC转换数量的 SQR1的L位
hadc.Init.NbrOfConversion = 1;
//设置间断模式写转换一次转换数量
hadc.Init.NbrOfDiscConversion = 0;
//不开启扫描模式 因为就一个通道
hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;
//设置ADC为软件触发转换
HAL_ADC_Init(&hadc);
//开启ADC校准
HAL_ADCEx_Calibration_Start(&hadc);
ADC_ChannelConfTypeDef sConfig;
//配置通道1
sConfig.Channel = ADC_CHANNEL_1;
//通道1第一个转换
sConfig.Rank = ADC_REGULAR_RANK_1;
//采样周期采用239.5
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
//配置ADC通道一为第一个转换
HAL_ADC_ConfigChannel(&hadc, &sConfig);
memset((void*)ADC_data,0,20);
//这句话最好写 不然下面那个会把半传输中断也开开 这里就会开一个中断 因为下面那个函数回把ADC半传输回调函数复制
//这句话不写也行 但是中断里边要清除所有位 不然卡在中断
HAL_DMA_Start_IT(&hdma, (uint32_t)ADC1->DR, (uint32_t)ADC_data, 20);
//开启DMA传输 这个函数里边会把中断都打开
HAL_ADC_Start_DMA(&hadc, (uint32_t* )ADC_data, 20);
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
//开启ADC1时钟
__HAL_RCC_ADC1_CLK_ENABLE();
//开启GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Mode = GPIO_MODE_ANALOG;
GPIO_Init.Pin = GPIO_PIN_1;
GPIO_Init.Pull = GPIO_NOPULL;
//设置PA1为模拟输入模式
HAL_GPIO_Init(GPIOA,&GPIO_Init);
//使能DMA1通道1中断
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
//设置中断优先级
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn,2,2);
}
这里主要是要注意先配置DMA中断,再调用ADC的DMA函数,具体原因在代码里写了