Author Topic: MSP430: How to start TimerA with minimal latency from a GPIO signal?  (Read 2876 times)

0 Members and 1 Guest are viewing this topic.

Offline smoothVTerTopic starter

  • Regular Contributor
  • *
  • Posts: 145
  • Country: us
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: [Select]
#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








 

Online coppice

  • Super Contributor
  • ***
  • Posts: 8650
  • Country: gb
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #1 on: February 02, 2018, 12:21:00 am »
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.
 

Offline voltsandjolts

  • Supporter
  • ****
  • Posts: 2300
  • Country: gb
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #2 on: February 02, 2018, 07:12:22 pm »
TLDR; but in general for pulse measurement/generation its better to use capture/compare registers and leave the timer itself running all the time.
 

Offline smoothVTerTopic starter

  • Regular Contributor
  • *
  • Posts: 145
  • Country: us
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #3 on: February 02, 2018, 07:23:09 pm »
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: [Select]
#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;


}



What am I missing here?  Why is there ~1ms delay between the low-to-high edge on SLAVSYNC and SLAVOUT started with Timer A0?
« Last Edit: February 02, 2018, 07:26:04 pm by smoothVTer »
 

Offline smoothVTerTopic starter

  • Regular Contributor
  • *
  • Posts: 145
  • Country: us
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #4 on: February 02, 2018, 10:35:08 pm »
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.   
 

Offline jbb

  • Super Contributor
  • ***
  • Posts: 1145
  • Country: nz
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #5 on: February 04, 2018, 05:16:53 am »
If you can accept a delay of 1 whole pulse output cycle, it might be best to have Timer A running continuously, then use input capture to find the counts when the input pulse goes low again. Then you can use PWM to make the slave pulse.

Alternatively, have you considered using a GPIO-triggered DMA transfer to push appropriate pre-cooked values into the timer control registers? (DMA transfers are quicker than CPU and don’t have interrupt latency.) It’s hackey as hell, but could be fun.
 

Offline danadak

  • Super Contributor
  • ***
  • Posts: 1875
  • Country: us
  • Reactor Operator SSN-583, Retired EE
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #6 on: February 04, 2018, 08:33:49 pm »
I was curious about this, so tried on a PSOC. The delay is fixed by the delay of the D.
When trig occurs the PWM is reloaded, so could have a single runt worst case generated,
then both streams identical except offset by a gate delay.

Could have synched and avoided runt, but then delay from trigger worst case would have
been one period. Just got lazy.

One line of code required, to start the PWM component. No ISR used, no latency.

Ignore the SRAM % resources used. Thats because PSOC Creator has a default setting allocating
large amounts of SRAM to heap, easily changed.

Everything is onchip except for push button.


Regards, Dana.

« Last Edit: February 05, 2018, 12:49:01 pm by danadak »
Love Cypress PSOC, ATTiny, Bit Slice, OpAmps, Oscilloscopes, and Analog Gurus like Pease, Miller, Widlar, Dobkin, obsessed with being an engineer
 

Online coppice

  • Super Contributor
  • ***
  • Posts: 8650
  • Country: gb
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #7 on: February 04, 2018, 10:27:12 pm »
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.
Is 16us OK for you? You do realise you can get the latency far lower than that, don't you?
 

Offline bson

  • Supporter
  • ****
  • Posts: 2270
  • Country: us
Re: MSP430: How to start TimerA with minimal latency from a GPIO signal?
« Reply #8 on: February 13, 2018, 10:21:11 am »
I believe while TACLR is set the prescalers are held at 0.  When you clear it they're released and the timer runs.  So maybe try changing initTA0 into:

Quote
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
    TA0R = 0;                     // For good measure
    TA0CTL &= ~TACLR;      // Run like the wind!
}//initTA0

If this is the case then you can probably preinitialize the timer and release it by clearing TACLR when you see your pulse on P2.3.

Also, check the IFG in your ISR:

Quote
{
  if (TA0CTL & TAIFG) {
    P1OUT ^= BIT0;                           // Pulse output pin
    TA0CCR0 += pulseTiming[pulseIdx];

    if(pulseIdx < numEdges - 1) { pulseIdx++;   }
    else                        { pulseIdx = 0; }
  }
}
« Last Edit: February 13, 2018, 10:35:28 am by bson »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf