Frequency doubler for lockin amplifier

pd0lew

Well-known member
Dear all,

For my measurements I need a freuency doubler, I use a SR830 lockin amplifier as source and the ouput frequency is 4 Hz
I like to double it to 8 Hz, the input wave is a squrae wave and after doubeling the wave must be exactly 8Hz.

The Lockin is adjustable I need a range from 4Hz to 16 Hz, so 4 Hz in = 8Hz out etc.
Who has a smart idea.....

Thanks in advance.
Johan
 
Do you mean with a Teensy + software? You could measure the period of the output of the SR830 and generate PWM with half of that period (twice the frequency). Since you know the output of the SR830 is a square wave, you don't have to measure the high/low time, and you can always generate a square wave via PWM.
 
The SR830 is adjustable in frequency, the output from the SR830 must be doubled at all times.
I already made something and it works awesome.

Anyway thanks for pointing out.

Best,
Johan


Code:
#define INPUT_PIN 2  // Pin for the 4 Hz input signal
#define OUTPUT_PIN 3 // Pin for the 8 Hz output signal

volatile bool lastInputState = false;  // Tracks the last state of the input signal
volatile uint32_t lastEdgeTime = 0;    // Time of the last detected edge
volatile uint32_t period = 0;          // Full period of the 4 Hz signal
volatile bool newPeriodAvailable = false; // Flag to indicate a new period measurement

uint32_t halfPeriod = 0;               // Half the period for output signal

// Calibration factor (adjust this based on testing)
const float calibrationFactor = 1.0009999;

void setup() {
  Serial.begin(115200);               // Initialize Serial communication for debugging
  pinMode(INPUT_PIN, INPUT);          // Set the input pin
  pinMode(OUTPUT_PIN, OUTPUT);        // Set the output pin
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), onInputChange, CHANGE); // Interrupt on signal change
}

void loop() {
  // Process new period and update half-period
  if (newPeriodAvailable) {
    noInterrupts(); // Disable interrupts to safely access shared variables
    uint32_t capturedPeriod = period; // Copy the latest period
    newPeriodAvailable = false;       // Clear the flag
    interrupts(); // Re-enable interrupts

    // Apply calibration directly
    halfPeriod = (capturedPeriod / 2) * calibrationFactor;
  }

  // Output signal generation based on the half-period
  static uint32_t lastToggleTime = 0;
  if (halfPeriod > 0) { // Ensure we have a valid half-period
    uint32_t now = micros();
    if (now - lastToggleTime >= halfPeriod) { // Check if it's time to toggle
      static bool toggleOutput = false;       // Track the output state
      toggleOutput = !toggleOutput;           // Toggle the output state
      digitalWrite(OUTPUT_PIN, toggleOutput); // Set output pin
      lastToggleTime = now;                   // Update the last toggle time
    }
  }
}

// Interrupt Service Routine (ISR) for input signal
void onInputChange() {
  bool currentInputState = digitalRead(INPUT_PIN); // Read the current input state
  uint32_t now = micros();                        // Get the current time in microseconds

  if (currentInputState != lastInputState) {      // Detect edge
    if (lastInputState) {                         // Falling edge
      period = now - lastEdgeTime;                // Calculate full period
      newPeriodAvailable = true;                 // Signal that a new period is available
    }
    lastEdgeTime = now;                           // Update the last edge time
  }
  lastInputState = currentInputState;             // Update the last input state
}
SR830.jpg
 
The project is not done.... there is a small / big issue because the output is not in sync with the input.
That means the rising edge from the output must inline with the input, I see drift in the output signal.

Here we need a smart solution perhaps hardware timers from T4.0 I not have the knowledge to solve this perhaps someone can help......

Thanks in advance,
Johan
 
T4 has lots of timers, and most (all?) of them have both input capture and output compare capability. I would start with GPT because it's a 32-bit timer so it's better suited to your low frequencies. QTMR is 16-bit and can be cascaded to make 32-bit or more, which would add complexity in your case.

With 2x frequency, you will want a rising edge on the output on each rising and falling edge of the input. The high time of the output will be computed as half the time between edges of the input, or 1/4 of the time between rising edges of the input.

I think what I would try first is this:

Configure input for input capture with interrupt on every edge. In the ISR, compute the time since the previous edge, divide by 2, add that to the capture value, and set that as the next output compare value. Set the output compare to toggle on match, so the output will go low at the halfway point between input edges. It won't be exact when the input frequency is changing, and it might do something strange if the input frequency changes very quickly, but it should quickly stabilize if the input frequency stabilizes.

I have some sample programs for GPT input capture that were based on work by @manitou that I can post tonight or tomorrow, but if you search the forum and github for manitou gpt, I think you'll find his examples.
 
I am stll not done perhaps someone can help, the offset is still to big see attachment.
Code:
#define INPUT_PIN 2  // Pin for the 4 Hz input signal
#define OUTPUT_PIN 3 // Pin for the 8 Hz output signal
#define TEST_PIN 4   // Pin for the adjustable test signal
#include <TimerOne.h>
#include <TimerThree.h>

volatile bool lastInputState = false;  // Tracks the last state of the input signal
volatile uint32_t lastEdgeTime = 0;    // Time of the last detected edge
volatile uint32_t halfPeriod = 0;      // Half the period of the 4 Hz signal
volatile bool toggleOutput = false;    // Toggles to create the output waveform
volatile bool toggleTestSignal = false; // Toggles to create the test signal

float testFrequency = 4.0; // Adjustable test frequency in Hz

void setup() {
  Serial.begin(115200);               // Initialize Serial communication for debugging
  pinMode(INPUT_PIN, INPUT);          // Set the input pin
  pinMode(OUTPUT_PIN, OUTPUT);        // Set the output pin
  pinMode(TEST_PIN, OUTPUT);          // Set the test pin

  // Set up Timer1 for output signal generation (8 Hz based on 4 Hz input)
  Timer1.initialize(500000);  // Timer1 set to overflow every 500,000 microseconds (half-period for 4 Hz input)
  Timer1.attachInterrupt(toggleOutputSignal); // Attach interrupt to toggle output signal
 
  // Set up Timer2 for test signal generation (adjustable frequency)
  Timer3.initialize(1000000 / testFrequency);  // Set Timer2 overflow interval to match testFrequency (in microseconds)
  Timer3.attachInterrupt(toggleTestSignalFunction);  // Attach interrupt for test signal

  // Attach an interrupt on the input pin to measure edges
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), onInputChange, CHANGE); // Interrupt on signal change
}

void loop() {
  // This loop does not need to handle output toggling or test signal directly
  // It's mainly for debugging and adjusting settings
  static uint32_t lastDebugTime = 0;
  if (millis() - lastDebugTime >= 1000) { // Print debug info every 1 second
    lastDebugTime = millis();
    
    if (halfPeriod > 0) {
      float inputFreq = 1000000.0 / (2 * halfPeriod); // Calculate input frequency
      float outputFreq = 2 * inputFreq;               // Double the frequency for output (8 Hz)
        
    }
  }
}

// Interrupt Service Routine (ISR) for input signal edge detection
void onInputChange() {
  bool currentInputState = digitalRead(INPUT_PIN); // Read the current input state
  uint32_t now = micros();                        // Get the current time in microseconds

  if (currentInputState != lastInputState) {      // Detect edge
    if (lastInputState) {                         // Falling edge
      halfPeriod = (now - lastEdgeTime) / 2;      // Calculate half period (time between rising/falling edges)
    }
    lastEdgeTime = now;                           // Update the last edge time
  }
  lastInputState = currentInputState;             // Update the last input state
}

// Interrupt Service Routine (ISR) for toggling the output signal
void toggleOutputSignal() {
  // Check if we have a valid half period for the input signal
  if (halfPeriod > 0) {
    toggleOutput = !toggleOutput;          // Toggle the output state
    digitalWrite(OUTPUT_PIN, toggleOutput); // Set output pin
  }
}

// Interrupt Service Routine (ISR) for generating the adjustable test frequency signal
void toggleTestSignalFunction() {
  toggleTestSignal = !toggleTestSignal;  // Toggle the test signal state
  digitalWrite(TEST_PIN, toggleTestSignal); // Set test pin
}


offset.png
 
I'm not sure what you're doing in your latest program with a Timer to toggle the output. The code below is based on your previous program with changes to make it faster. I haven't looked at the output with a scope or logic analyzer, but I think this will be about as good as it can get without using the hardware timers.

1) You don't need to keep track of the state (high/low) of the input. Since you want a rising edge on the output when there is a rising edge on the input, that means you want a rising edge of the output on every change (rising or falling edge) of the input, and you can simplify onInputChange() to just set the output high, record the timestamp of the input edge, and compute the delta time since the previous edge.

2) Use digitalWriteFast(), which is much faster that digitalWrite().

3) Use the ARM cycle counter ARM_DWT_CYCCNT (600 MHz) to get better resolution and speed than by using micros().

4) Use PWM to generate the test signal, so you don't have any additional interrupts that might get in the way of reacting to the input edges. The lowest possible test signal frequency is about 40 Hz, so that's what it has for the default. You can test lower frequencies with your real signal.

Code:
#define INPUT_PIN  2 // Pin for the 1x input signal
#define OUTPUT_PIN 3 // Pin for the 2x output signal
#define TEST_PIN   4 // Pin to generate 1x PWM for test

volatile uint32_t lastInputEdgeTime = 0;   // DWT_CYCCNT at last input edge
volatile uint32_t halfOutputPeriod = 0;    // half of the output period
volatile bool newInputEdge = false;        // flag to indicate a new input edge
volatile uint32_t inputEdgeCount = 0;      // DWT_CYCCNT at last input edge
volatile uint32_t outputEdgeCount = 0;     // half of the output period
elapsedMillis ms;

void setup() {
  Serial.begin(115200);                // init Serial for debugging
  while (!Serial && ms < 3000) {}      // wait for Serial ready or 3 seconds
  pinMode(INPUT_PIN, INPUT);           // Set the input pin
  pinMode(OUTPUT_PIN, OUTPUT);         // Set the output pin
  analogWriteFrequency(TEST_PIN, 40);  // 1x PMW on test pin
  analogWrite(TEST_PIN, 128);          // 50% duty cycle (square wave) on test pin
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), onInputChange, CHANGE);
  ms = 0;                              // restart elapsedMillis
}

void loop() {
  // Output a falling edge one half output period after each input edge
  if (newInputEdge) {       // on each new input edge
    newInputEdge = false;   // clear flag
    while( (ARM_DWT_CYCCNT - lastInputEdgeTime) < halfOutputPeriod) {
      ; // wait until time for output falling edge
    }
    digitalWriteFast(OUTPUT_PIN, LOW);   // output falling edge
    outputEdgeCount++;                   // increment falling edge count
  }

  if (ms >= 1000) {                      // once per second
    ms = 0;                              // re-init elapsedMillis
    Serial.print( "input: " );           // print input and output count
    Serial.print( inputEdgeCount );
    Serial.print( "   output: " );
    Serial.println( outputEdgeCount );
    inputEdgeCount = 0;
    outputEdgeCount = 0;
  }
}

// Interrupt Service Routine (ISR) for input signal
void onInputChange() {
  uint32_t now = ARM_DWT_CYCCNT;                   // get the ARM cycle count
  digitalWriteFast(OUTPUT_PIN, HIGH);              // set output high
  halfOutputPeriod = (now - lastInputEdgeTime)/2;  // calculate halfOutputPeriod
  lastInputEdgeTime = now;                         // update the last edge time
  newInputEdge = true;                             // set flag to trigger next output
  inputEdgeCount++;                                // increment input edge count
  outputEdgeCount++;                               // increment output edge count
}
 
Thanks a lot for the code, I think it will work I will test this upcoming Monday with the SR830 at 7.777 Hz and see what the output is.
1us.png



Again thanks a lot!!!

Best,
Johan
 
You're welcome. I guess you could reduce the rising edge delay a tiny bit by swapping the first two statements in onInputChange(). If the digitalWriteFast() is the first statement, the rising edge of the output will be as close as possible to the rising/falling edges of the input.
 
I will show you the results upcoming Monday when I do the real test.
Here I use the Lockin SR830 to generate say 7.0 Hz this is doubled to 14 Hz the output from T4.0 goes directly to a ZVA40 VNA to set the output power in dBm . I will send some pictures from my test setup.
 
The code above input is running from 0.1Hz till 10kHz with a perfect duty cycle..... at our university we tested a lot but simply this can only be done with a T4. The rising and falling edges from both input and output must be nearly the same and doubled in the code above about 200ns difference for my application more then perfect. Today I will put a few photo's from my setup.
 
This is the story from a PhD student who uses the awesome Teensy 4.0.

Big thanks to Joe Pasquariello who made this possible.

As attached some pictures.

Best,
Johan


Explained by student
---------------------------------------------------
My name is Prapti I am a PhD researcher at the University of Groningen in the
Netherlands. We use a Rohde and Schwarz ZVA40 Vector Network Analyzer (VNA) to do RF
based experiments. A frequency doubler is used for the inverse spin hall effect (ISHE)
detection (DC detection) with the VNA because the the external trigger port of the VNA only
responds to the rising edge or falling edge of the input TTL signal from a lock-in amplifier. As
a result, the trigger halves the modulation frequency of the input signal. To resolve this, a
frequency doubler is used to ensure that the ISHE signal is locked to the same modulation
frequency of the lock-in reference signal. It doubles the reference frequency of the lock-in
amplifier so that the amplitude-modulated output from the VNA is correctly synchronized with
the detection process and thus ensures accurate measurement.

3.jpeg
1.jpeg
5.jpeg
 
Hey, that’s great! Thanks for posting the photos. I visited Groningen once and really liked it. BTW, that’s quite a well-equipped lab you have there.
 
Back
Top