Multiple Frequency Counting *SOLVED*

I am struggling with the best way to go about juggling inputs on a nano and need some advice. I need to measure the frequency of two separate PWM sensor signals (0-5V; roughly 50% duty cycle). One (PWM-A) is in the range of 6 Hz to around 2 kHz and the other is 100 Hz to 6 kHz (PWM-B). I'd like to be accurate to around 1 Hz with PWM-A and on PWM-B I can tolerate up to 10 Hz off.

In addition to these two frequency measurements I have two other inputs that i need to be able to capture. One is a TTL signal that needs to be triggered on the falling edge (SIG-A) and has a width of 10 ms (may be 10 us but i don't think it is). The other is a user input (SIG-B) from a push button. Its a high priority signal as well.

All other inputs are not time critical. Output wise the only thing that comes into play is I do need to output 1 PWM signal on any PWM pin. I also will probably at some point use I2C and SPI. Currently my plan is to have SIG-A as INT0 and SIG-B as INT1.

Long story short I'm not sure the best method to measure the frequency of the two PWM signals. The problem is I've researched this a BUNCH and probably over thought it. I've heard for the slower frequencies the "better" way to measure is to use an external interrupt to start a count on an edge and time until the next edge. Most examples i've seen use INT0 and INT1, how ever SIG-A is more time critical than PWM-A or PWM-B. I can give up INT1 (SIG-B) to one of them if need be. I know its best to leave TIMER0 alone as its used by millis(). Here is what I see as possible options but don't know which is "better" or if they make sense:

For all:
SIG-A triggers INT0

Option 1:

  • SIG-B: use falling edge INT1
  • PWM-A: use TIMER1 to set a timer interrupt every X us seconds to check what ever pin PWM-A is on and when there is a change note the time
  • PWM-B: same as PWM-A but use INT2

Option 2:

  • Use INT1 for one PWM signal and get the time of each trigger
  • Use TIMER1 for the other PWM signal and interrupt every x us to check for a change
  • Use either a latch or pin change interrupt for SIG-B

Option 3:

  • Use INT1 for one PWM signal and get the time of each trigger
  • Use TIMER1 input capture
  • Use either a latch or pin change interrupt for SIG-B

Option 4:

  • Some combination I don't know about

First of all: PWM signals have a fixed frequency, what you have are ordinary square waves.

PCINT can be used to interrupt on multiple pins, and each port has its own PCINT. Catch the time of the rising or falling edge of a square wave, and build the time difference for the frequency determination.

The push button does not deserve an interrupt, instead it deserves debouncing.

Thank you for the advice. Yes, it's a square wave with an unmodulated pule width. Much better description. I thought about mentioning PCINT, but decided to omit it.

For such a relatively slow signal would you bother using a timer or just use micros()?

I’m trying to solve a similar problem, two concurrent frenquency measurements while letting the board carry out other functions while it waits for pins to change state. https://forum.arduino.cc/index.php?topic=636135.0

This is what I have so far. But in the event there’s no input on the two pins, the board will just hang around waiting for the pins to change state. I’m trying to get my head around using the micros() function to record the time of the pin state changes but doing it for two different pins I’m not sure about. Yet.

void Revs()  {

    Htime=pulseIn(18,HIGH);      //read high time
    Ltime=pulseIn(18,LOW);        //read low time
    
    Ttime = Htime+Ltime;

    RPM=1000000/Ttime/2*60;    //getting frequency with Ttime is in Micro seconds, then divide frequency by two, as there are 2 pulses per crank revolution to give revs per second, multiplied by 60 to get revs per minute

//Writing RPM value to nixie tubes

    writeToPort = (RPM / 1000 % 10) << 4;
    writeToPort = writeToPort + (RPM / 100 % 10);
    PORTE = writeToPort + (RPM / 100 % 10);
    writeToPort = (RPM / 10 % 10) << 4;
    writeToPort = writeToPort + (RPM % 10);
    PORTD = writeToPort;
}
void Speed()  {

    Htime=pulseIn(19,HIGH);      //read high time
    Ltime=pulseIn(19,LOW);        //read low time
    
    Ttime = Htime+Ltime;

    MPH=1000000/Ttime*3600*0.0011122571986   //getting frequency with Ttime is in Micro seconds, then multiply by 3600 to get revs per hour, multpilied by wheel circumference in miles to get miles travelled per hour 
    
//Writing MPH value to nixie tubes

    writeToPort = (MPH / 10 % 10) << 4;
    writeToPort = writeToPort + (MPH % 10);
    PORTF = writeToPort;   
}

So I'm no expert or even a novice but you can set a timeout in the pulseIn function. Basically once it's reached that timeout value it says "0". I did play around with it on my lower speed signal.

if (event) //or ISR
{
 newMicros = micros();
 interval = newMicros - oldMicros;
 oldMicros = newMicros;
}

with unsigned long old/newMicros for each signal to measure.

DrDiettrich:

if (event) //or ISR

{
newMicros = micros();
interval = newMicros - oldMicros;
oldMicros = newMicros;
}


with unsigned long old/newMicros for each signal to measure.

I see, and you stick that within the loop? I've never used (event) - how would you point that at a specific pin?

Make it the body of an ISR or poll for whatever event (edge...) you are expecting.

Dang hotel WiFi made me lose my response:

Thank you so much for your help. The blessing and the curse of Arduino is there are so many good deep dives into the 328p that you can get too far down in the weeds.

With the micros() example you’ve given about every 70 min micros() rolls over and the best way to deal with that is to just deal with it; as resetting micros() tends to have undesirable consequences. So in the code that calculates the frequency you just check to make sure that the interval isn’t negative. Something like this:

//untested code
unsigned long interval2; //unsigned long that is set in the PCINT ISR
float freq;      //the variable for the frequency value
void calcFrequency(){
//The X.0 is to keep the compiler from "helpfully" converting float to int
//if a roll over occurred during the interval set freq to be -1 which is easy to filter from data
if(interval2<=0) freq=-1.0;
else freq=1.0E6/interval2;
}

Use the two external interrupts; interrupt on either FALLING or RISING; use micros() timing for this. Use a flag to signal you have a new measurement.

Code would look like this:

volatile uint32_t freqMicros;
volatile uint32_t previousFreqMicros;
volatile bool haveFreq;

void loop() {
  if (haveFreq) {
    haveFreq = false;
    uint32_t frequency = 1000000 / (freqMicros - previousFreqMicros);
  }
}

void readFreq() {
  previousFreqMicros = freqMicros;
  freqMicros = micros();
  haveFreq = true;
}

loberd:
Dang hotel WiFi made me lose my response:

More likely this forum's web site is to blame, it's seriously misbehaving at the moment.

wvmarle:
Use the two external interrupts; interrupt on either FALLING or RISING; use micros() timing for this. Use a flag to signal you have a new measurement.

So I have an important input signal that is about 10 ms long. I had planned on using either INT0 or INT1 for that signal as this is my most "complex" project and I'm not sure how tight my loop will be. But if there is wiggle room I could move it. Or another way to handle this is to have lets say a 0.1 ms (100us) timer interrupt which checks for a change on the important signal and sets a flag? With 100us that gives you breathing room for the time to enter and exit various ISRs and that wouldn't be triggering too often. Although if I understand rules of thumbs, even a 1 ms timer interrupt would be sufficient to capture the important signal.

wvmarle:
More likely this forum's web site is to blame, it's seriously misbehaving at the moment.

I considered that as a possibility but I'm so jaded with hotel WiFi I blame it for everything.

That's a poor use of timer interrupts. Just use a regular interrupt to monitor that signal.

You have the two "external" interrupts INT0 and INT1 on pin 2 and 3.
Then you have the pin change interrupts - one per PORT (three total), select your pins well and you know which one triggered the interrupt.
The push button pin you best simply poll in loop().

Thank you so much for your help. So I’ve sat down and mapped out what pins i’m using for what and it seems like in my case PCINT8 or PCINT20 or PCINT0 are the better bets. PCINT0 seems more logical but PCINT20 seems more “tidy”. Here is my thought process:

  • I’m using INT0, INT1, I2C, and SPI. That takes out PCINT18, PCINT19, PCINT2-5, PCINT12, PCINT13
  • I have 1 analog in which can go on A6 or A7 as there are no PCINT on those pins
  • There is 1 PWM out that could go on D5 (PCINT21) D6 (PCINT22) or D9 (PCINT1)
  • By using D9 (PCINT0) that would fill up all the pins on PCIE0 leaving PCIE1 and PCIE2 free if I need them
  • Using PCINT20 has the advantage of having all interrupts next to each other which is nice from a conceptual point and wouldn’t tie up a PWM pin
  • PCINT8 could be a good option as ll as there isn’t as much going on PCIE1 but it would take out one of the valuable ADCs

So putting everything together I’d use INT0 and INT1 for my frequency measurement because I can easily trigger off of a RISING or FALLING signal which makes measuring period easier. I’d use PCINT0 for the important signal. You can’t directly tell if it was a RISING or FALLING interrupt so you have to be slightly careful. Since my signal is going through a Schmidt Trigger and is slow I can directly read PORTB (with a slight delay if needed) and the state of PB0 will tell me if it was triggered falling or rising.

The PCINT would be set up like:

void setup(){
 //Set up PCINT0; 
  pinmode(8, INPUT); //or INPUT_PULLUP if needed
  PCICR |=0b00000001; // turn on Pin Change interrupt port B.  Safer: PCICR |=(1<<PCIE0)
  PCIFR |=0b00000000; //clear flags Safer: PCIFR &=(1<<PCIF0)
  PCMSK0 |=0b00000001; //enable PCINT0 Safer: PCMSK0 |=(1<<PCINT0) 
}

Then the ISR associated with it would be:

ISR(PCINT0_vect) {
    PCMSK0 ^=0b00000001; //disable PCNT0 while working on it Safer: PCMSK0 ^=(1<<PCINT0) 
//if signal is noisy start a delay here with millis();
    triggerPCINT =!(PINB&0b00000001);//read bit 0 PINB and not it because it's pulled up 
// safer option would be:
// if(!(PINB&(1<<PB0))){
//triggerPCINT=true;
}

And then in the loop:

void loop(){
  //at some point
  if(triggerPCINT==true){
    //do what needs to be done
      PCIFR |=0b00000000; //clear flags 
      PCMSK ^=0b00000001; //enable PCNT0 when done
       triggerPCINT=false;
  }
}

Let me know how close to the ball park I am on this. If there’s nothing major I’ll consider this post closed.

Solution (still need to clean up code a bit):

Declaring variables for printing.

const byte pmcTriggerOutPin         = 5;       //Pin for PCM to trigger
unsigned long currentMillis         = 0;      // stores the value of millis() in each iteration of loop()
const unsigned int printInterval_ms = 1000; 
unsigned long previousPrintInterval = 0; //when was last print

PCINT variables:

volatile boolean pmcFlag = false;
volatile unsigned long pmcTriggerTime;
volatile unsigned int pmcChangeCount = 0;

ISR PCINT2

ISR(PCINT2_vect) {
  pmcChangeCount++;
  pmcTriggerTime = millis();
  pmcFlag = true;

Setup

pinMode(pmcTriggerOutPin, INPUT);
  PCMSK2 |= 0b00100000; //PCINT21 PD5 D5
  PCIFR = 0b00000000; //Clear all flags
  PCICR |= 0b00000100; //Turn on port D PCIE2
  Serial.begin(115200); //for testing purposes