For a physics project, I want to record the effect of 2 speakers emitting 100hz at different phase differences. Trouble is, the Arduino can only send one tone() signal at a time. How do I create a phase difference effect using one Arduino?
2 Answers
Since the frequency is so low, you can generate the signals in software. That would not be as clean as timer-generated signals, but it will be much easier. And probably clean enough for the intended purpose.
In order to generate a tone, you have to repeatedly switch the pin connected to the piezo. This is very similar to blinking an LED, only somewhat faster. Blinking an LED is the very first thing you learn to do with an Arduino, but you may notice that blinking two LEDs is a little bit trickier. The problem – and the solution – is discussed in the Arduino tutorial Blink Without Delay. Building on that tutorial, here is an example program that generates two 100 Hz signals in quadrature:
const uint8_t PIN_A = 8;
const uint8_t PIN_B = 9;
const uint32_t HALF_PERIOD = 5000; // 5000 us
const float PHASE_SHIFT = 0.25; // 1/4 period
uint8_t state_A = LOW;
uint8_t state_B = LOW;
uint32_t last_A_toggle = 0;
uint32_t last_B_toggle = - 2 * HALF_PERIOD * PHASE_SHIFT;
void setup() {
pinMode(PIN_A, OUTPUT);
pinMode(PIN_B, OUTPUT);
}
void loop() {
uint32_t now = micros();
if (now - last_A_toggle >= HALF_PERIOD) {
last_A_toggle += HALF_PERIOD;
state_A = !state_A;
digitalWrite(PIN_A, state_A);
}
if (now - last_B_toggle >= HALF_PERIOD) {
last_B_toggle += HALF_PERIOD;
state_B = !state_B;
digitalWrite(PIN_B, state_B);
}
}
If you compare this with the above mentioned tutorial, there are a couple of differences worth noticing:
- The timings are measured using microseconds rather than milliseconds, for getting a better accuracy
- The variables holding the times of last toggle are updated like
last_A_toggle += HALF_PERIOD
rather thanlast_A_toggle = now
(the equivalent ofpreviousMillis = currentMillis
). This is needed to keep an accurate average frequency. Otherwise the time taken to execute the code would make the frequency slightly off.
There is a little trickery in the initialization of last_B_toggle
,
which is an unsigned number initialized to a negative value. The
initialization is valid and works modulo 232. Using a small
positive value would have the ill effect of now - last_B_toggle
overflowing to a very large positive number on the first run of
loop()
, putting last_B_toggle
effectively in the distant past.
For a very simple example you can do something like this:
#define SPEAKER1_PIN 5
#define SPEAKER2_PIN 6
void setup() {
pinMode(SPEAKER1_PIN, OUTPUT);
pinMode(SPEAKER2_PIN, OUTPUT);
}
void loop() {
int phase_delay = 1000; // Phase delay (in us)
int freq_delay = 5000; // Half of square wave at 100Hz (5000us)
// Generate 2 square waves (out of phase)
while(1) {
digitalWrite(SPEAKER1_PIN, HIGH);
delayMicroseconds(phase_delay);
digitalWrite(SPEAKER2_PIN, HIGH);
delayMicroseconds(freq_delay - phase_delay);
digitalWrite(SPEAKER1_PIN, LOW);
delayMicroseconds(phase_delay);
digitalWrite(SPEAKER2_PIN, LOW);
delayMicroseconds(freq_delay - phase_delay);
}
}
By the way, 100Hz may be to low of a frequency to get much sound out of a Piezo speaker. You'll likely have better success by using higher frequencies.