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

ADC简介:

ADC(Analog-to-Digital Converter,模/ 数转换器)。也就是将模拟信号转换为数字信号进行处理,在存储或传输时,模数转换器几乎必不可少。

STM32在片上集成的ADC外设非常强大,我使用的奋斗开发板是STM32F103VET6,属于增强型的CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次,连续,扫描或间断模式执行,ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。

ADC工作过程分析:

我们以ADC规则通道转换过程来分析,如上图,所有的器件都是围绕中间的模拟至数字转换器部分展开的。它的左端VREF+,VREF- 等ADC参考电压,ADCx_IN0 ~ ADCx_IN15为ADC的输入信号通道,即某些GPIO引脚。输入信号经过这些通道被送到ADC器件,ADC器件需要收到触发信号才开始进行转换,如EXTI外部触发,定时器触发,也可以使用软件触发。ADC部件接受到触发信号后,在ADCCLK时钟的驱动下对输入通道的信号进行采样,并进行模数转换,其中ADCCLK是来自ADC预分频器。

ADC部件转换后的数值被保存到一个16位的规则通道数据寄存器(或注入通道数据寄存器)中,我们可以通过CPU指令或DMA把它读到内存(变量),模数转换之后,可以出发DMA请求或者触发ADC转换结束事件,如果配置了模拟看门狗,并且采集的电压大于阈值,会触发看门狗中断。

其实对于ADC采样,软件编程主要就是ADC的配置,当然我是基于DMA方式的,所以DMA的配置也是关键!话不多说看代码!

主函数:main.c

  1. #include "printf.h"
  2. #include "adc.h"
  3. #include "stm32f10x.h"
  4.  
  5. extern __IO uint16_t ADC_ConvertedValue;
  6. float ADC_ConvertedValueLocal;
  7. void Delay(__IO uint32_t nCount)
  8. {
  9. for(;nCount !=0;nCount--);
  10. }
  11. int main(void)
  12. {
  13. printf_init();
  14. adc_init();
  15. printf("******This is a ADC test******\n");
  16.  
  17. while(1)
  18. {
  19. ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;
  20. printf("The current AD value =0x%04X\n",ADC_ConvertedValue);
  21. printf("The current AD value =%f V\n",ADC_ConvertedValueLocal);
  22.  
  23. Delay(0xffffee);
  24. }
  25. return 0;
  26. }

注意ADC_ConvertedValueLocal保存了由转换值计算出来的电压值,计算公式是:实际电压值=ADC转换值 x LSB ,这里由于我的板子VREF+接的参考电压为3.3V,所以LSB=3.3/4096,STM32的ADC的精度为12位。

ADC与DMA配置:adc.c

  1. #include "adc.h"
  2. volatile uint16_t ADC_ConvertedValue;
  3. void adc_init()
  4. {
  5. GPIO_InitTypeDef GPIO_InitStructure;
  6. ADC_InitTypeDef ADC_InitStructure;
  7. DMA_InitTypeDef DMA_InitStructure;
  8.  
  9. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
  10. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);
  11.  
  12. GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
  13. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
  14. GPIO_Init(GPIOC,&GPIO_InitStructure);
  15. DMA_DeInit(DMA1_Channel1);
  16. DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//ADC地址
  17. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //内存地址
  18. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)
  19. DMA_InitStructure.DMA_BufferSize = 1; //传输内容的大小
  20. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定
  21. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //内存地址固定
  22. DMA_InitStructure.DMA_PeripheralDataSize =
  23. DMA_PeripheralDataSize_HalfWord ; //外设数据单位
  24. DMA_InitStructure.DMA_MemoryDataSize =
  25. DMA_MemoryDataSize_HalfWord ; //内存数据单位
  26. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; //DMA模式:循环传输
  27. DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //优先级:高
  28. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输
  29.  
  30. DMA_Init(DMA1_Channel1, &DMA_InitStructure); //配置DMA1的4通道
  31. DMA_Cmd(DMA1_Channel1,ENABLE);
  32.  
  33. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式
  34. ADC_InitStructure.ADC_ScanConvMode = DISABLE; //禁止扫描方式
  35. ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//开启连续转换模式
  36. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换
  37. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐
  38. ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目
  39. ADC_Init(ADC1, &ADC_InitStructure);
  40.  
  41. RCC_ADCCLKConfig(RCC_PCLK2_Div8);//配置ADC时钟,为PCLK2的8分频,即9Hz
  42. ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);//配置ADC1通道11为55.5个采样周期
  43.  
  44. ADC_DMACmd(ADC1,ENABLE);
  45. ADC_Cmd(ADC1,ENABLE);
  46.  
  47. ADC_ResetCalibration(ADC1);//复位校准寄存器
  48. while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成
  49.  
  50. ADC_StartCalibration(ADC1);//ADC校准
  51. while(ADC_GetCalibrationStatus(ADC1));//等待校准完成
  52.  
  53. ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换
  54. }

ADC配置还是比较简单的,毕竟只配置了单通道,还是分析一下吧!这里我是把ADC1的通道11使用的GPIO引脚PC1配置成模拟输入模式,在作为ADC的输入时,必须使用模拟输入。对于ADC通道,每个ADC通道对应一个GPIO引脚端口,GPIO的引脚在设为模拟输入模式后可用于模拟电压的输入。STM32F103VET6有三个ADC,这三个ADC公用16个外部通道。
DMA的整体配置为:使用DMA1的通道1,数据从ADC外设的数据寄存器(ADC1_DR_Address)转移到内存(ADC_ConvertedValue变量),内存外设地址都固定,每次传输的大小为半字(16位),使用DMA循环传输模式。

DMA传输的外设地址,也就是ADC1的地址为0x40012400+0x4c,这个地址可查STM32 datasheet获得,如图;

要特别注意ADC转换时间配置,由于ADC时钟频率越高,转换速度越快,那是不是就把ADC的时钟频率设的越大越好呢?其实不然,根据ADC时钟图可知,ADC时钟有上限值,即不能超过14MHz,如图:

这里ADC预分频器的输入为高速外设时钟(PCLK2),使用RCC_ADCCLKConfig()库函数来设置ADC预分频的分频值,PCLK2常用时钟为72MHz,而ADCCLK必须小于14MHz,所以这里ADCCLK为PCLK2的6分频,即12MHz,而我的程序中只是随便设为8分频,9MHz,若希望ADC以最高频率14MHz运行,可以把PCLK2设置为56MHz,然后再4分频得到ACCLK。

ADC的转换时间不仅与ADC的时钟有关,还与采样周期有关。每个ADC通道可以设置为不同的采样周期。STM32的ADC采样时间计算公式为:

T=采样周期+12.5个周期

公式中的采样周期就是函数中配置的 ADC_SampleTime,而后边加上的12.5个周期为固定值,则ADC1通道11的转换时间为T=(55.5+12.5) x 1/9=7.56us。

补充:在adc.c文件中定义了ADC_ConvertedValue变量,要注意这个变量是由关键字volatile修饰的,volatile的作用是让编译器不要去优化这个变量,这样每次用到这个变量时都要回到相应变量的内存中去取值,而如果不使用volatile进行修饰的话,ADC_ConvertedValue变量在被访问的时候可能会直接从CPU的寄存器中取出(因为之前该变量被访问过,也就是说之前就从内存中取出ADC_ConvertedValue的值保存到某个CPU寄存器中),之所以直接从寄存器中去取值而不去内存中取值,这是编译器优化代码的结果(访问CPU寄存器比访问内存快得多)。这里的CPU寄存器指R0,R1等CPU通用寄存器,用于CPU运算及暂存数据,不是指外设中的寄存器。

因为ADC_ConvertedValue这个变量值随时都会被DMA控制器改变的,所以用volatile来修饰它,确保每次读取到的都是实时ADC转换值。

adc.h:

  1. #ifndef _adc_H
  2. #define _adc_H
  3. #include "stm32f10x.h"
  4. #include "stm32f10x_dma.h"
  5. #include "stm32f10x_adc.h"
  6. #define ADC1_DR_Address ((uint32_t)0x4001244c);
  7.  
  8. void adc_init(void);
  9.  
  10. #endif

效果图:

由于我的开发板没有滑动变阻器,所以我就将电压的输入端接入通用IO口的3V引脚。如图:

STM32之ADC实例(基于DMA方式)的更多相关文章

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

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

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

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

  3. 案例 stm32单片机,adc的双通道+dma 内部温度

    可以这样理解 先配置adc :有几个通道就配置几个通道. 然后配置dma,dma是针对adc的,而不是针对通道的. 一开始我以为一个adc通道对应一个dma通道.(这里是错的,其实是我想复杂了) 一个 ...

  4. stm32之ADC应用实例(单通道、多通道、基于DMA)

    文本仅做记录.. 硬件:STM32F103VCT6 开发工具:Keil uVision4 下载调试工具:ARM仿真器 网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗).  所用的芯片内嵌 ...

  5. stm32之ADC应用实例(单通道、多通道、基于DMA)-转载精华帖,最后一部分的代码是精华

    硬件:STM32F103VCT6    开发工具:Keil uVision4    下载调试工具:ARM仿真器网上资料很多,这里做一个详细的整合.(也不是很详细,但很通俗).所用的芯片内嵌3个12位的 ...

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

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

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

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

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

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

  9. STM32应用实例十五:STM32的ADC通道间干扰的问题

    最近我们在开发一个项目时,用到了MCU自带的ADC,在调试过程中发现通道之间村在相互干扰的问题.以前其实也用过好几次,但要求都不高所以没有太关注,此次因为物理量的量程较大,所以看到了变化. 首先来说明 ...

随机推荐

  1. Java 方法与数组

    方法 什么是方法? 方法定义:方法(Method),函数(function),其实就指一个特定的功能操作,程序中完成独立功能,可重复使用的一段代码的集合. 方法的定义 方法定义格式: [修饰符] 返回 ...

  2. java定时案例

    好久没写笔记了,变懒了! java定时运行的三个案例: 一, 通过sleep方法来达到定时任务的效果 public class testTime { public static void main(S ...

  3. git 比较两个分支不同的commit

    比如我们有 2 个分支:master, dev,现在想查看这两个 branch 的区别,有以下几种方式: undefined 1.查看 dev 有,而 master 中没有的: 1.查看 dev 有, ...

  4. android: 日期转Unix时间戳,Unix时间戳转日期,带时区

    1.UTC时间&GMT时间 UTC时间是时间标准时间(Universal Time Coordinated),UTC是根据原子钟来计算时间,误差非常小. UTC也是指零时区的时间,如果要表示其 ...

  5. 转换为CString

    CString a, b, c;c = a + b; 使用Format方法方便的实现int.float和double等数字类型转换为CString字符串. %c 单个字符 %d 十进制整数(int) ...

  6. 安装mycat

    1.下载mycat 为了方便,我已经下载下来.我选择的版本是1.6版本 2.解压,安装在/home/xm6f/dev目录下 cd /home/xm6f/devtar -zxvf Mycat-serve ...

  7. Oracle SQL 脚本跟踪

    NC Oracle SQL 脚本跟踪 脚本: select * from v$sqlarea a and a.LAST_ACTIVE_TIME >= to_date( '2013-02-21 1 ...

  8. jQuery跨域调用WebService

    jQuery跨域调用WebService举例html: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...

  9. linux lnmp下无法使用mail发邮件的两种解决方法

    在配置了lnmp环境后,出现了mail函数不能发送邮件的问题,其实有两种方法,一是使用sendmail组件,而是使用postfix. 方法一,使用sendmail组件来发邮件  1.安装 sendma ...

  10. java基础系列(一):Number,Character和String类及操作

    这篇文章总结了Java中最基础的类以及常用的方法,主要有:Number,Character,String. 1.Number类 在实际开发的过程中,常常会用到需要使用对象而不是内置的数据类型的情形.所 ...