MSP430m05 INTS
MSP430m05 INTS
Introduction
What is an embedded system without interrupts?
If you just needed to solve a math problem you would most likely sit down and use a desktop
computer. Embedded systems, on the other hand, take inputs from real-world events and then
act upon them. These real-world events usually translate into ‘interrupts’ – asynchronous signals
provided to the microcontroller: timers, serial ports, pushbuttons … and so on.
This chapter discusses how interrupts work; how they are implemented on the MSP430 MCU,
and what code we need to write in order to harness their functionality. The lab exercises provided
are relatively simple (using a pushbutton to generate an interrupt), but the skills we learn here will
apply to all the remaining chapters of this workshop.
Learning Objectives
Objectives
Chapter Topics
Interrupts ..................................................................................................................................... 5-1
Interrupts, The Big Picture ........................................................................................................ 5-3
Polling vs Interrupts............................................................................................................... 5-3
Processor States and Interrupts ........................................................................................... 5-5
Threads: Foreground and Background ................................................................................. 5-6
How Interrupts Work ................................................................................................................. 5-7
1. Interrupt Must Occur ......................................................................................................... 5-9
2. Interrupt is Flagged (and must be Enabled) ................................................................... 5-10
3. CPU's Hardware Response ............................................................................................ 5-12
4. Your Software ISR .......................................................................................................... 5-14
Interrupts: Priorities & Vectors ............................................................................................... 5-17
Interrupts and Priorities ....................................................................................................... 5-17
Interrupt Vector (IV) Registers ............................................................................................ 5-18
Interrupt Vector Table ......................................................................................................... 5-19
Coding Interrupts..................................................................................................................... 5-22
Dedicated ISR (Interrupt Service Routine).......................................................................... 5-22
Grouped ISR (Interrupt Service Routine) ............................................................................ 5-24
Enabling Interrupts .............................................................................................................. 5-26
Miscellaneous Topics .............................................................................................................. 5-28
Handling Unused Interrupts ................................................................................................ 5-28
Interrupt Service Routines – Coding Suggestions .............................................................. 5-29
GPIO Interrupt Summary .................................................................................................... 5-30
Interrupt Processing Flow ................................................................................................... 5-30
Interrupts and TI-RTOS Scheduling........................................................................................ 5-31
Threads – Foreground and Background ............................................................................. 5-31
TI-RTOS Thread Types....................................................................................................... 5-33
Summary: TI-RTOS Kernel ................................................................................................. 5-36
Lab Exercise ........................................................................................................................... 5-37
From Wikipedia:
A hardware interrupt is an electronic alerting signal sent to the processor from an external device,
either a part of the [device, such as an internal peripheral] or an external peripheral.
In other words, the interrupt is a signal which notifies the CPU that an event has occurred. If the
interrupt is configured, the CPU will respond to it immediately – as described later in this chapter.
Polling vs Interrupts
In reality, though, there are two methods that events can be recognized by the processor. One is
called “Polling”; the other is what we just defined, “Interrupts”.
We start with a non-engineering analogy for these two methods. If you’ve ever taken a long family
vacation, you’ve probably dealt with the “Are we there yet” question. In fact, kids often ask it over-
and-over again. Eventually … the answer will be, “Yes, we’re there”. The alternative method is
when my spouse says, “Wake me up when we get there”.
Poooing Interrupts
Wane me up when we get there...
Both methods signal that we have arrived at our destination. In most cases, though, the use of
Interrupts tends to be much more efficient. For example, in the case of the MSP430, we often
want to sleep the processor while waiting for an event. When the event happens and signals us
with an interrupt, we can wake up, handle the event and then return to sleep waiting for the next
event.
A real-world event might be our system responding to a push-button. Once again, the event could
be handled using either Polling or Interrupts.
It is common to see “simple” example code utilize Polling. As you can see from the left-side
example below, this can simply consist of a while{} loop that keeps repeating until a button-push
is detected. The big downfall here, though, is that the processor is constantly running– asking the
question, “Has the button been pushed, yet?”
Polling Interrupts
while(1) { // GPIO button interrupt
// Polling GPIO button #pragma vector=PORT1_VECTOR
while (GPIO_getInputPinValue()==1) __interrupt void rx (void){
GPIO_toggleOutputOnPin(); GPIO_toggleOutputOnPin();
} }
The example on the right shows an Interrupt based solution. Since this code is not constantly
running, as in the previous example’s while{} loop, the CPU load is very low.
Why do simple examples often ignore the use of interrupts? Because they are “simple”.
Interrupts, on the other hand, require an extra three items to get them running. We show two of
them in the right-hand example above.
• The #pragma sets up the interrupt vector. The MSP430 has a handy pragma which makes it
easy to configure this item. (Note: we’ll cover the details of all these items later in this
chapter.)
• The __interrupt keyword tells the compiler to code this function as an interrupt service routine
(ISR). Interrupt functions require a context save and restore of any resources used within
them.
While not shown above, we thought we’d mention the third item needed to get interrupts to work.
For a CPU to respond to an interrupt, you also need to enable the interrupt. (Oh, and you may
also have to setup the interrupt source; for example, we would have to configure our GPIO pin to
be used as an interrupt input.)
So, in this chapter we leave the simple and inefficient examples behind and move to the real-
world – where real-world embedded systems thrive on interrupts.
To help demonstrate this point, we stole the following slide from a discussion about Capacitive
Touch. While most of this slide’s content is not important for our current topic, we thought the
current vs time graph was interesting. It tries to visually demonstrate the changing states of the
device by charting power usage over time.
If you look at the “code” below, you will see that there are three individual – and independent –
code segments below: main, ISR1, and ISR2.
We use the word independent because, if you were to examine the code in such a system, there
are no calls between these three routines. Each one begins and ends execution without calling
the others. It is common to call these separate segments of code: “Threads”.
As we’ve seen in the workshop already, it is our main() thread that begins running once the processor has
been started. The compiler’s initialization routine calls main() when its work is done. (In fact, this is why all C
programs start with a main() function. Every compiler works the same way, in this regard.)
With the main() thread started, since it is coded with a while(1) loop, it will keep running forever. That is,
unless a hardware interrupt occurs.
When an enabled interrupt is received by the CPU, it preempts the main() thread and runs the associated
ISR routine – for example, ISR1. In other words, the CPU stops running main() temporarily and runs ISR1;
when ISR1 completes execution, the CPU goes back to running main().
Here’s where the terms Foreground and Background come into play. We call main() the Background
thread since it is our “default” thread; that is, the program is designed such that we start running main() and
go back to it whenever we’re done with our other threads, such as ISR1.
It should be noted that it’s important to keep your interrupt service routines short and quick. This, again, is
common practice for embedded systems.
Note: We realize that our earlier definition of “Thread” was a little weak. What we said was true, but not complete. The
author’s favorite definition for “Thread” is as follows:
“A function or set of functions that operate independently of other code – running within their own context.”
The key addtion here is that a thread runs within its own context. When switching from one thread to another, the
context (register values and other resources) must be saved and restored.
If you’ve been reading this chapter, you might notice that we’ve already covered these four items.
Over the next few pages we enumerate these steps again, filling-in additional details.
Notes
Suffice it to say that most peripherals can generate interrupts to provide status and information to
the CPU. Most often, the interrupt indicates that data is available (e.g. serial port) and/or an event
has occurred that needs processing (e.g. timer). In some cases, though, an interrupt may be used
to indicate an error or exception in a peripheral that the CPU needs to handle.
Interrupts can also be generated from GPIO pins. This is how an external peripheral, or some
other controller, can signal the MSP430 CPU. Most MSP430 devices allow the pins from the first
two I/O ports (P1 and P2) to be individually configured for interrupt inputs. On the larger devices,
there may be additional ports that can be configured for this, as well.
Finally, your software can often generate interrupts. The logic for some interrupts on the
processor allow you to manually set a flag bit, thus ‘emulating’ a hardware interrupt. Not all
interrupts provide this feature, but when available, it can be a handy way to test your interrupt
service routine.
MSP430 devices are designed with “distributed” interrupt management. That is, most IFG bits are
found inside each peripheral’s control registers; this is different from most processors which have
a common, dedicated set of interrupt registers.
The distributed nature of the interrupts provides a number of benefits in terms of device flexibility
and future feature expansion; further, it fits nicely with the low-power nature of the MSP430.
The only ‘negative’ of distributed interrupts might be that it’s different — it’s just that many of us
older engineers are used to seeing all the interrupts grouped together. Bottom line, though, is that
working with interrupts (enabling interrupts, clearing flags, responding to them) is the same
whether the hardware is laid out centrally or in a distributed fashion.
Interrupt Flow
How does the interrupt signal reach the CPU?
We’ve just talked about the interrupt flag (IFG) bit – let’s start there. As described on the previous page,
when the interrupt source signal is received, the associated IFG bit is set. In fact, DriverLib contains
functions to read the status of most IFG bits. (Handy in those few cases where you need to poll an interrupt
source.)
When the IFG is set, the MSP430 device now sees that the signal has occurred, but the signal hasn’t made
its way to the CPU, yet. For that to happen, the interrupt must be enabled.
Interrupt Flow
IFG bit IE bit SR.GIE
Interrupt Interrupt “Individual” “Global”
Source ‘Flag’ Int Enable Int Enable
GPIO 0
TIMER_A 1 FPU
… 0
NMI 0
Interrupt enable bits (IE) exist to protect the CPU … and thus, your program. Even with so many peripherals
and interrupt sources, it’s likely that your program will only care about a few of them. The enable bits provide
your program with ‘switches’ that let you ignore all those sources you don’t need.
By default, all interrupt bits are disabled (except the Watchdog Timer). It is your program’s responsibility to
enable those interrupt sources that are needed. To that end, once again, DriverLib provides a set of
functions that make it easy for you to set the necessary IE bits.
Finally, there’s a “master” switch that turns all interrupts off. This lets you turn off interrupts without having to
modify all of the individual IE bits. The MSP430 calls this the global interrupt enable (GIE). It is found in the
MSP430 Status Register (SR).
Why would you need a GIE bit? Sometimes your program may need to complete some code atomically; that
is, your program may need to complete a section of code without the fear that an interrupt could preempt it.
For example, if your program shares a global variable between two threads – say between main() and an
ISR – it may be important to prevent interrupts while the main code reads and modifies that variable.
Note: There are a few non-maskable interrupts (NMI). These sources bypass the GIE bit. These
interrupts are often considered critical events – i.e. ‘fatal’ events – that could be used to provide a
warm reset of the CPU.
Earlier in the chapter we stated: “The interrupt preempts the current thread and starts running the
interrupt service routine (ISR).” While this is true, there are actually a number of items performed
by the hardware to make this happen – as shown below:
We hope the first 3 items are self-explanatory; the current instruction is completed while the
Program Counter (PC) and Status Register (SR) are written to the system stack. (You might
remember, the stack was setup for the MSP430 by the compiler’s initialization routine. Please
refer to the compiler user’s guide for more information.)
After saving the context of SR, the interrupt hardware in the CPU clears most of the SR bits. Most
significantly, it clears GIE. That means that by default, whenever you enter an ISR function, all
maskable interrupts have been turned off. (We’ll address the topic of ‘nesting’ interrupts in the
next section.)
The final 3 items basically tell us that the processor figures out which interrupt occurred and calls
the associated interrupt service routine; it also clears the interrupt flag bit (if it’s a dedicated
interrupt). The processor knows which ISR to run because each interrupt (IFG) is associated with
an ISR function via a look-up table – called the Interrupt Vector Table.
Note: We’ll describe Interrupt Vector Table in more detail later in the chapter.
Before we examine the details of the ISR; once again, how did we get to this point?
Looking at the diagram below, we can see that (1) the interrupt must have occurred; (2) the processor
flags the incoming interrupt; (3) if enabled, the interrupt flag signal is routed to the CPU where it saves
the Status Register and Return-to address and then branches to the ISR’s address found in the
appropriate location in the vector table. (4) Finally, your ISR is executed.
The crux of the ISR is doing what needs to be done in response to the interrupt; the 4th bullet
(listed in red) reads:
• Run your interrupt’s code
This is meant to describe the code you write to handle the interrupt. For example, if it’s a UART
interrupt, your code might read an incoming byte of data and write it to memory.
The 3rd bullet indicates that if this is a “grouped” interrupt, you have to add code to figure out
which interrupt, in the group, needs to be handled. This is usually done by reading the group’s IV
register. (This bullet was in red because it is code you need to write.)
The other bullets listed under “4. ISR” are related to saving and restoring the context of the
system. This is required so that the condition mentioned earlier can be met: “without adversely
affecting the code threads already running in the system.”
We show the interrupt flow in a slightly different fashion in the following diagram. As you can see,
when an enabled interrupt occurs, the processor will look up the ISR’s branch-to address from a
specific address in memory (called the interrupt vector). For the MSP430, this address is defined
using the vector pragma.
#pragma vector=WDT_VECTOR
Using Interrupt Keyword interrupt myISR(void){
Compiler handles context save/restore • {ave context of system
Call a function? Then full context is saved • (optional) Re-enable interrupts
No arguments, no return values • *If group INT, read assoc IV Reg
(determines source & clears ICD)
You cannot call any TI-RTO{ scheduler
functions (e.g. {wi_post) • Run your interrupt’s code
Nesting interrupts is aANUAL • Restore context of system
• Continue where it left off (RETI)
}
The context of the system – for example, the CPU registers used by the ISR – must be saved
before running your code and restored afterwards. Thankfully, the compiler handles this for you
when the function is declared as an interrupt. (As part of the “context restore”, the compiler will
return to running the previous thread of code by using the RETI instruction).
Please note the bullets under “Using the Interrupt Keyword” from the preceding diagram.
Using this keyword, the compiler handles all of the context save/restore for you and knows how to
return to your previous code – even restoring the original value for the Status Register (SR).
Hint: If you call a function within your ISR, the compiler will have to save/restore every CPU
register, not just the ones that it uses to implement your C code. This is because it
doesn’t know what resources the function call may end up using.
Since the interrupt occurs asynchronously to the background thread, you cannot pass arguments
to and receive return values from the ISR. You must communicate between threads using global
variables (or other appropriate data objects).
TI’s real-time operating system (TI-RTOS) provides a rich set of scheduling functions that are
often used within interrupt service routines. Be aware, though, that some of these functions can
only be used with RTOS “managed” interrupts. In fact, it’s actually easier to let TI-RTOS manage
your interrupts; it automatically handles plugging the interrupt vector as well as context
save/restore. (All you have to do is write a standard C function.) But, the details of TI-RTOS are
outside the scope of this workshop. While we provide a brief discussion of TI-RTOS at the end of
this chapter, please refer to the Introduction to TI-RTOS Kernel workshop for more details.
Hint: We encourage you to avoid nesting, if at all possible. Not only is it difficult, and error
prone, it often complicates your programs ability to reach low-power modes.
In the previous paragraph we used the phrase “pending priority” deliberately. As you might
remember from the last topic in this chapter, interrupts on the MSP430 do not nest within each
other by default. This is because the global interrupt (GIE) bit is disabled when the CPU
acknowledges and processes an interrupt. Therefore, if an interrupt occurs while an ISR is being
executed, it will have to wait for the current ISR to finish before it can be handled … even if the
new interrupt is of higher priority.
On the other hand, if two interrupts occur at the same time – that is, if there are two interrupts
currently pending – then the highest priority interrupt is acknowledged and handled first.
Most of the 23 interrupts on the ‘F5529 represent ‘groups’ of interrupts. There are actually 145
interrupt sources – each with their own interrupt flag (IFG) – that map into these 23 interrupts.
For example, the “Timer B (CCIFG0)” interrupt represents a single interrupt signal. When the
CPU acknowledges it, it will clear its single IFG flag.
On the other hand, the next interrupt in line, the “Timer B” interrupt, represents all the rest of the
interrupts that can be initiated by Timer0_B. When any one of the interrupts in this group occurs,
the ISR will need to determine which specific interrupt source occurred and clear its flag (along
with executing whatever code you want to associate with it).
For grouped interrupts, most users read the IV register at the beginning of the ISR and use the
return value to pick the appropriate code to run. This is usually implemented with a Switch/Case
statement. (We will explore an example of this code later in the chapter.)
Additionally, the first 3 rows (highlighted with red background fill) indicate that these interrupt
groups are non-maskable; therefore, they bypass the GIE bit.
Memory Map
InPerrupP VecPors & PrioriPies (F5529)
INT Source IV Register Vector Address Loc’n Priority
SysPem ReseP SYSRSTIV RESET_VECTOR 63 high
SysPem NMI SYSSNIV SYSNMI_VECTOR 62 Flash (128K)
User NMI SYSUNIV UNMI_VECTOR 61
ComparaPor CBIV COMP_B_VECTOR 60
Timer B (CCIFG0) CCIFG0 TIMER0_B0_VECTOR 59 0xFFFF
Timer B TB0IV TIMER0_B1_VECTOR 58 INT VecPors (80)
WDT InPerval Timer WDTIFG WDT_VECTOR 57
Serial PorP (A) UCA0IV USCI_A0_VECTOR 56 RAM (8K)
Serial PorP (B) UCB0IV USCI_B0_VECTOR 55
USB RAM (2K)
A/D ConverPor ADC12IV ADC12_VECTOR 54
Info Memory (512)
GPIO (PorP 1) P1IV PORT1_VECTOR 47 BooP Loader (2K)
The final column in the above diagram hints at the location of each interrupts address vector in
the memory map. For example, when using the WDT as an interval timer, you would put the
address of your appropriate ISR into location “57”. As we saw in a previous topic, this can easily
be done using the vector pragma.
The MSP430 devices reserve the range 0xFFFF to 0xFF80 for the interrupt vectors. This means
that for the ‘F5529, the address for the System Reset interrupt service routine will sit at addresses
0xFFFE – 0xFFFF. (A 16-bit address requires two 8-bit memory locations.) The remaining
interrupt vectors step down in memory from this point. The map to the right of the table shows
where the interrupt vectors appear within the full MSP430 memory map.
Here’s a quick look at the same table showing the MSP430FR5969 interrupt vectors and
priorities. The list is very similar to the ‘F5529; the main differences stem from the fact that the
two devices have a slightly different mix of peripherals.
The preceding interrupt tables were re-drawn to make them easier to view when projected during
a workshop. The following slide was captured from ‘F5529 datasheet. This is what you will see if
you examine the MSP430 documentation.
Each device’s datasheet provides a similar vector table listing. If you are using the ‘G2553 or
‘FR5969 devices, for example, you will find a similar table in each of their respective datasheets.
Coding Interrupts
As previously discussed, the code within your interrupt service routine will vary slightly based on
whether it handles a dedicated, single interrupt or if it handles a grouped interrupt. We will cover
both cases; starting with the easier, dedicated case.
The watchdog interrupt flag vector (WDTIFG) is a dedicated interrupt; therefore, your ISR code
only needs to respond to the single interrupt condition. Additionally, because it is a dedicated
interrupt, the CPU hardware automatically clears the WDTIFG bit when responding to the
interrupt and branching to your ISR.
When writing an ISR for dedicated interrupts, you code must address three items:
1. Put the ISR address into the vector table (using the vector #pragma)
2. Save/Restore the CPU context (using the __interrupt keyword)
3. Write your interrupt handler code (in other words, “Do what needs doing”)
We will use the following code example to demonstrate these three items.
tells the compiler to associate the function (on the following line) with the WDT_VECTOR.
Looking in the MSP430F5529 device-specific linker command file, you should find this vector
name (“WDT_VECTOR”) associated with vector #57. This matches with the datasheet
documentation we looked at earlier in the chapter.
Don’t forget, functions using the __interrupt keyword cannot accept arguments or return values.
Hint: Empirical analysis shows that “__interrupt” and “interrupt” are both accepted by the
compiler.
As we briefly mentioned earlier in the chapter (and will discuss in full detail in a later chapter), the
Timer_A and Timer_B peripherals are provided with two interrupts. For example, when looking at
Timer0_A5, there is a dedicated interrupt for TA0CCR0 (which stands for Timer0_A
Capture/Compare Register 0). Notice below how this is routed directly to the GIE input mux.
The remaining five Timer0_A5 interrupts are logically AND’d together; this combination provides a
second interrupt signal from Timer0_A5 to the GIE input mux.
)
1 1
INT – simplifies ISR
TA0CCR1 IFG auto cleared
0 1 0 0 0
0 1 0 0 1
TA0CCR2
TA0CCR3 52
TA0CCR4 TA0IV
TA0CTL
)
Bit 0
0 1 0 0 0 1 1 1
0 1 0 0 1 1 0 0
Bit 1
Bit 2
Bit 3 47
Bit 4 Reading P1IV
Bit 5 Example: returns highest
Bit 6 Interrupts on priority interrupt
Bit 7 pin 1 and 5 and clears it’s
IFG Nit
This diagram also shows that all of the input pins for GPIO port 1 (P1) share a single, grouped
interrupt. This means your GPIO ISR must always verify which pin actually caused an interrupt
whenever the ISR is executed.
The interrupt logic within the CPU recognizes each of these interrupt sources, therefore:
• If the first interrupt (TA0CCR0) occurs, it will cause the code at vector address 53
(TIMER_A0_VECTOR) to be executed.
• Similarly, the remaining Timer0 interrupts are associated with vector 52.
• Finally, the GPIO port (P1) was assigned (by the chip designer) to vector 47.
For grouped interrupts, though, we also need to determine which specific source caused the CPU
to be interrupted. As we’ve described, the Interrupt Vector (IV) register is an easy way to
determine the highest-priority, pending interrupt source. In the case of GPIO port 1, we would
read the P1IV register.
It’s common to see the IV register read within the context of a switch statement. In the above
case, if the P1IV register returns “6”, it means that pin 2 was our highest-priority, enabled
interrupt on Port 1; therefore, its case statement is executed. (Note, the return values for each IV
register are detailed in the F5xx device Users Guide and the F5xx DriverLib User’s Guide. You
will find similar documentation for all MSP430 devices..)
If our program was using Pin 2 on Port 1, you should see the code for case 0x06 executed if the
GPIO interrupt occurs.
By the way, there are two items in the above code example which help the compiler to produce
better, more optimized, code. While these intrinsic functions are not specific to interrupt
processing, they are useful in creating optimized ISR’s.
• The __even_in_range() intrinsic function provides the compiler a bounded range to evaluate.
In other words, this function tells the compiler to only worry about even results that are lower
or equal to 10.
• Likewise the _never_executed() intrinsic tells the compiler that, in this case, “default” will
never occur.
Enabling Interrupts
Earlier in the chapter we learned that for the CPU to recognize an interrupt two enable bits must
be set:
• Individual Enable – one IE bit for each interrupt source
• Global Interrupt Enable – GIE is a common “master” enable bit for all interrupts (except
those defined as non-maskable)
In the example below we show the code required to setup a GPIO pin as an interrupt. We chose
to enable the interrupt, as well as configuring the other GPIO pins, in a function called initGPIO();
implementing your code in this way is not required, but it’s how we decided to organize our code.
The key DriverLib function which enables the external interrupt is:
GPIO_enableInterrupt()
You will find that most of the MSP430ware DriverLib interrupt enable functions take a similar
form: <module>_enableInterrupt().
• GPIO_clearInterruptFlag() clears the IFG bit associated with our pin (e.g. P1.1). This is not
required but is commonly used right before a call to “enable” an interrupt. You would clear the
IFG before setting IE when you want to ignore any prior interrupt event; in other words, clear
the flag first if you only care about interrupts that will occur now – or in the future.
Finally, once you have enabled each individual interrupt, the global interrupt needs to be enabled.
This can be done in a variety of ways. The two most common methods utilize compiler intrinsic
functions:
• __bis_SR_register(GIE) instructs the compiler to set the GIE bit in the Status Register
− bis = bit set
− SR = Status Register
− GIE = which bit to set in the SR
• __enable_interrupts(void) tells the compiler to enable interrupts. The compiler uses the
EINT assembly instruction which pokes 1 into the GIE bit.
A better answer, as seen in our code example, is “right before the while{} loop”.
Conceptually, the main() function for most embedded systems consists of two parts:
• Setup
• Loop
That is, the first part of the main() function is where we tend to setup our I/O, peripherals, and other
system hardware. In our example, we setup the watchdog timer, power management, GPIO, and
finally the system clocks.
The second part of main() usually involves an infinite loop – in our example, we coded this with an
endless while{} loop. An infinite loop is found in almost all embedded systems since we want to run
forever after the power is turned on.
The most common place to enable interrupts globally (i.e. setting GIE) is right between these two
parts of main(). Looking at the previous code example, this is right where we placed our function that
sets GIE.
As a product example, think of the A/C power adaptor you use to charge your computer; most of
these, today, utilize an inexpensive microcontroller to manage them. (In fact, the MSP430 is very
popular for this type of application.) When you plug in your power adapter, we’re guessing that you
would like it to run as long as it’s plugged in. In fact, this is what happens; once plugged in, the first
part of main() sets up the required hardware and then enters an endless loop which controls the
adaptor. What makes the MSP430 such a good fit for this application is: (1) it’s inexpensive; and (2)
when a load is not present and nothing needs to be charged, it can turn off the external charging
components and put itself to sleep – until a load is inserted and wakes the processor back up.
Miscellaneous Topics
Handling Unused Interrupts
While you are not required to provide interrupt vectors – or ISR’s – for every CPU interrupt, it’s
considered good programming practice to do so. To this end, the MSP430 compiler issues a
warning whenever there are “unhandled” interrupts.
The following code is an example that you can include in all your projects. Then, as you
implement an interrupt and write an ISR for it, just comment the associated #pragma line from
this file.
#pragma vector=ADC12_VECTOR
#pragma vector=COMP_B_VECTOR
#pragma vector=DMA_VECTOR
#pragma vector=PORT1_VECTOR
...
#pragma vector=TIMER1_A1_VECTOR
#pragma vector=TIMER2_A0_VECTOR
#pragma vector=TIMER2_A1_VECTOR
#pragma vector=UNMI_VECTOR
#pragma vector=USB_UBM_VECTOR
#pragma vector=WDT_VECTOR
__interrupt void UNUSED_HWI_ISR (void)
{
__no_operation();
}
Note: The TI code generation tools distinguish between “warnings” and “errors”. Both represent
issues found during compilation and build, but whereas a warning is issued and code
building continues … when an error is encountered, an error statement is issued and the
tools stop before creating a final executable.
Do not call interrupt handling functions directly (Rather, write to IFG bit)
Interrupts can be handled directly with C/C++ functions using the interrupt
keyword or pragma
… Conversely, the TI-RTOS kernel easily manages Hwi context
Calling functions in an ISR
If a C/C++ interrupt routine doesn’t call other functions, usually, only those
registers that the interrupt handler uses are saved and restored.
However, if a C/C++ interrupt routine does call other functions, the routine saves
all the save-on-call registers if any other functions are called
Why? The compiler doesn’t know what registers could be used by a nested
function. It’s safer for the compiler to go ahead and save them all.
Re-enable interrupts? (Nesting ISR’s)
DON’T – it’s not recommended – better that ISR’s are “lean & mean”
If you do, change IE masking before re-enabling interrupts
Disable interrupts before restoring context and returning (RETI re-enables int’s)
We wrote the last bullet, regarding reentrancy, in a humorous fashion. That said, it speaks to an
important point. If you decide to enable interrupt nesting, you need to be careful that you either
prevent reentrancy - or that your code is capable of reentrancy.
This type of program/system error can be very difficult to debug (i.e. find and fix). This is
especially true if you call functions within your interrupt service routines. For example, the C
language’s malloc() function is not reentrant. If you were to call this function from an ISR and it
was interrupted, and then it is called again by another ISR, your system would most likely fail –
and fail in a way that might be very difficult to detect.
There are other devices in the MSP430 family that support interrupts on more than 2 ports, but of
the three example processors we use throughout this course, only the FR5969 (FRAM) devices
support interrupt inputs on additional ports (P3 and P4).
Interrupt Processing
Item1 Prior to ISR
Item2 SP
The Texas Instruments RTOS (TI-RTOS) – also known as SYS/BIOS – provides many functions
that you can use within your program; for example, the TI-RTOS kernel includes: Scheduling,
Instrumentation, and Memory Management. You can choose which parts of TI-RTOS are needed
and discard the rest (to saves memory).
Think of TI-RTOS as a library and toolset to help you build and maintain robust systems. If you’re
doing just “one” thing, it’s probably overkill. As you end up implementing more and more
functionality in your system, though, the tools and code will save you time and headaches.
The only part of TI-RTOS discussed in this chapter is “Scheduling”. We talk about this because it
is very much related to the topics covered throughout this chapter – interrupts and threads. In
many cases, if you’re using an RTOS, it will manage much of the interrupt processing for you; it
will also provide additional options for handling interrupts – such as post-processing of interrupts.
As a final note, we will only touch on the topics of scheduling and RTOS’s. TI provides a 2-day
workshop where you can learn all the details of the TI-RTOS kernel. You can view a video
version of the TI-RTOS course or take one live. Please check out the following wiki page for more
information:
http://processors.wiki.ti.com/index.php/Introduction_to_the_TI-RTOS_Kernel_Workshop
What is a Thread?
We all know what a function() is…
main() { A thread is a function that runs
init code within a specific context; e.g.
triority
Background while(1) { wegisters/CtU state
thread
nonRT Fxn {tack
} To retain a thread’s context,
} we must save
Foreground UART ISR
threads
get byte
process
output
We also discussed the idea of foreground and background threads as part of the interrupts
chapter. In the case shown below (on the left), the endless loop in main() will run forever and be
pre-empted by higher-priority hardware interrupts.
RTOS Scheduler
nonRT
} + instrumentation
TI-RTOS utilizes these same concepts … only the names and threads change a little bit.
Rather than main() containing both the setup and loop code as described earlier, TI-RTOS
creates an Idle thread that operates in place of the while{} loop found previously in main(). In
other words, rather than adding your functions to a while{} loop, TI-RTOS has you add them to
Idle. (TI-RTOS includes a GUI configuration tool that makes this very easy to do.)
Since interrupts are part of the MSP430’s hardware, they essentially work the same way when
using TI-RTOS. What changes when using RTOS are:
• TI-RTOS calls them Hwi threads … for Hardware Interrupts
• Much of the coding effort is handled automatically for you by TI-RTOS (very nice)
Don’t worry, though, you’re not locked into anything. You can mix-and-match how you handle
interrupts. Let TI-RTOS manage some of your interrupts while handling others in your own code,
just as we described in this chapter.
Hint: When using TI-RTOS, the author prefers to let it manage all of the interrupts because it’s
easier that way. Only
Only in a rare case – like to save a few CPU cycles – would there be a need to managed
an interrupt outside of TI-RTOS. Thusfar, the only reason I’ve actually done this is to
provde that it works.
Idle
Runs as an infinite while(1) loop
Users can assign multiple functions to Idle
Background Single priority level
TI-RTOS provides two additional thread types: Software Interrupts (Swi) and Tasks (Task). As
you can see above, these thread types fall between Hwi and Idle in terms of priority.
Each of these threads can be used to extend your system’s processing organization.
You might remember that we HIGHLY recommended that you DO NOT nest interrupts. But what
happens if you want to run an algorithm based on some interrupt event? For example, you want
to run a filter whenever you receive a value from an A/D converter or from the serial port.
Without an RTOS, you would need to organize your main while{} loop to handle all of these
interrupt, follow-up tasks. This is not a problem for one or two events; but for lots of events, this
can become very complicated – especially when they all run at different rates. This way of
scheduling your processing is called a SuperLoop.
With an RTOS, we can post follow-up activity to a Swi or Task. A Swi acts just like a software
triggered interrupt service routine. Tasks, on the other hand, run all the time (have you heard the term
multi-tasking before?) and utilize Semaphores to signal when to run or when to block (i.e. pause).
In other words, Swi’s and Task’s provide two different ways to schedule follow-up processing
code. They let us keep our hardware interrupts (Hwi’s) very short and simple – for example, all
we need to do is read our ADC and then post an associated Swi to run.
If all of this sounds complicated, it really isn’t. While outside the scope of this course, the TI-
RTOS course will have you up-and-running in no time. Once you experience the effective
organization provided by an RTOS, you may never build another system without one.
TI-RTOS Details
The following slide provides some “characteristics” of the TI-RTOS kernel. The bottom-line here is
that it is a priority-based scheduler. The highest priority thread gets to run, period. (Remember,
hardware interrupts are always the highest priority.)
While you can construct a time-slicing system using TI-RTOS, this is not commonly done. While
time-slicing can be a very effective technique in host operating systems (like Windows or Linux), it
is not a common method for scheduling threads in an embedded system.
post1 rtn
Swi 3 (Ii) tosted
wunning
int2 rtn weady
Swi 2
rtn
Swi 1 (Lo)
start
main
int1
Idle
Notice how the system enters Idle from main(). Idle is always ready to run (just as our old while{}
loop was always ready to run).
When a hardware interrupt (Hwi) occurs, we leave Idle and execute the Hwi thread’s code. Since
it appears the Hwi posted a Swi, that’s where the TI-RTOS scheduler goes to once the Hwi
finishes.
We won’t go through the remaining details in this course, though we suspect that you can all
follow the diagram. For this slide, and a lot more information, please refer to the TI-RTOS Kernel
Workshop.
The TI-RTOS product includes the kernel, shown above, along with a number of additional drivers
and stacks. Oh, and the kernel comes with complete source code – nothing is hidden from you.
For many, though, one of the compelling features of TI-RTOS is that it’s FREE*.
Remember, we make our money selling you devices. Our code and tools are there to help you
get your programs put together – and your systems to market – more quickly.
* That is, it’s free for use on all Texas Instruments processors.
Lab 5 – Interrupts
This lab introduces you to programming MSP430 interrupts. Using interrupts is generally one of
the core skills required when buiding embedded systems. If nothing else, it will be used
extensively in later chapters and lab exercises.
When complete, you should be able to push the SW1 button and toggle the Red LED on/off.
Lab 5b is listed as optional since, while these skills are valuable, you should know enough at the
end of Lab 5a to move on and complete the other labs in the workshop.
Lab Topics
Interrupts ................................................................................................................................... 5-36
Lab 5 – Interrupts .................................................................................................................... 5-37
Lab 5 Worksheet ................................................................................................................. 5-39
General Interrupt Questions ............................................................................................ 5-39
Interrupt Flow .................................................................................................................. 5-40
Setting up GPIO Port Interrupts ...................................................................................... 5-40
Interrupt Priorities & Vectors ........................................................................................... 5-41
ISR’s for Group Interrupts ............................................................................................... 5-42
Lab 5a – Push Your Button ................................................................................................. 5-44
File Management ............................................................................................................ 5-44
Configure/Enable GPIO Interrupt … Then Verify it Works.............................................. 5-47
Add a Simple Interrupt Service Routine (ISR) ................................................................ 5-50
Sidebar – Vector Error ................................................................................................ 5-50
Upgrade Your Interrupt Service Routine (ISR) ............................................................... 5-52
(Optional) Lab 5b – Can You Make a Watchdog Blink? ..................................................... 5-53
Import and Explore the WDT_A Interval Timer Example ................................................ 5-53
Run the code ................................................................................................................... 5-55
Change the LED blink rate .............................................................................................. 5-55
Appendix ................................................................................................................................. 5-56
Lab 5 Worksheet
General Interrupt Questions
Hint: You can look in the Chapter 5 discussion for the answers to these questions
1. When your program is not in an interrupt service routine, what code is it usually executing?
And, what ‘name’ do we give this code?
________________________________________________________________________
2. Why keep ISR’s short? That is, why shouldn’t you do a lot of processing in them)?
________________________________________________________________________
_________________________________________________________________________
3. What causes the MSP430 to exit a Low Power Mode (LPMx)?
________________________________________________________________________
4. Why are interrupts generally preferred over polling?
_________________________________________________________________________
_________________________________________________________________________
Interrupt Flow
5. Name 4 sources of interrupts? (Well, we gave you one, so name 3 more.)
Hint: Look at the chapter discussion, datasheet or User’s Guide for this answer.
Timer_A
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
_________________________________________________________________________
6. What signifies that an interrupt has occurred?
Hint: Look at the “Interrupt Flow” part of this chapter discussion.
7. Write the code to enable a GPIO interrupt for the listed Port.Pin?
// GPIO pin to use: F5529 = P1.1, FR4133 = P1.2, FR5969 = P1.1
_________________________________________________________________________
10. Where do you find the name of an “interrupt vector” (e.g. PORT1_VECTOR)?
Hint: Which header file defines symbols for each device?
_________________________________________________________________________
11. Write the code to set the interrupt vector? (To help, we’ve provided a simple ISR to go with the line
of code we’re asking you to complete. Finish the #pragma statement...)
// Put’s the ISR function’s address into the Port 1 vector location
#pragma
_______________________________________________________________________
__interrupt void pushbutton_ISR (void)
{
// Toggle the LED on/off
GPIO_toggleOutputOnPin( GPIO_PORT_P1, GPIO_PIN0 );
}
_________________________________________________________________________
_________________________________________________________________________
12. How do you pass a value into (or out from) and interrupt service routine (ISR)?
Hint: Look at the chapter topic “Interrupt Service Routines – Coding Suggestions”.
_________________________________________________________________________
13. For dedicated interrupts (such as WDT interval timer) the CPU clears the IFG flag when
responding to the interrupt. How does an IFG bit get cleared for group interrupts?
_________________________________________________________________________
_________________________________________________________________________
14. Creating ISR’s for grouped interrupts is as easy as following a ‘template’. The following code
represents a grouped ISR template.
• Fill in the appropriate blank line to respond to the Port 1 pin used for the pushbutton on
your Launchpad. (F5529/FR5969 = P1.1; FR4133 = P1.2)
• Add the code needed to toggle the LED (on P1.0) in response to the button interrupt.
#pragma vector=PORT1_VECTOR
__interrupt void pushbutton_ISR (void) {
switch(__even_in_range( ____________________, 0x10 )){
case 0x00: break; // None
case 0x02: break; // Pin 0
______________________________________________________
break;
case 0x04: // Pin 1
______________________________________________________
break;
case 0x06: // Pin 2
______________________________________________________
break;
case 0x08: // Pin 3
_____________________________________________________
break;
case 0x0A: // Pin 4
____________________________________________________
break;
case 0x0C: // Pin 5
_____________________________________________________
break;
case 0x0E: // Pin 6
______________________________________________________
break;
case 0x10: // Pin 7
______________________________________________________
default:
_never_executed();
}
We will begin by importing the solution to Lab 4a. After which we’ll need to delete a bit of ‘old’
code and add the following.
− Setup the interrupt vector
− Enable interrupts
− Create an ISR
File Management
1. Close all previous projects. Also, close any remaining open files.
lab_05a_buttonInterrupt
4. Verify the project is active, then check that it builds and runs.
Before we change the code, let’s make sure the original project is working. Build and run the
project – you should see the LED flashing once per second.
When complete, terminate the debugger.
You can take a quick look at this file, if you’d like. Notice that we created a single ISR function
that is associated with all of the interrupts on your device – since, at this point, all of the
interrupts are unused. As you add each interrupt to the project, you will need to modify this
file.
6. Before we start adding new code … comment out the old code from while{} loop.
Open main.c and comment out the code in the while{} loop. This is the old code that flashes
the LED using the inefficient __delay_cycles() function.
The easiest way to do this is to:
Select all the code in the while{} loop
After commenting out the while code, just double-check for errors by clicking the build
button. (Fix any error that pops up.)
8. Add the line of code needed to enable interrupts globally (i.e GIE).
This line of code should be placed right before the while{} loop in main(). Refer back to the
Lab 5 Worksheet, question # 8 (see page 5-40).
12. Next, set a breakpoint inside the ISR in the unused_interrupts.c file.
14. Open the Registers window in CCS (or show it, if it’s already open).
If the Registers window isn’t open, do so by:
View → Registers
17. Single-step the processor (i.e. Step-Over) and watch GIE change.
Click the toolbar button or tap the ^key. Either way, the Registers window should update:
Don‘t forget to fill in the ???? with your answer from question #11 from the worksheet (see
page 5-41).
This error tells us that the linker cannot fit the PORT1_VECTOR into memory because the
interrupt vector is defined twice. (INT47 on the ‘F5529 and ‘FR4133; INT39 on the ‘FR5969)
We just created one of these vectors, where is the other one coming from?
_________________________________________________________________________
View → Breakpoints
Click the Remove All button
27. Run your code … once the code is running, push the button to generate an interrupt.
The processor should stop at your ISR (location shown above). Breakpoints like this can
make it easier to see that we reached the interrupt. (A good debugging trick.)
28. Resuming once again, at this point inside the ISR should toggle-on the LED.
If it works, call out “Hooray!”
For grouped interrupts, if we use the appropriate Interrupt Vector (IV) register, we can easily
decipher the highest priority interrupt of the group; and, it clears the correct IFG bit for us.
30. Replace the code inside your ISR with the code that uses the P1IV register.
Once again, we have already created the code as part of the worksheet; refer to the
Worksheet, Step 14 (page 5-43).
To make life easier, here’s a copy of the original template from the worksheet. You may want
to cut/paste this code, then tweak it with answers from your worksheet. (Note: this is the code
for the ‘F5529 and ‘FR5969. Remember that the ‘FR4133 uses a different pin on Port 1.)
//*********************************************************************
// Interrupt Service Routines
//*********************************************************************
#pragma vector=PORT1_VECTOR
__interrupt void pushbutton_ISR (void) {
switch(__even_in_range( ????, 0x10 )) {
case 0x00: break; // None
case 0x02: break; // Pin 0
case 0x04: // Pin 1
??????????????????????;
break;
case 0x06: break; // Pin 2
case 0x08: break; // Pin 3
case 0x0A: break; // Pin 4
case 0x0C: break; // Pin 5
case 0x0E: break; // Pin 6
case 0x10: break; // Pin 7
default: _never_executed();
}
}
Hint: The syntax indentation often gets messed up when pasting code. If/when this occurs, the
CCS editor provides a way to correct this using (<ctrl>-I).
32. Launch the debugger. Run/Resume. Push the button. Verify the light toggles.
Run the program. Push the button and verify that the interrupt is taken every time you push
the button. If the breakpoint in the ISR is still set, you should see the processor stop for each
button press (and then you’ll need to click Resume).
You’re welcome to explore further by single-stepping thru code, using breakpoints,
suspending (halting) the processor and exploring the various registers.
In Lab 4 we used the Watchdog timer as a … well, a watchdog timer. In all other exercises, thus
far, we just turned it off with WDT_A_hold().
In this lab exercise, we’re going to use it as a standard timer (called ‘interval’ timer) to generate a
periodic interrupt. In the interrupt service routine, we’ll toggle the LED.
As we write the ISR code, you may notice that the Watchdog Interval Timer interrupt has a
dedicated interrupt vector. (Whereas the GPIO Port interrupt had 8 grouped interrupts that shared
one vector.)
Once imported you can close the TI Resource Explorer, if you want to get it out of the way.
and a popup box appears providing you with choices – select the one you want. In this case,
we suggest you divide by 32K.
Appendix
Lab 05 Worksheet (1)
General Interrupt Questions
1. When your program is not in an interrupt service routine, what code is it
usually executing? And, what ‘name’ do we give this code?
main functions while{} loop. We often call this ‘background’ processing.
______________________________________________________
2. Why keep ISR’s short (i.e. not do a lot of processing in them)?
______________________________________________________
We don’t want to block other interrupts. The other option is nesting
interrupts, but this is LbEFFLCLEbT. 5o interrupt follow-up processing in
______________________________________________________
while{} loop … or use TL-wThS kernel.
______________________________________________________
3. What causes the MSP430 to exit a Low Power Mode (LPMx)?
______________________________________________________
Lnterrupts
______________________________________________________
Watchdog Lnterval Timer
Analog Converter … and many more
______________________________________________________
6. What signifies that an interrupt has occurred?
A __________
flag bit is set
What’s the acronym for these types of ‘bits” ___________
LFG
FR4133:
GPIO_setAsInputPinWithPullUpResistor ( GPIO_PORT_1, GPIO_PIN2 );
___________________________________________________________________ // set up pin as input
GPIO_interruptEdgeSelect ( GPIO_PORT_P1, GPIO_PIN2, GPIO_LOW_TO_HIGH_TRANSITION); // set edge select
___________________________________________________________________
GPIO_clearInterruptFlag ( GPIO_PORT_P1, GPIO_PIN2 );
___________________________________________________________________ // clear individual INT
GPIO_enableInterrupt ( GPIO_PORT_P1, GPIO_PIN2 );
__________________________________________________________________ // enable individual INT
Let’s say you’re CPU is in the middle of the GPIO Port 2 ISR, can it be
interrupted by a new WDT interval timer interrupt? If so, is there anything
you could do to your code in order to allow this to happen?
______________________________________________________
bo, by default, aSt430 interrupts are disabled when running an LSw. To
______________________________________________________
enable this you could set up interrupt nesting (though this isn’t recommended)
______________________________________________________
Cor example: msp430f5529.h, msp430fr5969.h, or msp430fr4133.h
13. For dedicated interrupts (such as WDT interval timer) the CPU clears
the IFG flag when responding to the interrupt. How does an IFG bit get
cleared for group interrupts?
______________________________________________________
Either manually; or when you read the LV register (such as t1LV).
______________________________________________________