Beginner An Idiots guide: DIY Water Drop Controller with Arduinos and stuff.

Messages
16
Name
bill
Edit My Images
No
I've been having trouble getting repeatable results....I can get a nice collision image perhaps one or two times in ten attempts, but I'm not sure why I can't get the same style of collision with every image taken.
Your dripper code, triggering the drip & the camera delay is all done from one button push. If the drip is a bit sluggish to drop, the flash will fire earlier than had the drip been a bit nippier to drop. Also triggering the camera, rather than the flash adds latency.

You could try splitting the camera shutter and flash firing. Operate the camera shutter immediately and the flash after the delay.

Another alternate is to separate the timing. Drips can be fired as per your code, but time the firing of the flash from when the first drop breaks a light beam. You could use a photo-interrupter (they do ones with a wide gap) or use a separate IR LED and transistor.

Have fun & don't mix up 5V, 3.3V and 12V :eek:)
 
OP
OP
GarethB
Messages
1,116
Name
Gareth
Edit My Images
Yes
@GarethB Good work :eek:)

Have finished building my little digital readout for the Hiviz Multitrigger3 and Astrosplash. Wil post a piccy when plastic the LCD holder arrives from China & I can mount it onto the Hiviz.

Have also finished building my Binford 2000 Drip-o-Matic (anybody remember Tim-Tool-Time-Taylor?) It is just a MDF frame to hold bottle, drippers etc, that can be adjusted in all directions. I'm fed up with using light-stands and boom arms. Piccy to follow.

Waffling onto other things,
When making a sound or light activated trigger with Arduino, it never knows when the trigger will happen and could be doing time consuming things like updating the LCD or maths. The delay starting could be delayed or the trigger input missed completely.
I have bashed up some quick code using an interrupt to detect an input, which in turn starts the delay timer immediately.
Happy to post it here. Maybe we need to start a new thread for delay timers, as we have rather taken over Garath's fine building a dripper blog :eek:)
Many thanks Bill.
I'd be very interested to see your code Bill, I struggle with coding and I would definitely find it helpful, if it's not too much trouble.
And please feel free to post images of your Hiviz system too when it's finished.

In the meantime, here is a link to a simple sound operated delay in case peeps want to mess about with it
The sound module used are 99p on ebay.
You could probably get away with adding an input pot for the user to add a delay, but if starting to get clever with LCDs, the time between triggering & delay starting would start to vary too much.

https://petapixel.com/2017/05/10/build-simple-sound-trigger-high-speed-photos-Arduino/

Bye for now :eek:)
Thanks for the link Bill, I came across that tutorial when I was putting together my very simple sound trigger.
It hasn't progressed any farther past the basic prototype stage yet, but it seems to work quite well.
I'm using a sound module which I sourced from....you guessed it BITSBOX!
Likewise mine cost about a quid or so, and I'm looking forward to building something over the coming months.

Your dripper code, triggering the drip & the camera delay is all done from one button push. If the drip is a bit sluggish to drop, the flash will fire earlier than had the drip been a bit nippier to drop. Also triggering the camera, rather than the flash adds latency.

You could try splitting the camera shutter and flash firing. Operate the camera shutter immediately and the flash after the delay.

Another alternate is to separate the timing. Drips can be fired as per your code, but time the firing of the flash from when the first drop breaks a light beam. You could use a photo-interrupter (they do ones with a wide gap) or use a separate IR LED and transistor.
This makes sense Bill, I can see where I might have delay issues with a camera only trigger....I will give some serious thought to redesigning it to include a separate flash trigger too.
I like the idea of using a photo-interrupter, Martyn Currey talks about these in his tutorials for Arduino water drop controllers....sounds very interesting.

Have fun & don't mix up 5V, 3.3V and 12V :eek:)
:LOL:

This is a mistake that I have made, and will only make once!!!;)
 
Messages
16
Name
bill
Edit My Images
No
Timers, Interrupts & Delay timer

Hi folks, to hijack Gareth's thread (sorry Gareth :eek:) work starts to make a delay timer that can be activated by sound, breaking a light beam, or whatever else we dream up. This could then be integrated with the dripper code., or be a stand alone project.

The first thing we need is a countdown timer, without using the delay() function. Luckily for us, the Arduino already has a built in 16bit hardware timer. Even more lucky is that there is a library available for us to use, rather than having to write to the microcontroller registers directly.

To download the library, follow the link below. Click on 'download' to download the library to your pc.
The library must now be added to the IDE. Open the Arduino IDE on your pc, then select the menu item -sketch - Include Library - Add .Zip Library. Then point it to the downloaded file.

https://github.com/PaulStoffregen/TimerOne

OK, now the library is loaded, we can open an example program.
In the IDE, select the menu item File - Examples - TimerOne - Interrupt
Note this will be right at the bottom of the examples list.
So we should now have the example code as shown below. Load it to your Arduino and the LED should blink rapidly.

Exciting? Not really. However if we understand how this code is working, we can develop it for our delay timer.
Timer1 is loaded with a time of 150000 microseconds, or 0.15 of a second
Timer1 is then attached to an interrupt called blinkLED.

The main program can be put in the void loop, doing what ever we want, printing to LCD or dripping blobs of water for example.
However, every time out Timer1 counts to 150000 microseconds, the program will finish the line of code it is executing and jump to the bit of code called blinkLED.
It will then run this code and then jump back to the main loop at the next line of code.

Smart eh? we can now do two different things at the same time (almost) blink an LED for a certain time period and run code in the main loop.

Have a play with the above.

To progress the project, we need a way to,
allow the user to set the time delay
blink the led once only (i.e take one photo)
have an input to trigger the delay, for example from a sound or breaking a light beam, or a simple push button.

...to be continued in the next post......



Code:
#include <TimerOne.h>
// This example uses the timer interrupt to blink an LED
// and also demonstrates how to share a variable between
// the interrupt and the main program.

const int led = LED_BUILTIN;  // the pin with a LED
void setup(void)
{
pinMode(led, OUTPUT);
Timer1.initialize(150000);
Timer1.attachInterrupt(blinkLED); // blinkLED to run every 0.15 seconds
Serial.begin(9600);
}

// The interrupt will blink the LED, and keep
// track of how many times it has blinked.
int ledState = LOW;
volatile unsigned long blinkCount = 0; // use volatile for shared variables
void blinkLED(void)
{
if (ledState == LOW) {
ledState = HIGH;
blinkCount = blinkCount + 1; // increase when LED turns on
} else {
ledState = LOW;
}
digitalWrite(led, ledState);
}

// The main program will print the blink count
// to the Arduino Serial Monitor
void loop(void)
{
  unsigned long blinkCopy;  // holds a copy of the blinkCount
  // to read a variable which the interrupt code writes, we
// must temporarily disable interrupts, to be sure it will
// not change while we are reading. To minimize the time
// with interrupts off, just quickly make a copy, and then
// use the copy while allowing the interrupt to keep working.
noInterrupts();
blinkCopy = blinkCount;
  interrupts();
  Serial.print("blinkCount = ");
Serial.println(blinkCopy);
delay(100);
}
 
Messages
16
Name
bill
Edit My Images
No
...ok folks, here is a development of the code. I tried to keep it short to enable people learning c++ to follow.

User input via a potentiometer will allow the user to set the pause time between trigger input and flash firing.
The built in LED will light for the duration of the pause and pin D10 will go high (+5volts) after the pause, intended to fire a flashgun.

You will need to wire a pushbutton from pin D2 to ground. To play around with, this will be ok, however your finished circuit must debounce inputs, else havoc is caused.
For an article on bouncing switches and how to debounce, follow the link.
I would recommend the second circuit shown after 'FIX IT IN HARDWARE' and to order the components on ebay now. The 74HC14 ICs are 5 for 99p. Each IC has six inputs.
https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-I/

A potentiometer needs to be connected to A0. This is the user delay input. Centre leg to A0, outer two legs, one goes to +5v, other to 0volts
An LED can be connected to pin D10, via a resister of approx. 330 Ohm to 0volts

There are two interrupts being used. 'trigger' detects a trigger input on pin D2. It sets the timer running, which is the pause before flash firing.

'blinkLED' is called when Timer1 is set and when Timer1 expires. So when Timer1 is set (by the trigger ISR) 'blinkLED' will be called and turn on the LED.
When Timer1 expires, 'blinkLED' will again be called, which now turns off the LED, turns on output D10 and stops the timer.

Unless these two interrupts are called (due to triggering or timer) the code will just whizz round in the 'void loop' so we can do whatever we want here, print to LCD or screen, drop drips of water etc.
An interesting thing to note, interrupts will still work during a delay(). If the interrupt code can be executed during the delay period, the programmed delay time will not be extended. If the code runs longer than the delay() finish time, then obviously the next instruction will be a little bit late. So the trick is to keep interrupt subRoutines (ISRs) as short as possible, do not print or carry out complicated maths in them.

Note only D2 and D3 can be used as an interrupt input, so an LCD screen cannot be used with the code below. Don't worry for now, the final code will bump the LCD pins up.

- edit - deleted code
 
Last edited:
Messages
16
Name
bill
Edit My Images
No
A note on Chines knock-off Arduino clones.
I say this as a warning, in case anybody is following this thread and buys a Chinese knock-off and is most disappointed that they cannot join the fun.

A genuine Arduino uses a Amtel 16U USB driver IC. The Arduino microcontroller already has the bootloader installed.
These work fine, with no issues. Driver installs on Win 10 x64.
The Arduino software (IDE) shows the board in the list of com ports (enabling the correct one to be selected)

Some of the better clone products also work fine. For example, I originally purchased the Elegoo starter kit. This has their own Uno clone.
Happily this uses the correct Amtel 16U driver IC and has the bootloader installed. All works great.

Now onto the great Chinese knock-offs.
The Nanos that litter Ebay & Amazon all show the header pins not to be soldered. They also state 'CH340 USB driver IC'
Despite the description stating they have a bootloader, they do not.
I also purchased a Mega 2560 from a UK seller. this again had the CH340 IC. Guess what, no bootloader & made in China.

What is the problem? Well without a bootloader on the Arduino you will not be able to communicate with it.
So unless you can load your own bootloader, it is junk.

Loading (or burning as people call it) a bootloader onto a Chinese clone is really easy if you already have a working Arduino.
It is all done from within the Arduino IDE. The files to load onto the Arduino are already there, in the examples tab.
The working Arduino is used to copy a bootloader onto the clone. There are loads of Youtube videos explaining the process.

A second way to load the bootloader if no working Arduino is available, is to purchase one of the many ISP programmers on Ebay or Amazon. Ensure it has a six wire cable. Again the great YouTube has loads of videos covering this technique.

If you are planning to use lots of Chinese knock-offs, or you just like playing, I can highly recommend
Nick Gammon's solution. I have a dedicated Nano loaded with Nick's code and made my own 6 way cable by cutting down an 8 way ribbon cable (butchery I know, but it is all I had. Ebay sell a 6 way cable for 99p) A cable is not actually needed, female to female jumper wires do the job, or even male-female if you hop through a breadboard.
http://www.gammon.com.au/bootloader

The next issue is loading the CH340 driver onto your pc to allow USB communication to the knock-off. For some it seems easy, for others not so.
Use Dr. Google to find the driver, often the Ebay sellers show the link in their description.
In fairness I think many reported issues are caused by the knock-offs not having a bootloader, so will not communicate until they do.

Unlike a genuine Arduino, the com port a knock-off is connected to, will not show up in the 'port' tab under 'tools' in the Arduino IDE.
So one has to go to device manager to see which one it is. Hopefully the driver should have installed correctly, else you will have a yellow triangle next to the port.

Once I realised none of these Chinese knock-offs have a bootloader, even if they say they do and to look in device manager for the com port, I have had no issues with any of the Chines clones.

Happy coding :eek:)
 
OP
OP
GarethB
Messages
1,116
Name
Gareth
Edit My Images
Yes
Thank you most kindly Bill (@billbillbill ) for the incredibly detailed advice....sorry I didn't respond sooner, it's a lot to take in, and I wanted to get a handle on it first!
The articles you have linked to are very interesting, especially the one regarding 'de-bouncing' switches.
So I have read that article with great interest and have bought a handful of the 74HC14 ICs which I am currently testing on a new prototype build.
Nothing to report as yet, but I've studied the characteristics of these chips and they seem fairly straightforward.
I'll be having a play with the code you posted soon too.(y)

I'll hopefully be trying to not only incorporate interrupts into the next evolution, but also I would like to implement some kind of menu system too.
I would like to design a triple valve system, each with the capability of dispensing multiple, timed drops...certainly three but perhaps four.
And I will be separating the flash from the camera controls too, and having them each on their own separate, time-adjustable system.
I could buy an Arduino Mega and have one big screen and a ton of pots etc, or I would prefer to be efficient and work with menus.
I've done some preliminary research, but it's much harder than it sounds!

Are there any easy-to-understand resources out there for simpletons like me?!:LOL:
Not forgetting that up until now, the only programming I've ever done was in BASIC, over 35 years ago, to wit:

Code:
10 PRINT "HELLO"
20 GOTO 10
I have an awful lot to learn!!
Maybe I'm trying to run before I can walk here....this may be a bit too advanced at my stage of learning!

Regarding your advice on Chinese knock-off Arduino clones, I had a bit of an issue with mine at first (Elegoo Nanos) but they seemed to work fine when I changed to the 'old bootloader'. Don't remember downloading anything....I just fiddled around until it worked - not very methodical or scientific but it worked out in the end!
Is this 'old bootloader' ok to use? It seems to work fine for me.
I'll probably stick with the Elegoos anyway, they seem decent, and robust....except when one accidentally puts 12 VDC through it!!:facepalm:
I'll be having a look at the 'Nick Gammon' solution however....forewarned is forearmed as the saying goes.

Once again Bill, many thanks for the time you've taken to post such good and detailed advice!(y)
It'll take me a while to understand the principles of 'the interrupt' but I'll keep at it.
 
Messages
16
Name
bill
Edit My Images
No
haha, I like your basic programming. Reminds me of doing such things as a boy on shop demo computers.
I have a fully functioning delay-timer code that works with a uno/nano, happy to post here if wanted.

Was going to build some sound and break-beam triggers for the delay timer, however Ebay sell complete units, cheaper than I can by the individual components. They are pre-assembled on a little pcb with header pins, LED and logic to give a clean 0V/ 5V output. No need for debounce or Schmitt triggers. The sound sensor should work without modification. The collision avoidance sensor would need the IR LED un-soldered, so it can be positioned to point back at the PCB to create a beam, to be broken.

I have moved onto the Mega 2560. Simple reason it has far more IO and interrupts. Also, rather than using a separate protoboard for the components, a cheap development shell plugs straight on the top, so keeps everything neat & no jumpers between mega board and protoboard.
Have also switched to a four line 20 X 4 LCD with serial interface, so only 4 wires.

Looked at a menu system, using a rotary encoder. Idea being to cut down on the number of input pots required. However this has turned out to be such a faff, requiring two encoders, one for selection, other to change the value. This in turn needs 4 interrupts, loads of LCD code to move a cursor., highlight text & stuff.
I'm going to stick with a separate input pot for each adjustment, which will be.....
coarse time delay
fine time delay
timeout
drip 1 on time
pause time
drip 2 on time
pause time
drip 3 on time
Blimey, actually I think I might re-visit the idea of a menu system :eek:)

My Binford2000 has three solenoids, however they are ganged together. To program each separately will certainly be a challenge.

Using the old bootloader option in Arduino IDE is fine, it is there for compatibility. If you load a new bootloader onto the Arduino, it should allow the normal bootloader option. It is also a good idea to load the Uno bootloader onto a Nano. This will give you an additional 200k of memory to play with.

Right, my next step is to pinch your dripper code and add it to my delay timer code to make one splashtastic device.

bill.
 
Messages
16
Name
bill
Edit My Images
No
Hi Folks,

Code below is for a delay timer. Seems to work ok. Note LCD pins (2,3,4,5 all moved up one to 2,4,5,6)
This makes pin 2 the input trigger.
Stick two pots on A0 and A1 which will give a coarse and fine adjustment. A third pot could be used to allow re-trigger timeout adjustment.
The on-board led lights for the length of the delay.
pin 12 gives a brief output on completion of the delay.


Code:
// ver 1.7 26 March 2019
//Note the word 'pause' used as 'delay' is a function & could cause confusion
//Note TriggerPin must be debounced, else it plays havoc. - look at link below...
//https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-i/
#define DEBUG false  //set to true for debug output, false for no debug ouput
#define Serial if(DEBUG)Serial
#include <TimerOne.h> // https://github.com/PaulStoffregen/TimerOne
#include <LiquidCrystal.h> // built in library
LiquidCrystal LCD(10, 9, 6, 5, 4, 3); //connection pins to LCD
// Variables seup here.
volatile long pauseTime = 5000000; // time between triggering and flash firing uS
volatile int timeoutFlag = 0; // set to 1 if trigger pushed and timeout not expired
const int led = LED_BUILTIN; // the pin with on-pcb LED Uno pin 13, Nano pin 16
const int triggerPin = 2; // user input to start pause Uno pin 2 Nano pin 5
const int flashpausePin = 12; // output to flashgun
const int coarsePausePot = A0; // user coarse input pot
const int finePausePot = A1; // user fine input pot
long int pause = 1023; // user input pot to set pauseTime
long int oldPause = 1023; // last reading from user input pot
long int coarsePause = 400000; // var to store user input
long int finePause = 100000; // var to store user input
int val; // misc variable
int ledState = LOW; // LED status (on or off)
int timeout = 1000;                   // timeout before sytem can trigger again mS
void setup() {    // put your setup code here, to run once: *************************
pinMode(led, OUTPUT); // set on-pcb LED pin as output
pinMode(triggerPin, INPUT_PULLUP); // user input pin to start pause
pinMode(coarsePausePot, INPUT); // user input pot to set coarse pauseTime
pinMode(finePausePot, INPUT); // user input pot to set fine pauseTime
pinMode(flashpausePin, OUTPUT); // setup flashpausePin - this will connect to flashgun
EIFR |= (1 << INTF0); // clear interrupt flags
Timer1.initialize(500000); // setup timer 500000uS
Timer1.stop(); // stop timer as not needed yet
Timer1.attachInterrupt(timer1Expired); // setup interrupt timer1Expired to run for pauseTime uS
attachInterrupt(digitalPinToInterrupt(triggerPin), trigger, FALLING); // setup interrupt
EIFR |= (1 << INTF0); // clear interrupt flags

Serial.begin(9600); // debug setup output to screen
LCD.begin(16, 2); // format of LCD 16 column by 2 rows
LCD.clear(); // clear LCD screen
LCD.setCursor(0, 0); // position cursor on LCD
LCD.print(" TriggerTimer "); // print to LCD
}

// ISR when triggered, sets Timer1 pauseTime
void trigger (void) {
if (timeoutFlag == 0) { // check timeout time has expired before allowing new trigger
Serial.print("push button: "); // debug
Serial.println(pauseTime); // debug
Timer1.setPeriod(pauseTime); // activate timer pause counter
digitalWrite(led, HIGH); // turn on led for duration of the pause
timeoutFlag = 1; // set timeout flag to stop second activation
}
}
// ISR is called at the end of Timer1 period.
void timer1Expired(void){
Serial.println("ISR called"); //debug print to screen
digitalWrite(flashpausePin, HIGH); // fire flashgun
digitalWrite(led, LOW); // turn off led
Timer1.stop(); // stop timer as no longer needed
}
// end of ISRs ***************************************************
void loop() { // put your main code here, to run repeatedly:**********************  
// read and format user input
coarsePause = analogRead(coarsePausePot); // readuser input from coarse pause pot
finePause = analogRead(finePausePot); // readuser input from coarse pause pot
coarsePause = map(coarsePause, 0 , 1023, 0, 5000); // map input
finePause = map(finePause, 0 , 1023, 0, 500); // map input
coarsePause = (coarsePause * 100); // multiply input to give uS
finePause = (finePause * 2); // multiply input to give uS
  pause = (coarsePause + finePause);                   // add coarse & fine delays to give total pause
  // Update Pausetime if user has changed input
if (pause != oldPause) { // if user changed pause, update
noInterrupts(); // turn off interrupts as variable is used in ISR
pauseTime = pause; // update var used in interrupt
interrupts(); // turn on interrupts
    oldPause = pause;                           // update to check next time round
    // update LCD
LCD.setCursor(6, 1); // position LCD cursor
LCD.print(" "); // blank out old reading
LCD.setCursor(6, 1); // position LCD cursor
LCD.print(pauseTime); // print updated pauseTime to LCD
  }
  val = digitalRead(flashpausePin);           // read flashpausePot
if (val == HIGH) { // if flash has fired...
Serial.println("Flash has Fired"); // debug
delay(100); // delay before releasing flash contacts
digitalWrite(flashpausePin, LOW); //.........and open contacts
delay(timeout); //.....timeout delay....
timeoutFlag = 0; //reset timeoutFlag
}
else { // debug
Serial.println("not fired"); // debug
delay(1000);
  }                                           // debug
  // debug print out variables to screen
Serial.println(finePause);
Serial.println(coarsePause);
  Serial.println(pause);
  Serial.print("pause time:    ");          // debug output to screen
Serial.println(pauseTime); // debug output to screen
Serial.println(" ");
  delay(500);
  // end of void loop **********************
}
 
Messages
16
Name
bill
Edit My Images
No
Excellent work Sérgio!
Glad you have a working prototype.

They are good questions Sérgio, indeed the start button is supposed to start the sequence, but it should only do one cycle, and then wait until the button is pressed again to start another cycle.

This line of code is where the program waits.

C-like:
buttonSTATE=digitalRead(startBUTTON);
if(buttonSTATE==LOW) { //Wait for start button to be pressed before continuing
It shouldn't keep cycling, it should perform the solenoid and camera actions, then return to this inside the void loop and wait for the button to be pressed again.

I had some issues with the start button too, but because of my lack of electronics knowledge, I did some trial and error to get it to work.
I used a 1 KΩ resistor as a 'pull-up' resistor....I believe that's the right term!

Just reading through the older posts & noticed the above.
There are two issues, first, buttons should always have pullup or pulldown resisters, else the voltage floats & can cause all sorts of issues as the poor Arduino does not know what state (high or low) the button should be.
Luckily the answer is simple, the Arduino has built in pullup resisters, so there is no need to add external ones, just change the line in the code by adding '_PULLUP' as below. This will add a 10k pullup resister for you. Using a 1K resister is a bit low & is drawing 5mA

pinMode(triggerPin, INPUT_PULLUP);

The second is that switches are mechanical and don't just make or break once, but many times as we push or release them. They need to be debounced.
Just read the link below, Hardware debouncing is best, just a resister and a capacitor as shown in the first picture, better still use the IC in the second picture
https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-I/
 
OP
OP
GarethB
Messages
1,116
Name
Gareth
Edit My Images
Yes
Thanks Bill (@billbillbill ) for your work on the coding....it's invaluable!(y)
I've been reading quite a bit on interrupts lately, and it's been hard to adapt the general rules to something specific to drop controllers etc, so your code will certainly be a very useful learning tool.
Am I right in assuming that this would be better suited to a sound trigger system for example (eg. capturing an airgun pellet impact), rather than a water drop controller?
As we've discussed earlier, it seems that the interrupt function is better suited to much finer timings - in microseconds.
Not trying to avoid interrupts or anything, it's just that my brains are hurting!:giggle:

I've also moved to the I2C protocol for 20x4 LCDs to free up some pins on my Nanos....working well so far.
Thanks for the advice on the PULLUP/PULLDOWN feature - I'd heard of this recently and thought it was pretty clever, and you've just clarified it well!

I've just installed as many relevant libraries as I can today - TimerOne etc, so I have no more excuses, I have to put something together....and fear not Bill, all my switches will be debounced!;)
Actually, I do have one final question....I have a few 4x4 membrane panels (BITSBOX LINK) would all these need debouncing if I were to use them?
I don't have any firm plans for them yet, but they're dirt cheap, so I bought a few....I'm sure they'll come in handy for something.

Once again, thanks Bill.(y)
 
Messages
1,295
Name
Lee
Edit My Images
No
Hi Gareth and Bill,

Some really good stuff here - thank you both :) I have already built an aquarium LED light time switch when the original died and have a multi trigger flash-o-matic project in the pipeline :D

The stuff on Interrupts is interesting and has brought back some college memories. Time for a tinker I think (y)

PS - @GarethB - It's usual to debounce pretty much any mechanical switch as the contacts can appear to change state multiple times as they are pressed.
 
OP
OP
GarethB
Messages
1,116
Name
Gareth
Edit My Images
Yes
Hi Gareth and Bill,

Some really good stuff here - thank you both :) I have already built an aquarium LED light time switch when the original died and have a multi trigger flash-o-matic project in the pipeline :D

The stuff on Interrupts is interesting and has brought back some college memories. Time for a tinker I think (y)

PS - @GarethB - It's usual to debounce pretty much any mechanical switch as the contacts can appear to change state multiple times as they are pressed.
Thanks Lee, I do feel very fortunate that you're here to offer valuable advice. (y)
Great minds eh!....I'm beginning to plan a multi valve, multi drop system too....with menus possibly!
It's a very steep learning curve...and I do have a tendency to slide downwards!!

I didn't do computing unfortunately....just stuffy old physics!...so again I'm very grateful to you and Bill for sharing your expertise.

On the 4x4 membranes....so all 16 contacts need denouncing? Fair enough, looks like I'll be needing a bunch more of those 74HC14 chips then!....another bitsbox order incoming!!:LOL:
 
Messages
1,295
Name
Lee
Edit My Images
No
...On the 4x4 membranes....so all 16 contacts need denouncing? Fair enough, looks like I'll be needing a bunch more of those 74HC14 chips then!....another bitsbox order incoming!!:LOL:
Bitsbox could retire soon at the rate we're ordering :LOL:

I wonder if there is a clever way of 'debouncing' the array of switches using common returns or something to save a load of connections?
 
Messages
16
Name
bill
Edit My Images
No
hi @everyone

Yep, no getting away from it, all mechanical switches need to be debounced. You can of course do it in software, there are libraries for this, but best practice is always hardware debounce.

Using interrupts rather than delay is also good practice. whilst the microcontroller is sitting in a delay, it cannot be getting on with other things. For a drip controller it is not to much of an issue as we will be poised waiting for the drip rather than wanting to change settings on the Arduinio mid drip cycle.

Here is my code below for a combined trigger timer & splash/drippy thing. The camera flash delay uses an interrupt and the drips are a blatant rip-off of Gareth's code :eek:)

Pressing the startbutton (pin 11) will cause the drips to start and the delay timer will take it's timing from this button & fire the flash
Pressing the trigger button (pin 18) will cause the delay timer to start & fire the flash, but not operate the drips.
This uses an interrupt, so can be triggered by external sensors and will immediately jump into delay timing.

A development of the code will allow the user to select either of the above, or a third option of timing of the flash for the drips from an outside source, e.g a break-beam which starts the delay timer when broken by a blob of water for example. This is how my current system works, using the hiviz triggertimer3 and the astrosplash.

A few changes to Gareth's code. A pullup added to the trigger pin, using less variables and the LCD is only updated if there is a change, this stops the flickering and dimming of the LCD.

It is written for the megaa2560 as the nano does not have enough IO. Even if rotary encoders were used instead of the pots, the nano only has two pin interrupts, so would struggle. It is possible to use rotary encoders without interrupts, or use a multiplexing IO chip to give the nano more IO, but it is a bit of a faff.

Code:
// ver 1.8 24 March 2019
// Mega2560 version
//Note the word 'pause' used as 'delay' is a function & could cause confusion
//Note TriggerPin must be debounced, else it plays havoc. - look at link below...
//https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-i/
#define DEBUG false  //set to true for debug output, false for no debug ouput
#define Serial if(DEBUG)Serial
#include <TimerOne.h> // https://github.com/PaulStoffregen/TimerOne
#include <Wire.h> // built in libray
#include <LiquidCrystal_I2C.h> // built in library
LiquidCrystal_I2C LCD(0x27, 20, 4); // set the LCD address to 0x27
// how to wre LCD    https://www.youtube.com/watch?v=ZtwFpFIS1_0
// trigger timer  Variables seup here.
volatile long pauseTime = 850000; // time between triggering and flash firing uS
volatile int timeoutFlag = 0; // set to 1 if trigger pushed and timeout not expired
const int led = LED_BUILTIN; // the pin with on-pcb LED Uno pin 13, Nano pin 16
const int triggerPin = 18; // user input to start pause (interrupt 5)
const int flashLed = 23; // output to flashLed, lights when flash fires
const int pauseLed = 22; // output to pauseLed, lights between triggering and firing
const int coarsePausePot = A8; // user input coarse pause pot
const int finePausePot = A9; // user input fine pause pot
const int timeoutPot = A10; // user input time between triggering and re-triggering
long int pause = 620; // user input pot to set pauseTime
long int oldPause = 620; // last reading from user input pot
long int coarsePause = 500; // var to store user input
long int finePause = 120; // var to store user input
int val; // misc variable
int ledState = LOW; // LED status (on or off)
int timeout = 100; // timeout before sytem can trigger again mS
int timeoutOld = 10;
// splash vars
const int solPin = 7; // assign pin 7 to solenoid
const int camPIN = 6; // assign pin 6 to camera
const int dropOnePot = A1; // assign pin A1 to potentiometer 1
const int dropTwoPot = A2; // assign pin A2 to potentiometer 2
const int dropThreePot = A3; // assign pin A3 to potentiometer 3
const int dropDelayOnePot = A4; // assign pin A4 to potentiometer 4
const int dropDelayTwoPot = A5; // assign pin A5 to potentiometer 5
const int startBUTTON = 11; // assign pin 11 to startBUTTON
const int intTriggerSel = 10; // assign pin 10 to trigger select
int solDelayOne; // declare solDelayOne variable
int solDelayTwo; // declare solDelayOne variable
int camDEL; // declare camDEL variable
int dropOne; // declare dropOne variable
int dropTwo; // declare dropTwo variable
int dropThree; // declare dropTwo variable
int dropOneOld; // declare dropOne variable
int dropTwoOld; // declare dropTwo variable
int dropThreeOld;                   // declare dropTwo variable
int solDelayOneOld;                 // declare solDelayOne variable
int solDelayTwoOld;
int buttonSTATE = HIGH;             // set buttonSTATE variable to HIGH
boolean internalTrigger = true;      // select if drop button also starts trigger timer
void setup() {    // put your setup code here, to run once: *************************
//trigger timer
pinMode(led, OUTPUT); // set on-pcb LED pin as output
pinMode(triggerPin, INPUT_PULLUP); // user input pin to start pause
pinMode(coarsePausePot, INPUT); // user input pot to set coarse pauseTime
pinMode(finePausePot, INPUT); // user input pot to set fine pauseTime
pinMode(timeoutPot, INPUT); // user input pot min time before next trigger allowed
pinMode(intTriggerSel, INPUT_PULLUP); // user input select how flash is fired when using drop
pinMode(flashLed, OUTPUT); // setup flashLed - lights when flash fires
pinMode(pauseLed, OUTPUT); // setup pauseLed - lights between triggering and flash firing
Timer1.initialize(500000); // setup timer 500000uS
Timer1.stop(); // stop timer as not needed yet
Timer1.attachInterrupt(timer1Expired); // setup interrupt timer1Expired to run for pauseTime uS
  attachInterrupt(digitalPinToInterrupt(triggerPin), trigger, FALLING); // setup interrupt
  // splash
pinMode(solPin, OUTPUT); // set solPin to output
pinMode(camPIN, OUTPUT); // set camPIN to output
pinMode(dropOnePot, INPUT); // set potentiometer 1 as input
pinMode(dropTwoPot, INPUT); // set potentiometer 2 as input
pinMode(dropThreePot, INPUT); // set potentiometer 3 as input
pinMode(dropDelayOnePot, INPUT); // set potentiometer 4 as input
  pinMode(startBUTTON, INPUT_PULLUP);  // set startBUTTON as input
  Serial.begin(9600);                  // debug setup output to screen
LCD.init();
LCD.backlight();
LCD.clear(); // clear LCD screen
LCD.setCursor(0, 0); // position cursor on LCD
  LCD.print("    TriggerTimer    ");   // print to LCD
  LCD.setCursor(0, 1);                 // reset the cursor position
LCD.print("FLPa"); // Flash Pause
LCD.setCursor(10, 1); // reset the cursor position
LCD.print("TO"); // TimeOut
LCD.setCursor(0, 2); // reset the cursor position
LCD.print("Drp"); // print the line "Drp" drop
LCD.setCursor(0, 3); // reset the cursor position
LCD.print("Pau"); // print the line "Pau" pause
}
// ISRs go here *************************
// ISR when triggered, sets Timer1 pauseTime
void trigger (void) {
if (timeoutFlag == 0) { // check timeout time has expired before allowing new trigger
Timer1.setPeriod(pauseTime); // activate timer pause counter
digitalWrite(pauseLed, HIGH);
timeoutFlag = 1; // set timeout flag to stop second activation
}
}
// ISR is called at end of Timer1 period.
void timer1Expired(void) {
digitalWrite(pauseLed, LOW); // update state of pasuselength LED
digitalWrite(flashLed, HIGH); // fire flashgun
Timer1.stop(); // stop timer as no longer needed
}
// end of ISRs ***************************************************

void loop() { // put your main code here, to run repeatedly:**********************
// read and format triggertimer user input
coarsePause = analogRead(coarsePausePot); // readuser input from coarse pause pot
finePause = analogRead(finePausePot); // readuser input from coarse pause pot
coarsePause = map(coarsePause, 0 , 1023, 1, 800); // map input to mS
finePause = map(finePause, 0 , 1023, 0, 50); // map input to mS
  pause = (coarsePause + finePause);                   // add coarse & fine delays to give total pause
  // triggertimer - Update Pausetime & LCD if user has changed input
if (pause != oldPause) { // if user changed pause, update
noInterrupts(); // turn off interrupts as variable is used in ISR
pauseTime = pause * 1000; // convert to uS and update var used in interrupt
interrupts(); // turn on interrupts
oldPause = pause; // update to check next time round
LCD.setCursor(5, 1); // position LCD cursor
LCD.print(" "); // blank out old reading
LCD.setCursor(5, 1); // position LCD cursor
LCD.print(pause); // print updated pauseTime to LCD
  }                                          // end if
  // triggertimer -  Update timeout & LCD if user has changed input
timeout = analogRead(timeoutPot); // read user input from timeOut pot
timeout = map(timeout, 0 , 1023, 0, 500); // map result
timeout = (timeout * 10); // convert to mS
if (timeout != timeoutOld) { // if user changed timeut, update
timeoutOld = timeout; // update to check next time round
LCD.setCursor(13, 1); // position LCD cursor
LCD.print(" "); // blank out old reading
LCD.setCursor(13, 1); // position LCD cursor
LCD.print(timeout); // print updated pauseTime to LCD
  }

// splash Update drop & delay times & LCD if user has changed input
dropOne = analogRead(dropOnePot); // read analogue value from potentiometer 1
dropOne = map(dropOne, 0, 1023, 10, 100); // use map to increase accuracy of pot
if (dropOne != dropOneOld) { // only update LCD if value has changed
dropOneOld = dropOne; // copy var to check for change in if-loop
LCD.setCursor(5, 2); // set cursor position as bottom left
LCD.print(" "); // print 3 spaces to keep LCD clean
LCD.setCursor(5, 2); // reset cursor position
LCD.print(dropOne); // print the calculated time value dropOneval
  }                                       // end if
  dropTwo = analogRead(dropTwoPot);         // read analogue value from potentiometer 2
dropTwo = map(dropTwo, 0, 1023, 9, 100); // use map to increase accuracy of pot
if (dropTwo == 9) dropTwo = 0; // if below 10, set to 0, no second drop
if (dropTwo != dropTwoOld) { // only update LCD if value has changed
    dropTwoOld = dropTwo;                   // copy var to check for change in if-loop
    LCD.setCursor(9, 2);                    // reset cursor position
LCD.print(" "); // print 3 spaces to keep LCD clean
LCD.setCursor(9, 2); // reset cursor position
LCD.print(dropTwo); // print the calculated time value dropTwoval
  }                                         // end if
  dropThree = analogRead(dropThreePot);        // read analogue value from potentiometer 2
dropThree = map(dropThree, 0, 1023, 9, 100); // use map to increase accuracy of pot
if (dropThree == 9) dropThree = 0;
if (dropThree != dropThreeOld) { // only update LCD if value has changed
dropThreeOld = dropThree; // copy var to check for change in if-loop
if (dropThree == 9) dropThree = 0; // if below 10, set to 0, no second drop
LCD.setCursor(13, 2); // reset cursor position
LCD.print(" "); // print 3 spaces to keep LCD clean
LCD.setCursor(13, 2); // reset cursor position
LCD.print(dropThree); // print the calculated time value dropTwoval
  }                                            // end if
  solDelayOne = analogRead(dropDelayOnePot);        // read analogue value from potentiometer 3
solDelayOne = map(solDelayOne, 0, 1023, 20, 200); // use map to increase accuracy of pot
if (solDelayOne != solDelayOneOld) { // only update LCD if value has changed
solDelayOneOld = solDelayOne; // copy var to check for change in if-loop
LCD.setCursor(5, 3); // set cursor position as bottom line, 9 characters from left
LCD.print(" "); // print 3 spaces to keep LCD clean
LCD.setCursor(5, 3); // reset cursor position
LCD.print(solDelayOne); // print the calculated time value dropThreeval
  }                                                 // end if
  solDelayTwo = analogRead(dropDelayTwoPot);        // read analogue value from potentiometer 3
solDelayTwo = map(solDelayTwo, 0, 1023, 20, 200); // use map to increase accuracy of pot
if (solDelayTwo != solDelayTwoOld) { // only update LCD if value has changed
solDelayTwoOld = solDelayTwo; // copy var to check for change in if-loop
LCD.setCursor(9, 3); // reset cursor position
LCD.print(" "); // print 3 spaces to keep LCD clean
LCD.setCursor(9, 3); // reset cursor position
LCD.print(solDelayTwo); // print the calculated time value dropThreeval
  }                                                 // end if
  // Test for user input. If drop button pressed, start drop sequence
buttonSTATE = digitalRead(startBUTTON); // test state of start button
  if ((buttonSTATE == LOW) && timeoutFlag == 0) {       // if pressed, start drop sequence
    Timer1.setPeriod(pauseTime);                    // activate timer pause counter for flash delay
digitalWrite(pauseLed, HIGH);
LCD.setCursor(17, 1);
LCD.blink();
    timeoutFlag = 1;
    digitalWrite(solPin, HIGH);                     // set solPin to HIGH - open the solenoid for first drop
delay(dropOne); // delay for time value dropOne - first drop size
    digitalWrite(solPin, LOW);                      // set solPin to LOW - close the solenoid
    if (dropTwo != 0) {                             //test if second drop reqired
delay(solDelayOne); //delay for time value solDelayOneval
digitalWrite(solPin, HIGH); //set solPin to HIGH - open solenoid for second drop
delay(dropTwo); //delay for time value dropTwo - second drop size
digitalWrite(solPin, LOW); //set solPin to LOW - close the solenoid
    }
    if (dropThree != 0) {                     //test if third drop reqired
delay(solDelayTwo); //delay for time value solDelayOneval
digitalWrite(solPin, HIGH); //set solPin to HIGH - open solenoid for third drop
delay(dropThree); //delay for time value dropThree - third drop size
digitalWrite(solPin, LOW); //set solPin to LOW - close the solenoid
}
}
val = digitalRead(flashLed); // read flashpause
if (val == HIGH) { // if flash has fired...
Serial.println("Flash has Fired"); // debug
delay(100); // delay before releasing flash contacts
digitalWrite(flashLed, LOW); //.........and open contacts
delay(timeout); //.....timeout delay....
timeoutFlag = 0; //reset timeoutFlag
LCD.setCursor(17, 1);
LCD.noBlink();
}
else { // debug
Serial.println("not fired"); // debug
  }                                           // debug
  // debug print out variables to screen
if (DEBUG) {
Serial.print("finePause: ");
Serial.println(finePause);
Serial.print("coarsePause: ");
Serial.println(coarsePause);
Serial.print("pause: ");
Serial.println(pause);
Serial.print("pauseTime: "); // debug output to screen
Serial.println(pauseTime); // debug output to screen
Serial.print("timeout: "); // debug output to screen
Serial.println(timeout); // debug output to screen
Serial.println(" ");
delay(500);
}
}
// end of void loop **********************
 
Messages
16
Name
bill
Edit My Images
No
A couple of fotos, showing my Digital readout for the Hiviz Multitrigger3 and Astrosplash. An acrylic case & stand for the LCD was ordered from Ebay, unfortunately they sent a totally different part, so still need to box it up.
The four little resisters are a potential divider for the input, as the Multitrigger puts out about 8 volts on it's test socked & the Nano is max 5.5 V in.
Running off a USB lead at the moment, but as the Nano has an onboard regulator, will run it from the 12V supply from the Multitrigger when all boxed up.
It autodetects if both Multitrigger & Astrosplash or just one is connected and the display adjusts accordingly, foto shows it in Multitrigger only mode.

multitrigger2.JPG
 
Messages
16
Name
bill
Edit My Images
No
This is the Binford 3000 splash-o-matic.
Three solenoids are hooked up, they can be moved horizontally to vary the splash & the whole arm slides up & down to vary drop height.
The flash delay is triggered by a photo-interrupter, which can be seen below the middle solenoid.

Just made up from some MDF off-cuts. The arm is a 19" rack grill panel which I had lying around.



Binford3000.JPG
 
Last edited:
Top