前言
CH32V/F单片机能够在一定的电压范围内进行工作,以CH32V203C8T6 芯片为例,在不使用 USB 外设时,最低工作电压能够达到 2.4V。较为宽泛的工作电压,允许单片机直接使用电池供电,但由于 CH32V203C8T6 芯片没有独立的 Vref 引脚,使用 ADC 的过程中无法换算出真实的电压。为解决无法获得真实电压的问题,可以使用内置参考电压换算当前供电电压(即 ADC参考电压)。对于项目要求精确测量时,也可尝试使用该方法对 ADC 进行校准。
电源电压的换算
CH32V203C8T6 芯片内部参考电压是典型值为 1.2V,正负偏差为 0.04V 的电压范围,在 ADC 转换精度要求不高的应用场景下,可以直接使用 1.2V 换算芯片供电电压。
如果需要更加精确的转换结果,就应在稳定的供电条件下,先对内部参考电压进行测量并将结果保存在 Flash 中,实际的使用过程中,再根据已知的内部参考电压进行换算。
实现上述操作,可参考以下代码:
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 参考电压)的改变而改变。
==========
往期回顾:
==========
原文链接:点击阅读原文
平台:博客园
作者:wchmcu