版权声明:本文为博主原创文章,未经博主允许不得转载。 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. Linux系统下vi编辑器的一些简单使用操作

    Linux系统安装vi编辑器 打开终端,输入: sudo apt install vim 然后输入Y等待安装即可 Linux系统下使用vi编辑器的使用 安装好vim后,终端中输入vim即可进入vim编 ...

  2. ST Debug (printf) Viewer for Jlink

    Debug (printf) Viewer http://www.keil.com/support/man/docs/uv4/uv4_db_dbg_serialwin.htm Serial Windo ...

  3. OpenJudge计算概论-大象喝水

    /*========================================================= 大象喝水 总时间限制: 1000ms 内存限制: 65536kB 描述 一只大象 ...

  4. java匿名内部类new(){}

    匿名内部类:顾名思义,没有名字的内部类.表面上看起来它们似乎有名字,实际那不是它们的名字.当程序中使用匿名内部类时,在定义匿名内部类的地方往往直接创建该类的一个对象.匿名内部类的声明格式如下:new ...

  5. [Java复习] 设计模式 Design Pattern

    设计模式的六大原则 1.开闭原则(Open Close Principle) 对扩展开放,对修改关闭. 2.里氏代换原则(Liskov Substitution Principle) 任何基类可以出现 ...

  6. 一个Flask应用运行过程剖析

    相信很多初学Flask的同学(包括我自己),在阅读官方文档或者Flask的学习资料时,对于它的认识是从以下的一段代码开始的: from flask import Flask app = Flask(_ ...

  7. windows 10 enterprize LTSC

    to avtivate the windows 10 enterprize LTSC version, do the following command every 6 months. slmgr - ...

  8. LeetCode_121. Best Time to Buy and Sell Stock

    121. Best Time to Buy and Sell Stock Easy Say you have an array for which the ith element is the pri ...

  9. (三)java虚拟机内存管理和线程独占区和线程共享区

    一.内存管理 二.线程独占区之程序计数器(Program Counter Register) 程序计数器是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型里,字节 ...

  10. MYSQL 启动问题

    1.日志中出现  [ERROR] InnoDB: The Auto-extending innodb_system data file './ibdata1' is of a different si ...