This is a simple introduction to programming on the msp430 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.
- Install code composer studio Code Composer Studio (CCS) Simply make should be installed.
- Make an account on TI's website
- .
- Download and install CCS. Select anything containing "MSP430."
- A MSP430 EXP430F5529LP launchpadLaunchpad. (There is some available in the bay to use or you can purchase your own here).
- Read Software 101: Clearing, Toggling, Setting, and Checking a Bit.
- MSP430F5529 datasheets
Setting up a project
Go to the top menu bar a and click File->New->CCS Project, then make sure the . The dialog that appears looks 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 launch pad launchpad board, you can see a small LED beside one of the buttons labeled "LED1
" with with the label "P2.1" on top of P1.0
near it. The key piece of information here is "P2.1" P1.0
. This tells us that the LED is connected to "Port 2 1 Pin 10" on the microcontroller. That means to turn on Thus, to control the LED, we just need to turn Port 2 1 Pin 1 0 on and off to blink that LED.
...
General purpose input/output (GPIO) on the msp430 works through the use of PxDIR, PxOUT, and PxIN registers. These are 8 bit registers where each bit in the number corresponds to a certain pin. The "x" 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 . 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 2Port 1.
First lets , let's look at the PxDIR
register, or in this specific case P2DIRP2DIR is an 8 bit registers (think of registers as special variables that control input and output in the microcontroller) 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:
Code Block | ||||
---|---|---|---|---|
| ||||
uint8_t P1DIR @ P2DIR;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:
...
Now the right-most bit in the number is the first bit (BIT0)known as bit 0, and it corresponds to the first pin (Pin 10) connected to that port, which in this case that would be P2P1.10. Now when When that bit is a 0, this tells the IO controller that we want this pin to be an output so that we can turn on the LED. If we were to set this bit to 1 making: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.
Code Block | ||||
---|---|---|---|---|
| ||||
P2DIRP1DIR |= BIT0; 0b00000001 |
Then pin 1 would become an input, which would be useful to check if a button has been pressed. But since we are trying to turn the led on leave the bit as 0.
...
// BITx = (1 << x)
0b00000001 |
Now that P1.0 is configured as an output, we must now turn it on the output to turn the LED on. This is where the PxOUT
(or specifically P2OUT in P1OUT
in our case) register comes into play. Like P2DIR PxDIR
, P2OUT is PxOUT
is an 8-bit memory-mapped register where each bit corresponds to a specific pin on the port.
So just like P2DIR, BIT0 will P1DIR
, bit 0 of P1OUT
will correspond to pin 1 0 on port 21. That means to turn on the output of P2P1.1 0, we just have to set BIT0 bit 0 to 1. and then to To blink the led LED, we just have to set BIT0 back to 0 and then back to 1 again. 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 an the LED, so now we can start by looking at the code below:
...
Implementation
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#include <msp430.h> //* Include * main.c */MSP430-specific defines int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer P2OUTP1OUT &= ~BIT0; // Clear P1.0's output value P2DIRP1DIR |= BIT0; // Set P1.0 as an output for (;;) { P2OUTP1OUT ^= BIT0; // Toggle P1.0 using bitwise XOR __delay_cycles(100001000000); // 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 ( lets , let's go over the rest of the code that has not been explained yet, and figure out how and why it works. ) 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
This first line of code in the program is:
Code Block | ||||
---|---|---|---|---|
| ||||
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
.
Code Block | ||||
---|---|---|---|---|
| ||||
__delay_cycles(1000000); |
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
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#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?