MSP430:   How to start TimerA with minimal latency from a GPIO signal?  - Page 1
Forum section: electronics / general

author: smoothVTer
author_rank: Regular Contributor
timestamp: February 01, 2018, 11:41:57 pm
content: 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 possibleafter 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 alatency of 1.975msbetween 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>// Prototypesvoid 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 ticksunsigned int pulseTiming = {62, 620};volatile unsigned int numEdges = 2;volatile unsigned int pulseIdx = 0;int main(void){WDTCTL = WDTPW | WDTHOLD; // stop watchdog timerinitCS();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 inputPJSEL1 &= ~BIT4;CSCTL0_H = 0xA5; // Unlock password for updating CS//CSCTL1 |= // Don't care about this register, not using DCOCSCTL2 = SELM__XT1CLK + SELS__XT1CLK + SELA__XT1CLK; // MCLK source = XT1CLK; SMCLK source = XT1CLK;// ACLK source = XT1CLKCSCTL3 = DIVM__1 + DIVS__2 + DIVA__32; // MCLK/1; SMCLK/2; ACLK/32CSCTL4 |= XT1BYPASS | XTS | XT2OFF; // Select Bypass mode operation// for XT1; select high-freq. mode;// Don't need XT2 so turn it offdo{CSCTL5 &= ~XT1OFFG; // Clear XT1 fault flagSFRIFG1 &= ~OFIFG;__delay_cycles(10000); // Wait around a little while before next check} while (SFRIFG1&OFIFG); // Test oscillator fault flagPJDIR |= BIT5; // Set PJ.5 as a GPIO output ( don't need XOUT )PJDIR |= BIT0; // Set PJ.0 as SMCLK outputPJSEL0 |= BIT0;PJSEL1 &= ~BIT0;PJDIR |= BIT1; // Set PJ.1 as MCLK outputPJSEL0 |= BIT1;PJSEL1 &= ~BIT1;PJDIR |= BIT2; // Set PJ.2 as ACLK outputPJSEL0 |= BIT2;PJSEL1 &= ~BIT2;}//initCS()void initTA0(void){TA0CTL = TACLR; // Reset everything firstTA0CCTL0 = CCIE; // CCR0 interrupt enabledTA0CCR0 = pulseTiming; // Initialize CCR0TA0EX0 = TAIDEX_0; // Expansion divider: /1TA0CTL |= TASSEL_1 | MC__CONTINUOUS | ID__8; // TA0 clock source = ACLK;// Continuous mode// Input divider: /8 = 62.5kHz or 16us/tick}//initTA0void 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.0P1DIR |= BIT0; // Set P1.0 as an output pinP1OUT &= ~BIT0; // Initialize P1.0 to low// Sync sensing pin P2.3P2DIR &= ~BIT3; // Set P2.3 as an input pinP2REN |= BIT3; // Enable pu or pd on pinP2OUT &= ~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 pinTA0CCR0 += pulseTiming;if(pulseIdx < numEdges - 1) { pulseIdx++; }else { pulseIdx = 0; }}//TimerA0 ISR

author: coppice
author_rank: Super Contributor
timestamp: February 02, 2018, 12:21:00 am
content: 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.

author: voltsandjolts
author_rank: Unknown
timestamp: February 02, 2018, 07:12:22 pm
content: TLDR; but in general for pulse measurement/generation its better to use capture/compare registers and leave the timer itself running all the time.

author: smoothVTer
author_rank: Regular Contributor
timestamp: February 02, 2018, 07:23:09 pm
content: 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 SLAVThe 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 ticksunsigned int pulseTiming = {62, 620}; /// ~1ms high, ~10ms lowvolatile unsigned int numEdges = 2;volatile unsigned int pulseIdx = 0;volatile unsigned int secondsWaiting = 0;// ********* DEFINES *******************//#define SYNCWAIT_SECONDS 10volatile enum mode {UNLOCKED_SLAVE, LOCKED_SLAVE, MASTER}sysMode;int main(void){WDTCTL = WDTPW | WDTHOLD;// stop watchdog timerinitCS();sysMode = UNLOCKED_SLAVE;initGPIO();initTB0(); // Timing 10 seconds waiting for sync to come inenableConfig();__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_SLAVEstopTB0();while(1){}//while(1)return 0;}//main()void initCS(void){PJSEL0 |= BIT4; // Set PJ.4 as XIN, digital square wave inputPJSEL1 &= ~BIT4;CSCTL0_H = 0xA5; // Unlock password for updating CS//CSCTL1 |= // Don't care about this register, not using DCOCSCTL2 = SELM__XT1CLK + SELS__XT1CLK + SELA__XT1CLK; // MCLK source = XT1CLK; SMCLK source = XT1CLK;// ACLK source = XT1CLKCSCTL3 = DIVM__1 + DIVS__2 + DIVA__32; // MCLK/1; SMCLK/2; ACLK/32CSCTL4 |= XT1BYPASS | XTS | XT2OFF; // Select Bypass mode operation// for XT1; select high-freq. mode;// Don't need XT2 so turn it offdo{CSCTL5 &= ~XT1OFFG; // Clear XT1 fault flagSFRIFG1 &= ~OFIFG;__delay_cycles(10000); // Wait around a little while before next check} while (SFRIFG1&OFIFG); // Test oscillator fault flagPJDIR |= BIT5; // Set PJ.5 as a GPIO output ( don't need XOUT )PJDIR |= BIT0; // Set PJ.0 as SMCLK outputPJSEL0 |= BIT0;PJSEL1 &= ~BIT0;PJDIR |= BIT1; // Set PJ.1 as MCLK outputPJSEL0 |= BIT1;PJSEL1 &= ~BIT1;PJDIR |= BIT2; // Set PJ.2 as ACLK outputPJSEL0 |= BIT2;PJSEL1 &= ~BIT2;}//initCS()void initTA0(void){TA0CTL = TACLR; // Set TACLR to ensure reset of timer logicTA0CCTL0 = CCIE; // CCR0 interrupt enabledTA0CCR0 = pulseTiming; // Initialize CCR0TA0EX0 = TAIDEX_0; // Expansion divider: /1TA0CTL |= TASSEL_1 | MC__CONTINUOUS | ID__8; // TA0 clock source = ACLK;// Continuous mode// Input divider: /8 = 62.5kHz or 16us/tick}//initTA0void initTB0(void){TB0CCTL0 = CCIE; // CCR0 interrupt enabledTB0CCR0 = 62500; // 62500 ticks * 16us/tick = 1 secondTB0EX0 = TBIDEX_0; // Expansion divider: /1TB0CTL = TBSSEL_1 | MC__CONTINUOUS | ID__8; // TB0 clock source = ACLK,// continuous mode;// Input divider: /8 = 62.5kHz or 16us/tick}//initTB0void 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.0P1DIR |= BIT0; // Set P1.0 as an output pinP1OUT &= ~BIT0; // Initialize P1.0 to low// Sync sensing pin P2.3P2DIR &= ~BIT3; // Set P2.3 as an input pinP2REN |= BIT3; // Enable pu or pd on pinP2OUT &= ~BIT3; // Select pull-down resistor on pin__delay_cycles(5); // Give time for pullup to settleP2IES &= ~BIT3; // Interrupt edge select low-to-highP2IFG &= ~BIT3; // Ensure no interrupt is pendingP2IE |= 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 pinTA0CCR0 += pulseTiming;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 timingsP2IE &= ~BIT3; // Disable the interruptP2IFG &= ~BIT3; // Clear P2.3 IFG//P1OUT |= BIT0; // DEBUG: Test if this actually gets us to ISRsysMode = 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?

author: smoothVTer
author_rank: Regular Contributor
timestamp: February 02, 2018, 10:35:08 pm
content: 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.

author: jbb
author_rank: Super Contributor
timestamp: February 04, 2018, 05:16:53 am
content: 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.

author: danadak
author_rank: Super Contributor
timestamp: February 04, 2018, 08:33:49 pm
content: 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 havebeen 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 allocatinglarge amounts of SRAM to heap, easily changed.Everything is onchip except for push button.Regards, Dana.

author: coppice
author_rank: Super Contributor
timestamp: February 04, 2018, 10:27:12 pm
content: Quote from: smoothVTer on February 02, 2018, 10:35:08 pmIs 16us OK for you? You do realise you can get the latency far lower than that, don't you?

author: bson
author_rank: Unknown
timestamp: February 13, 2018, 10:21:11 am
content: 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:QuoteIf 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