Help with detecting the frequency of a modulated light wave

Hello,

I'm not sure where to begin, but I'll describe a project I've been trying to wrap my head around for the past couple of weeks.

I've been tasked with implementing essentially a frequency detector using arduino. The concept is I have a red laser pointer (650 ish nanometers) and I shine it onto a mirror that reflects the laser onto a photodiode. For the purposes of this project, I have this CJMCU-101 breakout board for the OPT101
photodiode.

Now, the catch is that this mirror is attached to a bluetooth speaker, which vibrates at any frequency I want from my phone. The idea is that by vibrating the mirror at some frequency, let's say 440Hz for now, the photodiode will pick up the frequency of the laser and I can modulate it, run it through an FFT or some spectrum analyzer on a microcontroller, and display the frequency of the light wave vibrating at 440Hz.

What hardware could I use for my setup? What software would I need?

I read online that an Arduino probably can't handle the frequency analysis, so I might need something beefier or I can just serial connect to matlab from my pc.

I moved your topic to an appropriate forum category @jim_stuart.

In the future, please take some time to pick the forum category that best suits the subject of your topic. There is an "About the _____ category" topic at the top of each category that explains its purpose.

This is an important part of responsible forum usage, as explained in the "How to get the best out of this forum" guide. The guide contains a lot of other useful information. Please read it.

Thanks in advance for your cooperation.

Thank you, I appreciate that. I'm new to the forum, so I apologize for incorrectly creating new topics in the wrong categories.

1 Like

If the mirror displaces the laser beam from being centered on the photodiode, you should be able to measure intensity differences, and it will be no problem to use FFT to analyze the audio frequencies associated with those differences.

Software depends on what Arduino you have. The OpenMusicLabs FFT works on AVR-based Arduinos like the Uno R3 and is the fastest for the class, while the ArduinoFFT library works best with newer Arduinos with faster CPU clocks and more memory.

But the frequency of the modulated light beam does not change. Only the amplitude. That is not what the OP originally asked about.

1 Like

I'm not sure what the OP had in mind.

However, it is a simple matter of fact the frequency of an amplitude modulated light beam DOES change, just as in radio communications, but indetectably so with Arduino.

1 Like

In my current inventory, I only have the Arduino Uno R3, or the Arduino Nano V3 from Lafvin. I also have some ESP32-WROOMs, but for the purposes of the project, I've been tasked with keeping the hardware to Arduino only.

Apologies for not making it clear in my original post. But as far as detecting the laser beam, the only parameters I was given is that a sound/note is being played by an app called GarageBand on my phone from a MIDI keyboard to the bluetooth speaker.

Not sure if the CJMCU-101 is enough, but I'm open to buy any other light frequency sensors. Just has to be compatible with Arduino. Whatever light frequency sensor I choose to use, it just needs to be able to detect the frequency of the bluetooth speaker playing the note.

And to display the frequency the arduino is detecting, I can just display it on an LCD screen.

That is based on the OPT101 photodiode/amplifier, which has a detection bandwidth of 14 kHz. So it should be fine for picking up audio frequency variations in the received intensity of the laser beam.

From the OPT101 data sheet, you can see that the detector responds to the entire visible light spectrum and part of the near-infrared.

1 Like

Alright so I've made some progress. I looked on this website to see how I can wire my arduino sensor to the CJMCU-101 board here.

According to the data sheet, I can also include an external capacitor in parallel on the "1M" line on the CJMCU-101. I'm not sure what for, but I presume it's for signal filtering.

So here's a schematic of what I setup so far on a breadboard. It seems to work so far since I'm getting some kind of output on the serial monitor:

I also borrowed some code from the ArduinoFFT library to perform an FFT and get the peak frequency. The code does not work; I'm getting a peak value of around 3.92Hz consistently even though I shine the modulated laser beam onto the OPT-101. I'm definitely sure I'm doing something wrong, but I pretty much copied and pasted from the ArduinoFFT examples.

Here's my code:

 
#include <arduinoFFT.h>

#define SAMPLES 512            // Number of samples (must be a power of 2)
#define SAMPLING_FREQUENCY 2000 // Sampling frequency in Hz
#define sensor 8


ArduinoFFT<double> FFT = ArduinoFFT<double>(); // FFT object

unsigned int samplingPeriod;
unsigned long microseconds;

double vReal[SAMPLES];
double vImag[SAMPLES];

// Change to your photosensor analog pin

int N = SAMPLES / 2;
double sum = 0.0;
double dcOffset = 0.0;

void setup() {
  Serial.begin(115200);
  samplingPeriod = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
  pinMode(sensor, INPUT);

   
  for (int i = 0; i < SAMPLES; i++) {
      sum += analogRead(sensor);
  }
 dcOffset = sum / SAMPLES;

  // Store DC offset for use in loop
  Serial.print("DC Offset: ");
  Serial.println(dcOffset);
}

void loop() {
  // Collect data from the photosensor
  for (int i = 0; i < SAMPLES; i++) {
    microseconds = micros();    // Track time
    vReal[i] = analogRead(sensor) - dcOffset; // Read light sensor value
    vImag[i] = 0;               // Imaginary part is 0 for real input data
    while (micros() < (microseconds + samplingPeriod)) {
      // Wait for the next sampling period
    }
  }

  // Perform FFT
  FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Apply window function
  FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); // Compute FFT
  FFT.complexToMagnitude(vReal, vImag, SAMPLES); // Compute magnitudes

  double peak = FFT.majorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
  Serial.print("Peak Frequency: ");
  Serial.println(peak);

  delay(1000);
  
}

And here are some of my outputs:

Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.94
Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.92
Peak Frequency: 3.92

So, I'm consistently getting 3.92Hz, and this is even with the bluetooth speaker playing at 440Hz continuously.

I'm unsure whether it's my code or my hardware setup that's causing me to produce 3.92Hz consistently.

The 3.92 value may be a DC offset on the analog input. The peak search function is not useful, unless you already know that the setup is working properly, and what to expect.

However, you are getting way ahead of yourself. The first thing to do is to just print analogRead() values and determine if the sensor responds to light, and changes in light intensity.

Use the Arduino Serial Plotter to graph those values in real time, as you wave your hand through the beam.

This line is not correct:

    while (micros() < (microseconds + samplingPeriod)) {

Replace it with

    while (micros() - microseconds < samplingPeriod) {
2 Likes

Alright, just by printing AnalogRead() values, I'm getting:

#define sensor 8

void setup() {
  Serial.begin(115200);
  
}

void loop() {
  int value = analogRead(sensor);
  value = map(value, 0, 1023, 300, 1100);
  Serial.println(value);

  double voltage = (value/1023.0) * 5.0;
  
  Serial.print("Voltage: ");
  Serial.println(voltage); 
  
  delay(1000);

}
 

Note: I put up a light shield made out of cardboard surrounding the OPT-101 sensor to limit ambient light from affecting the readings. I also rotated this configuration sideways so that the reflected laser light is directly hitting the sensor. However, I did keep my garage light on, so these readings may have been affected by ambient light.

On average, I noticed that I'm getting around 900 (around 4.75 volts when I perform the calculation in the line: (value/1023.0) * 5.0 ) when the reflected beam hits the sensor. I also get around 300 when I block the beam with my hand (around 2.5 volts).

So, the sensor definitely registers the change in light intensity.

If you remove the windowing in the fft the peak near 0 frequency disappears ( or with the windowing, set to zero the first few vReal values after calculating the magnitude ). I tested it with a sine wave input.

void loop() {
  // Collect data from the photosensor
  for (int i = 0; i < SAMPLES; i++) {
    microseconds = micros();    // Track time
    vReal[i] = analogRead(sensor) /*- dcOffset*/; // Read light sensor value
    vImag[i] = 0;               // Imaginary part is 0 for real input data
    while (micros() < (microseconds + samplingPeriod)) {
      // Wait for the next sampling period
    }
  }

  // Perform FFT
  //FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Apply window function
  //FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HANN, FFT_FORWARD); // Apply window function
  //FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_TRIANGLE, FFT_FORWARD); // Apply window function
  //FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_NUTTALL, FFT_FORWARD); // Apply window function
  //FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD); // Apply window function
  FFT.dcRemoval();
  FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); // Compute FFT
  FFT.complexToMagnitude(vReal, vImag, SAMPLES); // Compute magnitudes

  /*for (int i = 0; i < SAMPLES >> 1; i++ )
    {
    Serial.print("F:");
    Serial.print(i*1.0*SAMPLING_FREQUENCY/SAMPLES);
    Serial.print(",M:");
    Serial.println(vReal[i]/50);
    }*/
  double peak = FFT.majorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY);
  Serial.print("Peak Frequency: ");
  Serial.println(peak);

  delay(1000);
  
}

The map function and the voltage calculation are incompatible, so the result is not meaningful.

So where did you get the V- voltage from?

This is not shown on the diagram and you just can't ignore it because without a proper negative bias, the operational amplifiers which I assume are in the break out board, will not work correctly.

Many people, my self included, do not want our computers infested by more cookies. That site does not allow me to reject them all.

OPT101 Photodiode and CJMCU.pdf (60.3 KB)
Here is a clean pdf export of a site that had some useful information about the breakout board.

I'd reorder this:

...to take @jremington's suggestion, but also account for the not insignificant time it takes to do the for loop, analogRead, any micros() resolution issues, and the rest of the processing with:

  microseconds = micros();    // record start of sample
  for (int i = 0; i < SAMPLES; i++) {
    vReal[i] = analogRead(sensor) - dcOffset; // Read light sensor value
    vImag[i] = 0;               // Imaginary part is 0 for real input data
    while (micros() -microseconds  < samplingPeriod) {
      // Wait for the next sampling period
    }
    microseconds += samplingPeriod; // advance timer
  }
1 Like

Thanks for that, but none of the images are visible.

It is a long long time since I used transimpedance amplifiers, back in the early 1980s in fact. So it looks like the V- might in fact be an output. But I am still not sure.

Oops, sorry about that. I didn't check the pdf after I downloaded it. I'll see if I can fix it as the article had some good images of the pcb.

Hello, so the PCB itself looks like this:

I got the guide from this site:
https://www.electroschematics.com/photodiode/

Thanks for that. I think I can see where you are going wrong. The key is this is the pin allocation table.

In your circuit you just ignored the -V inputs. Pin 3 should be connected to Ground. This being the most negative voltage in the system. Now don't confuse this with the common ground which is the midpoint of the voltage range powering the circuit.

So the pin 2 marked -In should not be connected at all, and pin 8 connected to the mid voltage point.