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.
- Install code composer studio (CCS)
- Simply make an account on TI website, and after you have completed that find the download link for windows, linux, or mac (depending on what you use) from this site. Just run through the installer (default options should be fine) and you will have code composer studio 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
Setting up a project
Go to the top menu bar a click File->New->CCS Project, then make sure the dialog that appears looks 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 board you can see a small LED beside one of the buttons labeled "LED1" with the label "P2.1" on top of it. The key piece of information here is "P2.1" This tells us that the LED is connected to "Port 2 Pin 1" on the microcontroller. That means to turn on the LED we just need to turn Port 2 Pin 1 on and off to blink that LED.
MSP430 IO Ports and Pins
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" 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 2. First lets look at the PxDIR register, or in this specific case P2DIR
P2DIR is an 8 bit registers (think of registers as special variables that control input and output in the microcontroller), so it would look something like this:
uint8_t P2DIR;
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 the first bit (BIT0), and it corresponds to the first pin (Pin 1) connected to that port, in this case that would be P2.1. Now 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:
P2DIR |= 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.
Since we know now how to configure the pin as an output, we must now turn on the output to turn the LED on. This is where the PxOUT (or specifically P2OUT in our case) register comes into play. Like P2DIR, P2OUT is an 8 bit register where each bit corresponds to a specific pin on the port. So just like P2DIR, BIT0 will correspond to pin 1 on port 2. That means to turn on the output of P2.1 we just have to set BIT0 to 1. and then to blink the led we just have to set BIT0 back to 0 and then back to 1 again. This is all the basic information we need to blink an LED, so now we can start by looking at the code below:
Code
#include <msp430.h> /* * main.c */ int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer P2OUT &= ~BIT0; P2DIR |= BIT0; for (;;) { P2OUT ^= BIT0; __delay_cycles(10000); } return 0; }
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 lets 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 even that it gets stuck in some kind of infinite loop. For know 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 & 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 P2OUT and P2DIR registers so will skip over them and go to the for loop. In the for loop we are toggling BIT0 using the xor operator (The software 101 guide about bits in prerequisite section explains how this works) and then we call a special function "__delay_cycles"
__delay_cycles(10000);
What this function does is that it delays the processor for x cycles (10,000 in our case). So whats happening in our loop is we toggle the state of the LED (if its on it turns off, and if its off it turns on) and then we wait for a little bit. This creates the effect of the LED blinking. Now this is the inefficient way to blink an LED, there is a much better way 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. 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 will work slightly different.
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 can read that in order to start the time we need to set some information in the Timer A control register (TACTL), we need to set the source select (which clock to use), the mode control. We also need to enable interrupts globally and enable interrupts for the timer. We have to set the value the timer counts to, and we also need to create an interrupt function.
First to get started, it would probably be a good idea to understand what interrupts are. Interrupts are essentially a special function that will be get called by that hardware in the case of an event. So in our case we're going to create an interrupt that gets called when the hardware timer is done counting to it's maximum value.
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 "smclock" (this is usually the source clock you want to use for anything) 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.
Code
#include <msp430.h> /* * main.c */ int main(void) { WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer TACTL |= TASSEL_2 | MC_2 | ID_3; TACCTL0 |= CCIE; TACCR0 = 65535; __enable_interrupt(); P2OUT &= ~BIT0; P2DIR |= BIT0; return 0; } #pragma vector = PORT2_VECTOR __interrupt void PORT2_ISR(void) { P2OUT ^= BIT0; }
Here at the bottom in the "#pargma" section we can see the interrupt function we created, this interrupt function is called an "Interrupt Service Routine" or "ISR" for short. All this service routine does is toggle the state of the blinking LED. At the beginning of the main function we can see the timer setup code. First we setup the timer A control register (TACTL) with smclock (TASSEL_2), up-down mode (MC_2), and frequency divider of 8 (ID_3). Next we set the Timer a capture/compare control register (TACCTL0) bit to enable interrupts locally for the timer. After we set the value we will be counting to by setting "TACCR0" to 65535 (the maximum value it can store), this causes the time to start counting. Lastly we globally enable interrupts by calling "__enable_interrupt". Then we configure the IO like before and that's all well need.
You can run the above code and see an LED blink. 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 hardware on the MSP430, you could easily lookup that specific piece of hardware 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.