STM32 ADC采样使用内部参考电压
整理也能進(jìn)步!寫(xiě)得清楚才能理解更深。
【問(wèn)題背景】
在使用ADC時(shí),通常的用法是Vref+接電源VDD3.3V,然后計(jì)算時(shí)直接用3.3V做參考電壓,但是這種方法忽略了一些情況如供電電壓有可能隨外部一些其他用電器工作使用的大電流而導(dǎo)致電壓不穩(wěn)定,還有可能MCU供電LDO轉(zhuǎn)換的精度個(gè)別偏差較大。這時(shí)候依然用3.3V的定值做參考電壓計(jì)算顯然得出的值就會(huì)出現(xiàn)與實(shí)際電壓偏差較大的問(wèn)題。
附帶記錄VDD、VDDA,VBAT,VREF的區(qū)別
MCU 的參考手冊(cè)都會(huì)有一章節(jié)單獨(dú)介紹 MCU 的電源管理,針對(duì)不同的 MCU(封裝不同等)其外部電源如何連接也是有要求的,我們?cè)?MCU 上一般都會(huì)發(fā)現(xiàn)如下引腳(注意不同 MCU 是有區(qū)別的):
VDD / VSS: VDD is the external power supply for the I/Os, the internal regulator and the system analog such as reset, power management and internal clocks. It is provided externally through VDD pins.
VDDA / VSSA: VDDA 是A/D轉(zhuǎn)換器,D/A 轉(zhuǎn)換器,參考電壓緩沖器,運(yùn)算放大器和比較器的外部模擬電源。 VDDA 電壓電平與 VDD 電壓無(wú)關(guān)。 不使用這些外設(shè)時(shí),最好將 VDDA 連接到 VDD。
VBAT: 當(dāng)不存在 VDD 時(shí),VBAT 是 RTC,外部時(shí)鐘 32kHz 振蕩器和備用寄存器(通過(guò)電源開(kāi)關(guān))的電源。 對(duì)于沒(méi)有專(zhuān)用引腳的小型封裝,VBAT內(nèi)部連接到了 VDD
VREF+ / VREF-: VREF+ 是 ADC 和 DAC 的輸入?yún)⒖茧妷骸?使能后,它也是內(nèi)部參考電壓緩沖器的輸出。當(dāng)不使用 ADC 和 DAC 時(shí),VREF+ 可以接地。VRE- 必須始終等于 VSSA。
VREF- 和 VREF+ 引腳并非在所有封裝中都可用。 如果封裝上未提供它們,則它們?cè)?MCU 內(nèi)部分別與 VSSA 和 VDDA 相連。
【解決方案】
一般 100 腳以上的 STM32 MCU都有 VREF 引腳。對(duì)于 100 腳以下的芯片,STM32 沒(méi)有把 VREF 引腳引出來(lái),而是直接在內(nèi)部連接到了 VDDA 引腳。這樣就導(dǎo)致了 ADC 的供電電源和參考電源實(shí)際是一個(gè)。通常項(xiàng)目中我們VDDA也是連接到了VDD。
如果有 VREF 引腳,可以在VREF上接一個(gè)穩(wěn)定且精度高的電壓作為參考電壓。
還有一種方法是啟用內(nèi)部參考電壓。
根據(jù)STM32f10xCDE數(shù)據(jù)手冊(cè)中的數(shù)據(jù),這個(gè)參照電壓的典型值是1.20V,最小值是1.16V,最大值是1.24V(-40~85度)。這個(gè)電壓基本不隨外部供電電壓的變化而變化。
不同的芯片這個(gè)參考電壓的范圍不一樣,如下面這個(gè)(STM32L475 datasheet):
很明顯,如果以這個(gè)為參考電壓,我們也得測(cè)量其值,因?yàn)樗鼘?duì)于不同的芯片是一個(gè)范圍,并不是確定值。 STM32 可以通過(guò)配置將 VREFINT 接入到 ADC 內(nèi)部的通道17,然后我們就可以像測(cè)試普通的通道一樣測(cè)量 VREFINT 到底是多少。注意MCU 不同 具體連接的 ADC 通道也是不同的。
具體方法是在測(cè)量某個(gè)通道的電壓值之前,先讀出參照電壓的ADC測(cè)量數(shù)值(即從ADC1_IN17讀出的值),記為ADrefint;再讀出要測(cè)量通道的ADC轉(zhuǎn)換數(shù)值,記為ADchx;則要測(cè)量的電壓為:
Vchx = Vrefint * (ADchx/ADrefint)
其中Vrefint為參照電壓=1.20V(STM32F10x)。
推導(dǎo)過(guò)程:
Vchx = VREFINT* (((ADchx*(VREF/4096))/(ADre*(VREF/4096)))
注:VREFINT=1.2V(這里取1.20V實(shí)際上會(huì)有誤差),VREF為參考電壓值=3.3V
此公式可以理解為:(ADchx*(VREF/4096)是正常計(jì)算的含誤差電壓值,VREFINT/(ADre*(VREF/4096)是修正參數(shù),ADre*(VREF/4096)得到實(shí)際的參考電壓值比較接近VREFINT,VREFINT是校準(zhǔn)電壓值,VREFINT/(ADre*(VREF/4096)是約等于1的一個(gè)修正值。 – 個(gè)人理解
公式簡(jiǎn)化后:
Vchx = VREFINT*(ADchx/ADre)
該式計(jì)算得到的值是該通道的實(shí)際電壓值。
注意:上面的方法針對(duì)F1芯片只給了參考電壓的范圍,沒(méi)有提供出廠校準(zhǔn)值的情況,下面將介紹提供了校準(zhǔn)值的情況。
VREFINT_CAL是芯片出廠時(shí)廠家測(cè)量出來(lái)的參考電壓值固化在flash中,U16兩個(gè)字節(jié),可以作為基準(zhǔn)電壓。使用時(shí)讀出即可。該值不是所有系列芯片都有,F103貌似都沒(méi)有,下圖是L475的(注意不同芯片該值保存的地址不一致):
上圖表示,廠家在30度左右環(huán)境溫度下,VDDA和參考電壓等于3.0V的狀態(tài)下,通過(guò)ADC通道讀出的參考電壓值,保存在0x1FFF75AA開(kāi)始地址的2個(gè)字節(jié)中。如何讀取示例:VREFINT_CAL = *(u16*)0x1FFF75AA,筆者手里的L475讀出來(lái)是0x067F,換算3.0*(0x067F/4095)=1.218315,接近1.20了。
我們重點(diǎn)關(guān)注這個(gè)公式:
VREFINT_CAL:內(nèi)部參考電壓校準(zhǔn)值,直接地址讀取。比如該款芯片地址:0x1FFF75AA,那么我們可以這么做:
VREFINT_CAL = *(__IO uint16_t *)(0x1FFF75AA);
FULL_SCALE:根據(jù)我們?cè)O(shè)置的ADC分辨率而定,12位ADC分辨率值:2^12 - 1 = 4096 - 1
VREFINT_DATA:從ADC_17通道讀出實(shí)際內(nèi)部參考電壓值
ADCx_DATA:需要測(cè)試的電壓通道讀值
注意:公式中的3.0V有時(shí)可能是3.3V,取決于廠家給的校準(zhǔn)值是在3.0V條件下測(cè)試還是3.3V或是其他。
推導(dǎo)過(guò)程:
第一個(gè)公式VDDA = 3.0V x VREFINT_CAL / VREFINT_DATA 這個(gè)公式是怎么來(lái)的呢?
ST廠商 通過(guò)配置將 VREFINT 連接到 ADC 后,則有:VREFINT = 3.0V * (VREFINT_CAL / 4095); VREFINT_CAL 就是校準(zhǔn)條件下的 ADC 采樣值(校準(zhǔn)條件就是指VDDA=Vref+=3.0V,環(huán)境溫度30度),采到的VREFINT_CAL值寫(xiě)入到flash。
我們自己通過(guò)配置將 VREFINT 連接到 ADC:VREFINT = VDDA * (VREFINT_DATA / 4095);
因此,VDDA * (VREFINT_DATA / 4095) = 3.0 * (VREFINT_CAL / 4095);
VDDA = 3.0V x VREFINT_CAL / VREFINT_DATA
VDDA是這個(gè)公式中的重點(diǎn),我們常規(guī)的算法直接用3.3V作為VDDA計(jì)算才導(dǎo)致了誤差,因?yàn)閂DDA有誤差不是剛好3.3V。
第二個(gè)公式Vchannalx=VDDA*(ADCx_DATA/FULL_SCALE)
這個(gè)公式很好理解,就是我們常用的計(jì)算電壓方式,ADCx_DATA是讀出的采樣值,如:3.3*(1650/4095)
上面兩式聯(lián)立就能得出最后的公式。
前面講過(guò)的 Vchx = Vrefint * (ADchx/ADrefint),Vrefint=1.20V
實(shí)際上就是Vchannelx = (3.0xVrefint/FULL_SCALE)*(ADCx_DATA/VREFINT_DATA),其中(3.0xVrefint/FULL_SCALE)=1.20V
下面是使用stm32f103標(biāo)準(zhǔn)庫(kù)的內(nèi)部參考電壓示例代碼:
static u16 adcSampleValue[2]; void Adc_Init(void) { GPIO_InitTypeDef GPIO_InitStructure;DMA_InitTypeDef DMA_InitStructure; ADC_InitTypeDef ADC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC |RCC_APB2Periph_ADC1, ENABLE ); //使能ADC1通道時(shí)鐘RCC_ADCCLKConfig(RCC_PCLK2_Div6); //設(shè)置ADC分頻因子6 72M/6=12,ADC最大時(shí)間不能超過(guò)14M//PC1 作為模擬通道輸入引腳 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入引腳GPIO_Init(GPIOC, &GPIO_InitStructure); ADC_DeInit(ADC1); //復(fù)位ADC1 ADC_Cmd(ADC1, DISABLE); DMA_Cmd(DMA1_Channel1, DISABLE);ADC_DMACmd(ADC1, DISABLE);ADC_DeInit(ADC1);DMA_DeInit(DMA1_Channel1);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能 ADC1 通道時(shí)鐘.RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 設(shè)置ADC分頻因子6 72M/6=12,ADC最大時(shí)間不能超過(guò)14M.//* PC0 作為模擬通道輸入引腳.DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&adcSampleValue;DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;DMA_InitStructure.DMA_BufferSize = 2;DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;DMA_InitStructure.DMA_Priority = DMA_Priority_High;DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;DMA_Init(DMA1_Channel1, &DMA_InitStructure);ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // ADC工作模式:ADC1和ADC2工作在獨(dú)立模式.ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 模數(shù)轉(zhuǎn)換工作在單通道模式.ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 模數(shù)轉(zhuǎn)換工作在單次轉(zhuǎn)換模式.ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 轉(zhuǎn)換由軟件而不是外部觸發(fā)啟動(dòng).ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // ADC數(shù)據(jù)右對(duì)齊.ADC_InitStructure.ADC_NbrOfChannel = 2; // 順序進(jìn)行規(guī)則轉(zhuǎn)換的ADC通道的數(shù)目.ADC_Init(ADC1, &ADC_InitStructure); // 根據(jù)ADC_InitStruct中指定的參數(shù)初始化外設(shè)ADCx的寄存器. ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,采樣時(shí)間為239.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,采樣時(shí)間為239.5周期 ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE); // 使能指定的 ADC1.ADC_ResetCalibration(ADC1); // 使能復(fù)位校準(zhǔn) . while (ADC_GetResetCalibrationStatus(ADC1)); // 等待復(fù)位校準(zhǔn)結(jié)束.ADC_StartCalibration(ADC1); // 開(kāi)啟AD校準(zhǔn).while (ADC_GetCalibrationStatus(ADC1)); // 等待校準(zhǔn)結(jié)束.ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的軟件轉(zhuǎn)換啟動(dòng)功能 DMA_Cmd(DMA1_Channel1, ENABLE);ADC_TempSensorVrefintCmd(ENABLE);//打開(kāi)內(nèi)部參照電壓 }int main(void) {delay_init();NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);uart_init(115200);Adc_Init();while(1){printf("%fV\r\n",(float)1.20*((float)adcSampleValue[0]/adcSampleValue[1]));delay_ms(250);} }不同于普通ADC DMA采樣例程的關(guān)鍵操作是ADC_TempSensorVrefintCmd(ENABLE);//打開(kāi)內(nèi)部參照電壓
/*** @brief Enables or disables the temperature sensor and Vrefint channel.* @param NewState: new state of the temperature sensor.* This parameter can be: ENABLE or DISABLE.* @retval None*/ void ADC_TempSensorVrefintCmd(FunctionalState NewState) {/* Check the parameters */assert_param(IS_FUNCTIONAL_STATE(NewState));if (NewState != DISABLE){/* Enable the temperature sensor and Vrefint channel*/ADC1->CR2 |= CR2_TSVREFE_Set;}else{/* Disable the temperature sensor and Vrefint channel*/ADC1->CR2 &= CR2_TSVREFE_Reset;} }該函數(shù)操作的寄存器:
stm32 精確電壓測(cè)量法(內(nèi)部參考電壓)
STM32 之十 供電系統(tǒng)及內(nèi)部參照電壓(VREFINT)使用及改善ADC參考電壓
STM32之ADC(內(nèi)部基準(zhǔn)電壓,參考電壓)
https://www.pianshen.com/article/87041274716/
https://www.stmcu.org.cn/module/forum/forum.php?mod=viewthread&tid=627904
https://www.amobbs.com/thread-5607464-1-1.html
總結(jié)
以上是生活随笔為你收集整理的STM32 ADC采样使用内部参考电压的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: do{...}while(0);写法用途
- 下一篇: C语言 位域的使用