BSP430  20141115
Board Support Package for MSP430 microcontrollers
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
Bootstrapping a New Platform: Application LPM

This program gives an example of using LPM within an application, where there are timers, serial peripherals, and other things that need to be disabled and enabled around the low power transition.

Platform Results

Measurements using this application for different platforms and different configurations are below. The power values are in microamps; the boards were powered by USB at 3.6V. In the first column (+S, +U) the USCI serial and Timer_A-based uptime clock are left enabled. In the second (-S, +U), the USCI serial is placed in reset mode prior to entering LPM. In the third (-S, -U) the uptime clock is also placed in reset mode.

Some platforms support LPMx.5 modes, which are very low power and require something like the EEVblog uCurrent to measure. LPM3 and LPM4 for the MSP430G2553 is also sub-100nA and cannot be measured with a standard ammeter.

Note
I am not an electrical engineer, so cannot explain any anomalies in the measurements below. But they check out as consistent using different ammeters and are within range of the datasheet values, so I call them plausible.

EXP430G2

Use a revision 1.5 board; earlier ones do poorly. Place the ammeter across the Vcc pair on the header connecting the emulation and MSP430 sides of the board.

uA (+S,+U) uA (-S,+U) uA (-S,-U) Description
386 385 385 active mode with CPU running (reading USCI)
396 396 396 in active mode "idle"
77.1 77.0 76.8 LPM0
77.1 77.1 76.8 LPM1
23.1 23.1 22.9 LPM2
0.40 0.550 0.393 LPM3
0.098 0.062 0.060 LPM4

Current costs are slightly higher in LPM3 and LPM4 if the crystal is populated.

EXP430FR5739

Place the ammeter on the MSP PWR test header. Note that this board's lowest MCLK rate is 5.3MHz, unlike the others which are configured to run at 1MHz for this test. Since active-mode power consumption is proportional to MCLK rate, this may explain why the power consumption in active mode is relatively high.

uA (+S,+U) uA (-S,+U) uA (-S,-U) Description
649 650 650 active mode with CPU running (reading USCI)
431 425 425 in active mode "idle"
167 176 176 LPM0
166 124 123 LPM1
140 64 63.6 LPM2
136 5.8 5.8 LPM3
137 5.8 5.8 LPM4
0.254 0.290 0.290 LPM3.5
0.254 0.290 0.290 LPM4.5

I don't know why it's lower power to keep USCI and Timer_A enabled in LPMx.5. That's just what the measurements show.

EXP430F5438

Place the ammeter on the 430 PWR test header.

uA (+S,+U) uA (-S,+U) uA (-S,-U) Description
283 282 281 active mode with CPU running (reading USCI)
288 290 289 in active mode "idle"
81.4 83 82 LPM0
80.4 81.7 82 LPM1
6.9 8.9 8.8 LPM2
2.4 4.4 4.2 LPM3
2.4 4.4 1.0 LPM4
2.4 4.4 4.2 LPM3.5
2.4 4.4 0.051 LPM4.5

Note that LPM3.5 doesn't seem to be different from LPM3, and unlike the FR5739 LPM4.5 is not entered if either the serial or the timer are left enabled.

Wolverine

Place the ammeter between a 3.3V source and the external power header.

uA (+S,+U) uA (-S,+U) uA (-S,-U) Description
228 228 228 active mode with CPU running (reading USCI)
206 207 207 in active mode "idle"
62.6 62.6 62.6 LPM0
27.3 27.2 27.2 LPM1
0.45 0.52 0.48 LPM2
0.24 0.26 0.20 LPM3
0.24 0.26 0.13 LPM4

LPMX.5 modes could not be reliably measured.

main.c

#include <bsp430/clock.h>
#include <bsp430/lpm.h>
#include <string.h>
#if ! (BSP430_PLATFORM_BUTTON0 - 0)
#error No button available on this platform
#endif /* BSP430_PLATFORM_BUTTON0 */
typedef struct sCommand {
char cmd;
const char * description;
} sCommand;
const sCommand commands[] = {
#define CMD_MODE_ACTIVE 'a'
{ CMD_MODE_ACTIVE, "Remain in active mode while idle" },
#define CMD_MODE_LPM0 '0'
{ CMD_MODE_LPM0, "Set mode to enter LPM0" },
#define CMD_MODE_LPM1 '1'
{ CMD_MODE_LPM1, "Set mode to enter LPM1" },
#define CMD_MODE_LPM2 '2'
{ CMD_MODE_LPM2, "Set mode to enter LPM2" },
#define CMD_MODE_LPM3 '3'
{ CMD_MODE_LPM3, "Set mode to enter LPM3" },
#define CMD_MODE_LPM4 '4'
{ CMD_MODE_LPM4, "Set mode to enter LPM4" },
#ifdef BSP430_PMM_ENTER_LPMXp5_NI
#define CMD_MODE_LPM3p5 '5'
{ CMD_MODE_LPM3p5, "Set mode to enter LPM3.5" },
#define CMD_MODE_LPM4p5 '6'
{ CMD_MODE_LPM4p5, "Set mode to enter LPM4.5" },
#endif /* BSP430_PMM_ENTER_LPMXp5_NI */
#define CMD_HOLD_SERIAL 's'
{ CMD_HOLD_SERIAL, "Toggle whether serial is placed on hold during LPM" },
#define CMD_HOLD_CLOCK 'u'
{ CMD_HOLD_CLOCK, "Toggle whether uptime clock is placed on hold during LPM" },
#define CMD_HELP '?'
{ CMD_HELP, "Display help" },
#define CMD_STATE '='
{ CMD_STATE, "Display system state (clock speeds, etc.)" },
#define CMD_SLEEP '$'
{ CMD_SLEEP, "Enter sleep mode" },
#if (BSP430_PMM_SUPPORTS_COREV - 0)
#define CMD_COREV_INCR '>'
{ CMD_COREV_INCR, "Increment core voltage" },
#define CMD_COREV_DECR '<'
{ CMD_COREV_DECR, "Decrement core voltage" },
#endif /* BSP430_PMM_SUPPORTS_COREV */
};
typedef struct sState {
unsigned int lpm_bits;
const char * lpm_description;
int hold_serial;
int hold_clock;
} sState;
volatile int button;
static int
button_isr_ni (const struct sBSP430halISRIndexedChainNode * cb,
void * context,
int idx)
{
(void)cb;
(void)context;
(void)idx;
++button;
}
const sBSP430halISRIndexedChainNode button_cb = {
.next_ni = NULL,
.callback_ni = button_isr_ni,
};
char rx_buffer[16];
char * volatile rx_head;
char * volatile rx_tail;
static int
consume_rx_ni ()
{
int rc;
if (rx_head == rx_tail) {
return -1;
}
rc = *rx_tail++;
if (rx_tail > (rx_buffer + sizeof(rx_buffer) / sizeof(*rx_buffer))) {
rx_tail = rx_buffer;
}
return rc;
}
static int
consume_rx ()
{
int rc;
rc = consume_rx_ni();
return rc;
}
static int
rx_cbchain_ni (const struct sBSP430halISRVoidChainNode * cb,
void * context)
{
*rx_head++ = device->rx_byte;
if (rx_head > (rx_buffer + sizeof(rx_buffer) / sizeof(*rx_buffer))) {
rx_head = rx_buffer;
}
if (rx_head == rx_tail) {
(void)consume_rx_ni;
}
/* NOTE: For this test intentionally clear all LPM bits, not just
* the ones in BSP430_CORE_LPM_EXIT_MASK as would happen with
* BSP430_HAL_ISR_CALLBACK_EXIT_LPM. */
return LPM4_bits;
}
.callback_ni = rx_cbchain_ni
};
void main ()
{
volatile sBSP430hplPORTIE * b0hpl;
int b0pin;
sState state;
#if (BSP430_MODULE_SYS - 0)
unsigned long reset_causes = 0;
unsigned int reset_flags = 0;
#endif /* BSP430_MODULE_SYS */
#if (BSP430_MODULE_SYS - 0)
{
unsigned int sysrstiv;
/* Record all the reset causes */
while (0 != ((sysrstiv = uiBSP430sysSYSRSTGenerator_ni(&reset_flags)))) {
reset_causes |= 1UL << (sysrstiv / 2);
}
#ifdef BSP430_PMM_ENTER_LPMXp5_NI
/* If we woke from LPMx.5, we need to clear the lock in PM5CTL0.
* We'll do it early, since we're not really interested in
* retaining the current IFG settings. */
if (reset_flags & BSP430_SYS_FLAG_SYSRST_LPM5WU) {
PMMCTL0_H = PMMPW_H;
PM5CTL0 = 0;
PMMCTL0_H = 0;
}
#endif /* BSP430_PMM_ENTER_LPMXp5_NI */
}
#endif /* BSP430_MODULE_SYS */
#if APP_CONFIGURE_PORTS_FOR_LPM
#endif /* APP_CONFIGURE_PORTS_FOR_LPM */
cprintf("\napplpm " __DATE__ " " __TIME__ "\n");
#if (BSP430_MODULE_SYS - 0)
cprintf("System reset bitmask %lx; causes:\n", reset_causes);
{
int bit = 0;
while (bit < (8 * sizeof(reset_causes))) {
if (reset_causes & (1UL << bit)) {
}
++bit;
}
}
cputtext("System reset included:");
if (reset_flags & BSP430_SYS_FLAG_SYSRST_BOR) {
cputtext(" BOR");
}
if (reset_flags & BSP430_SYS_FLAG_SYSRST_LPM5WU) {
cputtext(" LPM5WU");
}
if (reset_flags & BSP430_SYS_FLAG_SYSRST_POR) {
cputtext(" POR");
}
if (reset_flags & BSP430_SYS_FLAG_SYSRST_PUC) {
cputtext(" PUC");
}
cputchar('\n');
#endif
b0hal->pin_cbchain_ni[b0pin] = &button_cb;
#if (BSP430_PORT_SUPPORTS_REN - 0)
#endif /* BSP430_PORT_SUPPORTS_REN */
rx_head = rx_tail = rx_buffer;
/* A careful coder would check to return values in the following */
tty = hBSP430console();
(void)iBSP430serialSetHold_rh(tty, 1);
rx_cb.next_ni = tty->rx_cbchain_ni;
tty->rx_cbchain_ni = &rx_cb;
(void)iBSP430serialSetHold_rh(tty, 0);
*rx_head++ = CMD_MODE_ACTIVE;
*rx_head++ = CMD_STATE;
memset(&state, 0, sizeof(state));
#if 1 && (BSP430_PMM_SUPPORTS_SVSM - 0)
#endif /* BSP430_MODULE_PMM */
while (1) {
int enter_sleep = 0;
unsigned long int sleep_utt;
unsigned long int wake_utt;
do {
int c;
while (0 <= ((c = consume_rx()))) {
const sCommand * cmdp = commands;
const sCommand * const commands_end = commands + sizeof(commands) / sizeof(*commands);
while ((cmdp < commands_end) && (cmdp->cmd != c)) {
++cmdp;
}
if (cmdp->cmd == c) {
cputs(cmdp->description);
switch (cmdp->cmd) {
case CMD_MODE_ACTIVE:
state.lpm_bits = 0;
state.lpm_description = "Active";
break;
case CMD_MODE_LPM0:
state.lpm_bits = LPM0_bits;
state.lpm_description = "LPM0";
break;
case CMD_MODE_LPM1:
state.lpm_bits = LPM1_bits;
state.lpm_description = "LPM1";
break;
case CMD_MODE_LPM2:
state.lpm_bits = LPM2_bits;
state.lpm_description = "LPM2";
break;
case CMD_MODE_LPM3:
state.lpm_bits = LPM3_bits;
state.lpm_description = "LPM3";
break;
case CMD_MODE_LPM4:
state.lpm_bits = LPM4_bits;
state.lpm_description = "LPM4";
break;
#ifdef BSP430_PMM_ENTER_LPMXp5_NI
case CMD_MODE_LPM3p5:
state.lpm_bits = BSP430_CORE_LPM_LPMXp5 | LPM3_bits;
state.lpm_description = "LPM3.5";
break;
case CMD_MODE_LPM4p5:
state.lpm_bits = BSP430_CORE_LPM_LPMXp5 | LPM4_bits;
state.lpm_description = "LPM4.5";
break;
#endif /* BSP430_PMM_ENTER_LPMXp5_NI */
case CMD_HELP: {
cmdp = commands;
cprintf("Available commands:\n");
while (cmdp < commands_end) {
cprintf("\t%c : %s\n", cmdp->cmd, cmdp->description);
++cmdp;
}
break;
}
case CMD_STATE:
cprintf("MCLK rate: %lu Hz\n", ulBSP430clockMCLK_Hz_ni());
cprintf("SMCLK rate: %lu Hz\n", ulBSP430clockSMCLK_Hz_ni());
cprintf("ACLK rate: %lu Hz\n", ulBSP430clockACLK_Hz_ni());
cprintf("LFXT1: %s\n", BSP430_CLOCK_LFXT1_IS_FAULTED_NI() ? "FAULTED" : "stable");
cprintf("Uptime (ACLK ticks): %lu\n", ulBSP430uptime_ni());
cprintf("Selected idle state: %s\n", state.lpm_description);
cprintf("Clocks will %s\n", state.hold_clock ? "freeze" : "run");
cprintf("Serial will %s\n", state.hold_serial ? "be held" : "be active");
#if (BSP430_PMM_SUPPORTS_COREV - 0)
cprintf("Core voltage level %d, SVSMHCTL %04x SVSMLCTL %04x\n",
(PMMCTL0 & PMMCOREV_3)/PMMCOREV0, SVSMHCTL, SVSMLCTL);
#endif /* BSP430_PMM_SUPPORTS_COREV */
#ifdef BSP430_PMM_ENTER_LPMXp5_NI
cprintf("LPM X.5 supported\n");
#else /* BSP430_PMM_ENTER_LPMXp5_NI */
cprintf("LPM X.5 is NOT supported\n");
#endif /* BSP430_PMM_ENTER_LPMXp5_NI */
break;
case CMD_HOLD_SERIAL:
state.hold_serial = ! state.hold_serial;
break;
case CMD_HOLD_CLOCK:
state.hold_clock = ! state.hold_clock;
break;
case CMD_SLEEP:
enter_sleep = 1;
break;
#if (BSP430_PMM_SUPPORTS_COREV - 0)
case CMD_COREV_INCR:
case CMD_COREV_DECR: {
int delta = 0;
int rc = 0;
do {
unsigned int level = PMMCTL0 & PMMCOREV_3;
if ((cmdp->cmd == CMD_COREV_INCR) && (level < PMMCOREV_3)) {
delta = 1;
}
if ((cmdp->cmd == CMD_COREV_DECR) && (level > PMMCOREV_0)) {
delta = -1;
}
if (0 != delta) {
rc = iBSP430pmmSetCoreVoltageLevel_ni(level + delta);
}
} while (0);
if (0 == delta) {
cprintf("Core voltage at limit\n");
} else {
cprintf("Core voltage adjusted %d to %d rv %d\n", delta, PMMCTL0 & PMMCOREV_3, rc);
}
cprintf("PMM: CTL0 %04x CTL1 %04x\n", PMMCTL0, PMMCTL1);
break;
}
#endif /* BSP430_PMM_SUPPORTS_COREV */
}
} else {
cprintf("Unrecognized command: %c\n", c);
}
}
} while (! enter_sleep);
sleep_utt = ulBSP430uptime_ni();
cprintf("Entering idle mode at %lu: %s\n", sleep_utt, state.lpm_description);
if (state.lpm_bits & BSP430_CORE_LPM_LPMXp5) {
cprintf("NOTE: Will use LPMx.5: press button to exit\n");
}
if (state.hold_clock) {
cprintf("Suspending clock\n");
}
if (state.hold_serial) {
cprintf("Disabling serial; press button to wake\n\n");
}
} else {
}
if (0 == state.lpm_bits) {
button = 0;
while ((rx_head == rx_tail) && (! button)) {
}
} else {
#ifdef BSP430_PMM_ENTER_LPMXp5_NI
if (state.lpm_bits & BSP430_CORE_LPM_LPMXp5) {
}
#endif /* BSP430_PMM_ENTER_LPMXp5_NI */
BSP430_CORE_LPM_ENTER_NI(state.lpm_bits);
/* Interrupts probably left enabled */
}
wake_utt = ulBSP430uptime_ni();
if (state.hold_serial) {
cprintf("Serial now awake\n");
}
if (state.hold_clock) {
cprintf("Clock resumed\n");
}
cprintf("Left idle mode at %lu: %lu asleep\n", wake_utt, wake_utt - sleep_utt);
}
}

bsp430_config.h

/* Although a crystal supports more accurate timing, it does increase
* current. Disable it unless otherwise requested */
#ifndef BSP430_PLATFORM_BOOT_CONFIGURE_LFXT1
#define BSP430_PLATFORM_BOOT_CONFIGURE_LFXT1 0
#endif /* BSP430_PLATFORM_BOOT_CONFIGURE_LFXT1 */
/* Application does output: support spin-for-jumper */
#ifndef configBSP430_PLATFORM_SPIN_FOR_JUMPER
#define configBSP430_PLATFORM_SPIN_FOR_JUMPER 1
#endif /* configBSP430_PLATFORM_SPIN_FOR_JUMPER */
/* Support console output */
#define configBSP430_CONSOLE 1
/* Monitor uptime and provide generic ACLK-driven timer */
#define configBSP430_UPTIME 1
/* Request a button */
#define configBSP430_PLATFORM_BUTTON0 1
/* And LEDs, configured at boot */
#define configBSP430_LED 1
#define BSP430_PLATFORM_BOOT_CONFIGURE_LEDS 1
#ifndef APP_CONFIGURE_PORTS_FOR_LPM
#define APP_CONFIGURE_PORTS_FOR_LPM 1
#endif /* APP_CONFIGURE_PORTS_FOR_LPM */
/* Get platform defaults */

Makefile

MCLK ?= 1000000
AUX_CPPFLAGS += -DBSP430_CLOCK_NOMINAL_MCLK_HZ=$(MCLK)
PLATFORM ?= exp430fr5739
MODULES = $(MODULES_PLATFORM)
MODULES += $(MODULES_UPTIME)
MODULES += $(MODULES_CONSOLE)
MODULES += periph/port
MODULES += periph/sys
MODULES += periph/pmm
MODULES += lpm
SRC=main.c
include $(BSP430_ROOT)/make/Makefile.common