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.

Timer0 capture for pulse width measuring



Hi all,

I have a problem with a code that i am developing and need your help. Next, the information about the problem:

I have a PWM signal from a range sensor (MaxBotix EZ3) with a period of 20Hz as an input into the Timer0 in capture mode into P4.0. I have to measure the pulse width (147us/inch) to calculate the proximity of the sensor to the ground.

For the microcontroller I have a 8MHz xtal and MCLK = ACLK = SMCLK = 8MHz. For the timer i used the ACLK clock source. The value of the capture result is sent via USART0 to the PC.

The problem is that i receive bad data. Whith the sensor in horizontal position pointing to the roof and using the osciloscope to check the PWM signal, the output are pulses of 13ms (147us/inch) that corresponds to 210cm, that is correct. Always the same. But when i capture the signal with the Timer0 capture module, i receive random values allways arround 60cm. I think the problem is related to frequencies of both the clock and the PWM signal or to the synchronization, but i tried all kind of posible solutions but allways receive the same random data.

Could you guys help me and tell me where is my error. Next i add my code:

#include  <msp430x16x.h>

unsigned int range;
unsigned char index = 0;
unsigned int measure;
unsigned int measure_1;
unsigned int measure_2;

main()
{
   
  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT
  P3SEL |= 0x30;                            // P3.4,5 = USART0 TXD/RXD
  BCSCTL1 |= XTS;                           // ACLK= LFXT1= HF XTAL
  do
  {
    IFG1 &= ~OFIFG;                           // Clear OSCFault flag
    for (i = 0xFF; i > 0; i--);               // Time for flag to set
  }
  while ((IFG1 & OFIFG));                   // OSCFault flag still set?
  BCSCTL2 |= SELM_3;                        // MCLK = LFXT1 (safe)

 //USART configuration
 
  ME1 |= UTXE0 + URXE0;                     // Enable USART0 TXD/RXD
  U0CTL |= CHAR;                            // 8-bit character
  U0TCTL |= SSEL0;                          // UCLK= ACLK
  U0BR0 = 0x45;                             // 8MHz 115200
  U0BR1 = 0x00;                             // 8MHz 115200
  U0MCTL = 0x00;                            // 8MHz 115200 modulation
  U0CTL &= ~SWRST;                          // Initialize USART state machine
  IE1 |= URXIE0;                            // Enable USART0 RX interrupt
 
  //TimerB capture configuration
 
    TBCCTL0 |= CM_3 + SCS + CCIS_1 + CAP + CCIE;
    TBCTL |= CNTL_0 + TBSSEL_1 + MC_2;
 
  _BIS_SR(GIE);   // interrupt
  
  P2DIR = 0x01;                             // P2.0=ACLK
  P2SEL = 0x01;
  P4DIR = 0x00;                             // Set P4.0 input dir
  P4SEL = 0x01;                             // Set P4.0 to TB0

  while (1)
  { }

}


#pragma vector=TIMERB0_VECTOR
__interrupt void TimerB0(void)
{
  unsigned int rangeH;
  unsigned char rangeHrot;
  unsigned int rangeL;
 
  measure= TBCCR0;

  index++;

  if (index == 1)
  {
      measure_1 = measure;
  }
  if (index == 2)
  {
      measure_2 =measure;

      range = (measure_2 - measure_1);
    
         
        rangeH = (0xFF00 & range);    //USART needs unsigned char --> I need to separate range in rangeH and rangeL
        rangeHrot  = (unsigned char)(rangeH/256); //rotate 8 bits
        rangeL= (unsigned char)(0x00FF & range);
  
    
      TXBUF0= rangeHrot;
        while (!(IFG1 & UTXIFG0));
      TXBUF0= rangeL;
        while (!(IFG1 & UTXIFG0));
       
     
      TBCCTL0 &= ~CCIFG;
     
      index=0;
  }
   
 
}

Thank you for your help in advance. I wait for your suggestions.

Greetings!!!

  • You need to consider the fact that the timer/counter and the capture/compare registers are all 16-bit wide. Thus the largest number of counts you can get is 65535. At 8MHz, the longest interval you can get is about 8.1 ms.

    The simplest solution is to sacrifice the resolution to extend the range. That is, set up Timer to use ACLK/2 and get 4MHz instead of the current 8MHz. Now you can measure intervals up to 16.3 ms (which is enough to cover your 13ms situation).

    There are ways to extend the 16-bit to more bits. But try the above simple way first,

  • Hi  old_cow_yellow,

    thanks for your suggestion but unfortunately, this idea didn't work. I have tried to use different ACLK clock dividing by 2 but still receive random data... You can see what i receive in the picture that i attach. I expect to receive the same value allways but is not like that.

    Any other suggestions...? thank you very much.

     

    Greetings!!

  • Hi again,

    I am a fool, I think i found the problem, but not sure although it is important to fix it. I was using a 5V PWM signal as an input to the MSP430 (Vcc= 3.3V). Hope that the microcontroller is not broken... I have to look for a solution... Not too much tricky i think.

    I know this is important but not sure if it can be so important for the capture module to "capture" the signal and give me a constant (not random) value at the output though not the correct one...

    Could someone clarify me it?

     

    Thanks a lot...

  • And again here....

    I fixed the voltages problem but still the same problem of random data at the output of the capture module...

    Waiting for suggestions...

     

    Thanks a lot in advance...

  • I may have misunderstood your problem. I thought your only problem was: when you think it should read 210cm, you got 60cm instead. Now it sounds like you got random data at any distance. I suggest that you isolate the problem of Timer0 capture from UART, PC, calculation etc.

    (1) Set up Timer0 capture as before, but use ACLK/2=4 MHz, not 8 MHz

    (2) Do not use UART to talk to PC. Instead, save some captured data in:

    static unsigned int saved[10];

    static char idx=0;

    The ISR should be as follows:

    {

    saved[idx++] = CCR0;

    if (idx==10) idx = 0;

    }

    (3) Use Debugger to run this code for a short while, stop it, and inspect saved[0..9].

    (Your debugger may not be able to handle LPM. Thus do not use LPM, use an infinit loop instead.)

    Do the saved data make sense? Let me know what you get.

  • Hi old_caw_yellow,

    Yes, my problem is that i receive random data at any distance. I spoke of 210cm because i make the tests with static PCB and always at the same distance. Sorry, was my error...

    mmm, seems like you are on the right track. I made what you said and found good values for the range sensor output. I attach two pictures, one for the 8MHz and other for 4MHz. As you said, i got the correct data with 4MHz. In the pictures you can see the values of the variable "saved" but with more samples. On the one hand, i think that my problem is the way that i split the variable "range". Anyway, I use the same lines in other piece of code in the same program and it works perfectly (i receive the expected data for example using the ADC12 module for an accelerometer...).

    rangeH = (0xFF00 & range);    //USART needs unsigned char --> I need to separate range in rangeH and rangeL
    rangeHrot  = (unsigned char)(rangeH/256); //rotate 8 bits
    rangeL= (unsigned char)(0x00FF & range);

     

    But on the other hand, maybe the problem is the way that I calculate the range because the difference can be negative if the TBCCR0 capture the pulse just before the overflow (then start again to increaset from 0x00). To solve this, i tried to calculate the range with this formula

    range = (65536 - measure_1) + measure_2;  

    And i got good data too. You can see the result in the third picture I attach.

    i.e: range[3]/4e+6=12.8ms

    So finally, the problem must be the way that i send the data to the PC. In this case the casts ((unsigned char)) that I make can be the problem. I make it because the USART only can send 8-bits data.Do you think that is the problem? Because, as i said before, i use the same three lines with the ADC12 and it works perfectly... Also i tried just make range=0xABCD and the receiving was perfect... Where is the error?

    Thanks a lot for your help, i was getting crazy with this, and soon we'll find a solution ...

     

    Greetings!!

     

    Pictures.rar
  • Hi Alejos,

    try using a union to split a integer value into two char values (high- and lowbyte). Try the code below:

    // OWN DATA TYPE DEFINITION
    /* ============================================================================
    // union to convert integer to two unsigned char or vice versa
    // VALUE          : holds integer value
    // LOWBYTE        : lowbyte of integer value
    // HIGHBYTE       : highbyte of integer value
    // ==========================================================================*/
    union SPLITINTTOBYTE
    {
      unsigned int VALUE;
      struct
      {
        unsigned char LOWBYTE;
        unsigned char HIGHBYTE;
      }SPLIT;
    };

    You will have to access the data of the union as follows:

    union SPLITINTTOBYTE ulocal_test;   // define a variable ulocal_test which is of type SPLITINTTOBYTE
    unsigned char uclocal_high = 0;    // local variable to hold high-byte; used for testing only
    unsigned char uclocal_low = 0;    // local variable to hold low-byte; used for testing only

    ulocal1.VALUE = 0xffaa;     // I'm assigning a value to the integer part of the union for the example

    uclocal_high = ulocal_test.SPLIT.HIGHBYTE;  // ulocal_high should read as 0xff
    uclocal_low = ulocal_test.SPLIT.LOWBYTE;  // ulocal_low should read as 0xaa

    Now, insert an additional line which is:
    ulocal_test.SPLIT.LOWBYTE = ulocal_test.SPLIT.HIGHBYTE; // Have a look at ulocal1.VALUE! What is the new value of it??

    And now, what the heck is this used for?? Try this in your example (shown above)

    TXBUF0= ulocal_test.SPLIT.HIGHBYTE; // send high-byte
     while (!(IFG1 & UTXIFG0)); 
     TXBUF0= ulocal_test.SPLIT.LOWBYTE; // send low-byte
     while (!(IFG1 & UTXIFG0));

    Maybe this 'Hardcore-C Code' (or whatever you will call it) if of good use for the community too.
    aBUGSworstnightmare

  • Hi aBUGSworstnightmare,

     

    thanks for the code.  I will try to use it,  but i am unable to understand this code, sorry. I don't see how or when do you really split the 16-bit register into two 8-bits registers. Anyway i will tray to use it... Maybe this is the solution i am waiting for...

     

    More possible alternatives will be wellcome... Thank you!!

     

    Alejos

  • Hi Alejos,

    to understand the code you only need to know how unions work; maybe this helps http://publications.gbdirect.co.uk/c_book/chapter6/unions.html (or have alook at your prefered C language book).

    Rgds
    aBUGSworstnightmare 

    P.S. I think that testing the code will aid you in understanding the UNION!

  • Hi,

    aBUGSworstnightmare, you were right. Your code works perfectly. I receieve a good LOWBYTE and HIGHBYTE in the example 0xFFAA. But, unfortunately again, my problem remains the same when i send the data from the sensor to the PC. Still random data received Oh!!! I hate the word RANDOM..  I will need more suggestions... Please, can anybody help me?

    Thank you for your help.

     

    Alejos

  • Please, don't forget me!! I need your help... I am not able to find the solution to this problem... Something must be happening when i send the good data to the PC and i cannot see it. The code to send data continues working with the ADC so i don't understand why is not working with the range sensor... If the variable contains good data why is the PC receiving other random values?  I need, at least, suggestions to continue working on the problem until find a solution... The project is almost working completely except this part...

     

    Thanks for your help...

     

    Alejos

  • Divide and conquer!

    You correctly captured the rising and falling edges of the PWM waveform.

    But I am not sure if you know which one is which. Thus I think you have a 50-50 chance of getting the pulse width right.

    You correctly split a 16-bit number into two 8-bit numbers. (You have two ways to do that, I think they are both correct.)

    I am not sure if you transmitted those two bytes correctly to the PC. And I am not sure you interpreted those two bytes correctly.

    I suggest that you use your current code and change only one of the lines -- namely, instead send the first byte, send a constant, e.g. 0x34. Can you see that constant on the PC side?

**Attention** This is a public forum