CO2 Laser Water Flow Monitor - Arduino Powered / Windows Controlled




Introduction: CO2 Laser Water Flow Monitor - Arduino Powered / Windows Controlled

About: I've been writing software since I was in the 6th grade, and working with mostly-digital electronics since High School. These days my career consists of software development and architecture that is focused...

CO2 Laser Water Flow Monitor and Alarm

Powered by an Arduino and Controlled by custom software on a Windows PC

In order to extend the life of a CO2 laser tube it is necessary to pump cooling water through it while it is operating. If the pump fails or a water line becomes crimped, or otherwise blocked slowing or stopping the flow of water, damage to the laser tube is likely.

This project will demonstrate how to create a flow rate monitor and alarm that will disable the laser when the water flow rate drops to an unacceptable level. Whether you have a professional, high quality CO2 laser, such as the Full Spectrum laser, or an inexpensive "K40" laser like mine, this is a must have tool to protect your investment.

And even if you don't currently have a CO2 laser, this article will teach valuable concepts that can be applied to many other projects, including how to write native Windows software that interfaces with an Arduino based circuit. In particular, it will demonstrate a simple MFC application that displays the water flow rate, alarm status, laser power status, and which allows a user to configure the threshold at which the water flow alarm will sound (and disable power to the laser tube.)

Here is a video of the device in action...

A more comprehensive video - be warned, this one is narrated and the narrator was tongue tied! ;)

Step 1: Parts List...

Thanks to the power of the Arduino, this device is relatively simple to build. I used mostly parts from my electronics stock pile. If you have different but similar parts, try those. Because I elected to use a Seeed Studio Relay Shield, it made sense to use an Arduino Shield Proto PCB to connect up everything else...


  1. An Arduino or compatible board that will work with standard Arduino Shields
    I used an old Arduino Duemilanove, but an Uno will work as well (Note that an inexpensive clone will work just fine.)
  2. A Seeed Studio Relay Shield v2.1 (Note: You can find these on eBay at a good discount).
  3. Water Flow Sensor ($6.50)
  4. Opto-isolator NTE3040 or 4N25 (Note: I used the NTE3040 because it was available locally at IntertexElectronics.)
  5. 380 Ohm Resistor (1/8 W)
  6. 10K Ohm Resistor (1/8 W)
  7. Arduino Shield Proto PCB Rev3 (A000082)
  8. Radio Shack Piezo Buzzer (273-0795)
  9. Radio Shack 7x5x3" Project Enclosure (270-1807)
  10. Various wire, connectors, and cable as desired for your system

Step 2: The Schematic...

While this device is relatively simple, the schematic is a bit complex because it contains pluggable components (the Arduino and Seeed Studio Relay Shield) and because of the connection points to the CO2 Laser's Power Supply - both to tell when the laser is powered off which will disable the audible alarm, as well as to disable the laser when the alarm is triggered.


  1. The piezo buzzer's positive lead connects to PIN 10 of the Arduino, it's negative lead to the ground.
  2. The water flow sensor's OUT lead connects to PIN 2 of the Arduino. It is important to use either PIN 2 (or alternatively PIN 3 if the sketch is changed) to readily use Interrupt 0 (Interrupt 1 for PIN 3) on the microcontroller. The positive lead connects to the 5 volt line and the ground lead to the ground.
  3. PIN 8 of the Arduino is connected to the Opto-Isolator's Collector (Pin 5 of the NTE3040). A 10k ohm resistor also ties PIN 8 of the Arduino to the 5 volt line as pull-up resistor.
  4. The Opto-Isolator's Emitter is connected to the ground.
  5. The Laser's Power Supply should supply a 5 volt line for it's electronics. If it does not you will need to adjust the value of the resistor or use another form of isolation. The 5 volt power on the Laser's PSU will connect to the anode of the opto-isolator (pin 1 of the NTE3040). The cathode (pin 2) will connect through a 380 ohm resistor to the Laser's PSU ground. Be sure to check the specifications on your opto-isolator's LED and use a suitable resistor to drop the voltage/current to the diode to a safe level.
  6. J1 COM and J1 NC act as a secondary cut-off switch for powering the laser. My K40 unit has a switch used to disable the laser (while allowing the rest of the system remain functional). I simply cut one of the wires between the switch and the Laser's Power Supply and spliced in connections to J1 COM and J1 NO
  7. This schematic was made using EasyEDA. Please support them as this service is extremely valuable!

Step 3: Assembly...

The rest of the assembly process is a matter of taste. If I were to do this all over again, I'd use the laser cutter to cut a custom acrylic box (as you will see in article on building a CO2 Laser Water Chiller). The laser does a far better job cutting plastic than I did here. (If you use a drill always use clamps! The drill will pick-up the plastic box and spin it at a high velocity into anything - like your hand - that it can.)

I used a drill to mark and drill holes to bolt the piezo buzzer to the base of the project box. I then did the same to bolt the Arduino to the base of the box. I used a hot knife to cut a hole in the side of the box for the Arduino's USB port. It is IMPORTANT to remember to place locate the Arduino so that the USB port is externally accessible!

Next I drilled two holes to match the posts on the bottom of the Water Flow Sensor. Though it might be possible to attach the sensor to the box using screws, I decided to make tight fighting holes for those posts and then I used epoxy on both sides to secure it to the surface of the box. This seems to work well.

With all of these essential elements placed, I drilled another hole for a 4-pin Male Mic Connector (matches to the 4-pin female mic connector with Radio Shack part #274-0001). I used two pins to connect to the Laser's power supply +5 volts and ground, and 2 wires to the laser enable/disable switch line to connect to the relay. Be forewarned that while it is very easy to solder wires to the female connector, the same is not true of the male side. Nevertheless, they make a nice looking "port" on the device.

With the Arduino bolted down you can now attach the Seeed Studio Relay Shield. With the Relay Shield in place, connect wires from your connector or Laser Power Supply enable/disable switch to the relay. The current sketch will allow you to use any one of the 4 relays, but you may as well just use J1's Normally Open and Common connection points.

Step 4: Build the Monitor Shield...

The "monitor shield" utilizes the A000082 Arduino Shield Proto PCB as a connection point for all of the remaining electronics (other than the relay shield). It will piggyback on the relay shield.

Follow the schematic for details on connections. You will notice that I used an oversized DIP socket to hold the NTE3040. This is one I had on hand, and I prefer to use these sockets over soldering chips directly to the board.

Though it is not necessary I also soldered a reset switch to the proto board to simplify testing.


With the DIP socket in place, run a wire from it's Pin 1 to where you will connect the Laser PSU's +5 volt line.

Then solder a 380 ohm resistor to the board to connect Pin 2 of the NTE3040 to where you will connect the Ground line from the Laser's PSU.

On the other side of the DIP socket, solder a wire from pin 5 to Arduino PIN 8. While doing this you will want to also solder a 10k ohm resistor to PIN 8 on one side, and to +5 volts on the other side of the resistor.

Next solder a jumper wire to connect Pin 4 of the NTE3040 to the ground line on the Proto PCB.

Piezo Buzzer

Solder the red (positive) wire from the Piezo Buzzer to the A000082 Proto PCB connecting it to Arduino PIN 10. Then solder the black (ground) wire to one of the ground lines on the Proto PCB.

Water Flow Sensor

Solder the red (positive +5v) wire from the water flow sensor to the A000082 Proto PCB connecting it to one of the +5 volt lines. Connect the black (ground) wire from the water flow sensor to one of the ground lines on the board. Finally, connect the yellow (signal) wire from the water flow sensor to the Proto PCB connecting it to PIN 2 of the Arduino.

With everything connected the board should look something like this:

Here the board is shown before the external connections were added:

Notice that the white and black wires going to the external connector are for the (white) +5v and (black) ground lines from the Laser PSU's +5 supply. The red and green wires connect to Relay J1 (COM and NO) to enable/disable power to the laser tube.

Next attach the new shield to the top of the relay shield as shown in the photos above.

Step 5: Program the Arudino...

You can now close up the box at any point but it may be easier to debug problems if you first program the Arduino. The sketch is attached as CO2WaterFlowMonitor.ino.

Open CO2WaterFlowMonitor.ino using the Arduino compiler, attach the USB port to the computer, and press the Upload button in the Arduino software. That is all there is to it -- unless you want to learn how it works...


First, we want to keep things as simple as possible. We will show what each pin does at the top of the sketch using a macro. The EEPROM.h header is used for our EEPROM setting of the flow-rate. This means that the power can go on and off but the setting remains.

Reading the Water Flow Sensor

We will use the Arduino's interrupt 0 to count the pulses from the water flow sensor. To do that we first setup a couple of global variables, and create a very basic Interrupt Service Routine (ISR) that bumps g_uiFlowDetect every time a pulse from the water flow sensor is detected.


Every Arduino sketch has a setup. In ours, we will...

-Set each pin to the proper mode...

-Setup Serial Output which the Windows Software will read, and send our first message to it...

-Turn off (COM to NC1) each relay and then silence the buzzer (line 55)...

-Attach our ISR (Flow Detect) to Interrupt 0 (PIN 2), and temporarily disable interrupts...

-Read the alarm threshold from the EEPROM, or "burn it" in if one was not there...

Next the main loop will be used to detect and output (to the USB serial port) the flow rate, set and clear the alarm when the rate falls below the threshold and accept input to change the threshold from the Windows software. To accomplish this the following helper functions are introduced...


This function is used to store the new threshold value in the EEPROM storage and to send a message indicating the change to the Windows Software.

Communicating with the Windows Software via WriteOutput and Detecting Laser Power

This function will send the status updates to the Windows Software. The format is short and simple. If the line starts with an asterisk it means that any alarm will be silenced because the laser power is disabled. After the laser power detection by reading PIN 8, we send the current flow rate a slash, and then the current alarm threshold. These numbers are in quarter-seconds.

The Main Loop - Capturing the Flow Rate while waiting for input from the Windows Software

At the top of the main loop the flow rate variable is cleared (while interrupts are disabled). Interrupts are then enabled and a 1/4 second wait is entered for serial input from the USB port (sent by the Windows Software). During that 250 millisecond "wait", all pulses from the water flow rate sensor will increment the flow rate counter by invoking the ISR.

On line 117 we check to see if the Windows Software sent a new threshold (a single byte value with the new threshold). If it did, bRC will be set to 1 (for one byte). In that case UpdateThreshold() is invoked and the loop is restarted. This keeps us from signaling an alarm due to the interrupted 250 ms. wait.

Setting and Clearing the Alarm

In the final half of the main loop the threshold is compared to the flow rate and when it falls below the rate for that 1/4 second, the alarm is sounded and the relay is switched...

On line 131 we test to see if the flow rate is less than the threshold. If it is we check to see if the laser system is power on, by testing ENABLE_ALARM_PIN (line 134). If the alarm is enabled, the buzzer is sounded (line 136), otherwise (line 140) it is silenced.

Lines 142 through 145 cause each relay to connect COM to NC1 (normally closed). This disable the laser. One line 146 the status is sent to the Windows Software and then on line 147 the loop recycles.

When the alarm is not triggered the code falls through to line 149 where the buzzer is silenced and each relay is switched to connect COM to NO (Normally Open), enabling the laser. Finally on line 154 the loop ends by sending the status to the Windows Software.

Step 6: The Windows Software...

The Windows Software was written using Microsoft Visual Studio 2015 Update 1. It is a native C++ MFC Dialog-based application created using the MFC dialog wizard. The UI - the dialog was created using the resource editor. The body of the code is generated by the Wizard. I will highlight only the parts that demonstrate communication with the Arduino. The remainder of the code is available in github here:

Finding the Arduino Serial "COM" port

Now that we have largely moved beyond the days of MODEMs, it is not common to have many COM ports in use on Windows PC. It is however possible that you may have USB devices like your Arduino that use a COM port as a serial interface. We can enumerate all of the available COM ports on the PC easily by using a for-loop which invokes QueryDosDevice. Once we have a list of COM ports we can present the list to the user allowing them to select the one that is their water flow monitor. If there is only one item in the list we can automatically open it. To start then, we add a container to the CCO2WaterFlowDlg class. This is on line 50 of CO2WaterFlowDlg.h, as a std::map of two strings. The first string (the key) is the COM port that we will display and open. The other string is the DOS device name, which we keep mostly for diagnostics.

The method declared on line 54 is used to populate the container. It is implemented in CO2WaterFlowDlg.cpp, lines 70 - 78:

BuildCOMPortMap() is called when the application starts up and an instance of CCO2WaterFlowDlg is constructed, as you can see on line 94 above. At this point, any COM devices present on the system will be in our container.

When the dialog first initializes it will check in OnInitDialog() to see if there are COM ports available. If not, a prompt will be displayed to the user asking them to plug-in the flow rate monitor. This message will repeat until the user clicks Cancel, or until a COM device is present. See the loop on lines 146-155 below:

The list of available devices will be displayed in the drop-down list (ComboBox). This is done when RefreshCOMList() is called on line 161 above. The code for this is fairly standard MFC code for populating a ComboBox. It is shown below:

Lines 296 through 301 clear entries in the combo box, while lines 304 through 309 add each item from the container to the combo box. Another container, m_COMSelectionMap is maintained which associates the Combo Box entry identifier with the COM port. When the user clicks on Open, this will be used to pull out the name of the COM port to open.

On line 310, the last item added to the combo box is selected.

If only one item is contained in the list (line 312), that item is automatically opened by simulating a user-click on the Open button (line 316).

Opening the COM port

When the user clicks the Open button, or when OnBnClickedButtonOpen() is invoked by RefreshCOMList() the currently selected Combo Box item is used to obtain the name of the COM port (lines 221 and 222 below):

On line 223 at attempt is made to open the serial port. If it fails the user will be prompted and will be allowed to select a new item. If however we successfully open the COM port, lines 230-232 will disable the Open and Scan Ports buttons and will enable the Set Threshold button. Once the COM port has been opened, a timer is set to fire every 1/8 of a second. The timer handler uses m_SerialPort to read the latest update (if available) from the Water Flow Monitor and it updates the status displayed on the dialog.

The ScanPorts button does nothing more than re-invoke BuildCOMPortMap() and RefreshCOMList().

The ArduinoSerial class

The ArduinoSerial class handles everything associated with opening, closing, reading, and writing to the Arduino through the serial port. It is designed specifically for the Water Flow Monitor, but can be easily adapted for alternative needs. It is defined in ArdSerial.h as follows:

Note that the class is self-initializing because we don't want to try to open the COM port until we know what to select. When Close() is called the COM port will be closed as m_hSerial is passed to CloseHandle(), and m_bConnected will be set back to false. Because this is an RAII class, it will automatically clean-up (e.g. close the handle) when it's destructor is invoked -- this is done by calling Close().

We have previously seen where the Open() method is invoked in CCO2WaterFlowDlg.cpp's OnBnClickedButtonOpen() [line 223 of CO2WaterFlowDlg.cpp]. You will notice that m_SerialPort is an instance of ArduinoSerial as declared on line 52 of CO2WaterFlowDlg.h:

CreateFile and Setting COM Parameters

Opening the device, once we know the COM port is simply a matter of calling CreateFile followed by calls to GetCOMState and SetCOMState which set the baud rate and other serial parameters. Lines 26 through 50 below show the call to CreateFile and the user-prompt on failure:

With the port open we obtain the COM state and adjust it to use a 9600 baud rate (feel free to change this if you'd like, but match the change in your Arduino sketch!) All of the remaining setup of the COM port is shown below. Though it looks like a lot of code, there is not much to explain:

Notice the call to PurgeComm on line 87 and the Sleep on line 89. Everytime you connect to the serial port on the Arduino the device resets.

Reading and Writing to/from the Arduino

Because we have purposefully kept the communication to and from the Arduino very simple, the ArduinoSerial read and write methods are limited to reading a full, null terminated line of text, and writing a single byte. You may have noticed the declaration of our read buffer on line 45 of ArdSerial.h as well as the private member function ReadAvailable() declared on line 47. ReadAvailable is used to append new data from the serial port to our buffer. It looks like this:

(Note the comment on line 124 is a carry-over from the Arduino Serial example here: On line 122 a temporary buffer is allocated the size of which matches the readable bytes reported by the call to ClearCommError on line 114. On line 126 those bytes are read into our temporary buffer (vArray). And on lines 131 through 134 those bytes are appended to m_vBuffer.

ReadAvailable() is invoked each time ReadNullZString() is called, and ReadNullZString just transfers the first null-terminated string in the buffer to the output strRead:

Writing to the Arduino is very simple -- and this is in large part because we only ever need to send a single byte, used to update the threshold value:

Tying it back to the User Interface

The timer that was previously mentioned, which fires every 1/8 of a second will call m_Serial.ReadNullZString(). Whenever a string is available it will then parse the string and update the User Interface. This is shown in part below. (Browse the github code to see the remainder of the code)

One important thing to note... The "flow rate" displayed is based on the specs for the flow rate sensor. If you use a different flow rate sensor, adjust the math starting on line 275 according to the sensor's specifications.

Setting the Threshold

The last thing to cover is how we send the new threshold to the Arduino. You've probably already guessed based on the above code, that we simply call m_SerialPort.WriteByte() with the new threshold. You would be correct. The code is very simple as you can see below:

On line 329, the threshold value is read from the UI, and converted to a BYTE on line 330, and sent to the Arduino on line 331. You will recall from the Arduino sketch that this will interrupt the main loop, adjust the alarm threshold, and then restart the main loop.

Step 7: Final Notes...

Binary Files for the Windows Software

A few final notes remain. If you are uninterested in building the Windows Software yourself, the github repository has compiled version available:

64-bit Windows Flow Rate Monitor

32-bit Windows Flow Rate Monitor

Beyond that, I also hope that the software details in this article will help you to interface your creations with the PC!

Full Spectrum Laser Contest 2016

Participated in the
Full Spectrum Laser Contest 2016

Be the First to Share


    • Pocket-Sized Speed Challenge

      Pocket-Sized Speed Challenge
    • Audio Challenge 2020

      Audio Challenge 2020
    • Maps Challenge

      Maps Challenge

    5 Discussions


    4 years ago

    Thank you for this. This was actually on my to-do to create this month. You saved me a lot off work!


    Reply 4 years ago

    Great, best wish with it. If you hit snags let me know and I'll help if I can.



    Reply 4 years ago


    Congratulations on a great how to build it article... exactly what I was looking for.

    Moved from East to West coast & had to leave many things behind so I am in the process of ordering stuff.

    I have received nearly all parts for the chiller so far and have some for flow rate. Have you given any more thought to designing a flow sensor box built as you did for the chiller controller.

    Expect to start the project in a month or two.

    BTW Do you think a small tablet running Windows 10 or similar could be subbed for the computer displaying flow rate... ?

    Once again ... Kudos on the project ...

    Will check-in later as I progress ....

    Thnaks !


    Reply 4 years ago

    Doug - Thanks! I've not been back to visit the box for it yet because what I have works fine. I do like the laser cut boxes, but I need to find a way to make them sealable with screws/bolts so that they can still be opened. For the Thermostat on the Water Chiller I just left the top on with friction fit, and that works but I'd like a better solution. So, maybe someday... Too many projects -- if I could do this kind of thing full time and not have to work for a living, I'd definitely work on it. ;)

    A small tablet running Windows 10 will work just fine, as long as it has a USB port. You could most likely (in other words, I have not tested it), use a Raspberry Pi running Windows 10. I just finished a Windows 10 IoT (Raspberry Pi) project that I'll document at some point in the future. It features a touch screen. Those two parts combined - the Raspberry Pi + the Touch Screen were less than $60 IIRC. The issue with Windows 10 IoT is that I don't think it will run a Desktop App, so the little application I wrote for Windows would have to be ported to a Windows 10 Universal App. That would be easy to do -- it just takes time. :)