This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/MSP432P401R: Capture the Frequency of the PIN with Timer_A

Part Number: MSP432P401R


Tool/software: Code Composer Studio

Hi everyone

I need to capture the frequency of a pin in msp432p401r. I need help in completing my code. 

I have defined the input pin as CCIxA: P10.4.  

Can you please help me with how I can read the frequency? Should use interrupts? I have brought a snippet of code for your review.

Thanks

#include "msp.h"


/**
 * main.c
 */

void main(void)
{
	WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;		// stop watchdog timer

    /*********************************TIMER_A0******************************/
	     NVIC_ClearPendingIRQ(TA0_0_IRQn); // Clear any stale status
	     NVIC_EnableIRQ(TA0_0_IRQn);  // Enable TA0_0 in the interrupt controller
	     TIMER_A0->CTL = TIMER_A_CTL_TASSEL_2 | //Select SMCLK as source for timer
	     TIMER_A_CTL_ID_3 | //Divide clock by 8 (this yields 6 MHz for the timer clock)
	     TIMER_A_CTL_MC_2; //Continuous mode
	     TIMER_A_CTL_CLR ; //Clear timer count
	     TA0CTL = TAIE; //Timer_A interrupt enable.
	     TA0CCTL0 = CM_1 + CCIS_0 + CAP; //Capture mode:Rising edge | Capture/compare input select (CCIxA: P10.4) | Capture mode |

  • Approximately what frequency range are you expecting? It makes a difference:

    1) To measure frequencies less than maybe 5kHz, Capture usually works better, since each capture pair gives you a pretty good instantaneous measurement. The difference between two consecutive captures is the period (in timer ticks), and the frequency is 1/period.

    2) To measure frequencies greater than maybe 50kHz, you should use frequency counting by bringing in the signal on e.g. TA0CLK and counting the number of pulses in a fixed period. If e.g. you see 5 ticks in 1ms, that's 5cycles/.001s = 5000Hz.

    3) For frequencies in between, either is probably fine.

    There's a capture example (msp430p401x_ta0_capture) in the Examples suite here:

    http://dev.ti.com/tirex/explore/node?node=AOqyDJonBtS4QACP3tJVEg__z-lQYNj__LATEST

    I don't see a frequency-counting example.

  • Thanks for your reply.

    Actually the signal is coming from an encoder used to measure/control the angular velocity of a motor shaft. So, the signal frequency ranges between approximately 13kHz to around160 kHz. In this case, which of the mentioned suggestions you think would work better?

    Thanks

    Saber

  • ollowing the example you mentioned, msp430p401x_ta0_capture. I changed the code to investigate if it goes to the timer interrupt handler. To do so, I defined a variable flag and put it on the interrupt handler. The flag is set when the interrupt occurs and the value of TIMER_A0->CCR[0] is stored in variable a. In the while loop, first, the flag is checked and if it is set, the code prints a. The pint associated with TA0CLK is P10.4. I tested the program. And it doesn't go to the interrupts

    #include "msp.h"
    
    
    /**
     * main.c
     */
    long long int a=0;
    int flag=0;
    void main(void)
    {
    
    	WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;		// stop watchdog timer
    
        /*********************************TIMER_A0******************************/
    	     /******Interrupt Activator*************/
    	     NVIC_ClearPendingIRQ(TA0_0_IRQn); // Clear any stale status
    	     NVIC_EnableIRQ(TA0_0_IRQn);  // Enable TA0_0 in the interrupt controller
    	     /*******Tegister Configurations******/
    	     // Configure GPIO
    	     P1->DIR |= BIT0;                        // Set P1.0 as output
    	     P1->OUT |= BIT0;                        // P1.0 high
    	     P2->DIR |= BIT1;                        // Set P2.1 as output
    	     P2->OUT &= ~BIT1;                        // P2.1 high
    	     P10->SEL0 |= BIT4;                       // TA0.CCI0A input capture pin, second function
    	     P10->DIR &= ~BIT4;
    	     // Timer0_A0 Setup
    	         TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CM_1 |      // Capture rising edge,
    	                             TIMER_A_CCTLN_CCIS_0 |    // Use CCI2A=ACLK,
    	                             TIMER_A_CCTLN_CCIE |      // Enable capture interrupt
    	                             TIMER_A_CCTLN_CAP |       // Enable capture mode,
    	                             TIMER_A_CCTLN_SCS;        // Synchronous capture
    	     // Timer0_A0 Setup
    	     TIMER_A0->CTL = TIMER_A_CTL_TASSEL_2 | //Select SMCLK as clock source for timer
    	                     TIMER_A_CTL_MC__CONTINUOUS |    // Start timer in continuous mode
    	                     TIMER_A_CTL_CLR;                // clear TA0R
    	     SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;    // Enable sleep on exit from ISR
    	     // Enable global interrupt
    	     __enable_irq();
    	     while(1)
    	     {
    	    if(flag==1)
    	    {
    	    flag=0;
    	    P2->OUT ^= BIT1;
    	     }
    	     }
    }
    // Timer A0 interrupt service routine
    void TA0_N_IRQHandler(void)
    {
            // Capture the CCR register value into the array
            a = TIMER_A0->CCR[0];
            flag=1;
            P2->OUT ^= BIT1;
        // Clear the interrupt flag
        TIMER_A0->CCTL[0] &= ~(TIMER_A_CCTLN_CCIFG);
    }
    

    .

    Do you have any idea where the problem comes from for this code? And how I can calculate the frequency of the signal on TA0CLK with the number in CCR0  and the clock source of SMCLK (3MHz)/

    Thanks

    
    

    
    

  • Per data sheet (SLAS826G) Table 6-79, P10.4 is TA3.CCI0A, i.e. it's connected to TIMER_A3, not TIMER_A0.

    TA0.CCI0A is on P7.3 [Ref Table 6-68]. So you can either (a) move your input wire to P7.3 or (b) change TIMER_A0 to TIMER_A3 (don't forget TA3_0_IRQn), whichever is easier.

    -----------------------

    >         SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;    // Enable sleep on exit from ISR

    Remove this line. Once you get an interrupt, main will never run again.

    -----------------------

    > int flag=0;

    This is used to communicate between main and the ISR, so use:

    > volatile int flag=0;

    --------------------

    [Edit: 

    > void TA0_N_IRQHandler(void)

    This should be 

    > void TA0_0_IRQHandler(void)

    to match with TA0_0_IRQn. If you switch to TA3, it would be TA3_0_IRQHandler. [Ref TRM (SLAU356I) Sec 19.2.6]

     

     
  • Thanks, Bruce for your reply. Now the timer interrupt is working by giving the external signal to P7.3.

    Now, I have a question on how I can calculate the frequency of the signal with this captures. The clock source is SMCLK, 3MHz. Specifically, does the value in TA0CCR0 represents the time interval between two captures?

    For example, I got two interrupts and for the first, CCR0 is 25806 and for the second the CCR0 value is 27301, so should I simply calculate the time between them as 

     (27301-25806)/(3MHz)=0.4 ms. 

    I'd appreciate it if you could help me with this. I have brought the code for your review.

    Thanks

    #include "msp.h"
    
    
    /**
     * main.c
     */
    unsigned int a=0;
    unsigned int b=0;
    volatile int flag=0;
    void main(void)
    {
    
    	WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;		// stop watchdog timer
    
        /*********************************TIMER_A0******************************/
    	     /******Interrupt Activator*************/
    	     NVIC_ClearPendingIRQ(TA0_0_IRQn); // Clear any stale status
    	     NVIC_EnableIRQ(TA0_0_IRQn);  // Enable TA0_0 in the interrupt controller
    	     /*******Tegister Configurations******/
    	     // Configure GPIO
    	     P1->DIR |= BIT0;                        // Set P1.0 as output
    	     P1->OUT |= BIT0;                        // P1.0 high
    	     P2->DIR |= BIT1;                        // Set P2.1 as output
    	     P2->OUT &= ~BIT1;                        // P2.1 high
    	     P7->SEL0 |= BIT3;                       // TA0.CCI0A input capture pin, second function
    	     P3->DIR &= ~BIT3;
    	     P2->SEL0 |= BIT5;                       // TA0.CCI2A input capture pin, second function
    	     P2->DIR &= ~BIT5;
    	     P4->SEL0 |= BIT2;                       // Set as ACLK pin, second function
    	     P4->DIR |= BIT2;
    	     //********** Timer0_A0 Setup********************//
    	         TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CM_1 |      // Capture rising edge,
    	                             TIMER_A_CCTLN_CCIS_0 |    // Use CCI2A=ACLK,
    	                             TIMER_A_CCTLN_CCIE |      // Enable capture interrupt
    	                             TIMER_A_CCTLN_CAP |       // Enable capture mode,
    	                             TIMER_A_CCTLN_SCS;        // Synchronous capture
    	        TIMER_A0->CTL = TIMER_A_CTL_TASSEL_1 | //Select SMCLK as clock source for timer
    	                        TIMER_A_CTL_MC__CONTINUOUS |    // Start timer in continuous mode
    	                        TIMER_A_CTL_CLR;                // clear TA0R
    	   //****************************************************//
    	     CS->KEY = CS_KEY_VAL;                   // Unlock CS module for register access
    	        // Select ACLK = REFO, SMCLK = MCLK = DCO
    	        CS->CTL1 = CS_CTL1_SELA_2 |
    	                CS_CTL1_SELS_3 |
    	                CS_CTL1_SELM_3;
    	        CS->KEY = 0;                            // Lock CS module from unintended accesses
    	        // Ensures SLEEPONEXIT takes effect immediately
    	           __DSB();
    	           NVIC->ISER[0] = 1 << ((TA0_N_IRQn) & 31);
    	     __enable_irq();
    	     __enable_interrupt();
    	  //*******************************************************//
    	     while(1)
    	     {
    	    if(flag==1)
    	    {
    	    flag=0;
    	    P2->OUT ^= BIT1;
    	    printf("b:%d",b);//TIMER_A0->CCR[0]//);
    	    printf("\n");
    	     }
    	     }
    }
    // Timer A0 interrupt service routine
    void TA0_0_IRQHandler(void)
    {
            // Capture the CCR register value into a
           b = TA0CCR0;
            a++;
            flag=1;
            P2->OUT ^= BIT1;
        // Clear the interrupt flag
        TA0CCTL0 &= ~(CCIFG);
    }
    

  • > (27301-25806)/(3MHz)=0.4 ms

    Yes. My calculator says (1/0.000498 sec/cycle)=2006Hz, so that's your instantaneous frequency.

    Eventually the timer (TA0R) will wrap back to 0. If you keep the counts as "uint16_t" (I think that's "unsigned short") the subtraction will still work due to unsigned underflow. 

    The 16-bit counter has a limited period: 65536/3MHz= ~21ms, so your frequency lower bound is 1/21ms=~45Hz. Below that, TA0R wraps completely between captures, and your arithmetic falls apart. You can reduce this lower bound by applying a divider (TACTL:ID, e.g.) to increase your range at the cost of some precision.

    >               NVIC->ISER[0] = 1 << ((TA0_N_IRQn) & 31);

    This looks like a leftover from something else; I think you don't want it. It's effectively NVIC_EnableIRQ(TA0_N_IRQn). 

  • Thanks Bruce!

    So far, to measure the instantaneous frequency of a signal, I need two consecutive captures of a signal and measure the time intervals between these two captures. And then calculate the frequency. Am I on the right path?

    I have defined two variables, capture1, capture2 to allocate the consecutive captures. whenever a capture occurs, in the ISR, the first capture is allocated to capture1, the second one to capture2. To define the order of captures, a variable instant is checked, and based on its value, the order is defined. After the allocation of two consecutive captures, the next capture is allocated to calculate the frequency (instant==2). Also, a variable is incremented if overflow(s) occurs between two captures. I have done this but in while loop, the won't print the calculated frequency in ISR. Seems that the code never exit the ISR. 

    Do you have any idea how I can calculate the frequency in the least interrupt with the main (As I need nine captures for three encoders!:)))

    Thanks

    	     NVIC_ClearPendingIRQ(TA0_0_IRQn); // Clear any stale status
    	     NVIC_EnableIRQ(TA0_0_IRQn);  // Enable TA0_0 in the interrupt controller
    	     /*******Tegister Configurations******/
    	     // Configure GPIO
    	     P1->DIR |= BIT0;                        // Set P1.0 as output
    	     P1->OUT |= BIT0;                        // P1.0 high
    	     P2->DIR |= BIT1;                        // Set P2.1 as output
    	     P2->OUT &= ~BIT1;                        // P2.1 high
    	     P7->SEL0 |= BIT3;                       // TA0.CCI0A input capture pin, second function
    	     P3->DIR &= ~BIT3;
    	     P2->SEL0 |= BIT5;                       // TA0.CCI2A input capture pin, second function
    	     P2->DIR &= ~BIT5;
    	     P4->SEL0 |= BIT2;                       // Set as ACLK pin, second function
    	     P4->DIR |= BIT2;
    	     //********** Timer0_A0 Setup********************//
    	         TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CM_1 |      // Capture rising edge,
    	                             TIMER_A_CCTLN_CCIS_0 |    // Use CCI2A=ACLK,
    	                             TIMER_A_CCTLN_CCIE |      // Enable capture interrupt
    	                             TIMER_A_CCTLN_CAP |       // Enable capture mode,
    	                             TIMER_A_CCTLN_SCS;        // Synchronous capture
    	        TIMER_A0->CTL = TIMER_A_CTL_TASSEL_2 | //Select SMCLK as clock source for timer
    	                        TIMER_A_CTL_ID_3 | //ID=8; SMCLK/8=3MHz/8=375 kHz
    	                        TIMER_A_CTL_MC__CONTINUOUS |    // Start timer in continuous mode
    	                        TIMER_A_CTL_CLR;                // clear TA0R
    	   //************************Cycle Calculation**********************//
    	       Pulse_frequency = 375000;//SMCLK/ID_3;//One pulse frequency.
    	       CYCLE=1/Pulse_frequency;//Duration of one pulse.
    	   //*************************************************************//
    	     CS->KEY = CS_KEY_VAL;                   // Unlock CS module for register access
    	        // Select ACLK = REFO, SMCLK = MCLK = DCO
    	        CS->CTL1 = CS_CTL1_SELA_2 |
    	                CS_CTL1_SELS_3 |
    	                CS_CTL1_SELM_3;
    	        CS->KEY = 0;                            // Lock CS module from unintended accesses
    	        // Ensures SLEEPONEXIT takes effect immediately
    	           __DSB();
    	     __enable_irq();
    	     __enable_interrupt();
    	  //*******************************************************//
    	     while(1)
    	     {
    	         if (TIMER_A0->CCTL[0]&COV)
    	         {overflow++;
    	         TIMER_A0->CCTL[0]&=~COV;}
    	         printf("freqency:%d",frequency);//print frequency
    	     }
    }
    // Timer A0 interrupt service routine
    void TA0_0_IRQHandler(void)
    {
          if (instant==0)
              {instant++;
              capture1=TA0CCR0;
              }
          else if (instant==1)
          {
            // Capture the CCR register value into a
           capture2 = overflow*2^16 + TA0CCR0;
           overflow=0;
           instant++;
           flag=1;
          }
          else
          {
               instant=0;
               duration=(capture2-capture1)*CYCLE;
               frequency=1/duration;
          }
            P2->OUT ^= BIT1;
        // Clear the interrupt flag
        TA0CCTL0 &= ~(CCIFG);
    }

  • You get "frequency: 0", or no output at all?

    How are frequency and CYCLE declared? If they're integer, keep in mind that (int)(1/integer_greater_than_1)==0.

    What frequency are you giving it? If it's really 375kHz, slow it down. This code will not be able to keep up.

    -------------------

    >       capture2 = overflow*2^16 + TA0CCR0;

    "^" is not the exponentiation operator, it's XOR.

    Also COV indicates an overrun (new capture before you've retrieved the old one), not a timer overflow (0xFFFF->0).

    As long as you stay within the prescribed range (the >45Hz I mentioned before) there's no need to count overflows at all, since uint16_t subtraction does the right thing, even if the counter overflows in between.

  • I was getting "frequency: 0". I changed the variable type to float and now it seems to be working.

    The CYCLE is 1/clock_source. For example, if the clock source is ACLK (32kHz), the CYCLE (float) would be 0.00003125 seconds.

    duration=(capture2-capture1)*CYCLE;

    in_frequency(float)=1/CYCLE(float);


    The modified code with 32 kHz clock source is as follows. The problem now is that the motor shaft is rotating with content speed, and as a result, the encoder should give a fixed frequency. Sometimes the in_frequency gives a number which is reasonable and sometimes it gives a frequency different from the first time and does not make sense. Any idea where the problem is and how I can resolve the issue?

    Thanks

    #include "msp.h"
    
    
    /**
     * main.c
     */
    unsigned int a=0;
    unsigned int b=0;
    volatile int flag=0;
    unsigned long long volatile int overflow=0;
    unsigned long long volatile int capture1=0;
    unsigned long long volatile int capture2=0;
    float CYCLE=0;
    int Pulse_frequency;
    int instant=0;
    int frequency=0;
    #define SMCLK   3000000;//SMCLK
    #define ACLK    32000;//ACLK
    #define ID_0     1;
    #define ID_1     2;
    #define ID_2     4;
    #define ID_3     8;
     float duration;
    float in_frequency;
    
    //float frequency;
    void main(void)
    {
    
    	WDT_A->CTL = WDT_A_CTL_PW | WDT_A_CTL_HOLD;		// stop watchdog timer
    
        /*********************************TIMER_A0******************************/
    	     /******Interrupt Activator*************/
    	     NVIC_ClearPendingIRQ(TA0_0_IRQn); // Clear any stale status
    	     NVIC_EnableIRQ(TA0_0_IRQn);  // Enable TA0_0 in the interrupt controller
    	     /*******Tegister Configurations******/
    	     // Configure GPIO
    	     P1->DIR |= BIT0;                        // Set P1.0 as output
    	     P1->OUT |= BIT0;                        // P1.0 high
    	     P2->DIR |= BIT1;                        // Set P2.1 as output
    	     P2->OUT &= ~BIT1;                        // P2.1 high
    	     P7->SEL0 |= BIT3;                       // TA0.CCI0A input capture pin, second function
    	     P3->DIR &= ~BIT3;
    	     P2->SEL0 |= BIT5;                       // TA0.CCI2A input capture pin, second function
    	     P2->DIR &= ~BIT5;
    	     P4->SEL0 |= BIT2;                       // Set as ACLK pin, second function
    	     P4->DIR |= BIT2;
    	     //********** Timer0_A0 Setup********************//
    	         TIMER_A0->CCTL[0] = TIMER_A_CCTLN_CM_1 |      // Capture rising edge,
    	                             TIMER_A_CCTLN_CCIS_0 |    // Use CCI2A=ACLK,
    	                             TIMER_A_CCTLN_CCIE |      // Enable capture interrupt
    	                             TIMER_A_CCTLN_CAP |       // Enable capture mode,
    	                             TIMER_A_CCTLN_SCS;        // Synchronous capture
    	        TIMER_A0->CTL = TIMER_A_CTL_TASSEL_1 | //Select SMCLK as clock source for timer
    	                        TIMER_A_CTL_ID_0 | //ID=8; SMCLK/8=3MHz/8=375 kHz
    	                        TIMER_A_CTL_MC__CONTINUOUS |    // Start timer in continuous mode
    	                        TIMER_A_CTL_CLR;                // clear TA0R
    	   //************************Cycle Calculation**********************//
    	      // Pulse_frequency = 32000;//SMCLK/ID_3;//One pulse frequency.
    	       CYCLE=0.00003125;//Duration of one pulse.
    	   //*************************************************************//
    	     CS->KEY = CS_KEY_VAL;                   // Unlock CS module for register access
    	        // Select ACLK = REFO, SMCLK = MCLK = DCO
    	        CS->CTL1 = CS_CTL1_SELA_2 |
    	                CS_CTL1_SELS_3 |
    	                CS_CTL1_SELM_3;
    	        CS->KEY = 0;                            // Lock CS module from unintended accesses
    	        // Ensures SLEEPONEXIT takes effect immediately
    	           __DSB();
    	     __enable_irq();
    	     __enable_interrupt();
    	  //*******************************************************//
    	     while(1)
    	     {
    	       //  if (TIMER_A0->CCTL[0]&COV)
    	       //  {overflow++;
    	       //  TIMER_A0->CCTL[0]&=~COV;}
    	                   if (flag==1)
    	                   {
    	                   instant=0;
    	                   duration=(capture2-capture1)*CYCLE;
    	                   in_frequency=1/duration;
    	                   flag=0;
    	                   //frequency=1/duration;
    	            //       printf("freqency:%f",in_frequency);//print frequency
    	            //       printf("\n");
    	                   }
    	     }
    }
    // Timer A0 interrupt service routine
    void TA0_0_IRQHandler(void)
    {
          if (instant==0)
              {instant++;
              capture1=TA0CCR0;
              }
          else if (instant==1)
          {
            // Capture the CCR register value into a
           capture2 = TA0CCR0;
           //overflow=0;
           instant=0;
           flag=1;
          }
            P2->OUT ^= BIT1;
        // Clear the interrupt flag
        TA0CCTL0 &= ~(CCIFG);
    }
    

  • > unsigned long long volatile int capture1=0;
    > unsigned long long volatile int capture2=0;
    These should be declared "unsigned short" (uint16_t) so the arithmetic works right. Once you have a difference, you can cast it to something longer.
    > unsigned short  latest_difference = (capture2 - capture1);
    Also, I suggest you do the subtraction in the ISR, and leave the result somewhere (as above). If the ISR comes in and updates capture1 in the middle of main's arithmetic, you'll be subtracting a later time from an earlier time. Referencing latest_difference would be atomic, and so not vulnerable to this.
    Either of these could cause the symptom you describe.
  • Thanks Bruce!

    You guessed right. I changed the data type and now the expected. I have another question:

    I'd like in my code, whenever needed, the capture is enabled and I calculated the coming signal's instantaneous frequency. And then disable it. How can I do this? I don't need continuous calculation of the signal's frequency. Just whenever a function is called, I need to have it.

    Thanks

  • There's no reason you can't leave the timer stopped (MC=0) until you need it, then set MC=2 and wait for "flag" to go to 1, then stop the timer (MC=0) again.

    You may gain something by reading the frequency a few times and averaging them.

  • My issues are totally resolved!

    Thanks Bruce!

  • As this problem has been solved, I will close this thread. Thanks Bruce.

    Eason

**Attention** This is a public forum