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/MSP430FR2311: Input Capture/Reading from Water Flow Sensor

Part Number: MSP430FR2311
Other Parts Discussed in Thread: MSP430F5529,

Tool/software: Code Composer Studio

Hello,

I have written some code to capture the count values from a water flow sensor when water flows through it. I based my code from another example written for an msp430f5529 which is here e2e.ti.com/.../461042.

#include <msp430.h>

//initialize variables
volatile unsigned int counter=0;
volatile unsigned int prev_counter=0;
unsigned int difference=0;
double pulse_freq;
double flow_rate=0.0;

__attribute__((ramfunc));
int main(void)
{
    WDTCTL = WDTPW | WDTHOLD; //Stop watchdog timer

    // Disable the GPIO power-on default high-impedance mode to activate
    // previously configured port settings
    PM5CTL0 &= ~LOCKLPM5;

    // Using Pin 2.1 / TB1.2
    P2DIR &= ~BIT1;//Bit 1 Set as 1
    P2SEL1 |= BIT1;//Bit 1 Set as 1

    // TB1CCR2 (Configure timer_B capture/compare register2);
    TB1CCTL2 = CAP + CM_3 + CCIE + SCS + CCIS_1;//capture mode + on both rising&falling edge + enable interrupts + synchronize capture source + Capture input select CCIxB

    // SMCLK, Cont Mode; start timer
    TB1CTL |= TBSSEL_2 + MC_2 + TBCLR;

    while(1) {
         __bis_SR_register(LPM0_bits + GIE); //enter low power mode0 with interrupt enable
         __no_operation();
         __delay_cycles (60000000);//delay for 60 sec before calculating L/min

        difference = counter - prev_counter;
        pulse_freq = 1/difference;

        flow_rate = (pulse_freq*60)/4.7; //multiply by delay (60sec) to get pulses/min and divide by constant from flow sensor datasheet.

        //The COV bit is used by the timer module as notification in case of overflow condition
        //where a second timer capture event occurs before the first/previous capture event can be read through the CCIFG bit
        if(TB1CCTL2 & COV) //Check for capture overflow
        {
          TB1CCTL2 &= ~COV; // Clear the COV bit when it has been detected
        }
             }

}

//Timer_B1 TBCCR1 Interrupt Vector Handler Routine
#pragma vector = TIMER1_B0_VECTOR
__interrupt void TIMER1_B0_ISR (void)
{
  switch(__even_in_range(TB1IV,0x0E))
  {
      case TB1IV_NONE: break;  //Vector 0: No interrupt
      case TB1IV_TBCCR1:       //Vector 2: TBCCR1 CCIFG. Interrupt source:capture/compare R1. Interrupt Flag: TBxCCR1 CCIFG.
          prev_counter = counter;
          counter = TB1CCR1;   // 'Counter' value is copied to TB1CCR1 register
          __bic_SR_register_on_exit(LPM0_bits + GIE); //exit LPM0?
          break;
      case TB1IV_TBCCR2: break;//Vector 4: TBCCR2 CCIFG
      //case TB1IV_TBCCR3: break;
      //case TB1IV_TBCCR4: break;
      //case TB1IV_5: break;
      //case TB1IV_6: break;
      case TB0IV_TBIFG: break; //Vector 6: TBIFG. CCIFG should be set, TBxCCRy register should be set as 0xCAFE
      default: break;

  }
}

I am using TimerB1.2 and SMCLK. I setup TB1CCTL2 and TB1CTL accordingly, input capture mode, both edges rising and falling. My flow sensor is connected to Pin 2.1.

My problem is that my code will not go into the interrupt. I've been debugging for a while and when I make water flow through the sensor, it just stays in the '__bis_SR_register(LPM0_bits + GIE)' line. Also, the values of 'counter' and 'prev_counter' stay at 0.

I made it work using the F5529 launchpad. But I need it to work for the FR2311. I'm thinking there is a problem with how my interrupt vector is setup or my timerB1.2 is not configured correctly.

Any and all help would be greatly appreciated.

Thanks

  • Hello,
    many thanks for reaching out to us for support.
    I would suggest to use code examples of the used device in general, when it comes to the register settings, means in this case the MSP430FR2311.
    In respect of the code, you have posted, I would suggest you to try two modifications:
    1. Please enable in addition to the TB1CCTL2 CCIE also the interrupt for the TimerB1 in the TB1CTL TB1CTL |= TBSSEL_2 + MC_2 + TBCLR + TBIE
    2. I'd recommend staying always with the BIT and register defines from TI, so in case of the TB1IV jump table
    switch(__even_in_range(TB1IV,TB1IV_TBIFG))
    { ...........

    Please check if this helped to solve your problem.
    Furthermore in general, when debugging Timer interrupt functionality:
    1. Check if the selected clock source is running (output SMCLK to GPIO)
    2. The input section of the CCRx block offers connections to GND and VCC. If you toggle these settings by SW, you can create any edge, which should trigger a Capture interrupt. This can be done also by halting the code execution and manipulation of the respective control bits.

    Best regards
    Peter
  • > #pragma vector = TIMER1_B0_VECTOR
    Try:
    > #pragma vector = TIMER1_B1_VECTOR

    > case TB1IV_TBCCR1: //Vector 2: TBCCR1 CCIFG. Interrupt source:capture/compare R1. Interrupt Flag: TBxCCR1 CCIFG.
    You enabled CCIE on CCR2, not CCR1. Try:
    > case TB1IV_TBCCR2:

    > TB1CCTL2 = CAP + CM_3 + CCIE + SCS + CCIS_1;//capture mode + on both rising&falling edge + enable interrupts + synchronize capture source + Capture input select CCIxB
    Based on SLASE58C Table 6-14, I think you want CCI2A, not CCI2B here. (It doesn't help that there appears to be a typo in the "MODULE OUTPUT SIGNAL" column.)Try:
    > TB1CCTL2 = CAP + CM_3 + CCIE + SCS + CCIS_0;//capture mode + on both rising&falling edge + enable interrupts + synchronize capture source + Capture input select CCIxA

    > pulse_freq = 1/difference;
    The result of this arithmetic will be either 0 or 1 (or whatever divide-by-0 gives). I suggest scaled arithmetic here.

    Unsolicited: Use of both edges implies that the pulse train will be a 50% square wave, which I haven't found to be the case in flow meters. I suggest sticking to either rising or falling, so you're counting the entire wave.
  • I want to thank you and Peter for your valuable input. I edited my code taking your suggestions into account and my program can enter the interrupt. Another primary reason I believe that was not allowing the interrupt to work was the configuration of the P2SEL1 and P2SEL0 registers. According to the User's Guide Section 7.4.9 "The values of each bit position in PxSEL1 and PxSEL0 are combined to specify the function. 00b = General-purpose I/O is selected, 01b = Primary module function is selected, 10b = Secondary module function is selected, and 11b = Tertiary module function is selected". I'm not sure if I selected the secondary module function or general purpose I/O, but the interrupt works.

    #include <msp430.h>
    
    //initialize variables
    volatile unsigned int counter=0;
    volatile unsigned int prev_counter=0;
    unsigned int difference=0;
    //double flow_rate=0.0;
    
    __attribute__((ramfunc));
    int main(void)
    {
        WDTCTL = WDTPW | WDTHOLD; //Stop watchdog timer
    
        // Disable the GPIO power-on default high-impedance mode to activate
        // previously configured port settings
        PM5CTL0 &= ~LOCKLPM5;
    
        // P2.1/TB1.2
        P2DIR &= ~BIT1;  //Set P2.1 to input direction
        P2SEL1 &= ~BIT1;
        P2SEL0 |= BIT1;
    
        // TB1CCR2; CCI2A input;
        TB1CCTL2 = CAP + CM_1 + CCIE + SCS + CCIS_0; //capture mode + on rising edge + enable interrupts + synchronize capture source + Capture input select CCI2A
    
        // SMCLK, Cont Mode; start timer
        TB1CTL |= TBSSEL_2 + MC_2 + TBCLR;
    
        while(1) {
            __bis_SR_register(LPM0_bits + GIE); //enter low power mode0 with interrupt enable
            __no_operation();
            __delay_cycles (1000000); //delay for 1sec to get amount of rising pulse edges in 1sec
    
            difference = counter - prev_counter; //amount of rising pulse edges
            //flow_rate = ((double)difference*60)/4.7; //multiply rising pulse edges by 60 to get pulses per min. Divide by constant from datasheet to get L/min.
    
            if(TB1CCTL2 & COV) //Check for capture overflow
            {
              TB1CCTL2 &= ~COV; // Clear the COV bit when it has been detected
            }
                 }
    }
    
    //Timer_B1 TBCCR2 Interrupt Vector Handler Routine
    #pragma vector = TIMER1_B1_VECTOR
    __interrupt void TIMER1_B1_ISR (void)
    {
      switch(__even_in_range(TB1IV,TB1IV_TBIFG))
      {
          case TBIV__NONE: break;       //Vector 0: No interrupt
          case TBIV__TBCCR1: break;     //Vector 2: TBCCR1 CCIFG. Interrupt source:capture/compare R1. Interrupt Flag: TBxCCR1 CCIFG.
          case TBIV__TBCCR2:            //Vector 4: TBCCR2 CCIFG
              prev_counter = counter;
              counter = TB1CCR2;        // 'Counter' value is copied to TB1CCR2 register
              __bic_SR_register_on_exit(LPM0_bits + GIE); //exit LPM0
              break;
          case TBIV__TBCCR3: break;
          case TBIV__TBCCR4: break;
          case TBIV__TBCCR5: break;
          case TBIV__TBCCR6: break;
          case TBIV__TBIFG: break;      //Vector 6: TBIFG
          default: break;
      }
    }

    Now, I am having trouble getting valid readings for this flow sensor in Liters/min. I tested the sensor by pouring water through it from a 1Liter bottle at a rate of about 1L/min. After several trial runs (for consistency) I recorded the values for one of these trials. 'Counter' = 63947, 'prev_counter'=32547, and 'difference'=31400. My train of thought was that 'difference' would be the amount of rising pulse edges. If I delayed for 1second before calculating 'difference', I would get the amount of rising pulse edges in 1sec. Multiplying 'difference' by 60 would give me rising pulses per minute. Then dividing by the datasheet constant (4.7) would give me water flow in L/min. But calculating this value for flow_rate by hand (using difference=31400) gives me 400,851 and this is definitely not an valid flow rate.

    Are my values for 'counter' and 'prev_counter' wrong? I am using SMCLK which runs at 1MHz. Also, Code Composer will not allow me to debug my code if I uncomment my 'flow_rate' equation because it takes up too much memory along with the 'delay_cycles' line.

    Maybe I'm taking a completely wrong approach. Again, any help or advice would be greatly appreciated.

    Many thanks.

  • Hello,
    many thanks for your feedback and status update on this.
    First please let me comment on the PSEL settings. The User's Guide is a description of the respective modules of a device family, which of course should be correct, and if we get the information about potential errors, we try to correct these as soon as possible. But even when it is correct, one has to keep in mind, the information might not contain all details, related to device specific conditions. Thus it is recommended, when checking about necessary control register settings for the pin functionality selection, to check with the device specific datasheet. The datasheet always contains for each pin of the MSP430 a table of the available GPIO/module functions and the respective control bit settings.

    Now coming to your current problem. To me it sounds you're using an external flow sensor device, which delivers pulses based on the flow it detects. Debugging potential issues with this device, is something you would need to consult the manufacturer of this device, unless it is a TI device.
    In terms of the MSP430 related portion I would suggest the following:
    1. I would try to verify, how many pulses this sensor generated at the MSP430 capture pin. This is something you could do with an external counter device, sniffing the capture/sensor signal. This way you would know, what to expect at the MSP430 side.
    2. Looking at your code, I'd suggest a few modifications.
    a) You're storing the capture values into counter and and prev_counter in the interrupt service routine, but process the difference in the main loop. This is possible, but you would need to find a mechanism, to handle potential issues with overwriting of those values. The risk depends on the frequency of the incoming capture pulses. With the simple code probably no issues. For a real final code, I'd tend processing the difference in an interrupt service routine.
    b) The more severe thing is the handling of the overflow bit COV. You're just clearing it, but do not use it for any corrections on your capture values correction. You have to keep in mind, if the COV is set, it means you had an overflow, before you processed the Timer interrupts, thus your stored values of the capture events will be corrupted, as they do no longer reflect the difference between the first and last event.
    c) You're using the delay loop for the 1s timing generation. For quick and dirty tests, this might be ok, but for a real code, I would recommend using a timer, set to respective 1s time frame interrupts. The MSP could go LPM meanwhile, to save current.
    d) I think the whole flow of the code does not match currently, what you're trying to implement.
    - you're entering the while(1) with enable capture interrupts
    - you enter the LPM0 with enabled GIE
    - in the TimerB ISR you're updating the stored Timer values triggered by capture events and exit the ISR with a wake up from LPM0
    - so after every capture event you include a delay of 1s. In the mean time, dependent on the frequency of the pulses from the sensor, potentially many capture events occurred, before you processed the difference.
    But even if you would manage to process in time, I think these values are not what you want to have.
    I assume, the sensor device, dependent on the flow, is delivering respective number of pulses. So to get the information about the flow, you need to count the pulses. Currently you're counting the pulses of the Timer, which from my understanding are in no relation to the flow.
    So if my assumptions are correct, you need to feed the pulses from the sensor device into the Timer as external clock, and then, to get the flow per second, you need to use another Timer, to create the 1s interrupt, creating by SW a capture event, to read out the Timer value, which in this case would reflect the amount of counted sensor device pulses, means would reflect the actual flow.

    Best regards
    Peter
  • The difference you're computing is the number of SMCLK ticks (at 1MHz) between pulses, so difference=31400 indicates 31400/1MHz=.0314 seconds (31.4ms). In units, that's .0314sec/pulse, so 1/.0314 = ~31.8 pulses/sec, or 31.8Hz. There should be a Hz->Flow conversion factor in your meter's user manual (or calibration certificate).

    That long delay is not ideal, but I don't think it's giving you "wrong" answers. It's just increasing your latency (1 answer/sec) and keeps you from doing averaging. Long term, you'll probably want to do this slightly differently, but I suggest getting everything else worked out first.

    The floating-point code is pretty big, though I would have guessed it would fit on an FR2311; apparently not. I suggest scaled arithmetic here.
  • A tiny bit of algebra offers a simpler way to combine conversion+scaling:

    1000000UL (SMCLKs/sec)/31400(SMCLKs/pulse) gives 31 pulses/sec

    If you want another digit of accuracy, you could put 10*1000000 in the numerator (result would be in "10th-pulses/sec"). Rounding is left as an exercise for the reader.

**Attention** This is a public forum