Disc1 Arduino Doser

I didn't think about posting that fix I found on here, thanks for sharing.

I've been using 1.0 now since then and I haven't run into any more issues yet.

I just recently added on a temperature probe that will submit the temp to a website my friend built for me. I felt pretty cool when I got that to work lol.

I think my next project should be to add an LCD but I don't really have a good spot to display it so I might hold off on that. When I get it all back assembled, I will try to remember to post some pics for all to see.

Thanks disc1 with all the help you've offered during this project, I don't think I would have gotten it done without you.
 
Hello
I assembled the doser and added two more pump and now I have a problem with the program.
Please if you can write a program with two pumps.

thank you

Bostjan
 
Hello
I assembled the doser and added two more pump and now I have a problem with the program.
Please if you can write a program with two pumps.

thank you

Bostjan

The code I have up is for two pumps. Do you mean for four?

Post what you've written and we can look at it and see if we can see where the problem is.

But no, I won't just write the program for you. My time is quite expensive. I will freely share anything I've written for myself, and you are free to use anything I've written here.
 
Thanks for the reply. I have already solved the problem. I can calibrate the pump when I set my schedule is not turned on additional pumps.

Well there is google translator.

Regards


Bostjan
 
Thanks for the reply. I have already solved the problem. I can calibrate the pump when I set my schedule is not turned on additional pumps.

Well there is google translator.

Regards


Bostjan


No problem. I was working on reorganizing this code so that it would be easier to add additional pumps but work has been really crazy for the last few months and I haven't had any time to put into it. I'll post what I have now in a bit with a disclaimer that it may not be stable.

To add more pumps to the version 6 code posted before, you would need to declare the pumps and the schedules and assign the pumps to the schedules. That part is easy if you look at the code as written.

The slightly tougher part is modifying the menus and calibration functions to recognize that there are more than just the original two schedules.

This particular project has become a hobby within a hobby and the current code has gone through 5 new iterations since the last code posted here. I've got something that is way different running on my tank right now, but there are still several things I want to fix up in that code. It still has a few bugs that don't bother me because I know where they are and what to expect, but might drive someone else crazy trying to figure out. But I'll post it up anyway just to let folks have a look see.
 
DISCLAIMER::::
I am only posting this code for a few who might be following along with the process. THIS CODE HAS KNOWN BUGS. Some real nasty ones that can possibly cause a pump to stick on or to not run. I'll try to note as many of them as I can here. The main bug is in the attempt to allow the user to disable the PWM control. As written the order of operations (Change PWM enabled, Then recalibrate, Then reset the schedule) is critical. If you enable PWM after the calibration you may not have a minimum flow rate set and the consequences are indeterminate.

However, this is what is currently running on my tank (with PWM disabled). It has been running for a few months now and hasn't let me down. I had another Arduino monitoring it and recording its dosing activity and everything was spot on timing wise. Still, the code is not complete. Work has been crazy and I haven't had time to fix this up.


The code has gone through a major rearrangement. All the same pieces are still there, but they work together differently now. This has finally evolved into a true finite state machine. It is now not only 1.0 compliant, but I am pretty sure that my prodigious use of the F() macro means that you need to have Arduino1.0 to run this.

The first major difference you'll notice is the loop function.
Code:
void loop()
{
  (*State_Functions[current_state])();  
  doColor();
}

Simple stuff huh? There are three different possibilities for the loop, menu state, dosing state, and free running state. Each one does different things, so I put them into different functions. That first line calls the appropriate function from an array of function pointers. Here are the other relevant code pieces.

From DOSE_head.h
Code:
typedef void (*S_Function)();
Code:
S_Function State_Functions[] = {
  &doRunState, &doMenuState, &doDoseState};
Code:
 #define NUM_STATES 6


enum state_vars {
  RUN_STATE, MENU_STATE, DOSE_STATE} 
current_state;

Then the three functions doDoseState, doRunState, and doMenuState are all defined in DOSE_head.h.

The second line of the loop is new. The LCD I am using has an RGB backlight. So I thought it would be neat to have it change colors for warnings and such. Right now it will change yellow or red based on how much supplement is remaining in the container.

The Menu has changed radically. I am trying to set up to integrate this with a larger controller idea and the old menu wasn't going to do that. This also eliminates that recursion in my old menu that could potentially cause problems down the road if the menu structure got any deeper. I've also moved all of the strings into PROGMEM. This has made the written code a lot longer and bulkier, but makes the compiled code fit much better on the Arduino.
Also many of the functions used by the menu have changed. I am setting up to allow for the seamless integration of more pumps or even other items on timers. I'm not quite there yet because the code gets caught in some of these functions if the user doesn't input. I need to bring them out into a state of their own and let the state machine handle them. But that's for way down the road. That's another one of those bugs I was talking about. While you are using menus, the rest of the program is frozen. So always make sure you exit out of the menus and don't try to access them while a pump is running. If a dose is due while you are in the menu, it will be made up as soon as you exit. If you leave it in there too long, it could ramp up some huge dose. I will add something to handle that, but I haven't got to that yet.

The last major change is the button. I liked the way the encoder worked so much, that I put the button on the other hardware interrupt. It handles its own debounce and automatically updates a flag that indicates a button push-and-release as well as a second flag that indicates the button is currently pressed down. A couple of simple macros allow the rest of the program to access the flags which are packed into an int type to be accessed by names. This will change soon as I am going to do a struct with a union to bit pack a bunch more flags into an unsigned long. That way I can implement the methods to access and twiddle the flags right there in the same struct they are held in. That way I will be able to pull that module out of this program and use it somewhere else if I want.

Here are the code bits I'm talking about. Notice that it still uses the QuickPin library, but it won't for long. This is the last place in the code that uses it, so I will switch this and the encoder to direct port manipulation and cut QuickPin out to save a little memory.

Code:
 void ISR_button_handler()
{
    if (button1 == LOW)  // FALLING button PRESSED
    {
      SETF(BUTTONMADE_FLAG);
      if (!BUTTON_PRESSED) press_detect_time = micros();      
    }
    else //  RISING button RELEASED
    {
      CLRF(BUTTONMADE_FLAG);
      if (press_detect_time && (micros() - press_detect_time > 50000))  // 50ms DEBOUNCE
      {
        SETF(BUTTONPRESS_FLAG);
      }
      press_detect_time = 0;
    }
}

And the flag stuff. This is a neat way to do it, but I think I'm going to go for a struct with a union holding a bitfield.
Code:
 /***********     FLAGS  ***********/
#define SETF(_flag) (PROGRAM_FLAGS |= (_flag))
#define CLRF(_flag) (PROGRAM_FLAGS &= ~(_flag))
#define CHKF(_flag) (PROGRAM_FLAGS & (_flag))



volatile byte PROGRAM_FLAGS;

// Set if button enabled
#define BUTTON_FLAG 0x01
#define BUTTON_ON CHKF(BUTTON_FLAG)

// Not Debouned Set when button is made low (contact made)
#define BUTTONMADE_FLAG 0x02
#define BUTTON_MADE CHKF(BUTTONMADE_FLAG)

// Requires a debounced press and release to set
#define BUTTONPRESS_FLAG 0x04
#define BUTTON_PRESSED CHKF(BUTTONPRESS_FLAG)

// Set if Encoder Enabled
#define ENCODER_FLAG 0x08
#define ENCODER_ON CHKF(ENCODER_FLAG)

// Set if PWM enabled
#define PWM_FLAG 0x10
#define PWM_ENABLED CHKF(PWM_FLAG)


I'm trying to allow for the user to select whether to run the pumps with PWM or not. I noticed that I always ran mine at full blast since they run better that way so I wanted to turn it off but also wanted to leave the ability to use it later for something else. Like I said before, that is real buggy. That's what I was working on when I left off.

Most of the other changes involve cleaning up functions. I reduced a bunch of overloads with default arguments. There are a lot of new things at the bottom of DOSE_head.h. Mostly those have to do with running the state machine. All of that is probably going to move to a different file soon.
I'm also working out some ways to make the interface a little easier. I used to have a problem hitting the button and updating the menu at the same time and selecting something I didn't mean to. So now with the button being interrupt and flag driven, I can clear any button press and ignore it if the display is updating at the same time.

There is a few new arrays besides the one for the state machine. One of them holds the schedules, and another with the same index holds name strings for the schedules. That way functions from the menu like chooseSchedule() don't have to be re-written if you add more pumps.


In the future, I want to make this truly turn into the basis of a more functional controller based on the Arduino MEGA2560. I want to turn the skeleton of the Dose_S class into a general scheduler base class that Dose_S can then derive from in addition to any number of other classes for other equipment that might need a timer.
I also need to finish working out the whole global flag system so the program can keep up with whether pumps are calibrated to match their modes and whether schedules have been set up before you run them. That's really where I left off with this code.

This code compiles and runs and does exactly what I expect it to. But then again I wrote it. It might not do exactly what you expect all the time so I wouldn't base a project on it just yet unless you plan to fix some things in the code yourself. As this is a work in progress I am always open to suggestions and criticism. But as I am a busy man these days you'll have to forgive me if I don't get back to this for a while.

If any of you are running a design like mine and would like to load up my code and try to break it, I always appreciate that.
 

Attachments

  • Dosing_Computer_11.zip
    16.1 KB · Views: 3
Also NOTE that some of the pin assignments have changed. Read the code to find out which ones.
 
Here are the three state functions that would run for the loop.

From DOSE_head.h
Code:
void doRunState()
{
  if (!BUTTON_ON) buttonOn();

  current_time = now();    //  get the time
  if (millis() - last_display_time > DISPLAY_DELAY)
  {
    LCD.clear();
    timePrint(current_time);   // and display it 
    last_display_time = millis();
  }
  
  
  if ((current_time % 60) > 30 ) has_run = false;  // Top half of minute reset the flag
  if (((current_time % 60) <= 30) && (!(has_run)))  // bottom half of minute and flag not set
  {
    //  run the schedules

    // If lock_out is set to true then the dose must be at least halfway through the other schedules interval
    //  This way, if a dose get's off schedule, the other dose maintains its distance
    //  To disable this feature, and allow the schedules to be independant
    //  Set lock_out = false somewhere before this in the loop

    if ( (!(lock_out)) || (((lengthOfTime(Ca_Schedule.last_time , timeOfDay(current_time)) + MIDNIGHT) % MIDNIGHT) >= (Alk_Schedule.interval / 2)))
    {
      Alk_Schedule.runSchedule();
    }
    if ( (!(lock_out)) ||  (((lengthOfTime(Alk_Schedule.last_time , timeOfDay(current_time))  + MIDNIGHT)  % MIDNIGHT)  >=  (Ca_Schedule.interval / 2))) 
    {
      Ca_Schedule.runSchedule();
    }

    has_run = true;  // set the run flag
  }

  // Check to see if we should transition to the DOSE_STATE
  for (int pump_count = 0; pump_count < NUMBER_OF_PUMPS; pump_count++)
  {
    if ((*schedules[pump_count]).pump_is_running)
    {
      buttonOff();
      current_state = DOSE_STATE;
    }
  }

  // Check for transition to MENU_STATE
  if ((BUTTON_PRESSED)  && (current_state != DOSE_STATE))  // If there's a button press and no pumps were running
  {
    buttonOff();  //Menu State expects this
    current_state = MENU_STATE;   //Go to the base menu
  }

}

Code:
void doDoseState()
{
  boolean exit_flag = true;

  for (int pump_count = 0; pump_count < NUMBER_OF_PUMPS; pump_count++)
  {
    if ((*schedules[pump_count]).pump_is_running)
    {
      exit_flag = false;   // if any pump is running stay in this state
      (*schedules[pump_count]).pumpTimer();  // let schedule see if it needs to turn it off
    }
  }

  if (!exit_flag)
  {
    //We're only going to update the display every DISPLAY_DELAY ms.
    if (millis() - last_display_time > DISPLAY_DELAY)
    {
      last_display_time = millis();
      for (int pump_count = 0; pump_count < NUMBER_OF_PUMPS; pump_count++)
      {
        if ((*schedules[pump_count]).pump_is_running)
        {
          //  Handle the display (will only display the lowest numbered pump running
          unsigned long time_remaining = ((*schedules[pump_count]).pump_run_time - (millis() - (*schedules[pump_count]).pump_start_time)) / 1000;

          LCD.clear();
          LCD.print(F("Running  "));
          LCD.print(schedule_names[pump_count]);
          LCD.setCursor(0,1);
          LCD.print(time_remaining);

          break;  // Kill the for loop.  Only display lowest numbered running pump
        }
      }  // end for loop

    }  // end if (millis()...
  }

  else current_state = RUN_STATE;  // exit_flag == true
}

and from DOSE_menu.h
Code:
void doMenuState()
{
  if (encoder_Counter)  
  {
    useRotaryEncoder(current_item, 0);

    //handle any rollover of the menu
    if (current_item < 0)
    {
      current_item = menu_sizes[current_menu] -1;  //subtract 1 cause it's an array index
    }
    else if (current_item >= menu_sizes[current_menu]) current_item = 0;
    
    //  COULD THIS BE //  current_item %= menu_sizes(current_menu);   // instead???  NO because it would break with negative numbers

    buttonOff();  //  Kill the button since the display is updating
    displayMenu();
  }

  else 
  {
    //  if button is on check and resolve it
    if (BUTTON_ON)
    {
      if (BUTTON_PRESSED)
      {
        buttonOff();
        encoderOff();
        //  Run menu branch function here!!!!!
        (*menu_Branches[current_menu])();
      }      

    }
    else // ELSE turn the button on
    {
      displayMenu();
      buttonOn();
      encoderOn();
    }

  }

}
 
Hi David,
this seems like a very cool project. However, I was wondering why this entire effort?

I am also thinking of using a DIY dosing pump to keep my kH on a good limit like 8.0. I have a 12v dosing pump connected to a ULN 2003.

So my code will basically cover once per day to turn pump on and off.
By calculating how many millilitres per second run through the pump, I can very easily set up a code:
if time== 8:30:00 => pump on
if time == 8:30:10 => pump off

So my question is: for my basic needs, what advantage would it be to use (part of) your code?

However, I really like your DIY project, just want to understand it a little deeper

THx
THorsten
 
Hi David,
this seems like a very cool project. However, I was wondering why this entire effort?

So my question is: for my basic needs, what advantage would it be to use (part of) your code?

However, I really like your DIY project, just want to understand it a little deeper

THx
THorsten



Yeah. It's total overkill.

The purpose is more of a learning experience. I write code really fast, so it's not really so much effort. But there are a lot of things I've never tried to code around and every time I think I have this finished I see something else and ask, "I wonder how you would do that?"

As for what advantage would it be? I don't know. It might not be. But for someone who has been through the Arduino Reference and has written a few Arduino sketches but hasn't ever really coded in C++, I think some of this code shows really basic examples of how to do certain things. For instance I tried to show above how I did the function pointer thing for the state machine. That question comes up all the time on the Arduino board. How can I have more than one loop function and decide which one based on some set of conditions? Well, up there you see how.


So I guess the overall answer is that it is a learning experience for me and if anyone else can learn anything from it then that is a positive. So I'm sharing because it can't possibly hurt.
 
You are witnessing the evolution of a monster.

A lot of this too comes from me trying to solve other problems in other code and I'll go try to figure it out in the dosing computer first to get ideas. If you'll notice, a lot of this code is quite modular and could be pulled out or rearranged to do many different things. That's because it was written to do many different things and got kludged together into this beast.
 
Double post on my double post. Wow. If this edit get's reposted I'll have hit the trifecta.
 
Good work here. I am attempting similar and will be looking through it for bits I can use.
Appreciate your efforts.
 
Sorry to bring this out of the dark past but, does anyone have a compilable version of this code? I would really love to give this project a go but, the version 11 provided above has at least a hundred errors when I try to do a build.
 
I've got lots of new and improved code I can post when I get home. But unfortunately the version that was here died with my last computer.
 
First, thanks for your reply. I've spent a lot of money for aquarium dosers. I was actually naive enough to spend over $450 for a GHL Profilux. It didn't take long to realize that no matter how much I spent, I'd never get the functionality (nor the quality) I wanted. I'd been searching a couple of months for a solution that I could implement. I know programming, although from many years ago - Cobol and Fortran, so designing my own would be out of the question. Then I found this thread - impressed is an understatement. I downloaded your code, as well as Atmel Studio and Visual Micro. After some updates, I managed to get the .ino, all the headers and the .cpp files compiled. When I tried a build it all fell apart. The Arduino software platform has changed quite a bit in the 5 years and many errors were the result. Using my bits of knowledge and a lot of stubborness, I managed to knock down the number of error by half. In the end your wide use of PROGMEM and F() managed to stymie me.

Anyway. If you have anything new on the automated dosing software front, it would be very much appreciated.
 
How's the suction on those little 12V pumps?

I have one and have been thinking about using it to dose Kalk through a reactor to work in tandem to my CA reactor.
 
How's the suction on those little 12V pumps? I have one and have been thinking about using it to dose Kalk through a reactor to work in tandem to my CA reactor.

I have used the friction driven, plastic roller, pumps where a head height of 6' had to be overcome and they worked OK. My experience, though, is that these pumps depend upon the friction of a 3/32" shaft against 3 plastic rollers to function - and function they do, until they don't. In my case, this happened often.
 
There are better pump heads out there, Welco for example, cost a bit more but can from time to time be found an Ebay for the same or less cost from new unused ink refill operations...........even new the extra cost is worth it, no issues with slipping of the shaft from the motor and they are able to drive pharmed tubing so life span is way, way, improved over the little motors running silicone......I'm relatively sure the DOS uses Welco pump heads, way better than what all the other hobby reef dosers are using....
 
I switched from the friction driven pumps to some more reliable gear driven pumps that I found on ebay for pretty cheap. They seem to work a lot better, but don't do well when driven with lower PWM values.

Here is a link to the github repo where the code now lives. This is the code I'm running at the moment on my tank. All the PROGMEM stuff is fixed so it should work out with the newer versions of Arduino. The latest version I've compiled on is 1.6.5.

https://github.com/delta-G/Disco_Doser
 
Back
Top