在本次项目中,限于空间要求我们选用了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的原始码值。

欢迎关注:

STM32F0使用LL库实现DMA方式AD采集的更多相关文章

  1. 基于uFUN开发板的心率计(一)DMA方式获取传感器数据

    前言 从3月8号收到板子,到今天算起来,uFUN到手也有两周的时间了,最近利用下班后的时间,做了个心率计,从单片机程序到上位机开发,到现在为止完成的差不多了,实现很简单,uFUN开发板外加一个Puls ...

  2. STM32之ADC实例(基于DMA方式)

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/zouleideboke/article/details/75112224 ADC简介: ADC(An ...

  3. 【STM32H7教程】第46章 STM32H7的ADC应用之DMA方式多通道采样

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第46章       STM32H7的ADC应用之DMA方式多 ...

  4. 【STM32H7教程】第60章 STM32H7的DAC应用之定时器触发实现DMA方式双通道波形

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第60章       STM32H7的DAC应用之定时器触发实 ...

  5. GD32F330 | ADC实例 基于DMA方式

    GD32F330 | ADC实例 基于DMA方式 简单记录一下 ADC多通道转换 DMA搬运 的使用,以 GD32F330G8U6 为例: 一.ADC 基础知识 12位ADC是一种采用逐次逼近方式的模 ...

  6. STM32 串口DMA方式接收(转)

    STM32 是一款基于ARM Cortex-M3内核的32位MCU,主频最高可达72M.最近因为要在车机上集成TPMS功能, 便开始着手STM32的开发工作,STM32F10x系列共有5个串口(USA ...

  7. 关于Stm32定时器+ADC+DMA进行AD采样的实现

    Stm32的ADC有DMA功能这都毋庸置疑,也是我们用的最多的!然而,如果我们要对一个信号(比如脉搏信号)进行定时采样(也就是隔一段时间,比如说2ms),有三种方法: 1.使用定时器中断每隔一定时间进 ...

  8. DMA方式的数据传送过程

      DMA方式具有如下特点: 1. 外部设备的输入输出请求直接发给主储存器. 主存储器既可以被CPU访问,也可以被外围设备访问.因此,在主存储器中通常要有一个存储管理部件来为各种访问主存储器的申请排队 ...

  9. 使用DMA方式发送串口数据

    一.初始化部分代码 //串口接收DMA缓存 uint8_t Uart_Rx[UART_RX_LEN] = {}; uint32_t Uart_Send_Buffer[] = {}; void USAR ...

随机推荐

  1. 【转】Linux中的特殊权限粘滞位(sticky bit)详解

    Linux下的文件权限 在linux下每一个文件和目录都有自己的访问权限,访问权限确定了用户能否访问文件或者目录和怎样进行访问.最为我们熟知的一个文件或目录可能拥有三种权限,分别是读.写.和执行操作, ...

  2. 2019-04-09 SpringBoot+Druid+MyBatis+Atomikos 的多数据源配置

    前面部分是网上找的,我按照网上写的把自己搭建的过程展示一次 1.引入依赖 目前项目本来使用到了Mybatis plus(在自己的Mapper接口中继承BaseMapper获得基本的CRUD,而不需要增 ...

  3. 4月22日MySQL学习

    前面学习的知识基本都是概念知识没有什么代码,然后还有图形界面来辅助学习. 今天学习了MySQL的存储引擎,最常用的两种 MYISAM:不支持事务,也不支持外键,但是访问速度快. INNODB:支持事务 ...

  4. [2019.04.01]Linux 学习心得(2)-- tar 命令的理解

    这篇文章并不是发布最早的但是阅读量却每天都见长,很想知道各位大大是怎么找到这篇文章的.如果不忙,还请各位大大评论一下我看看,没准我可以为大家改进一下本文,提升一下质量. =============== ...

  5. 【UOJ453】【集训队作业2018】围绕着我们的圆环 线性基 DP

    题目大意 有一个 \(n\times k\) 的 01矩阵 \(C\),求有多少个 \(n\times m\) 的矩阵 \(A\) 和 \(m\times k\) 的矩阵 \(B\),满足 \(A\t ...

  6. LeetCode--11_974_Subarray_Sums_Divisible_by_K

    题目链接:点击这里 public static int subarraysDivByK(int[] A, int K) { int ans = 0,sum = 0; int[] B = new int ...

  7. ZooKeeper集群与Leader选举

    说说你对ZooKeeper集群与Leader选举的理解?   ZooKeeper是一个开源分布式协调服务.分布式数据一致性解决方案.可基于ZooKeeper实现命名服务.集群管理.Master选举.分 ...

  8. FTP文件上传 支持断点续传 并 打印下载进度(二) —— 单线程实现

    这个就看代码,哈哈哈哈哈  需要用到的jar包是: <dependency> <groupId>commons-net</groupId> <artifact ...

  9. Word转PDF(SaveAsPDFandXPS + jacob)

    Windows系统下 1.下载所需插件和jar包 SaveAsPDFandXPS(微软自带office转PDF.XPS):http://www.microsoft.com/zh-cn/download ...

  10. 010-1 Socket地址族AddressFamily

    AddressFamily地址组成员 成员名称 说明 AppleTalk AppleTalk 地址. Atm 本机 ATM 服务地址. Banyan Banyan 地址. Ccitt 对于 CCITT ...