09 September 2011

Tutorial 13b: Interfacing the Comparator A+ and Timer A Modules

The idea behind the capacitance meter is simple, but (as may be evidenced in part by the time it took me to get to this) there are some particulars that need to be resolved.

First: how do we minimize any delay in the timing due to delays from carrying out instructions? If we're not careful, a non-negligible amount of time can pass between when we start the capacitor discharge and the timer, or between the comparator trigger and the timer capture.

Second: how do we determine what capacitances can be measured for a given configuration? Smaller capacitors will discharge more quickly, leading to shorter measurement times (meaning more error due to the digital nature of the timer). Larger capacitors take more time to charge up, and may not be fully charged when we start discharging.

The first issue is easily resolved by using features built into the Timer_A module. The second is less easily resolved, but easily understood, so we will know in advance the limitations of our code. There are a couple of things we'll be able to do to improve it generally, but for our purposes here we won't be too concerned about it.

New Feature of the Timer_A Module: Output

Let's introduce here two features in Timer_A we haven't used yet. First off, let's look at how we can use the timer to change an output. Recall that the Timer_A module has a certain number of "capture/compare" registers built into each MSP430 device. (The G2211 and G2231 have two.) Each of these registers can drive their own output; we can program the MSP430 to adjust the output every time the timer reaches the value stored in the register. For example, we can set the output TA0.1 to set the output (to 1, that is) every time the timer reaches the value set in TACCR1. Or we can toggle the output TA0.0 every time the timer reaches the value set in TACCR0. We also have modes that allow us to use both registers on the same output, giving one result at TACCR1 and another at TACCR0. (This ability is used for pulse width modulation, or PWM. We'll talk about that in an upcoming tutorial!)

Take a look at table 12-2 in you x2xx Family User's Guide:
This table lists all of the possible modes we can use to work with the timer outputs.

New Feature of the Timer_A Module: Capture

So far we have only discussed uses of the timer in compare mode. The other mode, capturing, can be used to do precise timing of events. In capture mode, the timer records the value in TAR into TACCRx at the moment when the capture is triggered. This trigger can come externally, or it can come from internal connections to other modules, including the comparator. By configuring the timer in this way, we can record the timer value when the comparator output provides either a rising edge (going from 0 to 1) or a falling edge (from 1 to 0).

Putting It All Together

Here's the general concept used in the capacitance meter. Note that accurate timing requires a calibrated clock, so we'll use the calibrated 1 MHz DCO for this project. First, we connect the resistor to a timer output. (For this purpose, we'll use the TA0.0 output.) The junction between the resistor and capacitor is tied to a comparator input, and the other end of the capacitor is connected to ground. We start charging the capacitor by setting TA0.0, and wait some specified amount of time for the capacitor to charge. The output is then reset (grounded) at the time specified in TACCR0. While the capacitor's voltage is above the reference voltage, the output is set at 1 (assuming we tied the RC circuit to V+ and the reference to V-). When it drops below that value, the comparator output falls, triggering a capture in the timer, recorded in TACCR1. The difference in time between TACCR1 and TACCR0 is an accurate measurement of the decay time of the RC circuit from Vcc to Vref. (Note that if TACCR0 is 0, no subtraction is needed.)

Obviously the longer the time it takes to fall, the more accurate our timing measurement will be overall. But what happens if the time is longer than 2^16 microseconds? TAR rolls over, and starts counting over again; so in our code, we'll need to account for any rollovers that may occur.

Let's examine the comparator configuration used in the code:

void CAinit(void) {
    CACTL1 = CARSEL + CAREF_1;  // 0.25 Vcc ref on - pin.
    CACTL2 = P2CA4 + CAF;       // Input CA1 on + pin, filter output.
    CAPD = AIN1;                // disable digital I/O on P1.1 (technically
                                // this step is redundant)
} // CAinit

This sets up the comparator to use CA1 on the V+ input. If you check the MSP430G2211 datasheet, CA1 is connected to P1.1. Looking up the register description in the Family User's Guide, we configure for CA1 on V+ by setting P2CA4. (P2CA0 also controls V+, and for CA1 should be clear.)

Now let's look at the timer configuration:
void TAinit(void) {
    TACTL = TASSEL_2 + ID_0 + MC_0;     // Use SMCLK (1 MHz Calibrated), no division,
                                        // stopped mode
    TACCTL0 = OUTMOD_1 + CCIE;          // TA0 sets VCTL at TACCR0
    TACCTL1 = CCIS_1 + SCS + CAP + CCIE;       // Use CAOUT for input, synchronize, set to 
                                        // capture mode, enable interrupt for TA1.
                                        // NOTE: Capturing mode not started.
} // TAinit

The timer is set up without starting. Setting TACCTL0 to OUTMOD_1 sets the output TA0.0 when TAR reaches TACCR0, which is 0 by default. Enabling the interrupt lets us keep track of overflows. In TACCTL1, we change to capture mode by setting CAP. To know how to connect the comparator, we need to check the device datasheet. Find the table called "TIMER_A2 SIGNAL CONNECTIONS" and make sure you're looking at the one specific to devices with COMP_A+. In the Device Input Signal column, find CAOUT (internal), and note the Module Input Name in the column next to it: CCI1B. The CCISx bits in TACCTLx select the input, and in the Family User's Guide, we see that these two bits should be set to 0b01 to select CCIxB. In the device's header file, we find that we can set these bits with CCIS_1.

Also note that by setting SCS, we synchronize the capture to the timer clock. We haven't started capturing yet, just as we haven't started the timer. The code is set up to wait for the user to push the button connected to P1.3. Doing so exits LPM0, and continues the main code. The following then happens:
First, the timer is turned on. When the timer rolls over the first time, TA0.0 (on P1.5 in this code) is set, charging the capacitor. We want to wait long enough for the capacitor to charge. The code is set to wait for 10 overflows, which corresponds to about 655 ms. At this point, the comparator is turned on, and the timer is configured to reset TA0.0 at the next overflow (so we've actually charged for 11 overflows at this point). We let the timer capture the next event, or when the voltage at the capacitor (on P1.1/CA1) drops to the value at Vref, or 1/4 Vcc. At this point, an interrupt is triggered. The interrupt routine turns off the captures and the timer and returns to the main code. The code loops back, and starts the process again, waiting for the user to press the button to start a measurement.

Note: Before running the code as set up, keep in mind we're using P1.1; what else is this pin used for? You'll want to remove the TXD jumper to get the project to work properly. Thanks to RobG over at www.43oh.com for pointing this out to me... I was really puzzled by it for an embarrassingly long time!

Try running the code found in CMeterG2211.c. To do this, you'll need a resistor and a capacitor. Use a 10 kO resistor and a 100 nF capacitor (it will have a label that says 104 on it) if you can. In the debugger, set the code to run freely, and push the button. The led should switch from green to red, then back to green, indicating the measurement is done. The timer has captured the event and recorded the time in TACCR1. Unfortunately, there's no way to see this as is yet! Pause the debugger, and examine the timer_a2 registers. The time is recorded in TACCR1. For the suggests RC combination, you should have a value somewhere around 1400. (You can change from the default hex format to decimal by right-clicking the register and selecting decimal in the format menu.)

Try increasing R to 100 kO. You should see the time increase by a factor of 10. Try using a 1 uF capacitor. (You might need to increase the number of overflows to wait while charging to get something accurate here.) In the watch window, we can view the value in the overflows variable. To do this, click where the window says , and type in the variable name. With such a large capacitor, you should see the number of times the timer wrapped around before stopping. With the measured time and the known resistor value, you should be able to use the formula provided in the previous tutorial to calculate the value of the capacitor. Feel free to experiment with this program. In particular, how consistent are your timing measurements?

Aside from the lack of ability to see the timer value without using the debugger, there are a few issues to work out still in this meter. We'll take a careful look at some of these in the future, and bring back this code to demonstrate ways we can see data and ways we can improve the timer.

Reader Exercise: Given the discussion in the previous tutorial about the accuracy of the meter, and from your own experimentation with the program, where are the major sources of error in the measurement? How consistent is the timing? What might cause the inconsistency? What assumptions have we made in the way we measure the capacitance? Once you've built an instrument to make any kind of scientific measurement, it's important to identify all of these aspects. By doing so, we identify the limitations of the instrument, both those we can fix and those we have to live with. 

15 comments:

Anonymous said...

Glad to see you've got new posts! Your previous CompA+ post was invaluable!

voodoobot said...

Good to see the note about P1.1 being tied to txd. When first I was playing with the CompA+ the circuit I was testing worked one way, sort of. It really made no sense. I kept trying to rewire until I had nothing left. After finally testing the pin with a multimeter the voltage on the pin made sense...and of course undoing the pin left it floating. I had many evenings of hair pulling prior to figuring this one out!

Jon said...

Two quick questions. When we pass the while loop there is a bunch of code to be executed. We turn on the comparator and reconfigure TACCTL0 and TACCTL1 and finally enter LPM. Obviously this can all happen before we overflow or we would run into a problem. Is there any chance we could run into a problem? How do you know the amount of time it takes to execute code versus an overflow.

Second I can't figure out why you have set overflows to -1.

Take care,

Jon

David said...

Good questions, Jon. Keep in mind that the while loop ends at an overflow, so TAR=0. The next overflow will occur at TAR=65536, so we have that many clock cycles where we can work before the next interrupt is triggered. You could imagine a race condition occurring, but it depends on the number of things to execute in the code; the rate the code executes scales with the clock, of course. The code we're executing is only a handful of assignments, which should only take a few clock cycles to complete.

As for the assignment to -1... We want overflows to be zero when we start the discharge. If we set it to zero here, on the next overflow, when the discharge begins, overflow is also incremented. By setting it to -1, the discharge overflow occurs at overflows=0, to properly account for the timing.

Hope that helps!

Jon said...

Awesome. Thanks David.

Take care,

Jon

Rajiv Gowda said...

Nice blog. Addictive indeed!!

Can you please elaborate more on how the jumpers mattered? Is that the jumpers are required only when the computer interacts with the MSP device?

Thank you.

David Olson said...

Rajiv,

That's a great question. The jumpers on P1.1 and P1.2 tie the MSP430 device on the LaunchPad to the MSP430 on the emulator side of the board; specifically, they're tied to the UART. The default signal in UART is a mark (voltage high), so the pins are tied to Vcc through the UART in the emulator. That can cause problems when you want to use either of those pins for other purposes, especially as inputs. Removing the jumpers disconnects the emulator UART, leaving those pins free to behave normally, being untied to anything else.

The LaunchPad also has jumpers to the LEDs on P1.0 and P1.6, but not on the button tied to P1.3. I think that's unfortunate, as it limits what you can do with that particular pin. It's not actively driven the way the UART pins are, so it's still usable, but it does draw more power that way.

Anonymous said...

I have recently started reading your blog and, so far, it was really great, but reaching here I don't understand what does this: P1SEL = VCTL in Cmeter do. Does it only select pin for TimerA0.0 output or can we use it for different outputs?

David Olson said...

Anon: that's right; if you look in the preamble portion of the code, you'll see that I've defined VCTL to be BIT5. By setting P1SEL to this value, it changes the function of P1.5 only-- setting it to the TimerA0.0 Output. You can change this code to any timer output, but then you'll have to change a few things around in both the code and the hardware. For example, you could switch to using P1.1 as the timer output, but then you'll have to select a different pin for your comparator input, likely P1.2. (On the LaunchPad, I'd avoid P1.0, as that's used for one of the LED indicators already. P1.3 isn't a good choice either as it's tied to the button, which is also used in this example code anyway.)

If you do that configuration, you'll also have to change so that the resistor is tied between P1.1 and P1.2, and the capacitor between P1.2 and ground.

Hope that helps!

Anonymous said...

Thanks for the quick reply. I understand now that by setting the bit in P1SEL register we choose the second function (not the default GPIO) for the corresponding pin. The datasheets from TI are really hard to understand easily.

David Olson said...

I can certainly understand that feeling-- any microcontroller is complicated enough that the data sheet will be pretty dense. I think that you'll find as you gain more understanding of the system that TI's data sheets really are quite good; they're just not meant to be the only document referenced. When used in conjunction with the Family User's Guide and example code, you can learn a great deal.

mat said...

I've had my MC programmed with Cmeter code and removed the TXD jumper as mentioned but it still didn't work. I've lost few hours looking for some fault measuring voltages and finally discovered (although the datasheet shows diffrently) that the pull-up resistor R34 which should be on the board isn't really there on newer LaunchPads. So to make the code work properly it is necessary to use a builtin one.

alexander korolev said...

Don't understand why you disable P1.7 and how it can be reached with setting CAPD to BIT1. I think this will disable P1.1?

David Olson said...

Looks like a typo in the comments there. I've fixed it now.

Anonymous said...

Thanks for pointing out how to connect the comparator to the timer capture input. This is very well hidden in the datasheet and family manual (much easier with Atmel AVR datasheets). This is the only way to capture switch 2 on the Launchpad (using G2211).

I modified your code and came to this information:

No need to clear the interrupt flag manually in TIMERA1_VECTOR if you read the TAIV register:

"Any access, read or write, of the TAIV register automatically resets the highest pending interrupt flag. If
another interrupt flag is set, another interrupt is immediately generated after servicing the initial interrupt.
For example, if the TACCR1 and TACCR2 CCIFG flags are set when the interrupt service routine
accesses the TAIV register, TACCR1 CCIFG is reset automatically. After the RETI instruction of the
interrupt service routine is executed, the TACCR2 CCIFG flag will generate another interrupt."

In fact, clearing it manually before reading TAIV will cause an error: there is nothing to read.