简体   繁体   中英

Frequency measurement with 8051 microcontroller

I simply want to continuously calculate the frequency of a sine signal with a comparator input (on the falling edges). The effective target frequency is about ~122 Hz and my implementation works most the time, but sometimes it calculates a wrong frequency with always about ~61Hz (which cannot be possible, I verified this with an oscilloscope).

It seems my implementation has a weakness, perhaps in form of a race condition or misuse of the timer, since it uses concurrent interrupt service routines and manually starts and stops the timer.

I also think the bug correlates with the measured frequency of about ~122Hz, because one timer overflow is pretty much the same:

One Timer Overflow = 1 / (1/8 MHz * 2^16 [Bits]) = 122.0703125 Hz

I am using a 8051 microcontroller (Silicon Labs C8051F121) with the following code:

// defines 
#define PERIOD_TIMER_FREQ 8000000.0 // Timer 3 runs at 8MHz

#define TMR3_PAGE      0x01 /* TIMER 3 */
#define CP1F_VECTOR    12   /* comparator 1 falling edge */
#define TF3_VECTOR     14   /* timer3 reload timer   */

sfr TMR3CN        = 0xC8; /* TIMER 3 CONTROL */
sfr TMR3L         = 0xCC; /* TIMER 3 LOW BYTE */
sfr TMR3H         = 0xCD; /* TIMER 3 HIGH BYTE */

// global variables
volatile unsigned int xdata timer3_overflow_tmp; // temporary counter for the current period
volatile unsigned int xdata timer3_lastValue; // snapshot of the last timer value
volatile unsigned int xdata timer3_overflow; // current overflow counter, used in the main routine
volatile unsigned int xdata tempVar; // temporary variable
volatile unsigned long int xdata period; // the caluclated period
volatile float xdata period_in_SI; // calculated period in seconds
volatile float xdata frequency; // calculated frequency in Hertz

// Comparator 1 ISR has priority "high": EIP1 = 0x40
void comp1_falling_isr (void) interrupt CP1F_VECTOR
{
  SFRPAGE = TMR3_PAGE;
  TMR3CN &= 0xfb;      // stop timer 3

  timer3_lastValue = (unsigned int) TMR3H;
  timer3_lastValue <<= 8;
  timer3_lastValue |= (unsigned int) TMR3L;

  // check if timer 3 overflow is pending 
  if (TMR3CN & 0x80)
  {
    timer3_overflow_tmp++; // increment overflow counter
    TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
  } 

  timer3_overflow = timer3_overflow_tmp;

  // Reset all the timer 3 values to zero
  TMR3H = 0;
  TMR3L = 0;
  timer3_overflow_tmp = 0;
  TMR3CN |= 0x04;     // restart timer 3
}

// Timer 3 ISR has priority "low", which means it can be interrupted by the
// comparator ISR: EIP2 = 0x00
// Timer 3 runs at 8MHz in 16 bit auto-reload mode
void timer3_isr(void) interrupt TF3_VECTOR using 2
{      
  SFRPAGE = TMR3_PAGE;
  timer3_overflow_tmp++;
  TMR3CN &= 0x7f; // Clear over flow flag. This will also clear a pending interrupt request.
}

void main(void)
{
  for(;;)
  {
    ...

calcFrequencyLabel: // this goto label is a kind of synchronization mechanism
                    // and is used to prevent race conditions caused by the ISRs
                    // which invalidates the current copied timer values

    tempVar = timer3_lastValue;
    period  = (unsigned long int)timer3_overflow;
    period  <<= 16;
    period  |= (unsigned long int)timer3_lastValue;

    // If both values are not equal, a race condition has been occured.
    // Therefore the the current time values are invalid and needs to be dropped.
    if (tempVar != timer3_lastValue) 
      goto calcFrequencyLabel;

    // Caluclate period in seconds
    period_in_SI = (float) period / PERIOD_TIMER_FREQ;

    // Caluclate period in Hertz
    frequency = 1 / period_in_SI; // Should be always stable about ~122Hz

    ...
  }  
}

Can someone please help me to find the bug in my implementation?

I can't pin-point the particular bug, but you have some problems in this code.

The main problem is that the 8051 was not a PC, but rather it was the most horrible 8-bit MCU to ever become mainstream. This means that you should desperately avoid things like 32 bit integers and floating point. If you disassemble this code you'll see what I mean.

There is absolutely no reason why you need to use floating point here. And 32 bit variables could probably be avoided too. You should use uint8_t whenever possible and avoid unsigned int too. Your C code shouldn't need to know the time in seconds or the frequency in Hz, but just care about the number of timer cycles.

You have multiple race condition bugs. Your goto hack in main is a dirty solution - instead you should prevent the race condition from happening in the first place. And you have another race condition between the ISRs with timer3_overflow_tmp .
Every variable shared between an ISR and main , or between two different ISR with different priorities, must be protected against race conditions. This means that you must either ensure atomic access or use some manner of guard mechanism. In this case, you could probably just use a "poor man's mutex" bool flag. The other alternative is to change to an 8 bit variable and write the code accessing it in inline assembler. Generally, you cannot have atomic access on an unsigned int on a 8-bit core.

With a slow edge as you would have for low frequency sine and insufficient hysteresis in the input (the default being none ), it would only take a little noise for a rising edge to look like a falling edge and result in half the frequency.

The code fragment does not include the setting of CPT1CN where the hysteresis is set. For your signal you probably need to max it out, and ensure that the peak-to-peak noise on your signal is less that 30mV.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM