
本文共 6191 字,大约阅读时间需要 20 分钟。
注:该帖为自己在使用stm32时的问题总结贴,如有错误欢迎大家指正出来,一起交流学习
目录
一、ADC简介
ADC是Analog-to-Digital Converter的缩写。指模/数转换器或者模拟/数字转换器。是指将连续变量的模拟信号转换为离散的数字信号的器件。典型的模拟数字转换器将模拟信号转换为表示一定比例电压值的数字信号。
stm32的ADC是一种逐次逼近型模拟数字转换器。它具有12位的精度、有多达18个通道,可测量16个外部和2个内部信号源。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
二、ADC功能框图
掌握了ADC 的功能框图,就可以对ADC 有一个整体的把握,在编程的时候可以做到了然如胸,不会一知半解。框图讲解采用从左到右的方式,跟ADC 采集数据,转换数据,传输数据的方向大概一致。
三、ADC功能描述
1、电压输入范围
ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。
设计原理图的时候一般把VSSA和VREF-接地,把VREF+和VDDA 接3V3,得到ADC的输入电压范围为:0~3.3V。
2、输入通道
我们确定好ADC 输入电压之后,那么电压怎么输入到ADC?这里我们引入通道的概念,STM32 的ADC 多达18 个通道,其中外部的16 个通道就是框图中的ADCx_IN0、ADCx_IN1...ADCx_IN5。这16 个通道对应着不同的IO 口,具体是哪一个IO 口可以从手册查询到。其中ADC1/2/3 还有内部通道:ADC1的通道16连接到了芯片内部的温度传感器,Vrefint 连接到了通道17。ADC2 的模拟通道16 和17 连接到了内部的VSS。ADC3 的模拟通道9、14、15、16 和17 连接到了内部的VSS。
我们在编程的时候需要根据使用的IO引脚来确定具体的通道。外部的16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有16路,注入通道最多有4 路。那这两个通道有什么区别?在什么时候使用?
规则通道
规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通道。
注入通道
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。
3、转换顺序
规则序列
规则序列寄存器有3 个,分别为SQR3、SQR2、SQR1。SQR3 控制着规则序列中的第一个到第六个转换,对应的位为:SQ1[4:0]~SQ6[4:0],第一次转换的是位4:0 SQ1[4:0],如果通道16 想第一次转换,那么在SQ1[4:0]写16 即可。SQR2 控制着规则序列中的第7 到第12 个转换,对应的位为:SQ7[4:0]~SQ12[4:0],如果通道1 想第8 个转换,则SQ8[4:0]写1即可。SQR1 控制着规则序列中的第13 到第16 个转换,对应位为:SQ13[4:0]~SQ16[4:0],如果通道6 想第10 个转换,则SQ10[4:0]写6 即可。具体使用多少个通道,由SQR1 的位L[3:0]决定,最多16 个通道。
注入序列
注入序列寄存器JSQR 只有一个,最多支持4 个通道,具体多少个由JSQR 的JL[2:0]决定。如果JL 的 值小于4 的话,则JSQR 跟SQR决定转换顺序的设置不一样,第一次转换的不是JSQR1[4:0],而是JCQRx[4:0] ,x = (4-JL),跟SQR 刚好相反。如果JL=00(1 个转换),那么转换的顺序是从JSQR4[4:0]开始,而不是从JSQR1[4:0]开始,这个要注意,编程的时候不要搞错。当JL 等于4 时,跟SQR 一样。
4、触发源
通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。ADC 转换可以由ADC 控制寄存器2: ADC_CR2 的ADON 这个位来控制,写1 的时候开始转换,写0 的时候停止转换,这个是最简单也是最好理解的开启ADC 转换的控制方式,理解起来没啥技术含量。
除了这种庶民式的控制方法,ADC 还支持触发转换,这个触发包括内部定时器触发和外部IO 触发。触发源有很多,具体选择哪一种触发源,由ADC 控制寄存器2:ADC_CR2 的EXTSEL[2:0] 和JEXTSEL[2:0]位来控制。EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激活,则由ADC 控制寄存器2:ADC_CR2 的EXTTRIG 和JEXTTRIG 这两位来激活。其中ADC3 的规则转换和注入转换的触发源与ADC1/2 的有所不同,在框图上已经表示出来。
5、时钟输入
ADC部件接收到触发信号之后,在ADC CLK时钟的驱动下对输入通道的信号进行采样,并进行模数转换,其中ADC CLK 是来自ADC预分频器的。
6、数据寄存器
一切准备就绪后,ADC 转换后的数据根据转换组的不同,规则组的数据放在ADC_DR寄存器,注入组的数据放在JDRx。
规则数据寄存器
ADC 规则组数据寄存器ADC_DR 只有一个,是一个32 位的寄存器,低16 位在单ADC时使用,高16 位是在ADC1 中双模式下保存ADC2 转换的规则数据,双模式就是ADC1 和ADC2 同时使用。在单模式下,ADC1/2/3 都不使用高16 位。因为ADC 的精度是12 位,无论ADC_DR 的高16 或者低16 位都放不满,只能左对齐或者右对齐,具体是以哪一种方式存放,由ADC_CR2 的11 位ALIGN 设置。
规则通道可以有16 个这么多,可规则数据寄存器只有一个,如果使用多通道转换,那转换的数据就全部都挤在了DR 里面,前一个时间点转换的通道数据,就会被下一个时间点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开启DMA 模式,把数据传输到内存里面,不然就会造成数据的覆盖。最常用的做法就是开启DMA 传输。
注入数据寄存器
ADC 注入组最多有4 个通道,刚好注入数据寄存器也有4 个,每个通道对应着自己的寄存器,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx 是32 位的,低16 位有效,高16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的11 位ALIGN 设置。
7、中断
转换结束中断
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断服务程序。
模拟看门狗中断
当被ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由ADC_LTR 和ADC_HTR 设置。例如我们设置高阈值是2.5V,那么模拟电压超过2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。
DMA 请求
规则和注入通道转换结束后,除了产生中断外,还可以产生DMA 请求,把转换好的数据直接存储在内存里面。要注意的是只有ADC1 和ADC3 可以产生DMA 请求。有关DMA请求需要配合《STM32F10X-中文参考手册》DMA控制器这一章节来学习。一般我们在使用ADC 的时候都会开启DMA 传输。
四、ADC程序配置
static void ADC1_GPIO_Config(void){ //��8��ģ������ֱ���GPIOA GPIOB GPIO_InitTypeDef GPIOA_IS; //����ADC1ʱ�ӣ�DMAʱ������DMA�ļ������� RCC_ADCCLKConfig(RCC_ADCCLK_HSI14); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE); GPIOA_IS.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5; GPIOA_IS.GPIO_Mode = GPIO_Mode_IN; GPIOA_IS.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOC,&GPIOA_IS); GPIOA_IS.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOB,&GPIOA_IS); }static void ADC1_Mode_Config(void){ DMA_InitTypeDef DMA_IS; ADC_InitTypeDef ADC_IS; ADC_DeInit(ADC1); /*ADC1 configuration*/ ADC_IS.ADC_ContinuousConvMode = ENABLE; ADC_IS.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_IS.ADC_ExternalTrigConv = 0; ADC_IS.ADC_DataAlign = ADC_DataAlign_Right; ADC_IS.ADC_Resolution = ADC_Resolution_10b; ADC_IS.ADC_ScanDirection = ADC_ScanDirection_Backward; ADC_Init(ADC1,&ADC_IS); //ADC1->CFGR1 = 0X080020FF; ADC_ChannelConfig(ADC1,ADC_Channel_8,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_10,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_11,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_12,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_13,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_14,ADC_SampleTime_239_5Cycles); ADC_ChannelConfig(ADC1,ADC_Channel_15,ADC_SampleTime_239_5Cycles); while(ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); while(!ADC_GetCalibrationFactor(ADC1)); ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular); ADC_DMACmd(ADC1,ENABLE); ADC_Cmd(ADC1,ENABLE); while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY)); //ADC_StartOfConversion(ADC1); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //DMA ͨ���������֪ADCĬ�϶�ӦDMA1 DMA_DeInit(DMA1_Channel1); //ADC DR ADDR DMA_IS.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address; //MEMORY ADDR DMA_IS.DMA_MemoryBaseAddr = (uint32_t)ADC_DATA.Conversion_Value; //����ԴΪ���裬������ַ�̶��� DMA_IS.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_IS.DMA_BufferSize = ADCn; DMA_IS.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_IS.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_IS.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_IS.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_IS.DMA_Mode = DMA_Mode_Circular; DMA_IS.DMA_Priority = DMA_Priority_High; DMA_IS.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1,&DMA_IS); //ENABLE DMA CHANNEL DMA_Cmd(DMA1_Channel1, ENABLE); ADC_StartOfConversion(ADC1);}void ADC1_Init(void){ ADC1_GPIO_Config(); ADC1_Mode_Config();}
五、碰到过的问题
1、使用adc buf中的数据进行计算时出错
在用adc值进行计算时发现会出现一个极大值,很奇怪时怎么出现的,最后经过推理之后发现是用dma传输的数据计算时,虽然有比较大小的限制,但是在比较完成之后,进行计算时,dma将adc buf的数据再次更新了,更新之后就变成了一个小值减去一个大的值,导致最终的计算结果出错。
发表评论
最新留言
关于作者
