STM32F0使用LL库实现DMA方式AD采集

  在本次项目中,限于空间要求我们选用了STM32F030F4作为控制芯片。这款MCU不但封装紧凑,而且自带的Flash空间也非常有限,所以我们选择了LL库实现。在本文中我们将介绍基于LL库的ADC的DMA采集方式。

1、概述

  这次我们使用DMA方式实现对AD的采集,在遗忘我们使用HAL库和标准库都做过,这次我们使用LL库来实现。接下来我们简单了解一下STM32F030F4中的ADC和DMA。

  首先看一看ADC,STM32F030F4是12位的ADC。它有多达19个多路复用通道,允许它测量来自16个外部和2个内部源的信号。各种通道的A/D转换可采用单通道、连续通道、扫描通道或不连续通道进行。ADC的结果存储在左对齐或右对齐的16位数据寄存器中。ADC结构图如下:

 

  这次我们只使用第1路外部输入。接下来说一说DMA,直接内存访问(DMA)用于在外设和内存以及内存到内存之间提供高速数据传输。DMA可以在没有任何CPU操作的情况下快速移动数据。这使CPU资源可以用于其他操作。STM32F030F4中的DMA控制器有5个通道,每个通道用于管理来自一个或多个外围设备的内存访问请求。它有一个仲裁器来处理DMA请求之间的优先级。DMA结构图如下:

 

  这次我们也使用DMA的第1通道。

2ADC配置

  在使用之前我们需要对ADC和DMA的相关寄存器惊醒必要的配置,才能实现我们想要的功能。我们来看看ADC需要配置的寄存器。ADC需要注意的寄存器主要有两个:ADC控制寄存器(ADC_CR)和ADC配置寄存器1(ADC_CFGR1)。首先我们来说说ADC控制寄存器(ADC_CR),器结构如下:

 

  关于ADC控制寄存器(ADC_CR),有几个设置需要说明一下。

  ADCAL:ADC校准,设置该位可以软件启动校准,校准完成硬件会复位掉这一位。需要注意的是只有ADC处于失能状态,软件对ADCAL的操作才是有效的。也就是说软件对ADCAL操作时,ADC控制寄存器(ADC_CR)必须是全复位状态,即ADCAL=0,ADSTART=0,ADSTP=0, ADDIS=0和 ADEN=0。

  ADSTART: ADC启动转换命令。需要注意只有在ADC已启用,并且没有禁用ADC的挂起请求。也就是说ADEN=1和ADDIS=0时,软件对ADSTART的操作才有效。

  ADEN: ADC使能命令。只有在ADC控制寄存器(ADC_CR)处于全复位状态,即ADCAL=0,ADSTART=0,ADSTP=0,ADDIS=0 和 ADEN=0下,软件对ADEN的操作才有效。这就有一个问题,如果你使用了ADCAL必须等校准完成,才能使能,否则无效。

  接下来我们看一看ADC配置寄存器1(ADC_CFGR1),其结构如下:

 

  关于ADC配置寄存器1(ADC_CFGR1),我们需要关注:CONT(转换模式)、EXTEN[1:0](外部触发使能)、DMACFG(DMA访问配置)、DMAEN(DMA访问使能)。需要说明的是,这几个配置都必须在启动转换前完成配置,即配置时ADSTART=0。

3DMA配置

  配置了ADC还需要配置DMA才能实现我们的想法。关于DMA的配置我们主要说一下4个寄存器:DMA通道配置寄存器(DMA_CCRx)、DMA通道数据数量寄存器(DMA_CNDTRx)、DMA通道外设地址寄存器(DMA_CPARx)、DMA通道内存地址寄存器(DMA_CMARx)。

  首先,我们来看看DMA通道配置寄存器(DMA_CCRx),其结构如下:

 

  对于DMA通道配置寄存器(DMA_CCRx),我们需要关注如下位:MSIZE[1:0](内存大小)、PSIZE[1:0] (外设大小)、MINC(内存的增加模式)、PINC(外设增加模式)、CIRC(循环模式)、DIR(数据传输方向)、EN(通道使能)。除通道使能外,其它均可通过初始化函数进行配置。

  接下来,我们来看看DMA通道数据数量寄存器(DMA_CNDTRx),其结构如下:

 

  其实DMA通道数据数量寄存器(DMA_CNDTRx)用于配置传送数据的个数,如果是往内存中写,就是内存缓冲区的大小,单位与配置寄存器中MSIZE和PSIZE有关。接下来,我们来看一看DMA通道外设地址寄存器(DMA_CPARx),其结构如下:

 

  对于DMA通道外设地址寄存器(DMA_CPARx),就是存储外设的地址,如果我们的外设是ADC,那就是ADC的地址。最后,我们来看一看DMA通道内存地址寄存器(DMA_CMARx),其结构如下:

 

  对于DMA通道内存地址寄存器(DMA_CMARx),其存储的就是对应的变量在内存中的地址,就是我们开辟的数据缓存区的首地址。

4、软件实现

  我们已经说明了ADC和DMA的配置,在这一小节,我们将根据我们前面的分析实现代码。首先来实现ADC的配置代码。

/* ADC 初始化配置 */

static void ADC_Init_Configuration(void)

{

  LL_ADC_InitTypeDef ADC_InitStruct = {0};

  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* ADC相关外设时钟使能 */

  LL_APB1_GRP2_EnableClock(LL_APB1_GRP2_PERIPH_ADC1);

  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_GPIOA);

  /**ADC GPIO 配置:PA0   ------> ADC_IN0  */

  GPIO_InitStruct.Pin = LL_GPIO_PIN_0;

  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;

  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* ADC DMA初始化 */

  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_WORD);

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_WORD);

  LL_DMA_SetDataLength(DMA1,LL_DMA_CHANNEL_1,ADBufferSize);

  LL_DMA_SetPeriphAddress(DMA1,LL_DMA_CHANNEL_1,LL_ADC_DMA_GetRegAddr(ADC1,LL_ADC_DMA_REG_REGULAR_DATA));

  LL_DMA_SetMemoryAddress(DMA1,LL_DMA_CHANNEL_1,(uint32_t)ADC_ConvertedValue);

  LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_1);

  /* 配置ADC通道 */

  LL_ADC_REG_SetSequencerChAdd(ADC1, LL_ADC_CHANNEL_0);

  /* 配置ADC的全局特性:时钟、分辨率、数据对齐和转换次数 */

  ADC_InitStruct.Clock = LL_ADC_CLOCK_ASYNC;

  ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;

  ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;

  ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;

  LL_ADC_Init(ADC1, &ADC_InitStruct);

  ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;

  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;

  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;

  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;

  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;

  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);

  LL_ADC_REG_SetSequencerScanDirection(ADC1, LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);

  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_239CYCLES_5);

  LL_ADC_DisableIT_EOC(ADC1);

  LL_ADC_DisableIT_EOS(ADC1);

  LL_ADC_StartCalibration(ADC1);

  while( LL_ADC_IsCalibrationOnGoing(ADC1));

  LL_ADC_Enable(ADC1);

  LL_ADC_REG_SetDMATransfer(ADC1,LL_ADC_REG_DMA_TRANSFER_UNLIMITED);

  LL_ADC_REG_StartConversion(ADC1);

}

  其实在ADC的初始化配置中也对DMA作了配置,但DMA还需要对始终和中断进行配置。

/**  DMA 控制器初始化配置  */

static void DMA_Init_Configuration(void)

{

  /* DMA 控制器时钟使能 */

  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

  /* DMA1_Channel1_IRQn中断配置 */

  NVIC_SetPriority(DMA1_Channel1_IRQn, 0);

  NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

  配置后,ADC的寄存器如下:

 

  配置后,DMA的寄存器如下:

 

  其实,到这里ADC采集世纪上已经实现了,DMA已经将数据从ADC读出来存到了指定的内存区域,后续的处理就很简单了。

5、总结

  我们已经实现了基于LL库使用DMA方式获取ADC的数据。下面我们就下载到目标设备并检测一下结果。测试结果如下:

 

  上图中,上部是计算完成的物理量值,下部则是DMA写到内存缓存区的ADC的原始码值。

欢迎关注:

猜你喜欢

转载自www.cnblogs.com/foxclever/p/10289772.html