內置參考電壓的使用 應用筆記 前言 CH32V/F 系列單片機能夠在一定的電壓範圍內進行工作,以 CH32V203C8T6 晶元為例,在不使用 USB 外設時,最低工作電壓能夠達到 2.4V。較為寬泛的工作電壓,允許單片機直接使用電池供電,但由於 CH32V203C8T6 晶元沒有獨立的 Vref ...
內置參考電壓的使用
應用筆記
前言
CH32V/F 系列單片機能夠在一定的電壓範圍內進行工作,以 CH32V203C8T6 晶元為例,在不使用 USB 外設時,最低工作電壓能夠達到 2.4V。較為寬泛的工作電壓,允許單片機直接使用電池供電,但由於 CH32V203C8T6 晶元沒有獨立的 Vref 引腳,使用 ADC 的過程中無法換算出真實的電壓。
為解決無法獲得真實電壓的問題,可以使用內置參考電壓換算當前供電電壓(即 ADC參考電壓)。對於項目要求精確測量時,也可嘗試使用該方法對 ADC 進行校準。
電源電壓的換算
CH32V203C8T6 晶元內部參考電壓是典型值為 1.2V,正負偏差為 0.04V 的電壓範圍,在 ADC 轉換精度要求不高的應用場景下,可以直接使用 1.2V 換算晶元供電電壓。
圖1 CH32V203C8T6 晶元數據手冊(V1.4)內部參考電壓截圖
如果需要更加精確的轉換結果,就應在穩定的供電條件下,先對內部參考電壓進行測量並將結果保存在 Flash 中,實際的使用過程中,再根據已知的內部參考電壓進行換算。
圖2 提供了內部參考電壓使用的流程。
圖2 內部參考電壓的使用
實現上述操作,可參考以下代碼。
u16 ADC_val = 0; s32 val_mv = 0; u16 Vref = 0; // Flash中存儲的內部參考電壓實測值 s32 Vref_To_VDD = 0; // 由Vref的實測值換算出的電源電壓值 if ( *(u32*)(FAST_FLASH_PROGRAM_START_ADDR) == 0xe339e339 ) { // 判斷Flash中是否有內部參考電壓的實測值 printf("Address:0x%08x -> %08x\r\n", FAST_FLASH_PROGRAM_START_ADDR, *(u32*)(FAST_FLASH_PROGRAM_START_ADDR)); // 獲取內部參考電壓實測值,此時務必保證電源電壓或參考電壓(如果有)的準確 ADC_val = Get_ADC_Average(ADC_Channel_Vrefint, 255); // 255次取平均 ADC_val = Get_ConversionVal(ADC_val); val_mv = (ADC_val * 3300 / 4096); printf("Vref_mv -> %d\r\n", val_mv); // 將測得的結果存儲在Flash中 buf[0] = val_mv; FLASH_Unlock_Fast(); FLASH_ProgramPage_Fast(FAST_FLASH_PROGRAM_START_ADDR, buf); FLASH_Lock_Fast(); printf("Address:0x%08x -> %08x\r\n", FAST_FLASH_PROGRAM_START_ADDR, *(u32*)(FAST_FLASH_PROGRAM_START_ADDR)); } else { printf("Address:0x%08x -> %08x\r\n", FAST_FLASH_PROGRAM_START_ADDR, *(u32*)(FAST_FLASH_PROGRAM_START_ADDR)); Vref = *(u32*)(FAST_FLASH_PROGRAM_START_ADDR); ADC_val = Get_ADC_Average(ADC_Channel_Vrefint, 255); // 255次取平均 ADC_val = Get_ConversionVal(ADC_val); Vref_To_VDD = (4096 * Vref / ADC_val); printf("Vref_To_VDD_mV -> %d\r\n", Vref_To_VDD); }
ADC 初始化過程中的校準
ADC 初始化函數中完成了一次校準過程,經過校準環節可大幅減小因內部電容器組的變化而造成的精準度誤差。校準過程中 ADC 僅獲取了 Vcc 的採樣值,與實際電壓大小無關,因此,在浮動電壓供電的場景中,不會引入額外的誤差。
獲取校準值函數,通過寫 ADC_CTLR2 寄存器的 RSTCAL 位置 1 初始化校準寄存器,等待 RSTCAL 硬體清 0完成初始化。置位 CAL 位,啟動校準功能,校準結束後,硬體自動清除 CAL 位,將校準碼存儲到 ADC_RDATAR 中。使用多次校準結果,計算 ADC 補償。
int16_t Get_CalibrationValue(ADC_TypeDef *ADCx) { __IO uint8_t i, j; uint16_t buf[10]; __IO uint16_t t; #if defined (CH32V20x_D6) __IO uint16_t p; #endif for(i = 0; i < 10; i++){ ADC_ResetCalibration(ADCx); while(ADC_GetResetCalibrationStatus(ADCx)); ADC_StartCalibration(ADCx); while(ADC_GetCalibrationStatus(ADCx)); buf[i] = ADCx->RDATAR; // printf("CalibrationValue[%d]->%d\r\n", i, buf[i]); } for(i = 0; i < 10; i++){ for(j = 0; j < 9; j++){ if(buf[j] > buf[j + 1]) { t = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = t; } } } #if defined (CH32V20x_D8) || defined (CH32V20x_D8W) t = 0; for( i = 0; i < 6; i++ ) { t += buf[i + 2]; } t = ( t / 6 ) + ( ( t % 6 ) / 3 ); return ( int16_t )( 2048 - ( int16_t )t ); #else t = 0; p = 0; /* 1024 */ for(i = 0; i < 6; i++ ){ if(buf[i+2] > 1536) break; t += buf[i+2]; } if(i > 0){ t = ( t / i ) + ( (( t % i )*2) / i ); } else t = 1024; /* 2048 */ j = 6-i; if(j > 0){ for(; i < 6; i++ ){ p += buf[i+2]; } p = ( p / j ) + ( (( p % j )*2) / j ); } else p = 2048; return ( int16_t )(((( int16_t )( 1024 - ( int16_t )t ) + ( int16_t )( 2048 - ( int16_t )p ))/2) + ((( int16_t )( 1024 - ( int16_t )t ) + ( int16_t )( 2048 - ( int16_t )p ))%2)); #endif }
可以在校準值轉換的 for 迴圈中添加列印,觀察每次校準值結果是否隨晶元供電電壓(即 ADC 參考電壓)的改變而改變。