Action Potential Generator Circuit

Few biological cells are as interesting to the electrical engineer as the neuron. Neurons are essentially capacitors (with a dielectric cell membrane separating conductive fluid on each side) with parallel charge pumps, leak currents, and nonlinear voltage-dependent currents. When massively parallelized, these individual functional electrical units yield complex behavior and underlie consciousness. The study of the electrical properties of neurons (neurophysiologically, a subset of electrophysiology) often involves the development and use of sensitive electrical equipment aimed at studying these small potentials produced by neurons and currents which travel through channels embedded in their membranes. It seems neurophysiology has gained an emerging interest from the hacker community, as evidenced by the success of Back Yard Brains, projects like the OpenEEG, and Hack-A-Day’s recent feature The Neuron – a Hacker’s Perspective.

In pondering designs for complex action potential detection and analysis circuitry, I realized that it would be beneficial to be able to generate action-potential-like waveforms on my workbench. The circuit I came up with to do this is a fully analog (technically mixed signal) action potential generator which produces lifelike action potentials.

 

Cellular Neurophysiology for Electrical Engineers (in 2 sentences): Neuron action potentials (self-propagating voltage-triggered depolarizations) in individual neurons are measured in scientific environments using single cell recording tools such as sharp microelectrodes and patch-clamp pipettes. Neurons typically rest around -70mV and when depolarized (typically by external excitatory input) above a threshold they engage in a self-propagating depolarization until they reach approximately +40mV, at which time a self-propagating repolarization occurs (often over-shooting the initial rest potential by several mV), then the cell slowly returns to the rest voltage so after about 50ms the neuron is prepared to fire another action potential. Impassioned budding electrophysiologists may enjoy further reading Active Behavior of the Cell Membrane and Introduction to Computational Neuroscience.

The circuit I describe here produces waveforms which visually mimic action potentials rather than serve to replicate the exact conductances real neurons employ to exhibit their complex behavior. It is worth noting that numerous scientists and engineers have designed more physiological electrical representations of neuronal circuitry using discrete components. In fact, the Hodgkin-Huxley model of the initiation and propagation of action potentials earned Alan Hodgkin and Andrew Huxley the Nobel Prize in Physiology and Medicine in 1936. Some resources on the internet describe how to design lifelike action potential generating circuits by mimicking the endogenous ionic conductances which underlie them, notably Analog and Digital Hardware Neural Models, Active Cell Model, and Neuromorphic Silicon Neuron Circuits. My goal for this project is to create waveforms which resemble action potentials, rather than waveforms which truly model them. I suspect it is highly unlikely I will earn a Nobel Prize for the work presented here.

The analog action potential simulator circuit I came up with creates a continuous series action potentials. This is achieved using a 555 timer (specifically the NE555) in an astable configuration to provide continuous square waves (about 6 Hz at about 50% duty). The rising edge of each square wave is isolated with a diode and used to charge a capacitor*. While the charge on the capacitor is above a certain voltage, an NPN transistor (the 2N3904) allows current to flow, amplifying this transient input current. The capacitor* discharges predictably (as an RC circuit) through a leak resistor. A large value leak resistor slows the discharge and allows that signal’s transistor to flow current for a longer duration. By having two signals (fast and slow) using RC circuits with different resistances (smaller and larger), the transistors are on for different durations (shorter and longer). By making the short pulse positive (using the NPN in common collector configuration) and the longer pulse negative (using the NPN in common emitter configuration), a resistor voltage divider can be designed to scale and combine these signals into an output waveform a few hundred mV in size with a 5V power supply. Pictured below is the output of this circuit realized on a breadboard. The blue trace is the output of the 555 timer.

*Between the capacitance of the rectification diode, input capacitance of the transistor, and stray parasitic capacitance from the physical construction of my wires and the rails on my breadboard, there is sufficient capacitance to accumulate charge which can be modified by changing the value of the leak resistor.

This circuit produces similar output when simulated. I’m using LTspice (free) to simulate this circuit. The circuit shown is identical to the one hand-drawn and built on the breadboard, with the exception that an additional 0.1 µF capacitor to ground is used on the output to smooth the signal. On the breadboard this capacitance-based low-pass filtering already exists due to the capacitive nature of the components, wires, and rails.

A few improvements naturally come to mind when considering this completed, functional circuit:

  • Action potential frequency: The resistor/capacitor network on the 555 timer determines the rate of square pulses which trigger action potentials. Changing these values will cause a different rate of action potential firing, but I haven’t attempted to push it too fast and suspect the result would not be stable is the capacitors are not given time to fully discharge before re-initiating subsequent action potentials.
  • Microcontroller-triggered action potentials: Since action potentials are triggered by any 5V rising edge signal, it is trivially easy to create action potentials from microcontrollers! You could create some very complex firing patterns, or even “reactive” firing patterns which respond to inputs. For example, add a TSL2561 I2C digital light sensor and you can have a light-to-frequency action potential generator!
  • Adjusting size and shape of action potentials: Since the waveform is the combination of two waveforms, you can really only adjust the duration (width) or amplitude (height) of each individual waveform, as well as the relative proportion of each used in creating the summation. Widths are adjusted by changing the leak resistor on the base of each transistor, or by adding additional capacitance. Amplitude and the ratio of each signal may be adjusted by changing the ratio of resistors on the output resistor divider.
  • Producing -70 mV (physiological) output: The current output is electirically decoupled (through a series capacitor) so it can float at whatever voltage you bias it to. Therefore, it is easy to “pull” in either direction. Adding a 10k potentiometer to bias the output is an easy way to let you set the voltage. A second potentiometer gating the magnitude of the output signal will let you adjust the height of the output waveform as desired.
  • The 555 could be replaced by an inverted ramp (sawtooth): An inverted ramp / sawtooth pattern which produces rapid 5V rising edges would drive this circuit equally well. A fully analog ramp generator circuit can be realized with 3 transistors: essentially a constant current capacitor charger with a threshold-detecting PNP/NPN discharge component.
  • This action potential is not all-or-nothing: In real life, small excitatory inputs which fail to reach the action potential threshold do not produce an action potential voltage waveform. This circuit uses 5V rising edges to produce action potential waveforms. However, feeding a 1V rising edge would produce an action potential 1/5 the size. This is not a physiological effect. However, it is unlikely (if not impossible) for many digital signal sources (i.e., common microcontrollers) to output anything other than sharp rising edge square waves of fixed voltages, so this is not a concern for my application.
  • Random action potentials: When pondering how to create randomly timed action potentials, the issue of how to generate random numbers arises. This is surprisingly difficult, especially in embedded devices. If a microcontroller is already being used, consider Make’s write-up on the subject, and I think personally I would go with a transistor-based avalanche nosie generator to create the randomness.
  • A major limitation is that irregularly spaced action potentials have slightly different amplitudes. I found this out the next day when I created a hardware random number generator (yes, that happened) to cause it to fire regularly, missing approximately half of the action potentials. When this happens, breaks in time result in a larger subsequent action potential. There are several ways to get around this, but it’s worth noting that the circuit shown here is best operated around 6 Hz with only continuous regularly-spaced action potentials.

In the video I also demonstrate how to record the output of this circuit using a high-speed (44.1 kHz) 16-bit analog-to-digital converter you already have (the microphone input of your sound card). I won’t go into all the details here, but below is the code to read data from a WAV file and plot it as if it were a real neuron. The graph below is an actual recording of the circuit described here using the microphone jack of my sound card.

import numpy as np
import matplotlib.pyplot as plt
Ys = np.memmap("recording.wav", dtype='h', mode='r')[1000:40000]
Ys = np.array(Ys)/max(Ys)*150-70
Xs = np.arange(len(Ys))/44100*1000
plt.figure(figsize=(6,3))
plt.grid(alpha=.5,ls=':')
plt.plot(Xs,Ys)
plt.margins(0,.1)
plt.title("Action Potential Circuit Output")
plt.ylabel("potential (mV)")
plt.xlabel("time (ms)")
plt.tight_layout()
plt.savefig("graph.png")
#plt.show()

Let’s make some noise! Just to see what it would look like, I created a circuit to generate slowly drifting random noise. I found this was a non-trivial task to achieve in hardware. Most noise generation circuits create random signals on the RF scale (white noise) which when low-pass filtered rapidly approach zero. I wanted something which would slowly drift up and down on a time scale of seconds. I achieved this by creating 4-bit pseudo-random numbers with a shift register (74HC595) clocked at a relatively slow speed (about 200 Hz) having essentially random values on its input. I used a 74HC14 inverting buffer (with Schmidt trigger inputs) to create the low frequency clock signal (about 200 Hz) and an extremely fast and intentionally unstable square wave (about 30 MHz) which was sampled by the shift register to generate the “random” data. The schematic illustrates these points, but note that I accidentally labeled the 74HC14 as a 74HC240. While also an inverting buffer the 74HC240 will not serve as a good RC oscillator buffer because it does not have Schmidt trigger inputs.

The addition of noise was a success, from an electrical and technical sense. It isn’t particularly physiological. Neurons would fire differently based on their resting membrane potential, and the peaks of action potential should all be about the same height regardless of the resting potential. However if one were performing an electrical recording through a patch-clamp pipette in perforated patch configuration (with high resistance between the electrode and the internal of the cell), a sharp microelectrode (with high resistance due to the small size of the tip opening), or were using electrical equipment or physical equipment with amplifier limitations, one could imagine that capacitance in the recording system would overcome the rapid swings in cellular potential and result in “noisy” recordings similar to those pictured above. They’re not physiological, but perhaps they’re a good electrical model of what it’s like trying to measure a physiological voltage in a messy and difficult to control experimental environment.

This project was an interesting exercise in analog land, and is completed sufficiently to allow me to move toward my initial goal: creating advanced action potential detection and measurement circuitry. There are many tweaks which may improve this circuit, but as it is good enough for my needs I am happy to leave it right where it is. If you decide to build a similar circuit (or a vastly different circuit to serve a similar purpose), send me an email! I’d love to see what you came up with.


     

TTL Triggered Stimulus Generator

I was presented with a need to rapidly develop a pulse generator to take a TTL input and output a programmable output (for now 0.1 ms pulses at 20 Hz for as long as the input is high). I achieved this with a one-afternoon turnaround and the result looks great! This post documents the design and fabrication of this prototype device, with emphasis placed on design considerations and construction technique. It is also worth noting that by stocking large quantities of frequently-used items, inventors can build beautiful and functional prototypes for new ideas at the drop of a hat. While it’s easy to inexpensively accumulate tens of thousands of passive components (resistors, capacitors, etc.), it’s the slightly more expensive components that people tend to order only when they need it for a project. However, paying high shipping rates or waiting months for items to arrive from overseas dramatically increases the barrier for initiating new projects. In my own workshop I have noticed that stocking large volumes of slightly more costly items (inductors, microcontrollers, connectors, enclosures, LED bezels, etc.) lowers the barrier for me to start new projects, and has proved to be a good investment! Now I can build a product on the same day that I have the idea! Today’s idea takes the form of a TTL-controlled pulse generator for physiology applications.

I designed the enclosure before I designed the circuit. Metal enclosures are always expensive compared to their plastic counterparts. Steel enclosures are difficult to drill, and aluminum enclosures are expensive. My most cringe-worthy stocking expenditure is ordering metal enclosures in quantities of 10+. The last I checked this specific one is listed as, “Aluminum Instrument Box Enclosure Case+Screw For Project Electronic 26X71X110MM” and is a little under $4 each. Brass hex stand-off nuts and black steel screws don’t exactly match the aluminum, but they’re what I had on hand. I knew I would need power and a BNC input and output, so I put those 3 on the back. I wasn’t sure about the exact functionality of this device (and it may change after it is initially implemented) but I thought a single button and two LEDs would be a good starting point.

The circuit demonstrates the general flow of this device: a microcontroller-controlled project with a buffered output. I drew this schematic after I finished the build (I kept adding passives here and there as I tested it out) but before I started I knew the gist of how I would organize the project. Mentally I knew that as long as my microcontroller (ATTiny2313) could sense the TTL input and had control over all outputs (LEDs and BNC alike), I had a lot of flexibility to control the operation of this device in software. I used a generic LM7805 linear voltage regulator with a few decoupling capacitors to take a who-knows-what input voltage (up to 40V) and turn it into a stable 5V output. Note that both inputs (the BNC TTL input and the push-button) have decoupling capacitors near the microcontroller input pin to aid in debouncing.

I’m leaning on a 74HC541 inverting line driver to clamp the output voltage firmly at TTL levels. The microcontroller (an ATTiny2313) isn’t really designed to source of sink much current (I think it’s rated to 20 mA max) and I don’t know about the input circuitry of the stimulus isolator I intend to control (and don’t forget about the impedance of 50-ohm cable). The line driver helps me take some of the pressure off the microcontroller and help me feel better about reliably driving the output BNC.

Should I have optically isolated the input? Well, probably not… the application at hand is low importance. If I wanted to rely on optical isolation I would probably lean on the H11B1 as previously used in my opto-isolated laser build. In retrospect I kind of wish I had just because it would have been cooler!

I added a header to allow me to program the microcontroller with a programmer configured with test clip grabbers. I have an AVR ISP MKII (clone), and building a programming adapter that uses test clips was one of the best decisions I ever made! It makes programming (and the inevitable re-programming) a breeze.

The program isn’t too complex. It uses a polling method to continuously check for the state of the input TTL. When it’s high, it starts a new cycle (0.1 ms pulse, 49.9ms delay, yielding 20 Hz). The code is ready to add a “mode select” feature (which uses the front-panel push-button to select different stimulation protocols), but that functionality is not included in the example below. Note that a lot of the millisecond and microsecond delays are empirically determined by picking a value and checking its output on the oscilloscope. I should note that absolute timing isn’t critical for my application, as long as it’s consistent. For this reason I’m not relying on the internal RC clock (which is temperature sensitive), but instead am using an external 20MHz crystal as a time source. It’s still temperature sensitive (and so are the loading capacitors on each side of it), but dramatically less so than the RC option. Note that the crystal wasn’t in the original photos, but it was added for later photos.

Configure the ATTiny2313 to use an external crystal clock source

@echo off
avrdude -c usbtiny -p t2313 -U lfuse:w:0xff:m -U hfuse:w:0xdf:m -U efuse:w:0xff:m
pause

The core program (main.c)

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

volatile char state=0;

void output_HIGH(){PORTB|=(1<<PB4);}
void output_LOW(){PORTB&=~(1<<PB4);}
void LED1_ON(){PORTB|=(1<<PB3);}
void LED1_OFF(){PORTB&=~(1<<PB3);}
void LED2_ON(){PORTB|=(1<<PB2);}
void LED2_OFF(){PORTB&=~(1<<PB2);}

void singlePulse_20Hz_100us(){
	output_HIGH();
	_delay_us(100);
	output_LOW();
	_delay_us(900);
	LED2_ON();
	_delay_ms(20);
	LED2_OFF();
	_delay_ms(28);
	_delay_us(920);
}

void poll(){
	if ((PIND&(1<<PD4))){singlePulse_20Hz_100us();}
}

int main(void){
	DDRB=255; // all outputs
	DDRD=0; // all inputs
	PORTD=(1<<PD5); // pull front button high
	LED1_ON();
	for(;;){
		poll();
	}
}

Compile “main” and load it onto the ATTiny2313

@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny2313 -Wall -Os -o main.elf main.c -w
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
pause
avrdude -c usbtiny -p t2313 -U flash:w:"main.hex":a 

I didn’t think to check my height profile! I got lucky, and things fit fine. Socketed ICs can be close calls, and so can vertically-installed electrolytic capacitors. Now that it was programmed and everything fit, it was time to seal it up and make labels.

The finished product looks great! Never underestimate the power of clear labels and square outlines. Following deployment, a couple screws will let me open it up and access the programming header in case I need to change the stimulation protocols stored in the microchip. I am pleased with how professional of a result I was able to achieve in one sitting! I look forward to seeing how this device works for my application.

PS: Microcontroller code for this project (and many others) is stored in my ever-growing AVR-Projects GitHub page: https://github.com/swharden/AVR-projects

PPS: I smiled when a Google search revealed the PulsePal, “A low-cost programmable pulse generator for physiology and behavior”.


     

Controlling Speakers with RADAR

I just finished building a device that uses RADAR to toggle power to my speakers when it detects my hand waiving near them! I have some crummy old monitor speakers screwed to a shelf, and although their sound is decent the volume control knob (which also controls power) is small and far back on my work bench and inconvenient to keep reaching for. I decided to make a device which would easily let me turn the speakers on and off without having to touch anything. You could built a device to detect a hand waive in several different ways, but RADAR (RAdio Detection And Ranging) has got to be the coolest!

 

This project centers around a 5.8 GHz microwave radar sensor module (HFS-DC06, $5.22 from icstation.com, + 15% discount code haics) which senses distance (sensitivity is adjustable with a potentiometer) and in response to crossing a threshold it outputs a TTL pulse (the duration of with is adjustable with another potentiometer). I ran the output of the module through divide-by-two circuit (essentially a flip-flop) so that an object-detect event would toggle a line rather than pull the line high for each detection. I didn’t have a cheap flip-flop IC on hand (the 74HC374 comes to mind, $0.54 on Mouser) but I did have a 74HC590 8-bit binary counter on hand ($0.61 on Mouser) which has a divide-by-two output. I used the radar sensor and this IC to produce a proximity-toggled TTL signal which enabled/disabled current flowing through a power n-channel MOSFET. All together this let met create a device with two DC barrel jacks (an input and an output), and current delivery on the output could be toggled with proximity sensing.

Design

I wanted this device to be extremely simple, with a single input DC jack and single output jack and no buttons or knobs. It’s a funny feeling making a user input device with no drill holes in the enclosure! The design is so simple it’s not worth reviewing in detail. The 15V line (which in reality could be almost anything) is brought to 5V with a LM7805 linear voltage regulator. Decoupling capacitors are commonly placed on the input and output of the regulator, but since the function is to toggle a switch I didn’t find it necessary (there is no downstream signal I wish to preserve the integrity of). The radar module has only 3 connectors: +5v, GND, and OUT. Out produces a high pulse when it detects something close. The output of the OUT signal is fed into a divide-by-two stage which is really just a 74HC590 8-bit binary counter taking output of the div/2 pin. That output is fed into an IRF510 N-channel MOSFET to switch current flow on the “DC output” on and off. A Darlington transistor (i.e., TIP122) would probably work fine too, but there would be a slightly greater voltage drop across it. Any power MOSFET would have worked, but I had a box of IRF510s on hand so I used one although they are more expensive ($0.82 on Mouser). Not shown is a status LED which is also on the output of the divide-by-2 chip (with a current limiting series resistor).

Construction

glued the radar module to the wall of a plastic enclosure. Isn’t radar messed-up by glue and plastic? Yes! But I’m not sensing things 50 feet away. I’m sensing a hand moving a few inches away. To this end, I experimented with how much glue and how thick plastic I needed to distort the signal enough so that it would only activate when I put my hand near it (as opposed to sitting down at my desk, which could also trigger the sensor without these attenuating structures). I found that aluminum tape further dampened the response, but luckily for the aesthetics of the build I didn’t have to use it. Also, rather than take time making a PCB (or even using perfboard), I found point-to-point construction quite sufficient. I hot glued the counter IC to the radar module, wired it all together, and it was done! A little black plastic LED bezel was a nice touch with the diffuse blue LED.

Alternative Designs

RADAR was a cool way to accomplish this task, but there certainly are additional methods which could achieve a similar result:

  • IR (infrared) – By pulsing IR and sensing the reflected signal intensity with an IR-filtered photo-transistor, you could invisibly detect presence of an object in front of the sensor. Adjusting the amplification of the photo-transistor (and/or diffusion of its lens or enclosure) could adjust for distance. In fact, many companies make paired IR-LED / IR-phototransistor modules specifically for this task. However, if you have a TV remote control or other device which uses IR to communicate, it could screw with this signal.
  • Sonar – Instead of light, pressure waves (sound) could be used to sense distance due to the time delay between an audio emission and detection of its reflection. Presumably an ultrasonic transducer would be used to prevent perpetual annoyance of those living in the area. Ultrasonic distance sensor modules can also be found online for this purpose. These technologies are what is commonly used by vehicles to detect objects in their path while backing-up, alerting the driver with a beep. The downside of this method is that it would not work inside an enclosure. Aesthetically, I didn’t want to have two silver screen-covered cans staring at me.

Radar Module Teardown

I had two of these modules on hand, so after I got this project working with one I used the other to conduct a destructive teardown. What I found inside was interesting! If someone were really interested, there may be some potential for hackability here. Aside from the microwave PCB goodness I found two primary ICs: the LM2904 dual op-amp and an ATTiny13 8-bit microcontroller. I was really surprised to find a microcontroller in here! With so much analog on these boards, it seemed that a timed pulse could be accomplished by a 555 or similar. A single-quantity ATTiny13 is $0.58 on Mouser (as compared to $0.36 for a 555) but maybe when you add the extra discrete components (plus cost of board space) it makes sense. Also, I’m not entirely sure how this circuit is sensing distance and translating it into pulses so perhaps there is some more serious computation than I’m giving it credit for.

The presence of an ATMEL AVR in this RADAR module is a potential site for future hacks. I’d be interested to solder some wires to it and see if I could extract the firmware. In any large scale commercial products the read/write access would be disabled, but with small run modules like this one seems to be there’s a chance I could reprogram it as-is. If I really wanted to use this layout but write a custom program for the micro I could desolder it and lay my own chip on the board. For now though, I’m really happy with how this project came out!


     

Precision Pressure Meter Project

I just completed building a device capable of measuring temperature to one hundredth of a degree Celsius and pressure to one ten-thousandth of a PSI! This project is centered around an ICstation MS5611 temperature sensor breakout board which was small enough to fit inside of a plastic syringe. The result is a small and inexpensive pressure sensor in a convenient form factor with a twist connector (a Luer-Lok fitting) that can be rapidly attached to existing tubing setups. Although the screw attachment would work well for industrial or scientific applications, I found that the inner connector (the non-threaded plastic nub with 6% taper) made a snug and air-tight connection with my CO2-impermanent aquarium tubing.

this MS5611 breakout board is small enough to fit inside a 10 mL syringe

I documented this project thoroughly so others can learn about the design process that goes into making one-off prototypes like this. The video is quite long considering how simple the task seems (read a number from a sensor and display it on a screen), but it gives a lot of tips and insights into rapidly making professional looking one-off projects like this. Reading datasheets can be intimidating for newcomers too, and this video walks through how to figure out how to bang out I2C commands to a new sensor using a Bus Pirate – a really convenient skill to have for hobby electrical engineers like me! After it’s working well with the sensor/computer interface you can move to the microcontroller level with confidence. Since no one has posted code for how to interface this sensor directly with the microcontroller platform I intended to use (AVR-GCC, notably not Arduino), my build process started by poking around with a Bus Pirate to learn how to interact with the device using  I2C commands. Once I was able to initiate temperature and pressure readings and pull its values by hand using the Bus Pirate, I wrote a Python script to automate the process (using PySerial to interact with the Bus Pirate) and allow recording and graphing of real-time pressure and temperature information. I then used a logic analyzer to glance at the data exchanged between the Bus Pirate and the pressure sensor (mostly for my own satisfaction, and to help with debugging in the future). Finally, I ditched the computer and had an ATMega328 microcontroller pull temperature/pressure readings and display them on a 16×2 HD44780 character LCD display beautifully framed with a laser-cut LCD bezel (from Tindie user widgeneering). I used a USB connector to give the device power (though there’s no reason it couldn’t run off of 3xAA batteries) and CAT5 cable as a convenient connector between the display and the sensor. After assembling everything and making some labels, the final product looks quite professional!

Project Summary Video

This video is quite extensive. It explores the design process for one-off projects like this, with extra time spent on the difficult parts that often pose the greatest challenges to newcomers (exploring datasheets, banging out I2C commands with a new sensor). I don’t see this part of the design process discussed too often in engineering videos, so I hope it will be an insightful and inspiring resource to people just starting to work with custom electronics and prototype design. Another group of people who benefit from watching the video are those who don’t know much about the design process of embedded devices, but will quickly realize that building a prototype device to do something as simple as reading a number from a sensor and displaying it on a screen can take an immense amount of insight, work, troubleshooting, and effort to create.

 

About the MS5611 Temperature and Pressure Sensor

The breakout board I’m using provides 5V access to the I2C interface of the MS5611. This is convenient because the MS5611 requires 3.3V and many microcontroller applications run at 5V. The MS5611 itself is the small (5mm by 3mm) silver rectangle on the side of the board. The MS5611 datasheet has all the information we need to know to get started poking around its I2C bus! The general idea is that it has an imperfect pressure sensor on board. During production the pressure sensors are produced with slightly different offsets and gains. Further, the pressure sensor varies its output as a function of temperature. They included a temperature sensor on there too, but that also varies by offset and gain due to production! To yield highly precise absolute pressure readings, the factory calibrates every device individually by storing six 16-bit calibration values in a program memory. They represent the sensitivities and offsets of these sensors.

When run through an algorithm (given a whole page in the datasheet), the 6 factory-programmed calibration values (16-bit integers) can be combined with the raw temperature and pressure readings (24-bit integers) to yield incredibly accurate and precise temperature and pressure readings down to 0.01 degree Celsius and 0.012 millibar (0.00017 PSI). This accuracy is enough to be able to measure changes in altitude of 10 centimeters!

These are some photos of the break-out board from the company’s product page and a few more taken from my USB microscope zoomed in on the sensor itself. If I feel inspired, I may use my hot air tool to lift the sensor off the board and incorporate into a future, smaller design. I’ll save that project for another day!

Using a Bus Pirate to Communicate with the Sensor

After reading the datasheet I learned the general flow of how to read data from this sensor. It was a three step command process for both temperature and pressure:

  • Tell the device what to measure and with what precision [by sending 1 byte]
    This is in the commands section (page 9/20) of the datasheet. Command 0x48 will tell it to use maximum oversampling ratio (OSR) to convert D1 (the digital pressure value). Highest OSR (4096) means the most precise reading but a slightly slower reading (9.04 ms) with higher current draw (12.5 µA at 1 Hz) as compared to the lowest OSR (256, 0.6 ms, 0.9 µA). 
  • Tell the device you are ready to perform an ADC read [by sending 1 byte]
    The byte you send to read the ADC is always 0x00. Don’t proceed to this step until the conversion has been given time to complete or your reading will be zero.
  • Read the ADC result [by reading 3 bytes]
    The ADC result will always be an 18-bit integer.

This was a great use for my Bus Pirate! Without the Bus Pirate in order to debug this device I would have needed to make a circuit board, wire-up a microcontroller, figure out how to program that microcontroller to interact with the sensor (with very limited hardware debug tools), and send readings (and debug messages) to a computer via a USB serial port. Also, I’d have to add bidirectional serial communication code if I wanted it to be interactive. What a nightmare! Recently I started really valuing my Bus Pirate as a way to immediately hook up to a new sensor out of the box and interactively pull data from it within a few seconds. To hack this to my Bus Pirate I soldered-on female headers (instead of soldering on the pins that came with the breakout board). The Bus Pirate pin descriptions page shows how to hook up an I2C device. It’s important to note that the sensor board will not receive power (and its LED won’t light up) until you send the “W” command to the Bus Pirate.

Here are the commands I use with the Bus Pirate to connect with the sensor. If you can’t get this part to work, I don’t recommend challenging using a microcontroller to pull I2C data from this part! This is kind of fool proof, so this stage not working means you’ve read the datasheet incorrectly and don’t know how to interact with the sensor as well as you thought you did, or that there is a hardware or connectivity issue with the circuit. All of this is in the video posted above, so watching that part of the video may help you get an idea of what it looks like interacting with circuits like this with a Bus Pirate. Also, be sure to review the Bus Pirate I2C guide.

  • Open RealTerm and connect to the Bus Pirate
    • change display mode to Ansi
    • set baud to 115200 baud (no parity, 8 bits, 1 stop bit)
  • # to reset the Bus Pirate (optional)
  • m to set mode
  • 4 to select I2C
  • 3 to select 100 KHz
  • W to enable power (the red LED on the sensor should light up)
  • P to enable pull-up resistors (no errors should be displayed)
  • (1) scan for I2C devices (the sensor should be displayed, likely as oxEE)
  • Let’s make a read! This is how to read raw pressure:
    • [0xEE 0x48] to do the 4096 OCR D1 read
    • [0xEE 0x00] to prepare to read the ADC
    • [0xEF r:3] to read 3 bytes

For the most accurate readings, use the algorithms on page 7/20 of the datasheet to use the calibration variables (C1-C6) in combination with pressure (D1) and temperature (D2) to produce an accurate temperature and pressure measurement.

Enclosing the Pressure Sensor

My application requires me to sense pressure in air-tight tubing. My solution was to insert this sensor inside a 10 mL syringe and seal it up with epoxy such that the only opening would be the twist connector I could attach to the air line. I accomplished this by cutting the syringe with a rotary tool, removing the rubber stopper from the plunger and puncturing it so I could pass the wires through, then sealing it up as tightly as I could. I crossed my fingers and hoped it wouldn’t leak as I mixed-up some epoxy and poured it in. After an hour of setting time, I was delighted to learn that it sealed air tight! I could now attach needles and tubes with the screw connector, or leave it disconnected to measure atmospheric pressure.

Sniffing I2C with a Logic Analyzer

Right off the bat my Bus Pirate could pull sensor data but the C code I wrote running on a microcontroller could not. What gives? Was the sensor hooked up wrong? Was the microcontroller sending the wrong commands? Were the commands not being read by the microcontroller properly? Were the messages not being transmitted to the LCD display properly? There are so many points for failure and such limited hardware debugging (I’m not using JTAG) that my first go-to was my logic analyzer. As you can probably tell by the video I don’t use this thing too often, but good gosh when I do it usually saves me hours of head scratching.

logic analyzer sniffing functional I2C between Bus Pirate and sensor board

In this case, I immediately saw that the I2C lines were always low (!) and realized that the problem was my reliance on microcontroller pull-up resistors to keep those lines continuously high. That was a rookie mistake. I guess I could have seen this with an oscilloscope, but at the time I hooked it up I thought it was a protocol issue and not a dumb hardware issue. I slapped on a few 10K resistors to the VCC line and it worked immediately. Regardless, it was nice to have the capability. See the video for details.

Building the Enclosure

I still can’t get over how good the silver aluminium looks against the black laser-cut display bezel in combination with the dark backbit LCD display. I couldn’t have done this without the LCD bezels I just found being sold on Tindie! Mounting character LCD displays on metal or plastic enclosures is a chore and usually looks awful. I cringe at some of my old projects which have displays loosely meshed with square cut-outs. My square holes look nicer now that I use a hand nibbler tool, but there’s just no way that I know of to make an LCD display look good in a square cut-out without a good bezel. Another advantage of a large bezel is you don’t have to make a perfectly square cut-out, since it will all get covered-up anyway!

I then proceeded to epoxy the connectors I wanted (USB and Ethernet) and drill holes for the PCB mount. I added the microcontroller (ATMega328) and the circuit is so simple I’m not even going to display it here. If you’re really interested, check out the video. My logic is that a 5V noisy power supply is fine since all we are doing is simple, slow, digital signaling, and that the sensitive stuff (analog pressure/temperature sensing) is done on a board which already has a linear regulator on it presumably filtering-out almost all of the supply line noise. Plus, my application is such that 0.1 PSI is good enough and measuring it to a ten-thousandth of a PSI is quite overkill and I didn’t even end-up displaying the last digit of precision.

I used CAT5 to carry I2C, which I understand is a bit iffy. I2C is designed to be sent over small distances (like across a circuit board), and not really for long distance transmission. That’s not to say long distance I2C isn’t possible; it just requires a few extra design considerations. The basic idea is that a long line has a lot of capacitance, so it would take a lot of current (sinking and sourcing) to rapidly pull that line fully high and fully low at the high speeds that I2C could use. The longer the cable, the greater the capacitance, and the lower speed I2C you have to use and/or higher current you need to drive it. I2C drivers exist to help with this purpose, and although I have some I found I didn’t actually need to use them. For more information, google on the topic of sending I2C over twisted pair. This Hackaday article on sending I2C over long distances is also quite nice. For the purposes of this prototype, it’s working with straight-through wiring (sensor-to-microcontroller) so let’s call it good and move on.

I had to use a slightly larger aluminum enclosure than I initially wanted because there was limited vertical space with the LCD risers as well as the risers I used for my own board. It was a tight squeeze when all was said and done, but it worked great!

Programming the Microcontroller

Let’s just say I programmed the microchip to do exactly what we did with the Bus Pirate. The code is messy as heck (and even is using two different I2C libraries to signal on the same I2C line!) but it works and the prototype is done and sealed so I don’t really have a lot of reason to fine-tune the software. The full project can be found on the GitHub page, and a few relevant lines of code are below.

Here are a few key points about the microcontroller software:

  • I added a “baseline reset” which resets the current pressure to 0.000 PSI.
  • I’m intentionally not showing full precision because I don’t need it for my application.
  • I hard-coded the calibration values in C rather than look them up each time. This is fine since this code will only run on this one microchip with this one sensor. If this were a production device, obviously they would be read on startup.
  • I am not using the formula provided in the datasheet to integrate the calibration values with temperature to calculate pressure. Instead, I came up with my own formula (essentially just Y=mX+b) which was fit to an ADC/PSI curve I plotted myself using the calibration values for this one sensor and the temperature (72F) where I know the device will be held.
  • Since I’m controlling for temperature and hard-coded my calibration values, I can get good enough precision without the need for floating point math. Adding floating point libraries to an 8-bit AVR consumes a lot of memory and can be slow. However, in a production unit this would probably be a must.
  • Adding logging / PC connectivity would be really easy since there’s already a USB connection there! In this circuit I’m just using it for the +5V power, but there’s no reason we couldn’t attach to the data lines and send our temperature and pressure readings via USB. The easiest way to do this would be by adding an FTDI TTL serial USB adapter such as the FT232 or its breakout board. The microcontroller already has TTL USART capability so it would only be a few extra lines of code.

Code to pull 16-bit calibration values from program memory:

volatile unsigned int prog[8]; // holds calibration values
uint8_t data[2];
char i,j;
for (i=0;i<8;i++){
	data[0]=160+i*2; // addresses from datasheet
	i2c2_transmit(0xEE,data,1);
	i2c2_receive(0xEF,data,2);
	prog[i]=data[0];
	prog[i]*=256;
	prog[i]+=data[1]; // prog[1] will be C1
}

Code to pull a 24-bit pressure sensor reading:

uint8_t data[3];
data[0]=72; // command 72 is "set register to pressure"
i2c2_transmit(0xEE,data,1); 
_delay_ms(10); // time for conversion to complete
data[0]=0; // command 0 is "ADC read"
i2c2_transmit(0xEE,data,1); 
i2c2_receive(0xEF,data,3);
pressure=0;
pressure+=data[0]; // pull in a byte
pressure=pressure<<8; // and shift its significance
pressure+=data[1]; // pull in another byte
pressure=pressure<<8; // shit it all again
pressure+=data[2]; // add the last byte

Example Application

It’s great to have an inexpensive precision temperature and pressure sensor design ready to go for any application I want to use it for in the future. This is a project I’ve been wanting to build for a long time for an aquarium purpose involving monitoring the rate of CO2 injection through the intake valve of an aquarium filter (which I am aware is discouraged because bubbles can be rough on the impeller) as part of a DIY yeast reactor, all to encourage aquatic plant growth. Botany in a sentence: plants use light to grow and metabolize carbon dioxide (CO2) while producing oxygen (O2). By supplementing (but not saturating) the water with CO2, I get better plants. There’s also an application to monitor the positive pressure (rather than negative pressure / suction) of a microcontroller-pressure-controlled reaction chamber this way. If I code it wrong, and the pressure isn’t released, 1 gallon of sugary yeasty water will end up bursting over my living room floor. (I guess this means the pressure is on to get the design right?) Alternatively this prototype may serve a role as a pressure sensor for scientific applications such as electrophysiology to monitor fluid pressure, pipette pressure, or incubator pressure and temperature. Most importantly, this project encouraged me to check out some new hardware I am really glad I found (laser-cut character LCD bezels), read-up on I2C transmission lines and power drivers, and get experience with a new type of sensor that a lot of the Internet has not seen before.

my aquarium
Test configuration measuring filter intake suction (negative pressure)

Components Used

Tools Used

Software

Additional Resources