Implement a system that measures distance to a hard surface using the MSP430 MCU and HC-SR04 ultrasonic range sensor.

1. Requirements:

  • Trigger the sensor from an output pin controlled from a timer.

  • Capture the width of the echo pulse using the timer input capture feature.

How you display or communicate the measured pulse time is your choice.

2. One step at a time

It is never a good plan to begin with the final requirements and directly code that solution. Better is to implement small aspects of the required functionality, then glue the parts together to build the complete system.

2.1. Hardware

The module works best with a 5V supply, but this is not directly compatible with the MSP430 inputs especially. Be sure that your hardware setup / circuit / wiring is safe for your MCU.

The tour of the documentation from Project 3 needed to figure out which physical pins are attach-able to which timer modules may be found at:

2.2. Sequence

Suggested sequence:

  • Trigger the ultrasound transmit pulse from a timer.

  • Fake an echo signal by driving the MCU input from your AD2 with a known pulse width.

  • Read the echo pin and find the high time in the easiest and simplest way that you can think of. Fanciness and innovation is prohibited. "It works" yields more street cred' than how cool your code is that doesn’t work.

Your entire goal is to measure the high time of the echo pin after you trigger the module. Hit that goal by any means necessary first, then morph your working code into a version that uses the timer hardware for the time-sensitive parts.

  • Wait for the echo pin to go high, then setup a timer input capture for the XYZ edge.

  • Start a timer on the rising edge of echo, then stop the timer at the next edge of echo.

2.3. Result output

There are many solutions to this.

  • Blink an LED with a certain duty cycle and/or frequency.

    • A low-pass R-C filter will convert a PWM signal to an analog voltage: Vx = 3.3V * duty. Valid when R×C ≫ PWM_period

Store the result in a variable and use the Debugger to read the value.

3. Notes on interrupts

Important information for using interrupts.

3.1. The tl;dr

The IFG and IV registers interact as a pair on the MSP430 (and other architectures).

Easy rule to follow:

Always read the xIV (e.g. TA0IV or P0IV) register early in your Interrupt Service Routine, then use its value to determine exactly which thing triggered the interrupt. Do this even when you already know that there is only one interrupt enabled.

3.2. How interrupts work

FUG section §1.3 Interrupts describes how interrupts are handled by the CPU and the sequence of actions that happens at each interrupt event. Section §1.3.4.1 Interrupt Acceptance specifically describes these events, which take six MCLK cycles.

Though not used so far, the NOTE: Enable and Disable Interrupt at the end of page 52 concludes with "Not following these rules might result in unexpected CPU execution." Such "unexpected" things can be a source of great frustration and suffering to those that ignore the rules.

Recall the class times that have had something to do with interrupts:

3.3. Interrupt flags

This section assumes you have the MSP430 Family User’s Guide open at the same time. To begin, navigate to FUG page 655 §25.2.6 Timer_A Interrupts.

The code that gets the appropriate interrupt service routine attached to the interrupt vector that gets enabled was:

// Timer0_A0 interrupt service routine
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR (void)
{
 P1OUT ^= BIT0;
}

The two important parts for this (using TI’s compiler):

  • #pragma vector = TIMER0_A0_VECTOR specifies exactly which interrupt source (vector) that this code belongs to.

  • __interrupt label tells the compiler to use RETI instead of a normal RET instruction when returning from the function.


Open up the datasheet (MSP430FR6989-datasheet.pdf) and go to page 79, section §6.4 and Table 6-4 Interrupt Sources, Flags, and Vectors.

All three keywords are important:

  • source - the hardware or module that makes interrupt request

  • flag - the name of a bit in a register that is set (1) for a particular enabled interrupt (may be many or only one possibility)

  • vector - the memory location where the address of the ISR function is stored

Notice that there are only two interrupt vectors for Timer_A TA0 and it has many IFGs. So far, we have only used the first one, for TA0CCR0:

  • This is plugged in with the #pragma vector = TIMER0_A0_VECTOR syntax.

  • According to the datasheet, this particular vector is located at memory location 0x0FFE8 and its only associated flag is TA0CCR0.CCIFG.

Read IFG as "interrupt flag."

Finally, notice the TA0IV, called the "Interrupt Vector Register," in the second column of datasheet Table 6-4. This register holds the information you (the interrupt service routine) need to determine exactly which IFG flag triggered the second interrupt at location 0x0FFE6.

3.4. Interrupt Vector Registers

Section §25.2.6.1 TAxCCR0 Interrupt describes how the TAxCCR0.CCIFG flag gets set. The second sentence says that this flag is automatically reset (to 0) when the interrupt request is serviced.

In other words:

  • The event happens and the flag gets raised (see the schematic).

  • The Interrupt Service Routine gets called.

  • -> The flag gets lowered.

The user program has nothing to do with this particular flag, it is automatically handled by the hardware. This is NOT TRUE for the other vector!


Scroll down to section §25.2.6.2 TAxIV, Interrupt Vector Generator and read the three paragraphs. Don’t worry about understanding it the first time.

Jump back to Figure 25-1. Timer_A Block Diagram.

Each CCRn module (refer ) has an output from the CAP multiplexer that sets the CCIFG bit in the corresponding TAxCCRn control register.

  • These six bits are OR’d together to generate an interrupt service request from the Timer_A module.

  • When the CPU services this request, it jumps to the address that is stored in memory location 0x0FFE6. This is the address in the datasheet Table 6-4.

Because this request is the logical OR of up to six compare/capture events, the ISR code doesn’t immediately know which of the CCR’s, we have to read the TAxIV register to tell us.

Navigate to page 662, section §25.3.5 TAxIV Register.

  • 16-bit-wide register.

  • The upper 8 bits are all read-only and zero.

  • Only bits {3…​1} are possibly non-zero.

  • The 4-bit code tells which interrupt source is active.

The register can hold only one code at a time, even if there are several pending events. The order that the user code is presented with the interrupt sources is by priority — lower-numbered CCRs get handled first if there is a conflict.


Ok …​ now that you see that TAxIV holds only one timer interrupt source’s "name," scroll back to section §25.2.6.2 TAxIV, Interrupt Vector Generator.

Read the third paragraph again: "Any access, …​"

No need to repeat the given example, here is another example:

  • TA0CCR1 generates a compare event (e.g. used as PWM on time, or an input pin’s edge made a snapshot of the current timer value).

  • The CCIFG bit is therefore set to 1 in the TA0CCTL1 register. See FUG page 660, section §25.3.3 TAxCCTLn (control) Register.

  • This bit is OR’d with its siblings and triggers an interrupt service request to the CPU.

  • …​

  • The configured ISR gets called. #pragma vector = TIMER0_A1_VECTOR <- different symbol!

  • …​ the ISR does its thing and exits.

  • Code execution resumes from wherever it was before the interrupt.

One MAJOR problem here.

The CCIFG bit in TA0CCTL1 is still set!! So, the ISR gets called again. And Again. And AGAIN. The IFG bit does not get set back to zero automatically, like it does for the (special) CCR0 interrupt.

Read that third paragraph of §25.2.6.2 one more time.

There are only two ways to lower this CCIFG bit:

  1. Write a zero to that bit in the TA0CCTL1 register.

  2. Touch the TA0IV register, read or write. Writing is a little bit pointless because it is a read-only register, but such an attempt does actually do something: lowers the highest-priority CCIFG bit.

Remember that this TAxIV register tells you exactly what the highest-priority active interrupt source is. You need this information when you have more than one CCRn interrupt enabled.

Yes, it is true that you could figure out the source by other methods, such as reading the state of the relevant input / output pin, but reading TAxIV is the ready-made solution.

Here we arrive at the tl;dr, so a summary:

  • The xIFG bit must be set back to zero, or we end up in a continuous interrupt loop (this is tricky but possible to detect with the debugger).

  • Reading xIV register is the easiest way to see which module requested the interrupt.

    • Use a switch/case or if/else inside the ISR to handle that event (only).

  • Reading xIV also sets the corresponding xIFG bit to zero.

Always read the xIV (e.g. TA0IV or P0IV) register early in your Interrupt Service Routine, then use its value to determine exactly which thing triggered the interrupt. Do this even when you already know that there is only one interrupt enabled.

This rule applies to port interrupts, which notify the program of rising/falling edges on input pins. See FUG page 369, section §12.2.6 Port Interrupts.


Thank-you for watching!

Please hit that "Subscribe" button if you want more content like this.

⋯ oh, and please visit my awesome sponsors!

See you in the next document!