RPM sensor output simulation to mock an ECU

Hi! New to the arduino forum, i´m a bit familiar with arduino but not an expert by any means.
Just wanted to ask a question about a project i´m doing where i simulate the output of an rpm sensor of an engine, which has 60 teeth (pulses fro 1 to 0), the thing is, in engines, there is two missing teeth to mark the top dead center of piston 1 usually.
Now that is not the problem, but contributes to the main problem, processing speed of arduino with my code, wich i´ll attach.
I´ll be attaching a pic with an image of what a wheel looks like, so you have a better idea.
there´s two false teeth 180º apart in the wheel i´m trying to replicate whereas this one only has one flase tooth.
unnamed.jpg
This is what the rpm sensor output looks like, now, i know this is not a square signal, but square signa does the job, just so you have a better idea of what the false tooth looks like.

Back to the problem. So the problem is that at 6000 rpm (100rps) a spin lasts 10ms, with 60 teeth, and 120 switches from 0 to 1, the gap is 83 microseconds between high and low, the problem shows in the first teeth of every spin, because i have to map a potenciometer to move the rps value, and show the rpm in a LCD screen, wich takes too much time and slows the process, making the signal bad and generating an error in the ECU i´m trying to trick in thinking there´s an engine running.
Btw i´m using an Arduino UNO Rev3.

Any ideas on how i can make it easier for arduino to run this program quicker? I´ve already used port manipulation to make the change between bool values faster, although the problem remains in the mapping and calculations for rpm and rps.

Thanks in advance!

#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

int val;
int pot = 3;
int rps;
int rpm;
int teeth = 60; //Teeth count
int interval; //interval between switch from 1 to 0
int count;
int output = 5;

void setup() {
  lcd.begin(16, 2);

  lcd.print("RPM");
  lcd.setCursor(0, 1);

  pinMode(output, OUTPUT);
}
void loop() {






  for (count = 0; count < teeth; count++) {       //for loop for teeth count

    val = analogRead(pot);
    rps = map(val, 0, 1023, 10, 50);                //pot map to revs per second (min-600rpm, max 3000rpm)
    rpm = (rps * 60);
    interval = (((1000000 / (rps * teeth)) / 2));   //interval in microseconds
    lcd.print(rpm);

    if (count = 0 or 1 or 30 or 31) {              //flase teeth 0
      digitalWrite(output, LOW); // pin 5 = 0
      delayMicroseconds(interval);
    }
    else if (count = 2 or 3 or 32 or 33) {         //false teeth 1
      digitalWrite(output, HIGH); //pin 5 = 1
      delayMicroseconds(interval);
    }
    else {                                         //normal functioning
      digitalWrite(output, LOW); // pin 5 = 0
      delayMicroseconds(interval);
      digitalWrite(output, HIGH); //pin 5 = 1
      delayMicroseconds(interval);
    }
  }
}

Proyecto.ino (1.3 KB)

unnamed.jpg

i am not going to download your code. In your design process, how did you intend to know when to simulate the missing teeth?
How often do you update thee display? Once per second if more than enough. Do you display ONLY the values that have changed? If you regularly clear the display, don't!
Paul

You definitely don't want (or need) to write to the LCD every single pass of loop().

You could set up the ADC "manually" -- using registers instead of the API -- to perform continuous conversions in the background and read the latest value when you need it.

You could (and probably should) use Timer1's output compare function to generate the waveform. Something along the lines of:

NOTE: In order to use Timer1's OC capability, the RPM output pin in this example is now pin 9.

I didn't include your LCD stuff though it shouldn't be hard to add.

YMMV.

const uint8_t pinRPMOut = 9;

volatile uint16_t
    g_OC1AHalfPeriod = 0x3415;

uint32_t
    timeNow,
    timeADC;
uint16_t
    rps,
    rpm;
    
void setup( void )
{
    Serial.begin( 115200 );
    
    pinMode( pinRPMOut, OUTPUT );

    //set up ADC with /16 prescaler, auto-trigger
    ADMUX = _BV(REFS0); //AVCC reference
    ADCSRA = _BV(ADEN) | _BV(ADATE) | _BV(ADPS2);
    //mux enable
    ADCSRB = _BV(ACME);
    //start conversions; they'll happen on their own now
    ADCSRA |= _BV(ADSC);

    //timer 1:    
    //zero the CNT and assign an arbitrary OC
    TCNT1 = 0;
    OCR1A = 0x0500;
    //set OC to clear pin
    TCCR1A = _BV(COM1A1);
    //set /1 prescaler (16MHz clock)
    TCCR1B = _BV(CS10);
    //enable OC1A interrupt
    TIMSK1 = _BV(OCIE1A);       
    
}//setup

void loop( void )
{
    //time for an ADC update?
    timeNow = millis();
    if( timeNow - timeADC >= 50ul )
    {
        timeADC = timeNow;
        //if conversion is available...
        if( ADCSRA & _BV(ADIF ) )
        {            
            //read latest and clear flag
            uint16_t ADCVal = ADC;
            ADCSRA |= _BV(ADIF);        
            //do the math    
            rps = map(ADCVal, 0, 1023, 10, 50);                //pot map to revs per second (min-600rpm, max 3000rpm)
            rpm = rps * 60;
            //number of ticks per OC is based on half-period
            uint32_t ulTemp = (16E6 / (60*rps))/2;
            //disable ints for a moment to update the global used by the ISR
            noInterrupts();
            g_OC1AHalfPeriod = (uint16_t)ulTemp;
            interrupts();        

            //update the serial monitor (or LCD in your case0
            Serial.print( ADC );
            Serial.print( "\t" ); Serial.println( rpm );
            
        }//if
        
    }//if
    
}//loop

ISR( TIMER1_COMPA_vect )
{
    static volatile uint8_t
        Teef = 0;
    uint16_t
        temp = OCR1A;

    //grab the current OC time and add the half-period to it to set the next edge
    OCR1A = temp + g_OC1AHalfPeriod;

    //if we're now 1 tooth behind the desired missing/false (?) teeth...
    if( Teef == 59 || Teef == 0 || Teef == 29 || Teef == 30 )
    {
        //set OC to leave pin low next interrupt
        TCCR1A = _BV(COM1A1);
    }
    else if( Teef == 1 || Teef == 2 || Teef == 31 || Teef == 32 )
    {
        //or leave pin high 
        TCCR1A = _BV(COM1A1) | _BV(COM1A0);
    }
    else
    {
        //or toggle the pin
        TCCR1A = _BV(COM1A0);
    }

    //bump the tooth counter; when we reach '60', zero it
    Teef++;
    if( Teef == 60 )
        Teef = 0;       
        
}//ISR compare A

Have you looked at Ardu-Stim?

Paul_KD7HB:
i am not going to download your code. In your design process, how did you intend to know when to simulate the missing teeth?
How often do you update thee display? Once per second if more than enough. Do you display ONLY the values that have changed? If you regularly clear the display, don't!
Paul

Yeah, every wheel spin the rpm value is read and shown in the LCD, does that take a lot of effort and time for arduino?. How do I display a value only when it has changed?
I have used a for loop to count the teeth and three if inside it to keep the output at 1 or 0

Hi,
Welcome to the forum.
It is best if you want to put images in to your post that you attach them first, rather than use off forum links.
There is one image in your original post that is not visible.

Please read the post at the start of any forum , entitled "How to use this Forum".
OR
http://forum.arduino.cc/index.php/topic,148850.0.html.
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Thanks.. Tom... :slight_smile:

edmcguirk:
Have you looked at Ardu-Stim?

I have, but since this is a school project, I wanted to do it myself, and for some reason the ardustim program doesn´t compile.

Blackfin:
You definitely don't want (or need) to write to the LCD every single pass of loop().

You could set up the ADC "manually" -- using registers instead of the API -- to perform continuous conversions in the background and read the latest value when you need it.

You could (and probably should) use Timer1's output compare function to generate the waveform. Something along the lines of:

NOTE: In order to use Timer1's OC capability, the RPM output pin in this example is now pin 9.

I didn't include your LCD stuff though it shouldn't be hard to add.

YMMV.

const uint8_t pinRPMOut = 9;

volatile uint16_t
   g_OC1AHalfPeriod = 0x3415;

uint32_t
   timeNow,
   timeADC;
uint16_t
   rps,
   rpm;
   
void setup( void )
{
   Serial.begin( 115200 );
   
   pinMode( pinRPMOut, OUTPUT );

//set up ADC with /16 prescaler, auto-trigger
   ADMUX = _BV(REFS0); //AVCC reference
   ADCSRA = _BV(ADEN) | _BV(ADATE) | _BV(ADPS2);
   //mux enable
   ADCSRB = _BV(ACME);
   //start conversions; they'll happen on their own now
   ADCSRA |= _BV(ADSC);

//timer 1:    
   //zero the CNT and assign an arbitrary OC
   TCNT1 = 0;
   OCR1A = 0x0500;
   //set OC to clear pin
   TCCR1A = _BV(COM1A1);
   //set /1 prescaler (16MHz clock)
   TCCR1B = _BV(CS10);
   //enable OC1A interrupt
   TIMSK1 = _BV(OCIE1A);      
   
}//setup

void loop( void )
{
   //time for an ADC update?
   timeNow = millis();
   if( timeNow - timeADC >= 50ul )
   {
       timeADC = timeNow;
       //if conversion is available...
       if( ADCSRA & _BV(ADIF ) )
       {            
           //read latest and clear flag
           uint16_t ADCVal = ADC;
           ADCSRA |= _BV(ADIF);        
           //do the math    
           rps = map(ADCVal, 0, 1023, 10, 50);                //pot map to revs per second (min-600rpm, max 3000rpm)
           rpm = rps * 60;
           //number of ticks per OC is based on half-period
           uint32_t ulTemp = (16E6 / (60*rps))/2;
           //disable ints for a moment to update the global used by the ISR
           noInterrupts();
           g_OC1AHalfPeriod = (uint16_t)ulTemp;
           interrupts();

//update the serial monitor (or LCD in your case0
           Serial.print( ADC );
           Serial.print( "\t" ); Serial.println( rpm );
           
       }//if
       
   }//if
   
}//loop

ISR( TIMER1_COMPA_vect )
{
   static volatile uint8_t
       Teef = 0;
   uint16_t
       temp = OCR1A;

//grab the current OC time and add the half-period to it to set the next edge
   OCR1A = temp + g_OC1AHalfPeriod;

//if we're now 1 tooth behind the desired missing/false (?) teeth...
   if( Teef == 59 || Teef == 0 || Teef == 29 || Teef == 30 )
   {
       //set OC to leave pin low next interrupt
       TCCR1A = _BV(COM1A1);
   }
   else if( Teef == 1 || Teef == 2 || Teef == 31 || Teef == 32 )
   {
       //or leave pin high
       TCCR1A = _BV(COM1A1) | _BV(COM1A0);
   }
   else
   {
       //or toggle the pin
       TCCR1A = _BV(COM1A0);
   }

//bump the tooth counter; when we reach '60', zero it
   Teef++;
   if( Teef == 60 )
       Teef = 0;      
       
}//ISR compare A

Wow this is some serious help, too complicated for my understanding but i´m giving it a serious look to learn a bit more, thanks dude, awesome, seriously.

for some reason the ardustim program doesn´t compile.

There is a detailed procedure for installing Ardustim given in this thread. It is somewhat complex.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.