Need to program 6 square waves at 40 kHz through 6 output pins on Arduino Uno

Hi
So for the last few weeks I have been trying to get my arduino UNO to output 6 square waves each from a different output pin but all at 40 kHz. I have tried everything from tone() to programming my own square wave function but it has been impossible to get multiple squarewaves at the required frequency. I can get one or two squarewaves to run at 40kHz but anything above that falls apart.
Does anyone have any ideas on how to get the 6 squarewaves from the arduino each at 40 kHz? Has anyone run into this problem before?
Thanks for your help!

The Arduino Uno has 3 timers, but Timer0 is already in use for millis().
With 2 timers, you have 4 outputs. You can get 4 pins at 40kHz (up to 4MHz I think).

When you don't need millis() or any library that uses it, then you can use Timer0 as well.
That is not easy, since Arduino itself and many other libraries depend on millis().

The microcontroller ATmega328P in the Arduino has special commands to toggle an output pin.
Once the pin is set as output with pinMode(13,OUTPUT); you can do this:

PINB = _BV(PB5);       // toggle pin 13

An interrupt with minimal code takes about 6 µs.
At 40kHz that is a load of 24% of the processing time, which is a lot.
It is almost impossible, but that could be done.

Conclusion: I'm not optimistic about it. Why do you need 6 pins with 40kHz ? Perhaps you can add extra hardware, for example a servo/pwm module.

Thank you for your help. That is what I was afraid of. Do you know if a different arduino can do it (not the uno)?
As for why I need 6 channels running at once unfortunately it is a requirement of my final project for my degree to make this work.

Would it be possible to set up port manipulation that turns 6 pins on, waits 1250 microseconds, then turns them off, repeat ad nauseum? Should get something close to a square wave. Or is this faster than what an Arduino can go?

Or is that what you were suggesting @Koepel?

There are much faster processors you can use with the Arduino IDE than the standard 16MHz AT328.

What about a DUE or ESP8266?

Allan

Try something like this for a 328P board:

void setup(){
pinMode (14, OUTPUT);
pinMode (15, OUTPUT);
pinMode (16, OUTPUT);
pinMode (17, OUTPUT);
pinMode (18, OUTPUT);
pinMode (19, OUTPUT);
}

void loop(){
while(1){
PORTB = 0x3F;
delay (1240);
PORTB = 0;
delay(1240);
}
}

1/ CrossRoads : Surely you mean delayMicroseconds()?

and if you want to do other things as well, an interrupt might be useful. A single timer would be all you'd need.

2/ Or you could use just 1 output and use eg a 74HC14 as a fanout.

Allan

10 µs for a register write? That's 160 clock cycles! Should be a lot faster than that.

Also for accuracy I'd do the timing loop differently. This way you're not dependent on how long it takes for the PORTB write, the only inaccuracy is the time it takes to do the loop check.

unsigned long lastMicros = micros();
bool portStatus = false;
void loop() {
  if (micros() - lastMicros() >= 1250) {
    if (portStatus) {
      PORTB = 0;
      portStatus = false;
    }
    else {
      PORTB = 0x3F;
      portStatus = true;
    }
    lastMicros += 1250;
  }
}

(edited: corrected code to something that actually may work)

I think that a timer overflow interrupt set up with a 6 pin port toggle would have pretty low overhead. You could probably fine tune the overflow frequency to handle the interrupt latency

You could certainly use a Due or other faster processor with the same technique.

What is the relationship between the 6 channels? The simple solutions proposed above all assume that the 6 outputs are synchronous. You could get the same effect by soldering 6 wires to one output pin.

The timer hardware on the Arduino is extremely flexible and powerful. Even for relatively complex 3-phase motor control, the timer hardware can be just "set and forget".

It is unlikely that you are doing something that the Atmel designers didn't already plan for. 6 channels is a lot - you may need a Mega or Teensy just because the bigger chips have more timers built in.

With a three-phase motors the intervals are all the same, isn’t it?

As long as the pin writes are fast enough my code should be able to scale to six pins each running independently and triggering at arbitrary intervals. That Arduino won’t have much time left to do other things, though.

Tried your code on a nano,wvmarle, and it doesn't work - for one thing using 1250us -> 400 Hz.

1/40kHz = 25uS.

This does........ but

void setup() {
  pinMode (2, OUTPUT);
  pinMode (3, OUTPUT);
  pinMode (4, OUTPUT);
  pinMode (5, OUTPUT);
  pinMode (6, OUTPUT);
  pinMode (7, OUTPUT);
}


unsigned long lastMicros = micros();
bool portStatus = false;
void loop() {
  while (1) {
    if (micros() >= lastMicros) {
      lastMicros += 12;
      if (portStatus) {
        PORTD = 0;
        portStatus = false;
      }
      else {
        PORTD = 0xfc;
        portStatus = true;
      }

    }
  }
}

The average frequency is about 40kHz, but there's a lot of jitter due to arduino overheads.

I don't think you'll get a clean 50% m-s square wave unless you go to a barebones 328.

Or a bit of inline assembler - but even then such things as millis() interrupts will cause inaccuracy.

A bit of hardware would easily do the job. Try eg a 74HC4060 with a suitable crystal.

Allan

Mmm... OK, right. Seems I can't calculate well enough :slight_smile: Took that number from previous posts.

At 25 us it's getting a bit tricky indeed. Agreed, a crystal, or if you need more room for tuning an oscillator (e.g. a 555 in astable mode, 40 kHz is no problem for them).

Hi,
What is the phase relationship between the 6 channels?

Thanks.. Tom.. :slight_smile:

I think @heeyrich doesn't want to tell us what the 6 channel 40 kHz is for because it might be a free-energy ultrasound tesla kickstarter scam.

We can help, but after 14 posts in this thread we don't even know if all 6 should change exactly at the same time or if there is a specific 3-phase with alternating outputs or if jitter is allowed, or what the current requirement of the signal is. Is the signal continuous or just short bursts of 40 kHz ?

Perhaps there is a XY-problem, and there is no need for a 40 kHz signal after all.

@heeyrich, we can help. Either with extra hardware or with a faster Arduino board, or perhaps it is possible with a Uno. But we have to know what kind of signals you want and what it is for.

Thank you all for your help! I really appreciate it!
So to clarify, each pin is connected to a coil and is being used (once it works) in a passive inductive charging device. Each coil needs to be triggered for long enough to transfer current to the receiver coil and must be at 40kHz. Technically all 6 pins should not be running at once but the problems we have been running into with the timer have led me to believe that the arduino is treated it like all the pins are on at one time rather than in groups of two or three like we intended in the program.
What we really need is to be able to have sets of three pins each running with a 40kHz square wave and to be able to turn those three off and run a different set of three pins. I don't mean that we only need pins 1,2,and 3 running and then pins 4,5, and 6 running but rather that combination 1= 1,2,3 while combination 2 =2,3,4 combination 3 =3,4, 5 etc. and to be able to turn on and off the combinations as we scan through the coils.

Could you make a sketch of your signals vs. time? So which signal has to be high or low when over one or two cycles. That'll be a lot clearer, then you can show duty cycles and overlaps between the pins.

I'm sure it can be done. Maybe on an Arduino, maybe on a faster MCU, maybe completely in hardware (using oscillators - the 555 for example can easily produce a 40 kHz block wave).

How precise does the 40 kHz have to be?

For drawing, something like this, but then for your six block wave signals:

Ok here is a drawing of the waves that need to be generated by each pin over time. The frequency can vary a little but not much so 40kHz +/- 3kHz at the most.
Here is the code we have been using that allows us to get squarewaves on the pins but at the wrong frequency.

int pin13 = 13;
int pin12 = 12;
int pin8 = 8;
int pin7 = 7;
int pin4 = 4;
int pin2 = 2;
int INpin = A0;
int sensorReading=0;
int co13 = 0;
int co12 = 0;
int co8 = 0;
int co7 = 0;
int co4 = 0;
int co2 = 0;
const int sensorMin = 0;
const int sensorMax = 100;
void setup() {
//declare coil pins as outputs
pinMode(pin13, OUTPUT);
pinMode(pin12, OUTPUT);
pinMode(pin8, OUTPUT);
pinMode(pin7, OUTPUT);
pinMode(pin4, OUTPUT);
pinMode(pin2, OUTPUT);
// declare telemetry feedback as Input
pinMode(INpin, INPUT);
Serial.begin(9600);
}
void loop() {

noTone(13);

tone(8, 40000, 2000);
delay(2000);
noTone(8);

tone(7, 40000, 2000);
delay(2000);
noTone(7);

tone(2, 40000, 2000);
delay(2000);
noTone(2);

tone(4, 40000, 2000);
delay(2000);
noTone(4);

tone(12, 40000, 2000);
delay(2000);
noTone(12);

tone(13, 40000, 2000);
delay(2000);

}

I fail to see the connection between this statement

What we really need is to be able to have sets of three pins each running with a 40kHz square wave and to be able to turn those three off and run a different set of three pins. I don’t mean that we only need pins 1,2,and 3 running and then pins 4,5, and 6 running but rather that combination 1= 1,2,3 while combination 2 =2,3,4 combination 3 =3,4, 5 etc. and to be able to turn on and off the combinations as we scan through the coils.

and this drawing
35ab6d1583f75f8366b0e014e479b97c749d0690.jpg

Your code appears to do neither, but is sequential square wave output for 2 seconds on each of 6 pins.

Can you please clarify some more about what you want.

Hi,
Why don’t you have ONE pin producing the 40KHz and with a digital switching circuit, connect any number of your outputs to the SINGLE output.
You then just need a SINGLE signal output and 6 digital outputs controlling the switching circuit.

The schematic below shows the principle, it is only a concept schematic, there may be other ways to accomplish the same type of switching output.
Output Switcher.jpg

Tom… :slight_smile: