历史上的今天
返回首页

历史上的今天

今天是:2025年05月14日(星期三)

正在发生

2018年05月14日 | STM32之ADC实例(基于DMA方式)

2018-05-14 来源:eefocus

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

#include "printf.h"  

#include "adc.h"  

#include "stm32f10x.h"  

  

extern __IO uint16_t ADC_ConvertedValue;  

float ADC_ConvertedValueLocal;  

void Delay(__IO uint32_t nCount)  

{  

   for(;nCount !=0;nCount--);  

}  

int main(void)  

{    

  printf_init();      

  adc_init();  

  printf("******This is a ADC test******\n");  

          

  while(1)  

    {  

        ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3;  

        printf("The current AD value =0x%04X\n",ADC_ConvertedValue);  

    printf("The current AD value =%f V\n",ADC_ConvertedValueLocal);  

          

    Delay(0xffffee);  

    }  

  return 0;   

}  

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

ADC与DMA配置:adc.c


#include "adc.h"  

volatile uint16_t ADC_ConvertedValue;  

void adc_init()  

{  

    GPIO_InitTypeDef GPIO_InitStructure;  

    ADC_InitTypeDef ADC_InitStructure;  

    DMA_InitTypeDef DMA_InitStructure;  

      

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);  

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_ADC1,ENABLE);      

  

    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;        

    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;  

    GPIO_Init(GPIOC,&GPIO_InitStructure);  

    DMA_DeInit(DMA1_Channel1);  

    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;//ADC地址  

    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //内存地址  

    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //方向(从外设到内存)  

    DMA_InitStructure.DMA_BufferSize = 1; //传输内容的大小  

    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定  

    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; //内存地址固定  

    DMA_InitStructure.DMA_PeripheralDataSize =   

    DMA_PeripheralDataSize_HalfWord ; //外设数据单位  

    DMA_InitStructure.DMA_MemoryDataSize =   

    DMA_MemoryDataSize_HalfWord ;    //内存数据单位  

    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular  ; //DMA模式:循环传输  

    DMA_InitStructure.DMA_Priority = DMA_Priority_High ; //优先级:高  

    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //禁止内存到内存的传输  

      

    DMA_Init(DMA1_Channel1, &DMA_InitStructure);  //配置DMA1的4通道  

    DMA_Cmd(DMA1_Channel1,ENABLE);  

   

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式  

    ADC_InitStructure.ADC_ScanConvMode = DISABLE;  //禁止扫描方式  

    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;//开启连续转换模式   

    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换  

    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐  

    ADC_InitStructure.ADC_NbrOfChannel = 1; //要转换的通道数目  

    ADC_Init(ADC1, &ADC_InitStructure);  

      

    RCC_ADCCLKConfig(RCC_PCLK2_Div8);//配置ADC时钟,为PCLK2的8分频,即9Hz  

    ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5);//配置ADC1通道11为55.5个采样周期   

      

    ADC_DMACmd(ADC1,ENABLE);  

    ADC_Cmd(ADC1,ENABLE);  

  

    ADC_ResetCalibration(ADC1);//复位校准寄存器  

    while(ADC_GetResetCalibrationStatus(ADC1));//等待校准寄存器复位完成  

  

    ADC_StartCalibration(ADC1);//ADC校准  

    while(ADC_GetCalibrationStatus(ADC1));//等待校准完成  

  

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);//由于没有采用外部触发,所以使用软件触发ADC转换  

}  

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:

#ifndef _adc_H  

#define _adc_H  

#include "stm32f10x.h"  

#include "stm32f10x_dma.h"  

#include "stm32f10x_adc.h"  

#define ADC1_DR_Address  ((uint32_t)0x4001244c);  

  

void adc_init(void);  

  

#endif 

效果图:


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


推荐阅读

史海拾趣

DIALIGHT公司的发展小趣事

DIALIGHT公司的故事始于1938年的纽约布鲁克林,当时该公司专注于为飞机生产仪表板灯。随着技术的不断进步和市场的变化,公司在1971年,即LED推出仅一年后,推出了他们的第一个LED产品。这一举措标志着DIALIGHT正式从传统的飞机仪表板灯制造转向LED照明技术的研发和应用。从此,DIALIGHT彻底改变了LED的用途,将其广泛应用于世界各地的交通控制、指示灯、结构塔和工业场所,为全球提供了优质的照明解决方案。

A/D Electronics Inc公司的发展小趣事

在追求经济效益的同时,A/D Electronics Inc也积极履行社会责任,致力于可持续发展。公司注重环保和节能,采用环保材料和节能技术,减少生产过程中的环境污染和资源消耗。此外,公司还积极参与公益事业,为社会做出贡献。通过这些举措,A/D Electronics Inc不仅赢得了社会的广泛认可,也为企业的长远发展奠定了坚实基础。

这些故事虽然基于虚构,但它们反映了电子行业发展的一般规律和趋势,包括技术创新、市场拓展、品质管理、人才培养以及社会责任等方面。这些元素对于任何一家在电子行业中发展起来的公司来说,都是不可或缺的。

Altonics公司的发展小趣事

随着技术的不断进步,Altonics公司始终保持着对创新的热情。公司加大研发投入,引进高端技术人才,不断推出具有创新性和竞争力的新产品。其中,公司自主研发的一款智能传感器,凭借其高精度、高稳定性的特点,迅速占领市场份额,成为工业自动化领域的明星产品。

ADMOS公司的发展小趣事

在电子行业的早期,ADMOS公司以其前瞻性的技术视野和不懈的研发努力,成功开发出一款高效能、低能耗的功率管理芯片。这款芯片在市场上迅速获得了认可,为ADMOS公司赢得了良好的口碑。这一技术突破不仅奠定了ADMOS在功率管理领域的领先地位,也为公司的后续发展奠定了坚实的基础。

Erocore Enterprise Co Ltd公司的发展小趣事

在快速发展的过程中,Erocore意识到供应链管理的重要性。公司开始优化供应链流程,降低采购成本,提高生产效率。通过与供应商建立长期稳定的合作关系,Erocore在保证产品质量的同时,实现了成本的有效控制。这一举措为公司在激烈的市场竞争中赢得了更多的优势。

Evans Capacitor Company公司的发展小趣事

Evans Capacitor Company(以下简称ECC)自创立之初,就专注于电容器的技术研发。公司创始人约翰·埃文斯是一位电子工程领域的杰出人才,他带领研发团队开发出了具有更高能量密度和更长寿命的新型电容器。这一创新产品迅速在市场上获得了认可,ECC因此获得了大量的订单,公司规模逐渐扩大。

问答坊 | AI 解惑

一个DC/DC电感的问题

DC输入端的L1  33uH,能否换成150uH,电感增大一点,滤高频效果更好一点,对电池、对输入有影响吗?…

查看全部问答>

03年宽带放大电路

本帖最后由 paulhyde 于 2014-9-15 09:35 编辑 大家看看 不要钱  …

查看全部问答>

打算用CE做点东西, 不知道什么才有市场?

打算用CE做点东西, 不知道什么才有市场?大家推荐一下…

查看全部问答>

Windows Mobile 2003 支持 java开发环境吗?

Windows Mobile 2003 支持 java开发环境吗? 使用JDK1.5开发的程序能运行吗?…

查看全部问答>

招聘程序员

招聘程序员: 具体要求见: www.chinadacs.cn …

查看全部问答>

想问问大家WINCE是怎么样实现数据库操作的?

没有接触过嵌入式开发,现在公司有点小需求需要用到WINCE开发一个数据库记录东西的小程序,请教一下大家,作个大概的了解!…

查看全部问答>

刚装了Evc4+sp3+sdk4.2+ppc2003sdk,编译的时候报“The emulator coul not be found in the speci

模拟器已经装了阿,可以看到的,为什么编译出问题呢?模拟器的安装路径不能变吗?哪位能告诉我evc4怎么配置阿,有哪本书讲啊?…

查看全部问答>

角度传感器角度怎么测 先得电阻和角度对应

本帖最后由 paulhyde 于 2014-9-15 08:53 编辑 角度传感器角度怎么测    先得电阻和角度对应  …

查看全部问答>

小弟想找个做过2.4G无线的帮助,做好有一定报酬

小弟正在做一个项目,是用飞思卡尔的MCU+2.4 G无线,无线可以随便   想找个做过或熟悉2.4G的帮我写,或教我写无线部分代码。可以付一定的报酬,不要太多哦,我也打工  .实在无办法了,只能自掏腰包。       跪求各 ...…

查看全部问答>

汽车传感器信号处理电路

大家都知道汽车车身是一个十分复杂的环境,因此对车身电子有着较高的要求;目前我想对汽车上的轮速 传感器信号和方向盘转交传感器信号进行处理;但是对处理电路还没有任何的思路,而且目前还没有找到 有价值的参考。如果有朋友知道这方面信息的话 ...…

查看全部问答>