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_gpiocrcc_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预分频器的输入为高速外设时钟(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引脚。如图:
云计算AI化 新一轮抢滩赛开始
典型的晶闸管应用电路图
腾达路由器恢复出厂设置的操作方法
十大热门手机充电器排行榜
SMT贴片加工中的红胶怎么选?
STM32的ADC简介_DMA方式的程序设计与实现
震惊!友达台中厂长传过劳逝世
未来AI如何创造真正的智能家居方案
使用示波器测量非隔离电源板子的正确方法
科学家研发STRAP技术,可用于多层塑料回收利用
小米9se—米酒的小屏版,价格更亲民
唱衰特斯拉的声音高涨,斯特拉真的会就此消失吗?
分享AI大学校长胡郁对AI的现状分析和未来的展望
PowerVR GPU架构的性能优化建议
医疗电子SMT贴片加工的特殊要求
T-Head原型为虚拟IOMMU提供创新的硬件支持
Trench工艺和平面工艺MOS的区别
如何对PIC16F628A的内部EEPROM执行基本的读写操作
苹果产线开始准备Plan B,传OV魅族有望加入鸿蒙生态
蓝牙调光器是什么,它都有什么特点