Project Fail Safe

I could use some help/Thoughts/Guidance on a project i am working on.

Project description:

I have an secondary automotive fuel injection system that i am trying to monitor. The system works off of the car's boost pressure to initiate a progressive fuel injection system. In my specific case, once the car starts making 12 LBS of boost a progressive controller uses a 12V 300-PSI UHO pump to inject fuel through nozzles mounted into the intake manifold. This adds additional fuel to increase the car's performance, Horsepower. My system is setup to start pumping at 12lbs of boost and progressively increase until 20lbs of boost. Once it detects 20lbs of boost the pump/system is at 100% .

The limitation of the system, is it is a 2ndary system not monitored by the car's ecu, so failure of the system often will lead to major internal engine damage, up to full engine failure and rapid destructive disassembly of the engine.

So the idea is to monitor the signal that is being sent to the pump via the injection systems controller, and compare that signal to a pressure sensor mounted just before the injection nozzle(s). If the nozzle pressure is too low or high, send a 5 volt signal out. (to be used to tell the car to reduce the boost pressure in a kill circuit i am working on).

System Information:

The signal the fuel system controller sends to the pump is a 12v DC PWM signal @ 16khz.
The approximant PSI readings at the nozzle are 160 +/- PSI @ 100% duty cycle.
I am using a 0-200 PSI 0-5V (.5-4.5V) pressure transducer/sensor.
Arduino Uno (hoping nano in future to reduce size).
DataLogger sheild to capture data. This will be used later on to decide fault limits/calculation.
Optocoupler circuit to duplicate the 12v signal to a 5 volt signal in order to safely read the signal with the aurdrino.
I2C LCD screen, to display readings.
Couple led's, to display status.

Current Project Status:
I actually have a couple "working" versions of this system. But each one has its "issues". I am looking for some help, or direction i should persue.

All of the issues are related to capturing the 16khz DC PWM signal.

Version and Issues:

V1: Note this is a stripped down code for debugging. This works great as a bench test jumping pin through the optocoupler into pin 2. Pin 6 is about 976 HZ.
But once i attempt it on the 16khz signal it reads 100% duty cycle. (i think this method just cant ready a signal that fast??)
:

// Libraries
#include <LiquidCrystal_I2C.h>  // LCD Screen
#include <SPI.h>                // Communicate with SDI devices
#include <SD.h>                 // SD Card
#include <Wire.h>               // must be included here so that Arduino library object file references work
#include "RTClib.h"             // Real Time Clock

//LCD Address
LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x3F for a 16 chars and 2 line display

byte copyrightSymbol[] = {
  B01110,
  B10001,
  B11111,
  B11001,
  B11001,
  B11111,
  B10001,
  B01110
};

// Interrupt / Pump Variables
  unsigned long fall_Time;                  // Placeholder for microsecond time when last falling edge occured.
  unsigned long rise_Time;                  // Placeholder for microsecond time when last rising edge occured.
  volatile int frequency = 0;               // Calculated frequency.
  volatile byte dutyCycle = 0;              // Duty Cycle %
  volatile byte isr0Tick = 0;               // Increments every ISR call (used to know if ISR fired since last read)
  const int pumpInPin = 2, pumpLEDPin = 7;  // Pump in Pin, LED output Pin
  float dCR, fR;

//Pump Signal Test System
  const int pWMOutPin = 6;
  float pWMOut = 125;

void PinChangeISR0() 
{ 
  unsigned long total_Time;
  unsigned long on_Time;
  unsigned long lastRead = micros();  // Get current time
  isr0Tick++;                         // Kick the tires to say we have been here
  if (digitalRead(pumpInPin) == LOW)  // Falling edge
  {
    fall_Time = lastRead;                             // Just store falling edge and calculate on rising edge
  } else {                                            // Rising edge
    total_Time = lastRead - rise_Time;                // Get total cycle time
    frequency = 1000000 / total_Time;                 // Calulate frequency and store
    on_Time = fall_Time - rise_Time;                  // Get on time during this cycle
    dutyCycle = 100 / ((float)total_Time / on_Time);  // Convert to a percentage
    rise_Time = lastRead;                             // Store rise time
  }
}

void showPressure() 
{
  lcd.setCursor(3, 2);
  lcd.print("System Status=");
  lcd.setCursor(0, 3);
  lcd.print("DC:");
  lcd.setCursor(3, 3);
  lcd.print(dutyCycle, 1);
  lcd.print("  ");
  lcd.setCursor(8, 3);
  lcd.print("%");
  lcd.setCursor(10, 3);
  lcd.print("HZ:");
  lcd.setCursor(13, 3);
  lcd.print(frequency, 1);
  lcd.print("  ");
}

//Setup
void setup() 
{
    //LCD Setup
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.createChar(0, copyrightSymbol);

    //Serial Setup
  Serial.begin(9600);
  Serial.println(F("Sensor is Starting Up"));

  //Pump_Interrupt Setup
  pinMode(pumpInPin, INPUT);                  // Interrupt Pin
  attachInterrupt(0, PinChangeISR0, CHANGE);  // Attach interrupt handler 0 = Pin 2
  pinMode(pumpLEDPin, OUTPUT);

  //Pump test System
  pinMode(pWMOutPin, OUTPUT);

    showPressure();
}

void loop() 
{

showPressure();

Serial.print("Frequency:");
Serial.print(frequency);
Serial.print("dutycycle");
Serial.println(dutyCycle);

//PWM for Pump Signal Simulation
  analogWrite(pWMOutPin, pWMOut);

//Frequency and Duty Cycle
  static byte oldisr0Tick = isr0Tick;
  if (oldisr0Tick != isr0Tick)  // ISR has fired so use it's values
  {
    oldisr0Tick = isr0Tick;
  } else { // No interrupt since last read so must be 0% or 100%
      if (digitalRead(2) == LOW) 
      {
        frequency = 0.00;
        dutyCycle = 0.00;
      } else {
        frequency = 0;
        dutyCycle = 100;
    }
  }
  if (dutyCycle > 0) 
  {
    digitalWrite(pumpLEDPin, HIGH);
  } else {
    digitalWrite(pumpLEDPin, LOW);
  }
}

V2. This one actually works with the 16khz signal. But its just too slow capture the signal. It also seems to be affected when i start adding additional functions to the program/loop (which i would could be ok with).
:

// Libraries
#include <LiquidCrystal_I2C.h>  // LCD Screen
#include <SPI.h>                // Communicate with SDI devices
#include <SD.h>                 // SD Card
#include <Wire.h>               // must be included here so that Arduino library object file references work
#include "RTClib.h"             // Real Time Clock

//LCD Address
LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x3F for a 16 chars and 2 line display

byte copyrightSymbol[] = {
B01110,
B10001,
B11111,
B11001,
B11001,
B11111,
B10001,
B01110
};

/* Pump DC setup
  * "PWMIn" is Pulse in
  * DC is Calculated Duty Cycle % 
  * Will be used on "REAL" Circuit*/
#define DISPLAY_INTERVAL   1000000L
#define IRQPIN             2
unsigned long DCRCurrent;
volatile unsigned int count = 0;
volatile unsigned int ovf = 0;
unsigned long frequency = 0;
unsigned long highCount = 0; 
unsigned long totalCount = 0;
float dutyCycle = 0;
unsigned long lastDisplay = 0;
float PWMIn, DC, dCR;
unsigned long DCRPrevious;
float HZ, fR;
float DCSignal;


//Pump Signal Test System
const int pWMOutPin = 6;
float pWMOut = 125;



void showPressure() 
{
lcd.setCursor(3, 2);
lcd.print("System Status=");
lcd.setCursor(0, 3);
lcd.print("DC:");
lcd.setCursor(3, 3);
lcd.print(dutyCycle, 1);
lcd.print("  ");
lcd.setCursor(8, 3);
lcd.print("%");
lcd.setCursor(10, 3);
lcd.print("HZ:");
lcd.setCursor(13, 3);
lcd.print(frequency, 1);
lcd.print("  ");
}

void pulse()
  {
  if (PIND & 0x04)
  {
  TCNT1 = 0x0000;
  ovf = 0;
  }
  else
  {
  highCount += TCNT1;
  highCount += ovf*65536L;
  }
  count++;
  }

//Setup
void setup() 
{
  //LCD Setup
lcd.init();
lcd.clear();
lcd.backlight();
lcd.createChar(0, copyrightSymbol);

  //Serial Setup
Serial.begin(9600);
Serial.println(F("Sensor is Starting Up"));

//Pump_Interrupt Setup
pinMode(IRQPIN, INPUT);
attachInterrupt(0, pulse, CHANGE);
TIMSK1 = (1 << TOIE1); // timer overflow interrupt enabled
TCCR1A = 0x00; // 
TCCR1B = 1; // prescaler == 1 => timer runs at clock speed: 16 MHz

//Pump test System
pinMode(pWMOutPin, OUTPUT);

  showPressure();
}

void loop() 
{

showPressure();

Serial.print("Frequency:");
Serial.print(frequency);
Serial.print("dutycycle");
Serial.println(dutyCycle);

//PWM for Pump Signal Simulation
analogWrite(pWMOutPin, pWMOut);

// Pump DC Simulation 
// 0-255 is 0-100% DC (use PWM var. in setup to adjust and test as needed)
DCRCurrent= millis();
DCSignal= digitalRead(2);
uint32_t now = micros();
if (now - lastDisplay > DISPLAY_INTERVAL)
{ 
lastDisplay = now;
cli();
{
frequency = count;
count = 0;
totalCount = highCount;
highCount = 0;
}
sei();
frequency /= 2;
dutyCycle = totalCount/160000.0;
}

}

ISR(TIMER1_OVF_vect) 
{
ovf++;
}

So my last thought, was instead of measuring the duty cycle directly. Why not just convert the digital signal into an "analog" signal? Then read the signal from an analog pin. Obviously that comes with its own "issues". I started working on "making" a low-pass filter to clean the digital pulse into a constant voltage. But i have no idea how to calculate the correct resistors, to use and the only capacitors i have are the Ceramic 104's and 10uf 50v capacitor's.

I am a mechanical engineer by trade, and electronics are outside my expertise. Also new to programming/self taught for about 4 months now. So please excuse my ignorance on this. I am still learning how to properly use the internal timers for a project like this.

Unfortunaly i also do not have a signal generator to bench test the system.

Thanks in advance.

Chris

// Libraries
#include <LiquidCrystal_I2C.h>  // LCD Screen
#include <SPI.h>                // Communicate with SDI devices
#include <SD.h>                 // SD Card
#include <Wire.h>               // must be included here so that Arduino library object file references work
#include "RTClib.h"             // Real Time Clock

//LCD Address
LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x3F for a 16 chars and 2 line display

byte copyrightSymbol[] = {
  B01110,
  B10001,
  B11111,
  B11001,
  B11001,
  B11111,
  B10001,
  B01110
};

const int pumpInPin = 2, pumpLEDPin = 7;  // Pump in Pin, LED output Pin

//Pump Signal Test System
const int pWMOutPin = 6;
float pWMOut = 125;
volatile unsigned long total_Time = 0;
volatile unsigned long Period_Time = 0;
volatile unsigned long isr0Tick = 0;// Increments every ISR call (used to know if ISR fired since last read)
unsigned long frequency = 0;
float dutyCycle = 0.0;


void PinChangeISR0() {
  static unsigned long fall_Time = 0;
  isr0Tick++;
  if (digitalRead(pumpInPin) )  total_Time = total_Time + micros() - fall_Time;
  else  {
    Period_Time = micros() - fall_Time;
    fall_Time = micros();
  }
}

void showPressure() {
  lcd.setCursor(3, 2);
  lcd.print("System Status=");
  lcd.setCursor(0, 3);
  lcd.print("DC:");
  lcd.print(dutyCycle, 1);
  lcd.print("% HZ:");
  lcd.print(frequency);
  lcd.print("  ");
}


void setup() {
  //LCD Setup
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.createChar(0, copyrightSymbol);

  //Serial Setup
  Serial.begin(9600);
  Serial.println(F("Sensor is Starting Up"));

  //Pump_Interrupt Setup
  pinMode(pumpInPin, INPUT);                  // Interrupt Pin
  pinMode(pumpLEDPin, OUTPUT);

  //Pump test System
  pinMode(pWMOutPin, OUTPUT);
  
  attachInterrupt(digitalPinToInterrupt(pumpInPin ), PinChangeISR0, CHANGE);  // Attach interrupt handler 0 = Pin 2
  delay(500);
  noInterrupts();
}

void loop() {
  showPressure();

  Serial.print("Frequency:");
  Serial.print(frequency);
  Serial.print("dutycycle");
  Serial.println(dutyCycle, 0);

  dutyCycle = float((100 * total_Time) / isr0Tick) / (float)Period_Time;
  frequency = uint32_t(1.0 / (float)Period_Time);
  total_Time = 0;
  Period_Time = 0;
  isr0Tick = 0;
  interrupts();
  delay(500);
  noInterrupts();

  //PWM for Pump Signal Simulation
  analogWrite(pWMOutPin, pWMOut);
  digitalWrite(pumpLEDPin, dutyCycle > 0);
}

Do you have knowledge of an automotive electrical system and the bad things associated with it? Where is this going to be mounted? What happens if the battery gets reversed or it goes to 24V? These are just a minimal amount of things you will need to consider and account for. Look up the AEC Q100 and Q200 documents, they will explain a lot.

Thanks for the heads up. Yes i am aware of the issues that come along with automotive electrical systems. Luckily this will placed inside the cabin (other than 3 or 4 wires routed and a pressure sensor into engine bay). The only thing that will be "connected" to the cars electrical system will be the power to the aurdino its self. Right now just powered by the factory 5v usb outlet.

Have a google of diode pump circuits as a method of producing your DC voltage
Just be careful that it doesn’t make more than 5v - so load the output with a zener diode and resistor .

I guess what you really want is to stop the car running lean - so you could just buy an air/fuel ratio gauge . STACK may do one with an alarm output ?

16 kHz is a period of 62.5 us (1000 CPU cycles). With say a 10% duty cycle, you'd be attempting to measure 6.25 us. The resolution of micros() is 4 us so it is not really good enough. You probably want to use the input capture mode of a timer to measure the wave form. Example: https://www.gammon.com.au/forum/?id=11504&reply=13#reply13

digitalRead() and digitalWrite() are also slow (a few us)

You also don't want to write to the LCD every loop iteration. It is slow and most libraries use blocking code. Maybe update the screen only if the last update was more 500 ms old. Which should be enough for human vision.

The optocoupler doesn't introduce much latency but, for the 12v to 5v reduction, a resistor divider network would do. However, the isolation the optocoupler provides may be good.

If you start looking at the low pass filter, at a guess you'd use a 100nF capacitor with a 330 ohm resistor but you may have to experiment. You could test this by configuring a Nano to generate a 16 kHz at various duty cycles and measure the voltage (say using another arduino) to see if it is responsive enough. I may get some time today to set up a simulation (LTspice)

Great info I really do appreciate you taking the time to pass it along. In the full blown code i do not lcd write every loop. Its set to 300 ms as of this moment and only writes the number locations not the entire screen. But i may open that up slightly as well.

I have read through Gammons information. I will focus on that section and try to learn more about the timers and capture.

Here is a simulation of 16 KHz 5v PWM with 10% and 90% duty cycles using an RC network of 100nF and 1k. Maybe it helps.

1 Like

Using the Gammons "duty cycle" capture method seems to be working perfectly for measureing the "on" portion of the pulse. I appreciate it. I think i am going to chase this technique for now. I will report back. I do need to see if i can grab the entire pulse length at the same time. For now i am just manually inserting the pulse length (1024us for 976 hZ) and (62.5us for 16kHz). Any input on capturing the full pulse length at the same time?

Thanks again!

Maybe show your test code.
The code sample obviously worked with the full wave, that is it detected the first rising edge, then flipped over to trigger on the falling edge, then set is trigger again on the next rising edge, timing the intervals in between. However, he was interested only in looking at a single wave. You'll have examine a complete stream.
There is another example of input capture in this project, which I wrote sometime ago for identifying sequences of IR pulses, but the logic may be a bit obscured by optimisation measures: Arduino IR Learning Remote Control

1 Like

6v6gt,

Here is a stripped working version i have of the code right now. (ignore the mess, lol)

This is where i am just plugging in the time value based on the known frequency.

dutyCycle = (((float (elapsedTime) * 62.5e-9 * 1e6) / 1024) * 100);

// Libraries
#include <LiquidCrystal_I2C.h>  // LCD Screen
#include <SPI.h>                // Communicate with SDI devices
#include <SD.h>                 // SD Card
#include <Wire.h>               // must be included here so that Arduino library object file references work
#include "RTClib.h"             // Real Time Clock

//LCD Address
LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x3F for a 16 chars and 2 line display

//Pulse Capture
volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;
unsigned long elapsedTime;
float dutyCycle;


//Pump Signal Test System
const int pWMOutPin = 6;
float pWMOut = 125;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect) 
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  
  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    TIFR1 |= bit (ICF1);     // clear Timer/Counter1, Input Capture Flag
    TCCR1B =  bit (CS10);    // No prescaling, Input Capture Edge Select (falling on D8)
    first = false;
    return;  
    }
    
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
  }  // end of TIMER1_CAPT_vect
  
void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
  }  // end of prepareForInterrupts


void showPressure() 
{
  lcd.setCursor(3, 2);
  lcd.print("System Status=");
  lcd.setCursor(0, 3);
  lcd.print("DC:");
  lcd.setCursor(3, 3);
  lcd.print(dutyCycle, 1);
  lcd.print("  ");
  lcd.setCursor(8, 3);
  lcd.print("%");
  // lcd.setCursor(10, 3);
  // lcd.print("HZ:");
  // lcd.setCursor(13, 3);
  // lcd.print(frequency, 1);
  // lcd.print("  ");
}

//Setup
void setup() 
{
  //LCD Setup
  lcd.init();
  lcd.clear();
  lcd.backlight();

  //Serial Setup
  Serial.begin(115200);   
  Serial.println(F("Sensor is Starting Up"));

  //Pump_Interrupt Setup
  // set up for interrupts
  prepareForInterrupts (); 


  //Pump test System
  pinMode(pWMOutPin, OUTPUT);

  showPressure();
}

void loop() 
{

showPressure();

Serial.print("dutycycle");
Serial.println(dutyCycle);

//PWM for Pump Signal Simulation
analogWrite(pWMOutPin, pWMOut);


// wait till we have a reading
if (!triggered)
  return;

// period is elapsed time
elapsedTime = finishTime - startTime;
  
dutyCycle = (((float (elapsedTime) * 62.5e-9 * 1e6) / 1024) * 100);  
Serial.print("dutycycle");
Serial.println(dutyCycle);

prepareForInterrupts (); 

}

This version is jumped from pin 6 to pin 8 for testing.
I have tried this with my 16kHz signal and it works perfectly (i have to change the value from 1024 to 62.5).

That all sounds good so far. Keep the thread up to date with your progress.

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