Basic Low-Power Design: Sleepy PWM on the MSP430

Recently, I wrote about setting up a basic ‘hello, world’ program for an MSP430 microcontroller. These chips look like they are designed to make low-power designs easy to code, so it seems like a good idea to start learning about their low-power sleep modes. TI claims that, at 3V and a 1MHz core clock speed, an MSP430x2xx will consume about 300μA while running, 55μA in “low-power mode 0”, and as little as 0.1μA in “low-power mode 4”. But you may have difficulty reaching those figures on a Launchpad board because of how the circuit is laid out.

You can see section 2.3 of the reference manual for more details on these sleep modes, but the most important thing to know is that different low-power modes can selectively disable the core CPU, system clocks, and peripherals on the chip. Some clocks and peripherals remain active in some low-power modes. For example, the chip’s timers can continue to run while the CPU is off as long as the system clock they are connected to is still active.

So in this tutorial, we’ll walk through pulsing a common-cathode RGB LED through various colors using the MSP430’s timers to generate PWM signals while the chip rests in LPM0 sleep mode. We need to set up the timers and attach an interrupt to periodically change the PWM duty cycles, but once that is done we can turn off the CPU. The timer interrupt will periodically wake it up, and the chip will automatically go back to sleep after the interrupt handler returns.

“Common-Cathode” LEDs let you control a red, green, and blue LED individually using PWM signals. Each LED is connected to the same cathode (‘ground’).

This post’s Github repository contains two projects; one which dims and brightens a single on-board LED using PWM, and one which uses a timer interrupt to pulse each color in a common-cathode RGB LED while putting the device to sleep when it is not active.

MSP430 Timers and PWM

The MSP430G2553 used in this tutorial has two “Timer A” peripherals, and they are fairly simple. They support the usual 16-bit timer functions; you can count up and/or down between 0x0000 – 0xFFFF, and use a set number of “capture/compare” channels to either generate PWM outputs or listen to PWM inputs. There are a few more options that you can read about in the “Timer_A” section of your reference manual, but if you’ve used a microcontroller’s timer peripheral before it will probably look familiar.

Each of the MSP430G2553’s “Timer A” peripherals has three “capture/compare” channels, but that is actually fairly limiting because we need to use one of each timer’s channels to generate a base frequency for our PWM signals. So that only leaves us with four adjustable PWM output channels in total. Also, one of Timer 0’s channels has no GPIO pins associated with its output signal. You can find this information (and more) in the “Timer_A3” section of this chip’s datasheet; page 16 as of the time of writing.

So if we want to drive three LEDs while the CPU is asleep, we’ll need to use the three timer channels with hardwired GPIO connections to power them. Each timer also has a generic interrupt which triggers whenever the timer’s counter reaches its maximum value and “ticks over”, and we can use that hardware interrupt to wake up the CPU periodically after we put it to sleep. But for now, let’s gradually dim and brighten a single LED to learn a bit about using PWM.

In the basic ‘hello world’ example, we blinked an LED by setting its GPIO pin to ‘output’ mode using a PxDIR register before toggling the pin’s state using a PxOUT register. If we want to set up a pin for use with a peripheral, the MSP430 also has two ‘mode selection’ registers which determine which peripheral a pin should connect to. For connecting a pin to a timer on the MSP430G2553, we set the appropriate bit in the PxSEL register and leave the PxSEL2 bit un-set (these settings are listed in the datasheet’s “Port Px Pin Functions” tables). So preparing P1.6 for PWM output is simple:

// Setup P1.6 for PWM output.
P1DIR |= (BIT6);
P1SEL |= (BIT6);

Then we can set up the timer peripheral, which also only takes a few lines of code:

The “Timer A” registers are documented in the reference manual like I mentioned at the beginning of this section, but in a nutshell, the TA0CCR0 register configures “capture/compare” channel 0 of timer 0. The MC_1 setting in TA0CTL tells the timer to count up to whatever value is stored in channel 0 before “ticking over”, so this channel will control the base frequency of our PWM signal. The TASSEL_2 setting just tells the timer to use the core system clock for its base frequency.

The TA0CCTL1 register controls “capture/compare” channel 1, which will control the duty cycle of an individual PWM signal. OUTMOD_7 sets the output mode, which you can also find more details about in the reference manual. Mode 7 alternates between setting and resetting the PWM output signal, which is good for controlling power to something like an LED. Finally, the TA0CCR1 register sets the duty cycle of the PWM signal, compared to TA0CCR0. If channel 0 is set to 1000 ticks and channel 1 is set to 500 ticks, the duty cycle will be 50% and the LED will be on half of the time.

To vary the brightness of the LED, we can adjust the duty cycle by modifying the TA0CCR1 register’s value. I think that technically you are supposed to halt the timer before changing this sort of setting, but it doesn’t really matter for a simple program like this. So here’s some simple logic to pulse the LED:

And that’s it – the full main.c file is available on Github, and if you build and flash the program to a Launchpad board it should start to smoothly pulse the LED connected to pin 1.6:

It’s not very obvious, but you can see that the red LED at the bottom of the board is dimmer than the yellow and green ones near the top, because it is being lit by PWM with a low duty cycle.

MSP430 Interrupts

That’s all well and good, but the newer MSP430G2 Launchpad boards also include a common-cathode RGB LED on the board with jumpers to pins 2.1, 2.3, and 2.5. I’m not sure why they wired it that way, because pin 2.3 connects to CCR0 in Timer 1, and like I mentioned in the previous section it looks like that channel needs to be reserved for setting a base PWM frequency. I’m probably missing something, so if you know how to control all 3 LEDs with a single timer peripheral I would love to hear about how that works.

But instead of banging my head against the wall, I connected the board’s P1.6 jumper to the P2.3 one and used Timer 0 for the green LED. If you have an old version of the board which does not have a tri-color LED built in, you can use any old 4-pin common-cathode LED board; they’re very cheap on Amazon/eBay/AliEpress/TaoBao/etc.

A “Common-Cathode” RGB LED board. The ‘R’, ‘G’, and ‘B’ pins connect to each LED’s anode, and the ‘-‘ pin connects to the common cathode.

Setting up the GPIO pins and timers works about the same as before, we just use more “capture/compare” channels and set the two timers’ CCR0 registers to the same value:

The starting red/green/blue values (rv, gv, bv) are arbitrary; I set the red and blue LEDs to start at half-brightness, and the green LED to start at zero brightness. The TAIE bit in TA0CTL tells the timer to trigger a hardware interrupt when it reaches the end of its count and restarts. The hardware interrupts for your MSP430 chips will be listed in its datasheet under the “Interrupt Vector Addresses” section. In this case, we want the TIMER0_A1_VECTOR interrupt. The GCC syntax for a hardware interrupt looks like this:

The update_colors method just applies the logic from the last PWM pulse example to each of the three LEDs; I won’t copy it all here but you can find it on Github. With an appropriate hardware interrupt defined and the peripherals all set up, we can put the chip to sleep and tell it to start listening to its hardware interrupts:

The _BIS_SR macro sets bits in the main status register; GIE is the “General Interrupt Enable” bit, and the chip’s maskable hardware interrupts will be ignored if it is not set. You can also disable interrupts with the corresponding ‘bit clear’ macro, _BIC_SR.

The LPM0_bits status register bits put the chip into “Low-Power Mode 0” by turning off the CPU. The system clocks will still run in LPM0 though, and so will the peripherals attached to them. Since we have set the GPIO pins to connect directly to the timer peripherals, the LEDs will continue to be lit by the rapid PWM signals that we programmed even while the CPU is sleeping.

So with that, the LEDs should start to pulse through a rainbow of colors. The CPU will also be sleeping most of the time, although the LEDs in this example program probably consume a lot more power than the microcontroller does in either run or sleep mode. Still, it’s a good learning exercise.

The newer Launchpad boards have an RGB LED built into the board, but this example code uses P1.6 instead of P2.3 for the green LED, so you will need to connect those pins with a jumper wire as shown here.

Conclusions

Besides the lower power consumption, this sort of example demonstrates how microcontroller programs can be entirely event-driven. If your chip has enough peripherals to associate different parts of your program with specific events or actions, you can easily conserve power by putting it to sleep when nothing important is happening. Neat!

Also, if you are reading this and you know of a good way for a hobbyist to measure very low-current applications, I would love to hear about it. There is an open-source project called “microCurrent Gold” or “μCurrent Gold” which is designed to accurately measure low-power devices, but I can’t find anywhere that still produces and distributes them. I guess that making one from the design files would be a good project in and of itself, but it’s always expensive and time-consuming to do that in quantities of one.

Related posts:

Evaluation boards are great, but eventually you’ll want to make a design which needs to fit in a smaller space, or which uses a type of chip that doesn’t have a cheap board available. When that happens, you’ll often want to design a PCB. And it seems like most microcontrollers have similar basic hardware requirements; decoupling capacitors on the power pins, maybe a pull-up resistor and filtering capacitor on the reset pin, and a few pins which are used for debugging and programming. The MSP430 is no different, although it does have a few small quirks to be aware of.

And while I haven’t found a cheap dedicated USB device for programming MSP430 chips, you can use the debuggers built into TI’s Launchpad boards to program and debug a custom board using their “Spy-Bi-Wire” protocol. So in this tutorial, I’ll go over a basic circuit design for an MSP430FR2111 chip. It comes in a TSSOP-16 package with 3.75KB of FRAM and no Flash memory.

A simple example MSP430FR2111 breakout board design.

I’ll also go over the differences between programming a ‘pulsing LED’ example for the MSP430FR2111 and the MSP430G2553 that we used in the last two examples, as well as how to connect a Launchpad board’s debugger to upload programs to the custom board. So let’s get started!

Most of the embedded programming that I’ve written about so far has focused on the STM32 family of ARM microcontrollers produced by ST Microelectronics. Those are a reasonable starting point for learning about microcontrollers, because they have a solid suite of open-source development tools, a decade-long history of popular use, and affordable hardware development tools which let you get started for less than $20.

But now that platforms like Arduino have become popular and presumably profitable, more vendors are getting on board with providing affordable “DIY” development tools. So while I couldn’t find any cheap and widely-available USB debuggers for TI’s MSP430 core, the “Launchpad” boards that they sell each include an on-board debugger which they say can talk to most MSP430 chips through a two-wire interface similar to the “Serial-Wire Debug” protocol used by a number of Mobile ARM cores including the STM32.

Like the Arduino Uno, some Launchpad boards use DIP chips which you can remove and plug into a breadboard once they have been programmed.

So let’s learn how to write bare-metal programs for the MSP430! This will be easier than bootstrapping a build system for the STM32, because TI opted to design a special-purpose 16-bit RISC core for these chips instead of buying a cookie-cutter core from ARM. That means that the MSP430-GCC toolchain can include ‘glue’ code like linker scripts and vector tables for every MSP430 chip ever made, and we will not need to write our own. There are also fewer steps involved in setting up clocks and peripherals, so this is a comparatively easy platform to develop on. And as usual, this tutorial’s code is on Github.

Like many of us, I’ve been stuck indoors without much to do for the past month or so. Unfortunately, I’m also in the process of moving, so I don’t know anyone in the local area and most of my ‘maker’ equipment is in storage. But there’s not much point in sulking for N months straight, so I’ve been looking at this as an opportunity to learn about designing and implementing FPGA circuits.

I tried getting into Verilog a little while ago, but that didn’t go too well. I did manage to write a simple WS2812B “NeoPixel” driver, but it was clunky and I got bored soon after. In my defense, Verilog and VHDL are not exactly user-friendly or easy to learn. They can do amazing things in the hands of people who know how to use them, but they also have a steep learning curve.

Luckily for us novices, open-source FPGA development tools have advanced in leaps and bounds over the past few years. The yosys and nextpnr projects have provided free and (mostly) vendor-agnostic tools to build designs for real hardware. And a handful of high-level code generators have also emerged to do the heavy lifting of generating Verilog or VHDL code from more user-friendly languages. Examples of those include the SpinalHDL Scala libraries, and the nMigen Python libraries which I’ll be talking about in this post.

I’ve been using nMigen to write a simple RISC-V microcontroller over the past couple of months, mostly as a learning exercise. But I also like the idea of using an open-source MCU for smaller projects where I would currently use something like an STM32 or MSP430. And most importantly, I really want some dedicated peripherals for driving cheap addressable “NeoPixel” LEDs; I’m tired of needing to mis-use a SPI peripheral or write carefully-timed assembly code which cannot run while interrupts are active.

But that will have to wait for a follow-up post; for now, I’m going to talk about some simpler tasks to introduce nMigen. In this post, we will learn how to read “program data” from the SPI Flash chip on an iCE40 FPGA board, and how to use that data to light up the on-board LEDs in programmable patterns.

The LEDs on these boards are very bright, because you’re supposed to use PWM to drive them.

The target hardware will be an iCE40UP5K-SG48 chip, but nMigen is cross-platform so it should be easy to adapt this code for other FPGAs. If you want to follow along, you can find a 48-pin iCE40UP5K on an $8-20 “Upduino” board or a $50 Lattice evaluation board. If you get an “Upduino”, be careful not to mis-configure the SPI Flash pins; theoretically, you could effectively brick the board if you made it impossible to communicate with the Flash chip. The Lattice evaluation board has jumpers which you could unplug to recover if that happens, but I don’t think that the code presented here should cause those sorts of problems. I haven’t managed to brick anything yet, knock on wood…

Be aware that the Upduino v1 board is cheaper because it does not include the FT2232 USB/SPI chip which the toolchain expects to communicate with, so if you decide to use that option, you’ll need to know how to manually write a binary file to SPI Flash in lieu of the iceprog commands listed later in this post.