A simple (I2C/SPI) 8-bit solution to count pulses?

Hello,

after searching the web, it seems there is no "out of the box" solution which does exactly this. I´ve read many advices using "counting" IC´s which have parallel output but due to lack of digital pins in my project I need a serial output. - So there you find some parallel to serial converter chips.

Or even suggestions to use a dedicated ATtiny85 or an extra Arduino Chip to do this job and send it via I2C to the main Arduino.


The problem is, I need to count impulses on 2 lines simultaneously. And calculate the Speed from them independently.

I´ve looked into some software "polling" solutions or interrupt solutions. And I can forget the polling as there would be so much delay in the code that it would influence the rest of it. Nevermind that this solution can miss out some pulses resulting in not accurate readings.

And I am not sure about interrupt as they would probably still catch every pulse but still need much CPU power to actually calculate something out of them.

Beside this task the main Arduino should control a TLC5947 Led driver which display should be as fluent as possible, and I doubt it would work alongside some "impulse counting" as this would demand too much power to do.

So I guess I need a "dedicated" solution which just "sends" preprocessed values to the main arduino resulting in very little delay in the main code.

I am happy about your suggestions, whether it is something of the mentioned solutions at the beginning of my post (along with an explanation how to do it) or something I did not even think of.

you should use interrupts, and no, they do not take much more CPU power than polling.

Here a sketch to get started …

//
//    FILE: attachInterruptExample.ino
//  AUTHOR:
// VERSION: 0.1.00
// PURPOSE:
//    DATE:
//     URL:
//
// Released to the public domain
//

uint32_t lastTime = 0;

volatile uint32_t count0 = 0;
volatile uint32_t count1 = 0;


void setup()
{
  Serial.begin(9600);
  Serial.println("start...");

  attachInterrupt(0, isr0, CHANGE);
  attachInterrupt(1, isr1, CHANGE);
}

void loop()
{
  if (millis() - lastTime >=1000)
  {
    lastTime = millis();

    // make local copies of irq counters, disable IRQ's while copying
    cli();
    uint32_t cnt0 = count0;
    count0 = 0; // reset counter
    sei();

    cli();
    uint32_t cnt1 = count1;
    count1 = 0;
    sei();

    Serial.print(cnt0);
    Serial.print("\t");
    Serial.println(cnt1);
  }
}

void isr0()
{
  count0++;
}

void isr1()
{
  count1++;
}

So you mean there is no point in using dedicated hardware for this?

What does exactly happen when an interrupt occurs in the middle of a function? Does it happen in backround, like multitasking? Or does it simple break the current action and resume where it was after the attached interrupt function is done?

And what does: cli(); and sei();

exactly mean in your example?

thx so far.

So you mean there is no point in using dedicated hardware for this? yes

What does exactly happen when an interrupt occurs in the middle of a function? the function is stopped, the address of the next instruction (PC=Program Counter) is saved in memory. The ISR is executed, then the position of PC is restored and the function is continued.

Does it happen in backround, like multitasking? No, the arduino is single tasking, it has only one thread.

Or does it simple break the current action and resume where it was after the attached interrupt function is done? Yes.

cli() = clear Interrupts == NoInterrupts() sei() = enable interrupts == Interrupts();

@robtillaart thank you I will look more deeply into it.

Will they work independently? I mean what if int0 and int1 occur at the same time? I guess I will miss both of them then? And I think in my project (measuring RPM and Speed) there could indeed something like this happen as there will be several pulses per second.

thats also my concern about it doing it all in one chip. will the rest of the code be possibly affected too much?

Will they work independently? I mean what if int0 and int1 occur at the same time? I guess I will miss both of them then?

They will both be served. You can test that quite easily ...

thats also my concern about it doing it all in one chip. will the rest of the code be possibly affected too much?

No, there are many people that did this before you on an UNO, Nano or Mega etc. So it should work for you, but yes if you keep on adding functionality there is a moment it will not fit in memory or another resource.

Well I tested it today, printing the values into the serial console with little dissapointing results.

I used a impulse generator for testing, it works nice but at higher frequencies its getting more and more bumpy, which is exactly what I don´t need. - its probably just the way interrupt works. For example - when I implemented the "Led Driver" Library which was sending the SPI Data to the TLC5947 the values "changed" simply for the reason that there was much more time needed to go through all the "sending" process each loop.

Resulting in giving more time for the impulses to come in.

Maybe there is a way to optimize oder compensate this which I am not aware of?

Too bad it has only one hardware counter at D5. Which is (i guess?) cpu independend.

Anyway I am considering to use one or two dedicated ATtiny´s (only one if 2 counter pins are present) only for this counting task. I guess it´s not right to fire up 1000 interrupts per second on a chip which controls some visual effects which should run smoothly.

Is it possible to use the Arduino IDE to write the code for them, and let them communicate via I2C or any other simple to set-up way with the main arduino?

thx so far

but at higher frequencies its getting more and more bumpy,

What frequency of pulses are you trying to count?

The example code provided by Rob is counting both the rising and falling edges of each pulse.

attachInterrupt(0, isr0, CHANGE);
  attachInterrupt(1, isr1, CHANGE);

If you change the interrupt mode from CHANGE to RISING or FALLING and get only one count per pulse will that get you into the frequency range you need?

Well I did all the tests with

 attachInterrupt(0, isr0, HIGH);
 attachInterrupt(1, isr1, HIGH);

I tested it up to 1kHz I don´t need more. As this would be the limit of the signal I want to measure anyway.
But as the frequency increased the “slower” was the refresh rate of the serial output.

So I think its just a more clean approach to use a dedicated chip for this. especially when the LED control HAS to be fluent.

I dont know how the I2C communication work yet but I expect to get every 1/100sec the preprocessed values from Attiny to Arduino, which shouldn´t demand too much ressources.

Are you using an Arduino Due? It is the only board where HIGH is a valid mode. It also has unusual syntax for the external interrupts attachInterrupt(pin, ISR, mode)

With

 attachInterrupt(0, isr0, HIGH);
 attachInterrupt(1, isr1, HIGH);

you are putting the interrupt on serial pins and I believe that is the cause of some of your problem. One khz should be easy. HIGH is also likely to be blocking for the entire length of the pulse. For simple counting it is better to read RISING or FALLING.

Rob's code was putting the interrupts on pins 2 and 3 with most other Arduino boards.

Sorry I forgot to mention that I am using an Arduino Nano.


First I though its all the serial output which cause the delay.
So I removed all output and added just one in the if statement

Well because this if-statement is time critical I´ve put a serial output

int difference = millis() - lastTime;
Serial.println(difference);

inside of here:

if (millis() - lastTime >=10)
  {

int difference = millis() - lastTime;
Serial.println(difference);

    lastTime = millis();

//
// The rest of the code here
//
   
  }

So that I can actually see how much time passed instead of the defined 10ms.

And found out its actually 100 - 150 ms. :o So I disabled the Led driver communication and it got better. now there is only ~0-5ms difference with all interrupts running which is perfect as I will go up to 50ms-100ms refreshrate anyway later.

So the interrupts seems to not affect the delay in negative way. I guess it was the (maybe too time consuming function).