The diagram, Arduino on left Rotork valve operator right, blue outline of turbo-gen extreme right. |
/*
Generator RPM control Robert J Honders Sr 7/9/2013
Changed: 8/30/13 to control the 120VAC motor on ROTORK valve actuator.
Tuned: 2/7/2014, onsite w Gen1 on grid
and simulated unloading by switching off C1 then Gen1.
Loads continuously powered by grid. Still problem osc when no load.
2/12/2014 tweaked program, added Inverse of Deviation element to help stay put within range.
3/23/2014 Added intermediate Proportional error bands.
Rev6 3/24/2014 Reversed valve adjustment order to close first.
Rev 6.5 added DeltaRPM to correct rapid changes. Abandoned that idea.
Rev 6.6 Added more speed error bands, now 7.
WAY OVER, OVER, NEAR+, WITHIN SPAN, NEAR-, UNDER, WAY UNDER.
NOTE: A lot of testing stuff is commented out of the program. By removing
the '//' you can get stuff to print out on screen. Input to pin 2
can be simulated with 'hall effect' type pickup (TLE4906) and a tiny magnet
on the VARIAC controlled test motor shaft. Here is a simple Arduino pulse generator
routine to give a 1ms negative pulse every 32ms (variable with a pot) for off site testing.
I needed to have a variable source of negative going pulses, potentiometer adjustable, around 33 ms apart,
to simulate what comes from my Micro Hydro generator hall pickup. Just so I would not have to be in the
noisy powerhouse to develop the PID valve controller to operate the motorized valves.(Another project.)
Control the frequency of pulse at pin 13 with 10K potentiometer, center to pin A0, one end to +5, other to GND.
Not possble to use - delayMicroseconds() - if the pulses need to be shorter and faster because DelayMicroseconds
is only good up to 16383. I got around that by dividing the time in four. Lots of details to help you
and me understand it. Robert Honders Sr 3/28/2014
//START OF TEST PULSE GENERATOR.
// constants won't change. Used here to set pin numbers.
const int pulsePin = 13; // sets which output pin to pulse.
//Declare some variables.
int OldPotValue;//declared here it is good throughout and only zeroed here.
int PotValue; //declared here it is good throughout and only zeroed here.
int error; //Really the change in PotValue as we turn the shaft.
int minPotDelta=100;// This is the minimum change sensed in 100000, turning the pot.
// this is to eliminate jitter in sucessive A/D conversion readings
void setup() // the setup routine runs once when reset is pressed.
{
Serial.begin(9600);
Serial.println(" pot variable generator RPM pulse simulator");
pinMode (pulsePin, OUTPUT);// set digital pin to OUTPUT, they default to inputs.
}
void loop() // the loop routine runs over and over again forever
{
//Serial.println (OldPotValue); //milliseconds between pulses NOT?
// delay (1000);
PotValue = analogRead(A0);// read the pot input on analog pin A0:
PotValue = map(PotValue,0,1000, 0,100000);//the second 2 can be swapped to get the pot to give higher RPM on clockwise.
PotValue = PotValue/4;// divided in four to get around delayMicroseconds limitation, the latest pot value.
error = OldPotValue - PotValue; // is it different than the last pot value by an amount called error
if (abs(error) <= minPotDelta) //error is change in pot setting if small change use OldPotValue, eliminates small A/D conv errors.
{
PotValue = OldPotValue;// if the pot value changed by less than the minPotDelta than we will use the OldPotValue.
// if the pot is not moved we'll be up here.
digitalWrite(pulsePin, LOW);
delayMicroseconds(500);//fixed pulse width of half a millisec.
digitalWrite(pulsePin, HIGH);
delayMicroseconds(PotValue);
delayMicroseconds(PotValue);// four times to make up for /4
delayMicroseconds(PotValue);
delayMicroseconds(PotValue);
//Serial.println ("no change ") ;
//Serial.println (PotValue);
//Serial.println (error);
}
if (abs(error) >= minPotDelta)
{
// if the pot IS adjusted we'll be down here.
digitalWrite(pulsePin, LOW);
delayMicroseconds(500);//fixed pulse width of half a millisec.
digitalWrite(pulsePin, HIGH);
delayMicroseconds(PotValue);
delayMicroseconds(PotValue);// four times to make up for /4
delayMicroseconds(PotValue);
delayMicroseconds(PotValue);
Serial.println (" new PV ") ;
Serial.println (PotValue);
Serial.println (" Change in PotValue ") ;
Serial.println (error);
}
OldPotValue=PotValue;//store the current pot value to compare with the new.
}
//END OF TEST PULSE GENERATOR.
The sketch uses a timer to work out the interval
between two consecutive rising edges on pin D2. This time we use a "rising"
interrupt on D2 to notice the leading edge. We also set up a high-precision
timer (Timer 1) which is a 16-bit timer.
By using no prescaler, Timer 1 counts 1 for every clock cycle (say, every
62.5 nS at 16 MHz).
By multiplying the number of counts between the leading edges by 62.5, and
then taking the inverse, we can deduce the frequency.
The advantage of this method is that we get a very quick calculation.
For example, at 10 KHz the period is 1/10000, namely 100 µS, so we get our
result 100 µS later.
Note that due to the time taken to service the interrupts on the data's leading
edges, the maximum achievable frequency you can sample is around 100 KHz
(which would mean the ISR(Interrupt Service Routine) is taking around 10 µS).
Frequency timer
Author: Nick Gammon
Date: 10th February 2012
Adapted and annotated for Generator RPM control Robert J Honders Sr 7/9/2013 - 9/2/13
A variable should be declared volatile whenever its value
can be changed by something beyond the control of the code section in which it appears,
such as a concurrently executing thread. In the Arduino, the only place that this is
likely to occur is in sections of code associated with interrupts, called an interrupt service routine.
*/
volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;
// here on rising edge on pin 2, is interrupt 0 always on pin 2 ??
void isr () //Start of Interrupt Service Routine
{
//digitalWrite(2, HIGH);//sets pin 2 hi eliminates 10 k pullup, can be left in crkt.
unsigned int counter = TCNT1; // quickly save counter 1 count in counter.
if (triggered)// wait until we notice last rising edge
return;
if (first)
{
startTime = (overflowCount << 16) + counter;
first = false;
return;
}
finishTime = (overflowCount << 16) + counter;
triggered = true;
detachInterrupt(0);
} // end of interrupt service routine
// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect)
{
overflowCount++;//increment overflow count by 1
} // end of TIMER1_OVF_vect
void prepareForInterrupts ()
{
// get ready for next time
EIFR = _BV (INTF0); // clear flag for interrupt 0
first = true;
triggered = false; // re-arm for next time
attachInterrupt(0, isr, RISING); //look for next rising edge and run ISR
} // end of prepareForInterrupts
void setup ()
{
Serial.begin(9600);
//Serial.println("Tachometer");
// reset Timer 1
TCCR1A = 0;
TCCR1B = 0;
// Timer 1 - interrupt on overflow
TIMSK1 = _BV (TOIE1); // enable Timer1 Interrupt
// zero it
TCNT1 = 0;
// start Timer 1
TCCR1B = _BV (CS20); // no prescaling
prepareForInterrupts (); // set up for interrupts to get new RPM data.
} // end of setup
int previousMillis = 0; // will store last TIME RPM was updated.
int previousRPM =0; // will store last RPM read.
void loop () // the main loop will have to be modified for PID.
{
if (!triggered)// if NOT triggered
return;//wait here until it is.
unsigned long elapsedTime = finishTime - startTime;
int RPM = 60.0 / ((float (elapsedTime) * 62.5e-9)); // each tick is 62.5 nS
/* 1800 RPM = .333s per rev = frequency of 30 per second X 60 s/min = 1800 RPM
So 1800 RPM is 33.33ms / .0000625ms = 533,333 ticks or counts of the 16 Mhz Arduino clock in one revolution!
That hapens between pulses from magnetic "Hall" motor pickup, so we get very high resolution RPM,
any small change will be detectable, probably overkill.
*/
Serial.println () ;
Serial.print (RPM);
Serial.println (" RPM ");
//Figure Delta RPM here and work it into the if over/under corrections.
int currentRPM = RPM; //store the RPM now
int DeltaRPM = currentRPM - previousRPM; //the change in RPM readings.
previousRPM = currentRPM; // save the last time you got the RPMs
/*
The following attempt at PID control not successful so far.
// figure Delta T here
int currentMillis = millis(); //store the count now
int DeltaT = (currentMillis - previousMillis)/60000; //the time between RPM readings in minutes.
previousMillis = currentMillis; // save the last time you got the RPMs
Serial.print (DeltaT);
Serial.println (" Delta T ");
int Deriv=DeltaRPM/DeltaT; // Drpm/Dt 1st derivative variable Delta T
Serial.print (Deriv);
Serial.println (" Derivative ");
*/
//this section will run the valve motor for a duration proportional to the deviation or error
// to close valve if RPM is HI, or to open valve if RPM is LO. Also DeltaRPM will reduce the effective RunTime.
//Added setup code, Do these go here as part of setup??
//Yes, did not work in "void setup()" above, local variable scope? do we need a second Void Setup.
//******************SET*UP***********************
int CenterRPM=1820;// 1820, adjust center RPM to open valve with minimum pulse width (green blink)in grid mode.
int Span = 20;// 10, sets max + AND - deviation from CenterRPM, (+ plus - ), sets system accuracy.
int WindDownTime = 1000; // 100-2000, time for motor to stop, ms, before next pulse, if within range it is multiplied by InvDevMag.
int RunTime = 6;//2-10, motor run time to move valve, ms, automatically reduced if deviation is small, increased if deviation is big.
//**************END*SET*UP************************
float Deviation;// Error signal signed, + is fast
int DevMag; //absolute value of Deviation, magnitude only, unsigned.
float InvDevMag;//the inverse of deviation magnitude.
int openPin = 8; // sets which output pin to open valve, these 2 pins must not both be high.
int closePin = 7; //sets what pin to set high to close valve.
pinMode (openPin, OUTPUT);// set digital pins to OUTPUT, they default to inputs.
pinMode (closePin, OUTPUT);//analog pins do not have to be declared inputs or outputs.
//end of added setup code
//DeltaRPM = constrain (DeltaRPM,-RunTime+25,RunTime-25);
//Serial.print (DeltaRPM);
//Serial.println (" Delta RPM ");
Deviation = RPM-CenterRPM;// positive is running FAST
//if(Deviation < 0)//running slow, ERROR signal.
if (Deviation == 0) (Deviation = 1);// so we don't end up dividing by 0 in the next line.
InvDevMag =((Span)/Deviation);//less deviation bigger number, stay put longer time when within Span.
InvDevMag =(abs(InvDevMag)); //no negative time allowed, Inverse Deviation Magnetude, small deviation stay put longer.
//InvDevMag = map(InvDevMag,0,Span, 0,(3*Span));
//Serial.print ("Deviation ");
//Serial.println (Deviation);
//Serial.print ("inverse of deviation ");
//Serial.println (InvDevMag);
DevMag = (abs(Deviation));//just the magnitude to set the output motor RunTime time proportionally.
//Serial.print ("DevMag ");
//Serial.println (DevMag);
//DevMag = map(DevMag,0,Span, 1,(Deviation)*Span); // re maps 0 to Span >> 1 to abs Deviation*Span eliminates 0 from DevMag.
//DevMag = abs(DevMag);//no negative values
//Serial.print ("DevMag ");
//Serial.println (DevMag);
DevMag = constrain (DevMag,1,2000); //keeps deviations from blowing up when motor stopped.
//Serial.print ("Constrained DevMag ");
//Serial.println (DevMag);
// These error bands progressively increase duration of RunTime and decrease duration of
// WindDownTime in proportion to the Deviation Magnitude or error, modulating the AC motor speed and direction.
// WAY OVER, OVER, NEAR+, WITHIN SPAN, NEAR-, UNDER, WAY UNDER.
if (Deviation > 2*Span) // RPM Deviation way too high, close valve longer.
{
Serial.println ("WAY OVER");
digitalWrite(openPin,LOW);//GREEN LED off, allow CLOSE VALVE
//delay(WindDownTime);//so we allow the motor to spin to a stop between pulses.
digitalWrite(closePin,HIGH);//RED LED on to CLOSE VALVE.
delay (RunTime*DevMag/4);// keep motor running CLOSING valve longer if Dev Magnitude is big.
digitalWrite(closePin,0);//reset RED closePin (5) to stop motor.
digitalWrite(openPin,0);//reset GREEN openPin (6) to stop motor.
delay(2*WindDownTime*InvDevMag);//so we allow the motor to spin to a stop between pulses for shorter time if deviation is big.
}
else if (Deviation > (Span) && (Deviation <= 2*Span)) // positive deviations, RPM a little too high, CLOSE valve short time.
{
Serial.println ("OVER");
digitalWrite(openPin,LOW);//GREEN LED off, allow CLOSE VALVE
//delay(WindDownTime);//so we allow the motor to spin to a stop between pulses.
digitalWrite(closePin,HIGH);//RED LED on to CLOSE VALVE.
delay ((RunTime)*(DevMag/5));// keep motor running CLOSING valve longer if Dev Magnitude is big.
digitalWrite(closePin,0);//reset RED closePin (5) to stop motor.
digitalWrite(openPin,0);//reset GREEN openPin (6) to stop motor.
delay(4*WindDownTime*InvDevMag);//so we allow the motor to spin to a stop between pulses for short time if deviation is big.
}
else if (Deviation > Span/2 && Deviation <= Span) // positive deviations, RPM a little too high, CLOSE valve short time.
{
Serial.println ("NEAR+");
digitalWrite(openPin,LOW);//GREEN LED off, allow CLOSE VALVE
//delay(WindDownTime);//so we allow the motor to spin to a stop between pulses.
digitalWrite(closePin,HIGH);//RED LED on to CLOSE VALVE.
delay (RunTime*(DevMag/2));// keep motor running CLOSING valve longer if Dev Magnitude is big.
digitalWrite(closePin,0);//reset RED closePin (5) to stop motor.
digitalWrite(openPin,0);//reset GREEN openPin (6) to stop motor.
delay(2*WindDownTime*InvDevMag);//so we allow the motor to spin to a stop between pulses for short time if deviation is big.
}
else if (Deviation >= (Span/-2) && (Deviation <= Span/2)) //within span, just keep the Valve motor stopped longer.
{
Serial.println ("Within Span");
digitalWrite(closePin,LOW);//reset RED closePin to keep motor stopped.
digitalWrite(openPin,LOW);//reset GREEN openPin to keep motor stopped.
delay((InvDevMag/4)*WindDownTime);//Speed is within span so wait a while proportional to how close to center RPM
//so we allow the motor to spin to a stop between adjustments.
}
else if (Deviation < (Span/-2) && (Deviation >= -1*Span)) // small negative deviations, RPM a little too low, OPEN valve short time.
{
Serial.println ("NEAR-");
digitalWrite(closePin,LOW);//RED LED off, allow OPEN VALVE
//delay(WindDownTime);//put some time between OPEN and last CLOSE commands so we don't do both.
digitalWrite(openPin,HIGH);//GREEN LED on OPEN VALVE.
delay ((RunTime*DevMag/2));// keep motor running OPENING valve longer if Deviation Magnitude is big.
digitalWrite(closePin,LOW);//reset RED closePin to stop motor.
digitalWrite(openPin,LOW);//reset GREEN openPin to stop motor.
delay(2*WindDownTime*InvDevMag);//so we allow the motor rest a shorter time if dev is big, between pulses.
}
else if (Deviation < (-1*Span) && (Deviation >= -2*Span)) // small negative deviations, RPM a little too low, OPEN valve short time.
{
Serial.println ("UNDER");
digitalWrite(closePin,LOW);//RED LED off, allow OPEN VALVE
//delay(WindDownTime);//put some time between OPEN and last CLOSE commands so we don't do both.
digitalWrite(openPin,HIGH);//GREEN LED on OPEN VALVE.
delay ((RunTime)*(DevMag/5));// keep motor running OPENING valve longer if Deviation Magnitude is big.
digitalWrite(closePin,LOW);//reset RED closePin to stop motor.
digitalWrite(openPin,LOW);//reset GREEN openPin to stop motor.
delay(4*WindDownTime*InvDevMag);//so we allow the motor rest a shorter time if dev is big, between pulses.
}
else // big negative deviations, RPM way too low, open valve longer time.
{
Serial.println ("WAY UNDER");
digitalWrite(closePin,LOW);//RED LED off, allow OPEN VALVE
//delay(WindDownTime);//put some time between OPEN and last CLOSE commands so we don't do both.
digitalWrite(openPin,HIGH);//GREEN LED on OPEN VALVE.
delay (RunTime*DevMag/4);// keep motor running OPENING valve longer if Deviation Magnitude is big.
digitalWrite(closePin,LOW);//reset RED closePin to stop motor.
digitalWrite(openPin,LOW);//reset GREEN openPin to stop motor.
delay(2*WindDownTime*InvDevMag);//so we allow the motor rest a shorter time if dev is big, between pulses.
}
prepareForInterrupts (); // get a new reading of RPM
} // end of loop