Hey everyone. I'm fairly new to Arduino other than a Mechatronics course I took a semester ago. Currently I'm working on my undergraduate thesis - An Exhaust Backpressure system that will operate on Arduino controls. I've already sourced a Closed-Loop Stepper Motor Kit (complete with a driver and power supply) from StepperOnline, as well as two Pressure Transducers from DataQ. I've already finished machining a housing for them to fit in that puts them downstream of the exhaust, but I just need to get the electronics down now.
The idea is to create a pressure delta along stream using a butterfly valve actuated by a stepper motor. Each sensor is set up on opposite sides of the valve so I can precisely control the pressure. I've never used pressure sensors or stepper motors in Arduino before, so any help/recommendations would be valuable! I'm still reading through the forums as I go, but figured that I would reach out to see if anybody had tips for getting started. I'm not very familiar with power supplies and electronics, so this should be a challenging learning experience.
Looks like a good time. Out of curiosity, why did you choose a stepper instead of a servo for this? Servo seems like it would be a more natural fit for this project.
My other question is, in what situation is exhaust back-pressure desirable? I tried searching very briefly on Google and nothing came up. I have always been given to understand that we rarely want exhaust backpressure.
Actually there is ONE more piece you need to add to your drawing. That is a sensor of some type to identify when the butterfly valve is open. That is needed by your program so it can detect a starting position for the stepper motor, as well as positive feedback when your code moves the valve to the open position.
Remember, the whole operation takes time to function and your program runs very fast!
To be honest - I'd chock that all down to a lack of knowledge/experience lol. I agree that a Servo Motor would have been the all-around better choice for the given application. I think it started with Stepper Motors being cheaper (at the entry-level), but at this point it's just kind of rolling with what I got.
To answer your other question, I am working with an Engine Manufacturer where the goal is to be able to simulate backpressure that would otherwise result from Catalytic Converters, Mufflers, etc. that we don't typically provide, but would be valuable to our customers.
That's a good catch! You're completely correct. However I wonder, would an encoder help to determine the status of the valve? My Stepper Motor comes with a built-in encoder (which should be able to help me align it?) And using the Pressure Transducers, the valve should be self-correcting (to a degree), where it defaults to a position that doesn't create backpressure. (not sure yet how I'll end up implementing that one, though!)
How do you see an encoder telling your program about a specific fixed location? As far as pressure transducers, the engine will have to be running before any values will be available. Would it not be better to ensure an open exhaust system before starting an engine?
Great question. I think the best way I can think of implementing that would otherwise be to store the encoder values on the Arduino's memory so it remembers what angle it was at when it shuts off. Not that I've figured out how to do that or if it's even possible, but that's how I'd think about it intuitively.
As for the exhaust system, since it is designed to act as a stand-in for a muffler, there actually should be some amount of resistance/restriction during start-up, but I don't think there's as much of a concern of it blocking flow too much since I've intentionally undersized the valve for the tubing due to temperature expansion concerns (which guarantees a gap for gases to bypass the valve even while fully "shut"), but I'll check with my advisors to see if the startup backpressure is a big deal or not, since that could be a weird transition to take into consideration for the pressure transducers (particularly the "pressure-delta").
There are also encoder ICs that will allow you to burn in a zero position. For example the as5600. Search the datasheet for the Burn_Angle command to see how that works.
Everything works as intended! (At least during isolated testing) My next big hurdle is figuring out how to get the system to determine what position the butterfly valve is in. I'd like to avoid using another sensor if possible, but if I have to, I'd prefer something that's simple and easy to integrate into Arduino controls. I haven't had much luck with the onboard encoder, so I've just been using software in order to limit the rotation of the motor, but it wipes every time the system resets, so I'd need to implement either another sensor or find a way to "memorize" the zero/max position(s). Any thoughts? I'd love to hear what you guys think!
// USER DEFINED
float spacing = 0.5; // time spacing (for display) in seconds (minimum value of 0.1), sensors will scan 100x faster than the diplay will show
float target_P = 5; // pressure target (manual setting) in PSI
float margin = 0.2; // acceptable range of variance for the pressure target in PSI
// SENSOR SETTINGS
int sensor1 = A0; // set analog pin for sensor 1
int sensor2 = A1; // set analog pin for sensor 2
// MOTOR SETTINGS
int steps; // step counter for motor loop
int PUL = 9; // PUL will be set to Pin #8
int DIR = 8; // DIR will be set to Pin #9
// ADVANCED SENSOR SETTINGS
float correction1 = 3328 - 3276; // baseline voltage factor to use for sensor calibration (3275.8 is the ideal reading of the sensor at atmospheric pressure)
float correction2 = 3226 - 3276; // correction factor for 2nd sensor
// PLACEHOLDERS
int counter; // counter value used for stepping loops
float ms; // millisecond conversion of spacing variable
float measurement_loop; // millis live timer for measurement loop
float display_loop; // millis live timer for display loop
float step_loop; // millis live timer for stepping loop
float measurement_interval; // spacing interval for measurement loop
float display_interval; // spacing interval for display loop
float step_interval; // spacing interval for stepping loop
int display_counter = 0; // counter value for display loop
unsigned long current_time = millis(); // intializes millis timer value that will control timing for the rest of the code
int val1 = 0; // counter value for sensor 1 data
int val2 = 0; // counter value for sensor 2 data
float analog1; // raw value from sensor 1
float analog2; // raw value from sensor 1
float voltage1; // voltage conversion from sensor 1
float voltage2; // voltage conversion from sensor 2
float current1; // current conversion from sensor 1
float current2; // current conversion from sensor 2
float pressure1; // pressure conversion from sensor 1
float pressure2; // pressure conversion from sensor 2
float delta_P; // difference in pressure measured between two sensors
float delta_display; // pressure delta displayed in loop
int err_1_2 = 0; // storage variable for combined sensor error
int err_1 = 0; // storage variable for sensor #1 error
int err_2 = 0; // storage variable for sensor #2 error
float position = 0; // live position value - would ideally have an onboard storage for this or use the encoder to update/track
float min_pos = 0; // minimum allowed position
float max_pos = 90; // maximum allowed position
float limiter_timeout; // timer preventing limiter from overcommunicating
void setup() {
// VALUE CHECKS
if(spacing < 0.1){ // guarantees that spacing will not be under 0.1
spacing = 0.1;
}
if(target_P > 30){ // guarantees that target pressure cannot be set greater than the bounds of the sensor
target_P = 30;
}
else if(target_P < -30){ // guarantees that target pressure cannot be set greater than the bounds of the sensor
target_P = -30;
}
// CONVERSIONS
ms = spacing * 1000; // converts run time-spacing into milliseconds
display_interval = ms; // sets display intervals based on millis and user-defined timing variables
measurement_interval = ms / 100; // sets measurement intervals based on millis and display intervals(100x/display)
step_interval = measurement_interval; // sets display intervals based on user-defined timing variables
display_loop = display_interval; // starts display loop AFTER the first cycle to guarantee that there will be reference data
measurement_loop = 0; // starts measurement loop at zero to guarantee first-cycle priority for data collection
step_loop = step_interval; // starts stepping loop AFTER the first cycle to guarantee that there will be reference data
// SENSOR SETUP
analogReadResolution(14); // change to 14-bit resolution
pinMode(sensor1, INPUT); // sets sensor 1 pin to input mode
pinMode(sensor2, INPUT); // sets sensor 2 pin to input mode
Serial.begin(9600); // starts serial monitor reading
// MOTOR SETUP
pinMode(PUL, OUTPUT); // set PUL pin
pinMode(DIR, OUTPUT); // set DIR pin
}
void loop() {
current_time = millis(); // updates current time using millis
// MEASUREMENT LOOP
if(current_time >= measurement_loop) { // enables measurement loop whenever the timer is due relative to the current millis clock
// SENSOR RESET
val1 = analogRead(sensor1); // raw 14-bit reading on sensor 1
val2 = analogRead(sensor2); // raw 14-bit reading on sensor 2
analog1 = val1 - correction1; // applies correction factor to raw value (sensor 1)
analog2 = val2 - correction2; // applies correction factor to raw value (sensor 2)
voltage1 = (analog1 / 16383.0) * 5.0; // converts 14-bit ADC signal into voltage (sensor 1)
voltage2 = (analog2 / 16383.0) * 5.0; // converts 14-bit ADC signal into voltage (sensor 2)
current1 = (voltage1 / 250.00) * 1000.00; // uses known resistance and ADC Voltage to show current (sensor 1)
current2 = (voltage2 / 250.00) * 1000.00; // uses known resistance and ADC Voltage to show current (sensor 2)
// CALCULATION UNIT CONVERSIONS
pressure1 = (voltage1 - 1.00) / 5.00 * 30.00; // converts signal into pressure in PSI (sensor 1)
pressure2 = (voltage2 - 1.00) / 5.00 * 30.00; // converts signal into pressure in PSI (sensor 2)
delta_P = pressure1 - pressure2; // pressure delta between initial and final pressure
delta_display += delta_P; // adds to total delta (used to calculate avg delta across 100 measurement points)
display_counter += 1; // adds to counter (used to calculate avg delta across 100 measurement points)
measurement_loop += measurement_interval; // stages next measurement loop iteration through millis
// ERROR STATEMENT --> REPLACE WITH AN EXTRA BONUS SET OF "ERROR STORAGE VARIABLES IN DISPLAY LOOP"
if (analog1 <= 0 || analog2 <= 0) {
if(analog1 <= 0 && analog2 <= 0) { // use case where both sensors are showing impossible / absent values
err_1_2 += 1;
}
else if(analog1 <= 0) { // use case where sensor #1 is showing an impossible / absent value
err_1 += 1;
}
else if(analog2 <= 0) { // use case where sensor #2 is showing an impossible / absent value
err_2 += 1;
}
analog1 = 0; // resets analog values before relieving the error statement
analog2 = 0;
}
}
// DISPLAY LOOP
if(current_time >= display_loop) { // enables display loop whenever the timer is due relative to the current millis clock
// TO BE USED FOR DEBUGGING
/*
Serial.println(analog1); // displays 1st analog signal
Serial.print(voltage1); // displays 1st voltage value
Serial.println(" V");
Serial.print(current1); // displays 1st current value
Serial.println(" mA");
Serial.print(pressure1); // displays 1st pressure value
Serial.println(" psi");
Serial.println(analog2); // displays 2nd analog signal
Serial.print(voltage2); // displays 2nd voltage value
Serial.println(" V");
Serial.print(current2); // displays 2nd current value
Serial.println(" mA");
Serial.print(pressure2); // displays 2nd pressure value
Serial.println(" psi");
*/
// MAIN CODE (will not display if an error is detected)
if(err_1_2 == 0 && err_1 == 0 && err_2 == 0){
delta_display = delta_display / display_counter; // calculates avg pressure across 100 measurement points
Serial.print("Pressure Delta: ");
Serial.print(delta_display); // displays difference between two pressures (+/- will matter in the final setup)
Serial.println(" psi");
delta_display = 0; // resets display total value
display_counter = 0; // resets diplay counter value
display_loop += display_interval; // stages next display loop iteration through millis
}
// ERROR MESSAGE STATEMENTS
else{
Serial.print("ERROR: ");
if(err_1_2 > 0 || (err_1 > 0 && err_2 > 0)) { // use case where both sensors are showing impossible / absent values
Serial.println("Both sensors are failing to read.");
err_1_2 = 0; // resets error storage variables
err_1 = 0;
err_2 = 0;
}
else if(err_1 > 0) { // use case where sensor #1 is showing an impossible / absent value
Serial.println("Sensor #1 is failing to read.");
err_1 = 0; // resets error storage variable
}
else if(err_2 > 0) { // use case where sensor #2 is showing an impossible / absent value
Serial.println("Sensor #1 is failing to read.");
err_2 = 0; // resets error storage variable
}
else { // use case for oddball unexpected errors
Serial.println("An unknown error has occurred.");
err_1_2 = 0; // resets error storage variables
err_1 = 0;
err_2 = 0;
}
}
Serial.println();
}
// MOTOR LOOP
if(current_time >= step_loop && val1 > 0 && val2 > 0) { // enables stepping loop whenever the timer is due relative to the current millis clock AND a valid sensor reading
if(delta_P > target_P + margin) { // completes a single step when above pressure target
if(position < max_pos) {
digitalWrite(DIR, HIGH); // sets positive direction
digitalWrite(PUL, HIGH); // pulses on
delayMicroseconds(500);
digitalWrite(PUL,LOW); // pulses off
delayMicroseconds(500);
position += 1.8;
}
else if(current_time >= limiter_timeout) {
Serial.println("VALVE FULLY CLOSED");
limiter_timeout += display_interval; // MILLIS TIME LIMIT
}
}
else if(delta_P < target_P - margin) { // completes a single step when below pressure target
if(position > min_pos) {
digitalWrite(DIR, LOW); // sets negative direction
digitalWrite(PUL, HIGH); // pulses on
delayMicroseconds(500);
digitalWrite(PUL,LOW); // pulses off
delayMicroseconds(500);
position -= 1.8;
}
else if(current_time >= limiter_timeout) {
Serial.println("VALVE FULLY OPENED");
limiter_timeout += display_interval; // MILLIS TIME LIMIT
}
}
step_loop += step_interval; // stages next stepping loop iteration through millis
}
}
The usual solution is to attach a potentiometer to the opposite end of the valve shaft and monitor the position with and A/D converter attached to the pot slider. The pot is connected as a voltage divider.
I really like that idea, it's reliable yet simple - and definitely Arduino-compatible/friendly. But I'm not sure if a potentiometer would survive on the tip of the shaft due to the high temperature conditions. Is there some form of potentiometer that would fit (like a ring) around the shaft on the other side? (Like the gearbox/stepper-motor side) Or I suppose some kind of sensor that works from a distance (let's say a couple inches to half-a-foot away for example) could work as well. Do you think it's possible to reliably store a variable so that I can make my Arduino "remember" what position the valve system was in last?
Good grief! Use a coupling with ceramic to insulate the metal. Just the other day I saw a flexible coupling with a ceramic disk insulating one side from the other. That was in the junk out in my shop. Or machine a coupling with built-in heat dissipating fins. Or actually measure the heat transfer. Or use a high temperature wire-wound potentiometer.
I figured as much. I was hoping to avoid this, but it sounds like my efforts to reduce complexity are only overcomplicating the code. I suppose I'll have to look into one of these string/wire-wound potentiometers then.
Unfortunately, most of the options that can actually withstand target temperatures are well out of the scope of my project's budget. Exhaust temperatures for this valve system are expected to be as high as 900°C, but we've observed measurements as high as 1100°C. I'll look into the wire-wound/string-type potentiometers, since that seems to potentially be an affordable solution, but the long-story short is that we need to keep the electronics as far away from the exhaust as possible. I will run heat transfer calculations eventually, but those will be more geared towards my final analysis. The project timeline is too tight for me to change anything based on radiant heating, so I'm just using as much insulation as possible and hoping everything works out.
If I was designing this from scratch, and with the advantage of hindsight, I might have a gear at the motor end of the shaft, and mount an absolute rotary encoder and stepper either side with 1:1 gear. Or maybe a timing belt.
Hindsight 20/20, I agree that would be a more effective design method, but I've already got everything manufactured to the specifications shown in the thread. My thinking is that I can probably mount the potentiometer on the output shaft as close to the gearbox as possible, which will allow me to keep the potentiometer (relatively) cool, and also give me a sturdy mounting for it.