The personal website of Scott W Harden
August 8th, 2016

DIY ECG with 1 op-amp

⚠️ Check out my newer ECG design:

I made surprisingly good ECG from a single op-amp and 5 resistors! An ECG (electrocardiograph, sometimes called EKG) is a graph of the electrical potential your heart produces as it beats. Seven years ago I posted DIY ECG Machine on the Cheap which showed a discernible ECG I obtained using an op-amp, two resistors, and a capacitor outputting to a PC sound card's microphone input. It didn't work well, but the fact that it worked at all was impressive! It has been one of the most popular posts of my website ever since, and I get 1-2 emails a month from people trying to recreate these results (some of them are during the last week of a college design course and sound pretty desperate). Sometimes people get good results with that old circuit, but more often than not the output isn't what people expected. I decided to revisit this project (with more patience and experience under my belt) and see if I could improve it. My goal was not to create the highest quality ECG machine I could, but rather to create the simplest one I could with emphasis on predictable and reproducible results. The finished project is a blend of improved hardware and custom cross-platform open-source software (which runs on Windows, Linux, and MacOS), and an impressively good ECG considering the circuit is so simple and runs on a breadboard! Furthermore, the schematics and custom software are all open-sourced on my github!

Here's a video demonstrating how the output is shown in real time with custom Python software. The video is quite long, but you can see the device in action immediately, so even if you only watch the first few seconds you will see this circuit in action with the custom software. In short, the amplifier circuit (described in detail below) outputs to the computer's microphone and a Python script I wrote analyzes the audio data, performs low-pass filtering, and graphs the output in real time. The result is a live electrocardiograph!

ECG Circuit

The circuit is simple, but a lot of time and thought and experimentation went into it. I settled on this design because it produced the best and most reliable results, and it has a few nuances which might not be obvious at first. Although I discuss it in detail in the video, here are the highlights:

  • The output goes to the microphone jack of your computer.

  • There's nothing special about the op-amp I used (LM741). A single unit of an LM324 (or any general purpose op-amp) should work just as well.

  • Resistor values were chosen because I had them on hand. You can probably change them a lot as long as they're in the same ballpark of the values shown here. Just make sure R1 and R2 are matched, and R3 should be at least 10MOhm.

  • Do not use a bench power supply! "BAT+" and "BAT-" are the leads of a single 9V battery.

  • Note that the leg electrode is ground (same ground as the computer's microphone ground)

  • R5 and R4 form a traditional voltage divider like you'd expect for an op-amp with a gain of about 50.

    • You'd expect R4 to connect to ground, but since your body is grounded, chest 2 is essentially the same

    • R3 must be extremely high value, but it pulls your body potential near the optimal input voltage for amplification by the op-amp.

    • R1 and R2 split the 9V battery's voltage in half and center it at ground, creating -4.5V and +4.5V.

  • altogether, your body stays grounded, and the op-amp becomes powered by -4.5V and +4.5V, and your body is conveniently near the middle and ready to have small signals from CHEST1 amplified. Amplification is with respect to CHEST2 (roughly ground), rather than actual ground, so that a lot of noise (with respect to ground) is eliminated.

For those of you who would rather see a picture than a schematic, here's a diagram of how to assemble it graphically. This should be very easy to reproduce. Although breadboards are typically not recommended for small signal amplification projects, there is so much noise already in these signals that it doesn't really matter much either way. Check out how good the signals look in my video, and consider that I use a breadboard the entire time.

The most comfortable electrodes I used were made for muscle simulators. A friend of mine showed me some muscle stimulator pads he got for a back pain relief device he uses. As soon as I saw those pads, I immediately thought they would be perfect for building an ECG! They're a little bit expensive, but very comfortable, reusable, last a long time, and produce brilliant results. They also have 3.5 mm (headphone jack) connectors which is perfect for DIY projects. On Amazon.com you can get 16 pads for $11 with free shipping. I decided not to include links, because sometimes the pads and cords are sold separately, and sometimes they have barrel connectors and sometimes they have snap connectors. Just get any adhesive reusable electrodes intended for transcutaneous electrical nerve stimulation (TENS) that you can find! They should all work fine.

Pennies as Electrodes

You can make your own electrodes for $0.03! Okay that's a terrible joke, but it's true. I made not-awful electrodes by soldering wires to copper pennies, adding strength by super-gluing the wire to the penny, and using electrical tape to attach them to my chest. Unless you want a tattoo of an old guy's face on your torso, wait until they cool sufficiently after soldering before proceeding to the adhesion step. I suspect that super gluing the penny to your chest would also work, but please do not do this. Ironically, because the adhesive pads of the TENS electrodes wear away over time, the penny solution is probably "more reusable" than the commercial electrode option.

This ECG was recorded using pennies as electrodes:

Notes on filtering: Why didn't I just use a hardware low-pass filter?

  1. It would have required extra components, which goes against the theme of this project
  2. It would require specific value components, which is also undesirable for a junkbox project
  3. I'm partial to the Chebyshev filter, but getting an extremely sharp roll-off a few Hz shy of 50Hz would take multiple poles (of closely matched passive components) and not be as trivial as it sounds.

Notes on software: This a really cool use of Python! I lean on some of my favorite packages numpy, scipy, matplotlib, pyqrgraph, and PyQt4. I've recently made posts describing how to perform real-time data graphing in Python using these libraries, so I won't go into that here. If you're interested, check out my real-time audio monitor, notes on using PlotWidget, and notes on using MatPlotLib widget. I tried using PyInstaller to package this project into a single .EXE for all my windows readers who might want to recreate this project, but the resulting EXE was over 160MB! That's crazy! It makes sense considering packagers like PyInstaller and Py2EXE work by building your entire python interpreter and all imported libraries. With all those fun libraries I listed above, it's no wonder it came out so huge. It may be convenient for local quick-fixes, but not a good way to distribute code over the internet. To use this software, just run it in Python. It was tested to work with out-of-the-box WinPython-64bit-3.5.2.1 (not the Qt5 version), so if you want to run it yourself start there.

Notes on safety. How safe is this project? I'm conflicted on this subject. I want to be as conservative as I can (leaning on the side of caution), but I also want to be as realistic as possible. I'm going to play it safe and say "this may not be safe, so don't build or use it". As an exercise, let's consider the pros and cons:

  • PROS:

    • It's powered from a 9V battery which is safer than a bench power supply (but see the matching con).

    • The only connections to your body are:

      • leg - ground. you ground yourself all the time. using a wrist grounding strap is the same thing.
      • chest 1 - extremely high impedance. You're attaching your chest to the high impedance input of an op-amp (which I feel fine with), and also to a floating battery through a 10MOhm resistor (which also I feel fine with)
      • chest 2 - raises an eyebrow. In addition to a high impedance input, you're connected to an op-amp through a 100k resistor. Even if the op-amp were putting out a full 4.5V, that's 0.045mA (which doesn't concern me a whole lot).
    • I don't know where to stick this, but I wonder what type of voltages / currents TENS actually provide.

  • CONS / WARNINGS:

    • It's powered from a 9V battery. So are many stun guns.
    • If the op-amp oscillates, oscillations may enter your body. Personally I feel this may be the most concerning issue.
    • Small currents can kill. I found a curiously colored website that describes this. It seems like the most dangerous potential effect is induction of cardiac fibrillation, which can occur around 100mA.

Improving safety through optical isolation: The safety of this device may be improved (albeit with increased complexity) through the implementation of opto-isolators. I may consider a follow-up post demonstrating how I do this. Unlike digital signals which I've optically isolated before, I've never personally isolated analog signals. Although I'm sure there are fully analog means to do this, I suspect I'd accomplish it by turning it into a digital signal (with a voltage-to-frequency converter), pulsing the output across the optoisolator, and turning it back into voltage with a frequency-to-voltage converter or perhaps even a passive low-pass filter. Analog Devices has a good write-up about optical isolation techniques.

Do you have comments regarding the safety of this device? Write your thoughts concisely and send them to me in an email! I'd be happy to share your knowledge with everyone by posting it here.

Did you build this or a device similar to it? Send me some pictures! I'll post them here.

Source code and project files: https://github.com/swharden/diyECG-1opAmp/

LEGAL: This website is for educational purposes only. Do not build or use any electrical devices shown. Attaching non-compliant electronic devices to your body may be dangerous. Consult a physician regarding proper usage of medical equipment.

Markdown source code last modified on January 18th, 2021
---
title: DIY ECG with 1 op-amp
date: 2016-08-08 01:49:24
tags: diyECG, python, circuit
---

# DIY ECG with 1 op-amp

> **⚠️ Check out my newer ECG design:** 
* [**Sound Card ECG with AD8232**](https://swharden.com/blog/2019-03-15-sound-card-ecg-with-ad8232/)

__I made surprisingly good ECG from a single op-amp and 5 resistors! __An ECG (electrocardiograph, sometimes called EKG) is a graph of the electrical potential your heart produces as it beats. Seven years ago I posted _[DIY ECG Machine on the Cheap](https://www.swharden.com/wp/2009-08-14-diy-ecg-machine-on-the-cheap/)_ which showed a discernible ECG I obtained using an op-amp, two resistors, and a capacitor outputting to a PC sound card's microphone input. It didn't work well, but the fact that it worked at all was impressive! It has been one of the most popular posts of my website ever since, and I get 1-2 emails a month from people trying to recreate these results (some of them are during the last week of a college design course and sound pretty desperate). Sometimes people get good results with that old circuit, but more often than not the output isn't what people expected. I decided to revisit this project (with more patience and experience under my belt) and see if I could improve it. My goal was not to create the highest quality ECG machine I could, but rather to create the _simplest_ one I could with emphasis on predictable and reproducible results. The finished project is a blend of improved hardware and custom cross-platform open-source software (which runs on Windows, Linux, and MacOS), and an impressively good ECG considering the circuit is so simple and runs on a breadboard! Furthermore, the schematics and custom software are all [open-sourced on my github](https://github.com/swharden/diyECG-1opAmp/)!

![](https://www.youtube.com/embed/AfirWls9Sys)

__Here's a video demonstrating how the output is shown in real time with custom Python software.__ The video is quite long, but you can see the device in action immediately, so even if you only watch the first few seconds you will see this circuit in action with the custom software. In short, the amplifier circuit (described in detail below) outputs to the computer's microphone and a Python script I wrote analyzes the audio data, performs low-pass filtering, and graphs the output in real time. The result is a live electrocardiograph!

<div class="text-center">

[![](ECG_1470609065_thumb.jpg)](ECG_1470609065.png)

</div>

### ECG Circuit

<div class="text-center">

[![](circuit_thumb.jpg)](circuit.jpg)

</div>

__The circuit is simple, but a lot of time and thought and experimentation went into it.__ I settled on this design because it produced the best and most reliable results, and it has a few nuances which might not be obvious at first. Although I discuss it in detail in the video, here are the highlights:

*   The output goes to the microphone jack of your computer.
*   There's nothing special about the op-amp I used ([LM741](http://www.ti.com/lit/ds/symlink/lm741.pdf)). A single unit of an [LM324](http://www.ti.com.cn/cn/lit/ds/symlink/lm2902-n.pdf) (or any general purpose op-amp) should work just as well.
*   Resistor values were chosen because I had them on hand. You can probably change them a lot as long as they're in the same ballpark of the values shown here. Just make sure R1 and R2 are matched, and R3 should be at least 10MOhm.
*   <span style="color: #ff0000;">Do not use a bench power supply!</span> "BAT+" and "BAT-" are the leads of a single 9V battery.
*   Note that the leg electrode is ground (same ground as the computer's microphone ground)
*   R5 and R4 form a traditional voltage divider like you'd expect for an op-amp with a gain of about 50.

    *   You'd expect R4 to connect to ground, but since your body is grounded, chest 2 is essentially the same

    *   R3 must be extremely high value, but it pulls your body potential near the optimal input voltage for amplification by the op-amp.

    *   R1 and R2 split the 9V battery's voltage in half and center it at ground, creating -4.5V and +4.5V.

*   altogether, your body stays grounded, and the op-amp becomes powered by -4.5V and +4.5V, and your body is conveniently near the middle and ready to have small signals from CHEST1 amplified. Amplification is with respect to CHEST2 (roughly ground), rather than actual ground, so that a lot of noise (with respect to ground) is eliminated.

<div class="text-center img-border">

[![](IMG_7574_thumb.jpg)](IMG_7574.jpg)

</div>

__For those of you who would rather see a picture than a schematic__, here's a diagram of how to assemble it graphically. This should be very easy to reproduce. Although breadboards are typically not recommended for small signal amplification projects, there is so much noise already in these signals that it doesn't really matter much either way. Check out how good the signals look in my video, and consider that I use a breadboard the entire time.

<div class="text-center">

[![](design_thumb.jpg)](design.jpg)

</div>

__The most comfortable electrodes I used were made for muscle simulators.__ A friend of mine showed me some muscle stimulator pads he got for a back pain relief device he uses. As soon as I saw those pads, I immediately thought they would be perfect for building an ECG! They're a little bit expensive, but very comfortable, reusable, last a long time, and produce brilliant results. They also have 3.5 mm (headphone jack) connectors which is perfect for DIY projects. On Amazon.com you can get 16 pads for $11 with free shipping. I decided not to include links, because sometimes the pads and cords are sold separately, and sometimes they have barrel connectors and sometimes they have snap connectors. Just get any adhesive reusable electrodes intended for [transcutaneous electrical nerve stimulation (TENS)](https://en.wikipedia.org/wiki/Transcutaneous_electrical_nerve_stimulation) that you can find! They should all work fine.

<div class="text-center img-border">

[![](IMG_7576_thumb.jpg)](IMG_7576.jpg)

</div>

### Pennies as Electrodes

__You can make your own electrodes for $0.03!__ Okay that's a terrible joke, but it's true. I made not-awful electrodes by soldering wires to copper pennies, adding strength by super-gluing the wire to the penny, and using electrical tape to attach them to my chest. Unless you want a tattoo of an old guy's face on your torso, wait until they cool sufficiently after soldering before proceeding to the adhesion step. I suspect that super gluing the penny to your chest would also work, but please do not do this. Ironically, because the adhesive pads of the TENS electrodes wear away over time, the penny solution is probably "more reusable" than the commercial electrode option.

<div class="text-center img-border img-small">

[![](IMG_7527_thumb.jpg)](IMG_7527.jpg)
[![](IMG_7570-1_thumb.jpg)](IMG_7570-1.jpg)

</div>

This ECG was recorded using pennies as electrodes:

<div class="text-center">

[![](ECG_1470611901_thumb.jpg)](ECG_1470611901.png)

</div>

__Notes on filtering:__ Why didn't I just use a [hardware low-pass filter](https://en.wikipedia.org/wiki/Low-pass_filter)?

1.   It would have required extra components, which goes against the theme of this project
2.   It would require _specific value_ components, which is also undesirable for a junkbox project
3.   I'm partial to the [Chebyshev filter](https://en.wikipedia.org/wiki/Chebyshev_filter), but getting an extremely sharp roll-off a few Hz shy of 50Hz would take multiple poles (of closely matched passive components) and [not be as trivial as it sounds](http://www.analog.com/library/analogDialogue/archives/43-09/EDCh%208%20filter.pdf?doc=ADA4661-2.pdf).

__Notes on software:__ This a really cool use of Python! I lean on some of my favorite packages [numpy](http://www.numpy.org/), [scipy](https://www.scipy.org/), [matplotlib](http://matplotlib.org/), [pyqrgraph](http://www.pyqtgraph.org/), and [PyQt4](https://wiki.python.org/moin/PyQt4). I've recently made posts describing how to perform real-time data graphing in Python using these libraries, so I won't go into that here. If you're interested, check out my [real-time audio monitor](https://www.swharden.com/wp/2016-07-31-real-time-audio-monitor-with-pyqt/), notes on using [PlotWidget](https://www.swharden.com/wp/2016-07-31-live-data-in-pyqt4-with-plotwidget/), and notes on using [MatPlotLib widget](https://www.swharden.com/wp/2016-07-30-live-data-in-pyqt4-with-matplotlibwidget/). I tried using [PyInstaller](http://www.pyinstaller.org/) to package this project into a single .EXE for all my windows readers who might want to recreate this project, but the resulting EXE was over 160MB! That's crazy! It makes sense considering packagers like PyInstaller and Py2EXE work by building your entire python interpreter and all imported libraries. With all those fun libraries I listed above, it's no wonder it came out so huge. It may be convenient for local quick-fixes, but not a good way to distribute code over the internet. To use this software, just run it in Python. It was tested to work with out-of-the-box [WinPython-64bit-3.5.2.1](https://sourceforge.net/projects/winpython/files/) (not the Qt5 version), so if you want to run it yourself start there.

__Notes on safety.__ How safe is this project? I'm conflicted on this subject. I want to be as conservative as I can (leaning on the side of caution), but I also want to be as realistic as possible. I'm going to play it safe and say "this may not be safe, so don't build or use it". As an exercise, let's consider the pros and cons:

*   __PROS:__

    *   It's powered from a 9V battery which is safer than a bench power supply (but see the matching con).
    *   The only connections to your body are:

        *   leg - ground. you ground yourself all the time. using a wrist grounding strap is the same thing.
        *   chest 1 - extremely high impedance. You're attaching your chest to the high impedance input of an op-amp (which I feel fine with), and also to a floating battery through a 10MOhm resistor (which also I feel fine with)
        *   chest 2 - raises an eyebrow. In addition to a high impedance input, you're connected to an op-amp through a 100k resistor. Even if the op-amp were putting out a full 4.5V, that's 0.045mA (which doesn't concern me a whole lot).

    *   I don't know where to stick this, but I wonder what type of voltages / currents [TENS](https://en.wikipedia.org/wiki/Transcutaneous_electrical_nerve_stimulation) actually provide.

*   __CONS / WARNINGS:__

    *   It's powered from a 9V battery. So are many stun guns.
    *   If the op-amp oscillates, oscillations may enter your body. Personally I feel this may be the most concerning issue.
    *   Small currents can kill. I found a [curiously colored website](https://www.physics.ohio-state.edu/~p616/safety/fatal_current.html) that describes this. It seems like the most dangerous potential effect is induction of cardiac fibrillation, which can occur around 100mA.

__Improving safety through optical isolation:__ The safety of this device may be improved (albeit with increased complexity) through the implementation of [opto-isolators](https://en.wikipedia.org/wiki/Opto-isolator). I may consider a follow-up post demonstrating how I do this. Unlike digital signals which I've [optically isolated before](https://www.swharden.com/wp/2016-07-28-opto-isolated-laser-controller-build/), I've never personally isolated analog signals. Although I'm sure there are fully analog means to do this, I suspect I'd accomplish it by turning it into a digital signal (with a voltage-to-frequency converter), pulsing the output across the optoisolator, and turning it back into voltage with a frequency-to-voltage converter or perhaps even a passive low-pass filter. Analog Devices has a good write-up about [optical isolation techniques](http://www.analog.com/media/en/training-seminars/tutorials/MT-071.pdf).

__Do you have comments regarding the safety of this device?__ Write your thoughts concisely and send them to me in an email! I'd be happy to share your knowledge with everyone by posting it here.

__Did you build this or a device similar to it?__ Send me some pictures! I'll post them here.

__Source code and project files:__ <https://github.com/swharden/diyECG-1opAmp/>

___LEGAL__: This website is for educational purposes only. Do not build or use any electrical devices shown. Attaching non-compliant electronic devices to your body may be dangerous. Consult a physician regarding proper usage of medical equipment._
July 31st, 2016

Python Real-time Audio Frequency Monitor

A new project I'm working on requires real-time analysis of soundcard input data, and I made a minimal case example of how to do this in a cross-platform way using python 3, numpy, and PyQt. Previous posts compared performance of the matplotlib widget vs PyQtGraph plotwidget and I've been working with PyQtGraph ever since. For static figures matplotlib is wonderful, but for fast responsive applications I'm leaning toward PyQtGraph. The full source for this project is on a github page, but here's a summary of the project.

I made the UI with QT Designer. The graphs are QGraphicsView widgets promoted to a pyqtgraph PlotWidget. I describe how to do this in my previous post. Here's the content of the primary program:

from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pyqtgraph
import SWHear

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        pyqtgraph.setConfigOption('background', 'w') #before loading widget
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.grFFT.plotItem.showGrid(True, True, 0.7)
        self.grPCM.plotItem.showGrid(True, True, 0.7)
        self.maxFFT=0
        self.maxPCM=0
        self.ear = SWHear.SWHear()
        self.ear.stream_start()

    def update(self):
        if not self.ear.data is None and not self.ear.fft is None:
            pcmMax=np.max(np.abs(self.ear.data))
            if pcmMax>self.maxPCM:
                self.maxPCM=pcmMax
                self.grPCM.plotItem.setRange(yRange=[-pcmMax,pcmMax])
            if np.max(self.ear.fft)>self.maxFFT:
                self.maxFFT=np.max(np.abs(self.ear.fft))
                self.grFFT.plotItem.setRange(yRange=[0,self.maxFFT])
            self.pbLevel.setValue(1000*pcmMax/self.maxPCM)
            pen=pyqtgraph.mkPen(color='b')
            self.grPCM.plot(self.ear.datax,self.ear.data,
                            pen=pen,clear=True)
            pen=pyqtgraph.mkPen(color='r')
            self.grFFT.plot(self.ear.fftx[:500],self.ear.fft[:500],
                            pen=pen,clear=True)
        QtCore.QTimer.singleShot(1, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")

This project uses a gutted version of the SWHEar class which I still haven't released on githib yet. It will likely have its own project folder. For now, take this project with a grain of salt. The primary advantage of this class is that it makes it easy to use PyAudio to automatically detect input sound cards, channels, and sample rates which are likely to succeed without requiring the user to enter any information.

All files used for this project are in a GitHub folder

Audio Visualizer Screenlet

2016-09-05: Okko adapted this project into a screenlet (cross platform) which also includes an installer for Windows: https://github.com/ninlith/audio-visualizer-screenlet

Markdown source code last modified on January 18th, 2021
---
title: Python Real-time Audio Frequency Monitor
date: 2016-07-31 15:21:11
tags: python, old
---

# Python Real-time Audio Frequency Monitor

__A new project I'm working on requires real-time analysis of soundcard input data, and I made a minimal case example of how to do this in a cross-platform way using python 3, numpy, and PyQt.__ Previous posts compared performance of the [matplotlib widget](https://www.swharden.com/wp/2016-07-30-live-data-in-pyqt4-with-matplotlibwidget/) vs [PyQtGraph plotwidget](https://www.swharden.com/wp/2016-07-31-live-data-in-pyqt4-with-plotwidget/) and I've been working with [PyQtGraph](http://www.pyqtgraph.org/) ever since. For static figures matplotlib is wonderful, but for fast responsive applications I'm leaning toward PyQtGraph. The [full source for this project is on a github page](https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-37_qt_audio_monitor), but here's a summary of the project.

<div class="text-center img-border img-small">

![](demo-1.gif)

</div>

![](https://www.youtube.com/embed/lDS9rI0o6mM)

__I made the UI with QT Designer.__ The graphs are _QGraphicsView_ widgets promoted to a pyqtgraph_ PlotWidget_. I describe how to do this [in my previous post](https://www.swharden.com/wp/2016-07-31-live-data-in-pyqt4-with-plotwidget/). Here's the content of the primary program:

```python
from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pyqtgraph
import SWHear

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        pyqtgraph.setConfigOption('background', 'w') #before loading widget
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.grFFT.plotItem.showGrid(True, True, 0.7)
        self.grPCM.plotItem.showGrid(True, True, 0.7)
        self.maxFFT=0
        self.maxPCM=0
        self.ear = SWHear.SWHear()
        self.ear.stream_start()

    def update(self):
        if not self.ear.data is None and not self.ear.fft is None:
            pcmMax=np.max(np.abs(self.ear.data))
            if pcmMax>self.maxPCM:
                self.maxPCM=pcmMax
                self.grPCM.plotItem.setRange(yRange=[-pcmMax,pcmMax])
            if np.max(self.ear.fft)>self.maxFFT:
                self.maxFFT=np.max(np.abs(self.ear.fft))
                self.grFFT.plotItem.setRange(yRange=[0,self.maxFFT])
            self.pbLevel.setValue(1000*pcmMax/self.maxPCM)
            pen=pyqtgraph.mkPen(color='b')
            self.grPCM.plot(self.ear.datax,self.ear.data,
                            pen=pen,clear=True)
            pen=pyqtgraph.mkPen(color='r')
            self.grFFT.plot(self.ear.fftx[:500],self.ear.fft[:500],
                            pen=pen,clear=True)
        QtCore.QTimer.singleShot(1, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")

```

This project uses a gutted version of the SWHEar class which I still haven't released on githib yet. It will likely have its own project folder. For now, take this project with a grain of salt. The primary advantage of this class is that it makes it easy to use PyAudio to automatically detect input sound cards, channels, and sample rates which are likely to succeed without requiring the user to enter any information.

All files used for this project are [in a GitHub folder](https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-37_qt_audio_monitor)

### Audio Visualizer Screenlet

2016-09-05: Okko adapted this project into a screenlet (cross platform) which also includes an installer for Windows: https://github.com/ninlith/audio-visualizer-screenlet

<div class="text-center img-border">

[![](widget_thumb.jpg)](widget.png)

</div>
July 31st, 2016

Live Data in PyQt4 with PlotWidget

After spending a day comparing performance of MatplotlibWidget with PlotWidget, when it comes to speed PlotWidget wins by a mile! Glance over my last post where I describe how to set up the window with QT Designer and convert the .ui file to a .py file. With only a few changes to the code, I swapped the matplotlib _MatplotlibWidget _with the pyqtgraph PlotWidget. I easily got a 20x increase in speed (frame rate), and I'm likely to favor PyQtGraph over matpltolib for python applications involving realtime display of data. Just like the previous example using matplotlib, this one uses the system time to control the phase and color of a sine wave in real time. You can grab the full code from my github folder.

When designing the GUI with QT Designer, add a QGraphicsView widget then assign it to become a PyQtGraph object. To do this, follow the steps found on the pyqtgraph docs page:

  • In Designer, create a QGraphicsView widget
  • Right-click on the QGraphicsView and select “Promote To...”
  • Set “Promoted class name” to “PlotWidget”
  • Under “Header file”, enter “pyqtgraph”
  • Click “Add”, then click “Promote”
  • apparently this only needs to be done once per project

In addition to faster frame rate, the PyQtGraph method is easy to interact with. Clicking and dragging scrolls the data, and right-clicking and dragging zooms on each axis. Here's the program code:

from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pylab
import time
import pyqtgraph

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        pyqtgraph.setConfigOption('background', 'w') #before loading widget
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.btnAdd.clicked.connect(self.update)
        self.grPlot.plotItem.showGrid(True, True, 0.7)

    def update(self):
        t1=time.clock()
        points=100 #number of data points
        X=np.arange(points)
        Y=np.sin(np.arange(points)/points*3*np.pi+time.time())
        C=pyqtgraph.hsvColor(time.time()/5%1,alpha=.5)
        pen=pyqtgraph.mkPen(color=C,width=10)
        self.grPlot.plot(X,Y,pen=pen,clear=True)
        print("update took %.02f ms"%((time.clock()-t1)*1000))
        if self.chkMore.isChecked():
            QtCore.QTimer.singleShot(1, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")

This project is available on GitHub: https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-31_qt_PyQtGraph_sine_scroll

Markdown source code last modified on January 18th, 2021
---
title: Live Data in PyQt4 with PlotWidget
date: 2016-07-31 07:28:28
tags: python, old
---

# Live Data in PyQt4 with PlotWidget

__After spending a day comparing performance of MatplotlibWidget with PlotWidget, when it comes to speed PlotWidget wins by a mile!__ Glance over my [last post](https://www.swharden.com/wp/2016-07-30-live-data-in-pyqt4-with-matplotlibwidget/) where I describe how to set up the window with QT Designer and convert the .ui file to a .py file. With only a few changes to the code, I swapped the matplotlib _MatplotlibWidget _with the pyqtgraph _PlotWidget_. I easily got a 20x increase in speed (frame rate), and I'm likely to favor [PyQtGraph](http://www.pyqtgraph.org/) over matpltolib for python applications involving realtime display of data. Just like the [previous example using matplotlib](https://www.swharden.com/wp/2016-07-30-live-data-in-pyqt4-with-matplotlibwidget/), this one uses the system time to control the phase and color of a sine wave in real time. You can grab the full code from [my github folder](https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-31_qt_PyQtGraph_sine_scroll).

<div class="text-center img-border img-small">

![](demo2.gif)
[![](demo2cmd_thumb.jpg)](demo2cmd.png)

</div>

__When designing the GUI with QT Designer, add a QGraphicsView widget then assign it to become a PyQtGraph object.__ To do this, follow the steps found on the [pyqtgraph docs page](http://www.pyqtgraph.org/documentation/how_to_use.html#embedding-widgets-inside-pyqt-applications):

* In Designer, create a QGraphicsView widget
* Right-click on the QGraphicsView and select “Promote To...”
* Set “Promoted class name” to “PlotWidget”
* Under “Header file”, enter “pyqtgraph”
* Click “Add”, then click “Promote”
* _apparently this only needs to be done once per project_

<div class="text-center img-border">

[![](promoted_thumb.jpg)](promoted.png)

</div>

__In addition to faster frame rate, the PyQtGraph method is easy to interact with.__ Clicking and dragging scrolls the data, and right-clicking and dragging zooms on each axis. Here's the program code:

```python
from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pylab
import time
import pyqtgraph

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        pyqtgraph.setConfigOption('background', 'w') #before loading widget
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.btnAdd.clicked.connect(self.update)
        self.grPlot.plotItem.showGrid(True, True, 0.7)

    def update(self):
        t1=time.clock()
        points=100 #number of data points
        X=np.arange(points)
        Y=np.sin(np.arange(points)/points*3*np.pi+time.time())
        C=pyqtgraph.hsvColor(time.time()/5%1,alpha=.5)
        pen=pyqtgraph.mkPen(color=C,width=10)
        self.grPlot.plot(X,Y,pen=pen,clear=True)
        print("update took %.02f ms"%((time.clock()-t1)*1000))
        if self.chkMore.isChecked():
            QtCore.QTimer.singleShot(1, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")
```

This project is available on GitHub: https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-31_qt_PyQtGraph_sine_scroll
July 30th, 2016

Live Data in PyQt4 with MatplotlibWidget

I keep getting involved in projects which require live data to be graphed in real time. Since most of my back-end is written in Python, it makes sense to have a Pythonic front-end. Cross-platform GUI programming in Python is frustratingly non-trivial, as there multiple window frameworks available (TK, GTK, and QT) and their respective graphical designers (torture, Glade, and QT Designer) and each has its own way of doing things. Add different ways to plot data in the mix (gnuplot, matplotlib, and custom widgets) and it can become a complicated mess. Different framework combinations favor different features (with unique speed / simplicity / elegance), so my goal is to slowly test out a few combinations most likely to work for my needs, and add my findings to a growing github repository. The first stab is using PyQt4 and matplotlib's widget (MatplotlibWidget). Rather than capture data from the sound card (my ultimate goal), I'm going to generate a sine wave whose phase and color is related to the system time. Matplotlib plotting is a bit slow, but the output is beautiful, and their framework is so robust. Here's the output of my first test showing the sine wave generated as well as the console output (showing that each call to the plotting function takes about 40 ms. At this rate, I can expect a maximum update rate of ~25 Hz.

Designing this project was easy, but it was surprisingly hard to figure out how to do this based on examples I found on the internet. This is part of why I wanted to place this example here. The downside of many internet examples is that they did not use Qt Designer to make the window, so their code to create a window and insert the MatplotlibWidget wasn't copy/paste compatible with my goals, and often more complex than I needed. Some internet examples did use Qt Designer to make the window, but left a frame empty which they later manually filled with a widget and attached to a matplotlib canvas. This is fine, but more complex than I need to get started.

First, I designed a GUI with Qt Designer. I dropped a MatplotlibWidget somewhere, and used its default name. I saved this file as ui_main.ui (which is an XML file, ready to be used for multiple programming languages).

Next, I converted the UI file into a .py file with a standalone python script that's an alternative to using pyuic from the command line. The script to do this is ui_convert.py and it calls PyQt4.uic.compileUi():

from PyQt4 import uic
fin = open('ui_main.ui','r')
fout = open('ui_main.py','w')
uic.compileUi(fin,fout,execute=False)
fin.close()
fout.close()

I then created my main program file which populates the matplotlib widget with data. I called it go.py and running it will launch the app. The code explains it all, and there's not much more to say! It produces the output at the top of this post.

from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pylab
import time

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.btnAdd.clicked.connect(self.update)
        self.matplotlibwidget.axes.hold(False) #clear on plot()

    def update(self):
        t1=time.time()
        points=100 #number of data points
        X=np.arange(points)
        Y=np.sin(np.arange(points)/points*3*np.pi+time.time())
        C=pylab.cm.jet(time.time()%10/10) # random color
        self.matplotlibwidget.axes.plot(X,Y,ms=100,color=C,lw=10,alpha=.8)
        self.matplotlibwidget.axes.grid()
        self.matplotlibwidget.axes.get_figure().tight_layout() # fill space
        self.matplotlibwidget.draw() # required to update the window
        print("update took %.02f ms"%((time.time()-t1)*1000))
        if self.chkMore.isChecked():
            QtCore.QTimer.singleShot(10, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")

This project is on GitHub: https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-30_qt_matplotlib_sine_scroll

Markdown source code last modified on January 18th, 2021
---
title: Live Data in PyQt4 with MatplotlibWidget
date: 2016-07-30 23:15:23
tags: python, old
---

# Live Data in PyQt4 with MatplotlibWidget

__I keep getting involved in projects which require live data to be graphed in real time.__ Since most of my back-end is written in Python, it makes sense to have a Pythonic front-end. Cross-platform GUI programming in Python is frustratingly non-trivial, as there multiple window frameworks available (TK, GTK, and QT) and their respective graphical designers ([torture](https://wiki.python.org/moin/Intro%20to%20programming%20with%20Python%20and%20Tkinter), [Glade](https://glade.gnome.org/), and [QT Designer](http://doc.qt.io/qt-4.8/designer-manual.html)) and each has its own way of doing things. Add different ways to plot data in the mix ([gnuplot](http://www.gnuplot.info/), [matplotlib](http://matplotlib.org/), and [custom widgets](http://qcustomplot.com/)) and it can become a complicated mess. Different framework combinations favor different features (with unique speed / simplicity / elegance), so my goal is to slowly test out a few combinations most likely to work for my needs, and add my findings to a [growing github repository](https://github.com/swharden/Python-GUI-examples). The first stab is using PyQt4 and matplotlib's widget (MatplotlibWidget). Rather than capture data from the sound card (my ultimate goal), I'm going to generate a sine wave whose phase and color is related to the system time. Matplotlib plotting is a bit slow, but the output is beautiful, and their framework is so robust. Here's the output of my first test showing the sine wave generated as well as the console output (showing that each call to the plotting function takes about 40 ms. At this rate, I can expect a maximum update rate of ~25 Hz.

<div class="text-center img-border img-small">

![](demo.gif)
[![](qt4_thumb.jpg)](qt4.png)

</div>

__Designing this project was easy, but it was surprisingly hard to figure out how to do this based on examples I found on the internet.__ This is part of why I wanted to place this example here. The downside of many internet examples is that they did not use Qt Designer to make the window, so their code to create a window and insert the MatplotlibWidget wasn't copy/paste compatible with my goals, and often more complex than I needed. Some internet examples _did_ use Qt Designer to make the window, but left a frame empty which they later manually filled with a widget and attached to a matplotlib canvas. This is fine, but more complex than I need to get started.

__First, I designed a GUI with Qt Designer__. I dropped a MatplotlibWidget somewhere, and used its default name. I saved this file as [ui\_main.ui](https://github.com/swharden/Python-GUI-examples/blob/master/2016-07-30_qt_matplotlib_sine_scroll/ui_main.ui) (which is an XML file, ready to be used for [multiple programming languages](http://doc.qt.io/qt-4.8/designer-using-a-ui-file.html)).

<div class="text-center img-border">

[![](pyqt4-designer_thumb.jpg)](pyqt4-designer.png)

</div>

__Next, I converted the UI file into a .py file__ with a standalone python script that's an alternative to using pyuic from the command line. The script to do this is [ui\_convert.py](https://github.com/swharden/Python-GUI-examples/blob/master/2016-07-30_qt_matplotlib_sine_scroll/ui_convert.py) and it calls PyQt4.uic.compileUi():

```python
from PyQt4 import uic
fin = open('ui_main.ui','r')
fout = open('ui_main.py','w')
uic.compileUi(fin,fout,execute=False)
fin.close()
fout.close()
```

__I then created my main program file which populates the matplotlib widget with data.__ I called it [go.py](https://github.com/swharden/Python-GUI-examples/blob/master/2016-07-30_qt_matplotlib_sine_scroll/go.py) and running it will launch the app. The code explains it all, and there's not much more to say! It produces the output at the top of this post.

```python
from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pylab
import time

class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
    def __init__(self, parent=None):
        super(ExampleApp, self).__init__(parent)
        self.setupUi(self)
        self.btnAdd.clicked.connect(self.update)
        self.matplotlibwidget.axes.hold(False) #clear on plot()

    def update(self):
        t1=time.time()
        points=100 #number of data points
        X=np.arange(points)
        Y=np.sin(np.arange(points)/points*3*np.pi+time.time())
        C=pylab.cm.jet(time.time()%10/10) # random color
        self.matplotlibwidget.axes.plot(X,Y,ms=100,color=C,lw=10,alpha=.8)
        self.matplotlibwidget.axes.grid()
        self.matplotlibwidget.axes.get_figure().tight_layout() # fill space
        self.matplotlibwidget.draw() # required to update the window
        print("update took %.02f ms"%((time.time()-t1)*1000))
        if self.chkMore.isChecked():
            QtCore.QTimer.singleShot(10, self.update) # QUICKLY repeat

if __name__=="__main__":
    app = QtGui.QApplication(sys.argv)
    form = ExampleApp()
    form.show()
    form.update() #start with something
    app.exec_()
    print("DONE")
```

This project is on GitHub: https://github.com/swharden/Python-GUI-examples/tree/master/2016-07-30_qt_matplotlib_sine_scroll
July 28th, 2016

Opto-Isolated Laser Controller Build

This page documents the design and build of a small device to interface a modern fiber-coupled DPSS laser with old scientific hardware designed to control mechanical relays. This project involves analog and digital circuitry, microcontrollers, and lasers, and it turned out to be a pretty cool build so I'm sharing the design and construction process online in case it will be helpful for others or even my future self.

The existing hardware I must interface is made by Coulbourn Instruments and is essentially just a large multi-channel computer-controlled DAC/ADC. It does its job well (turning lights on and off, recording button presses, etc.), but this new task requires millisecond resolution and modulation patterns which lies outside the specs of this system and software. My goal is to interface a free output line of this old hardware and use it to signal to a new device I build to activate the laser to produce a pulsed pattern. This way there would be no modification to any existing equipment, and no software to install. Further, since this hardware isn't mine, I don't like the idea of permanently modifying it (or even risking breaking it by designing something which could damage it by connecting to it). The specific goal is to allow the existing software to cause the laser to fire 20 ms pulses at 15 Hz for a few dozen cycles of 5s on, 5s off. It's also important to have some flexibility to reprogram the high speed laser stimulation pattern in the future. Experiments are already underway and I need this device to be complete within a couple of days! As much as I'd love to go to the internet and order the perfect cheap components, make a proper PCB, and have a beautiful build completed in a month, my goal is to build this over a weekend using only using parts I already have at my home.

Probing the existing hardware revealed many surprises. After inspecting the existing hardware setup I found an auxiliary output which could be controlled by software. This AUX port has a frustratingly rare connector 1mm dual keyhole touchproof connector which I couldn't buy in bulk on eBay or Amazon, and couldn't figure out the part numbers of on Mouser or Digikey. The manual even says "you may find it convenient to fit them with CI-type connectors" which makes me wonder why it wasn't designed this way in the first place! Luckily the laboratory had an old (broken) device with that connector on it they said I could use for this build. After plugging in the connector, I used a volt meter to measure the output. To my surprise, it wasn't a TTL signal! I expected to see my volt meter read 5V, but it read 28V! After consulting the manual I found mention of this: "Graphic State Notation software is designed for use with our Habitest animal-behavior-analysis environments or any other animal-behavior-testing apparatus that operates on the industry-standard 28-Volt control voltage." I was surprised that 28V signals is a standard for any industry.

Control voltages are negative! Elsewhere in the manual I found the phrase "The power base is capable of delivering 8 Amps of -28 VDC" which made me question the voltage reading I took earlier. The voltmeter showed 28V, but that's the difference between one keyhole connector output and the other. It became apparent that it really may be 0V (GND) and -28mV (an even more curious "industry standard"). I wondered if connecting the negative terminal to ground would destroy the unit (think about how easy this would be to do! If it were a TTL signal, the first thing you would do is connect the negative terminal to ground and start sampling the positive terminal). There was even talk of me interfacing with a different output port (which I hadn't probed, so I didn't know the voltage). Moving forward, I realized I had to tread very carefully. Doing something like connecting two grounds together could permanently damage this system!

Optical isolation should be used as a caution. Not really knowing if I should design to expect a TTL signal, a +28V signal, or a -28V signal, I decided to design a circuit to accommodate all of the above, while achieving total electrical discontinuity from whatever circuit I develop. I'm going to accomplish this using an opto-isolator on the input. I drew the schematic using KiCad as I built the board manually using through-hole construction. I considered laying-out a PCB (I have most of these components in SMT form factors too) but I knew I wouldn't manage a one-weekend turnaround if I went that direction.

Design Notes

  • The input should be able to accomodate any signal (TTL, CMOS, 28V, etc)
  • The input is totally isolated electrically, so this should be very safe on the hardware
  • The microcontroller is a socketed ATTiny85 which I programmed with a Bus Pirate.
  • I decided to rely on a crystal rather than the internal RC clock to improve temporal precision of the output signal. A 11.0592 MHz crystal was chosen because I already had an abundance of them (they're perfect for serial communication at all common baud rates). Any crystal could be used, as long as its frequency is defined in software.
  • Capacitors were added more to ensure oscillation initiates than to bring down the oscillation frequency. (I'm told that omitting them may cause a case where the crystal doesn't resonate as well, but I've never found this in my personal experience.) A good note on microcontroller clocks is in a Microchip PIC application note.
  • I included a "test" button (momentary switch) to simulate having an input signal.
  • Note that R1 must be able to handle the current applied to it. It was mistakenly designed as 1k, and later replaced with 10k. See the bodge note at the bottom of this post for details.

Potential Improvements

  • Forward protection diodes on the input could protect accidental reverse polarity
  • Adding an ICSP header would prevent de-socketing of the MCU if reprogramming is desired
  • The BNC output is directly from a MCU pin. A transistor-buffered output design could be considered to deliver higher current for more confident control of the laser input.

Because there is a possibility that a different output (laser control) pattern may be desired in the future, I considered whether or not I should make the output pattern user-configurable. Adding buttons, a display, and designing a menu system in software would be a lot of work and it's unclear if a protocol change will ever actually be required, so I concluded that I'm going to build this device to the specific task at hand and extended it later if/when needed. If the end user eventually wants the ability to modulate the pattern on their own, the device they ask for would be a very different one than the one I was tasked to create. Since the current pattern is burned into a microchip, a compromise is that I could have new patterns burned into new microchips, and the end-user could change the chip (as long as it's an infrequent occurrence).

EDIT: About a year later a modification was indeed required (something like changing 15 Hz to 40 Hz), and the solution was to burn these two patterns into two microchips. Since they're socketed, they're swappable (albiet a limited number of times). This design worked-out well.

Is a microcontroller/crystal overkill for a system which could be accomplished using an analog circuit? Generating 20 ms pulses at 15 Hz sounds like an easy task for a 555 timer without the need for digital circuitry. I considered this for a while, but concluded that the advantage of the MCU (crystal-disciplined time precision) outweighed the convenience of a purely analog circuit. A 555 timer in astable / multi-vibrator configuration would mostly get the job done, but you would either (1) only allow one output pattern and rely on precision passive components (which I don't have on hand), or (2) allow the end-user to adjust duty/frequency with potentiometers (which would require the output to be quantitatively monitored on an oscilloscope). There's also the issue where RC oscillators can be highly temperature sensitive, so the microcontroller/crystal design seemed like a more robust solution for reliable and defined behavior.

I started the build by measuring/marking drill points. I used a Dremel drill press to make the holes, then smoothed them with a deburring tool. I also drilled holes in the base of the enclosure.

I had an enclosure ready to go. I always buy enclosures in bulk, and even though nice ones tend to be expensive, having them on hand encourages me to build devices as I think of them, rather than making flaky hardware which I have a history of doing which sometimes borders on ridiculousness. I usually stock unfinished Hammond diecast aluminum enclosures for making quick RF projects, and boxes with feet and side vents for fancier projects, but for this task I decided to enclose everything inside a typical (but a little more costly) aluminum enclosure ordered in bulk from eBay. I love using low current LEDs, and I started going with frosted instead of clear LEDs because they're easier on the eyes. Also, I switched to mostly 3mm LEDs instead of 5MM because they look a little nicer in these small enclosures with small black bezels.

I used nicer perfboard with plated holes to build this circuit. Normally I use cheap perfboard with little copper rings glued to one side because it's faster to solder (the copper is so thin it heats quickly), but it's not always a good long-term solution because the copper pads have a tendency to un-stick. I rarely use this nicer perfboard (it is more expensive), but it's nice to have for more reliable builds.

I marked areas of optical isolation with a black marker. This makes it obvious where the potentially dangerous, potentially high-voltage (well, higher than TTL), potentially negative input comes in. No wires or connections should invade this space on the board. The special connector which will connect this device to the scientific hardware is on-site, and I'll have to solder it at the time of delivery/installation. I left an extra hole in the back to accommodate this wire. I didn't have any rubber grommets, but I suspect this build may have benefitted from one.

Once it was all together, the device seemed to perform well. The test button on the back made it easy to inspect the output. I build so many RF circuits that I instinctively reached for a 50-ohm terminator, but the square wave quickly transformed into shark fins (RC curves) reminding me that and I realized 50 ohms is far too low impedance. If it's a TTL signal, let's assume it's virtually infinite impedance. I was uncertain whether or not I should drive the output directly with a microcontroller pin. There may be a need for a buffered output. The microcontroller's datasheet suggests limiting its current to 20 mA per pin (requiring termination of no less than 250 Ohms at 5V), and I'm going to move forward assuming the laser TTL input doesn't sink much current.

How should TTL timing be controlled in software? I want this device to perform identically over long periods of time, favoring reliability and consistency over microsecond time precision. To achieve 15 Hz of 20ms pulses I need 20ms on and 46.666667 ms off. I could probably get pretty close if I wanted to, but I rounded it to 20 ms on and 46 ms off. This gives time for the instruction cycles toggling the output pins to occur (although it's on an order of magnitude faster time scale), which slightly biases the time in the right direction. I considered adding a _delay_us(666) after the _delay_ms(46) but I'm satisfied with it this knowing it's within 1% accuracy of 15 Hz and that precision is locked to that of the crystal (around 10 ppm, or 0.001%).

Hard-coded _delay_ms() is somewhat inelegant. The use of AVR timers should probably be considered as an alternative strategy. Here's an awesome guide on the topic, and here's another. Timers would be preferred if I wanted the program code of the microcontroller to be free to do other things like drive menus or multiplex a display. Since this task is simply required generation of a TTL output pattern (and nothing more), the hard-coded delay method met this need, but I found it useful to consider the asynchronous strategies:

  • Timers: Set the timer to overflow every 1 ms. On overflow, a counting variable would be incremented and a function would be called to determine what to do. At pre-programmed time points (with respect to the counting variable), the output pin would be toggled, or the counting variable would be reset.

  • Output compare registers: Utilize the built-in OCR (output compare register) to turn the output signal on and off. Set the timer to overflow at 15 Hz, turning the output on. Set the OCR (to the fractional point between 0 and the maximum timer value) such that when it is passed, the output is turned off. This way 15 Hz, 20 ms pulses would be continuously running without any code being executed. Input sensing could simply enable and disable the timer.

  • Input interrupts: Why stop at timers? Polling the input pin for a TTL signal puts the chip in an infinite loop. Relying on the AVR's external (pin change) hardware interrupts could eliminate this as well. I always rely heavily on the datasheet when setting these interrupts.

These alternative implementations will be useful in the future if a more accurate time source is desired, an advanced display is added, or menus are implemented which would benefit from letting the pulsing output operate in the background while accepting user input. For now, I'm happy with the blocking delay strategy.

After I was satisfied with construction, I started labeling the enclosure. I want to tip my hat to Onno Hoekstra on this one, as his webpage and some email correspondence helped me realize how good clear labels look when outlined and applied to aluminum enclosures. I'm using a DYMO LetraTag LT-100T Plus label maker and clear tape. It's important to enable the black outline around the text, then I cut carefully slightly outside the outline with regular scissors, and apply the labels with a hobby knife or razor blade.

The morning I delivered the product and added the final proprietary connector which I didn't have at home. It's an inelegant knot-retained configuration, but I think it'll get the job done! It was a surprisingly rare, fully shielded, keyhole-shaped touchproof connector apparently used only in medical applications. At this point, I'm thinking this connector was chosen to (A) protect the user from accidentally shorting a 28V 8A power source (that's over 200 watts!), (B) to prevent you from damaging the equipment by plugging in something that doesn't belong (could you imagine what would happen if this -28V high current source had a BNC connector and you plugged this into something expecting a 5V TTL input?), and (C) prevent you from plugging in anything that wasn't made by this company. The last option is most likely a well-intentioned attempt by the manufacturer to prevent customers from damaging their product rather than the company trying to maintain its status as a sole distributor of accessories, but it makes me wonder. I would have preferred power pole sockets, molded power connectors like those on motherboards, or even barrel connectors! Surely there's a more standard touchproof connector for moderate voltage/currents than this bizarre keyhole connector.

I plugged the device in to the computer, attached the laser, and it worked immediately! I wasn't really surprised that it worked (I tested it extensively at home), but it still felt good to watch the blue laser trigger as it was supposed to. Another interesting one-off project is complete, and have some interesting photos and notes about the build to share on this website. I hope this little device continues to do its job well for many years in its new laboratory home.

Microcontroller Code

#define F_CPU 11059200UL
#include <avr/io.h>
#include <util/delay.h>

int main (void){
    DDRB=(1<<PB0); // set port B pin 0 as an output
    PORTB=0;       // pull all pins low
    while(1){
        while((PINB&(1<<PB2))==0){} // do nothing while the input is low
        PORTB=(1<<PB0);   // TTL ON
        _delay_ms(20);    // stay on for 20ms
        PORTB&=~(1<<PB0); // TTL OFF
        _delay_ms(46);    // stay off for 46ms
    }
}

Here's the batch script I used to compile and load the code onto the microcontroller. I compiled the code with AVR-GCC and copied it onto the microcontroller with a Bus Pirate. Note also that I'm setting the fuses to respect an external oscillator.

@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny85 -Wall -Os -o main.elf main.c
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c buspirate -p attiny85 -P com3 -e -U flash:w:main.hex
avrdude -c buspirate -p attiny85 -P com3 -U lfuse:w:0xff:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
pause

Final Design

Update: Improve Current Handling

After a few days I got an email from someone concerned about the current handling capability of the front-end of the circuit. It was noted that a standard 1/4 watt resistor may not be suitable for R1, as a 28V potential would stress it beyond its specs. With 28V applied, R1 (a quarter-watt resistor) would experience P=IE=28mA*28V=784mW of current! It might last (especially if pulsed), but it also might fail with time. The advantage of the R1/D1/R2 system is that the output current will be identical across a wide range of input voltages. The disadvantage is that it's hard to predict how much current R1 needs to be expected to tolerate. I could have placed five 4.7k resistors in parallel to replace R1 (this would let me handle over 1 watt of input power), but I instead simply upped it from 1kOhm to 10kOhm. This further reduced the current that the opto-isolator sees (now only about 0.2 mA) but it seems to work still. I'm satisfied with this modification, but a little disappointed I didn't catch it sooner. Note that the new input resistor (a 10k R1) should now only have to dissipate about 80mW, well within its specs.

Update: H11B1 minimum current and AC noise

What is the minimum current required to confidently activate the optoisolator? I considered that a 10K input resistor on 28V would only allow 2.8 mA to pass, and I was unsure if this would reliably activate the optocoupler. Considering only 3.3V will persist after the zener (a ~11.7% current retaining ratio, if that's valid), I figured that a best 330µA were passing through the opto-isolator. That seems outside of the specs of the device, because their datasheet graphs always start at 1mA. I decided to run some tests at my home to see how this device performed with lower input currents. I determined that a 10k resistor still works with 5V (500 µA into the device), but checking the output on the oscilloscope I realized that the device operates only partially, and slowly at that low voltage/current. The darlington transistor configuration is very high gain, which is the only reason this works at all, but such low currents are sensitive to parasitic capacitance and infiltrating RF currents. Because of this it seems the chip takes a few ms to activate and deactivate. Since this application only uses 5s on and 5s off inputs, it's fine for this application, but I wouldn't expect high speed pulsing of the input signal to work well. Furthermore, in my breadboard I realized I was getting funny output currents oscillating around 60Hz, which made me suspicious that the device was picking up AC somehow. I realized it was from pin 6 (the exposed darlington base). Normally the LED is so strong is blasts the device fully on or off, but hovering on the edge like this, that pin is picking up signals that influence the open state of the transistor inside. Since it's not connected to anything anyway, I cut the pin off as close to the microchip as I could, and noticed an instant improvement in 60Hz rejection. In conclusion, I wouldn't try to reliably drive an opto-isolator with a complex pattern using less than 1 mA, but it seems to work well for simple on/off control.

This is the output of the unmodified H11B1:

This is the output of the H11B1 with the base pin removed:

Conclusion: This was an interesting build! I'm satisfied with the use of optical isolation here to adapt two incompatible systems with unknown/unspecified input/output properties, and the lack of electrical connections between the inputs and outputs gave me high confidence to experiment along the way.

Markdown source code last modified on May 4th, 2021
---
title: Opto-Isolated Laser Controller Build
date: 2016-07-28 02:33:12
tags: microcontroller, circuit, old
---

# Opto-Isolated Laser Controller Build

**This page documents the design and build of a small device to interface a [modern fiber-coupled DPSS laser](http://www.lasercentury.com/product.asp?id=612) with [old scientific hardware](http://www.coulbourn.com/product_p/h02-08.htm) designed to control mechanical relays.** This project involves analog and digital circuitry, microcontrollers, and lasers, and it turned out to be a pretty cool build so I'm sharing the design and construction process online in case it will be helpful for others or even my future self.

<div class="text-center img-border">

[![](IMG_7304_thumb.jpg)](IMG_7304.jpg)

</div>

**The existing hardware I must interface is made by [Coulbourn Instruments](http://www.coulbourn.com/) and is essentially just a large multi-channel computer-controlled DAC/ADC**. It does its job well (turning lights on and off, recording button presses, etc.), but this new task requires millisecond resolution and modulation patterns which lies outside the specs of this system and software. **My goal is to interface a free output line of this old hardware and use it to signal to a new device I build to activate the laser to produce a pulsed pattern.** This way there would be no modification to any existing equipment, and no software to install. Further, since this hardware isn't mine, I don't like the idea of permanently modifying it (or even risking breaking it by designing something which could damage it by connecting to it). The specific goal is to allow the existing software to cause the laser to fire 20 ms pulses at 15 Hz for a few dozen cycles of 5s on, 5s off. **It's also important to have some flexibility to reprogram the high speed laser stimulation pattern in the future.** Experiments are already underway and I need this device to be complete within a couple of days! As much as I'd love to go to the internet and order the perfect cheap components, make a proper PCB, and have a beautiful build completed in a month, my goal is to build this over a weekend using only using parts I already have at my home.

<div class="text-center img-micro">

[![](hardware-stack_thumb.jpg)](hardware-stack.jpg)
![](hardware-stack2.jpg)
[![](hardware-pci_thumb.jpg)](hardware-pci.jpg)

</div>

**Probing the existing hardware revealed many surprises.** After inspecting the existing hardware setup I found an auxiliary output which could be controlled by software. This AUX port has a [frustratingly rare connector 1mm dual keyhole touchproof connector](http://shop.cephalon.eu/Braebon,-Keyhole-to-two-touchproof-(1,5mm)-connect/ItemDetails.aspx?9=GB&5=0574&11=1531) which I couldn't buy in bulk on eBay or Amazon, and couldn't figure out the part numbers of on Mouser or Digikey. The manual even says "_you may find it convenient to fit them with CI-type connectors_" which makes me wonder why it wasn't designed this way in the first place! Luckily the laboratory had an old (broken) device with that connector on it they said I could use for this build. After plugging in the connector, I used a volt meter to measure the output. To my surprise, it wasn't a [TTL signal](https://en.wikipedia.org/wiki/Transistor%E2%80%93transistor_logic)! I expected to see my volt meter read 5V, but it read 28V! [After consulting the manual](http://www.coulbourn.com/v/vspfiles/assets/manuals/GraphicStateNotation2-101SoftwareUserGuide.pdf) I found mention of this: "_Graphic State Notation software is designed for use with our Habitest animal-behavior-analysis environments or any other animal-behavior-testing apparatus that operates on the industry-standard 28-Volt control voltage._" I was surprised that 28V signals is a standard for any industry. 

**Control voltages are negative!** Elsewhere in the manual I found the phrase "_The power base is capable of delivering 8 Amps of -28 VDC_" which made me question the voltage reading I took earlier. The voltmeter showed 28V, but that's the difference between one keyhole connector output and the other. It became apparent that it really may be 0V (GND) and -28mV (an even more curious "industry standard"). I wondered if connecting the negative terminal to ground would destroy the unit (think about how easy this would be to do! If it were a TTL signal, the first thing you would do is connect the negative terminal to ground and start sampling the positive terminal). There was even talk of me interfacing with a different output port (which I hadn't probed, so I didn't know the voltage). _Moving forward, I realized I had to tread very carefully. Doing something like connecting two grounds together could permanently damage this system!_ 

**Optical isolation should be used as a caution.** Not really knowing if I should design to expect a TTL signal, a +28V signal, or a -28V signal, I decided to design a circuit to accommodate all of the above, while achieving _total electrical discontinuity_ from whatever circuit I develop. I'm going to accomplish this using an [opto-isolator on the input](https://en.wikipedia.org/wiki/Opto-isolator). I drew the schematic using [KiCad](http://kicad-pcb.org/) as I built the board manually using through-hole construction. I considered laying-out a PCB (I have most of these components in SMT form factors too) but I knew I wouldn't manage a one-weekend turnaround if I went that direction.

<div class="text-center">

[![](schematic-4_thumb.jpg)](schematic-4.png)

</div>

### Design Notes

*   The input should be able to accomodate any signal (TTL, CMOS, 28V, etc)
*   The input is _totally_ isolated electrically, so this should be very safe on the hardware
*   The microcontroller is a socketed [ATTiny85](http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf) which I programmed with a [Bus Pirate](https://www.swharden.com/wp/2016-07-14-controlling-bus-pirate-with-python/).
*   I decided to rely on a [crystal rather than the internal RC clock](http://www.electroschematics.com/9481/avr-clock-source-fuse-bits/) to improve temporal precision of the output signal. A 11.0592 MHz crystal was chosen because I already had an abundance of them (they're [perfect for serial communication at all common baud rates](http://wormfood.net/avrbaudcalc.php)). Any crystal could be used, as long as its frequency is defined in software.
*   Capacitors were added more to ensure oscillation initiates than to bring down the oscillation frequency. (I'm told that omitting them may cause a case where the crystal doesn't resonate as well, but I've never found this in my personal experience.) A good note on microcontroller clocks is in a [Microchip PIC application note](http://ww1.microchip.com/downloads/en/appnotes/00826a.pdf).
*   I included a "test" button (momentary switch) to simulate having an input signal.
*   Note that R1 must be able to handle the current applied to it. It was mistakenly designed as 1k, and later replaced with 10k. See the bodge note at the bottom of this post for details.

### Potential Improvements

*   Forward protection diodes on the input could protect accidental reverse polarity
*   Adding an [ICSP](http://www.ladyada.net/learn/avr/programming.html) header would prevent de-socketing of the MCU if reprogramming is desired
*   The BNC output is directly from a MCU pin. A [transistor-buffered](https://en.wikipedia.org/wiki/Buffer_amplifier#Single-transistor_circuits) output design could be considered to deliver higher current for more confident control of the laser input.

**Because there is a possibility that a different output (laser control) pattern may be desired in the future, I considered whether or not I should make the output pattern user-configurable.** Adding buttons, a display, and designing a menu system in software would be a lot of work and it's unclear if a protocol change will ever actually be required, so I concluded that I'm going to build this device to the specific task at hand and extended it later if/when needed. If the end user eventually wants the ability to modulate the pattern on their own, the device they ask for would be a very different one than the one I was tasked to create. Since the current pattern is burned into a microchip, a compromise is that I could have new patterns burned into new microchips, and the end-user could change the chip (as long as it's an infrequent occurrence).

_EDIT: About a year later a modification was indeed required (something like changing 15 Hz to 40 Hz), and the solution was to burn these two patterns into two microchips. Since they're socketed, they're swappable (albiet a limited number of times). This design worked-out well._

**Is a microcontroller/crystal overkill for a system which could be accomplished using an analog circuit?** Generating 20 ms pulses at 15 Hz sounds like an easy task for a [555 timer](https://en.wikipedia.org/wiki/555_timer_IC) without the need for digital circuitry. I considered this for a while, but concluded that the advantage of the MCU (crystal-disciplined time precision) outweighed the convenience of  a purely analog circuit. A 555 timer in [astable / multi-vibrator configuration](http://www.circuitbasics.com/555-timer-basics-astable-mode/) would mostly get the job done, but you would either (1) only allow one output pattern and rely on precision passive components (which I don't have on hand), or (2) allow the end-user to adjust duty/frequency with potentiometers (which would require the output to be quantitatively monitored on an oscilloscope). There's also the issue where RC oscillators can be highly temperature sensitive, so the microcontroller/crystal design seemed like a more robust solution for reliable and defined behavior.

**I started the build** by measuring/marking drill points. I used a Dremel drill press to make the holes, then smoothed them with a deburring tool. I also drilled holes in the base of the enclosure.

<div class="text-center img-small img-border">

[![](IMG_7186_thumb.jpg)](IMG_7186.jpg)
[![](IMG_7188_thumb.jpg)](IMG_7188.jpg)
[![](IMG_7191_thumb.jpg)](IMG_7191.jpg)
[![](IMG_7192_thumb.jpg)](IMG_7192.jpg)
[![](IMG_7203_thumb.jpg)](IMG_7203.jpg)

</div>

**I had an enclosure ready to go.** I always buy enclosures in bulk, and even though nice ones tend to be expensive, having them on hand encourages me to build devices as I think of them, rather than making flaky hardware which I [have a history of doing](https://www.swharden.com/wp/2011-01-16-first-homebrew-qso-ever/) which sometimes [borders on ridiculousness](https://www.swharden.com/wp/2010-06-03-aj4vd-qrss-vd-mept/). I usually stock [unfinished Hammond diecast aluminum enclosures](http://www.alliedelec.com/hammond-manufacturing-1590g/70165610/) for making quick RF projects, and [boxes with feet and side vents](https://www.amazon.com/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords=aluminum+electronic+project+enclosure+box) for fancier projects, but for this task I decided to enclose everything inside a typical (but a little more costly) aluminum enclosure ordered in bulk from eBay. I love using low current LEDs, and I started going with frosted instead of clear LEDs because they're easier on the eyes. Also, I switched to mostly 3mm LEDs instead of 5MM because they look a little nicer in these small enclosures with small black bezels.

<div class="text-center img-small img-border">

[![](IMG_7191_thumb.jpg)](IMG_7191.jpg)
[![](IMG_7206_thumb.jpg)](IMG_7206.jpg)
[![](IMG_7204_thumb.jpg)](IMG_7204.jpg)
[![](IMG_7208_thumb.jpg)](IMG_7208.jpg)

</div>

**I used nicer perfboard with plated holes to build this circuit.** Normally I use [cheap perfboard](https://www.circuitspecialists.com/solderable-perf-boards) with little copper rings glued to one side because it's faster to solder (the copper is so thin it heats quickly), but it's not always a good long-term solution because the copper pads have a tendency to un-stick. I rarely use this nicer perfboard (it is more expensive), but it's nice to have for more reliable builds.

<div class="text-center img-small img-border">

[![](IMG_7258_thumb.jpg)](IMG_7258.jpg)
[![](IMG_7215_thumb.jpg)](IMG_7215.jpg)
[![](IMG_7216_thumb.jpg)](IMG_7216.jpg)
[![](IMG_7252_thumb.jpg)](IMG_7252.jpg)
[![](IMG_7259_thumb.jpg)](IMG_7259.jpg)

</div>

**I marked areas of optical isolation with a black marker.** This makes it obvious where the potentially dangerous, potentially high-voltage (well, higher than TTL), potentially negative input comes in. No wires or connections should invade this space on the board. The special connector which will connect this device to the scientific hardware is on-site, and I'll have to solder it at the time of delivery/installation. I left an extra hole in the back to accommodate this wire. I didn't have any [rubber grommets](http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1313.TR0.TRC0.H0.Xrubber+grommets.TRS0&_nkw=rubber+grommets&_sacat=0), but I suspect this build may have benefitted from one.

<div class="text-center img-border">

[![](IMG_7232_thumb.jpg)](IMG_7232.jpg)

</div>

**Once it was all together, the device seemed to perform well.** The test button on the back made it easy to inspect the output. I build so many RF circuits that I instinctively reached for a 50-ohm terminator, but the square wave quickly transformed into shark fins (RC curves) reminding me that and I realized 50 ohms is far too low impedance. If it's a TTL signal, let's assume it's virtually infinite impedance. I was uncertain whether or not I should drive the output directly with a microcontroller pin. There may be a need for a buffered output. The microcontroller's datasheet suggests limiting its current to 20 mA per pin (requiring termination of no less than 250 Ohms at 5V), and I'm going to move forward assuming the laser TTL input doesn't sink much current.

<div class="text-center img-border img-medium">

[![](IMG_7284_thumb.jpg)](IMG_7284.jpg)
[![](scope_thumb.jpg)](scope.jpg)

</div>

**How should TTL timing be controlled in software?** I want this device to perform identically over long periods of time, favoring reliability and consistency over microsecond time precision. To achieve 15 Hz of 20ms pulses I need 20ms on and 46.666667 ms off. I could probably get pretty close if I wanted to, but I rounded it to 20 ms on and 46 ms off. This gives time for the instruction cycles toggling the output pins to occur (although it's on an order of magnitude faster time scale), which slightly biases the time in the right direction. I considered adding a _delay_us(666) after the _delay_ms(46) but I'm satisfied with it this knowing it's within 1% accuracy of 15 Hz and that precision is locked to that of the crystal (around 10 ppm, or 0.001%).

**Hard-coded \_delay\_ms() is somewhat inelegant.** The use of [AVR timers](http://www.atmel.com/Images/Atmel-2505-Setup-and-Use-of-AVR-Timers_ApplicationNote_AVR130.pdf) should probably be considered as an alternative strategy. Here's an [awesome guide on the topic](http://maxembedded.com/2011/06/introduction-to-avr-timers/), and [here's another](http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers?page=all). Timers would be preferred if I wanted the program code of the microcontroller to be free to do other things like drive menus or multiplex a display. Since this task is simply required generation of a TTL output pattern (and nothing more), the hard-coded delay method met this need, but I found it useful to consider the asynchronous strategies:

*   **Timers**: [Set the timer](http://eleccelerator.com/avr-timer-calculator/) to overflow every 1 ms. On overflow, a counting variable would be incremented and a function would be called to determine what to do. At pre-programmed time points (with respect to the counting variable), the output pin would be toggled, or the counting variable would be reset.

*   **Output compare registers:** Utilize the built-in OCR (output compare register) to turn the output signal on and off. [Set the timer](http://eleccelerator.com/avr-timer-calculator/) to overflow at 15 Hz, turning the output on. Set the OCR (to the fractional point between 0 and the maximum timer value) such that when it is passed, the output is turned off. This way 15 Hz, 20 ms pulses _would be continuously running without any code being executed_. Input sensing could simply enable and disable the timer.

*   **Input interrupts:** Why stop at timers? Polling the input pin for a TTL signal puts the chip in an infinite loop. Relying on the AVR's external (pin change) hardware interrupts could eliminate this as well. I always [rely heavily on the datasheet](http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf) when setting these interrupts.

**These alternative implementations will be useful in the future** if a more accurate time source is desired, an advanced display is added, or menus are implemented which would benefit from letting the pulsing output operate in the background while accepting user input. For now, I'm happy with the blocking delay strategy.

<div class="text-center img-border img-small">

[![](IMG_7265_thumb.jpg)](IMG_7265.jpg)
[![](IMG_7262_thumb.jpg)](IMG_7262.jpg)
[![](IMG_7301_thumb.jpg)](IMG_7301.jpg)
[![](IMG_7300_thumb.jpg)](IMG_7300.jpg)

</div>

**After I was satisfied with construction, I started labeling the enclosure.** I want to tip my hat to [Onno Hoekstra](http://www.qsl.net/pa2ohh/) on this one, as [his webpage](http://www.qsl.net/pa2ohh/tlabels.htm) and some email correspondence helped me realize how good clear labels look when outlined and applied to aluminum enclosures. I'm using a [DYMO LetraTag LT-100T Plus](https://www.amazon.com/DYMO-LetraTag-Personal-Hand-Held-1733013/dp/B001B1FIW2/) label maker and [clear tape](https://www.amazon.com/DYMO-Labeling-LetraTag-Labelers-Black/dp/B00006B8FA). It's important to enable the black outline around the text, then I cut carefully slightly outside the outline with regular scissors, and apply the labels with a hobby knife or razor blade.

<div class="text-center img-border img-small">

[![](IMG_7310_thumb.jpg)](IMG_7310.jpg)
[![](IMG_7308_thumb.jpg)](IMG_7308.jpg)
[![](IMG_7307_thumb.jpg)](IMG_7307.jpg)
[![](IMG_7305_thumb.jpg)](IMG_7305.jpg)

</div>

**The morning I delivered the product** and added the final proprietary connector which I didn't have at home. It's an inelegant knot-retained configuration, but I think it'll get the job done! It was a [surprisingly rare, fully shielded, keyhole-shaped touchproof connector](http://shop.cephalon.eu/Braebon,-Keyhole-to-two-touchproof-(1,5mm)-connect/ItemDetails.aspx?9=GB&5=0574&11=1531) apparently used only in medical applications. At this point, I'm thinking this connector was chosen to (A) protect the user from accidentally shorting a 28V 8A power source (that's over 200 watts!), (B) to prevent you from damaging the equipment by plugging in something that doesn't belong (could you imagine what would happen if this -28V high current source had a BNC connector and you plugged this into something expecting a 5V TTL input?), and (C) prevent you from plugging in anything that wasn't made by this company. The last option is most likely a well-intentioned attempt by the manufacturer to prevent customers from damaging their product rather than the company trying to maintain its status as a sole distributor of accessories, but it makes me wonder. I would have preferred power pole sockets, molded power connectors like those on motherboards, or even barrel connectors! Surely there's a more standard touchproof connector for moderate voltage/currents than this bizarre keyhole connector.

**I plugged the device in to the computer, attached the laser, and it worked immediately!** I wasn't really surprised that it worked (I tested it extensively at home), but it still felt good to watch the blue laser trigger as it was supposed to. Another interesting one-off project is complete, and have some interesting photos and notes about the build to share on this website. I hope this little device continues to do its job well for many years in its new laboratory home.

<div class="text-center img-border">

[![](IMG_7316_thumb.jpg)](IMG_7316.jpg)

</div>

### Microcontroller Code

```c
#define F_CPU 11059200UL
#include <avr/io.h>
#include <util/delay.h>

int main (void){
    DDRB=(1<<PB0); // set port B pin 0 as an output
    PORTB=0;       // pull all pins low
    while(1){
        while((PINB&(1<<PB2))==0){} // do nothing while the input is low
        PORTB=(1<<PB0);   // TTL ON
        _delay_ms(20);    // stay on for 20ms
        PORTB&=~(1<<PB0); // TTL OFF
        _delay_ms(46);    // stay off for 46ms
    }
}
```

**Here's the batch script I used to compile and load the code onto the microcontroller.** I compiled the code with AVR-GCC and copied it onto the microcontroller with a [Bus Pirate](https://www.swharden.com/wp/2016-07-14-controlling-bus-pirate-with-python/). Note also that I'm [setting the fuses to respect an external oscillator](http://www.engbedded.com/fusecalc/).

```bash
@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny85 -Wall -Os -o main.elf main.c
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c buspirate -p attiny85 -P com3 -e -U flash:w:main.hex
avrdude -c buspirate -p attiny85 -P com3 -U lfuse:w:0xff:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
pause
```

### Final Design

<div class="text-center img-border">

[![](IMG_7304_thumb.jpg)](IMG_7304.jpg)

</div>

### Update: Improve Current Handling

**After a few days I got an email from someone concerned about the current handling capability of the front-end of the circuit.** It was noted that a standard 1/4 watt resistor may not be suitable for R1, as a 28V potential would stress it beyond its specs. With 28V applied, R1 (a quarter-watt resistor) would experience P=IE=28mA*28V=784mW of current! It might last (especially if pulsed), but it also might fail with time. The advantage of the R1/D1/R2 system is that the output current will be identical across a wide range of input voltages. The disadvantage is that it's hard to predict how much current R1 needs to be expected to tolerate. I could have placed five 4.7k resistors in parallel to replace R1 (this would let me handle over 1 watt of input power), but I instead simply upped it from 1kOhm to 10kOhm. This further reduced the current that the opto-isolator sees (now only about 0.2 mA) but it seems to work still. I'm satisfied with this modification, but a little disappointed I didn't catch it sooner. Note that the new input resistor (a 10k R1) should now only have to dissipate about 80mW, well within its specs.

<div class="text-center img-border">

[![](IMG_7368_thumb.jpg)](IMG_7368.jpg)

</div>

### Update: H11B1 minimum current and AC noise

**What is the minimum current required to confidently activate the optoisolator?** I considered that a 10K input resistor on 28V would only allow 2.8 mA to pass, and I was unsure if this would reliably activate the optocoupler. Considering only 3.3V will persist after the zener (a ~11.7% current retaining ratio, if that's valid), I figured that a best 330µA were passing through the opto-isolator. That seems outside of the specs of the device, because their datasheet graphs always start at 1mA. I decided to run some tests at my home to see how this device performed with lower input currents. I determined that a 10k resistor still works with 5V (500 µA into the device), but checking the output on the oscilloscope I realized that the device operates only partially, and slowly at that low voltage/current. The darlington transistor configuration is very high gain, which is the only reason this works at all, but such low currents are sensitive to parasitic capacitance and infiltrating RF currents. Because of this it seems the chip takes a few ms to activate and deactivate. Since this application only uses 5s on and 5s off inputs, it's fine for this application, but I wouldn't expect high speed pulsing of the input signal to work well. Furthermore, in my breadboard I realized I was getting funny output currents oscillating around 60Hz, which made me suspicious that the device was picking up AC somehow. I realized it was from pin 6 (the exposed darlington base). Normally the LED is so strong is blasts the device fully on or off, but hovering on the edge like this, that pin is picking up signals that influence the open state of the transistor inside. Since it's not connected to anything anyway, I cut the pin off as close to the microchip as I could, and noticed an instant improvement in 60Hz rejection. In conclusion, I wouldn't try to reliably drive an opto-isolator with a complex pattern using less than 1 mA, but it seems to work well for simple on/off control.

This is the output of the unmodified H11B1:

<div class="text-center img-border img-small">

[![](IMG_7495_thumb.jpg)](IMG_7495.jpg)
![](SDS00008.bmp)

</div>

This is the output of the H11B1 with the base pin removed:

<div class="text-center img-border img-small">

[![](IMG_7497_thumb.jpg)](IMG_7497.jpg)
![](SDS00010.bmp)

</div>

**Conclusion:** This was an interesting build! I'm satisfied with the use of optical isolation here to adapt two incompatible systems with unknown/unspecified input/output properties, and the lack of electrical connections between the inputs and outputs gave me high confidence to experiment along the way.
Pages