Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Homodyne detection of a 1 kHz signal
/*
* homodyne.ino: Homodyne detection of a 1 kHz signal.
*
* This program continuously samples analog input 0 and uses an homodyne
* detection scheme to identify a signal at 1 kHz (+/- 24 Hz @ -3dB).
*
* The analog-to-digital converter is set to "free running mode" and
* takes one sample every 104 us. The samples are multiplied by two
* generated signals at 1 kHz (the "local oscillator"), in quadrature to
* one another. The products are then low-pass filtered with a time
* constant of 64 sample periods (6.656 ms), which gives the (I, Q)
* signals with a 24 Hz bandwidth. Finally, the signal power is computed
* as I^2 + Q^2.
*
* The program is intended for an Arduino Uno, and is likely to work on
* any AVR-based Arduino having an ADC and clocked at 16 MHz.
*
* For a detailed explanation, see
* http://arduino.stackexchange.com/a/21175
*
* Copyright (c) 2016 Edgar Bonet Orozco.
* Released under the terms of the MIT license:
* https://opensource.org/licenses/MIT
*/
#include <util/atomic.h>
// Analog input to use, should be between 0 and 5.
const uint8_t analog_in = 0;
// The frequency we want to detect, in Hz.
const float SIGNAL_FREQ = 1000.0;
// Timing bits.
const float SAMPLING_FREQ = F_CPU / (128 * 13.0); // 9.615 kHz
const long PHASE_INC = round(SIGNAL_FREQ / SAMPLING_FREQ * (1L << 16));
const int LOG_TAU = 6; // tau = 64 / SAMPLING_FREQ = 6.656 ms
// Set the ADC to free running mode.
static void configure_adc()
{
ADMUX = _BV(REFS0) // ref = AVCC
| _BV(ADLAR) // left adjust result
| analog_in; // input channel
ADCSRB = 0; // free running mode
ADCSRA = _BV(ADEN) // enable
| _BV(ADSC) // start conversion
| _BV(ADATE) // auto trigger enable
| _BV(ADIF) // clear interrupt flag
| _BV(ADIE) // interrupt enable
| 7; // prescaler = 128
}
// Demodulated (I, Q) amplitudes.
volatile int16_t signal_I, signal_Q;
// Interrupt handler called each time an ADC reading is ready.
ISR(ADC_vect)
{
// Read the ADC and convert to signed number.
int8_t sample = ADCH - 128;
// Update the phase of the local oscillator.
static uint16_t phase;
phase += PHASE_INC;
// Multiply the sample by square waves in quadrature.
int8_t x = sample;
if (((phase>>8) + 0x00) & 0x80) x = -1 - x;
int8_t y = sample;
if (((phase>>8) + 0x40) & 0x80) y = -1 - y;
// First order low-pass filter.
signal_I += x - (signal_I >> LOG_TAU);
signal_Q += y - (signal_Q >> LOG_TAU);
}
/* Return a power reading. */
static uint16_t get_power_reading()
{
int16_t I, Q;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
I = signal_I;
Q = signal_Q;
}
return sq((int8_t)(I >> LOG_TAU)) + sq((int8_t)(Q >> LOG_TAU));
}
/***********************************************************************
* Example usage.
*/
void setup()
{
configure_adc();
Serial.begin(9600);
}
void loop()
{
// Print a power reading every 500 ms.
static const uint16_t print_period = 500;
static uint16_t last_print;
uint16_t now = millis();
if (now - last_print >= print_period) {
Serial.println(get_power_reading());
last_print += print_period;
}
}
@Lars-Dekkers

This comment has been minimized.

Copy link

Lars-Dekkers commented Oct 3, 2019

For a school project I want to make a device that detects a whistle from a referee so that deaf people get a signal whenever the referee blows the whistle. In this code right now we would like to make a different function so that when a peak is reached something happens, for example for now starting with the built in LED to light up. Can you please show what to change in the void loop() to get this?

@edgar-bonet

This comment has been minimized.

Copy link
Owner Author

edgar-bonet commented Oct 3, 2019

Simply turn on the LED whenever the power reading is higher than a threshold. The optimal threshold value has to be determined experimentally.

@Lars-Dekkers

This comment has been minimized.

Copy link

Lars-Dekkers commented Oct 4, 2019

Okay thanks, I finally got it to work. Can you also maybe tell me how to make it detect a wider range of frequency say for example from 3khz to 4khz? Because the whistle is not a pure frequency

@edgar-bonet

This comment has been minimized.

Copy link
Owner Author

edgar-bonet commented Oct 6, 2019

@Lars-Dekkers: You already asked this question on arduino.stackexchange, and you got an answer there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.