1.ADC概念
ADC,全称:Analog-to-Digital Converter,指模拟/数字转换器
2 STM32各系列ADC的主要特性
3.F4框图
4.转换序列与转换时间
A/D转换被组织为两组:规则组(常规转换组)和注入组(注入转换组)
规则组最多可以有16个转换,注入组最多有4个转换
4.1规则组和注入组执行优先级对比
4.2转换时间
5.触发源
6.中断
7.单次转换模式和连续转换模式与扫描模式
单次转换模式和连续转换模式
扫描模式
8 结构体
typedef struct
{
ADC_TypeDef Instance; / ADC 寄存器基地址 /
ADC_InitTypeDef Init; / ADC 参数初始化结构体变量 */
DMA_HandleTypeDef DMA_Handle; / DMA 配置结构体 */
……
} ADC_HandleTypeDef;
typedef struct
{
uint32_t DataAlign; /* 设置数据的对齐方式 /
uint32_t ScanConvMode; / 扫描模式 /
FunctionalState ContinuousConvMode; / 开启单次转换模式或者连续转换模式 / uint32_t NbrOfConversion; / 设置转换通道数目 /
FunctionalState DiscontinuousConvMode; / 是否使用规则通道组间断模式 /
uint32_t NbrOfDiscConversion; / 配置间断模式的规则通道个数 /
uint32_t ExternalTrigConv; / ADC 外部触发源选择 */
} ADC_InitTypeDef;
typedef struct
{
uint32_t Channel; /* ADC 转换通道*/
uint32_t Rank; /* ADC 转换顺序 /
uint32_t SamplingTime; / ADC 采样周期 */
} ADC_ChannelConfTypeDef;
9. 多通道采集实验配置步骤
10 实战
10.1 adc.h
#ifndef __ADC_H
#define __ADC_H
#include "./SYSTEM/sys/sys.h"
//1.开启 ADCx 和通道输出的 GPIO 时钟,配置该 IO 口的复用功能输出
//1.1 ADC及引脚定义 ADC复用PA5口
#define ADC_ADCX_CHY_GPIO_PORT GPIOA
#define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_5
#define ADC_ADCX_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) //PA口时钟使能
#define ADC_ADCX ADC1
#define ADC_ADCX_CHY ADC_CHANNEL_5 //通道Y, 0 <= Y <= 17
#define ADC_ADCX_ADCX_GPIO_CLK_ENABLE() do{ __HAL_RCC_ADC1_CLK_ENABLE(); }while(0) // PA口时钟使能
#define ADC_CH_NUM 6 //转换的通道数目
///* ADC DMA采集 DMA数据流相关 定义
// * 注意: 这里我们的通道还是使用上面的定义.
#define ADC_ADCX_DMASx DMA2_Stream4
#define ADC_ADCX_DMASx_Chanel DMA_CHANNEL_0 //ADC1_DMA请求源
#define ADC_ADCX_DMASx_IRQn DMA2_Stream4_IRQn //DMA2_Stream4_IRQn
#define ADC_ADCX_DMASx_IRQHandler DMA2_Stream4_IRQHandler
#define ADC_ADCX_DMASx_IS_TC() ( DMA2->HISR & (1 << 5) ) //判断 DMA2_Stream4 传输完成标志, 这是一个假函数形式, * 不能当函数使用, 只能用在if等语句里面
#define ADC_ADCX_DMASx_CLR_TC() do{ DMA2->HIFCR |= 1 << 5; }while(0) //清除 DMA2_Stream4 传输完成标志
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime); /* ADC通道设置 */
void adc_dma_enable( uint16_t ndtr); /* 使能一次ADC DMA采集传输 */
void adc_nch_dma_init(uint32_t tmr); /* ADC多通道 DMA采集初始化 */
void adc_nch_dma_gpio_init(void); /* ADC多通道 GPIO初始化 */
void adc_nch_dma_enable(uint16_t ndtr); /* 使能一次ADC DMA多通道采集传输 */
#endif
10.2 adc.c
#include "./SYSTEM/delay/delay.h"
#include "./BSP/ADC/adc.h"
//使用 ADC1 采集(DMA 读取)通道 0\1\2\3\4\5 的电压,在 LCD 模块上面显示对应的 ADC
//转换值以及换算成电压后的电压值。可以使用杜邦线连接 PA0\PA1\PA2\PA3\PA4\PA5 到你想测
//量的电压源(0~3.3V),然后通过 TFTLCD 显示的电压值。
//多通道 ADC 采集(DMA 读取)配置步骤:
//1.开启 ADCx 和通道输出的 GPIO 时钟,配置该 IO 口的复用功能输出
//2.初始化 ADCx,配置其工作参数
//2.1 HAL_ADC_Init 函数,设置 ADCx 时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
//2.2 会调用:HAL_ADC_MspInit 回调函数来,对 ADC 底层以及其输入通道 IO
//的初始化,包括:ADC 及 GPIO 时钟使能、GPIO 模式设置等
//3.配置 ADC 通道并启动 AD 转换器
//3.1在 HAL 库中,通过 HAL_ADC_ConfigChannel 函数来设置配置 ADC 的通道,根据需求设
//置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。这里配置多通道输出,
//需要多次调用该函数
//3.2 配置好 ADC 通道之后,通过 HAL_ADC_Start 函数启动 AD 转换器
//4 初始化 DMA
//4.1HAL_DMA_Init 函数初始化 DMA,包括配置通道,外设地址,存储器地址,传输数
//据量等。
//4.2HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识
//符,来连接 DMA 和外设句柄。这个宏定义为__HAL_LINKDMA。
//5 使能 DMA 对应数据流中断,配置 DMA 中断优先级,使能 ADC,使能并启动 DMA
//5.1 HAL_ADC_Start 函数开启 ADC 转换
//5.2 HAL_DMA_Start_IT 函数启动 DMA 读取,使能 DMA 中断。
//5.3 HAL_NVIC_EnableIRQ 函数使能 DMA 数据流中断。
//5.4 HAL_NVIC_SetPriority 函数设置中断优先级
//6 编写中断服务函数
//通用DMA中断处理函数HAL_DMA_IRQHandler,
//在该函数内部
ADC_HandleTypeDef g_adc_nch_dma_handle; /* 与DMA关联的ADC句柄 */
DMA_HandleTypeDef g_dma_nch_adc_handle; /* 与ADC关联的DMA句柄 */
uint8_t g_adc_dma_sta = 0; /* DMA传输状态标志, 0,未完成; 1, 已完成 */
// * @brief ADC初始化函数
// * @note 本函数支持ADC1/ADC2任意通道, 但是不支持ADC3
// * 我们使用12位精度, ADC采样时钟=21M, 转换时间为: 采样周期 + 12个ADC周期
// * 设置最大采样周期: 480, 则转换时间 = 492 个ADC周期 = 23.42us
// * @param 无
// * @retval 无
// */
//2.初始化 ADCx,配置其工作参数
//2.1 HAL_ADC_Init 函数,设置 ADCx 时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
//2.2 会调用:HAL_ADC_MspInit 回调函数来,对 ADC 底层以及其输入通道 IO
//的初始化,包括:ADC 及 GPIO 时钟使能、GPIO 模式设置等
//4 初始化 DMA
//4.1HAL_DMA_Init 函数初始化 DMA,包括配置通道,外设地址,存储器地址,传输数
//据量等。
//4.2HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识
//符,来连接 DMA 和外设句柄。这个宏定义为__HAL_LINKDMA。
void adc_nch_dma_init(uint32_t mar)
{
ADC_ADCX_CHY_CLK_ENABLE(); //使能ADCx时钟
//疑问1
if ((uint32_t)ADC_ADCX_DMASx > (uint32_t)DMA2) /* 大于DMA1_Stream7, 则为DMA2 */
{
__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
}
else
{
__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
}
// HAL_DMA_Init 函数初始化 DMA,包括配置通道,外设地址,存储器地址,传输数据量等。
g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx; /* 设置DMA数据流 寄存器基地址 */
g_dma_nch_adc_handle.Init.Channel = DMA_CHANNEL_0; /* 设置DMA通道 */
g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; /* DIR = 1 , 外设到存储器模式 */
g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; /* 外设数据长度:16位 */
g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; /* 存储器数据长度:16位 */
g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */
g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
HAL_DMA_Init(&g_dma_nch_adc_handle); /* 初始化DMA */
//5.1 HAL_ADC_Start 函数开启 ADC 转换
HAL_DMA_Start(&g_dma_nch_adc_handle, (uint32_t)&ADC_ADCX->DR, mar, 0); /* 配置DMA传输参数 */
//HAL_ADC_Init 函数,设置 ADCx 时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
g_adc_nch_dma_handle.DMA_Handle = &g_dma_nch_adc_handle; /* 设置ADC对应的DMA */
g_adc_nch_dma_handle.Instance = ADC_ADCX;
g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; /* 4分频,ADCCLK = PCLK2/4 = 84/4 = 21Mhz */
g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位模式 */
g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐 */
g_adc_nch_dma_handle.Init.ScanConvMode = ENABLE; /* 扫描模式 */
g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式,转换完成之后接着继续转换 */
g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */
g_adc_nch_dma_handle.Init.NbrOfConversion = ADC_CH_NUM; /* 使用转换通道数,需根据实际转换通道去设置 */
g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0; /* 不连续采样通道数为0 */
g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; /* 软件触发 */
g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; /* 使用软件触发, 此位忽略 */
g_adc_nch_dma_handle.Init.DMAContinuousRequests = ENABLE; /* 开启DMA连续转换 */
HAL_ADC_Init(&g_adc_nch_dma_handle); /* 初始化ADC */
adc_nch_dma_gpio_init(); /* GPIO 初始化 */
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_0,1, ADC_SAMPLETIME_480CYCLES);// 设置采样规则序列1~6
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_1,2, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_2,3, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_3,4, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_4,5, ADC_SAMPLETIME_480CYCLES);
adc_channel_set(&g_adc_nch_dma_handle, ADC_CHANNEL_5,6, ADC_SAMPLETIME_480CYCLES);
//5 使能 DMA 对应数据流中断,配置 DMA 中断优先级,使能 ADC,使能并启动 DMA
//5.2 HAL_DMA_Start_IT 函数启动 DMA 读取,使能 DMA 中断。
//5.3 HAL_NVIC_EnableIRQ 函数使能 DMA 数据流中断。
//5.4 HAL_NVIC_SetPriority 函数设置中断优先级
HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3); /* 设置DMA中断优先级为3,子优先级为3 */
HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn); /* 使能DMA中断 */
HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, sizeof(uint16_t)); /* 开始DMA数据传输 */
__HAL_DMA_ENABLE_IT(&g_dma_nch_adc_handle, DMA_IT_TC); /* TCIE=1, 使能传输完成中断 */
}
/**
* @brief 设置ADC通道采样时间
* @param adcx : adc句柄指针,ADC_HandleTypeDef
* @param ch : 通道号, ADC_CHANNEL_0~ADC_CHANNEL_17
* @param stime: 采样时间 0~7, 对应关系为:
* @arg ADC_SAMPLETIME_3CYCLES, 3个ADC时钟周期 ADC_SAMPLETIME_15CYCLES, 15个ADC时钟周期
* @arg ADC_SAMPLETIME_28CYCLES, 28个ADC时钟周期 ADC_SAMPLETIME_56CYCLES, 56个ADC时钟周期
* @arg ADC_SAMPLETIME_84CYCLES, 84个ADC时钟周期 ADC_SAMPLETIME_112CYCLES,112个ADC时钟周期
* @arg ADC_SAMPLETIME_144CYCLES,144个ADC时钟周期 ADC_SAMPLETIME_480CYCLES,480个ADC时钟周期
* @param rank: 多通道采集时需要设置的采集编号,
假设你定义channel1的rank=1,channel2 的rank=2,
那么对应你在DMA缓存空间的变量数组AdcDMA[0] 就i是channel1的转换结果,AdcDMA[1]就是通道2的转换结果。
单通道DMA设置为 ADC_REGULAR_RANK_1
* @arg 编号1~16:ADC_REGULAR_RANK_1~ADC_REGULAR_RANK_16
* @retval 无
*/
//3.配置 ADC 通道并启动 AD 转换器
//3.1在 HAL 库中,通过 HAL_ADC_ConfigChannel 函数来设置配置 ADC 的通道,根据需求设
//置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。这里配置多通道输出,
//需要多次调用该函数
//3.2 配置好 ADC 通道之后,通过 HAL_ADC_Start 函数启动 AD 转换器
void adc_channel_set(ADC_HandleTypeDef *adc_handle, uint32_t ch, uint32_t rank, uint32_t stime)
{
/* 配置对应ADC通道 */
ADC_ChannelConfTypeDef adc_channel;
adc_channel.Channel = ch; /* 设置ADCX对通道ch */
adc_channel.Rank = rank; /* 设置采样序列 */
adc_channel.SamplingTime = stime; /* 设置采样时间 */
HAL_ADC_ConfigChannel(adc_handle, &adc_channel); /* 初始化ADC通道 */
}
/**
* @brief 多通道ADC的gpio初始化函数
* @param 无
* @note 此函数会被adc_nch_dma_init()调用
* @note PA0-ADC_CHANNEL_0、PA1-ADC_CHANNEL_1、PA2-ADC_CHANNEL_2
PA3-ADC_CHANNEL_3、PA4-ADC_CHANNEL_4、PA5-ADC_CHANNEL_5
* @retval 无
*/
//2.2对 ADC 底层以及其输入通道 IO的初始化,包括:ADC 及 GPIO 时钟使能、GPIO 模式设置等
void adc_nch_dma_gpio_init()
{
GPIO_InitTypeDef gpio_init_struct;
__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA引脚时钟 */
/* ADC采集引脚模式设置,模拟输入 */
gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5; /* GPIOA0~5 */;
gpio_init_struct.Mode = GPIO_MODE_ANALOG;
gpio_init_struct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &gpio_init_struct);
}
/**
* @brief 使能一次ADC DMA传输
* @param ndtr: DMA传输的次数
* @retval 无
*/
void adc_nch_dma_enable(uint16_t ndtr)
{
__HAL_ADC_DISABLE(&g_adc_nch_dma_handle); /* 先关闭ADC */
__HAL_DMA_DISABLE(&g_dma_nch_adc_handle); /* 关闭DMA传输 */
g_dma_nch_adc_handle.Instance->NDTR = ndtr; /* 重设DMA传输数据量 */
__HAL_DMA_ENABLE(&g_dma_nch_adc_handle); /* 开启DMA传输 */
__HAL_ADC_ENABLE(&g_adc_nch_dma_handle); /* 重新启动ADC */
ADC_ADCX->CR2 |= 1 << 30; /* 启动规则转换通道 */
}
/**
* @brief ADC DMA采集中断服务函数
* @param 无
* @retval 无
*/
void ADC_ADCX_DMASx_IRQHandler(void)
{
if (ADC_ADCX_DMASx_IS_TC()) //是否传输完成
{
g_adc_dma_sta = 1; /* 标记DMA传输完成 */
ADC_ADCX_DMASx_CLR_TC(); /* 清除DMA2 数据流4 传输完成中断 */
}
}
10.3 main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/ADC/adc.h"
#include "./BSP/DMA/dma.h"
#define ADC_DMA_BUF_SIZE 50 * 6 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */
uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */
extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0, 未完成; 1, 已完成 */
int main(void)
{
uint16_t i, j;
uint16_t adcx;
uint32_t sum;
float temp;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
adc_nch_dma_init((uint32_t)&g_adc_dma_buf);
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "ADC 6CH DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 12, 12, "ADC1_CH0_VAL:", BLUE);
lcd_show_string(30, 122, 200, 12, 12, "ADC1_CH0_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 140, 200, 12, 12, "ADC1_CH1_VAL:", BLUE);
lcd_show_string(30, 152, 200, 12, 12, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 170, 200, 12, 12, "ADC1_CH2_VAL:", BLUE);
lcd_show_string(30, 182, 200, 12, 12, "ADC1_CH2_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 200, 200, 12, 12, "ADC1_CH3_VAL:", BLUE);
lcd_show_string(30, 212, 200, 12, 12, "ADC1_CH3_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 230, 200, 12, 12, "ADC1_CH4_VAL:", BLUE);
lcd_show_string(30, 242, 200, 12, 12, "ADC1_CH4_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
lcd_show_string(30, 260, 200, 12, 12, "ADC1_CH5_VAL:", BLUE);
lcd_show_string(30, 272, 200, 12, 12, "ADC1_CH5_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */
adc_nch_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA多通道采集 */
while (1)
{
if (g_adc_dma_sta == 1)
{
/* 循环显示通道0~通道5的结果 */
for(j = 0; j < 6; j++) /* 遍历6个通道 */
{
sum = 0; /* 清零 */
for (i = 0; i < ADC_DMA_BUF_SIZE / 6; i++) /* 每个通道采集了10次数据,进行10次累加 */
{
sum += g_adc_dma_buf[(6 * i) + j]; /* 相同通道的转换数据累加 */
}
adcx = sum / (ADC_DMA_BUF_SIZE / 6); /* 取平均值 */
/* 显示结果 */
lcd_show_xnum(108, 110 + (j * 30), adcx, 4, 12, 0, BLUE); /* 显示ADC采样后的原始值 */
temp = (float)adcx * (3.3 / 4096); /* 获取计算后的带小数的实际电压值,比如3.1111 */
adcx = temp; /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
lcd_show_xnum(108, 122 + (j * 30), adcx, 1, 12, 0, BLUE); /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */
temp -= adcx; /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
temp *= 1000; /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
lcd_show_xnum(120, 122 + (j * 30), temp, 3, 12, 0X80, BLUE);/* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
}
g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */
adc_nch_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA多通道采集 */
}
LED0_TOGGLE();
delay_ms(100);
}
}