Not sure where to go Hz question

I am not sure this can be done. I need to read a varying (2-15kHz) frequency. After a manipulation then generate a similar but different frequency in the similar range. Can anyone point me in the right direction or options to research?

P.S. Just a square wave pattern.

Hello Krieath

Take some time to read and study the interrupt processing of the Arduino.

  • Why are you needing to do this ? :thinking:

Showing my analog background here but if its as simple as that I'd just wire up a f-v converter to a v-f converter.

2 Likes

use a fast microcontroller (STM32, ESP32, RP2040, etc) using interrupt on change to capture the pulse sequence timing? e.g. have a look at manchester-decoding

also have a look at the ESP32 Pulse Counter (PCNT)

Attempting to manipulate a sensor for diagnostic testing purposes my control system is very specific to operation. However, modification of this frequency can cause a slight variation in operation down stream without setting a red flag. My old system used an analog voltage. So adding a small resistor could trick it. My new system uses a frequency generator to monitor the same operation. It may not even work but attempting to try something to see if it is even possible.

Here is some code that does what you require.

Using an Arduino Uno R3 or other Arduino using an ATmega328P.
I have used an interrupt to measure the period of the incoming square wave using micros().

I have used timer 1 to generate the output frequency. Part of the code in setup() generates a continuous square wave on pin 10.

The code in loop() just adjusts the output frequency based on the period measured by the ISR.

const byte inputPin = 2;
const byte outputPin = 10;

volatile unsigned long startTime;
volatile unsigned long lastStartTime;
unsigned long period;

float scalingFactor = 1.1;                                            // multiply input frequency by this factor
unsigned long n = 16000;                                              // initial output frequency 1kHz

void setup() {
  Serial.begin(115200);
  pinMode(inputPin, INPUT_PULLUP);
  pinMode(outputPin, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(inputPin), measurePeriod, RISING);
  Serial.print("Input frequency is multiplied by ");
  Serial.println(scalingFactor);

  TCCR1A = bit (WGM10) | bit (WGM11);                                 // fast PWM,
  TCCR1B = bit (WGM12) | bit (WGM13) | bit (CS10);                    // fast PWM, no prescaler
  OCR1A =  n;                                                         // count n clock cycles
  OCR1B = ((n + 1) / 2) - 1;                                          // 50% duty cycle
  bitSet (TCCR1A, COM1B1);                                            // clear OC1B on compare

}

void loop() {
  n = (period * 16 / scalingFactor);                                  //  calculate new value of n
  OCR1A =  n;
  OCR1B = ((n + 1) / 2) - 1;
  delay(10);
}

unsigned long measurePeriod() {
  startTime = micros();
  noInterrupts();
  period = startTime - lastStartTime;
  lastStartTime = startTime;
  interrupts();
  return period;
}

Here are some oscilloscope traces showing the operation:

  • Channel 1 - yellow trace - input frequency on pin 2.
  • Channel 3 - blue trace - output frequency from pin 19.

Here is a 15kHz input multiplied by 1.33 to give 20kHz.

And a 500Hz input multiplied by 0.6 to give 300Hz.

There is a lower limit of around 250Hz to the frequency that can be generated (period = 4.096ms).

Frequency error is greatest at higher frequencies as the period of the incoming 15kHz input (66.66µs) is measured in multiples of 4µs.

I've tested the code with the scaling factor between 0.1 and 10.

1 Like

Interrupts are turned off by default within an ISR, and on again during the ISR exit phase, so the two calls are unnecessary.

micros() will return incorrect results if the TIMER0 interrupt is missed within an ISR.

An ISR has nowhere to return a value, so I don't understand the function or purpose of return period; Period is in any case a global variable.

jremington,
Thank you for your comments, and the information about interrupts being turned off by default in an ISR.
I think i was getting confused with some other situation where they are necessary.

some processors, e.g. PIC24FJ1024GA610, have a priority based system where more critical high priority interrupts can interrupt lower priority ISRs

e.g. using the MPLAB Code Configurator to set interrupt priority level

If frequency is relatively low (<5khz) then you can use interrupts and simply count interrupts to determine the frequency.

I am afraid at 15kHz interrupt rate your Arduino will die :slight_smile:

You can connect cheap Frequency-to-Voltage IC (LM331) to your Arduino's analog pin and read analog values. This will require some calibration.

LM331 can work in other direction too: convert voltage to frequency: you can generate your desired frequency using Arduino's DAC.

Thank you for all the feedback. I agree from what I have found as well the Arduino cant do it all. However the LM331 is a very viable option in conjunction with the Arduino.

the LM331 looks interesting
otherwise consider using an ESP32, e.g. to measure frequency and pulse width

// ESP32 - measure pulse width and rising edge-to-edge time using timer1 pins 16 and 17

// updated for Arduino ESP32 core 3.0 see
// https://docs.espressif.com/projects/arduino-esp32/en/latest/api/timer.html

// note using hw_timer_t or micros() give similar values
hw_timer_t *Timer1_Cfg = NULL;  // timer object

// pulse timing data
volatile uint64_t riseTime, period, width;
// rising edge interrupt calculate period uSec
void IRAM_ATTR rising() {
  uint64_t rise = timerReadMicros(Timer1_Cfg);
  period = rise - riseTime;
  riseTime = rise;
}

// falling edge interrupt calculate pulse width uSec
void IRAM_ATTR falling() {
  width = timerReadMicros(Timer1_Cfg) - riseTime;
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.printf("\n\nESP32 measure pins 16 and 17 pulse information \n");
  //Timer1_Cfg = timerBegin(0, 80, true);           // API 2.x setup timer for 1uSec
  if ((Timer1_Cfg = timerBegin(1000000)) == NULL)  // API 3.0 setup timer for 1uSec
    Serial.println("timerBegin Failed!!");
  Serial.print("timerBegin() OK frequenmcy ");
  Serial.println(timerGetFrequency(Timer1_Cfg));
  //  setup interrput routines - connect signal to pins 16 and 17
  attachInterrupt(digitalPinToGPIONumber(16), rising, RISING);    // detect rising edge on pin 16
  attachInterrupt(digitalPinToGPIONumber(17), falling, FALLING);  // detect falling edge on pin 17
  Serial.println("displaying results");
}

void loop() {
  static unsigned long int timer = millis();
  // if 2 seconds have elapsed since last redaing print results
  if (millis() - timer > 2000) {
    timer += 2000;
    Serial.printf("period %llduSec width = %llduSec  frequency %.2fHz\n",
                  period, width, 1000000.0/period);
  }
}

test results 1Hz to 100KHz

ESP32 measure pins 16 and 17 pulse information 
timerBegin() OK frequenmcy 1000000
displaying results
period 10uSec width = 6uSec  frequency 100000.00Hz
period 10uSec width = 4uSec  frequency 100000.00Hz
period 10uSec width = 6uSec  frequency 100000.00Hz
period 100uSec width = 51uSec  frequency 10000.00Hz
period 100uSec width = 52uSec  frequency 10000.00Hz
period 100uSec width = 51uSec  frequency 10000.00Hz
period 10000uSec width = 5000uSec  frequency 100.00Hz
period 10000uSec width = 5000uSec  frequency 100.00Hz
period 100000uSec width = 50000uSec  frequency 10.00Hz
period 100001uSec width = 50001uSec  frequency 10.00Hz
period 100000uSec width = 50000uSec  frequency 10.00Hz
period 999993uSec width = 499995uSec  frequency 1.00Hz
period 999994uSec width = 499997uSec  frequency 1.00Hz
period 999994uSec width = 499994uSec  frequency 1.00Hz

test pulse test frequency 1Hz .01 duty cycle (100uSec width)

period 999992uSec width = 101uSec  frequency 1.00Hz
period 999992uSec width = 99uSec  frequency 1.00Hz
period 999993uSec width = 99uSec  frequency 1.00Hz
period 999990uSec width = 100uSec  frequency 1.00Hz
period 999992uSec width = 98uSec  frequency 1.00Hz
period 999993uSec width = 100uSec  frequency 1.00Hz
period 999993uSec width = 97uSec  frequency 1.00Hz
period 999992uSec width = 99uSec  frequency 1.00Hz

1. Is it:

2 Hz to 15 kHz

or

2 kHz to 15 kHz?

2. Is Arduino UNO allowed to generate the frequency?

Ideally both, I need the input to manipulate the output.

can you give examples of the input frequencies and duty cycles and what output you would generate?
how are you planning to control input to output manipulations? e.g. fixed in code, modified using a potentiometer, modified using a smartphone app, ???

by measuring the input signal period and duty cycle would you generate the output in 'real time'?

  1. output pulses with a different period but same duty cycle
  2. output pulses same period but different duty cycle
  3. output pulses with a different period and different duty cycle

how do you set the updated values?

I am attempting to measure a varying input Hz input, lets say 1500 to 6000 range most likely. Idealy the output will be controled by a potentiometer or just hard coded that can be changed. The output would then be generated in real time or even if it is slightly offset should work.

did you try the code by @JohnLincoln in post 7?
did it do what you require?
if not what was the problem?