-第2页-0、友情提示《零死角玩转STM32》系列教程由初级篇、中级篇、高级篇、系统篇、四个部分组成,根据野火STM32开发板旧版教程升级而来,且经过重新深入编写,重新排版,更适合初学者,步步为营,从入门到精通,从裸奔到系统,让您零死角玩转STM32。M3的世界,与野火同行,乐意惬无边。另外,野火团队历时一年精心打造的《STM32库开发实战指南》将于今年10月份由机械工业出版社出版,该书的排版更适于纸质书本阅读以及更有利于查阅资料。内容上会给你带来更多的惊喜。是一本学习STM32必备的工具书。敬请期待!-第3页-2、ADC(DMA模式)2.1ADC简介ADC(AnalogtoDigitalConverter),模/数转换器。在模拟信号需要以数字形式处理、存储或传输时,模/数转换器几乎必不可少。STM32在片上集成的ADC外设非常强大。在STM32F103xC、STM32F103xD和STM32F103xE增强型产品,内嵌3个12位的ADC,每个ADC共用多达21个外部通道,可以实现单次或多次扫描转换。如野火STM32开发板用的是STM32F103VET6,属于增强型的CPU,它有18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。2.2STM32的ADC主要技术指标对于ADC来说,我们最关注的就是它的分辨率、转换速度、ADC类型、参考电压范围。分辨率12位分辨率。不能直接测量负电压,所以没有符号位,即其最小量化单位LSB=Vref+/212。转换时间转换时间是可编程的。采样一次至少要用14个ADC时钟周期,而ADC的时钟频率最高为14MHz,也就是说,它的采样时间最短为1us。足以胜任中、低频数字示波器的采样工作。ADC类型ADC的类型决定了它性能的极限,STM32的是逐次比较型ADC。参考电压范围-第4页-STM32的ADC参考电压输入见图2-1。图2-1参考电压从图中可知,它的参考电压负极是要接地的,即Vref-=0V。而参考电压正极的范围为2.4V≦Vref+≦3.6V,所以STM32的ADC是不能直接测量负电压的,而且其输入的电压信号的范围为:VREF-≦VIN≦VREF+。当需要测量负电压或测量的电压信号超出范围时,要先经过运算电路进行平移或利用电阻分压。-第5页-2.3ADC工作过程分析图2-2ADC架构图我们以ADC的规则通道转换来进行过程分析。所有的器件都是围绕中间的模拟至数字转换器部分(下面简称ADC部件)展开的。它的左端为VREF+、VREF-等ADC参考电压,ADCx_IN0~ADCx_IN15为ADC的输入信号通道,即某些GPIO引脚。输入信号经过这些通道被送到ADC部件,ADC部件需要受到触-第6页-发信号才开始进行转换,如EXTI外部触发、定时器触发,也可以使用软件触发。ADC部件接收到触发信号之后,在ADCCLK时钟的驱动下对输入通道的信号进行采样,并进行模数转换,其中ADCCLK是来自ADC预分频器的。ADC部件转换后的数值被保存到一个16位的规则通道数据寄存器(或注入通道数据寄存器)之中,我们可以通过CPU指令或DMA把它读取到内存(变量)。模数转换之后,可以触发DMA请求,或者触发ADC的转换结束事件。如果配置了模拟看门狗,并且采集得的电压大于阈值,会触发看门狗中断。2.4ADC采集实例分析使用ADC时常常需要不间断采集大量的数据,在一般的器件中会使用中断进行处理,但使用中断的效率还是不够高。在STM32中,使用ADC时往往采用DMA传输的方式,由DMA把ADC外设转换得的数据传输到SRAM,再进行处理,甚至直接把ADC的数据转移到串口发送给上位机。本小节对ADC的DMA方式采集数据实例进行讲解,在讲解ADC的同时让读者进一步熟悉DMA的使用。2.4.1实验描述及工程文件清单实验描述串口1(USART1)向电脑的超级终端以一定的时间间隔打印当前ADC1的转换电压值。硬件连接PC1-ADC1连接外部电压(通过一个滑动变阻器分压而来)。用到的库文件startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.cFWlib/stm32f10x_usart.cFWlib/stm32f10x_adc.c-第7页-FWlib/stm32f10x_dma.c用户编写的文件USER/stm32f10x_it.cUSER/usart1.cUSER/adc.c图2-3野火STM32开发板ADC硬件原理图2.4.2配置工程环境本ADC(DMA方式)实验中我们用到了GPIO、RCC、USART、DMA及ADC外设,所以我们先要把以下库文件添加到工程stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_usart.c、stm32f10x_dma.c、stm32f10x_adc.c,添加旧工程中的外设用户文件usart1.c,新建adc.c及adc.h文件,并在stm32f10x_conf.h中把使用到的ST库的头文件注释去掉。1./**2.*********************************************************3.*@fileProject/STM32F10x_StdPeriph_Template/stm32f10x_conf.h4.*@authorMCDApplicationTeam5.*@versionV3.5.06.*@date08-April-20117.*@briefLibraryconfigurationfile.8.*************************************************************/9.#includestm32f10x_adc.h10.#includestm32f10x_dma.h11.#includestm32f10x_gpio.h12.#includestm32f10x_rcc.h13.#includestm32f10x_usart.h-第8页-2.4.3main文件配置好工程环境之后,我们就从main文件开始分析:1.#includestm32f10x.h2.#includeusart1.h3.#includeadc.h4.5.//ADC1转换的电压值通过MDA方式传到SRAM6.extern__IOuint16_tADC_ConvertedValue;7.8.//局部变量,用于保存转换计算后的电压值9.10.floatADC_ConvertedValueLocal;11.12.//软件延时13.voidDelay(__IOuint32_tnCount)14.{15.for(;nCount!=0;nCount--);16.}17.18./**19.*@briefMainprogram.20.*@paramNone21.*@retval:None22.*/23.24.intmain(void)25.{26./*USART1config*/27.USART1_Config();28.29./*enableadc1andconfigadc1todmamode*/30.ADC1_Init();31.32.printf(\r\n-------这是一个ADC实验------\r\n);33.34.while(1)35.{36.ADC_ConvertedValueLocal=(float)ADC_ConvertedValue/4096*3.3;//读取转换的AD值37.38.printf(\r\nThecurrentADvalue=0x%04X\r\n,ADC_ConvertedValue);39.printf(\r\nThecurrentADvalue=%fV\r\n,ADC_ConvertedValueLocal);40.41.Delay(0xffffee);//延时42.43.44.}45.}浏览一遍main函数,在调用了用户函数USART1_Config()及ADC1_Init()配置好串口和ADC之后,就可以直接使用保存了ADC转换值的变量ADC_ConvertedValue了,在main函数中并没有对ADC_ConvertedValue重新赋值,这个变量是在什么时候改变的呢?除了可能在中断服务函数修改了变量-第9页-值,就只有DMA有这样的能耐了,而且大家知道,在使用DMA传输时,由于不是内核执行的指令,所以修改变量值是绝对不会出现赋值语句的。2.4.4ADC初始化本实验代码中完全没有使用中断,而ADC及DMA的配置工作都由用户函数ADC1_Init()完成了。配置完成ADC及DMA后,ADC就不停地采集数据,而DMA自动地把ADC采集得的数据转移至内存中的变量ADC_ConvertedValue中,所以在main函数的while循环中使用的ADC_ConvertedValue都是实时值。接下来重点分析ADC1_Init()这个函数是如何配置ADC的。ADC1_Init()函数使能了ADC1,并使ADC1工作于DMA方式。ADC1_Init()这个函数是由在用户文件adc.c中实现的用户函数:1./*2.*函数名:ADC1_Init3.*描述:无4.*输入:无5.*输出:无6.*调用:外部调用7.*/8.voidADC1_Init(void)9.{10.ADC1_GPIO_Config();11.ADC1_Mode_Config();12.}ADC1_Init()调用了ADC1_GPIO_Config()和ADC1_Mode_Config()。这两个函数的作用分别是配置好ADC1所用的I/O端口;配置ADC1初始化及DMA模式。2.4.4.1配置GPIO端口ADC1_GPIO_Config()代码:1./*2.*函数名:ADC1_GPIO_Config3.*描述:使能ADC1和DMA1的时钟,初始化PC.014.*输入:无5.*输出:无6.*调用:内部调用7.*/8.staticvoidADC1_GPIO_Config(void)9.{-第10页-10.GPIO_InitTypeDefGPIO_InitStructure;11.12./*EnableDMAclock*/13.RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);14.15./*EnableADC1andGPIOCclock*/16.RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPIOC,ENABLE);17.18./*ConfigurePC.01asanaloginput*/19.GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;20.GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;21.GPIO_Init(GPIOC,&GPIO_InitStructure);//PC1,输入时不用设置速率22.}ADC1_GPIO_Config()代码非常简单,就是使能DMA时钟,GPIO时钟及ADC1时钟。然后把ADC1的通道11使用的GPIO引脚PC1配置成模拟输入模式,在作为ADC的输入时,必须使用模拟输入。这里涉及到ADC通道的知识,每个ADC通道都对应着一个GPIO引脚端口,GPIO的引脚在设置为模拟输入模式后可用于模拟电压的输入端。STM32F103VET6有三个ADC,这三个ADC共用16个外部通道,从《STM32数据手册》的引脚定义可找到ADC的通道与GPIO引脚的关系