Speeding up the measuring of PWM signals

Hi all.

Have done all the searching I can, now thinking I may have made some critical errors in my circuit design that the arduino just wont be able to live with.

Nano Every.

4x PWM inputs.
speed (10hz to 460hz)
tach (10 to 200hz
battery voltage and fuel level (voltage to PWM converters used as seemed easiest to isolate and get a signal to arduino, hz can be basically anything i want, so no issue)

I am using this code per input

  if ((millis() - speedrefresh_timer) > speedREFRESH_TIME) {
    speedrefresh_timer = millis();  // Set the timer equal to millis, create a time stamp to start over the "delay"
    speedpulseHigh = pulseIn(speedPin, HIGH, 100000);
    speedpulseLow = pulseIn(speedPin, LOW, 100000);
    speedpulseTotal = speedpulseHigh + speedpulseLow;  // Time period of the pulse in microseconds
    speedfrequency1 = 1000000 / fuelpulseTotal;        // Frequency in Hertz (Hz)
    speedkph = (speedfrequency1 / 4) * 3;
  • I have set timers so that each of my four PWM signals are measured as seldom as I feel comfortable. (RPM 10 times a second and speed 10 times a second, fuel and volts every 10 and 5 seconds) I have also staggered the timing so that they hopefully do not all end up trying to measure during the same loop.
  • I also have timers on my digital reads.

*I have set the timeout function to the lowest I can manage, which I believe may work at 100,000us in the code above (please let me know if my thinking is correct there)

  • tacometer is not such a problem, I guess when the motor is running it wont have to timeout on each reading, so it will speed up. The problem is the speedometer. When the vehicle is not moving and the hz drop to nothing, loop speed will be limited by the requirement to timeout waiting on pulses, so I end up doing under 10 loops a second- and thats before I even try sending serial data to my display :frowning:

What are some keywords I could search to learn something about speeding this up?

Would a teensy 4.0 do it via just brute force? (Understand that it wont fix my pulsein time outs, but perhaps the fact the rest of the sketch runs so much faster it'll compensate a bit)

Maybe I could read the Pulsein total and if its slower than a certain frequency I could drop my measurements per second to free up the arduino to do other things- just worried that even with ten measurements a second the RPM bar will appear jerky.

Any advice appreciated. Please dont rip on the code too much, i'm achieving pretty cool things with a huge amount of study and some basic code :slight_smile:

Can you describe what the problem actually is? You mentioned that it's too slow and you want to speed it up, but didn't actually say why or what problems you are having because it's too slow.

I suspect the problem is that pulseIn() is a blocking function. It waits for a pulse to start & finish. The MCU can't be performing other tasks during that time.

So I would suggest you try not using pulseIn(), but instead monitor the digital pins with digitalRead() and when a change happens, you can use micros() to calculate and record the HIGH or LOW pulse time.

OK, chatting through it with a mate.

I do 3.5 seconds 0-100 (should go faster but im not so brave). If i am taking a speed measurement ten times a second, that means I am taking 35 measurements during that acceleration run, or it will take a measurement every 2.8kph of speed change. Thats more than enough resolution. This has lead me to dropping my speed updates to 5 times a second, things run much much nicer (it means there are 5 less readings taking place, and each of those 5 readings doesnt have to time out twice at 100,000us per timeout.

Excellent :slight_smile:

Thanks Paul.

You are correct on all accounts.

I want to keep frame rates and updates per second high so that my display (eg, the sliding rev bar) is not skipping up and down. I'd like it to be smooth as this is usually the biggest difference between an aftermarket and a DIY system.

I'll try reading with digitalRead(), just thought I was doing things properly with Pulsein :smiley:

Kindest Regards,

Using pulseIn() is the usual way, it's a convenient and simple way to measure pulses. But you have come up against its limitations because you are trying to measure several different signals at the same time with reasonably high frequency. Swapping to a faster MCU would make (almost) no difference with pulseIn() because it's a blocking function.

Using digitalRead() and micros() to achieve the same measurements is more difficult. To achieve accurate results, the rest of your code must be free of other slow or blocking functions, otherwise the results won't be accurate. Updating your display, for example, might cause those kinds of problems. It might even be necessary to use interrupts to capture PWM signal edges to achieve accurate results.

1 Like

thanks again, I'll give that a shot now.

"non blocking" was actually the key for me and has opened up a plethora of good search results.

I would use Pin Change Interrupts on the four input pins. Recording the micros() time at the rising and falling interrupts will give you duty cycle, high time and low time. The micros() function counts 4-microsecond intervals so you can get better precision by using Timer1 as a fast timer. It will give intervals of a 16th of a microsecond (64 times the resolution).

1 Like

Hi John, hope you are well.

Would really appreciate if you could take a look at the following basic code for me.

I'm pretty slow at figuring this stuff out. Milliseconds is nowhere near the resolution needed. I thought that micro would be good enough but I am only getting an output of 8us with the below code and 50hz input, dropping to 4 at higher hz, so resolution there seems terrible too.

Is this what you would expect to see or am I doing something fundamentally wrong?

next mission is to learn how to use Timer1 as a fast timer as you suggest :slight_smile: lots of searching so far but I'm going to need a whole lot longer for that to sink in and start seeming relevant.


long int tachrise1;
long int tachrise2;
long int timebetweenrises;


byte status = 1;

void setup() {
  attachInterrupt(digitalPinToInterrupt(2), tachriseISR, RISING);
  pinMode(2, INPUT_PULLUP);
  Serial.begin(9600);
}

void tachriseISR() {
  if (status == 1) {
tachrise1 = micros();
status = 2;
  }
  if (status == 2)
  tachrise2 = micros();
  status = 1;

}

void loop() {
delay(500);

timebetweenrises = tachrise2 - tachrise1;

  Serial.println(timebetweenrises);

}

LOL!

I just worked out im making it far too complex.

While showing a friend how fast it was operating while measuring with interrupts, i decided to add a count to show how many times it was interrupting each second, and of course that shows me the hz...... so theres no need to do any timing, just a count over a small duration and boom, done.

Far simpler than i had made it appear in my mind :smiley:

Thanks everyone for their help.

One problem here:

'status' is always 1 when you enter the ISR so the first 'if' is always true. The first 'if' sets 'status' to 2 so the second 'if' always is true. That means both tachrise1 and tachrise2 are set to micros() at nearly the same time.

1 Like

oh this is embarrassing.

sometimes cant see the forest for the trees, I was too focused on everything else around.

Thanks mate :smiley:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.