• 硬件:STM32F103VCT6
  • 开发工具:Keil uVision4
  • 下载调试工具:ARM仿真器

网上资料很多,这里做一个详细的整合。(也不是很详细,但很通俗)。
所用的芯片内嵌3个12位的模拟/数字转换器(ADC),每个ADC共用多达16个外部通道,2个内部通道。

3个:代表ADC1、ADC2、ADC3(下图是芯片固件库的截图)

12位:也叫ADC分辨率、采样精度。先来看看二进制的12位可表示0-4095个数,也就是说转换器通过采集转换所得到的最大值是4095,如:“111111111111”=4095,那么我们怎么通过转换器转换出来的值得到实际的电压值呢?如果我们要转换的电压范围是0v-3.3v的话,转换器就会把0v-3.3v平均分成4096份。设转换器所得到的值为x,所求电压值为y。
那么就有:

16个外部通道:简单的说就是芯片上有16个引脚是可以接到模拟电压上进行电压值检测的。16个通道不是独立的分配给3个转换器(ADC1、ADC2、ADC3)使用,有些通道是被多个转换器共用的。首先看看16个通道在固件库的宏定义(写代码要看的):

到这里大家可能会有疑问,每个通道到底对应哪个引脚呢?下面先给出部分引脚图:

16个通道的引脚都在上面的图中,拿其中的一个进行说明:

ADC123_IN10:字母“ADC”不用多说,“123”代表它被3个(ADC1、ADC2、ADC3)转换器共用的引脚,“10”对应刚才那张宏定义图里面的ADC_Channel_10,这样就能找到每个通道对应的引脚了。

2个内部通道:一个是内部温度传感器,一个是内部参考电压。


在某个项目中要用到芯片里面的AD转换器,那么要怎么写应用代码?(以下是代码讲解)

芯片固件的库函数为我们提供了很多封装好的函数,只要运用它提供的函数接口就可以了,宏观上来讲就搞懂两个事情就行了:

  • 初始化(设置用的哪个引脚、单通道、还是多通道同时转换、是否使用DMA等配置)?
  • 怎么让转换器进行一次数据获取?

以下分别讲述三种不同方式(单通道、多通道、基于DMA的多通道采集)的ADC应用实例:

/*单通道的ADC采集*/
void  Adc_Config(void)
{   /*定义两个初始化要用的结构体,下面给每个结构体成员赋值*/ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure;/*使能GPIOA和ADC1通道时钟注意:除了RCC_APB2PeriphClockCmd还有RCC_APB1PeriphClockCmd,那么该如何选择?APB2:高速时钟,最高72MHz,主要负责AD输入,I/O,串口1,高级定时器TIMAPB1:低速时钟,最高36MHz,主要负责DA输出,串口2、3、4、5,普通定时器TIM,USB,IIC,CAN,SPI*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );      RCC_ADCCLKConfig(RCC_PCLK2_Div6);  //72M/6=12, ADC的采样时钟最快14MHz  /*配置输入电压所用的PA0引脚*/         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //GPIO_Mode_AIN:模拟输入(还有其他什么模式?请看下面的附录图1)GPIO_Init(GPIOA, &GPIO_InitStructure);   ADC_DeInit(ADC1); //复位,将ADC1相关的寄存器设为默认值ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;   //工作模式:ADC1和ADC2独立工作模式  (还有其他什么模式?请看下面的附录图2)ADC_InitStructure.ADC_ScanConvMode = DISABLE;  //数模转换工作:扫描(多通道)模式=ENABLE、单次(单通道)模式=DISABLEADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//数模转换工作:连续=ENABLE、单次=DISABLEADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  //ADC转换由软件触发启动 (还有其他什么模式?请看下面的附录图3)ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐   除了右就是左:ADC_DataAlign_LeftADC_InitStructure.ADC_NbrOfChannel = 1;   //顺序进行规则转换的ADC通道的数目   范围是1-16ADC_Init(ADC1, &ADC_InitStructure);    //根据ADC_InitStruct中指定的参数初始化外设ADC1的寄存器/*为啥要设置下面这一步?细心的你可以发现上面初始化了一个引脚通道,初始化了一个ADC转换器,但ADC转换器并不知道你用的是哪个引脚吧?这一步目的是:设置指定ADC的规则组通道(引脚),设置它们的转化顺序和采样时间函数原型:void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, u8 ADC_Channel, u8 Rank, u8 ADC_SampleTime)参数1 ADCx:x可以是1或者2来选择ADC外设ADC1或ADC2 参数2 ADC_Channel:被设置的ADC通道  范围ADC_Channel_0~ADC_Channel_17参数3 Rank:规则组采样顺序。取值范围1到16。ADC_SampleTime:指定ADC通道的采样时间值  (取值范围?请看下面的附录图4)*/ ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );       ADC_Cmd(ADC1, ENABLE);  //使能指定的ADC  注意:函数ADC_Cmd只能在其他ADC设置函数之后被调用/*下面4步按流程走,走完就行*/ADC_ResetCalibration(ADC1); //重置指定的ADC的校准寄存器while(ADC_GetResetCalibrationStatus(ADC1)); //等待上一步操作完成ADC_StartCalibration(ADC1);  //开始指定ADC的校准状态  while(ADC_GetCalibrationStatus(ADC1));//等待上一步操作按成       }

初始化完成之后,在主函数中:

void main(void)
{   float ADC_ConvertedValue; float ADC_ConvertedValueLocal; Adc_Config();while(1){ADC_SoftwareStartConvCmd(ADC1, ENABLE);      //启动转换  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  //等待转换完成ADC_ConvertedValue=ADC_GetConversionValue(ADC1); //获取转换结果*ADC_ConvertedValue* ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);   //计算出实际电压值*ADC_ConvertedValueLocal*//这里适当加上一些延迟//最好连续转换几次 取平均值  这里就省略写了 点到为止}
}

附录图1-GPIO_Mode值:

附录图2-ADC_Mode值:

附录图3-ADC_ExternalTrigConv值:

附录图4-ADC_SampleTime值:

对于一些刚接触stm32的人来说,看了上面的代码可能还会有很多疑问。

  • 为什么要使能时钟?时钟到底设置多少才合适?
  • 对于ADC_GetConversionValue(ADC1)这个函数参数并没有指定那个通道,如果多个通道同时使用CAN1转换器转换时怎么获取每个通道的值?

第一个问题,所有的外设都要使能时钟,时钟源分为外部时钟和内部时钟,外部时钟比如接8MHz晶振,内部时钟就在芯片内部集成,时钟源为所有的时序电路提供基本的脉冲信号。时钟源好比是一颗跳动的心脏,它按照一定的频率在跳动,所有的器官(外设)要跟心脏(时钟源)桥接起来才能工作,但不同的外设需要的频率不同,所以在时钟源跟外设之中常常还会有一些分频器或者倍频器,以实现对频率的衰减或增强。还想了解更多专业的解释可以去研究stm32的时钟树图。

**第二个问题,**回答这个问题那么就等于开始介绍多通道转换怎么实现了,看下图

由图理解,一个ADC转换器只能选择转换一个通道,那么对比单通道我们只需做一下改变(以双通道为例):
1.在void Adc_Config(void)函数里面添加:

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);

配置多一个IO(PA1)口, 也就是通道1。

2.在void Adc_Config(void)函数里面添加:

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );    

先不指定ADC转换通道。

3.在主函数循环里改为:

    while(1){/*先采集通道1数据*/ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );ADC_SoftwareStartConvCmd(ADC1, ENABLE);        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  ADC_ConvertedValue=ADC_GetConversionValue(ADC1); ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);  /*再采集通道2数据*/ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );ADC_SoftwareStartConvCmd(ADC1, ENABLE);     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));  ADC_ConvertedValue=ADC_GetConversionValue(ADC1); ADC_ConvertedValueLocal=(float)ADC_ConvertedValue*(3.3/4096);  //加入适当延时}

完成以上三步就能把单通道扩展到双通道(或者更多个通道)。不过还有一种基于DMA的多通道转换更加合适。


首先简单介绍DMA,DMA(Direct Memory Access,直接内存存取) ,用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无需CPU干预,节省CPU资源;ADC转换出来的值直接赋值给定义好的变量中。配置好的DMA可以不停地将ADC转换值写到该变量中,在主函数直接判断该变量就知道此时的AD值,也就是说在主函数中不需要调用ADC_GetConversionValue()函数来获取转换值。

DMA跟其他外设一样需要进行配置通道,使能时钟等参数。
下面直接看代码分析:

/*基于DMA的ADC多通道采集*/volatile uint16 ADCConvertedValue[10][3];//用来存放ADC转换结果,也是DMA的目标地址,3通道,每通道采集10次后面取平均数void DMA_Init(void)
{DMA_InitTypeDef DMA_InitStructure;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//使能时钟DMA_DeInit(DMA1_Channel1);    //将通道一寄存器设为默认值DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);//该参数用以定义DMA外设基地址DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;//该参数用以定义DMA内存基地址(转换结果保存的地址)DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//该参数规定了外设是作为数据传输的目的地还是来源,此处是作为来源DMA_InitStructure.DMA_BufferSize = 3*10;//定义指定DMA通道的DMA缓存的大小,单位为数据单位。这里也就是ADCConvertedValue的大小DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//设定外设地址寄存器递增与否,此处设为不变 DisableDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//用来设定内存地址寄存器递增与否,此处设为递增,EnableDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度为16位DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//数据宽度为16位DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环缓存模式DMA_InitStructure.DMA_Priority = DMA_Priority_High;//DMA通道拥有高优先级 分别4个等级 低、中、高、非常高DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//使能DMA通道的内存到内存传输DMA_Init(DMA1_Channel1, &DMA_InitStructure);//根据DMA_InitStruct中指定的参数初始化DMA的通道DMA_Cmd(DMA1_Channel1, ENABLE);//启动DMA通道一
}

下面是ADC的初始化,可以将它与上面的对比一下有啥不同,重复的就不解析了

void Adc_Init(void)
{ADC_InitTypeDef ADC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;/*3个IO口的配置(PA0、PA1、PA2)*/GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(GPIOA, &GPIO_InitStructure);/*IO和ADC使能时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOA,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div6);ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;ADC_InitStructure.ADC_ScanConvMode = ENABLE;ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;ADC_InitStructure.ADC_NbrOfChannel = 3;ADC_Init(ADC1, &ADC_InitStructure);ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);//通道一转换结果保存到ADCConvertedValue[0~10][0]ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_239Cycles5););//通道二转换结果保存到ADCConvertedValue[0~10][1]ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_239Cycles5); );//通道三转换结果保存到ADCConvertedValue[0~10][2]ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA支持ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while(ADC_GetResetCalibrationStatus(ADC1));ADC_StartCalibration(ADC1);while(ADC_GetCalibrationStatus(ADC1));}

做完这两步,ADCConvertedValue数组的值就会随输入的模拟电压改变而改变,在主函数中最好取多几次的平均值,再通过公式换算成电压单位。下面是主函数:

int main(void)
{int sum;u8 i,j;float ADC_Value[3];//用来保存经过转换得到的电压值ADC_Init();DMA_Init();ADC_SoftwareStartConvCmd(ADC1, ENABLE);//开始采集while(1){for(i=0;i<3;i<++){sum=0;for(j=0;j<10;j++){sum+=ADCConvertedValue[j][i];}ADC_Value[i]=(float)sum/(10*4096)*3.3;//求平均值并转换成电压值//打印(略)}//延时(略)}
}

ADCConvertedValue的定义用了volatile修饰词,因为这样可以保证每次的读取都是从绝对地址读出来的值,不会因为被会编译器进行优化导致读取到的值不是实时的AD值。

最后提醒一下,接线测试的时候记得接上基准电压,就是VREF+和VREF-这两个引脚。如果不想外接线测试就将内部通道的电压读出来,这样就不用配置IO口了。

水平有限,仅供参考,错误之处以及不足之处还望多多指教。

推荐阅读:
基于stm32、spi协议的Fatfs文件系统移植(附完整代码下载)
基于RT-Thread实现的小游戏(贪吃蛇、俄罗斯方块)
stm32之IIC应用实例(AT24C02芯片,硬件和软件方式驱动)

stm32之ADC应用实例(单通道、多通道、基于DMA)相关推荐

  1. 【STM32】STM32CUBEMX + ADC(单通道,双通道DMA)

    STM32CUBEMX + ADC(单通道,双通道DMA) 案例应用 :使用ADC采集电压(单通道.单通道+DMA.双通道+DMA),并利用串口打印采集转换后的电压值 1.工具 IAR STM32CU ...

  2. 14、江科大stm32视频学习笔记——AD单通道和AD多通道代码

    目录 一.程序现象 1.AD单通道 2.AD多通道 二.原理图 三.AD单通道 1.AD.c(单次转换非扫描) 2.改为连续转换非扫描 3.main.c 四.AD双通道(单次转换非扫描) 1.思路 2 ...

  3. 数字源表测试太阳能电池片单通道多通道方案

    太阳能电池也叫光伏电池,其发电过程主要有三部分:第一,半导体材料吸收光能产生出非平衡的电子-空穴对或偶极子:第二,非平衡电子和空穴从产生处向势场运动,这种运动可以是扩散运动,也可以是漂移运动:第三,非 ...

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

    出处:https://blog.csdn.net/zouleideboke/article/details/75112224?locationNum=9&fps=1 ADC简介: ADC(An ...

  5. STM-32:ADC模数转换器—ADC单通道转换/ADC多通道转换

    目录 一.ADC 模数转换器 1.1ADC简介 1.2 逐次逼近型ADC工作原理 1.3STM32中的ADC基本结构 1.4STM32中ADC的输入通道 1.5STM32中的ADC的四种转换模式 1. ...

  6. STM32 ADC单通道与多通道_DMA学习笔记

    转自:https://blog.csdn.net/dmfylb/article/details/72802690 第一部分  ADC单路采集 下面我们将 PC0引脚配置成 AD1的通道10为例进行讲解 ...

  7. stm32 多通道AD和单通道AD的差别

    今天萧条了一下 stm32的AD 感觉用起来还是比较顺手的~下面我就总结一下 多通道AD和单通道的一些差别 (1)首先 我们要改的就是vu16 ADC1ConvertedValue[2]: 这个vol ...

  8. STM32之ADC模数转换器单通道。

    ADC模数转换器 ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁. 12位逐次 ...

  9. STM32学习:通过DMA读取ADC规则通道多通道转换数据

    1.STM32的DMA简介 直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输.无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作. ...

最新文章

  1. ulimit限制 新系统_系统限制ulimit学习
  2. tomcat的缺少tcnative-1.dll的解决
  3. mysql 取出全部数据库_php取出mysql数据库中所有数据
  4. NET问答: 如何让 HttpClient 支持 Http 2.0 协议?
  5. [Java基础]反射获取成员变量并使用练习
  6. pcl_openmap_OpenMap教程第2部分–使用MapHandler构建基本地图应用程序–第1部分
  7. 西门子宣布美国充电桩扩产计划
  8. 软件测试的目标及组织测试过程
  9. 【Vue】—props属性
  10. linux快速查找文件中所包含的指定字段的个数
  11. python 批量gif转tif_使用Python 批量转移*.tif和*.mov文件
  12. linux编译安装mysql的意思,linux编译模式安装mysql 步骤说明
  13. android高仿ios控制中心,控制中心IOS 13-安卓仿苹果ios控制中心插件
  14. java http 401_401 API请求状态错误
  15. 春江花朝秋月夜,往往取酒还独倾——python函数进阶
  16. postfix 测试邮件服务器,搭建Postfix邮件服务器
  17. 围城如社会,故事如生活
  18. 如何用计算机名称获取计算机ip
  19. du -sh 和ls -lh的区别
  20. 数据结构——图书管理系统

热门文章

  1. PHP不能连接MS SQL Server的解决方法
  2. Bzoj1312 / POJ3155 Neerc2006 Hard Life
  3. SpringMVC默认欢迎页面的问题
  4. [原创]JMeter初次使用总结
  5. 我的2012年度总结
  6. POJ3041Asteroids(二分图最少顶点覆盖)
  7. 基于运维网V8环境安装ntop
  8. postman代码没有问题,但是文件上传失败
  9. 使用kubectl port-forward暴露minikube k8s service端口
  10. /etc/resolv.conf root用户下不能修改