For clock synchronization I want to implement a pulse counter on the Arduino Nano Every. There are two clock signals at 40-100 kHz, the Nano runs on 20 MHz. Nyquist says we need at least 2 times the sampling frequency, we're at 200 times. So I assume it's possible to program a reliable pulse counter.
Currently, I'm using attachInterrupt(digitalPinToInterrupt(pin), isr, RISING)
to count the rising edges on A0 and A1 and periodically display them via Serial.print()
. If I have a clock on a single pin, the right amount of edges is counted. But if I connect both pins, each pin counts only 50% to 70% of the rising edges. At the scope, the signal looks fine to me.
volatile unsigned long risingCount0 = 0;
volatile unsigned long risingCount1 = 0;
void isr0() { risingCount0++; }
void isr1() { risingCount1++; }
void setup() {
Serial.begin(9600);
pinMode(A0, INPUT_PULLUP);
pinMode(A1, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(A0), isr0, RISING);
attachInterrupt(digitalPinToInterrupt(A1), isr1, RISING);
}
void loop() {
static unsigned long lastPrintTime = 0;
int waitSec = 5;
if (millis() - lastPrintTime >= waitSec * 1000) {
lastPrintTime = millis();
unsigned long c0, c1;
noInterrupts();
c0 = risingCount0;
c1 = risingCount1;
interrupts();
Serial.print(c0);
Serial.print(" ");
Serial.println(c1);
}
}
This person says pin 2-5 works for them, but when using digital pins 2 and 3, instead only 10 or 100 results per second are counted. This puzzles me, because the docs also say the digital pins are fine for interrupts. Simultaneous interrupts should be stored as well.
Reading
How do interrupts work on the Arduino Uno and similar boards?,
I see that, e.g. millis()
disables interrupts for a short time, so I assume something is preventing the interrupts from happening. Reducing serial prints also mitigates the issue somehow, but still doesn't fix it. Also, interrupts should just be handled after one another and ISRs writing a single variable should take only single microseconds.
Ideally, there's a solution that continuously measures and prints the result to serial to perform long term measurements. Still, I'm now implementing a version that alternatively prints and measures, to make sure serial prints don't influence the measurement.
Any suggestions for libraries, improving the existing code or all together different approaches are highly welcome. I added project-critique
tag as suggested by the help page.
1 Answer 1
The fact that it works with a single clock suggests your Arduino may simply be overloaded. You may try lowering the clock frequencies: if the counts get correct, then that is your issue.
Two clock signals at 100 kHz means your Arduino has, on average, 5 μs (i.e. 100 CPU cycles) to handle a single clock edge. This may seem like a lot, but it is not that much. Incrementing a one-byte variable from an ISR may need as much as 9 cycles :
- 2 cycles for saving a CPU register on the stack
- 2 cycles for loading the variable from SRAM
- 1 cycles for incrementing the value
- 2 cycles for storing the result in SRAM
- 2 cycles for restoring the CPU resister from the stack
For a 4-byte variable (unsigned long
), this is already 36 cycles. But
then, you are not writing an ISR. The actual ISR is provided by the
Arduino core. It has to search for the function pointer you provided as
an actual interrupt handler, make sure it is not nullptr
and call it.
It presumably has to save way more than 4 registers to the stack: all
the registers that are "call-used" as per the calling convention. All
this is not very efficient. You may want to disassemble the binary to
see what it is doing.
I do not think you can make your code significantly faster while using
the Arduino core. If you have time to study the datasheet of the
ATmega4809, you may want to write your interrupt handlers as actual
ISRs, instead of relying on attachInterrupt()
. You could even use a
hardware counter but, if I understand the datasheet correctly, only the
Timer/Counter Type A is suitable for the job, and you have a single
instance of it.
-
If it's necessary, I could study it. However, it's just a helper, I want it to be easy to understand for people unfamiliar with arduino. So I'll look for a simpler solution first. Thanks for outlining the overhead. Do you think a busy loop would perform better?Mo_– Mo_2025年05月27日 15:07:18 +00:00Commented May 27 at 15:07
-
1@Mo_ It would not, possible fixes, do the edge counting in hardware. Or get a faster processor.Questor– Questor2025年05月27日 18:57:08 +00:00Commented May 27 at 18:57
-
1@Mo_ For verifying the theory, you could toggle a pin during the ISRs. Make sure to use the library digitalWriteFast that compiles functions with constant pin numbers into a single machine instruction. Then use your oscilloscope.the busybee– the busybee2025年05月28日 07:02:58 +00:00Commented May 28 at 7:02
-
1@Mo_ If you can limit the range of the counters, using a smaller data type like
unsigned char
could reduce the number of cycles spent in the ISRs.the busybee– the busybee2025年05月28日 07:04:23 +00:00Commented May 28 at 7:04 -
1@Mo_: Re "Do you think a busy loop would perform better": a busy loop calling
digitalRead()
may not be any better, as that function is pretty slow. WithdigitalReadFast()
, the busy loop will most likely work fine... until you callSerial.print()
, and that method starts doing a (slow) binary-to-decimal conversion.Edgar Bonet– Edgar Bonet2025年05月28日 11:15:54 +00:00Commented May 28 at 11:15
Explore related questions
See similar questions with these tags.
nano
"not every"? Thanks for the TCx hint. I hoped I could ditch learning counters on Arduino, but if that's the way so be it.-S
)? That can give you a better idea on how long it takes to do something than C will.