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转运的数据只会被转运到数组的前计数器个数个元素中去,其他数组数据元素不会被转运数据。