Fun with a chip from Texas Instruments [part 2]
Last time we simply switched on two LEDs and let them shine forever (well, until someone pulled the power!). I feel it's almost too trivial of an example, but on the other hand; getting past the initial challenges of using the GNU assembler/linker and explaining the basic concepts is essential.
Let's take it one tiny step further. Let's light up those two LEDs one after the other, like a siren light.
To accomplish this, we'll be exploring interrupts and timers. The MSP chip has a Basic Clock Module that helps us to write embedded applications with extremely low power consumption. We'll be using the DCO (digitally controlled oscillator) clock source. Now, three clock signals are available from the Basic Clock Module, namely ACLK, MCLK and SMCLK. We'll be using the SMCLK; the sub-main clock!
The clockrate at which we will be sourcing the SMCLK from DCO is approx. 1MHz. Not exactly mind-boggingly fast, but keep in mind that MSP is a very power efficient microcontroller.
Note that if you wanted to use the MSP for something that requires good timing, then you'd add a crystal to the circuit. I don't have a crystal, but I see that the LaunchPad development kit supports soldering a crystal oscillator onto it (e.g. a 32 768 kHz watch crystal).
In configuring the Basic Clock Module, I just went for the default settings as specified in the MSP430 user manual, as seen below:
clr.b &DCOCTL ; set lowest DCOx and MODx settings
bis.b #DCO2+DCO1+DCO0, &DCOCTL ; select max DCO Tap
bis.b #RSEL0+RSEL1+RSEL2, &BCSCTL1 ; select range 7
We'll be using Timer A which is an integral part of all the MSP chips. Some MSP-chips also have Timer B onboard, but I haven't played with that yet. For this simple and rather unsophisticated example, we'll just set a "highish" TACCR0 value. Doing so we achieve a slight delay between changing the LEDs lights. You'll see that I used 0xFFFF in the source code.
Let's source Timer A from SMCLK. We're going to be using TACTL, the control register for Timer A. It has a structure specified in the manuals, and as you can see we set it to 0x0210. Translated to binary that is 0000 0010 0001 0000. If you compare this to the structure of this control register, you'll see that we'll be sourcing from SMCLK (and not ACLK etc), and MC "Up-Mode". Up-mode simply means that we let the timer count up to the value in TACCR0, and then it resets back to zero.
We still need to trigger an interrupt so we can alternate the lights! For this we'll be setting TACCTL1, which is the capture/compare control register for Timer A. As you can see from the source code below, I'm setting it to 0x0F0. That's 0000 0000 0000 1111 in binary. Have a look at the MSP430 specs, and you'll see that we're setting the CCIFG, COV, OUT and CCI flags. From this setting, we'll make sure that our interrupt is raised when the timer has counted up to the value in TACCR0.
Check the interrupt vector table below and note that I've inserted "siren" as the entry for the Timer A interrupt. This means that for each time the counter reaches TACCR0, we'll be executing the code from the "siren" label.
Let's get to the source. Take the test.asm file from the previous example, clear it and paste the following:
.global main ; Constants/registers .set WDTCTL, 0x0120 ; watchdog control port .set WDTPW, 0x5A00 ; watchdog power .set WDTHOLD, 0x0080 ; watchdog hold .set P1DIR, 0x0022 ; direction of data bits on port P1 .set P1SEL, 0x026 .set P1OUT, 0x021 .set TACCR1, 0x0174 .set TACCR0, 0x0172 .set TACTL, 0x0160 ; Timer A is 0x0160 to 0x017F .set TACCTL1, 0x0164 ; Capture/compare control register .set DCOCTL, 0x056 ; DCO control register .set BCSCTL1, 0x058 ; Oscillator control register #2 ; Settings for Basic Clock Module .set RSEL0, 0x01 ; These bits select the frequency .set RSEL1, 0x02 ; range of the DCO .set RSEL2, 0x04 .set DCO0, 0x20 ; These bits set the fundamental frequency .set DCO1, 0x40 ; of the DCO, within the range defined by the .set DCO2, 0x80 ; RSEL bits above. .text main: dint mov #0x0280, r1 ; set stackpointer to top of RAM mov #WDTPW+WDTHOLD, &WDTCTL ; stop watchdog ; let's configure the basic clock module clr.b &DCOCTL ; set lowest DCOx and MODx settings bis.b #DCO2+DCO1+DCO0, &DCOCTL ; select max DCO Tap bis.b #RSEL0+RSEL1+RSEL2, &BCSCTL1 ; select range 7 ; we set red LED, keeping green LED cleared for now bis.b #0x01, &P1DIR bic.b #0x40, &P1DIR ; set P1.0 = TA0.1 (OUT1 is timer A's output) bis.b #0x01, &P1SEL ; we need to set CCR0, ; in our case to the highest possible value (all bits set in 16-bit value) mov #0xFFFF, &TACCR0 ; we need to source timer A from SMCLK, up mode. We counts up to the value in CCR0, ; and then it resets to zero. mov #0x0210, &TACTL ; Interrupt when timer has counted up to the value in CCR0 (see above) mov #0x0f0, &TACCTL1 ; enable interrupts and shut down cpu (LPM0 with interrupts enabled) eint bis #0x0018, r2 siren: bic.b #0x01, &TACCTL1 ; clear interrupt xor.b #0x01, &P1DIR ; toggle red xor.b #0x40, &P1DIR ; toggle green reti __noint: reti .section .vectors, "a" ; Set attribs CONTENTS, ALLOC, LOAD, READONLY, DATA .word __noint, __noint, __noint, __noint .word __noint, __noint, __noint, __noint .word siren, __noint, __noint, __noint .word __noint, __noint, __noint, main
As you may have noticed, our interrupt vector table contains a lot of references to __noint. That's good code-practice. If another interrupt is raised (for whatever reason, e.g. due to a bug), then we'll make sure that we run "reti" (return from interrupt) immediately, to handle it cleanly. This can often help with debugging, as sometimes you'd want to do something before the reti, and set a breakpoint to catch it.
You'll also notice that we're setting the CPU into LPM0 mode, aka "low power mode 0", and it simply means that we are only running the SMCLK and ACLK clocks on our chip. We've basically switched off the CPU. That's one of the cool features of the MSP, i.e. that you can enable the CPU (MCLK) when you need it, and you can even increase the clock frequency as needed as well for those computationally intensive moments, only to lower it shortly after to save battery.
Lastly, here is a video of the demo. I do sincerely apologize for the horrible video quality.
Compiling and deploying the app is the same procedure as before:
$ msp430-as -o test.o -mmsp430x2012 test.asm
$ msp430-ld -o test.elf -T test.map test.o
$ mspdebug rf2500 "prog test.elf"
[..snip snip..]
Programming...
Writing 82 bytes to f800...
Writing 32 bytes to ffe0...
$