Mixing/Shifting ultrasonic frequency to audible spectrum

Status
Not open for further replies.
// Teensy 3.6 on Windows

Ultimately, I'm working on a project in which I would like to send a ~40 kHz signal as an input into the Teensy. I would then like to generate a 38 kHz square wave and multiply that wave against this input wave, so as to create signal that has been "shifted" to an audible frequency. Finally, I'd like to keep a rolling average of this product as an output.

Is this project feasible with the Teensy 3.6, or am I trying to reach beyond its capabilities?

Details:
I've worked through some of the examples and tutorials for the Teensy, and I've so far been able to read in an analog sine wave (up to at least 40 kHz) and I've been able to output this same sine wave, although it gets quite choppy upwards of 10 kHz. I've also been successful in using the analogWriteFrequency() function to output a nice 38 kHz square wave (thanks to this post).

So, I'm at the point that I would like to generate a 38 kHz internal square wave and multiply each of the input signal's data points to the corresponding data point of the square wave (+/- 1). However, so far, I haven't been able to dig up any sort of information about creating an "internal square wave" with the Teensy. Is it possible to do this?

If it is possible to generate an internal square wave, would anybody have any suggestions/pointers (tutorials would be a god-send) on how I might go about snagging individual input signal data points and their corresponding points on the square wave?

I'm not entirely new to programming, but I am brand new at microprocessor programming and signal processing.
Thanks in advance!
 
So, I'm at the point that I would like to generate a 38 kHz internal square wave and multiply each of the input signal's data points to the corresponding data point of the square wave (+/- 1). However, so far, I haven't been able to dig up any sort of information about creating an "internal square wave" with the Teensy. Is it possible to do this?

I'll have to review my understanding of signal processing, but it seems you might solve this problem by under-sampling your input at 38KHz. The output may look like a 2KHz sine wave. You may need a low-pass filter on the output to eliminate the higher aliased signal.

Whether this will work for you depends a lot on how you want to use the resulting data. Do you want to save a rolling average of the amplitude or the frequency of the resulting downshifted output?
 
Is this question any different than the one in your other thread?

I doubt that multiplying a 40kHz audio signal by a 38kHz square wave is going to conveniently shift the audio down to 2kHz. The square wave has an infinite
number of harmonics which, I suspect, would produce massive aliasing of the audio signal making it useless.

I think you should be looking at multiplying by a 38kHz sine wave which is why in your other thread I pointed you to the Bat detector.

Pete
 
My vague recollections that sub-sampling might do the downshifting seems to be valid. I wrote a simple program to collect ADC data at 38KHz and fed it a 40KHz sine wave from my signal generator. The sampled output was fed to the T3.6 DAC for display on the oscilloscope. Here is the result:
SubSample.jpg

Here is the code that I used to collect the data:

Code:
/*****************************************************

  Use T3.6 DAC to display subsampling results with 40KHz
  input subsampled at 38KHz.
  Oscilloscope display of downsampled data was displayed
  through a single-pole RC low-pass filter with R = 250 Ohms
  and C = 0.047uF.  This reduced noise at 40KHz by about 11dB.
  M. Borgerson   3/19/21
**********************************************************/

#include <ADC.h>

const int adcpin = A3; // ADC0
const int dacpin = A21;

ADC *adc = new ADC(); // adc object;
const char compileTime [] = "Compiled on " __DATE__ " " __TIME__;

#define ADCTOGGLE digitalToggleFast(32);
// Specify how fast to collect samples
#define SAMPRATE  38000
#define USEFILTER 0  // Set to 1 to use digital filter

void setup() {
  uint16_t count;
  Serial.begin(9600);

  delay(1000);

  analogWriteResolution(12);
  Serial.print("\n\n38KHz subsampled data display ");
  Serial.println(compileTime);
  Serial.println("Analog Input on pin A3.  Analog output on pin DAC0/A21.");
  if(USEFILTER) {
    Serial.println("Digital filter is enabled");
  } else {
    Serial.println("Digital filter is disabled");
  }
  
  pinMode(adcpin, INPUT_DISABLE);
  // Set up ADC0 with default 3.3V VRef 
  adc->adc0->setAveraging(1); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::HIGH_SPEED); // change the conversion speed
  adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

  adc->adc0->stopTimer();
  adc->adc0->startSingleRead(adcpin); // call this to setup everything 
  delay(1);
  adc->adc0->readSingle(); // and initial read to get things started

  // Set up the DAC pin with 12-bit resolution
  analogWriteResolution(12);
  // now start the ADC collection timer.
  // Sampling is handled by the hardware timer and cannot be delayed by 
  // other interrupt handlers.  
  adc->adc0->startTimer(SAMPRATE); //frequency is 38KHz Hz
  Serial.printf("ADC0 timer rate is %lu\n", adc->adc0->getTimerFrequency());
  adc->adc0->enableInterrupts(ADC_ISR);
}

void loop() {
  // we don't do anything here--everything happens in 
  // the ADC interrupt handler
}

// You can change filter bandwidth and gain with these defines,
// but you have to account for offset with the unipolar input
// and output
#define DACFILTERWIDTH 1.0  // higher number for more filtering
#define FILTERCOEFF ((DACFILTERWIDTH-1.0)/DACFILTERWIDTH)
#define FILTERGAIN 1

// This is the interrupt handler called by the ADC timer
void ADC_ISR(void) {
  uint16_t adcval, dacval;
  float volts;
  static float dacsum;
  ADCTOGGLE   // you will see a square wave at 1/2 sample frequeny
              // on pin 32
  
  adcval = adc->adc0->readSingle();
#if USEFILTER == 1
  //  Super-simple IIR filter
  dacsum = (FILTERCOEFF * dacsum + adcval);
  dacval = (uint16_t)(dacsum/DACFILTERWIDTH + 0.5); 
  analogWrite(dacpin, FILTERGAIN * dacval); // gain added for scope display
#else
  analogWrite(dacpin, adcval); // write raw adc value
#endif
}


This code was run on a T3.6 at 180MHz using the 3.3V VREF for the ADC. The result shows a pretty clean sine wave at ~2KHz. Shifting the input frequency generated the appropriate shift in the output.

I included an optional digital filter of the result. However, if you implement digital filters on this kind of data you have to account for the fact that the ADC and DAC are unipolar with 0 to 3.3V range. If you apply gain in the wrong place, you will saturate the DAC output.

In a real-world application, you might need significant analog hardware to level-shift and bandpass filter the input.
 
"Aliasing" the signal does indeed work (sampling a 40 kHz signal with 38kHz), but you still have to make sure that no unwanted signal passes through.
In this case you must make sure that no signal is between 0 and 19 kHz (especially no 50/60Hz).
But mixing the 40 KHz signal with a 38 KHz signal followed by a LP filter is the normal approach.

IIRC, in Radio terms sampling close to signal frequency is called direct conversion (beat receiver) and the mixing is called heterodyne
 
Note that most sigma-delta audio ADCs internally filter everything above 20kHz if sampling at 44.1 or 48kSPS. They do not behave like
a textbook discrete sampling system.
 
"Aliasing" the signal does indeed work (sampling a 40 kHz signal with 38kHz), but you still have to make sure that no unwanted signal passes through.
In this case you must make sure that no signal is between 0 and 19 kHz (especially no 50/60Hz).
But mixing the 40 KHz signal with a 38 KHz signal followed by a LP filter is the normal approach.

IIRC, in Radio terms sampling close to signal frequency is called direct conversion (beat receiver) and the mixing is called heterodyne

I played around with LTSpice a bit this morning and designed a 4-pole Butterworth bandpass filter with about 6dB gain from 38 to 42KHz and -40dB rejection at 19KHz and LOTS more at 60Hz. It takes a dual op amp, about 6 resistors and 4 capacitors, in addition to those you need to establish a virtual ground and filter the supplies.

Filtering the output using a 4-pole IIR digital filter shouldn't take too many CPU cycles. You could also decimate the output to get about 8K samples per second if you are going to analyze or save the data.

The OP didn't say much about the signal source or what will be done with the output. I've been working on the assumption that the input is an ultrasonic transducer.

I think the direct conversion approach is nice because you don't have to generate a stable 38KHz sine wave to send to the mixer. I suppose that the mixer could be either analog or digital. If it's a properly filtered analog mixer, then you don't need to sample the result at much more than 8KHz to work with results in the 0 to3KHz band.

If you implement a digital mixer, you can generate a lookup table that will give you decent values for the 38KHz sine value at the sampling interval for the input signal. I think a potential problem is that you may need to sample the input at a much higher rate than with an analog mixer.
 
It just occurred to me that this down-conversion scheme might be a nice way to put together an ultrasonic doppler velocity sensor. I've got a handful of 40KHz ultrasonic sensors in the toolbox and lots of time between the March Madness games of the local university (Go Beavers!). It's pretty easy to drive the output sensor with a PWM output and a bit of filtering. I'll have to see if the input needs amplification and filtering. I will send the output to a headphone through the DAC and see if I can detect a pitch change in the output. (headphone output is much simpler than running a continuous FFT and updating a display.)
 
A big thank you to everyone's input on this. I definitely feel hopeful for this project, but I just haven't been able to find much on this subject through my google-fu. So I certainly appreciate all of the suggestions and comments.

To clarify some questions, my source is an ultrasonic transducer with a center frequency around 40 kHz. I'll pass that through a pre-amp and into the Teensy for mixing. For output, I'd like to pass it through an audio amp and then into some headphones. Basically, I want to hear ultrasound.

I'll update this with some results as soon as I have some!
 
Status
Not open for further replies.
Back
Top