MSP430 assembly part 1: Blinking LED hello world
Lets blink an LED on a MSP430 launchpad the old fashioned way! Assembly! Don't worry, MSP430 has a nice, small RISC instruction set. It's only true 24 instructions, with a total of 27 higher level ones emulated by the assembler.
The full user's guide for the MSP430 microcontroller i will be covering (on an older launchpad) is available here ,
grab a copy because it covers the instruction set and microcontroller completely.
First, I will present the source with the CCS generated boiler plate for ELF sections and stack setup:
.cdecls C,LIST,"msp430.h" ; Include device header file .def RESET ; Export program entry-point to ; make it known to linker. .text ; Assemble into program memory. .retain ; Override ELF conditional linking ; and retain current section. .retainrefs ; And retain any sections that have ; references to current section. RESET mov.w #__STACK_END,SP ; Initialize stackpointer StopWDT mov.w #WDTPW|WDTHOLD,&WDTCTL ; Stop watchdog timer ; our code will go here moving forward .global __STACK_END .sect .stack .sect ".reset" ; MSP430 RESET Vector .short RESET
For all intents and purposes we can ignore the setup scaffolding, it just turns off the watchdog timer and setups the stack and reset vector. This lets you press the reset button to .. jump to our RESET label.
To blink an LED on a microcontroller you need to do 2 things:
- Set the pin(s) of the PORT the LED is connected to to OUTPUT
- Switch the output high/low
On the Ti launchpad, there are two LEDs on PORT1. For simplicity we can set the entire port to output. To do this,
mov an immediate value into the named mapping like so:
mov #0FFh, P1DIR ; P1DIR is port1 direction control. '#0FFh' is an immediate value in hex
Now the entire port is set as output. To check, you could just try to lightup all the LEDs by turning all the pins on:
mov #0FFh, P1OUT
However this isnt very exciting because they just stay lit. We need to toggle. The easiest way is an exclusive or (xor) of the bits.
xor #0FFh, P1OUT ; flip all bits in P1OUT
However, we need to loop over this so we dont just change the output once and fall through:
LOOP xor #0FFh, P1OUT ; flip all bits in P1OUT jmp LOOP
Astute readers know what's coming next. if you run this code the LEDs will appear to be solid. It's because the loop's executing so fast the human eye cannot see the difference. In fact there probably is no difference, as the rate this loop executes is many 1000s of instructions per second.
I lied when I said we only need to do 2 things. We need a delay loop. Unfortunately we do not have something as convenient as wiring's
delay(). We need to make a delay loop by hand! This is much easier than it sounds. There are a few ways to do so, we will do the naive approach first:
- Put a big value in a register
- decrement the value until it hits zero
- once it hits zero, execute our toggle code
The tricky part about this approach is figuring out how big of a number you need, which can depend on the MCU clock rate, and what instructions you effectively execute within the loop. Precise timing can be achieved with some math and
nop sleds. But for our purpose, we can ballpark some visible delay.
Lets stuff our countdown in the first general register R4. The MSP430 is a 16-bit cpu so lets try the biggest 16-bit unsigned value to start with:
mov #0FFFFh, R4
Now lets run it down to zero. We can check if it hit zero using the status register SR, this is like the x86 EFLAGS register. It will have bits set automatically after certain instructions, which are documented for every instruction that affects status flags. Alternatively, there is a conditional jump when the zero flag is set, so we will use it - jnz.
DELAYLOOP SUB #1h, R4 jnz DELAYLOOP ; we fall through here once delay loop is done
This will spinwait until R4 hits zero and then fall through, now all that's left is to jump to top loop again:
This will start the whole process over. On a MSP430G2553, this first try at a delay loop works out to around 250ms, making for a nice blinking rate.
For completeness, the entire body of our effective code looks like this (with some added comments):
mov #0FFh, P1DIR ; setup - set port1 to output LOOP xor #0FFh, P1OUT ; flip port 1 bits mov #0FFFFh, R4 ; R4 will be our delay counter DELAYLOOP SUB #1h, R4 ; subtract 1 from R4... jnz DELAYLOOP ; if we hit zero we're done with delay loop jmp LOOP
This code can be simplified even further, do you see how? This is an exercise left to the reader. It's also midnight and I'm starting to get tired in my old age.
In part two we will use the built in timer peripherals of the microcontroller and use interrupts for a cleaner approach. After that it will be on to PWM (Pulse Width Modulation) duty cycles and controlling a servo using an ADC input.