Generating two square waves from one Arduino

Hello everyone,

I am trying to create a program to control the speedometer and tachometer on an instrument cluster from a Subaru Impreza. Both require a 12V square wave with a 50% duty cycle.

I have already figured out how to send one square wave from the Arduino, but trying to send two seems to be troublesome.

toneAC() operates the speedometer on pin 9, while pin 3 runs the tachometer.
I figured it out to a degree using the code below, but trying to go any lower than 31Hz on the tach causes it to peg out to the max. I've already switched the speedo and the tach around to rule out a fault with the cluster itself.

#include <toneAC.h>

void setup() {
}
void loop() {
  toneAC(20);
  int frequency = 31;
  long interval = (1 / (frequency * 2.0)) * 1000000;
  digitalWrite(3, HIGH);
  delayMicroseconds(interval);
  digitalWrite(3, LOW);
  delayMicroseconds(interval);
}

Both waves need to be different frequencies with the speedo on a range from 0Hz to 135Hz, while the tach is on a range from 0Hz to 265 Hz. Part of my problem maybe that the timers are conflicting, but I'm not 100% positive. Any help would be greatly appreciated.

Thank you,
-Kyle

The functions delay() and delayMicroseconds() block the Arduino until they complete.
Have a look at how millis() is used to manage timing without blocking in Several Things at a Time.

And see Using millis() for timing. A beginners guide if you need more explanation.

You can use the same technique with micros() if you need finer resolution.

I don't know anything about the toneAC() library. Perhaps the simplest thing is not to use it at all.

...R

You can also accomplish your task using the TimerOne library.

There is a note in the documentation for the standard tone() library which speaks of a lower limit of 31 Hz:

"It is not possible to generate tones lower than 31Hz. For technical details, see Brett Hagman’s notes."

But, anyway, the frequencies you are working with are quite low so you can easily do something yourself to get both instruments working together. For example, you could get timer1 (if you are working with a Uno etc.) to deliver an interrupt every 100 microseconds and you could decide in the interuupt service routine in the pins (in your case 3 and 9 ) should be high or low to achieve the 50% duty cycle at the required frequency for the current road/engine speed.

Is the mapping linear over the range? , for example:

Speedo 0 to 135 Hz represents 0 to 180 km/h
Tacho 0 to 265 Hz represents 0 to 7000 RPM

I would have rewritten this:

long interval = (1 / (frequency * 2.0)) * 1000000;

as:

long interval = 500000L / frequency ;

to minimise the risk of loss of precision.

What frequency ranges are you looking at?

Low frequencies like 31 Hz are indeed easiest implemented using the millis() or micros() timers (see blink without delay; basically you have to double the code in that sketch's loop()).

For higher frequencies (say 10 kHz or more) or if the same Arduino has a lot more to do you may need to resort to timer interrupts, or fiddle with the PWM outputs. Doable but a lot harder.

6v6gt:
I would have rewritten this:

long interval = (1 / (frequency * 2.0)) * 1000000;

as:

long interval = 500000L / frequency ;

to minimise the risk of loss of precision.

Does it? The implementation by OP forces floating point calculations, so should not be losing much if any precision. Your solution may save a lot of memory and be much faster for using integers only, but tbh I don't know how it actually compiles.

Robin2:
The functions delay() and delayMicroseconds() block the Arduino until they complete.
Have a look at how millis() is used to manage timing without blocking in Several Things at a Time.

And see Using millis() for timing. A beginners guide if you need more explanation.

You can use the same technique with micros() if you need finer resolution.

I was able to use this suggestion to accomplish what I was looking for.

#include <toneAC.h>

unsigned long speedoDelayStart;
unsigned long tachWaveStateChange;
unsigned long currentMicros;
unsigned long tachDelayStart;
int tachFreq = 1;
unsigned long tachPeriod = (1 / (tachFreq * 2.0)) * 1000000;
int speedFreq = 1;
const unsigned long tachDelay = 52500;
const unsigned long speedoDelay = 100000;

void setup() {
  speedoDelayStart = micros();
  tachWaveStateChange = micros();
  tachDelayStart = micros();
}

void loop() {
  currentMicros = micros();  //get the current time
  speedo();
  tach();
}

void speedo() { 
  if (currentMicros - speedoDelayStart >= speedoDelay) {
    speedFreq += 1;
    toneAC(speedFreq);
    if (speedFreq == 137) {
      speedFreq = 1;
    }
    speedoDelayStart = currentMicros;
  }
}

void tach() {
  if (currentMicros - tachDelayStart >= tachDelay) {
    tachFreq += 1;
    if (tachFreq == 265) {
      tachFreq = 1;
    }
    tachPeriod = (1 / (tachFreq * 2.0)) * 1000000;
    tachDelayStart = currentMicros;
  }
  
  if (currentMicros - tachWaveStateChange >= tachPeriod) {
    digitalWrite(3, !digitalRead(3));
    tachWaveStateChange = currentMicros;
  }
}

My only issue now is that I want to axe the toneAC() method for manually changing the state of the pin. The problem is the speedometer seems to only like the waveform toneAC() produces. I'm trying to replace the speedo() function with the one below and it's not working.

void speedo() {
  if (currentMicros - speedoDelayStart >= speedoDelay) {
    speedFreq += 1;
    if (speedFreq == 137) {
      speedFreq = 1;
    }
    speedPeriod = (1 / (speedFreq * 2.0)) * 1000000;
    speedoDelayStart = currentMicros;
  }

  if (currentMicros - speedoWaveStateChange >= speedPeriod) {
    digitalWrite(8, !digitalRead(8));
    speedoWaveStateChange = currentMicros;
  }

Why are you constantly trying to change the speedFreq value?

wvmarle:
Why are you constantly trying to change the speedFreq value?

This makes the needle on the speedometer sweep from 0 to 120mph on a loop. Same for tachFreq (0 - 8000 RPM). That was just to test to see if I could get the code to work.