STM32 ADC采样使用内部参考电压
整理也能進步!寫得清楚才能理解更深。
【問題背景】
在使用ADC時,通常的用法是Vref+接電源VDD3.3V,然后計算時直接用3.3V做參考電壓,但是這種方法忽略了一些情況如供電電壓有可能隨外部一些其他用電器工作使用的大電流而導致電壓不穩定,還有可能MCU供電LDO轉換的精度個別偏差較大。這時候依然用3.3V的定值做參考電壓計算顯然得出的值就會出現與實際電壓偏差較大的問題。
附帶記錄VDD、VDDA,VBAT,VREF的區別
MCU 的參考手冊都會有一章節單獨介紹 MCU 的電源管理,針對不同的 MCU(封裝不同等)其外部電源如何連接也是有要求的,我們在 MCU 上一般都會發現如下引腳(注意不同 MCU 是有區別的):
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轉換器,D/A 轉換器,參考電壓緩沖器,運算放大器和比較器的外部模擬電源。 VDDA 電壓電平與 VDD 電壓無關。 不使用這些外設時,最好將 VDDA 連接到 VDD。
VBAT: 當不存在 VDD 時,VBAT 是 RTC,外部時鐘 32kHz 振蕩器和備用寄存器(通過電源開關)的電源。 對于沒有專用引腳的小型封裝,VBAT內部連接到了 VDD
VREF+ / VREF-: VREF+ 是 ADC 和 DAC 的輸入參考電壓。 使能后,它也是內部參考電壓緩沖器的輸出。當不使用 ADC 和 DAC 時,VREF+ 可以接地。VRE- 必須始終等于 VSSA。
VREF- 和 VREF+ 引腳并非在所有封裝中都可用。 如果封裝上未提供它們,則它們在 MCU 內部分別與 VSSA 和 VDDA 相連。
【解決方案】
一般 100 腳以上的 STM32 MCU都有 VREF 引腳。對于 100 腳以下的芯片,STM32 沒有把 VREF 引腳引出來,而是直接在內部連接到了 VDDA 引腳。這樣就導致了 ADC 的供電電源和參考電源實際是一個。通常項目中我們VDDA也是連接到了VDD。
如果有 VREF 引腳,可以在VREF上接一個穩定且精度高的電壓作為參考電壓。
還有一種方法是啟用內部參考電壓。
根據STM32f10xCDE數據手冊中的數據,這個參照電壓的典型值是1.20V,最小值是1.16V,最大值是1.24V(-40~85度)。這個電壓基本不隨外部供電電壓的變化而變化。
不同的芯片這個參考電壓的范圍不一樣,如下面這個(STM32L475 datasheet):
很明顯,如果以這個為參考電壓,我們也得測量其值,因為它對于不同的芯片是一個范圍,并不是確定值。 STM32 可以通過配置將 VREFINT 接入到 ADC 內部的通道17,然后我們就可以像測試普通的通道一樣測量 VREFINT 到底是多少。注意MCU 不同 具體連接的 ADC 通道也是不同的。
具體方法是在測量某個通道的電壓值之前,先讀出參照電壓的ADC測量數值(即從ADC1_IN17讀出的值),記為ADrefint;再讀出要測量通道的ADC轉換數值,記為ADchx;則要測量的電壓為:
Vchx = Vrefint * (ADchx/ADrefint)
其中Vrefint為參照電壓=1.20V(STM32F10x)。
推導過程:
Vchx = VREFINT* (((ADchx*(VREF/4096))/(ADre*(VREF/4096)))
注:VREFINT=1.2V(這里取1.20V實際上會有誤差),VREF為參考電壓值=3.3V
此公式可以理解為:(ADchx*(VREF/4096)是正常計算的含誤差電壓值,VREFINT/(ADre*(VREF/4096)是修正參數,ADre*(VREF/4096)得到實際的參考電壓值比較接近VREFINT,VREFINT是校準電壓值,VREFINT/(ADre*(VREF/4096)是約等于1的一個修正值。 – 個人理解
公式簡化后:
Vchx = VREFINT*(ADchx/ADre)
該式計算得到的值是該通道的實際電壓值。
注意:上面的方法針對F1芯片只給了參考電壓的范圍,沒有提供出廠校準值的情況,下面將介紹提供了校準值的情況。
VREFINT_CAL是芯片出廠時廠家測量出來的參考電壓值固化在flash中,U16兩個字節,可以作為基準電壓。使用時讀出即可。該值不是所有系列芯片都有,F103貌似都沒有,下圖是L475的(注意不同芯片該值保存的地址不一致):
上圖表示,廠家在30度左右環境溫度下,VDDA和參考電壓等于3.0V的狀態下,通過ADC通道讀出的參考電壓值,保存在0x1FFF75AA開始地址的2個字節中。如何讀取示例:VREFINT_CAL = *(u16*)0x1FFF75AA,筆者手里的L475讀出來是0x067F,換算3.0*(0x067F/4095)=1.218315,接近1.20了。
我們重點關注這個公式:
VREFINT_CAL:內部參考電壓校準值,直接地址讀取。比如該款芯片地址:0x1FFF75AA,那么我們可以這么做:
VREFINT_CAL = *(__IO uint16_t *)(0x1FFF75AA);
FULL_SCALE:根據我們設置的ADC分辨率而定,12位ADC分辨率值:2^12 - 1 = 4096 - 1
VREFINT_DATA:從ADC_17通道讀出實際內部參考電壓值
ADCx_DATA:需要測試的電壓通道讀值
注意:公式中的3.0V有時可能是3.3V,取決于廠家給的校準值是在3.0V條件下測試還是3.3V或是其他。
推導過程:
第一個公式VDDA = 3.0V x VREFINT_CAL / VREFINT_DATA 這個公式是怎么來的呢?
ST廠商 通過配置將 VREFINT 連接到 ADC 后,則有:VREFINT = 3.0V * (VREFINT_CAL / 4095); VREFINT_CAL 就是校準條件下的 ADC 采樣值(校準條件就是指VDDA=Vref+=3.0V,環境溫度30度),采到的VREFINT_CAL值寫入到flash。
我們自己通過配置將 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是這個公式中的重點,我們常規的算法直接用3.3V作為VDDA計算才導致了誤差,因為VDDA有誤差不是剛好3.3V。
第二個公式Vchannalx=VDDA*(ADCx_DATA/FULL_SCALE)
這個公式很好理解,就是我們常用的計算電壓方式,ADCx_DATA是讀出的采樣值,如:3.3*(1650/4095)
上面兩式聯立就能得出最后的公式。
前面講過的 Vchx = Vrefint * (ADchx/ADrefint),Vrefint=1.20V
實際上就是Vchannelx = (3.0xVrefint/FULL_SCALE)*(ADCx_DATA/VREFINT_DATA),其中(3.0xVrefint/FULL_SCALE)=1.20V
下面是使用stm32f103標準庫的內部參考電壓示例代碼:
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通道時鐘RCC_ADCCLKConfig(RCC_PCLK2_Div6); //設置ADC分頻因子6 72M/6=12,ADC最大時間不能超過14M//PC1 作為模擬通道輸入引腳 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模擬輸入引腳GPIO_Init(GPIOC, &GPIO_InitStructure); ADC_DeInit(ADC1); //復位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 通道時鐘.RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div8); // 設置ADC分頻因子6 72M/6=12,ADC最大時間不能超過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工作在獨立模式.ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 模數轉換工作在單通道模式.ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 模數轉換工作在單次轉換模式.ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 轉換由軟件而不是外部觸發啟動.ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // ADC數據右對齊.ADC_InitStructure.ADC_NbrOfChannel = 2; // 順序進行規則轉換的ADC通道的數目.ADC_Init(ADC1, &ADC_InitStructure); // 根據ADC_InitStruct中指定的參數初始化外設ADCx的寄存器. ADC_RegularChannelConfig(ADC1, ADC_Channel_11, 1, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,采樣時間為239.5周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_17, 2, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,采樣時間為239.5周期 ADC_DMACmd(ADC1, ENABLE);ADC_Cmd(ADC1, ENABLE); // 使能指定的 ADC1.ADC_ResetCalibration(ADC1); // 使能復位校準 . while (ADC_GetResetCalibrationStatus(ADC1)); // 等待復位校準結束.ADC_StartCalibration(ADC1); // 開啟AD校準.while (ADC_GetCalibrationStatus(ADC1)); // 等待校準結束.ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的軟件轉換啟動功能 DMA_Cmd(DMA1_Channel1, ENABLE);ADC_TempSensorVrefintCmd(ENABLE);//打開內部參照電壓 }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采樣例程的關鍵操作是ADC_TempSensorVrefintCmd(ENABLE);//打開內部參照電壓
/*** @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;} }該函數操作的寄存器:
stm32 精確電壓測量法(內部參考電壓)
STM32 之十 供電系統及內部參照電壓(VREFINT)使用及改善ADC參考電壓
STM32之ADC(內部基準電壓,參考電壓)
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
總結
以上是生活随笔為你收集整理的STM32 ADC采样使用内部参考电压的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: do{...}while(0);写法用途
- 下一篇: C语言 位域的使用