This is a simple introduction to programming on the MSP430 microcontroller, which was the main microcontroller for Midnight Sun XI.
Prerequisites
- All code running on our electrical system is written in C. It would be a good idea to become familiar with C before continuing with this tutorial. You can find many resources online about programming in C such as this one.
- Code Composer Studio (CCS) should be installed.
- A MSP430 EXP430F5529LP Launchpad. (There is some available in the bay to use or you can purchase your own here).
- Read Clearing, Toggling, Setting, and Checking a Bit.
- MSP430F5529 datasheets
Setting up a project
Go to the top menu bar and click File->New->CCS Project. The dialog that appears should look something like this:
Verify that the target and project name are correct, and click finish to create the project.
Blinking an LED the slow way
To get started, we are going to write some code to blink an LED on the launchpad. If you look at the launchpad board, you can see a small LED beside one of the buttons labeled LED1
with the label P1.0
near it. The key piece of information here is P1.0
. This tells us that the LED is connected to "Port 1 Pin 0" on the microcontroller. Thus, to control the LED, we just need to turn Port 1 Pin 0 on and off to blink that LED.
MSP430 IO Ports and Pins
General purpose input/output (GPIO) pins on the MSP430 are capable of acting as both digital inputs and outputs. GPIO pins are generally broken up into groups known as "ports" for ease of use. GPIO pins are commonly referred to as PX.Y, where X refers to the port the pin belongs to, and Y refers to the pin's 0-indexed place in the port.
Each port is assigned a number of 8-bit memory-mapped registers to control the pins assigned to the port. In this case, the registers of interest are PxDIR
, PxOUT
, and PxIN
. The x corresponds to which port we are currently trying to access. There can be any number of ports on an MSP430 (this is something you would look for on the datasheet), but right now, we are only concerned about Port 1.
First, let's look at the PxDIR
register. This register is responsible for controlling GPIO direction. In this specific case, that would be P1DIR
. P1DIR
is an 8-bit memory-mapped register, so it would look something like this:
uint8_t P1DIR @ 0xABCD; // This is located at some arbitrary memory address
and since we are concerned about the bits in it, by default it would have a value like this in binary:
0b00000000
Now the right-most bit in the number is known as bit 0, and it corresponds to the first pin (Pin 0) connected to that port, which in this case that would be P1.0. When that bit is a 0, this tells the IO controller that we want this pin to be an input. If it's a 1, it should be an output. For example, if we set bit 0 of P1DIR
to 1, then P1.0 would become an output.
P1DIR |= BIT0; // BITx = (1 << x) 0b00000001
Now that P1.0 is configured as an output, we must now turn it on to turn the LED on. This is where the PxOUT
(or specifically P1OUT
in our case) register comes into play. Like PxDIR
, PxOUT
is an 8-bit memory-mapped register where each bit corresponds to a specific pin on the port.
So just like P1DIR
, bit 0 of P1OUT
will correspond to pin 0 on port 1. That means to turn on the output of P1.0, we just have to set bit 0 to 1. To blink the LED, we just have to toggle that bit. Note that PxIN
works similarly, but is a read-only register where each bit represents the digital input on that pin.
This is all the basic information we need to blink the LED, so now we can start by looking at the code below:
Implementation
#include <msp430.h> // Include MSP430-specific defines int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer P1OUT &= ~BIT0; // Clear P1.0's output value P1DIR |= BIT0; // Set P1.0 as an output for (;;) { P1OUT ^= BIT0; // Toggle P1.0 using bitwise XOR __delay_cycles(1000000); // Software delay } return 0; }
Explanation
You can copy and paste this code into the main.c file of your project to get started, and then you can run it by hitting the debug button () and then the run button () (a dialog box might appear telling you to update firmware, just click okay and let it update). The code should begin to run on the microcontroller and you will see the LED begin to blink, congratulations! Now, let's go over the rest of the code that has not been explained yet, and figure out how and why it works.
This first line of code in the program is:
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
This line has the important function of disabling the watchdog timer. The watchdog timer is a special timer on the microcontroller that will restart the microcontroller if it counts to a certain defined value without the timer being reset. This is useful to reset the running program in the event that it gets stuck, such as in an infinite loop. For now, the watchdog timer will just be confusing to us, so we will disable it. To figure out what the register WDTCTL
does when you set the bits WDTPW
and WDTHOLD
, we can look at the datasheet for the MSP430x5xx family. Searching for the section on the watchdog timer, we can now find the WDTCTL
register section on page 461. From here, we can see that WDTHOLD
stops the watchdog timer from running and WDTPW
is the password we need to use to modify the watchdog timer.
I've already explained the PxOUT
and PxDIR
registers, so we'll skip over them and look at the for loop. In the for loop, we are toggling bit 0 of P1OUT
using the XOR operator (See Clearing, Toggling, Setting, and Checking a Bit) and then we call a special function, __delay_cycles
.
__delay_cycles(10000);
This function delays the processor for x cycles. In our case, we chose 1,000,000 because MCLK runs at 1Mhz by default so it should be around 1 second of delay. So, in our loop, we toggle the state of the LED (if it's on, it turns off, and if it's off, it turns on) and then we wait for a little bit. This creates the effect of the LED blinking.
However, this is the inefficient way to blink an LED. There are better ways to blink an LED.
Blink an LED with timers
The issue with the above method of blinking an LED is the __delay_cycles
function. When this function is running, we aren't doing anything else. This is wasted time that we could be using to compute other things on the microcontroller or to conserve power by sleeping. Luckily, the MSP430 provides a way to run a timer in the background while other code is running. To get started, create a new project like before but this time name it "TestTimerBlink". Now, the code we are going to use is very similar, but it will work slightly differently.
First, to understand how timers work, it would be a good idea to look at the timer section in the datasheet (Documentation for Timer A starts on page 462). Here, we can see all the documentation about the registers that interact with Timer A on the microcontroller. From this, we find out that in order to use the timer, we need to set some information in the Timer A control register (TA0CTL)
and handle the interrupts.
- Set the source select (which clock to use)
- Set the mode control
- Enable interrupts globally
- Enable interrupts for the timer
- Set the value the timer counts to
- Create an interrupt service routine
First, to get started, it would probably be a good idea to understand what interrupts are. Interrupts are essentially special events in hardware that call functions known as interrupt service routines (ISRs), interrupting main program execution. In our case, we're going to create an interrupt that gets called when the hardware timer is done counting to its maximum value. For more detail, read Interrupts and Busy-Wait.
Next, we need to figure out what values to set in the timer control registers. We can check the datasheet for that. We want to use the source clock SMCLK (a clock derived from the master clock that is 1Mhz by default) and we want to count in up-down mode. From the datasheet, we can see that we want TASSEL_2
for the source select and MC_2
for the mode control. We are also going to divide the clock frequency (to make it count slower) by 8 using ID_3
(input divider 3, which divides the input clock frequency by 8). We can see this all in action in the code below that you can copy and paste into your editor.
Implementation
#include <msp430.h> int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer P1OUT &= ~BIT0; P1DIR |= BIT0; TA0CTL |= TASSEL_2 | MC_2 | ID_3; TA0CCTL0 |= CCIE; TA0CCR0 = 65535; __enable_interrupt(); for (;;) { } return 0; } #pragma vector = TIMER0_A0_VECTOR __interrupt void TIMER0_A0_ISR(void) { P1OUT ^= BIT0; }
Explanation
At the beginning of the main function, we can see the IO and timer setup code. The IO configuration is the same as before, but the timer setup is new.
First, we setup the Timer A control register (TA0CTL
) to use SMCLK (TASSEL_2
), up-down mode (MC_2
), and a frequency divider of 8 (ID_3
). Next, we set the Timer A capture/compare control register (TA0CCTL0
) to enable interrupts locally for the timer. After that, we set the value we will be counting to by setting TA0CCR0
to 65535 (the maximum value it can store). This will cause the timer to start counting. Lastly, we globally enable interrupts by calling __enable_interrupt
and loop forever.
Here at the bottom in the #pragma
section, we can see the ISR we created for Timer A interrupts. All this service routine does is toggle the state of the LED to make it blink.
Recap
This method is slightly more complicated but is much better. It allows to run other code while you wait for the LED to blink. That way, no time is wasted.
This was a very specific example. If you wanted to learn how to use different peripherals on the MSP430 or any other microcontroller, you could easily look up that specific peripheral in the datasheet and see how to use configure and use it properly. The datasheet should be your go-to place to find out what the MSP430 is capable of and how to actually do things.
Fun Challenges
Think you figured out how all this works? Try these challenges!
- Blinking an LED using a timer output: Although we used an ISR to blink the LED, timer peripherals can actually output to specific pins on a microcontroller!
- Make the LED respond to a button press: Learn how to handle inputs and state! How can you communicate between the ISR and main code?
- Sleep between blinks: We mentioned sleeping to conserve power. When would you do this? How do you wake up again? What stays active?