STM32 DAC+ADC(DMA转运数据) HAL库实验

1.实验目的

由于自带板子(正点原子战舰V3:F103)的模拟量模块较少,本实验利用板子自带的DAC的OUT1和OUT2生成2个模拟量,并利用ADC3和DMA对两个模拟量进行采集,可通过按键对模拟量增减,并通过LCD屏实时显示数据变化。通过该实验加深对DAC/ADC/DMA的认识。

2.实验使用外设

DAC: 板载DAC的OUT1(PA4:模拟模式)和OUT2(PA5:模拟模式) 

ADC:ADC1和ADC3具有DMA功能,本实验选择ADC3的ADC_CHANNEL_1(PA1:模拟模式)和ADC_CHANNEL_4(PF6:模拟模式)                  

DMA:由硬件触发时,特定的外设对应特定的DMA和通道,本实验由ADC3触发,故选择DMA2_Channel5

LCD屏,板载LED灯,板载蜂鸣器。

3.具体代码

①DAC驱动代码

1、dac.h

dac.h头文件中仅包含函数的声明,这里就不列出来了

2、dac.c

DAC使用软件触发的方式,初始化DAC之后,DAC通道默认输出为0。主函数通过调用HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, dacval1); 函数来配置 DAC 的通道输出值,参数3是指定数据对齐方式(本实验选择12 位右对齐格式),参数4是要加载到选定数据保存寄存器中的数据。主函数又通过调用HAL_DAC_GetValue 函数获取所选 DAC 通道的最后一个数据输出值。

#include "./BSP/DAC/dac.h"
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"

//使用DAC1的两个通道
DAC_HandleTypeDef g_dac_handle;         /* DAC句柄 */
/**
 * @brief     DAC初始化函数
 * @note      本函数支持DAC1_OUT1/2通道初始化
 *            DAC的输入时钟来自APB1, 时钟频率=36Mhz=27.8ns
 *            DAC在输出buffer关闭的时候, 输出建立时间: tSETTLING = 4us (F103数据手册有写)
 *            因此DAC输出的最高速度约为:250Khz, 以10个点为一个周期, 最大能输出25Khz左右的波形
 *
 * @param     无
 * @retval    无
 */
void dac_init(void)//dac两个通道初始化
{
    GPIO_InitTypeDef gpio_init_struct;
    DAC_ChannelConfTypeDef dac_ch_conf;
    
    //1.使能DAC和DAC输出引脚的时钟
    __HAL_RCC_DAC_CLK_ENABLE();                                 /* 使能DAC1的时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();                     /* 使能DAC OUT1/2的PA4/PA5 */
    
    //2.GPIO初始化
    gpio_init_struct.Pin = GPIO_PIN_4|GPIO_PIN_5; 
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    
    //3.DAC初始化
    g_dac_handle.Instance = DAC;
    HAL_DAC_Init(&g_dac_handle);                                /* 初始化DAC */

    //4.DAC OUT1/OUT2通道配置
    dac_ch_conf.DAC_Trigger = DAC_TRIGGER_NONE;                 /* 不使用触发功能 */
    dac_ch_conf.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;    /* DAC1输出缓冲关闭 */
    HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_1);  /* 配置DAC通道1 */
    HAL_DAC_ConfigChannel(&g_dac_handle, &dac_ch_conf, DAC_CHANNEL_2);  /* 配置DAC通道2 */
    
    //5.开启DAC通道      
    HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_1);                         /* 开启DAC通道1 */
    HAL_DAC_Start(&g_dac_handle,DAC_CHANNEL_2);                         /* 开启DAC通道2 */
            
}
/**
 * @brief       设置通道1/2输出电压
                本实验主函数没用,该函数可直接通过HAL_DAC_SetValue对值进行调整,
                调试时可使用USMART对输出值进行设置
 * @param       outx: 1,通道1; 2,通道2
 * @param       vol : 0~3300,代表0~3.3V
 * @retval      无
 */

void dac_set_voltage(uint8_t outx, uint16_t vol)
{
    double temp = vol;
    temp /= 1000;
    temp = temp * 4096 / 3.3;

    if (temp >= 4096)temp = 4095;   /* 如果值大于等于4096, 则取4095 */

    if (outx == 1)   /* 通道1 */
    {
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, temp); 
                                               /* 12位右对齐数据格式设置DAC值 */
    }
    else            /* 通道2 */
    {
        HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, temp); 
                                               /* 12位右对齐数据格式设置DAC值 */
    }
}

②ADC/DMA驱动代码

1.ADC具有单次转换模式和连续转换模式,非扫描模式和扫描模式。连续转换模式和扫描模式老是弄混,这里讲一下他们的差别。

非扫描模式和扫描模式:是否要扫描多个ADC通道,若只有一个通道,就可以使用非扫描模式。若有多个通道模拟值需要采集,就可以使用扫描模式。扫描模式相当于有多个ADC通道需要转换,非扫描模式就默认只有一个通道。

单次转换模式和连续转换模式:若有1个通道,单次转换就是ADC转换一次这个通道就停下来。连续转换就是ADC转换一次这个通道后,继续将最新的通道值转换出来(模拟量转数字量,转换频率由ADC转换频率决定,可以设置ADC采样时间)。若有多个ADC通道,单次转换就是ADC转换一次这多个通道(按照一定的顺序(规则组、注入组),可以设置)就停下来。连续转换就是ADC转换一次这多个通道后,继续将最新的多个通道值转换出来,继续按照之前设置好的通道顺序进行转换。

2.DMA具有正常模式和循环模式。

正常模式就是指定转运数据个数,DMA转运这些数据后DMA就停止工作。循环模式是转运之前指定个数的数据之后,继续转运指定个数的数据,一直转运。当用ADC硬件触发DMA工作时,ADC的连续转运模式就可以和DMA的循环模式配合,达到一直实时更新数据的目的。

3.adc.h

#ifndef __ADC_H
#define __ADC_H

#include "./SYSTEM/sys/sys.h"


//my_adc注意点:  ADC1和ADC3支持DMA   ADC3的DMA通道只能是DMA2_Channel5
//此实验DMA通道用了宏定义  以后用那个外设就要找匹配的DMA通道
#define ADC_DMA_CHx DMA2_Channel5 


//相关函数声明
void my_adc_nch_dma_init(uint32_t mar);
void my_adc_dma_enable(uint16_t cndtr);
uint16_t get_value(uint8_t n,uint8_t x,uint16_t dat[],uint16_t num);

#endif 

4.adc.c

//ADC:连续模式、扫描模式  用软件触发   ADC使用
//DMA:循环模式  用ADC触发


DMA_HandleTypeDef g_dma_nch_adc_handle = {0};        /* 定义要搬运ADC多通道数据的DMA句柄 */
ADC_HandleTypeDef g_adc_nch_dma_handle = {0};        /* 定义ADC(多通道DMA读取)句柄 */

/**
 * @brief       ADC N通道(2通道) DMA读取 初始化函数
 * @note        本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置
 *              另外,由于本函数用到了ADC2个通道, 宏定义会比较多内容, 因此,本函数就不采用宏定 
 *              义的方式来修改通道了,直接在本函数里面修改, 这里我们使用了ADC通道1(PA1)和通道                  
 *              4(PF6)这2个通道.
 *              
 * @param       mar: 存储器地址 DMA转运的数据默认是32位的,ADC转换的数据默认为12位。
 * @retval      无
 */
void my_adc_nch_dma_init(uint32_t mar)
{
    GPIO_InitTypeDef gpio_init_struct;
    RCC_PeriphCLKInitTypeDef adc_clk_init = {0};
    ADC_ChannelConfTypeDef adc_ch_conf = {0};
    
	//1.使能ADC3/GPIOA(PA1)、GPIOF(PF4)/DMA时钟
    __HAL_RCC_ADC3_CLK_ENABLE();                                    /* 使能ADC1时钟 */
    __HAL_RCC_GPIOA_CLK_ENABLE();                                   /* 开启GPIOA时钟 */
    __HAL_RCC_GPIOF_CLK_ENABLE();                                   /* 开启GPIOF时钟 */
		
    if ((uint32_t)ADC_DMA_CHx > (uint32_t)DMA1_Channel7))/*大于DMA1_Channel7, 则为DMA2的通道了 */
    {
        __HAL_RCC_DMA2_CLK_ENABLE();               /* DMA2时钟使能 */ //本实验用的是DMA2
    }
    else
    {
        __HAL_RCC_DMA1_CLK_ENABLE();               /* DMA1时钟使能 */
    }

    /* 设置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时钟 */
    
    /* 
        设置ADC3通道1、4对应的IO口模拟输入
        AD采集引脚模式设置,模拟输入
        PA1对应 ADC3_IN1
        PF6对应 ADC3_IN4       
    */
	//2.初始化ADC的输入引脚
    gpio_init_struct.Pin = GPIO_PIN_1;                                        /* GPIOA1*/
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;                                 /* 模拟 */
    HAL_GPIO_Init(GPIOA, &gpio_init_struct);
    
	gpio_init_struct.Pin = GPIO_PIN_6;                                        /* GPIOF6*/
    gpio_init_struct.Mode = GPIO_MODE_ANALOG;                                 /* 模拟 */
    HAL_GPIO_Init(GPIOF, &gpio_init_struct);
    
    //3.初始化DMA 
    g_dma_nch_adc_handle.Instance = ADC_DMA_CHx;                    /* 设置DMA通道 */
    g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY;  /* 从外设到存储器模式 */
    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_CIRCULAR;//DMA_NORMAL;             /* 循环模式 */                    
    g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM;             /* 中等优先级 */
    HAL_DMA_Init(&g_dma_nch_adc_handle);
    
    //将DMA与硬件连接起来
    __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle);   

    //4.初始化ADC 
    g_adc_nch_dma_handle.Instance = ADC3;                       /* 选择哪个ADC */
    g_adc_nch_dma_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;  /* 数据对齐方式:右对齐 */
    g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_ENABLE;   /* 使能扫描模式 */
    g_adc_nch_dma_handle.Init.ContinuousConvMode = ENABLE;      /* 使能连续转换 */
    g_adc_nch_dma_handle.Init.NbrOfConversion = 2;              /* 赋值范围是1~16,本实验用到2个规则通道序列 */
    g_adc_nch_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止规则通道组间断模式 */
    g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0;         /* 配置间断模式的规则通道个数,禁止规则通道组间断模式后,此参数忽略 */
    g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;       /* 软件触发 */
    HAL_ADC_Init(&g_adc_nch_dma_handle);                                   /* 初始化 */
    
    //5.校准ADC
    HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle);                    /* 校准ADC */

    //6.配置ADC通道 
    adc_ch_conf.Channel = ADC_CHANNEL_1;                        /* 配置使用的ADC通道PA1 */ 
    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通道 */ 
    adc_ch_conf.Channel = ADC_CHANNEL_4;                        /* 配置使用的ADC通道PF6 */ 
    adc_ch_conf.Rank = ADC_REGULAR_RANK_2;                      /* 采样序列里的第2个 */
    HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);         /* 配置ADC通道 */


    /* 配置DMA数据流请求中断优先级 */  //不使用中断不需要配置
    //HAL_NVIC_SetPriority(ADC3_IRQn, 3, 3);
    //HAL_NVIC_EnableIRQ(ADC3_IRQn);
    //7.启动DMA和ADC
    HAL_DMA_Start(&g_dma_nch_adc_handle, (uint32_t)&ADC3->DR, mar, 0);/* 启动DMA且不开启中断 */
    HAL_ADC_Start_DMA(&g_adc_nch_dma_handle, &mar, 0);     /* 开启ADC,通过DMA传输结果 */
}                                // HAL_ADC_Start_DMA最后一个参数应该与DMA_start的个数一致


/**
 * @brief       使能一次ADC DMA传输
 * @note        该函数用寄存器来操作,防止用HAL库操作对其他参数有修改,也为了兼容性
 * @param       ndtr: DMA传输的次数
 * @retval      无
 */
void my_adc_dma_enable(uint16_t cndtr)//使能一次DMA,之后就一直传输 //其实也可以直接在init函数规定次数后直接开始转运
{
    ADC3->CR2 &= ~(1 << 0);                    /* 先关闭ADC */
    DMA2_Channel5->CCR &= ~(1 << 0);           /* 关闭DMA传输 */
    while (DMA2_Channel5->CCR & (1 << 0));     /* 确保DMA可以被设置 */

    DMA2_Channel5->CNDTR = cndtr;              /* DMA传输数据量 */
    
    DMA2_Channel5->CCR |= 1 << 0;              /* 开启DMA传输 */
    ADC3->CR2 |= 1 << 0;                       /* 重新启动ADC */
    ADC3->CR2 |= 1 << 22;                      /* 启动规则转换通道 */
} 

//
/**
 * @brief       对数组数据进行平均处理
 * @note        当数组元素较多时,注意sum不要超界
 * @param       n:ADC使用的通道数 
 * @param       x:要计算第x个通道     
 * @param       dat[]:采集的数据数组
 * @param       num:采集数据数组的元素个数  必须是n的整数倍
 * @retval      最后返回值强制转换为uint16_t
 */
uint16_t get_value(uint8_t n,uint8_t x,uint16_t dat[],uint16_t num)
{
	uint8_t i=0;
	uint32_t sum=0;//不能是u16否则数据会超过最大范围
    for (i = x - 1; i < num; i += n) /* 累加 */
      {
        sum += dat[i];
      }
    return (uint16_t)(sum /(num/n));     //sum的外() 不能去,()比/的优先级更高
}

这里着重讲一下__HAL_LINKDMA宏定义

HAL 库为了处理各类外设的 DMA 请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接 DMA 和外设句柄。例如要使用串口 DMA 发送,所以方式为: __HAL_LINKDMA(&g_uart1_handler, hdmatx, g_dma_handle); 其中 g_uart1_handler 是串口初始化句柄。g_dma_handle 是 DMA 初始化句柄。hdmatx 是外设句柄结构体的成员变量,在这里实际就是 g_uart1_handler 的成员变量。在 HAL 库中,任何一个可以使用 DMA 的外设,它的初始化结构体句柄都会有一个DMA_HandleTypeDef 指针类型的成员变量,是 HAL 库用来做相关指向的。hdmatx 就是 DMA_HandleTypeDef 结构体指针类型。

我的理解是HAL库没有特别规定DMA的触发方式,HAL库要使用外设(硬件)触发DMA工作时,就必须使用这个宏定义来将硬件与DMA联系起来,作用相当于标准库的设置DMA的硬件触发通道(ADC_DMACmd(ADC3,ENABLE))。HAL库的__HAL_LINKDMA()宏第一个参数是外设句柄的地址,第二个参数是外设结构体成员,第三个参数是要连接的DMA结构体。

③主函数代码

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/DAC/dac.h"
#include "./BSP/ADC/adc3.h"


#define DATA_NUM 100         //指定存储数组的个数(此实验个数必须是通道数的整数倍)

extern DAC_HandleTypeDef g_dac_handle;
uint16_t g_adc_dma_buf[DATA_NUM];   
int main(void)
{
	  
    uint16_t adcx;
    float temp;
    uint8_t t = 0;
    uint16_t dacval = 0;
	uint16_t dacval1 = 0;
    uint8_t key;

    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    usmart_dev.init(72);                        /* 初始化USMART */
    led_init();                                 /* 初始化LED */
    lcd_init();                                 /* 初始化LCD */
    key_init();                                 /* 初始化按键 */
	beep_init();                                /* 初始化蜂鸣器 */
    my_adc_nch_dma_init((uint32_t)&g_adc_dma_buf); /* 初始化ADC3 */
    dac_init();	                                /* 初始化DAC1_OUT1/OUT2通道 */                      
    my_adc_dma_enable(DATA_NUM);                //也可以不使用该函数,可在adc_dma函数中设置
	
    lcd_show_string(30,  50, 200, 16, 16, "STM32F103", RED);//LCD显示
    lcd_show_string(30,  70, 200, 16, 16, "DAC1 OUT1", RED);
    lcd_show_string(30,  90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "WK_UP:+  KEY1:-", RED);

    lcd_show_string(30, 130, 200, 16, 16, "DAC VAL:", BLUE);
    lcd_show_string(30, 150, 200, 16, 16, "DAC VOL:0.000V", BLUE);
    lcd_show_string(30, 170, 200, 16, 16, "ADC VOL:0.000V", BLUE);
		
		lcd_show_string(30, 190, 200, 16, 16, "STM32F103", RED);
    lcd_show_string(30, 210, 200, 16, 16, "DAC1 OUT2", RED);
    lcd_show_string(30, 230, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 250, 200, 16, 16, "KEY0:+  KEY2:-", RED);

    lcd_show_string(30, 270, 200, 16, 16, "DAC VAL:", BLUE);
    lcd_show_string(30, 290, 200, 16, 16, "DAC VOL:0.000V", BLUE);
    lcd_show_string(30, 310, 200, 16, 16, "ADC VOL:0.000V", BLUE);

    while (1)
    {
        t++;
        key = key_scan(0);          /* 按键扫描 */

        if (key == WKUP_PRES)
        {
            if (dacval < 4000)dacval += 200;

            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacval);/* 输出增大200 */
        }
        else if (key == KEY1_PRES)
        {
            if (dacval > 200)dacval -= 200;
            else dacval = 0;

            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dacval); /* 输出减少200 */
        }
				else if (key == KEY0_PRES)
        {
            if (dacval1 < 4000)dacval1 += 200;
        
            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, dacval1); /* 输出增大200 */
        }
				else if (key == KEY2_PRES)
        {
            if (dacval1 > 200)dacval1 -= 200;
            else dacval1 = 0;

            HAL_DAC_SetValue(&g_dac_handle, DAC_CHANNEL_2, DAC_ALIGN_12B_R, dacval1); /* 输出减少200 */
        }

        if (t == 10 || key == KEY1_PRES || key == WKUP_PRES|| key == KEY0_PRES|| key == KEY2_PRES)        /* WKUP/KEY1按下了,或者定时时间到了 */
        {
            adcx = HAL_DAC_GetValue(&g_dac_handle, DAC_CHANNEL_1);  /* 读取前面设置DAC1_OUT1的值 */
            lcd_show_xnum(94, 130, adcx, 4, 16, 0, BLUE);           /* 显示DAC寄存器值 */
            
            temp = (float)adcx * (3.3 / 4096);                      /* 得到DAC电压值 */
            adcx = temp;
            lcd_show_xnum(94, 150, temp, 1, 16, 0, BLUE);        /* 显示电压值整数部分 */
            
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 150, temp, 3, 16, 0X80, BLUE);   /* 显示电压值的小数部分 */
            
            adcx =get_value(2,1,g_adc_dma_buf,DATA_NUM);     /* 得到ADC3通道1的转换结果 */
            temp = (float)adcx * (3.3 / 4096);           /* 得到ADC电压值(adc是16bit的) */
            adcx = temp;
            lcd_show_xnum(94, 170, temp, 1, 16, 0, BLUE);        /* 显示电压值整数部分 */
                
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 170, temp, 3, 16, 0X80, BLUE);   /* 显示电压值的小数部分 */
					
					  
		    adcx = HAL_DAC_GetValue(&g_dac_handle, DAC_CHANNEL_2);  /* 读取前面设置DAC1_OUT1的值 */
            lcd_show_xnum(94, 270, adcx, 4, 16, 0, BLUE);           /* 显示DAC寄存器值 */
            
            temp = (float)adcx * (3.3 / 4096);                      /* 得到DAC电压值 */
            adcx = temp;
            lcd_show_xnum(94, 290, temp, 1, 16, 0, BLUE);         /* 显示电压值整数部分 */
            
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 290, temp, 3, 16, 0X80, BLUE);   /* 显示电压值的小数部分 */
            
            adcx =get_value(2,2,g_adc_dma_buf,DATA_NUM);     /* 得到ADC3通道4的转换结果 */
            temp = (float)adcx * (3.3 / 4096);           /* 得到ADC电压值(adc是16bit的) */
            adcx = temp;
            lcd_show_xnum(94, 310, temp, 1, 16, 0, BLUE);         /* 显示电压值整数部分 */
                
            temp -= adcx;
            temp *= 1000;
            lcd_show_xnum(110, 310, temp, 3, 16, 0X80, BLUE);   /* 显示电压值的小数部分 */
            
            LED0_TOGGLE();                                  /* LED0闪烁,提示程序在运行 */
            t = 0;
												
        }
        if((float)get_value(2,1,g_adc_dma_buf,DATA_NUM)* (3.3 / 4096)>=3.0)
            {
						  LED1(0);  //黄灯亮
						}
				else	LED1(1);
	    if((float)get_value(2,2,g_adc_dma_buf,DATA_NUM)*(3.3 / 4096)>=3.0)
            {
						  BEEP(1) ;  
						}
				else	BEEP(0) ;
           delay_ms(10);
    }
}

4.实验现象

通过按键KEY1和WK_UP对DAC的OUT1输出的电压值进行加和减,通过按键KEY2和KEY0对DAC的OUT2输出的电压值进行加和减,LCD屏实时显示DAC的量化值和实际模拟值以及ADC3对OUT1和OUT2的采集模拟值,通过对按键的按压,发现DAC量化值和模拟值递增或递减,并观察到ADC的采集值与DAC的输出值同步变化且误差很小。当ADC采集到的通道1模拟量大于3.0V时,LED1亮起,小于3.0V后熄灭。当ADC采集到的通道2模拟量大于3.0V时,蜂鸣器响,小于3.0V后不响。

上图为调试观察采集的数据数组。

5.实验总结

为了实现ADC多通道+DMA采集数据的实时更新的不同方法(若是单通道,扫描模式改为非扫描模式即可):

1.ADC:扫描模式+单次转换模式   DMA:正常模式,使用DMA传输完成中断。

使用DMA传输完成中断,DMA的传输计数器就可以设为ADC所用通道数的N倍,用于接收的数组元素个数也是上述传输计数器的个数,这样ADC转换完成触发一次,DMA转运一次,用于接收的数组就可以接收N组ADC多个通道的采集值,一轮采集结束后(数组数据被填充完),触发中断,可在中断里综合处理数据,且因为是正常模式,需要手动开启一次(重置传输计数器的值)DMA,循环上述过程,对数据连续采集。

2.ADC:扫描模式+连续转换模式   DMA:循环模式,不使用中断。(本实验采用2-②)

①DMA的传输计数器就可以直接设为ADC所用通道数,用于接收的数组元素个数也是上述传输计数器的个数,这样ADC转换完成一次,触发DMA转运一次,用于接收的数组就可以接收ADC多个通道的采集值,一轮采集结束后(数组数据被填充完),由循环模式会再次开启DMA从源地址和目的地址传输数据,继续搬运ADC的采集值。此时的DMA只需要开启一次,后面就一直转运数据。

②DMA的传输计数器就可以设为ADC所用通道数的N倍,用于接收的数组元素个数也是上述传输计数器的个数,这样ADC转换完成一次,触发DMA转运一次,用于接收的数组就可以接收N组ADC多个通道的采集值,一轮采集结束后(数组数据被填充完),由循环模式会再次开启DMA从源地址和目的地址传输数据,继续搬运ADC的采集值。此时的DMA只需要开启一次,后面就一直转运数据。

可通过对数组数据取平均值/舍去最大最小值来减少ADC采集的数据误差。

传输计数器与数组元素个数不同的后果

1.计数器>元素个数 DMA转运的数据会被转运到数组最大存储空间的下一地址,也读取不到该数据。

2.计数器<元素个数 DMA转运的数据只会被转运到数组的前计数器个数个元素中去,其他数组数据元素不会被转运数据。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值