Go Down

Topic: Read signal (5v wave) get it's frequency, output converted frequency x2 (Read 337 times) previous topic - next topic

motorica

Hi

I need to convert square wave 12v signal from car engine ecu, calculate it's frequency and output converted PWM signal with frequency multiply by a factor.

I tried several timer libraries but no success up to now.

1. Read PWMinput
2. Calculate frequency of PWMinput
3. Generate PWMoutput signal with PWMinput x someFactor

Any ideas ?

Thanks !
David

Robin2

PWM signals usually have a constant frequency and a variable mark-space ratio. Your outline does not mention measuring the mark-space ratio.

Do you really have a PWM input signal or just a square wave of varying frequency?

Do you want to output a square wave at twice the input frequency or do you want to output a PWM signal with a mark-space ratio that reflects the frequency of the incoming signal?

Or what?

Any of these things should be doable. I doubt if any of the Timer libraries will be necessary.

...R

motorica

Hi Robin

The input is analog 12v square wave signal which is an input to rpm gauge cluster.
Due to engine conversion (different number of cylinders) i need to correct the signal to display the actual rpm.

I can lower the signal to 5V using voltage divider.
The rpm gauge works with arduino 5V pwm signal frequency between 10-220 HZ (tested)

Thanks
David

PeterH


The rpm gauge works with arduino 5V pwm signal frequency between 10-220 HZ (tested)


It seems unlikely that you tested what you describe above, since it's neither easy nor necessary to generate a variable frequency PWM signal in this case.

All you need is to
(1) measure the incoming pulse length in microseconds.
(2) halve that value.
(3) use the technique demonstrated in blink without delay to output a square wave of that pulse length

The whole sketch would probably be about a dozen lines of code.
I only provide help via the forum - please do not contact me for private consultancy.

Robin2


The input is analog 12v square wave signal which is an input to rpm gauge cluster.
Due to engine conversion (different number of cylinders) i need to correct the signal to display the actual rpm.

I can lower the signal to 5V using voltage divider.
The rpm gauge works with arduino 5V pwm signal frequency between 10-220 HZ (tested)


You need to be more careful in your use of technical terms - both your for your benefit and ours.

What you describe in the first two lines is a common problem and has nothing to do with PWM.

What you say in the last line is a mystery because you use the word PWM in the same sentence as variable frequency. But the Arduino does not produce a variable frequency PWM signal when you use analogWrite().  Please show the code you used to test the gauge with the Arduino.

If, as seems likely, all you need to do is produce a square wave with a different frequency @PeterH has described how to do it.

...R

motorica

What i did up to now is activate the gauge using pwm.h library , not a variable pwm but a fixed.

Thanks for guiding me to drop  the PWM , it did helped a lot !


Paul__B

Certainly you do not need any form of PWM.  Or library!

Automotive timing is readily managed in straightforward software running on the MCU.  And don't even think about trying to use interrupts.

You use a code loop that records the time between successive transitions of the input signal.    On each transition, it begins an output pulse whose length is defined as one quarter of the length of the sum of the last two transition durations (in other words, a quarter of the current input cycle, considered in alternating direction).  The code, whilst waiting for the next transition, times out that pulse.

This might have to be modified if the input waveform is not "square" but in fact consists of narrow pulses, but I suspect it is designed to be "square" if it is to control an analog gauge.

motorica

Hi guys

I tried the most simple flow you suggested, but with no success..
The rpm needle is responding to engine rpm but rpm is lower , no matter what is the conversion value of the duration i'm measuring using pulsein method.

I have also tried interrupt and it works great ! but i can't change the output signal, only output what the exact input signal.

Any ideas ?

Thanks !
David

pulsein method

Code: [Select]
#define OUT_PIN 13
#define IN_PIN 7

   
unsigned long duration = 0;
int rpmFactor = 100;
int convertedDuration;
unsigned long readTimeout = 10000;
int state=0;

void setup(){
  pinMode(OUT_PIN, OUTPUT);
  pinMode(IN_PIN, INPUT);
  Serial.begin(9600);
}

void loop()
{
  duration = pulseIn(IN_PIN, HIGH);
  digitalWrite(OUT_PIN, !digitalRead(OUT_PIN));
  delayMicroseconds(duration);
  Serial.println(duration);
}



Interrupt

Code: [Select]
int pin = 13;
volatile int state = LOW;

void setup()
{
  pinMode(pin, OUTPUT);
  attachInterrupt(0, blink, CHANGE);
  Serial.begin(9600);
  int state = 0;
}

void loop()
{
    digitalWrite(pin, state);   
}

void blink()
{
  state = !state;
}

Robin2

The first problem with your two sketches is a conceptual one. In both cases the output is exactly governed by the input so it simply can't be different in the way you want.

@PeterH described the general princple that should be used in your first sketch.

In this case I think it will be more difficult usin interrupts because for every interrupt you need to produce 2 outputs that are evenly spaced between the inputs.

You need to use pulseIn to measure the width of the pulse and save it after dividing by 2. Then you need a separate function to generate the output pulses based on those half widths. Something like this pseudo code

Code: [Select]
void loop() {
    curMicros = micros();
    getAndSavePulseWidth();
    makeNewPulses();
}

void makeNewPulses() {
   if (curMicros - prevPulseMicros >= halfPulseWidth) {
      pinState = ! pinState
      digitalWrite(pulsePin, pinState);
      prevMicros += halfPulseWidth;
   }
}


A possible (probable ?) problem with this is that the pulseIn function blocks the CPU until it finishes. If that does cause a problem you may need to use interrupts in place of pulseIn.

If you have attachInterrupt(0, measurePulse, CHANGE);

your ISR could be something like this (not tested)

Code: [Select]
void measurePulse() {
   pulseMicros = micros();
   halfPulseWidth = (pulseMicros - prevMicros) / 2;
   prevMicros = pulseMicros;
}


...R

motorica

Hi Robin

My first example don't include a change in the measured time i've recorded but i did tried to convert this value and it didn't influence the output to the gauge.

I also thinks that the pulsein hangs the cpu and cause the code not to work as expected, this is why i tried interrupts just for proof of concept. It doesn't include any change for the output signal just a copy.

So i guess interrupts are do needed, i will try your idea to measure the time within interrupt and will update.
Maybe there is a need for 2 interrupts, one for measuring and another for generating..


Thanks !
David

Robin2


Maybe there is a need for 2 interrupts, one for measuring and another for generating..


I don't think so. Once you have a suitable value for the halfPulseWidth any simple code will produce the output.

I guess you could use one of the hardware timers (Timer1 or Timer2) to create the output using something derived from the measured halfPulseWidth to set the timing interval. I think the timers can cause a pin change directly.

...R

motorica

I just tried to measure the pulse within the interrupt, it doesn't work.
I see on the serial window that on each 14 readings the value jumps from around 1800 to 28,000 .. (computed halfPulseWidth)


Code: [Select]
int out_pin = 13;
volatile long unsigned pulseMicros;
volatile long unsigned halfPulseWidth;
volatile long unsigned prevMicros = 0;;

void setup()
{
  pinMode(out_pin, OUTPUT);
  attachInterrupt(0, measurePulse, CHANGE);
  Serial.begin(9600);
}

void loop()
{
  makeNewPulses();
}

void makeNewPulses()
{
  digitalWrite(out_pin, !digitalRead(out_pin));
  delayMicroseconds(halfPulseWidth);
  Serial.println(halfPulseWidth);
}


void measurePulse()
{
   pulseMicros = micros();
   halfPulseWidth = (pulseMicros - prevMicros)/2 ;
   prevMicros = pulseMicros;
}

 

Robin2

What happens if you use my code for making the pulses. It does not depend on delay().

What you are experiencing is the fun and frustration of using interrupts. They can be very tiresome to debug.

Are you quite sure that the value of halfPulseWidth should not vary like your numbers indicate?

Are you quite sure that I wrote the code correctly?

What happens if you just print halfPulseWidth and don't bother with the digitalWrite() statements?

Maybe you should just interrupt on RISING or FALLING rather than CHANGE.

These are all things I would expect you to think of trying yourself without any prompting. Each of them only take a few minutes to try. The Arduino is a great device for learning by doing.

...R

Paul__B


I just tried to measure the pulse within the interrupt, it doesn't work.

You are not listening, are you?

No interrupts, no libraries.
Code: [Select]
// Pulse Doubler
// Public Domain  Paul B.

#define OUT_PIN 13
#define IN_PIN 7

// Concept: Look for transitions; need state memory
byte in_was = LOW;
// Read once per cycle
byte in_now;
// Time of last transition up
unsigned long wentup = 0;
// Time of last transition down
unsigned long wentdn = 0;
// Output pulse duration
unsigned long pulsetime = 0;
// Time last pulse started
unsigned long pulseis = 0;

// Initiate an output pulse
void pulseon(unsigned long length) {
 digitalWrite(OUT_PIN, HIGH);
 pulsetime = length >> 2;  // A quarter of the last interval
 pulseis = micros();
}

void setup(){
 pinMode(OUT_PIN, OUTPUT);
 pinMode(IN_PIN, INPUT);
 digitalWrite(IN_PIN, HIGH);  // Input pullup
}

void loop()
{
 // Read pin once
 in_now = digitalRead(IN_PIN);
 // Now, were we high or low?
 if (in_was == LOW) {
   if (in_now == HIGH) {
     // So, has transitioned from low to high
     in_was = HIGH;
     pulseon(micros() - wentup);
     wentup = micros();
   }
 }
 else {
   if (in_now == LOW) {
     // So, has transitioned from high to low
     in_was = LOW;      
     pulseon(micros() - wentdn);
     wentdn = micros();
   }
 }

 delay(15); // Very crude debounce for proof of concept only
 // Now perform the pulse delay
 if (pulsetime != 0) {
   if (micros() - pulseis >= pulsetime) {
     pulsetime = 0;
     digitalWrite(OUT_PIN, LOW);
   }
 }
}


So, here is some proven code written - especially for you!   :smiley-eek:

Note it is test code; proof of concept using a crude debounce delay and an input pullup so that you can test it with a button on pin 7.

Prove it for yourself, then for your tacho, remove the delay() and do what you like with the input pullup.

One little warning, the duty cycle of the input must be between about 35 and 65% (because the pulse width is set at 25%).


Edit: First tested using millis(), but no point not using micros() for the demo anyway!

motorica

Actually it isn't that simple to check , as i need to start the car (very noisy) each time i make a change

Anyway i will check any new idea and will update, i'm sure i will help in the future as this project is relevant for anyone that wants to correct speedometer reading, tachometer reading, on board car computer and so on.

Thanks guys !

David


Go Up