Electronics > Microcontrollers

MSP430: How to start TimerA with minimal latency from a GPIO signal?

(1/2) > >>

smoothVTer:
2 MSP430's on my bench, both are MSP430FR5739 dev boards.    Both are using an external TCXO as the clock source. MCLK = 16MHz on both here.  ACLK = 500kHz, ACLK/8 is used as clock source for Timer_A0.

I've got MSP430 #1  "Jabba"  outputting 91Hz @ 10% duty cycle  ( 1ms high time, 10ms low time ) from a GPIO pin.   It does this by manipulation of CCR0 in a TimerA ISR.   Time intervals are known at compile time, stored in an array.   Everything fine here as far as pulse generation & timing is concerned.

I've got MSP430 #2 "Leia" configured to wait around in a polling loop, waiting for P2.3 to go high.    As soon as possible after P2.3 goes high, I want to immediately start timerA0 on Leia to start outputting the same pulses as Jabba is sending.    Leia can do this because the same time intervals are known at compile time, stored in an array.    Basically, the pulse train intervals are known and shared ahead of time between Leia and Jabba through the software. 

I am not certain what is achievable when I say "as soon as possible", but I cannot tolerate the large delay I am seeing between P2.3 going high and TimerA finally starting.   Here's a scope shot showing a latency of 1.975ms between P2.3 on Leia going high, and P1.0's first pulse from the Timer_A0 module:



I need to get this latency down to 1us or less.  I am not sure why TimerA takes so long to start up ( approx. 31,600 MCLK cycles ).  By the way, using an ISR to read P2.3 instead of polling results in the same latency ( I experimented )   I realize polling a GPIO in this manner is generally bad practice but for sake of this thread, take it as it is.   Code below:


--- Code: ---#include <msp430fr5739.h>

// Prototypes
void initCS(void);
void initGPIO(void);
void initTA0(void);
void enableConfig(void);

//   Pulse-sending pin is P1.0
//   Pulse-reading pin is P2.3

//**************************************************************//
//************ GLOBALS GLOBALS GLOBALS GLOBALS *****************//
//**************************************************************//

// Timer A sourced from ACLK @ 500kHz
// Timer A clk source / 8 for 62.5kHz tick or 16us/tick
// 0.992ms = 62 ticks
// 9.92ms  = 620 ticks
// 99.2ms  = 6200 ticks
// 992ms   = 62000 ticks
unsigned int pulseTiming[2]          = {62, 620};
volatile unsigned int numEdges       = 2;
volatile unsigned int pulseIdx       = 0;

int main(void)
{
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer

    initCS();

    initGPIO();

    enableConfig();

    __bis_SR_register(GIE);                 // Enable global interrupts


    // We are in MSP Leia's code here only.
    // MSP Jabba is off sending pulses already.
    // Endlessly poll P2.3 ...
    while(1)
    {
        if( P2IN & BIT3 )                  // If P2.3 goes high ...
        {
            initTA0();                     // ... start timerA right away
        }

    }//while(1)

    return 0;
}//main()


void initCS(void)
{
    PJSEL0 |= BIT4;                                        // Set PJ.4 as XIN, digital square wave input
    PJSEL1 &= ~BIT4;

    CSCTL0_H = 0xA5;                                       // Unlock password for updating CS

    //CSCTL1 |=                                            // Don't care about this register, not using DCO
    CSCTL2 = SELM__XT1CLK + SELS__XT1CLK + SELA__XT1CLK;   // MCLK source = XT1CLK;  SMCLK source = XT1CLK;
                                                           // ACLK source = XT1CLK
    CSCTL3 = DIVM__1 + DIVS__2 + DIVA__32;                 // MCLK/1;  SMCLK/2;  ACLK/32

    CSCTL4 |= XT1BYPASS | XTS | XT2OFF;                    // Select Bypass mode operation
                                                           // for XT1; select high-freq. mode;
                                                           // Don't need XT2 so turn it off
    do
    {
        CSCTL5 &= ~XT1OFFG;                               // Clear XT1 fault flag
        SFRIFG1 &= ~OFIFG;
        __delay_cycles(10000);                            // Wait around a little while before next check
    } while (SFRIFG1&OFIFG);                              // Test oscillator fault flag




    PJDIR  |= BIT5;      // Set PJ.5 as a GPIO output ( don't need XOUT )

    PJDIR  |=  BIT0;     // Set PJ.0 as SMCLK output
    PJSEL0 |=  BIT0;
    PJSEL1 &= ~BIT0;

    PJDIR  |=  BIT1;     // Set PJ.1 as MCLK output
    PJSEL0 |=  BIT1;
    PJSEL1 &= ~BIT1;

    PJDIR  |=  BIT2;     // Set PJ.2 as ACLK output
    PJSEL0 |=  BIT2;
    PJSEL1 &= ~BIT2;

}//initCS()

void initTA0(void)
{
    TA0CTL = TACLR;                                // Reset everything first
    TA0CCTL0 = CCIE;                               // CCR0 interrupt enabled
    TA0CCR0 = pulseTiming[0];                      // Initialize CCR0
    TA0EX0  =  TAIDEX_0;                           // Expansion divider: /1
    TA0CTL |= TASSEL_1 | MC__CONTINUOUS | ID__8;   // TA0 clock source = ACLK;
                                                   // Continuous mode
                                                   // Input divider: /8 = 62.5kHz or 16us/tick
}//initTA0


void enableConfig(void)
{
    PM5CTL0 &= ~LOCKLPM5;                   // Disable the GPIO power-on default high-impedance mode
                                            // to activate previously configured port settings
}

void initGPIO(void)
{
    // Pulse output pin P1.0
    P1DIR |=  BIT0;              // Set P1.0 as an output pin
    P1OUT &= ~BIT0;              // Initialize P1.0 to low

    // Sync sensing pin P2.3
    P2DIR &=  ~BIT3;             // Set P2.3 as an input pin
    P2REN |=  BIT3;              // Enable pu or pd on pin
    P2OUT &= ~BIT3;              // Select pull-down resistor on pin
    __delay_cycles(5);           // Give time for pullup to settle
}//initGPIO()


// Timer A0 ISR
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) Timer0_A0_ISR (void)
#else
#error Compiler not supported!
#endif
{
    P1OUT ^= BIT0;                           // Flip pulse output pin
    TA0CCR0 += pulseTiming[pulseIdx];

    if(pulseIdx < numEdges - 1) { pulseIdx++;   }
    else                        { pulseIdx = 0; }

}//TimerA0 ISR



--- End code ---






coppice:
When P2.3 goes high you initialise the timer. This happens in a loop, so you keep reinitialising the timer until P2.3 goes low. The timer interrupt is hit 992us after the timer is finally left to run. Then P1.0 goes high. The timer runs for another 992us, and the interrupt hits again. P1.0 then goes low. The timer should then run for 9920us before the next interrupt.

You seem to be getting exactly what you have programmed.

voltsandjolts:
TLDR; but in general for pulse measurement/generation its better to use capture/compare registers and leave the timer itself running all the time.

smoothVTer:
I see your point, coppice.   Polling the loop for ( P2IN & BIT3 )  will be true for the entire high-time pulse and continually to re-initialize timerA.   So to eliminate this possibility ...

What I did next is to start timerA within the port2 ISR.     Setup Port2 for a low-to-high edge transition on P2.3.   Upon receipt of such an edge, start timerA inside the Port2 ISR, the disable the Port2 interrupt to prevent any other edges other than the first one from calling initTA0().  The MASTER pulse train, ongoing all the time, is inhibited from transmission initially by a NO switch which I close when I want to transmit pulses from MASTER to SLAV

The same issue still exists:
( "SLAVOUT" is P1.0;   "SLAVSYNC" is P2.3 input;  "MASTER" is the ongoing asynchronous pulse train from MSP #1 )





A look at the falling-edge of SLAVSYNC indicates that TimerA0 is started prior to the SLAVSYNC pulse high time being over, i.e., the pulse on time does not seem to correlate with the start of Timer A0:




Code for this is all below.   


--- Code: ---#include <msp430fr5739.h>
#include <main.h>

//   Pulse-sending pin is P1.0
//   Pulse-reading pin is P2.3


//**************************************************************//
//************ GLOBALS GLOBALS GLOBALS GLOBALS *****************//
//**************************************************************//

// Timer A sourced from ACLK @ 500kHz
// Timer A clk source / 8 for 62.5kHz tick or 16us/tick
// 0.992ms = 62 ticks
// 9.92ms  = 620 ticks
// 99.2ms  = 6200 ticks
// 992ms   = 62000 ticks
unsigned int pulseTiming[2]          = {62, 620};  /// ~1ms high, ~10ms low
volatile unsigned int numEdges       = 2;
volatile unsigned int pulseIdx       = 0;
volatile unsigned int secondsWaiting = 0;

// ********* DEFINES *******************//
#define SYNCWAIT_SECONDS 10


volatile enum mode {UNLOCKED_SLAVE, LOCKED_SLAVE, MASTER}
    sysMode;

int main(void)
{
WDTCTL = WDTPW | WDTHOLD; // stop watchdog timer

initCS();

sysMode = UNLOCKED_SLAVE;

    initGPIO();

    initTB0();                              // Timing 10 seconds waiting for sync to come in

    enableConfig();

    __bis_SR_register(GIE);                 // Enable global interrupts


    // Wait around in a loop so long as we are unlocked
    // and waiting.  If rising edge comes in on P2.3, ISR starts TA0
    // and then triggers sets mode to LOCKED_SLAVE.
    while( sysMode == UNLOCKED_SLAVE ){ }

    // Here, we are either in sysMode == MASTER ( timerB ran out in SYNCWAIT_SECONDS)
    //   and then  sysMode-->MASTER in the ISR
    //   ...or we are in sysMode == LOCKED_SLAVE, where timerA was started upon
    //   entry to Port1 ISR and the ISR changed sysMode-->LOCKED_SLAVE
    //   I.E.  at this point there should be no chance sysMode == UNLOCKED_SLAVE


    stopTB0();

while(1)
{





}//while(1)




return 0;
}//main()


void initCS(void)
{
    PJSEL0 |= BIT4;                                        // Set PJ.4 as XIN, digital square wave input
    PJSEL1 &= ~BIT4;

    CSCTL0_H = 0xA5;                                       // Unlock password for updating CS

    //CSCTL1 |=                                            // Don't care about this register, not using DCO
    CSCTL2 = SELM__XT1CLK + SELS__XT1CLK + SELA__XT1CLK;   // MCLK source = XT1CLK;  SMCLK source = XT1CLK;
                                                           // ACLK source = XT1CLK
    CSCTL3 = DIVM__1 + DIVS__2 + DIVA__32;                 // MCLK/1;  SMCLK/2;  ACLK/32

    CSCTL4 |= XT1BYPASS | XTS | XT2OFF;                    // Select Bypass mode operation
                                                           // for XT1; select high-freq. mode;
                                                           // Don't need XT2 so turn it off
    do
    {
        CSCTL5 &= ~XT1OFFG;                               // Clear XT1 fault flag
        SFRIFG1 &= ~OFIFG;
        __delay_cycles(10000);                            // Wait around a little while before next check
    } while (SFRIFG1&OFIFG);                              // Test oscillator fault flag




    PJDIR  |= BIT5;      // Set PJ.5 as a GPIO output ( don't need XOUT )

    PJDIR  |=  BIT0;     // Set PJ.0 as SMCLK output
    PJSEL0 |=  BIT0;
    PJSEL1 &= ~BIT0;

    PJDIR  |=  BIT1;     // Set PJ.1 as MCLK output
    PJSEL0 |=  BIT1;
    PJSEL1 &= ~BIT1;

    PJDIR  |=  BIT2;     // Set PJ.2 as ACLK output
    PJSEL0 |=  BIT2;
    PJSEL1 &= ~BIT2;




}//initCS()

void initTA0(void)
{
    TA0CTL = TACLR;                                // Set TACLR to ensure reset of timer logic
    TA0CCTL0 = CCIE;                               // CCR0 interrupt enabled
    TA0CCR0 = pulseTiming[0];                      // Initialize CCR0
    TA0EX0  =  TAIDEX_0;                           // Expansion divider: /1
    TA0CTL |= TASSEL_1 | MC__CONTINUOUS | ID__8;   // TA0 clock source = ACLK;
                                                   // Continuous mode
                                                   // Input divider: /8 = 62.5kHz or 16us/tick
}//initTA0



void initTB0(void)
{
    TB0CCTL0 = CCIE;                               // CCR0 interrupt enabled
    TB0CCR0 = 62500;                               // 62500 ticks * 16us/tick = 1 second
    TB0EX0 = TBIDEX_0;                             // Expansion divider: /1
    TB0CTL = TBSSEL_1 | MC__CONTINUOUS | ID__8;    // TB0 clock source = ACLK,
                                                   // continuous mode;
                                                   // Input divider:  /8 = 62.5kHz or 16us/tick

}//initTB0

void stopTB0(void)
{
    TB0CTL = MC__STOP;                            // REMMEBER TO RE-INTIALIZE A STOPPED TIMER
}//stopTB0()


void enableConfig(void)
{
    PM5CTL0 &= ~LOCKLPM5;                   // Disable the GPIO power-on default high-impedance mode
                                            // to activate previously configured port settings
}//enableConfig()

void initGPIO(void)
{
    // Pulse output pin P1.0
    P1DIR |=  BIT0;              // Set P1.0 as an output pin
    P1OUT &= ~BIT0;              // Initialize P1.0 to low

    // Sync sensing pin P2.3
    P2DIR &=  ~BIT3;             // Set P2.3 as an input pin
    P2REN |=  BIT3;              // Enable pu or pd on pin
    P2OUT &= ~BIT3;              // Select pull-down resistor on pin
    __delay_cycles(5);           // Give time for pullup to settle
    P2IES &= ~BIT3;              // Interrupt edge select low-to-high
    P2IFG &= ~BIT3;              // Ensure no interrupt is pending
    P2IE  |=  BIT3;              // Enable interrupt on P2.3
}//initGPIO()




// Timer A0 interrupt service routine
// This timer responsible for sending pulse train out on P1.0
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer0_A0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) Timer0_A0_ISR (void)
#else
#error Compiler not supported!
#endif
{
    P1OUT ^= BIT0;                           // Pulse output pin
    TA0CCR0 += pulseTiming[pulseIdx];

    if(pulseIdx < numEdges - 1) { pulseIdx++;   }
    else                        { pulseIdx = 0; }


}

// Timer B0 interrupt service routine
// This timer times 10 seconds for syncronization, otherwise, we
// become master and start sending pulses on our own time.
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = TIMER0_B0_VECTOR
__interrupt void Timer0_B0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_B0_VECTOR))) Timer0_B0_ISR (void)
#else
#error Compiler not supported!
#endif
{
    if( secondsWaiting < 9)
    {
        secondsWaiting++;
    }
    else{
        secondsWaiting = 0;        // 10 seconds has passed without a sync pulse,
        TB0CTL = MC__STOP;         // so stop the 10 second timer,
        sysMode = MASTER;          // and switch to MASTER mode ( this unit becomes master )
        initTA0();                 // ... and start pulses on P1.0
    }
}


// Port 2 interrupt service routine
// Upon rising edge on P2.3, start TA0 as soon as possible
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(PORT2_VECTOR))) Port_2 (void)
#else
#error Compiler not supported!
#endif
{

    initTA0();                                // Initialize and start Timer_A w/
                                              // its associated pulse high-low timings
    P2IE  &= ~BIT3;                           // Disable the interrupt
    P2IFG &= ~BIT3;                           // Clear P2.3 IFG

    //P1OUT |= BIT0;                          // DEBUG:  Test if this actually gets us to ISR
    sysMode = LOCKED_SLAVE;


}



--- End code ---

What am I missing here?  Why is there ~1ms delay between the low-to-high edge on SLAVSYNC and SLAVOUT started with Timer A0?

smoothVTer:
I figured it out.

P1.0 starts out low.

TimerA0 is started.

TimerA0 is now timing a LOW time ... not a high time.

Upon entry in Port_2 ISR, must flip P1.0 manually, THEN call initTA0(), THEN increment pulseIdx++, and finally update sysMode.

With this code change I am able to sync pulses with a latency of only about 16us.   

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod