Go Down

Topic: precision timings within loop (Read 666 times) previous topic - next topic

Hi All
I am using an Uno to control 3 solenoid valves and an external trigger. These valves and trigger are being used to create drips of water and trigger a camera to photograph the collisions. The approach I have taken is to set up all the parameters in structures representing each valve and 1 for the trigger.
When it is time to open a valve I open it and set a variable in the appropriate structure dictating when to close it again. I then go off and do other things, checking the time during each iteration of the loop. The timings are short, typically between say 10 and 100ms. when millis() <= the stored off time, then the associated pin for the valve is set low. Similar principle for the trigger. It is conceivable that I may want to turn on 4 valves for say 30ms, 20ms, 80ms & 10ms all within a time frame of 70ms, then turn them off after there allotted open time. Somewhere in there I need to set the trigger pin high too.
This approach seems to work well enough, until the 3rd and 4th valves are introduced, when what was a nice small drip from usually only 1 of the valves seems to turn into a bit of a stream lasting seemingly 3 or 4 times longer than it should. Oddly, this usually effects only the first valve although others may be effected to a lesser extent. My software includes a mechanism for reporting how long the Arduino thinks it has had the valve open for and these figures are pretty much spot on. At least within 3ms or so.
I have a fair amount of programming experience and I cant see why this shouldn't work all the time. However I don't have much experience in programming Arduinos though and Im wondering if Im just asking too much of the chip RTC.
I have banged my head against this for a few months now and represents a fair amount of my time. Before I give up (not really in my nature) Id like to run my problems past you guys in case I just being dim!!
This is the important bit of the code. Sketch files attached if anyone would like to look :)

I appreciate this is a BIG ask, but I'd be eternally (if not longer!) for your thoughts / suggestions.

Many Many thanks for your time and help
Regards
Philip

Code: [Select]

void loop()
{
 long tmp = 0;
 boolean AllDripsPerformed = false  ;
 boolean Done = true;
 int i,j;
 ReadSerialPort();
 if (MsgComplete == true)
 {
   ActionMessage();
   MsgComplete = false;
 }
 // check for Open Valve or Do Drips Cmd
 // Only run if system is not running
 if(SysRunning == 0)
 {
       if(ValveArray[0]->Drip_active == true){
         digitalWrite(ValveArray[0]->Arduino_Pin, HIGH);
         delay(DripArray[0]->drip_form_time);
         digitalWrite(ValveArray[0]->Arduino_Pin, LOW);
         Serial.print(F("Doing Drips on Valve 1\r\n "));
         delay(1000);
       }
       if(ValveArray[1]->Drip_active == true){
         digitalWrite(ValveArray[1]->Arduino_Pin, HIGH);
         delay(DripArray[1]->drip_form_time);
         digitalWrite(ValveArray[1]->Arduino_Pin, LOW);
         Serial.print(F("Doing Drips on Valve 2\r\n "));
         delay(1000);
       }
       if(ValveArray[2]->Drip_active == true){
         digitalWrite(ValveArray[2]->Arduino_Pin, HIGH);
         delay(DripArray[2]->drip_form_time);
         digitalWrite(ValveArray[2]->Arduino_Pin, LOW);
         Serial.print(F("Doing Drips on Valve 3\r\n "));
         delay(1000);
       }
 }//if

// check for Open Valev or Do Drips Cmd
// Only run if system is not running
if(SysRunning == 0)
   {
    if(ValveArray[0]->Open_active == true){
        digitalWrite(ValveArray[0]->Arduino_Pin, HIGH);
     }//if

     if(ValveArray[1]->Open_active == true){
        digitalWrite(ValveArray[1]->Arduino_Pin, HIGH);
     }//if

     if(ValveArray[2]->Open_active == true){
       digitalWrite(ValveArray[2]->Arduino_Pin, HIGH);
     }//if

     for(int t = 0; t<=2; t++)
     {
        if(ValveArray[t]->Was_Open_Active == true){
         ValveArray[t]->Open_active = false;
         ValveArray[t]->Was_Open_Active = false;
         digitalWrite(ValveArray[t]->Arduino_Pin, LOW);
         }//if
     }//for
   }//if SysRunning == 0

   if(Test_Trigger == true){
     Serial.print(F("Testing Trigger on Pin "));Serial.print(Trigger.Ard_Pin);Serial.print(F("\r\n"));
     digitalWrite(Trigger.Ard_Pin, LOW);
     delay(Trigger.Trig_Pin_High_time);
     digitalWrite(Trigger.Ard_Pin, HIGH);
     Test_Trigger = false;
   }//if

if(SysRunning == 1 && FirstDripTTG == true)
 {
   // do first drip,
   DripArray[0]->ms_to_off = millis() + DripArray[0]->drip_form_time;
   StartDrip(DripArray[0],0,NULL);
   DripArray[0]->started = true;
   
   // work out when the other drips need to go relative to the StartDrip
   FirstDripFinish = millis();
   // drip delays cant be changed once the drip sequ has begun, so set the ms_to_go field now
   for(i=1; i<=3; i++){
      DripArray[i]->ms_to_go = (DripArray[i]->drip_delay + FirstDripFinish);
   }//for
   
// and the trigger time
   if(Trigger.active == true){
     Trigger.ms_to_go = Trigger.Trig_delay + FirstDripFinish;
   }//if
 
   while(AllDripsPerformed == false){  // Hang around in this loop till all drips and trigger that is active has been started AND stopped again for this sequence of drips
     
     tmp = millis();
//   check to see if other drips need stopping, including drip 0  ...
    for(j=0; j<=3; j++){
      if(DripArray[j]->active == true && DripArray[j]->started == true && DripArray[j]->performed == false){
        if(tmp >= DripArray[j]->ms_to_off){
          digitalWrite((DripArray[j]->use_valve)->Arduino_Pin,LOW);
          DripArray[j]->performed = true;
          Serial.print(F("===== DRIP "));Serial.print(j+1);Serial.print(F(" Stopped. Size Delay = "));Serial.print(tmp - (DripArray[j]->form_start_time));Serial.print(F("ms\r\n"));
        }//if
      }//if
    }//for

     if(Trigger.active == true && Trigger.Trig_Fired == false){
       if(tmp >= Trigger.ms_to_go){  
         digitalWrite(Trigger.Ard_Pin, HIGH);
         Trigger.delay_start_time = tmp;
         Trigger.Trig_Fired = true;
         Trigger.ms_to_off = tmp + Trigger.Trig_Pin_High_time;
         Serial.print(F("Trigger pin set HIGH after "));Serial.print(tmp - FirstDripFinish);Serial.print(F("ms\r\n"));
       }//if
     }//if

//   check to see if other drips need starting ...
    for(j=1; j<=3; j++){
      if(DripArray[j]->active == true && DripArray[j]->started == false){
        if(tmp >= DripArray[j]->ms_to_go){
          DripArray[j]->ms_to_off = tmp + DripArray[j]->drip_form_time;
          StartDrip(DripArray[j],j,NULL);
          DripArray[j]->started = true;
        }//if
      }//if
    }//for

//    test to see if the ext trigger should be fired yet. When Fired, re set it at the end so as not to mess up the valve timings with the delay needed to fire the trigger
     if(Trigger.active == true && Trigger.Trig_Fired == false){
       if(tmp >= Trigger.ms_to_go){  
         digitalWrite(Trigger.Ard_Pin, HIGH);
         Trigger.delay_start_time = tmp;
         Trigger.Trig_Fired = true;
         Trigger.ms_to_off = tmp + Trigger.Trig_Pin_High_time;
         Serial.print(F("Trigger pin set HIGH after "));Serial.print(tmp - FirstDripFinish);Serial.print(F("ms\r\n"));
       }//if
     }//if

     if(Trigger.active == true && Trigger.Trig_Fired == true && Trigger.Trig_performed == false){
       if(millis() >= Trigger.ms_to_off){  
         digitalWrite(Trigger.Ard_Pin, LOW);
         Trigger.Trig_performed = true;
         Serial.print(F("Trigger pin set LOW after "));Serial.print(Serial.print(tmp - Trigger.delay_start_time));Serial.print(F("ms\r\n"));
         }//if
       }//if

   // right, lets check to see if all active drips are completely finished, and the trigger off course!
    j=0;
    AllDripsPerformed = true;    
    while((AllDripsPerformed == true) && (j <=3)){ //  check to see if other drips need stopping, including drip 0  ...
      if((DripArray[j]->active == true) && (DripArray[j]->performed == false)){
          AllDripsPerformed = false;
        }//if
      j++;
      }//while
   
     if(Trigger.active == true && Trigger.Trig_performed == false){
       AllDripsPerformed = false;
     }//if
   }//while

   Serial.println(" ");

   }//if(SysRunning ==1 && FirstDripTTG == true)

   // by now, all drips that are active should have performed, so re-set ms_to_go and performed fields
  for(i=0; i<=3; i++){
      DripArray[i]->ms_to_go = 0;
      DripArray[i]->ms_to_off = 0;
      DripArray[i]->performed = false;
      DripArray[i]->started = false;
  }//for
 
   // re set trigger variables
   Trigger.ms_to_go = 0;
   Trigger.ms_to_off = 0;
   Trigger.Trig_Fired = false;
   Trigger.Trig_performed = false;

   // re set the sequence ...
   FirstDripTTG = false;
   AllDripsPerformed = false;
 
  if(millis() >= WaitUntil){
    Serial.flush();
    Serial.println("tick");
    FirstDripTTG = true;
    WaitUntil = millis() + Sequence_Delay;
  }//if
}//loop


Grumpy_Mike

I answerd such a request a few years ago, this is what I found while looking for that thread:-
http://arduino.cc/forum/index.php/topic,8524.0.html

Anyway what you should do it to take the approach used in the blink without delay example to do your timing, get rid of delays altogether they only screw you up.

Thanks Grumpy

That is exactly what Im trying to achieve! Great shots arnt they.
My code doesnt use delays, too much going on while code is suspended. I just calculate times stored in a valves structure and check those in the 'loop'. Judging from his description, the poster in the link is just using 1 valve. I have 3. The point is that with 3 I can initiate second and third drips more quickly as the hysteresis involved in properly closing 1 valve (to get a clean drip) means the turnaround time from drip 1 to 2 to 3 is too long.
Thanks for looking
Phil

Grumpy_Mike

It could be some hardware problems. Have you got diodes on the solonoides? You could improve things by getting schockely diodes. You could also add large capacitors on your supplies and try and isolate each solinoide's supply with an inductor. It could be that they are interfering slightly with each other. Also make sure the power supply is up to driving that ammount of current.

dc42

This is exactly the sort of software problem that is more easily solved using a cooperative scheduler or RTOS. You can find my cooperative scheduler at https://github.com/dc42/arduino, and there are lots of others around. Create a separate task for each drip, another one for the trigger, and others for any other I/O that you do.

The main limitations of this approach are:
- You must write each task such that it completes in well under the tick interval (1ms by default in my scheduler). So you mustn't calls a function in an Arduino libraries that may block or take a long time to execute.
- You can only schedule events that are multiples of the tick interval apart, unless you do tricky things with timer interrupts.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

#5
Mar 22, 2013, 07:57 pm Last Edit: Mar 22, 2013, 07:59 pm by SurfCracker Reason: 1
Grumpy,
Thanks for your reply, I do have flyback diodes on my solenoids, but not schockelys. It hadn't occurred to me that it could be a supply issue. Im afraid my electronics knowledge is scanty at best.
I will look into the problem. Can you suggest a way that I might confirm the issue? I have access to a oscilloscope and probably someone to help me use it ??

dc42
Thanks also for your suggestion. I had thought of using a coop scheduler, but didn't invest the time to work out how to implement one. I will defiantly take a look at yours with a view to a re-write.
In fact I'll do that next
How can I determine weather my code or library calls are taking too long please ?

Thanks to you both
Phil

Grumpy_Mike

Quote
Can you suggest a way that I might confirm the issue?

Replace the solenoids one at a time with LEDs. If it works fine with only one solenoid in any position then you can be sure it is a hardware problem.

Thanks Mike, That would to it !
Phil

Go Up