Handling Interrupts and LPM in C language

Introduction

Well I’ve already written about handling interrupts in assembly language. Now I thought of writing about handling interrupts in C language. Since most of us are used to writing in C language, it is very important that one learns how to handle interrupts and write interrupt subroutines. Now first things first. Whatever you use be it C,C++ or assembly language, the controller understands only binary language. So when you burn the code its the hex file and nothing else that matters. Now the C language code is converted to assembly language which in turn is compiled to create the hex file. So one must know this fact to be able to use C language properly. In C language we can declare variables of various data types like char,int,etc. but when it comes to embedded C care must be taken. If your ALU size is 16 bit then the register size is 16 bit as well. So when you initialize a variable in C it is nothing but a register which is being initialized and used. Thus if you use double or any other type which is greater than the size of ALU you are bound to get error. Now to a C programmer it might seem absurd but if you know what is happening behind the scene its always easier to debug and understand what is happening. So if one knows assembly language then viewing the disassembly and debugging is easier.

Interrupts in C language

Handling interrupt is nothing but writing a code at a location whose starting address is written at the vector address of the interrupt being handled. In order to do this one must know the concept of #pragma vector. Now #pragma vector=Vector_Address is a directive just like ORG is in assembly language. What it tells the compiler is that the function that is being written next is a ISR and its starting address has to be stored at the Vector_Address. That being said, the function for ISR has a particular syntax which has to be followed.

Syntax for ISR function

__interrupt void function_name(void){}

(Note there are two underscores before the interrupt keyword). Now the function name can be anything except a keyword(which is quite obvious.). In the body of this function is what you write for handling the interrupt.

Syntax for writing a complete ISR

#pragma vector=Vector_Address

__interrupt void function_name(void)

{

// code for ISR

}

With that being covered what remains is the Vector_Address, one cannot type the vector address directly in hexadecimal value. In the msp430.h header file (rather see the device specific header file for e.g. msp430g2553.h ) there is a section for vector addresses where all vector addresses have been assigned a macro. I’ll paste the portion of header file concerning the topic at hand.

/************************************************************
* Interrupt Vectors (offset from 0xFFE0)
************************************************************/

#define VECTOR_NAME(name)       name##_ptr
#define EMIT_PRAGMA(x)          _Pragma(#x)
#define CREATE_VECTOR(name)     void (* const VECTOR_NAME(name))(void) = &name
#define PLACE_VECTOR(vector,section) EMIT_PRAGMA(DATA_SECTION(vector,section))
#define ISR_VECTOR(func,offset) CREATE_VECTOR(func); \
PLACE_VECTOR(VECTOR_NAME(func), offset)

#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define PORT1_VECTOR            “.int02”                     /* 0xFFE4 Port 1 */
#else
#define PORT1_VECTOR            (2 * 1u)                     /* 0xFFE4 Port 1 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define PORT2_VECTOR            “.int03”                     /* 0xFFE6 Port 2 */
#else
#define PORT2_VECTOR            (3 * 1u)                     /* 0xFFE6 Port 2 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define ADC10_VECTOR            “.int05”                     /* 0xFFEA ADC10 */
#else
#define ADC10_VECTOR            (5 * 1u)                     /* 0xFFEA ADC10 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define USCIAB0TX_VECTOR        “.int06”                     /* 0xFFEC USCI A0/B0 Transmit */
#else
#define USCIAB0TX_VECTOR        (6 * 1u)                     /* 0xFFEC USCI A0/B0 Transmit */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define USCIAB0RX_VECTOR        “.int07”                     /* 0xFFEE USCI A0/B0 Receive */
#else
#define USCIAB0RX_VECTOR        (7 * 1u)                     /* 0xFFEE USCI A0/B0 Receive */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define TIMER0_A1_VECTOR        “.int08”                     /* 0xFFF0 Timer0)A CC1, TA0 */
#else
#define TIMER0_A1_VECTOR        (8 * 1u)                     /* 0xFFF0 Timer0)A CC1, TA0 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define TIMER0_A0_VECTOR        “.int09”                     /* 0xFFF2 Timer0_A CC0 */
#else
#define TIMER0_A0_VECTOR        (9 * 1u)                     /* 0xFFF2 Timer0_A CC0 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define WDT_VECTOR              “.int10”                     /* 0xFFF4 Watchdog Timer */
#else
#define WDT_VECTOR              (10 * 1u)                    /* 0xFFF4 Watchdog Timer */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define COMPARATORA_VECTOR      “.int11”                     /* 0xFFF6 Comparator A */
#else
#define COMPARATORA_VECTOR      (11 * 1u)                    /* 0xFFF6 Comparator A */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define TIMER1_A1_VECTOR        “.int12”                     /* 0xFFF8 Timer1_A CC1-4, TA1 */
#else
#define TIMER1_A1_VECTOR        (12 * 1u)                    /* 0xFFF8 Timer1_A CC1-4, TA1 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define TIMER1_A0_VECTOR        “.int13”                     /* 0xFFFA Timer1_A CC0 */
#else
#define TIMER1_A0_VECTOR        (13 * 1u)                    /* 0xFFFA Timer1_A CC0 */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define NMI_VECTOR              “.int14”                     /* 0xFFFC Non-maskable */
#else
#define NMI_VECTOR              (14 * 1u)                    /* 0xFFFC Non-maskable */
#endif
#ifdef __ASM_HEADER__ /* Begin #defines for assembler */
#define RESET_VECTOR            “.reset”                     /* 0xFFFE Reset [Highest Priority] */
#else
#define RESET_VECTOR            (15 * 1u)                    /* 0xFFFE Reset [Highest Priority] */
#endif

/************************************************************
* End of Modules
************************************************************/

As you can see there is macro for each vector address of the controller. You have to use the macro, I tried using the “.int08” value but it gave error. If you find out any other way please share it with me. One can’t have enough knowledge.

So we covered the interrupt handling in C part. Now let’s see the LPM part.

LPM in C language

Well entering into LPM mode in assembly language is a piece of cake. Just set the required bits of SR and its done. When I first tried to enter into LPM in C language I wrote SR |= LPM3|GIE and I got an error. I remembered that in C we can’t use the registers directly. So I had to see an example project given in the MSP430ware. There I came across this BIS instruction equivalent function.

_BIS_SR(bits_to_be_set);

This is a function which will write BIS.W #bits_to_be_set,SR in the disassembly. So just write the bits you want to set in this function and you are ready to enter into LPM.

Example

_BIS_SR(LPM3_bits + GIE);

Program to demonstrate above concepts

/*
*  inception.c
*  Created on    : 24-Nov-2013 10:59:50 AM
*  Author        : Manpreet Singh Minhas
*  Website       : https://learningmsp430.wordpress.com
*
*  Description   : In this program we toggle the SMD LED1 after one 
*                  second delay.We use timer in up mode. I’ve enabled the TAIE so
*                  that we get an interrupt on roll over.Also we have to set GIE 
*                  bit in SR. And I’ve used LPM3 for this, since I need only the
*                  ACLK for this. (Note: The 32KHz crystal needs to be soldered 
*                  on the Launchpad.) 
*/

#include <msp430g2553.h>

void main(void)
{
WDTCTL = WDTPW|WDTHOLD;
P1DIR |= BIT0;
P1OUT |= BIT0;
TA0CCR0 = 0x8000;
TA0CTL = TASSEL_1|ID_0|MC_1|TAIE;

_BIS_SR(LPM3_bits + GIE);

}

#pragma vector=TIMER0_A1_VECTOR
__interrupt void Timer_A(void)
{
P1OUT ^= BIT0;
TA0CTL &= ~(TAIFG);
}

So I’ll be ending this post here. Hope you found it useful. Thank you for reading and as always you can ask me any doubts you have. This will help me gain new knowledge and perspective.

Advertisements

Interrupt concept and Low Power Modes (LPM)

Interrupts

I had  written about hardware interrupts earlier. Now I’ll write about interrupts in general. How to write program to enable interrupts and handle those. Basically there are two types of interrupts.

  • Hardware Interrupts
  • Software Interrupts

As the name suggests hardware interrupts are given on the pins of the controller. An example of this can be found in by blog post.

Software interrupts on the other hand are made by using software. And these are vectored interrupts i.e. the program counter is loaded with the contents of vector address of the interrupt that occurred. In fact all interrupts are vectored. All peripherals can generate an interrupt specific to their operation.

I’ll be taking the example of TimerA interrupt . But lets see a little theory about how to handle interrupts.

First I’ll tell you what is maskable and non-maskable interrupt.  Maskable interrupts can be disabled using software and non-maskable interrupt cannot be disabled. RST is a non-maskable interrupt. Now masking is done to avoid accidental triggering of the interrupt which can distort the program flow.

All the maskable interrupts have to be enabled. This is done by setting the GIE(General Interrupt Enable) bit of status register. So if you include the msp430.h file you can use the GIE macro defined in the header file and OR(bitwise) it with status register(SR), to enable the interrupts by setting that bit.

Now finally let’s see what happens when an interrupt occurs.

interrupt_acceptance 

So when an interrupt occurs the PC and SR contents are pushed onto the stack after the current instruction is executed. This is known as context saving. Depending on priority the interrupt with highest priority is processed first. Thus the PC is loaded with the contents of the vector address and context switching takes place.

returning_from_interrupts

The image above shows how to return from an Interrupt Sub Routine (ISR). An ISR is nothing but the code you write for handling the interrupt. The address of this ISR is stored at the vector address of the interrupt you want to handle.

One has to use “RETI” instruction to return to the main program. What RETI does is it pops the PC and SR contents back from the stack. Thus original state of program is restored.

Interrupt Vector Addresses

Now these are device specific. So one has to refer datasheet of the required microcontroller for this. Since I’ll be using msp430g2553 for this I’ll post the vector address table for the device.

vector_address_table

That is that.

Low Power Modes(LPM) of Operating modes of msp430

Now-a-days its all about reducing the power consumption. And Texas Instruments msp430 is a low power microcontroller. That means that there must be low power modes. Those are nothing but LPM. These are selected by setting or resetting the bits of SR. These modes are well explained in the user guide. I’ll post the page concerning these modes.

modes

All the SCG1,SCG0,OSCOFF,CPUOFF are bits of the status register(SR). You can use the bits defined in the header file for setting these bits. I’ll explain more about this after the program.

Now when you enter a LPM. You can come out of it when there is an interrupt. Now we know that PC and SR are pushed onto the stack. So if you want to come out of the LPM on exit from ISR you need to change the SR contents pushed onto the stack. The process is given in the user guide.

entering_exit

The bic instruction is used to clear the corresponding bits of SR that are used for controlling LPM.

In LPM basically we shut the oscillators which consume the maximum power. Thus depending on your need you can shut down the ACLK,SMCLK,MCLK and DCO.

Program

;Name             : Manpreet Singh Minhas
;Date                 : 22 Nov 2013
;Website        : https://learningmsp430.wordpress.com/
;Software        : IAR Embedded Workbench

Aim                  : To blink an LED by using TimerA interrupt and use msp430 in LPM.
#include “msp430g2553.h”     ; Include the header file
ORG 0FFFEH                                                                    ; Power On Reset Vector address
        DW MAIN                                                                    ; Here MAIN is nothing but a macro for 0C000H
                                                                                             ; memory location. You might as well write
                                                                                             ; 0C000h. We try to make the code as reader
                                                                                             ; friendly as possible, that is why MAIN macro.

                               
ORG 0C000H                                                                 ; Starting address of FLASH MEMORY in
                                                                                          ; msp430g2553 where the program will be burnt.
MAIN:        MOV.W #WDTPW|WDTHOLD,&WDTCTL  ; Stop watchdog timer
                  MOV.W #03FFH,SP                                         ; Initialize stack pointer to top of
                  MOV.B #BIT2,&P1DIR                                             ; RAM. You’ll have to use device’s
                  MOV.W #08000H,&TA0CCR0                               ; datasheet for finding this address.
                  BIC.B #BIT2,&P1OUT                                            
                  MOV.W #TASSEL_1|ID_0|MC_1|TAIE,&TA0CTL ; Initialize timer
UP:           MOV.W #LPM3|GIE,SR                                             ; Enable interrupts and enter LPM3
                 JMP UP
TMR_ISR:    XOR.B #BIT2,&P1OUT                                         ; Toggle LED
                      BIC.W #TAIFG,&TA0CTL                                      ; Clear the interrupt flag bit
                      RETI
       
ORG 0FFF0H
        DW TMR_ISR
END

The above code will make led connected P1.2 to blink after 1 sec delay. You can change the BIT2 to BIT0 or BIT6 if you don’t want to use an external LED. I’ve used TimerA in up mode. And I’m using ACLK for this part. I’ve connected a 32kHz crystal there.  So you need to connect it there by soldering. Then it’s given in the user manual that when the count goes from TA0CCR0 value to 0, the TAIFG flag is set. I’ve enabled the interrupts in SR by setting the GIE bit, also you need to enable it in the peripheral’s control register. I’ve enabled the TimerA interrupt by setting TAIE bit of TA0CTL. Now it’s vital to clear the interrupt flag of the interrupt that you are handling. In this case the TAIFG flag in TA0CTL register. So that we can acknowledge the next interrupt. Its easy to forget this so just beware. And regarding the LPM3 macro that I’ve used it just sets the corresponding bits of SR. The definition is:

#define LPM3                (SCG1+SCG0+CPUOFF)

So either you can write LPM3 or the corresponding SR bits. And instead of using mov instruction you can use bis instruction as well.

<

p>If any doubts feel free to ask. Thank you for reading this. Hope this was useful and informative.