Saturday, January 29, 2011

Arduino and bidirectional AC valve motor controller for Micro Hydro

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

5 comments:

gknowles@sunfieldsolar.com said...

Hi there Rob I am the one whom contacted you on facebook last night thought I would leave a post here to see if this will help with the communication gap you had spoken of last night.
The turbine I am trying to work on was up and running until about 6 years ago when a solar expert installed a pv panel on the off grid building since then the turbine power was lost and left to basically rot. I know this turbine has the capability to far surpass the pv panels installed I just need someone to coach me as I try to help my family get her running again so let me know if you feel like helping me.

Anonymous said...

Great section of content material I have to admit. Well developed and extremely valuable, many thanks!

Anonymous said...

Thanks for a fantastic submit to help me on the way, We are grateful for all your strive in researching and also penning this blog site

Anonymous said...

Hi rob, I have really enjoyed looking through your postings. I have a couple of questions though. Does it hurt to get a larger motor (50hp) and use less of the potential Kw (20 Kw usage) would that help with heat build up in the windings? What does it do for efficiency?
Second is have you considered using a verticle turbine (stacked turbines) pump? Would that work as well as or better than a single turbine? Thanks for your thoughts. I can be reached at dan.munger@usu.edu

Honders said...

Using a somewhat larger motor / generator is a good idea especially if you are using a 3 phase motor and converting to single phase with a C-2C capacitor bank. Maybe 33% to 50% larger is good, after that you'll reach the point of diminishing returns, where the bearing and air friction will exceed your gains.
I have considered 2 stage centrifugals but I have not tried any. I have a gut feeling that the increased whetted surfaces and higher velocities would lead to decreased efficiencies.