While playing with solar cells and MSP430 Launchpad I needed to check if MCU’s power supply level is high enough to switch to higher clock speed and do some power-hungry stuff. As MSP430G2553 comes with built-in 10-bit analog-to-digital converter and precise 1.5V and 2.5V internal voltage references, the task was quite easy.
My specific power source (solar cell + 0.47F supercap + 3.3V LDO) provides rather wide range of voltages – from 0V to ~3.25V. The MSP microcontroller starts from about 1.8V. To measure the whole range, I had to use both voltage references: 1.5V for voltages lower than 3V and 2.5V for voltages higher (or equal) than 3V. Sort of a dual range voltage meter 😉
This C function reads and returns Vcc level in millivolts.
UPDATE: To properly power down ADC after measurement, ENC flag must be cleared before clearing ADC10ON. Code updated 🙂
uint16_t Msp430_GetSupplyVoltage(void)
{
uint16_t raw_value;
// first attempt - measure Vcc/2 with 1.5V reference (Vcc < 3V )
ADC10CTL0 = SREF_1 | REFON | ADC10SHT_2 | ADC10SR | ADC10ON;
ADC10CTL1 = INCH_11 | SHS_0 | ADC10DIV_0 | ADC10SSEL_0;
// start conversion and wait for it
ADC10CTL0 |= ENC | ADC10SC;
while (ADC10CTL1 & ADC10BUSY) ;
// stop conversion and turn off ADC
ADC10CTL0 &= ~ENC;
ADC10CTL0 &= ~(ADC10IFG | ADC10ON | REFON);
raw_value = ADC10MEM;
// check for overflow
if (raw_value == 0x3ff) {
// switch range - use 2.5V reference (Vcc >= 3V)
ADC10CTL0 = SREF_1 | REF2_5V | REFON | ADC10SHT_2 | ADC10SR | ADC10ON;
// start conversion and wait for it
ADC10CTL0 |= ENC | ADC10SC;
while (ADC10CTL1 & ADC10BUSY) ;
raw_value = ADC10MEM;
// end conversion and turn off ADC
ADC10CTL0 &= ~ENC;
ADC10CTL0 &= ~(ADC10IFG | ADC10ON | REFON);
// convert value to mV
return ((uint32_t)raw_value * 5000) / 1024;
} else
return ((uint32_t)raw_value * 3000) / 1024;
}
Great post, and thanks for making it so easy to read. We’re using the MSP430 to build low-energy wireless sensors and we’re now using your code to evaluate the remaining battery capacity.
You mentioned that the MSP starts up at about 1.8v, yet the datasheet says that the internal 1.5v regulator requires a minimum Vcc of 2.2v. How does this routine behave when 1.8<Vcc<2.2?
Below the 2.2V required for the 1.5V reference, the temperature diode seems to behave OK as a very rough reference (allow lots of margin):
const int NomTemp = 25; // °C
const float Vtemp = 0.00355 * NomTemp + 0.986; // BEWARE large unit-to-unit offset !!
#define TEMP_COUNTS(Vcc) ((int)(1024 * Vtemp / (Vcc))) // INCH_10 // higher with lower Vcc
int readTempDiode() {
ADC10CTL0 &= ~ENC;
ADC10CTL0 = SREF_0 | ADC10SHT_3 | REFBURST | ADC10ON;
ADC10CTL1 = INCH_10 | ADC10DIV_1 | ADC10SSEL_0; // measure temperature diode vs. Vcc
ADC10CTL0 |= ENC | ADC10SC;
while (!(ADC10CTL0 & ADC10IFG));
return ADC10MEM;
}
while (readTempDiode() > TEMP_COUNTS(2.4)); // wait for Vcc to reach 2.2V