For the Voltage-Ampere-Meter based upon my tiny STM32F030P4 boards I needed to measure different sources - two external sources and the supply voltage. Now from the datasheet, the samples on the net and in the Standard Peripheral Lib, all you need to do is to configure the channels you want to measure and that's it. But as soon as I wanted to measure more than one source, I got weird results. Research on the net gave no good hints where to look for the error.
int read_vbat(void) {
uint32_t temp = 0;
adc_init(ADC_Channel_0, ADC_SampleTime_28_5Cycles);
// throw away first result like on AVR? first value seems wrong.
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
for (uint8_t i = 0; i<8; i++) { // resampling
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
temp += ADC_GetConversionValue(ADC1);
}
ADC_Cmd(ADC1, DISABLE);
return ( (temp>>3) );
}
int read_shunt(void) {
uint32_t temp = 0;
adc_init(ADC_Channel_1, ADC_SampleTime_28_5Cycles);
// throw away first result like on AVR? first value seems wrong.
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
for (uint8_t i = 0; i<8; i++) { // resampling
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
temp += ADC_GetConversionValue(ADC1);
}
ADC_Cmd(ADC1, DISABLE);
return ( (temp>>3) );
}
void read_Vdda(void) {
// Get Vdda
adc_init(ADC_Channel_17, ADC_SampleTime_28_5Cycles);
ADC_VrefintCmd(ENABLE);
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
int temp=0;
for (uint8_t i = 0; i<8; i++) { // resampling
ADC_StartOfConversion(ADC1);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
temp += ADC_GetConversionValue(ADC1);
}
Vdda=(temp>>3)*3300/(VREFINT_CAL); // VREFINT_CAL/VRefInt=3300/Vdda -> Vdda=3300*Vref/VREFINT_CAL
ADC_VrefintCmd(DISABLE);
ADC_Cmd(ADC1, DISABLE);
}
void adc_init(int channel, int sample_time) {
ADC_InitTypeDef ADC_InitStructure;
ADC_DeInit(ADC1); // clean up
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // on demand
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_TRGO;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 12 bit right aligned
ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
ADC_Init(ADC1, &ADC_InitStructure);
// ADC calibration; but not used as returned value ends nowhere now...
ADC_GetCalibrationFactor(ADC1);
ADC_ChannelConfig(ADC1, channel, sample_time);
ADC_Cmd(ADC1, ENABLE);
ADC_DiscModeCmd(ADC1, ENABLE);
}
Other observations: Although the STM32 ADCs have an internal voltage reference and even store their values measured at the factory at 30°C and 3.3V - you can only use that for calibrating. You can measure the actual VRefInt value and calculate your supply voltage based upon that. Which is in case of STM32F030P4 always your ADC reference voltage; this TSSOP20 µC lacks a Aref Pin. The address is different for different STM32 µC families - for STM32F030P4, you can read the factory calibration data like this:
const uint16_t *p = (uint16_t *)0x1FFFF7BA; // 2 Byte at this address is VRefInt @3.3V/30°C
VREFINT_CAL = *p; // read the value at pointer address
Before converting a channel with my desired signals I always call the function to read Vdda as well as the supply voltage may be unstable. This way, the results will get even more stable. Finally I can use the STM32 ADC - before finding out these weird behaviours it delivered mostly garbage. Now it's nearly a 12bit presision instrument ;)
TP4056 Charger plotted with STM32 ADC. |