ADC实验(多通道 ADC 采集(DMA 读取)实验)(单通道 ADC 过采样(16 位分辨率)实验)

多通道 ADC 采集(DMA 读取)实验

本实验我们来学习多通道 ADC 采集(DMA 读取)实验。本实验使用规则组多通道的连续 转换模式,并且通过软件触发,即由 ADC_CR2 寄存器的 SWSTART 位启动。由于使用连续转 换模式,所以使用 DMA 读取转换结果的方式。

ADC 寄存器

本实验我们很多的设置和单通道 ADC 采集(DMA 读取)实验是一样的,所以下面介绍寄 存器的时候我们不会继续全部都介绍,而是针对性选择与单通道 ADC 采集(DMA 读取)实验 不同设置的 ADC_SQRx 寄存器进行介绍,其他的配置基本一样的。另外我们用到 DMA 读取数 据,配置上和单通道 ADC 采集(DMA 读取)实验是一样的。

ADC 规则序列寄存器有四个(ADC_SQR1~ ADC_SQR3),具体怎么配置,需要看我们用 多少个通道,比如本实验我们使用 6 个通道同时采集 ADC 数据,具体配置如下:

⚫ ADC 规则序列寄存器 1(ADC_SQR1)

L[3:0]位用于设置规则序列的长度,取值范围:0~15,表示规则序列长度为 1~16。本实验 使用到 6 个通道,所以设置这几个位的值为 5 即可。

SQ13[4:0]~SQ16[4:0]位设置规则组序列的第 13~16 个转换编号,第 1~12 个转换编号的设 置请查看 ADC_SQR2 和 ADC_SQR3 寄存器。

下面我们来看看本实验是怎么设置的:SQ1[4:0]位赋值为 0、SQ2[4:0]位赋值为 1、SQ3[4:0] 位赋值为 2、SQ4[4:0]位赋值为 3、SQ5[4:0]位赋值为 4、SQ6[4:0]位赋值为 5,即规则序列 1 到 6 分别对应的输入通道是 0 到 5。SQ1~SQ6 位都是在 ADC_SQR3 寄存器中配置。

使用 ADC1 采集(DMA 读取)通道 1\2\3\4\5\6 的电压,在 LCD 模块上面显示对应的 ADC 转换值以及换算成电压后的电压值。可以使用杜邦线连接 PA0\PA1\PA2\PA3\PA4\PA5 到你想测 量的电压源(0~3.3V),然后通过 TFTLCD 显示的电压值。LED0 闪烁,提示程序运行。

代码 

#include "./BSP/ADC/adc.h"

DMA_HandleTypeDef g_dma_nch_adc_handle;/* 定义要搬运ADC数据的DMA句柄 */
ADC_HandleTypeDef g_adc_nch_dma_handle;/* 定义ADC(DMA读取)句柄 */

uint8_t g_adc_dma_sta;/* DMA传输状态标志, 0,未完成; 1, 已完成 */

/* ADC N通道(6通道)DMA读取 初始化函数 */
void adc_nch_dma_init(uint32_t mar)
{
    ADC_ChannelConfTypeDef adc_ch_conf = {0};
    
    __HAL_RCC_DMA1_CLK_ENABLE();/* DMA1时钟使能 */
    
    g_dma_nch_adc_handle.Instance = DMA1_Channel1;/* 设置DMA通道 */
    g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 从外设到存储器模式 */
    g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;/* 存储器数据长度:16位 */
    g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    g_dma_nch_adc_handle.Init.Mode = DMA_NORMAL;/* 外设流控模式 */
    g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 外设数据长度:16位 */
    g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_HIGH;/* 高等优先级 */
    HAL_DMA_Init(&g_dma_nch_adc_handle);
    
    __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);/* 将DMA与adc联系起来 */
    
    g_adc_nch_dma_handle.Instance = ADC1;/* 选择ADC1 */
    g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;/* 打开连续转换模式 */
    g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 数据对齐方式:右对齐 */
    g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE;/* 禁止规则通道组间断模式 */
    g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 触发转换方式:软件触发 */
    g_adc_nch_dma_handle.Init.NbrOfConversion = 6;/* 赋值范围是1~16,本实验用到6个规则通道序列 */
    g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数,禁止规则通道组间断模式后,此参数忽略 */
    g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;/* 扫描模式,用到六个通道 */
    HAL_ADC_Init(&g_adc_nch_dma_handle);/* 初始化 */
    
    HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);/* 校准ADC */
    
    adc_ch_conf.Channel = ADC_CHANNEL_0;/* 通道0 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* 序列1 */
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;/* 采样时间239.5个ADC时钟周期 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* 通道1 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_2;/* 序列2 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    adc_ch_conf.Channel = ADC_CHANNEL_2;/* 通道2 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_3;/* 序列3 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    adc_ch_conf.Channel = ADC_CHANNEL_3;/* 通道3 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_4;/* 序列4 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    adc_ch_conf.Channel = ADC_CHANNEL_4;/* 通道4 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_5;/* 序列5 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    adc_ch_conf.Channel = ADC_CHANNEL_5;/* 通道5 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_6;/* 序列6 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    HAL_DMA_Start_IT(&g_dma_nch_adc_handle, (uint32_t)&ADC1->DR, mar, 0);/* 启动DMA,并开启中断 */
    HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, 0);/* 开启ADC,通过DMA传输结果 */
}

/* ADC Msp底层函数初始化 */
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
    if(hadc->Instance == ADC1)
    {
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        GPIO_InitTypeDef gpio_init_struct = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();/* 开启GPIOA时钟 */
        __HAL_RCC_ADC1_CLK_ENABLE();/* 使能ADC1时钟 */
        
        /* 设置ADC时钟 */
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/* ADC外设时钟 */
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/* 分频因子6时钟为72M/6=12MHz */
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/* 设置ADC时钟 */
        
        /* 设置ADC采集通道对应IO引脚工作模式 */
        gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |GPIO_PIN_3 | GPIO_PIN_4 |GPIO_PIN_5;/* ADC通道IO引脚 */
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;/* 模拟 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        
        /* 配置DMA数据流请求中断优先级 */
        HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
        HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    }
}

/* 使能一次 ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
    /* 寄存器方式 */
    ADC1->CR2 &= ~(1 << 0);/* 先关闭ADC */
    DMA1_Channel1->CCR &= ~(1 << 0); /* 关闭DMA传输 */
    
    while(DMA1_Channel1->CCR & (1 << 0));/* 确保DMA可以被设置 */
    DMA1_Channel1->CNDTR = cndtr;/* DMA传输数据量 */
    
    DMA1_Channel1->CCR |= (1 << 0);/* 开启DMA传输 */
    ADC1->CR2 |= (1 << 0);/* 重新启动ADC */
    
    ADC1->CR2 |= (1 << 22);/* 启动规则转换通道 */
}

/* ADC DMA读取中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & (1 << 1))
    {
        g_adc_dma_sta = 1;/* 标记DMA传输完成 */
        DMA1->IFCR |= (1 << 1);/* 清除DMA1 数据流 传输完成中断 */
    }
}

#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"

#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(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    usart_init(115200);                 /* 传口初始化 */
    
    led_init();                         /* LED初始化 */
    lcd_init();                         /* LCD初始化 */
    
    adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC DMA采集 */

    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_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++)  /* 每个通道采集了50次数据,进行50次累加 */
                {
                    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);   /* 显示ADCC采样后的原始值 */

                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_dma_enable(ADC_DMA_BUF_SIZE);       /* 启动下一次ADC DMA采集 */
        }

        LED0_TOGGLE();
        delay_ms(100);
    }
}

 这里使用了 DMA 传输数据,DMA 传输的数据存放在 g_adc_dma_buf 数组里,该数组的大 小是 50 * 6。本实验用到 6 个通道,每个通道使用 50 个 uint16_t 大小的空间存放 ADC 的结果。 输入通道 0 的转换数据存放在 g_adc_dma_buf[0]到 g_adc_dma_buf[49],输入通道 1 的转换数据 存放在 g_adc_dma_buf[50]到 g_adc_dma_buf[99],后面的以此类推。然后对数组的每个通道的 数据取平均值,减少误差。最后在 LCD 屏上显示 ADC 的转换值和换算成电压后的电压值。

单通道 ADC 过采样(16 位分辨率)实验

本实验我们来学习单通道 ADC 过采样(16 位分辨率)实验。STM32F103 自带的 ADC 分 辨率只有 12 位,虽然可以满足一般的应用,但是有些场合可能需要更高的分辨率,怎么办呢? 可以使用外部专用的 ADC,或者换一个带更高分辨率 ADC 的主控芯片。这样做往往会增加额 外的成本,那么有没有其它办法呢?答案是有的,可以通过引入过采样技术来实现。

ADC 过采样技术,是利用 ADC 多次采集的方式,来提高 ADC 分辨率。

由该方程可以知道,采样速度每提高 4 倍,分辨率位数可以提高 1 位。结合 ADC 的实际 情况,换个思路来说,分辨率位数每提高 1 位,如果采样频率不变的情况下,那么采样速度就 会降低 4 倍。本实验要求得到 16 位分辨率,即需要增加 4 位分辨率,那么采样速度就会降低 256 倍,即需要采集 256 次才能得出 1 次数据,相当于 ADC 速度慢了 256 倍。

理论上只要 ADC 足够快,我们可以无限提高 ADC 精度,但实际上 ADC 并不是无限快的, 而且由于 ADC 性能限制,并不是位数无限提高,结果就越好,需要根据自己的实际需求和 ADC 的实际性能来权衡的。

 代码

#include "./BSP/ADC/adc.h"

DMA_HandleTypeDef g_dma_adc_handle;/* 定义要搬运ADC数据的DMA句柄 */
ADC_HandleTypeDef g_adc_dma_handle;/* 定义ADC(DMA读取)句柄 */

uint8_t g_adc_dma_sta;/* DMA传输状态标志, 0,未完成; 1, 已完成 */

/* ADC DMA读取 初始化函数 */
void adc_dma_init(uint32_t mar)
{
    ADC_ChannelConfTypeDef adc_ch_conf = {0};
    
    __HAL_RCC_DMA1_CLK_ENABLE();/* DMA1时钟使能 */
    
    g_dma_adc_handle.Instance = DMA1_Channel1;/* 设置DMA通道 */
    g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 从外设到存储器模式 */
    g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;/* 存储器数据长度:16位 */
    g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE;/* 存储器增量模式 */
    g_dma_adc_handle.Init.Mode = DMA_NORMAL;/* 外设流控模式 */
    g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;/* 外设数据长度:16位 */
    g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设非增量模式 */
    g_dma_adc_handle.Init.Priority = DMA_PRIORITY_HIGH;/* 高等优先级 */
    HAL_DMA_Init(&g_dma_adc_handle);
    
    __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle);/* 将DMA与adc联系起来 */
    
    g_adc_dma_handle.Instance = ADC1;/* 选择ADC1 */
    g_adc_dma_handle.Init.ContinuousConvMode = ENABLE;/* 打开连续转换模式 */
    g_adc_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;/* 数据对齐方式:右对齐 */
    g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE;/* 禁止规则通道组间断模式 */
    g_adc_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 触发转换方式:软件触发 */
    g_adc_dma_handle.Init.NbrOfConversion = 1;/* 赋值范围是1~16,本实验用到1个规则通道序列 */
    g_adc_dma_handle.Init.NbrOfDiscConversion = 0;/* 配置间断模式的规则通道个数,禁止规则通道组间断模式后,此参数忽略 */
    g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 非扫描模式,仅用到一个通道 */
    HAL_ADC_Init(&g_adc_dma_handle);/* 初始化 */
    
    HAL_ADCEx_Calibration_Start(&g_adc_dma_handle);/* 校准ADC */
    
    adc_ch_conf.Channel = ADC_CHANNEL_1;/* 通道1 */
    adc_ch_conf.Rank = ADC_REGULAR_RANK_1;/* 序列1 */
    adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;/* 采样时间1.5个ADC时钟周期 */
    HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);/* 通道配置 */
    
    HAL_DMA_Start_IT(&g_dma_adc_handle, (uint32_t)&ADC1->DR, mar, 0);/* 启动DMA,并开启中断 */
    HAL_ADC_Start_DMA(&g_adc_dma_handle, &mar, 0);/* 开启ADC,通过DMA传输结果 */
}

/* ADC Msp底层函数初始化 */
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
    if(hadc->Instance == ADC1)
    {
        RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
        GPIO_InitTypeDef gpio_init_struct = {0};
        
        __HAL_RCC_GPIOA_CLK_ENABLE();/* 开启GPIOA时钟 */
        __HAL_RCC_ADC1_CLK_ENABLE();/* 使能ADC1时钟 */
        
        /* 设置ADC时钟 */
        adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;/* ADC外设时钟 */
        adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;/* 分频因子6时钟为72M/6=12MHz */
        HAL_RCCEx_PeriphCLKConfig(&adc_clk_init);/* 设置ADC时钟 */
        
        /* 设置ADC采集通道对应IO引脚工作模式 */
        gpio_init_struct.Pin = GPIO_PIN_1;/* ADC通道IO引脚 */
        gpio_init_struct.Mode = GPIO_MODE_ANALOG;/* 模拟 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        
        /* 配置DMA数据流请求中断优先级 */
        HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 3);
        HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    }
}

/* 使能一次 ADC DMA传输函数 */
void adc_dma_enable(uint16_t cndtr)
{
    /* 寄存器方式 */
    ADC1->CR2 &= ~(1 << 0);/* 先关闭ADC */
    DMA1_Channel1->CCR &= ~(1 << 0); /* 关闭DMA传输 */
    
    while(DMA1_Channel1->CCR & (1 << 0));/* 确保DMA可以被设置 */
    DMA1_Channel1->CNDTR = cndtr;/* DMA传输数据量 */
    
    DMA1_Channel1->CCR |= (1 << 0);/* 开启DMA传输 */
    ADC1->CR2 |= (1 << 0);/* 重新启动ADC */
    
    ADC1->CR2 |= (1 << 22);/* 启动规则转换通道 */
}

/* ADC DMA读取中断服务函数 */
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA1->ISR & (1 << 1))
    {
        g_adc_dma_sta = 1;/* 标记DMA传输完成 */
        DMA1->IFCR |= (1 << 1);/* 清除DMA1 数据流 传输完成中断 */
    }
}
#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"

/* ADC过采样技术, 是利用ADC多次采集的方式, 来提高ADC精度, 采样速度每提高4倍
 * 采样精度提高 1bit, 同时, ADC采样速度降低4倍, 如提高4bit精度, 需要256次采集
 * 才能得出1次数据, 相当于ADC速度慢了256倍. 理论上只要ADC足够快, 我们可以无限
 * 提高ADC精度, 但实际上ADC并不是无限快的, 而且由于ADC性能限制, 并不是位数无限
 * 提高结果就越好, 需要根据自己的实际需求和ADC的实际性能来权衡.
 */

#define ADC_OVERSAMPLE_TIMES    256                         /* ADC过采样次数, 这里提高4bit分辨率, 需要256倍采样 */
#define ADC_DMA_BUF_SIZE        ADC_OVERSAMPLE_TIMES * 10   /* ADC DMA采集 BUF大小, 应等于过采样次数的整数倍 */

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;
    uint32_t adcx;
    uint32_t sum;
    float temp;
    
    HAL_Init();                         /* 初始化 HAL 库 */
    sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
    delay_init(72);                     /* 延时初始化 */
    usart_init(115200);                 /* 传口初始化 */
    
    led_init();                         /* LED初始化 */
    lcd_init();                         /* LCD初始化 */
    
    adc_dma_init((uint32_t)&g_adc_dma_buf);     /* 初始化ADC DMA采集 */

    lcd_show_string(30,  50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30,  70, 200, 16, 16, "ADC OverSample TEST", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH1_VAL:", BLUE);
    lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH1_VOL:0.000V", BLUE); /* 先在固定位置显示小数点 */

    adc_dma_enable(ADC_DMA_BUF_SIZE);   /* 启动ADC DMA采集 */
    
    while(1)
    {
        if (g_adc_dma_sta == 1)
        {
            /* 计算DMA 采集到的ADC数据的平均值 */
            sum = 0;

            for (i = 0; i < ADC_DMA_BUF_SIZE; i++)   /* 累加 */
            {
                sum += g_adc_dma_buf[i];
            }

            adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES); /* 取平均值 */
            
            adcx >>= 4; /* 除以2^4倍, 得到12+4位 ADC精度值, 注意: 提高 N bit精度, 需要 >> N */

            /* 显示结果 */
            lcd_show_xnum(134, 110, adcx, 5, 16, 0, BLUE);      /* 显示ADCC采样后的原始值 */

            temp = (float)adcx * (3.3 / 65536);                 /* 获取计算后的带小数的实际电压值,比如3.1111 */
            adcx = temp;                                        /* 赋值整数部分给adcx变量,因为adcx为u16整形 */
            lcd_show_xnum(134, 130, adcx, 1, 16, 0, BLUE);      /* 显示电压值的整数部分,3.1111的话,这里就是显示3 */

            temp -= adcx;                                       /* 把已经显示的整数部分去掉,留下小数部分,比如3.1111-3=0.1111 */
            temp *= 1000;                                       /* 小数部分乘以1000,例如:0.1111就转换为111.1,相当于保留三位小数。 */
            lcd_show_xnum(150, 130, temp, 3, 16, 0X80, BLUE);   /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */

            g_adc_dma_sta = 0;                                  /* 清除DMA采集完成状态标志 */
            adc_dma_enable(ADC_DMA_BUF_SIZE);                   /* 启动下一次ADC DMA采集 */
        }

        LED0_TOGGLE();
        delay_ms(100);
    }
}

上面的代码中,ADC_OVERSAMPLE_TIMES 宏定义表示为了提高 4 位分辨率,ADC 需要 进行 256 次采样才能得的一次 16 位分辨率的数据。为了减少误差,ADC_DMA_BUF_SIZE 宏 定义是 ADC_OVERSAMPLE_TIMES 的 10 倍,为了后期取 16 位转换结果平均值的。 g_adc_dma_buf 数组是 uint16_t 类型的,用于存放转换结果。

为了提高 ADC 的采样速度,调用

adc_ch_conf.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;/* 采样时间1.5个ADC时钟周期 */

将采样时间调整为 1.5 个 ADC 时钟 周期,以得到最高的采样速度。

adcx = sum / (ADC_DMA_BUF_SIZE / ADC_OVERSAMPLE_TIMES);语句可以得到 ADC 采 样 10 次的 16 位分辨率转换结果的平均值。adcx >>= 4;语句对该平均值右移 4 位,这个过程通 常被称为抽取。这样就可以得到 16 位有用的数据,该数据的取值范围是 0~65535,这个操作被 称为累加和转储。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32中,使用DMA进行ADC单通道采集的步骤如下: 1. 首先,在头文件adc.h中定义ADC相关的函数和变量。这些函数包括adc_dam_init()用于初始化ADCDMA,HAL_ADC_MspInit()用于初始化ADC的外设,adc_get_result()用于获取ADC转换结果,adc_get_result_average()用于获取ADC转换结果的平均值,adc_dma_enable()用于使能DMA传输,DMA1_Channel1_IRQHandle()用于处理DMA传输完成的中断。 2. 其次,在中断函数DMA2_Stream0_IRQHandler()中,当DMA传输完成时,需要关闭ADC并设置标志。 3. 然后,在获取转换值的函数Read_ADCValue()中,先禁用ADC,然后循环读取DMA传输的数据,并将其保存到数组Value中。读取完数据后,清空DMA存储空间,并使能ADC和开始转换。 通过以上步骤,可以实现使用DMA进行ADC单通道采集。 #### 引用[.reference_title] - *1* [STM32-单通道ADC采集DMA读取实验](https://blog.csdn.net/Mr_rustylake/article/details/130670100)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32F4 ADC+DMA单通道采集](https://blog.csdn.net/weixin_43512696/article/details/126489564)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [STM32 ADC单/多通道采样+DMA搬运](https://blog.csdn.net/lmgandxka/article/details/128952819)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值