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.
The 12 bit ADC from ST knows some different ways to work: single shot measurement or continous mode, one channel or more channels. As soon as more than one channel is configured, it seems the ADC goes into the scanning mode where it measures each channel by subsequent ADC-Start commands. So if you want to measure your supply voltage in the beginning and later on something on Pin A0, every second call for an ADC conversion will measure Vcc instead of Pin A0. At least that would explain the results delivered in my tests.
To do it right, you need to DeInit the ADC after the measurement and for a new measurement completely re-initialize it. This leads to reliable and reasonable results. So here are some code snippets to show how I solved that for me.
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);
}
It seems necessary to split every channel reading into separate functions. Another solution would be to always read through all configured channels and update their values. For oversampling, that would mean quite some overhead if you don't need all those channels at that time.
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:
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 ;)
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. |
This is how a proper measurement cycle looks like in the end. Way better than with AVR / Arduino 10 bit ADC.
Keine Kommentare:
Kommentar veröffentlichen