External Interrupts, Analog compare and Sine Wave measurement

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have two programs one is Nick Gammons frequency counter using  D5 as the interrupt to Timer1, and the other is an analog comparator using the ISR to count the 'ticks' into Timer Counter 1

 

I am trying to accurate counts in the 4 to 20 HZ Sine Wave range with a quick update for a changing frequency

 

I have a signal generator connected with a 5V P-P wave and a 2.5V offset to test with.

 

Nicks program measures down to these low sine wave values but due to having a fixed sample period (say 500mS) then the answers for odd harmonics will oscillate between the one up and one down even value. This is sort of understandable in that the sample  numbers of the waveforms is fractional for odd frequencies as can be seen by the following, but in the  sampling period at least two complete waveforms should have been detected from 5 Hz up

 

     

Sample Period(mS)

500

 

Hz

mS

No

4

250.0

2

5

200.0

2.5

6

166.7

3

7

142.9

3.5

8

125.0

4

9

111.1

4.5

 

 

The second program uses analog compare with either a set trigger level on AIN1= 2.5 or using the 1.1V internal reference on AIN0

The example below measures over 5 cycles (1 second at 4 hz) with is too slow for my needs

 

The program gives better resolution at lower frequencies but only on SQUARE waves and rubbish !!!!!! on sine waves, which is breaking my heart

 

So there are some questions here:

a) Why does Nick's program oscillate at the low values considering it should be seeing complete waveforms in the 500mS fixed sample period?

b) What is the internal hardware difference between D5 to the analog comparator on AIN0/AIN1 that makes D5 work better? 

c) Why would the analog comparator work fine on low Hz square waves but not in sine?

d) Normally the advise is that D5 should vary between 0 to 5V, but what is its actual trigger level on D5 as I have had success with lower amplitude signals but cant capture the switch band?

 

Enclosed is Nick' program

// Timer and Counter example  ATMEGA 328
// Author: Nick Gammon
// Date: 17th January 2012

// Input: Pin D5

// these are checked for in the main program
volatile unsigned long timerCounts;
volatile boolean counterReady;

// internal to counting routine
unsigned long overflowCount;
unsigned int timerTicks;
unsigned long timerPeriod;

void startCounting (unsigned int ms) 
  {
  counterReady = false;         // time not up yet
  timerPeriod = ms;             // how many 1 ms counts to do
  timerTicks = 0;               // reset interrupt counter
  overflowCount = 0;            // no overflows yet

  // reset Timer 1 and Timer 2
  TCCR1A = 0;             
  TCCR1B = 0;              
  TCCR2A = 0;
  TCCR2B = 0;

  // Timer 1 - counts events on pin D5
  TIMSK1 = bit (TOIE1);   // interrupt on Timer 1 overflow

  // Timer 2 - gives us our 1 ms counting interval
  // 16 MHz clock (62.5 ns per tick) - prescaled by 128
  //  counter increments every 8 µs. 
  // So we count 125 of them, giving exactly 1000 µs (1 ms)
  TCCR2A = bit (WGM21) ;   // CTC mode
  OCR2A  = 124;            // count up to 125  (zero relative!!!!)

  // Timer 2 - interrupt on match (ie. every 1 ms)
  TIMSK2 = bit (OCIE2A);   // enable Timer2 Interrupt

  TCNT1 = 0;      // Both counters to zero
  TCNT2 = 0;     

  // Reset prescalers
  GTCCR = bit (PSRASY);        // reset prescaler now
  TCCR2B =  bit (CS20) | bit (CS22) ;  // prescaler of 128
  TCCR1B =  bit (CS10) | bit (CS11) | bit (CS12); // External clock source on T1 pin (D5). Clock on rising edge.
  //TCCR1B =  bit (CS11) | bit (CS12);              // External clock source on T1 pin (D5). Clock on falling edge.
  }  // end of startCounting

ISR (TIMER1_OVF_vect)
  {
  ++overflowCount;               // count number of Counter1 overflows  
  }  // end of TIMER1_OVF_vect


//******************************************************************
//  Timer2 Interrupt Service is invoked by hardware Timer 2 every 1 ms = 1000 Hz
//  16Mhz / 128 / 125 = 1000 Hz

ISR (TIMER2_COMPA_vect) 
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = TCNT1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;

  // see if we have reached timing period
  if (++timerTicks < timerPeriod) 
    return;  // not yet

  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 256)
    overflowCopy++;

  // end of gate time, measurement ready

  TCCR1A = 0;    // stop timer 1
  TCCR1B = 0;    

  TCCR2A = 0;    // stop timer 2
  TCCR2B = 0;    

  TIMSK1 = 0;    // disable Timer1 Interrupt
  TIMSK2 = 0;    // disable Timer2 Interrupt
    
  // calculate total count
  timerCounts = (overflowCopy << 16) + timer1CounterValue;  // each overflow is 65536 more
  counterReady = true;              // set global flag for end count period
  }  // end of TIMER2_COMPA_vect

void setup () 
  {
  Serial.begin(57600);       
  Serial.println("Frequency Counter");
  } // end of setup

void loop () 
  {
  // stop Timer 0 interrupts from throwing the count out
  byte oldTCCR0A = TCCR0A;
  byte oldTCCR0B = TCCR0B;
  TCCR0A = 0;    // stop timer 0
  TCCR0B = 0;    
  
  startCounting (500);  // how many ms to count for

  while (!counterReady) 
     { }  // loop until count over

  // adjust counts by counting interval to give frequency in Hz
  float frq = (timerCounts *  1000.0) / timerPeriod;

  Serial.print ("Frequency: ");
  Serial.print ((unsigned long) frq);
  Serial.println (" Hz.");
  
  // restart timer 0
  TCCR0A = oldTCCR0A;
  TCCR0B = oldTCCR0B;
  
  // let serial stuff finish
//  delay(200);
  }   // end of loop

 Enclosed is my analog comparator program

/* Yet another Analog Comp frequency Counter
   Rod McMahon 
   1 Nov 2017    

Connection option 1
 - Signal generator connected D7(AIN1) and GND  D6(AIN0) = 1.1v by internal reference 
Connection option 2
 - Signal generator connected D6(AIN0) and GND D7(AIN1 connected to external trigger voltage 
*/ 

#define F_CPU 16000000UL              //16 MHZ Arduino Nano
volatile int overflows = 0;           // Overflow counter on TimerCounter No 1
volatile int trigger = 0;             // counts triggers
volatile float tstart = 0;             // time at start of count
volatile float tstop = 0;              // time at stop of count
volatile int cycles = 5;              // number of cycles sampled
//float frequency = 0;
int SampleFinish=0;



 /*
 Analog Interrupt ACSR options 
 ACSR|=(1<<ACD);         // Bit 7 - 1 stops comparator 
 ACSR|=(1<<ACBG);        // Bit 6 - 1 Enables fixed bandwith on AO (1.1 V)
 ACSR  (ACO)             // Bit 5 - 1 if comparator fires
 ACSR|=(1<<ACI);         // Bit 4 - 1 = Set by hardware when comparator event triggers the interrupt mode
 ACSR|=(1<<ACIE);        // Bit 3 - 1= Activates interrupt
 ACSR|=(1<<ACIC);        // Bit 2 - 1= triggers input function of Timer/Counter 1  
 ACSR|=(1<<ACIS1);       // Rising Edge           
 ACSR|=(1<<ACIS0); 
  0 0  Comparator Interrupt on output toggle
  1 0   Falling Edge
  1 1   Rising Edge
*/
void setAnalogComp()
 {
 DIDR1 = (1<<AIN1D) | (1<<AIN0D);      // disable digital inputs
 ADCSRB=0;               
 ACSR=0;
// ACSR|=(1<<ACBG);        // Bit 6 - 1 = Enables fixed bandwith on AO (1.1 V) ( Option 1)
 ACSR|=(1<<ACIE);        // Bit 3 1= Activates interrupt
 ACSR|=(1<<ACIC);        // Bit 2 1= triggers input function of Timer/Counter 1 
 ACSR|=(1<<ACIS1);       // Bit1 and Bit 0 Rising Edge 
 ACSR|=(1<<ACIS0);  
 }


void setTimerCounter1()
{   
 TIMSK1|=(1<<TOIE1);          // enable overflow interrupt
 TCCR1A=0;                    // Set Timer/Counter 1 in normal mode where it will count to 0xFFFF then repeat.
 TCCR1B=0;                    // initiate
 TCCR1B|=(1<<CS10);           // No prescaler.
}

void setup(void) 
  {
  setTimerCounter1();
  setAnalogComp();
  sei();
  Serial.begin(57600);       // For printing the frequency to the terminal
  Serial.println("Started");
  }

void loop(void) 
  {
  if (SampleFinish==1)                  // total cycle interrupts counted
    {
  int  frequency = F_CPU*cycles/(tstop - tstart)+1; 
    Serial.print("Frequency  :");Serial.println(frequency); 
    delay(200);
    overflows=0;
    trigger=0;
    SampleFinish=0;
    TCNT1=0;
    }
  }

ISR(TIMER1_OVF_vect)
{
overflows += 1;
}

ISR(ANALOG_COMP_vect)
  {
  unsigned long tnow = overflows*65535.0 + TCNT1; // TimerCounter counter value (16 bit=65535)
  if (SampleFinish==0) 
    {
    trigger += 1;
    if (trigger == 1)           
      tstart = tnow;            // set start time
    if (trigger> cycles)  
     {
      tstop = tnow;             // set finish time
      SampleFinish=1;
     } 
   }
  }

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
volatile uint32_t overflows = 0;           // Overflow counter on TimerCounter No 1
volatile uint16_t trigger = 0;             // counts triggers
volatile uint32_t tstart = 0;             // time at start of count
volatile uint32_t tstop = 0;              // time at stop of count
volatile int cycles = 5;              // number of cycles sampled
//float frequency = 0;
volatile uint8_t SampleFinish=0;



 unsigned long tnow = overflows*65535.0 + TCNT1; // TimerCounter counter value (16 bit=65535)
 
 uint32_t tnow = (uint32_t)overflows * 65535UL + (uint32_t)TCNT1;
 
 //but then you copy this value to a float!

 

Use unsigned if you don't expect negative values. Chose your variable sizes carefully - the AVR is an 8 bit micro, so 8bit vars are preferred.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

16 bits is 65536 not 65535. Which means you can speed up the ISR hugely.

"This forum helps those that help themselves."

"How have you proved that your chip is running at xxMHz?" - Me

"If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Multiplication by 65536  would be replaced by "shifts" (Ram copies)? 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you all for that. One day I will figure units out! Changed the program and tested. Used float for calcs to get some decimal places

Square waves great , sine awful

 

For a 5.73 Hz sine signal 5VPP 2.5V offset and trigger voltage of 2.5V

these are the measured values

7.16

8.14

5.73

7.16

11.37

7.16

5.73

7.16

7.16,

5.73

7.16

7.16

5.73

7.16

9.55

 

So to the original questions?

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rodmcm wrote:

...these are the measured values

7.16

8.14

5.73

...

 

Which version of the code is giving these?

 

Also, you are not initialising your peripherals from scratch, ie too many '|=' in there. As you are running inside the Arduino framework you are starting out with their peripheral initialisation and adding to them, not taking control of them and making them your own.

 

I'd start by reading up on how to do 'raw' C code, even if you use the Arduino IDE to write it, and take full control of the AVR. Ditch 'setup()' and 'loop()' and just have your own 'while(1)' in there. Arduino is great for getting something up and running but too much goes on hidden away for serious work.

"This forum helps those that help themselves."

"How have you proved that your chip is running at xxMHz?" - Me

"If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rodmcm wrote:

a) Why does Nick's program oscillate at the low values considering it should be seeing complete waveforms in the 500mS fixed sample period?

 

Who knows. Unfortunately both programs above demonstrate the classic error people make when commenting code. They tell us WHAT a line of code does and not WHY.

 

For example...

tstart = tnow;            // set start time

 

What does the comment tell us that we cannot see directly from the code? We can see a variable called 't(ime)start' being set to the value of 't(ime)now'. Which is what the comment says. But WHY is 'tstart' being set to 'tnow'?

 

And this...

 

ISR (TIMER1_OVF_vect)
  {
  ++overflowCount;               // count number of Counter1 overflows  
  }  // end of TIMER1_OVF_vect

 

What is Timer 1 doing? What is clocking it? Why are we using it? The only two comments attached to this bit of code are totally redundant.

 

 

Rodmcm wrote:

b) What is the internal hardware difference between D5 to the analog comparator on AIN0/AIN1 that makes D5 work better? 

 

Luck. Although I suspect there is something wrong with how you are using the comparator.

 

 

Rodmcm wrote:

c) Why would the analog comparator work fine on low Hz square waves but not in sine?

 

How 'pure' is your sine wave? Is it monotonic?

What hysteresis does the comparator have?

Does the comparator have a minimum slew rate it will work with?

 

 

Rodmcm wrote:

d) Normally the advise is that D5 should vary between 0 to 5V, but what is its actual trigger level on D5 as I have had success with lower amplitude signals but cant capture the switch band?

 

Digital inputs do not have a 'trigger level' They have a voltage below which they will be seen as a '0' and a voltage above which they will be seen as a '1'. Between those two figures they are free to do whatever they like (and often do).

 

Table 34.2 in the datasheet tells us that a low is any voltage below 0.3Vcc, so 1.5V in your case; and a high is anything above 0.6Vcc, so 3V in your case. Between those two voltages you can assume nothing.

"This forum helps those that help themselves."

"How have you proved that your chip is running at xxMHz?" - Me

"If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand." - Heater's ex-boss

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry Brian but i don't know how to do cut and paste of your comment to answer to, however

 

a) Program: The readings are from my analog comp program

 

b) Raw Code: I am not a trained programmer hence the easier Arduino framework. I would be interested in sighting your rendition of my program in "raw code" to learn and compare speed and performance

 

c) Comments: I thought that i did quite a good job of commenting, while the (1<<ACGB) statements seem clumsy they are certainly easier to read and adjust than most of the alternatives. As to the other variables I thought that anyone with knowledge of use of timers for period measurement would easily see the intent without detailed comment. As to Nick' program he can respond to your criticisms directly if he wishes

 

d)  Comparator: I have sought out as many examples of analog comparator use that i could find, and tested the ACSR options. I usually exhaust all options before I ask for help, but as i work alone it is easy to miss things and then ask "should have seen" dumb questions. If you can see the error please advise

 

e) Sine Wave.. I use a commercial sig gen along with a Rigol scope. The waveform is clean, and i have a small capacitor on the input to reduce noise, but not a low pass filter ( next experiment)

 

f) Comparator Hysteresis Slew rate et al: This is exactly the information I am asking about the built in comparator in the Atmel chips. I cannot find any literature its characteristics other than statements about how it works and the options ( there are really only two)  I had hoped someone with detailed knowledge of the inner workings of the comparator could answer this. 

 

g) Digital. Fair call, but certainly not what i have seen under experiment. The reason I am interested is my project wave is not a pure sine wave and will oscillate between 2.5V to 1V. If I cannot get the analog comparator option to work then I will have to put in an externals Schmidt trigger or the like for input to D5, which I am loath to do when there is a built in comparator

 

h) Further Information

 

I have found that if I just use the measurement of one cycle (analog compare and external trigger voltage) then the answers are repeatable and accurate to two decimal places at the lower sine wave levels

 

So why would the multiple cycle accuracy be wrong as shown above.. 4 Hz is 250mS so only 1/4 of F_CPU to count per cycle.

And what is really going on in the analog comparator?

 

 

 

 

 

 

 

 


 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I’d add code to output the comparator state to a port pin and scope it. If there’s oscillation, then hopefully it will be visible.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Rodmcm wrote:
i don't know how to do cut and paste of your comment to answer

Just press the 'Quote' button - it will give you the whole post enclosed between 'quote=<author>' and '/quote' tags 

 

The tags are just plain text - you can manually add more yourself, or just use the   'Block Quote' button on the toolbar...

 

EDIT

 

FIX quote

 

EDIT 2

 

When I say,

between 'quote=<author>' and '/quote' tags

those single-quote will actually be square brackets. 

 

Just try the button - you'll see what I mean.

 

Or you can just use the standard copy & paste facilities for your platform, with the 'Block Quote' button ...

 

#HowToQuote

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
Last Edited: Fri. Nov 17, 2017 - 10:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks for that

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The reason I am interested is my project wave is not a pure sine wave and will oscillate between 2.5V to 1V.

I am trying to accurate counts in the 4 to 20 HZ Sine Wave  

Need some more info to work with.

Is the Peak-to-Peak sine wave 1 V varying with a max of 2.5 V PP?

Or is the negative peak 1 V and the positive peak 2.5?

 

How accurately do you need to measure the frequency?

 

Is the DC offset for the signal changing also, or just the amplitude of the signal?

 

What else must the micro do?

(Display the frequency on an LCD, respond to push button switches, flash an LED with every period?)

 

If the DC offset changes, how fast, and by how much?

 

As it sounds like you are working on a one-off project, and not selling millions of units, spending a buck or two on hardware for the analog front end signal processing ought not be an issue.

 

One method yo might wish to consider would be:

 

Feed Input Signal through a very low cut off frequency low pass filter.

The output of this is essentially the DC average of the signal.

It can vary up and down a bit as the input signal changes its DC offset.

 

Feed the LPF'd DC offset into an op-amp.

Feed the original sin wave into the op-amp.

 

You can configure the op-amp in any of several methods, including as a comparator, or as an inverting amp, etc.

If you configure it as an amp you make the gain high, so the op-amp "clips" (or saturates), high and low as the sin wave goes positive or negative.

 

Either way, you are converting the sine wave to a square wave.

 

The nice thing about using an external comparator is that you get to control both input signals, and you make one of them the DC baseline for the signal.

 

It now, also, doesn't matter what the amplitude of the sine wave is, within reason.

A several mV P-P sinewave will still give you a nice square wave output, as will a several volt signal.

 

Now, with the input signal converted to a square wave, feed it to the micro.

 

There are many options for calculating the signal's frequency when the frequency is so low.

 

If you use the input capture you can measure the pulse's width, (1/2 period), but at very slow frequencies care has to be taken in making this work.

Very similar to this is using the input signal and the pin change interrupt.  Once again with low frequencies you will have to deal with watching the timer/counter's overflow.

 

A "simpler" approach might be to set up a Timer/Counter to generate a once per 1 mSec interrupt.

Your program watches the input signal pin go high and then starts counting the 1 mSec interrupts.

When the signal goes low, you stop counting the interrupts, and now know how many mSec, 1/2, (or 1, depending on your software), period is.

That might give, counting 1 full period, +/- 2% error.

 

So, change the interrupt to fire at 10 times per mSec, or every 0.1 mSec, and the timing resolution improves, and the 20 Hz signal is now measured to +/- 0.2 %.

 

If the micro is running at 16 MHz, and it takes a couple of clock cycles to recognize that the input signal went high, then doing this edge detection in software adds some error to the system, compared to letting the Timer/Counter hardware directly detect the input signal change in state.  So the first two methods avoid this error.

But were talking about a few clock cycles, so a uSec or 2, compared to a 50 mSec period signal, (20 Hz).

 

(Hence the question as to the desired accuracy.)

 

I guess the real point is this, if you convert the sine wave to a square wave externally, you can control that conversion and have the circuit compensate for various changes in DC off set and make amplitude variations irrelevant.

 

Once the signal is "digital", i.e. a "square wave", then there are many ways to use the Timer/Counter to trigger the counting, or count timer pulses, etc., so many ways to then "measure" the frequency of the input signal.

 

You can then, also, determine whether you wish to measure each cycle's period for display (or whatever the use is), or measure each cycle's period and average them for display/use, or measure the "period" over several cycles, and then calculate the average for that sample session, (essentially a "gate time").

 

Good luck with your project!

 

JC

 

 

  

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DocJC wrote:
Need some more info to work with.

Is the Peak-to-Peak sine wave 1 V varying with a max of 2.5 V PP?

Or is the negative peak 1 V and the positive peak 2.5?

Clearest way to answer that would be with a diagram ...

 

How to post images: https://www.avrfreaks.net/comment...

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...