Go Down

Topic: Driving Comp Air Engine with Solenoids and Arduino (Read 7648 times) previous topic - next topic

Newman180

Quote
It is just a 10k resistor 1/4 watt, 1/2 watt probably doesn't matter either. It is just called a pull up or pull down because of the way it is used to connect the wiring. Pull up resistor is wired to power, pull down it is wired to ground. You should be able to get about 100 10k resistors from anywhere for about $1 US. I think I even saw them at Walmart once?!

Shows u how much I know about electronics....  :-/

Thanks, I have a bunch on the way from Digikey anyways.

Newman180

Quote
No, I don't think you are getting it.
We don't care about "RPM".
We don't care about "degrees".

The only things we care about are:
1) When is TDC?
2) How long has it been since the last TCD?  (in microseconds)
3) How far past TDC do we open solenoid x?  when do we close it? (in percentage of rotation)

By subtracting THIS TDC from the PREVIOUS TDC, we know the period of full rotation.
By knowing the percentage of rotation ("degrees" if you must) for solenoid x to open, we know how many microseconds past TDC we need to open it.
etc. etc.


Thanks I understand now. This is a completely different way of thinking then I'm used to. I will post an updated code later.

Jonnym

... random comment - this is super interesting, thanks for keeping it public   _J

knightschade

Quote
No, I don't think you are getting it.


I love how bitter and angry Richard gets. It's entertaining. He's why i love this forum!LOL

Resistance is Futile

Newman180

I have a slight worry...since I am using only one sensor, and using the time-timeold formula , technically every command to the solenoids is one rpm behind. This could be be bad if the engine rev'd up or down too quickly... Am I correct that it is in fact always one revolution delay?

This leads me to another research angle. If I were to count the magnets with the hall effect sensor it would give me more accurate readings. For example 2 magnets would give me a half rpm delay, 4 gives me a quarter.
I have learned that I can store data in the EEPROM. If I were to have 10 magnets (for example purposes) and told my sensor counter that after 10 counts reset to 1, Then after calibration I would know which magnet is TDC (for example #5). Right before shutting the arduino down, I would prompt EEPROM to store the last count. When turning the arduino back on, EEPROM will state the stored count. The arduino would pick up where it left off if the engine hasn't been touched, which would also allow it to start without any outside help. Sound feasible?

Newman180

Quote
Yes, that is true. But you still need one unique signal at TDC.  You must know BOTH speed AND position.


If your counter labeled the TDC magnet why would you need another unique signal? Your device would say that the #5 magnet (example) is always at the top of the stroke. Which as mentioned before the EEPROM would save that calibration for the next run.

Quote
Yes, that is true, but how much change do you anticipate between one revolution and another?  How much rotating mass and friction are we dealing with here? Even if you tried, you can't change the speed all that fast

There will be a lot torque generated by this engine.  Imagine that the engine is decelerating by 1 rpm but the code still says to accelerate by 1 rpm... The piston would be returning to the top of the stroke while the inlet was open. Disaster waiting to happen....

gardner

Quote
decelerating by 1 rpm but the code still says to accelerate by 1 rpm


If it were turning 500 RPM at the time then you might see a 2 in 500 variation in speed from one turn to the next.  It really would be quite accurate to guess that since the last rotation actually took 2,000 microseconds, the one coming up will be within a few % of 2,000 microseconds too.

It is also only a few more lines of code to look at if the speed is trending up or down, and correct the estimated time for the upcoming rotation accordingly.

It should be possible to readily determine the position just using one position sensor and timing.

Newman180

Quote
If it were turning 500 RPM at the time then you might see a 2 in 500 variation in speed from one turn to the next.  It really would be quite accurate to guess that since the last rotation actually took 2,000 microseconds, the one coming up will be within a few % of 2,000 microseconds too.

It is also only a few more lines of code to look at if the speed is trending up or down, and correct the estimated time for the upcoming rotation accordingly.


You make an interesting point. Do you have an example of said code?


Another note, I realized I can manually start the engine without a starter motor. Since a compressed air engine has maximum torque at low RPM's I do not find it necessary to spin the engine to 500-600 rpm's just to get the sensors reading.
Since I can see the TDC with my own eyes, I can link the solenoids to 2 buttons controlling the 4 intake valves and 4 exhaust valves. Then just hit each button to get it to spin and let the arduino take over... Food for thought at least

Newman180

Quote
Maybe that is why most engines use "hardware" solutions like cam shafts?

As this is a senior design project, the mechanical systems were way out of my budget. I had one designed in a CAD program, but when it came to machining it was through the roof.

Quote
And gardner makes an excellent point about extrapolation and predictive algorithms. That is one of the nice things about using a micro-processor, you can do these kinds of things.


Since you have convinced me why not to use the eeprom method, I would like to move forward with the "extrapolation and predictive algorithms".  I have no idea how to program that.  :o I'm still learning the basics...  Any idea of how to write the code? If not how would I search for how to write the code?

Once again, you guys have been an invaluable source of help, and I thank you for that.

Newman180

Sorry I never responded. I have been busy studying for two big exams before finals. I will research the formula's and talk to a few of my professors. Ill update you guys with the code in a few days.

Newman180

Here is the latest update.
How do I make sure that the value of eventcount doesn't go over 1? If I'm not mistaken, with the eventcount++ it adds one to the last count, but in the ReadingRPM Playground, it acts like after 20 it resets. With my statement of if(eventcount >= 1) will that limit it to 1 and only 1?  Do I even need to include eventcount in my (time-timeold)/eventcount  if its always one?
Code: [Select]
///
///
///
///

//-----------------------------------------------
volatile unsigned int eventcount; // Assinging the Count Feature
unsigned int rt; // Rotation Time
unsigned int degree; // Position of Piston
unsigned int rpm; // RPM
unsigned long timeold; // Previous time Hall Sensor Picked up
const int So17Pin = 3; // Solenoids 1 and 7 assigned to Pin 3
const int So35Pin = 4; // Solenoids 3 and 5 assigned to Pin 4
const int So28Pin = 5; // Solenoids 2 and 8 assigned to Pin 5
const int So46Pin = 6; // Solenoids 4 and 6 assigned to Pin 6
const int buttonPin = 13; // Button For Switching "Valve Open Time" Percent
const int button2Pin = 14; // Button For Start Mode Intake 1+7
const int button3Pin = 15; // Button For Start Mode Intake 3+5
int buttonPushCounter = 0;   // counter for the number of button presses--- used with button for "Valve Open Time" Percent
int buttonState = 0;         // current state of the button
int button2State = 0;        // current state of the button 2
int button3State = 0;        // current state of  the button 3
int lastButtonState = 0;     // previous state of the button
int lastButton2State = 0;     // previous state of the button 2
int lastButton3State = 0;     // previous state of the button 3

void setup()
{
  Serial.begin(9600); // Communicate to Serial Port
  pinMode (So17Pin, OUTPUT); // Solenoid 1+7 as Output
  pinMode (So35Pin, OUTPUT); // Solenoid 3+5 as Output
  pinMode (So28Pin, OUTPUT); // Solenoid 2+8 as Output
  pinMode (So46Pin, OUTPUT); // Solenoid 4+6 as Output
  pinMode(buttonPin, INPUT); // Button for "Valve Open Time" as Input
  pinMode(button2Pin, INPUT); // Button For Start Mode 1+7 Intake
  pinMode(button3Pin, INPUT); // Button For Start Mode 3+5 Intake
  attachInterrupt(0, event_count, RISING); // Interrupt 0 is Pin 2 Hall Effect Sensor
 
}
  void loop()
{ if(eventcount >=1)
  // Calculate position of piston
    rt = (micros() - timeold)/ eventcount; // Time Between Each Hall Pulse
    rt = rt/360; // Time Between Each Hall Pulse Divided by ( 360 Degrees)--- Time for 1 degree of rotation
    rt = abs(rt); // Absolute value of found time--- To make sure every degree is a positive number ( with time-timeold and deceleration of rotation speed, there will be a negative number produced)
    timeold = micros();
    eventcount = 0;    
   
   // Calculate RPM
    rpm = (micros() - timeold) / eventcount;  // Time Between Each Hall Pulse
    rpm = 1000000/rpm;  // (Microseconds to Second) Divided by (Time Between Each Hall Pulse)
    rpm = rpm*60; //((Microseconds to Second) Divided by (Time Between Each Hall Pulse)) Times (Seconds to Minutes)
    timeold = micros();
    eventcount = 0;
         
    Serial.println(rpm,DEC); // Print RPM on Serial
   
  //-------------------------------------------------------------------------------------- START MODE
 
   if (rpm = 0) // If Device is not spinning then Enable the Button's 2 and 3. If it is spinning Disable the Button's 2 and 3
   {
   if (button2State != lastButton2State) // Start Mode Button2 Pressed--- Intake 1 and 7 Exhaust 4 and 6
   {
       if (button2State == HIGH) {
    digitalWrite(So17Pin, HIGH); // Fire Intake 1 and 7
    digitalWrite(So46Pin, HIGH); // Fire Exhaust 4 and  6
    Serial.println("Start In 1+7"); // Display Via Serial "Start In 1+7"
      }
    }
 
   {
   if (button3State != lastButton3State) // Start Mode Button3 Pressed--- Intake 3 and 5 Exhaust 2 and 8
   {
   
   if (button2State == HIGH) {
    digitalWrite(So35Pin, HIGH); // Fire Intake 3 and 5
    digitalWrite(So28Pin, HIGH); // Fire Exhaust 2 and 8
    Serial.println("Start In 3+5"); // Display Via Serial "Start In 1+7"
      }
    }
   }
  //------------------------------------------------------------------------------------ RUN MODE
    {
  }
 // Open for 100% Stroke Intake (1 and 7) Exhaust (4 and 6)
 if (degree <= ( rt*179) || (degree >= rt*1) ) // If the piston is inbetween 1 to 179 degrees of revolution then...
 {
    digitalWrite(So17Pin, HIGH); // Fire Solenoid 1 and 7 (Intake)
    digitalWrite(So46Pin, HIGH); // Fire Solenoid 4 and 6 (Exhaust)
    Serial.println("Intake (1,7) Exhaust (4,6) Fired 100%"); // Print Status and Percent of Valve Open
    }
else {
 digitalWrite(So17Pin, LOW); // Don't Fire Solenoid 1 and 7 (Intake)
 digitalWrite(So46Pin, LOW); // Don't Fire Solenoid 4 and 6 (Exhaust)
 }
 // Open for 100% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*359) || (degree >= (rt*181)) ) // If the piston is inbetween 181 to 359 degrees of revolution then...
 {
    digitalWrite(So35Pin, HIGH); // Fire Solenoid 3 and 5 (Intake)
    digitalWrite(So28Pin, HIGH); // Fire Solenoid 2 and 8 (Exhaust)
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 100%"); // Print Status and Percent of Valve Open
    }
else {
 digitalWrite(So35Pin, LOW); // Don't  Fire Solenoid 3 and 5 (Intake)
  digitalWrite(So28Pin, LOW); // Don't Fire Solenoid 2 and 8 (Exhaust)
}

}

 //------------------------------------------ Button Control--- Allows the Change of How Long Valve Stays Open Per Revolution
 
  if (buttonPushCounter <= 9){
  if (buttonState != lastButtonState) // "Percent of "Valve Open Time" " Button Pressed
  {
   
   if (buttonState == HIGH) {
      buttonPushCounter++; // Update Button Counter
     Serial.print("number of button pushes:  "); // Display Via Serial "number of button pushes: "
     Serial.println(buttonPushCounter, DEC); // Display Via Serial Button Push Count
   }
   else {          
    lastButtonState = buttonState; // Stay at last button state if Button is not Pressed
   }
  }
  else {
    buttonPushCounter = 0; // Resets to 0 if it has been pressed more than 9 times
  }

//---------------- 90% Valve Open  
 if (buttonPushCounter == 1) // If button is pressed once
 {
   Serial.println("90%");
  // Open for 90% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*161.1) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 90%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 90% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*342.1) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 90%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 80% Valve Open    
  if (buttonPushCounter == 2) {
    Serial.println("80%");
  // Open for 80% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*143.2) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 80%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 80% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*324.2) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 80%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 70% Valve Open  
if (buttonPushCounter == 3) {
 Serial.println("70%");
  // Open for 70% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*125.3) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 70%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 70% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*306.3) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 70%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 60% Valve Open  
if (buttonPushCounter == 4) {
 Serial.println("60%");
  // Open for 60% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*107.4) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 60%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 60% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*288.4) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 60%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 50% Valve Open  
if (buttonPushCounter == 5) {
 Serial.println("50%");
  // Open for 50% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*89.5) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
   

Newman180

Continuation...
Code: [Select]
Serial.println("Intake (1,7) Exhaust (4,6) Fired 50%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 50% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*270.5) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 50%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 40% Valve Open  
if (buttonPushCounter == 6) {
 Serial.println("40%");
  // Open for 40% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*71.6) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 40%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 40% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*252.6) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 40%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 30% Valve Open  
if (buttonPushCounter == 7) {
 Serial.println("30%");
  // Open for 30% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*53.7) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 30%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 30% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*234.7) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 30%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 20% Valve Open  
if (buttonPushCounter == 8) {
 Serial.println("20%");
  // Open for 20% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*35.8) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 20%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 20% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*216.8) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 20%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

//---------------- 10% Valve Open  
if (buttonPushCounter == 9) {
 Serial.println("10%");
  // Open for 10% Stroke Intake (1 and 7) Exhaust (4 and 6)
  if (degree <= ( rt*17.9) || (degree >= rt*1) ){
    digitalWrite(So17Pin, HIGH);
    digitalWrite(So46Pin, HIGH);
     Serial.println("Intake (1,7) Exhaust (4,6) Fired 10%");
    }
else {
 digitalWrite(So17Pin, LOW);
 digitalWrite(So46Pin, LOW);
}
 // Open for 10% Stroke Intake (3 and 5) Exhaust (2 and 8)
 if (degree <= (rt*198.9) || (degree >= (rt*181)) ){
    digitalWrite(So35Pin, HIGH);
    digitalWrite(So28Pin, HIGH);
    Serial.println("Intake (3,5) Exhaust (2,8) Fired 10%");
    }
else {
 digitalWrite(So35Pin, LOW);
 digitalWrite(So28Pin, LOW);
}

}

  }
}
 void event_count()
{
  eventcount++; // Hall Effect Count Updater
}



//-----------------------------------------------


PaulS

Enough of the cross-posting. Talk about hardware issues here, and software issues in your other thread in the software section.

Newman180

Quote
Enough of the cross-posting. Talk about hardware issues here, and software issues in your other thread in the software section.

I'm double posting because it adds a wider auidence and another set of eyes and suggestions. Also, there have been people in this post who have helped me with the code as well as the hardware.

MI_Troll

Not sure how far along you are at this point, but I figured I throw a wrench into the works.

My background is internal combustion, I've never given much thought on compressed air, so here's my assumptions about your project:

1) 4 cylinder engine
2) 1 intake solenoid, 1 exhaust solenoid per cylinder
3) Piston going down, intake is open, exhaust is closed
4) Piston going up, intake is closed, exhaust is open
5) 2 cylinders firing simultaneously

With regard to crank position sensing and rpm determination, most automotive engines have a crankshaft position sensor with 36 teeth - 1 missing tooth, typically located 10 degrees BTDC cyl 1.  Some use more teeth, I don't know of any that use less. In a typical 4 stroke engine this gives you position, but doesn't give you stroke.  In your application this is irrelevant so we won't worry about that.  I would recommend you go this route.  

Using this method you would receive a pulse every 10 degrees.  By determining the time between pulses you know rpm.  When the missing tooth comes along the time between pulses will be twice as long, this is your indicator that the missing tooth went by and you next pulse is TDC.  You could also accomplish this with a 2nd sensor facing the front  of the 36 tooth wheel that sensed a feature you added at TDC.  This would simplify the code, but software is free and you don't have to deal with selling additional sensors, brackets, and wiring to management.

To run your engine the easiest thing to do would be to synthesize a signal off of the wheel that drives your injectors.  Since you're not worried about valve timing, spark advance etc... this could be as simple as TDC comes around, set a flag high, 18 pulses later, set it low.   Flag is high, intake solenoid is on,  exhaust off or the other way around depending on where you are in the firing order.  

This of course would give you 100% duty cycle all the time, so you would probably want to calculate the period and determine an injector on time.  This gives you more flexibility than the 10% increments you've got now.  You could replace your button with a potentiometer and dial up the duty cycle as desired.

There was some discussion regarding the need to predict wether the engine was accelerating or decelerating.  This shouldn't matter.  You should only be firing the solenoids based on engine position, if the injector is open and TDC /BDC comes around, that should trigger closing the injector regardless of what any other calculations may be saying.  


Go Up