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 ofecho
.
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 |
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 useRETI
instead of a normalRET
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 IFG
s.
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 isTA0CCR0.CCIFG
.
Read |
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 theTA0CCTL1
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:
-
Write a zero to that bit in the
TA0CCTL1
register. -
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-priorityCCIFG
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 correspondingxIFG
bit to zero.
Always read the |
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!