STM32 ADC知识总结及多通道采样实验详解

1.ADC概念

ADC,全称:Analog-to-Digital Converter,指模拟/数字转换器



2 STM32各系列ADC的主要特性

3.F4框图

4.转换序列与转换时间

A/D转换被组织为两组:规则组(常规转换组)和注入组(注入转换组)
规则组最多可以有16个转换,注入组最多有4个转换

4.1规则组和注入组执行优先级对比



4.2转换时间


5.触发源blog.csdnimg.cn/direct/f7ca0dd32f5443aab9798321c743be8e.png)

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);
    }
}
物联沃分享整理
物联沃-IOTWORD物联网 » STM32 ADC知识总结及多通道采样实验详解

发表评论