TK

5Jun/110

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...
$