<p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"><p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"><p><br/><p> 1. ①电压输入范围<p>ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+ 、VDDA 、VSSA、这四个外部引脚决定。<p><br/><p>我们在设计原理图的时候一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到<p>ADC 的输入电压范围为:0~3.3V。<p><br/><p>如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可<p>以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就<p>可以测量了。<p><br/><p>2. ②输入通道<p>我们确定好 ADC 输入电压之后,那么电压怎么输入到 ADC?这里我们引入通道的概念,<p>STM32 的 ADC 多达 18 个通道,其中外部的 16 个通道就是框图中的 ADCx_IN0 、<p>ADCx_IN1...ADCx_IN5。这 16 个通道对应着不同的 IO 口,具体是哪一个 IO 口可以从手<p>册查询到。其中 ADC1/2/3 还有内部通道:ADC1 的通道 16 连接到了芯片内部的温度传感器,Vrefint 连接到了通道 17。ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。<p>ADC3 的模拟通道 9、14、15、16 和 17 连接到了内部的 VSS。<p><br/><p><p>外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16<p>路,注入通道最多有 4 路。那这两个通道有什么区别?在什么时候使用?<p><br/><p>规则通道<p>规则通道:顾名思意,规则通道就是很规矩的意思,我们平时一般使用的就是这个通<p>道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。<p><br/><p>注入通道<p>注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转<p>换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就<p>要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断<p>程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。<p><br/><p>3. ③转换顺序<p>规则序列<p>规则序列寄存器有 3 个,分别为 SQR3、SQR2、SQR1。SQR3 控制着规则序列中的第<p>一个到第六个转换,对应的位为: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 个通道。<p><p>注入序列<p>注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[2:0]<p>决定。如果 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 一样。<p><br/><p><p>4. ④触发源<p>通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。ADC 转换可以由<p>ADC 控制寄存器 2: ADC_CR2 的 ADON 这个位来控制,写 1 的时候开始转换,写 0 的时候停止转换,这个是最简单也是最好理解的开启 ADC 转换的控制方式,理解起来没啥技术含量。<p><br/><p>除了这种庶民式的控制方法,ADC 还支持触发转换,这个触发包括内部定时器触发和<p>外部 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 的有所不同,在框图上已经表示出来。<p><br/><p>5. ⑤转换时间<p>ADC 时钟<p>ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大是 14M,分频因子由 RCC 时<p>钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,注意这里没有 1 分频。一般我们设置 PCLK2=HCLK=72M。<p><br/><p>采样时间<p>ADC 使用若干个 ADC_CLK 周期对输入的电压进行采样,采样的周期数可通过 ADC <p>采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置,ADC_SMPR2 控制的是通道 0~9,ADC_SMPR1 控制的是通道 10~17。每个通道可以分别用不同的时间采样。<p><br/><p>其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5<p>个周期,这里说的周期就是 1/ADC_CLK。<p><br/><p>ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为:Tconv = 采样时间 + <p>12.5 个周期。当 ADCLK = 14MHZ (最高),采样时间设置为 1.5 周期(最快),那么总的转换时间(最短)Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。<p>一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采<p>样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。<p><br/><p>6. ⑥数据寄存器<p>一切准备就绪后,ADC 转换后的数据根据转换组的不同,规则组的数据放在 ADC_DR<p>寄存器,注入组的数据放在 JDRx。<p>规则数据寄存器<p>ADC 规则组数据寄存器 ADC_DR 只有一个,是一个 32 位的寄存器,低 16 位在单 ADC<br/><p>时使用,高 16 位是在 ADC1 中双模式下保存 ADC2 转换的规则数据,双模式就是 ADC1 和ADC2 同时使用。在单模式下,ADC1/2/3 都不使用高 16 位。因为 ADC 的精度是 12 位,无论 ADC_DR 的高 16 或者低 16 位都放不满,只能左对齐或者右对齐,具体是以哪一种方式存放,由 ADC_CR2 的 11 位 ALIGN 设置。<p><br/><p>规则通道可以有 16 个这么多,可规则数据寄存器只有一个,如果使用多通道转换,那<p>转换的数据就全部都挤在了 DR 里面,前一个时间点转换的通道数据,就会被下一个时间<p>点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开<p>启 DMA 模式,把数据传输到内存里面,不然就会造成数据的覆盖。最常用的做法就是开<p>启 DMA 传输。<p><br/><p>注入数据寄存器<p>ADC 注入组最多有 4 个通道,刚好注入数据寄存器也有 4 个,每个通道对应着自己的<p>寄存器,不会跟规则寄存器那样产生数据覆盖的问题。ADC_JDRx 是 32 位的,低 16 位有<p>效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由<p>ADC_CR2 的 11 位 ALIGN 设置。<p><br/><p>7. ⑦中断<p>转换结束中断<p>数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换<p>通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中<p>断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断<p>服务程序。<p><br/><p>模拟看门狗中断<p>当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我<p>们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTR 和 ADC_HTR 设置。例如我<p><br/><p>们设置高阈值是 2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之<p>低阈值也一样。<p><br/><p>DMA 请求<p>规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的<p>数据直接存储在内存里面。要注意的是只有 ADC1 和 ADC3 可以产生 DMA 请求。有关<p>DMA 请求需要配合《STM32F10X-中文参考手册》DMA 控制器这一章节来学习。一般我<p>们在使用 ADC 的时候都会开启 DMA 传输。<p><br/><p>8. ⑧电压转换<p>模拟电压经过 ADC 转换后,是一个 12 位的数字值,如果通过串口以 16 进制打印出来<p>的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际<p>的模拟电压(用万用表测)对比,看看转换是否准确。<p><br/><p>我们一般在设计原理图的时候会把 ADC 的输入电压范围设定在:0~3.3v,因为 ADC<p>是 12 位的,那么 12 位满量程对应的就是 3.3V,12 位满量程对应的数字值是:2^12。数值0 对应的就是 0V。如果转换后的数值为 X ,X 对应的模拟电压为 Y,那么会有这么一个等式成立: 2^12 / 3.3 = X / Y,=> Y = (3.3 * X ) / 2^12。<p><br/><p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"><p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"><p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"> <p style="box-sizing: border-box; outline: 0px; margin-top: 0px; margin-bottom: 16px; padding: 0px; font-family: "Microsoft YaHei", "SF Pro Display", Roboto, Noto, Arial, "PingFang SC", sans-serif; color: rgb(77, 77, 77); line-height: 26px; overflow-x: auto; word-wrap: break-word; white-space: normal; background-color: rgb(255, 255, 255);"><p>31.4 独立模式单通道采集实验<p>#include "bsp_adc.h"<p> <p>static void ADCx_GPIO_Config(void)<p>{<p><span style="white-space:pre"> </span>GPIO_InitTypeDef GPIO_InitStructure;<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>// 打开 ADC IO端口时钟<p><span style="white-space:pre"> </span>ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>// 配置 ADC IO 引脚模式<p><span style="white-space:pre"> </span>// 必须为模拟输入<p><span style="white-space:pre"> </span>GPIO_InitStructure.GPIO_Pin = ADC_PIN;<p><span style="white-space:pre"> </span>GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>// 初始化 ADC IO<p><span style="white-space:pre"> </span>GPIO_Init(ADC_PORT, &GPIO_InitStructure);<span style="white-space:pre"> </span><p>}<p> <p>static void ADCx_Mode_Config(void)<p>{<p><span style="white-space:pre"> </span>ADC_InitTypeDef ADC_InitStruct;<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_APBxClock_FUN ( ADC_CLK, ENABLE );<p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;<p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_ScanConvMode = DISABLE;<p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;<span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;<p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;<p><span style="white-space:pre"> </span>ADC_InitStruct.ADC_NbrOfChannel = 1;<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_Init(ADC_x, &ADC_InitStruct);<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>RCC_ADCCLKConfig(RCC_PCLK2_Div8);<p><span style="white-space:pre"> </span>ADC_RegularChannelConfig(ADC_x, ADC_CHANNEL, 1, ADC_SampleTime_55Cycles5);<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE);<p><span style="white-space:pre"> </span>ADC_Cmd(ADC_x, ENABLE);<p><span style="white-space:pre"> </span><p> // ADC开始校准<p><span style="white-space:pre"> </span>ADC_StartCalibration(ADC_x);<p><span style="white-space:pre"> </span>// 等待校准完成<p><span style="white-space:pre"> </span>while(ADC_GetCalibrationStatus(ADC_x));<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_SoftwareStartConvCmd(ADC_x, ENABLE);<p>}<p> <p>static void ADC_NVIC_Config(void)<p>{<p> NVIC_InitTypeDef NVIC_InitStructure;<p><span style="white-space:pre"> </span>// 优先级分组<p><span style="white-space:pre"> </span>NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);<p> <p> // 配置中断优先级<p> NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;<p> NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;<p> NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;<p> NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;<p> NVIC_Init(&NVIC_InitStructure);<p>}<p> <p>void ADCx_Init(void)<p>{<p><span style="white-space:pre"> </span>ADC_NVIC_Config();<p><span style="white-space:pre"> </span>ADCx_GPIO_Config();<p><span style="white-space:pre"> </span>ADCx_Mode_Config();<p>}<p> <p>/<p> <p> * @file main.c<p> * @author fire<p> * @version V1.0<p> * @date 2013-xx-xx<p> * @brief 串口中断接收测试<p> <p> * @attention<p> *<p> * 实验平台:秉火 F103-霸道 STM32 开发板 <p> * 论坛 :http://www.firebbs.cn<p> * 淘宝 :http://firestm32.taobao.com<p> *<p> <p> */ <p> <p> <p>#include "stm32f10x.h"<p>#include "bsp_usart.h"<p>#include "bsp_adc.h"<p> <p> <p>extern __IO uint16_t ADC_ConvertedValue;<p> <p>// 局部变量,用于保存转换计算后的电压值 <span style="white-space:pre"> </span> <p>float ADC_ConvertedValueLocal; <p> <p>// 软件延时<p>void Delay(__IO uint32_t nCount)<p>{<p> for(; nCount != 0; nCount--);<p>} <p> <p>/<p> * @brief 主函数<p> * @param 无<p> * @retval 无<p> */<p>int main(void)<p>{<span style="white-space:pre"> </span><p> /*初始化USART 配置模式为 8-N-1,中断接收*/<p> USART_Config();<p><span style="white-space:pre"> </span>printf("欢迎使用秉火STM32开发板nnnn");<p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADCx_Init();<p><span style="white-space:pre"> </span><p> while(1)<p><span style="white-space:pre"> </span>{<span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; <p><span style="white-space:pre"> </span><p><span style="white-space:pre"> </span>printf("rn The current AD value = 0x%04X rn",ADC_ConvertedValue); <p><span style="white-space:pre"> </span>printf("rn The current AD value = %f V rn",ADC_ConvertedValueLocal); <p><span style="white-space:pre"> </span>printf("rnrn");<p> <p><span style="white-space:pre"> </span>Delay(0xffffee); <p><span style="white-space:pre"> </span>}<span style="white-space:pre"> </span><p>}<p> <p>// 作业<p> <p>// 1-把程序改成 ADC1/3<p>// 2-换成其他的通道试一试<p> <p> <p> <p> <p> <p> <p> <p> <p> <p> <p> <p>/*END OF FILE/<p>void ADC_IRQHandler(void)<p>{<p><span style="white-space:pre"> </span>if( ADC_GetITStatus(ADC_x, ADC_IT_EOC) == SET)<p> {<p><span style="white-space:pre"> </span>ADC_ConvertedValue = ADC_GetConversionValue(ADC_x);<p><span style="white-space:pre"> </span>}<p><span style="white-space:pre"> </span>ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC);<p>}<p><br/> <p>是否使用DMA传输<p>使用DMA传输,那么流程为:<p>ADC初始化,DMA初始化,TIM2初始化 <p>其中:TIM2作为ADC的中断源<p>当发生一次定时器的中断时,进入AD转换,在DMA的初始化时与ADC-DR寄存器进行绑定,在该寄存器获得数据时,直接通过DMA通道将该寄存器的数据保存在给定的数组里面,把缓存数组装满后,会触发一次DMA的中断,在DMA的中断里面将缓存数组保存到100个电压值的数组里面。<p>定时器设为200us发生一次中断,来进行一次AD转换,获得寄存器里面的数据以后,保存在数组,保存100个数组后停止定时器的工作以及DMA的工作,对这份数据进行处理。 <p>至此,DMA工作的流程已经结束,那么其中又怎样的缺漏导致不用DMA进行操作,按照上述流程获得的100个离散点的电压是较为准确的(PS:这里可以把TIM2的定时中断时间改为采集32个点的625us或者采集64个点的312us),但是,如果只是放置在采集单相电压电流可以考虑,如果采集多相的电流电压,就要考虑到相位的问题,在之前的思路中<p>使用定时采集,流程为:<p>ADC初始化、定时器TIM3初始化 <p>这里的TIM3的中断时间为100us<p>进过200us,即0.2ms,启动AD转换,比如三相交流电压,依次对A,B,C三通道AD进行转换,获得当前的电压值,保存在数组里面。<p>当保存了100个数据以后,如果不考虑误差,那么0.2ms*100=20ms,即为50Hz电压的一个周期的时间采集到的电压值,采集到的100个离散点使用均方根公式进行求值获得该通道的电压的有效值。<p>在此转换中,进过0.2ms进行一次转换,不停止,功耗比较大,但是每次获得数据后,都要通过数据是否过上升沿下降沿开始,判断有无错相。(关于是否错相算法见下文)<p>总结<p>对比两个流程可以知道:如果不考虑相位问题,那么可以使用DMA+ADC采集数据,比如0.5s开启一下DMA与定时器采集一次,采集到的100个数据保存在数组里面,关闭定时器与DMA,采集好以后进行处理一下存储在当前电压值的变量里面,但是这样的一组周期内的电压,存在着可能无法判断是否错相的情况,比如采集ABC三相,当A出现下降沿过抬高电压位置时,开始计时,可能从这时开始到这个周期结束没有发生B相出现上升沿过抬高电压的情况,这样判断错相就会出现问题,当然,也可以直接使用DMA+ADC去类似0.2ms就进行一次转换,不间断的进行这个流程,这个方式我认为与0.2ms手动开启AD转换,不经过DMA传输直接读取寄存器ADC-DR的值相差的功耗不大。(待测)<p><br/><p>算法<p>采集<p>AD采集到的数据进行处理获得当前的瞬时电压值: <p>ADC_DR/当前电压值 = 4096/3300毫伏 <p>即 当前电压值 = (ADC_DR*3300)/4096; <p>在程序中我们对其进行了简化,最终的公式为 <p>当前电压值 = (ADC_DR*825)>>10 <p>就这样,我们保存了一个周期内100个点的电压值,也就是100个正弦波形上的离散点。<p>计算<p>根据100个离散点求得该引脚的电压的有效值: <p>均方根公式:<p><br/><p>这个网上不少,直接百度即可<p>Xi为离散点的电压值,N为100,Xrms为电压有效值 <p>其中要注意的是:Xi²实则为(引脚处电压 – 抬高电压)²<p><br/><p>错相判断<p>下面为正相序时的波形图:<p><br/><p>改天抽时间重画,原来的文件没了<p>譬如检测AB有无错相,当A出现下降沿并且过0点时(实际程序中是过抬高电压值时),开始计时,当B出现上升沿并且过0点时停止计时,这段时间T = 16.67ms,如果考虑一些特殊情况,比如电压的频率波动,开启计时的时间存在误差,可以假定T ≥15.5ms,AB为正相序。 <p>下面为反相序时的波形图:<p><br/><p>由此图我们可以看出,依旧像上面说的那样,在A(假定是A)相电压出现下降沿过0点时开启计时,在B相电压出现上升沿过零点时停止计时,此时时间远远小于正相序时的时间,如果无误差时T = 3.3ms,考虑到上述误差影响,那么我们把时间T ≤3.6ms时判断得AB为反相序。在程序中扩大了一下范围,把这个判断是否为反相序的时间调整为4.0ms,如果这段时间小于4.0ms,那么就认为是错相,大于的话默认为正相序。<p><br/><p>错相与缺相的逻辑判断:<p><1>. 当存在缺相时,不执行错相判断函数; <p><2>. 当存在缺相时,将通信返回的内容里面的错相位置为0,只将缺相对应位置为1。 <p><3>. 当不存在缺相时,只判断AB是否错相,从A出现下降沿过0点开始计时,B出现上升沿过0点关闭计时,判断是否小于4.0ms,若是,则将错相位全部置为1,如果不是,全部置为0。 <p><4>. 关于缺相判断,在求得电压的有效值后,有一个范围,目前设定为50mv,如果小于50mv,说明该相电压缺相。<p><br/> <p>STM32一般都拥有1~3个ADC,这些ADC可以独立使用,也可以使用双重/三重ADC采样模式,本文使用STM32F103ZET6的双重ADC模式,同步采集两个通道的电压信号。<p>一、注意事项<p>1、配置ADC的采样模式为同步规则采样<p>ADC1和ADC2采样模式相同,但其中ADC1为主ADC,ADC2为从ADC。<p>该模式在ADC_CR1寄存器中配置:(具体资料请参详STM32参考手册)<p><img src="https://6.eewimg.cn/news/uploadfile/2019/1030/002826.png" alt="在这里插入图片描述"/><p>2、使能DMA位<p>在双ADC模式中,ADC1和ADC2的规则通道转换数据均会保存到主数据寄存器,也就是ADC1数据寄存器(ADC1_DR)中。为了能在主寄存器中读取从ADC的转换数据,必须使能DMA位。 无论是否使用DMA传输规则通道数据 <p>在ADC_CR2寄存器中配置:<p><img src="https://6.eewimg.cn/news/uploadfile/2019/1030/76437.png" alt="在这里插入图片描述"/><p>3、触发方式配置<p>如果ADC1使用软件触发,ADC2则使用外部通道触发;ADC1使用外部事件触发时,ADC2设置成软件触发,这样可以防止意外触发从转换。<p><br/><p>二、代码配置<p>1、adc.c<p> /<p> * 程序功能:实现双ADC同步采集,每路ADC各有1个通道(单通道)<p> *<p> /<p> #include "adc.h"<p> #include "delay.h"<p> #include "usart.h" <p><br/><p>#define M 128<p>#define N 8<p>uint16_t value[N][M];<p>u32 ADC_ConvertedValue; <p><br/><p><br/><p>//初始化ADC1<p>void Adc1_Multi_Init(void)<p>{ <p> ADC_InitTypeDef ADC_InitStructure; <p> GPIO_InitTypeDef GPIO_InitStructure;<p> <p> RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE ); //使能ADC1通道时钟<p> <p> <p> RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M<p> <p> //PA1 作为模拟通道输入引脚<p> GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; <p> GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚<p> GPIO_Init(GPIOA, &GPIO_InitStructure); <p> <p> ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值<p> <p> ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult; //ADC工作模式:ADC1同步规则组模式<p> ADC_InitStructure.ADC_ScanConvMode =DISABLE; //模数转换工作在非扫描模式<p> ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式<p> ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动<p> ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐<p> ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目<p> ADC_Init(ADC1, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 <p> <p> ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );<p> <p> // 开启ADC的DMA支持<p> ADC_DMACmd(ADC1, ENABLE); //使能ADC的DMA位<p> <p> /* Enable ADC1 */<p> ADC_Cmd(ADC1, ENABLE); //使能ADC1<p><br/><p> /* Enable ADC1 reset calibaration register 使能ADC1复位校准寄存器 */ <p> ADC_ResetCalibration(ADC1);<p> /* Check the end of ADC1 reset calibration register ADC1复位校准寄存器检查结束*/<p> while(ADC_GetResetCalibrationStatus(ADC1));<p><br/><p> /* Start ADC1 calibaration 启动ADC1校准 */<p> ADC_StartCalibration(ADC1);<p> /* Check the end of ADC1 calibration ADC1校准检查结束 */<p> while(ADC_GetCalibrationStatus(ADC1));<p> <p>} <p><br/><p><br/><p>//初始化ADC2<p>void Adc2_Multi_Init(void)<p>{ <p> ADC_InitTypeDef ADC_InitStructure; <p> GPIO_InitTypeDef GPIO_InitStructure;<p> <p> RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC |RCC_APB2Periph_ADC2 , ENABLE ); //使能ADC2通道时钟<p> <p> RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M<p> <p> //PB0,1 作为模拟通道输入引脚<p> GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; <p> GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚<p> GPIO_Init(GPIOC, &GPIO_InitStructure); <p> <p> ADC_DeInit(ADC2); //复位ADC2,将外设 ADC2 的全部寄存器重设为缺省值<p> <p> ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult; //ADC工作模式:ADC1同步规则组模式<p> ADC_InitStructure.ADC_ScanConvMode =DISABLE; //模数转换工作在非扫描模式<p> ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式<p> ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动<p> ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐<p> ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目<p> ADC_Init(ADC2, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器 <p> <p> ADC_RegularChannelConfig(ADC2, ADC_Channel_10, 1, ADC_SampleTime_239Cycles5 );<p><br/><p> ADC_ExternalTrigConvCmd(ADC2, ENABLE); //使能ADC2的外部触发模式 <p> <p> /* Enable ADC2 */<p> ADC_Cmd(ADC2, ENABLE); //使能ADC2<p><br/><p> /* Enable ADC1 reset calibaration register 使能ADC2复位校准寄存器 */ <p> ADC_ResetCalibration(ADC2);<p> /* Check the end of ADC1 reset calibration register ADC2复位校准寄存器检查结束*/<p> while(ADC_GetResetCalibrationStatus(ADC2));<p><br/><p> /* Start ADC1 calibaration 启动ADC2校准 */<p> ADC_StartCalibration(ADC2);<p> /* Check the end of ADC1 calibration ADC2校准检查结束 */<p> while(ADC_GetCalibrationStatus(ADC2));<p> <p>}<p><br/><p><br/><p>/*初始化ADC */<p>void MY_ADC_Init(void)<p>{<p> Adc1_Multi_Init();<p> Adc2_Multi_Init();<p> <p>}<p><br/><p><br/><p>void task_adc(void)<p>{ <p> int i;<p> printf("rn 采样开始rn");<p> for(i=0;i<M;i++)<p> {<p> /* Start ADC1 Software Conversion 启动ADC1软件转换 */ <p> ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开始转换<p> <p> ADC_ConvertedValue=ADC1->DR;<p> <p> value[0][i] = (ADC_ConvertedValue&0xffff); //获取ADC的值<p> value[1][i] = ((ADC_ConvertedValue>> 16)&0xffff); //获取ADC的值<p> <p> printf("rn 编号%d t 编号%d t AD值: %drn", 0,i, value[0][i]);<p> printf("rn 编号%d t 编号%d t AD值: %drn", 1,i, value[1][i]); <p> <p> }<p> printf("rn 采样结束rn");<p>}<p><br/><p>2、adc.h<p>#ifndef __ADC_H<p>#define __ADC_H <p>#include "sys.h"<p><br/><p><br/><p>void Adc1_Multi_Init(void);<p>void Adc2_Multi_Init(void);<p>void MY_ADC_Init(void);<p>void task_adc(void);<p><br/><p>#endif <p><br/><p>3、main.c<p>#include "led.h"<p>#include "delay.h"<p>#include "key.h"<p>#include "sys.h"<p>#include "usart.h" <p>#include "adc.h"<p> <p><br/><p> int main(void)<p> { <p> <p> delay_init(); //延时函数初始化 <p> NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级<p> uart_init(); //串口初始化为<p> LED_Init(); //LED端口初始化 <p> MY_ADC_Init(); //ADC初始化<p> <p> while(1)<p> {<p> task_adc();<p> LED0=!LED0;<p> delay_ms(250); <p> <p> }<p>}<p><br/> <p>写在开头,本人本科对单片机还是比较熟悉的,8/16/32的单片机都玩过(寄存器的配置,库函数的调用),最近接了个很小的项目,以为1~2天就搞完了,结果弄了好几天,因此将该问题记录下来,大家以后碰到可以参考。<p><p><br/><p>项目中:一个功能是通过STM32103ZET6的串口(UART1)与一个数字传感器进行通信获取数据,第二个功能通过12位ADC单通道采集模拟传感器数据,最终将二者数据进行屏显,并通过键盘进行阈值大小设置,声光报警功能。<p><br/><p>坑1:ADC采集问题<p>我当时写的代码在我的一个开发板(芯片:STM32VET6)上是可以正常ADC采集数据的,当时通过串口将数据发至串口助手上打印查看,发现是OK的,(这里提一下,为了防止摸黑调试,串口助手是个好东西,)结果下载到我的STM32103ZET6最小系统板上就不对,串口助手打印一直为高电平(3.29V),我开始以为是芯片不兼容的问题(管脚定义错误这种问题我排除了,换个ADC通道采集的问题试过我也排除了),于是我找了份STM32103ZET6采集程序在ZE上还是不行,但在VE开发板上却可以,这就排除芯片不兼容问题,剩下肯定是硬件上的问题没跑了。(一步步将问题排查缩小)。由于身边还有块32的板子,我就直接拿了过来用,结果发现还是采集不了(我有3块STM32板子,1个是VE的开发板,2个是ZE的最小系统板),我纳闷了,难道是2块板子都有问题,这种问题出现的概率应该不大啊,试了一下午也没找出问题,最后我试了两块ZE板子上其他功能GPIO口,都好着,我排除两块板子的问题,可是为什么我那个VE的开发板能采集,为什么这两块就是不行,是硬件问题可是硬件到底哪里有问题?我想到了VE的开发板人家为了稳定在做的时候肯定把好多电路都已经设计上去了,而我的核心板很小,为了简化肯定哪里电路没有连接之类的(我核心板只有芯片,管脚,晶振),是不是就是ADC那里漏了,于是我找手册,发现基准电压,Vref+管脚接3.3V,Vref-接地(什么VDDA我没管),然后我用万用表一测,果然我的核心板Vref+管脚没有拉高,Vref-没有拉低,当我焊上使其连接时,好了,一切的问题搞定,其实我是先焊上Vref+接到3.3V,可以正常采集了(我开始用的湿度传感器做测试,输出在3V左右),但我的传感器输出只有10mv,采集出来还是0v,最后将Vref-接地问题才得以解决。(建议大家以后买核心板做ADC采集时,一定要先看板子上Vref和3.3V之间的跳帽有没有插上,或者测量其电压是否为高电平)。<p><br/><p>坑2:电流问题,USB线与电流不匹配<p>我的供电方式通过USB线给单片机,从单片机的电压管脚引出给(2个传感器,显示屏),USB线我用的带磁环的线,一切工作正常。当我交付时,我不太想给我的USB线,所以淘宝买了根0.5米不带磁环的线,可问题就来了,单片机工作不正常问题,导致屏显输出问题,采集问题,按键按下失败问题,旁边人说是不是电路哪里虚焊了,我认为肯定是电流和线不匹配导致的问题,电流输出2A,影响单片机正常工作会导致后面这一系列问题,回来把线换了,结果一切正常,终于交付了。<p><br/><p>至此,所有问题都解决了,有快一年半没有玩单片机了,这次的问题也是让我找了好久,所以当大家遇到问题时,先不要急,冷静分析下自己的问题有可能出现在哪里,利用手上工具尝试并排除,缩小问题范围,实在解决不了, 上各大论坛都找找解决方案。
讯享网

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/166429.html