PS4 Controller Hack – Adding Auto-Run

After a long day it can be really nice to have a relaxing hobby to clear your head, and few activities shut down your brain as effectively as video games. However, a newly released video game is physically hurting me as my impulse to move quickly causes me to perpetually click the run button. After a few days of game-play and a really sore left thumb, I decided to do something about it: hack-in a microchip to automatically click the button for me. Modifying game controllers to do things like automatically rapid fire is nothing new. I once modified a USB computer mouse to add an extra “rapid fire” button (link). Hackaday ran a story about a guy who hacked a PS4 controller to add mouse and keyboard functionality. Today’s hack isn’t quite as elaborate, but it’s very effective. Here I show how I modified a PlayStation 4 controller to automatically click the L3 button so I am always running. This auto-run functionality mimics the auto-run feature built into many games (like Titanfall 2 which spoiled me to expect this), but I built the circuit so it can be toggled on and off by clicking the L3 button. After playing Titanfall 2 for the last few months, the recent release of the Call of Duty WWII beta is driving me crazy as it requires me to click the run button over and over (every two seconds) which, after an afternoon of playing, is actually painful.

Assessing the PS4 Controller

I started out by looking online to see what the PS4 controller looked like inside. Imgur has a great PS4 dualshock controller teardown photo collection which was an excellent starting place. From these photos I realized this hack would be pretty easy since the L3 “click” action is achieved by a through-hole SPDT tactile switch placed under the joystick.

I was surprised to find my PS4 controller (below) was a little different (green for starters), but the overall layout was the same. I quickly identified the 4 pins of the L3 tactile switch and got to work…

After probing around with a multimeter and an oscilloscope, I was able to determine which pins do what. Just from looking at the trace it’s pretty obvious that two of the pins are the positive voltage rail. In this controller the positive voltage (VCC) is about 3 volts, so keep that in mind and don’t hook-up a 5V power supply if you decide to debug this thing.

To test my idea I attached 3 wires to VCC, GND, and SENSE and ran into the other room where my PS4 was. As I held the left joystick up in a game, shorting the SENSE and GND wires (by tapping them together) resulted in running! At this point I knew this hack would work, and proceeded to have a microcontroller control voltage of the L3 sense line.

Simulating L3 Presses with a Microcontroller

I glued a microcontroller (ATTiny85) to the circuit board, then ran some wires to the points of interest. Visual inspection (and a double check with a multimeter when the battery was in) provided easy points for positive and ground which could power my microcontroller. The “L3 sense” pin (which toggles between two voltages when you press the L3 button) was run to pin 3 (PB4) of the microcontroller. In a production environment current limiting resistors and debounce capacitors would make sense, but in the spirit of the hack I keep things minimalistic.

The device could be easily reassembled, and there was plenty of room for the battery pack and its plastic holder to snap in place over the wires. Excellent!

While I was in the controller, I removed the light-pipe that carries light to the diffuser on the back. The PS4 has an embarrassingly poor design (IMO) where the far side of the controller emits light depending on the state of the controller (blue for in use, black for off, orange for charging, etc). This is a terrible design in my opinion because if you have a glossy and reflective TV screen like I do, you see a blue light reflect back in the screen and bobble up and down as you hold the controller. Dumb! Removing the light pipe dramatically reduced the intensity, but still retains the original functionality.

Programming the microcontroller was achieved with an in circuit serial programmer (USBtinyISP) with test clips. This is my new favorite way to program microcontrollers for one-off projects. If the pins of the microcontroller aren’t directly accessable, breaking them out on 0.1″ headers is simple enough and they make great points of attachment for test clips. The simplest code to continuously auto-run is achieved by just swinging the sense line between 5V and 0V. This is the code to do that:

for(;;){ // do this forever
	// simulate a button press
	PORTB|=(1<<PB4); // pull high
	_delay_ms(50); // hold it there
	// simulate a button release
	PORTB&=~(1<<PB4); // pull low
	_delay_ms(50); // hold it there
}

Sensing Actual Button Presses to Toggle Auto-Run On/Off

Simulating L3 presses was as simple as toggling the sense line between VCC and GND, but sensing manual L3 presses wasn’t quite as easy. After probing the output on the scope (see video) I realized that manual button presses toggle between voltages of about 2V and 3V, and the line never really goes down to zero (or below VCC/2) so it’s never read as “off” by the digital input pin. Therefore, I changed my strategy a bit. Instead of clamping between 5V and 0V, I toggled between low impedance and high impedance states. This seemed like it would be gentler on the controller circuit, as well as allow me to use the ADC (analog-to-digital controller) of the microcontroller to read voltage on the line. If voltage is above a certain amount, the microcontroller detects a manual button press is happening and toggles the auto-run functionality on/off. The new code is this:

for(;;){ // do this forever

	// simulate a button press
	PORTB|=(1<<PB4); // pull high
	DDRB|=(1<<PB4); // make output
	_delay_ms(50); // hold it there
	
	// simulate a button release
	DDRB&=~(1<<PB4); // make input
	PORTB&=~(1<<PB4); // pull low
	_delay_ms(50); // hold it there
	
	// check if the button is actually pressed to togggle auto press
	if (ADC>200) { // if the button is manually pressed
		_delay_ms(100); // wait a bit
		while (ADC>200) {} // wait until it depresses
		while (ADC<200) {} // then wait for it to be pressed again
	}	
}

It works! My Call of Duty character is running just like a Titan Pilot. After giving it a spin on the Call of Duty WWII Beta, I’m happy to report that this circuit is holding up well and I’m running forever effortlessly. I still suck at aiming, shooting, and not dying though.

Follow-up: After playing the fast-paced and highly dynamic Titanfall 2 for so long, I rapidly became disenchanted with the Call of Duty WWII game-play which now feels slow and monotonous in comparison. Although this auto-sprint controller hack works, I don’t really use it because I barely play the game I made it for! I’m going back to exclusively playing Titanfall 2 for now, and if you get the chance I highly recommend giving it a spin!

 

 


     

Microcontroller Action Potential Generator

Here I demonstrate how to use a single microcontroller pin to generate action-potential-like waveforms. The output is similar my fully analog action potential generator circuit, but the waveform here is created in an entirely different way. A microcontroller is at the core of this project and determines when to fire action potentials. Taking advantage of the pseudo-random number generator (rand() in AVR-GCC’s stdlib.h), I am able to easily produce unevenly-spaced action potentials which more accurately reflect those observed in nature. This circuit has a potentiometer to adjust the action potential frequency (probability) and another to adjust the amount of overshoot (afterhyperpolarization, AHP). I created this project because I wanted to practice designing various types of action potential measurement circuits, so creating an action potential generating circuit was an obvious perquisite.

 

The core of this circuit is a capacitor which is charged and discharged by toggling a microcontroller pin between high, low, and high-Z states. In the high state (pin configured as output, clamped at 5V) the capacitor charges through a series resistor as the pin sources current. In the low state (pin configured as output, clamped at 0V) the capacitor discharges through a series resistor as the pin sinks current. In the high-Z / high impedance state (pin configured as an input and little current flows through it), the capacitor rests. By spending most of the time in high-Z then rapidly cycling through high/low states, triangular waveforms can be created with rapid rise/fall times. Amplifying this transient and applying a low-pass filter using a single operational amplifier stage of an LM-358 shapes this transient into something which resembles an action potential. Wikipedia has a section describing how to use an op-amp to design an active low-pass filter like the one used here.

The code to generate the digital waveform is very straightforward. I’m using PB4 to charge/discharge the capacitor, so the code which actually fires an action potential is as follows:

// rising part = charging the capacitor
DDRB|=(1<<PB4); // make output (low Z)
PORTB|=(1<<PB4); // make high (5v, source current)
_delay_ms(2); // 2ms rise time
	
// falling part
DDRB|=(1<<PB4); // make output (low Z)
PORTB&=~(1<<PB4); // make low (0V, sink current)
_delay_ms(2); // 2ms fall time
_delay_us(150); // extra fall time for AHP

// return to rest state
DDRB&=~(1<<PB4); // make input (high Z)

Programming the microcontroller was accomplished after it was soldered into the device using test clips attached to my ICSP (USBtinyISP). I only recently started using test clips, and for one-off projects like this it’s so much easier than adding header sockets or even wiring up header pins.

I am very pleased with how well this project turned out! I now have an easy way to make irregularly-spaced action potentials, and have a great starting point for future projects aimed at measuring action potential features using analog circuitry.

Project code (GitHub)

Notes

  • Action potential half-width (relating to the speed of the action potential) could be adjusted in software by reducing the time to charge and discharge the capacitor. A user control was not built in to the circuit shown here, however it would be very easy to allow a user to switch between regular and fast-spiking action potential waveforms.
  • I am happy that using the 1n4148 diode on the positive input of the op-amp works, but using two 100k resistors (forming a voltage divider floating around 2.5V) at the input and reducing the gain of this stage may have produced a more reliable result.
  • Action potential frequency (probability) is currently detected by sensing the analog voltage output by a rail-to-rail potentiometer. However, if you sensed a noisy line (simulating random excitatory and inhibitory synaptic input), you could easily make an integrate-and-fire model neuron which fires in response to excitatory input.
  • Discussion related to the nature of this “model neuron” with respect to other models (i.e., Hodgkin–Huxley) are on the previous post.
  • Something like this would make an interesting science fair project

     

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”.


     

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


     

Hacking a Cheap Ammeter / Voltmeter to Provide a Bluetooth PC Interface

I love analyzing data, so any time I see a cool device to measure something I usually want to save its output. I’ve lately come to enjoy the cheap panel-mount volt meters and current meters on eBay, and figured it would be cool to hack one to provide PC logging capability. After getting a few of these devices for ~$8 each on eBay and probing around, I realized they didn’t output measurement data on any of the pins (not that I really expected they would), so I coded a microcontroller to watch the lines of the multiplexed 7-segment display and figure out what the screen is displaying (an odd technique I’ve done once or twice before), then send its value to a computer using the microcontroller’s UART capabilities. Rather than interfacing a traditional serial port (using a MAX232 level converter, or even a TTL-level USB serial adapter) I decided to go full-scale-cool and make it wireless! I succeeded using a HC-06 Bluetooth serial adapter which you can find on eBay for ~$3. Although I have previously used custom software to hack the output of a TENMA multimeter to let me log voltage or current displayed on the multimeter, now I can measure current and voltage at the same time (wirelessly no less) and this is a far less expensive option than dedicating a multimeter to the task! The result is pretty cool, so I took pictures and am sharing the build log with the world.

The video summarizes the project, and the rest of this page details the build log. All of the code used to program the microcontroller (AVR-GCC), interface the device with the Bluetooth serial adapter, and plot the data (Python) is available as part of a GitHub project.

img_8436

This is what one of these modules looks like, and how it is intended to be used. One of the connectors has 3 wires (black = ground, red = power to run the display (anything up to 30V), and yellow = voltage sense wire). The other connector is thicker and is the current sense circuit. The black wire is essentially short-circuited to ground, so unfortunately this can only be used for low-side current sensing.

img_8134

The side of the display indicates which model it is. Note that if you wish to buy your own panel mount meters, look carefully at their current measuring range. Most of them measure dozens of amps with 0.1 A resolution. There are a few which only measure <1 A, but down to 0.1 mA resolution. This is what I prefer, since I rarely build equipment which draws more than 1 A.

img_8138

On the back you can see all of the important components. There’s a large current shunt resistor on the right, solder globs where the through-hole 4 character 7-segment displays fits in, and the microcontroller embedded in this device is a STM8S003 8-Bit MCU. This chip has UART, SPI, and I2C built-in, so it may be technically possible to have the chip output voltage and current digitally without the need for a man-in-the-middle chip like I’m building for this project. However, I don’t feel like reverse-engineering the hardware and software which takes measurements of voltage and current (which is an art in itself) and also figure out how to drive the display, so I’m happy continuing on developing my device as planned! I did probe all the pins just to be sure, and nothing looked like it was outputting data I could intercept. That would have been too easy!

img_8142

I snapped the device out of its plastic frame to be able to access the pins more easily.

img_8154

I then soldered-on headers to help with reverse-engineering the signals. Note that this was part of my investigation phase, and that these header pins were not needed for the end product. I have multiple panel mount ammeter / voltmeter modules on hand, so I left this one permanently “pinned” like this so I could access the pins if I needed to. A quick check with the continuity tester confirmed that every segment of every character (of both displays) is continuous (wired together).

img_8443

These headers made it easy to attach my 16-channel logic analyzer. I’m using an off-brand Saleae compatible logic analyzer. Their software is open source and very simple and easy to use. Saleae sells their official logic analyzers (which are well made and company supported) on their website, but they are expensive (although probably worth it). I purchased an eBay knock-off logic analyzer ($40) which “looks” like a Saleae device to the computer and works with the same open source software. If I were really serious about building professional products, I would certainly invest in an official Saleae product. For now, this is a good option for me and my hobby-level needs. An 8-channel version if as low as $10 on eBay, and $149 from Saleae.

img_8447

Connections are straightforward. I began probing only a single display. This is a good time to mention that an understanding of display multiplexing is critical to understanding how I’m reading this display! If you don’t know what a multiplexed display is, read up on the subject then come back here. It’s an important concept. While you’re at it, do you know what charlieplexing is?

img_8460

After gazing at the screen of squiggly lines, I was able to piece together which signals represented characters (due to their regularity) and which represented segments (which changed faster, and were more sporadic).

img_8463

I’ll be honest and say that I cheated a bit, using a very high value current limiting resistor and applying current (backwards) into the pins when the device was unplugged. I manged to illuminate individual segments of specific characters in the LCD. This supported what I recorded from the logic analyzer, and in reality could have been used to entirely determine which pins went to which characters/segments.

img_8594-1

Here’s what I came up with! It’s not that complicated: 16 pins control all the signals. The microcontroller raises all lines “high” to only one character at a time, then selectively grounds the segments (A-H) to pass current through only the LEDs intended to be illuminated. Characters are numbers and segments are letters. Note that “A” of the top display (voltage) is connected to the “A” of the second display (current), so both rows of 4 characters make 8 characters as far as the logic is concerned. The transistor isn’t really a discrete transistor, it’s probably the microcontroller sinking current. I used this diagram to conceptualize the directionality of the signals. The sample site of letters is high when a letter is illuminated, and the sample site of a segment is low when that segment is illuminated (the sample site of the segment is the base of the imaginary transistor).

logic

Knowing this, I can intentionally probe a few segments of a single character. Here is the logic analyzer output probing the second character (top), and two representative segments of that character (bottom). You can see the segments go nuts (flipping up and down) as other segments are illuminated (not shown). If you look closely at the blue annotations, you can see that each character is illuminated for about 1 ms and repeats every 13 ms.

img_8471

Now it was time to make my device! I started with a new panel meter and an empty project box. By this point I had reverse-engineered the device and concluded it would take 16 inputs of a microcontroller to read. I chose an ATMega328 which was perfect for the job (plenty of IO) although I could have used a much less powerful microcontroller if I wanted to interface an IO expander. The MCP23017 16-bit IO expander may have been perfect for the job! Anyway, I drilled a few circular holes in the back with a step-bit and cut-away a large square hole in the front with a nibbler so everything would snap-in nicely.

img_8482

I soldered wires to intercept the signal as it left the device’s microcontroller and went into the LED display.

img_8517

I then soldered the wires directly to my microcontroller. I also have an extra header available for programming (seen at the bottom) which I was able to remove once the software was complete. The red clip is clamping the serial Tx pin of the microcontroller and capturing the output into a USB serial adapter. Initially I debugged this circuit using the microcontroller’s on-board RC oscillator (1MHz) transmitting at 600 baud. I later realized that the serial bluetooth module requires 9600 baud. Although I could hack this with the internal RC clock, it was very unstable and garbage characters kept coming through. Luckily I designed around the potential of using an external crystal (pins 9 and 10 were unused) so it was an easy fix to later drop in a 11.0592 MHz crystal to allow stable transmission at 9600 baud.

img_8548

Now you can see the power regulation (LM7805) providing power to the MCU and wireless bluetooth module. Here’s the HC-06 datasheet (which is similar to HC-05) and another web page demonstrating how to use the breakout board. Also, I added a switch on the back which switches the voltage sense wire between the power supply and a sense connector which is on the back of the project box (red plastic banana jack).

img_8554

The bluetooth adapter expects 3.3V signals, so I added a quick and easy zener diode shunt regulator. I could have accomplished this by running my MCU on 3.3V (I didn’t have 3.3V regulators on hand though, and even so the module wants >3.6V to power the wireless transmitter) or perhaps a voltage divider on the output. On second thought, why did I use a zener ($!) over a resistor? Maybe my brain is stuck thinking about USB protocol standards.

img_8557

Since the chip was unstable transmitting 9600 baud, I tightened it up using a 11.0592 MHz crystal. The advantage of making your entire circuit look sketchy is that bodge jobs like this blend in perfectly and are unrecognizable!

img_8563

A quick reprogram to set the AVR fuses to switch from internal clock to external full-swing crystal was easy thanks to the female header I was able to pop out. I only recently started soldering-on headers like this with ribbon cable, but it’s my new favorite thing! It makes programming so easy.

img_8559

I packed it all in then added hot glue around the primary components (not shown). Again, if this were a production product I would have designed the hardware very differently. Since it’s a one-off job, I’m happy with it exactly like it is! It works, and it withstands bumps and shakes, so it’s good enough for me.

img_8581

I tested on a big piece of electrical equipent I’m building on the other side of the room. This device has its own 13.8V regulated power supply (and its own shelf!), so the wireless capability is fantastic to have. I just dropped this device between the power supply and the device under test. Rather than record the power supply voltage (which would always be a boring 13.8V) I decided to record a voltage test point of interest: the point just downstream of an LM7809 voltage regulator. I expected this voltage to swing wildly as current draw was high, and was very interested to know the voltage of this test point with respect to current draw. Although I have previously used custom software to hack the output of a TENMA multimeter to let me log voltage or current of this exact circuit, now I can measure both at the same time! Additionally, this is a far less expensive option than dedicating a multimeter to the task.

img_8584

I’m using RealTerm to access the serial port and log its output to a text file.

img_8590

A quick python script lets me graph the voltage/current relationship with respect to time. The (short) code to do this is on the GitHub page, and is demonstrated in the YouTube video.

demo

Here’s some data which shows the relationship between voltage (red trace) probed just downstream of an LM7809 voltage regulator and the total current draw of the system (blue trace). This data was recorded in real time, wirelessly, from across the room! This is exactly the type of interesting reading I was hoping to see.

img_8599

Now that it’s all together, I’m very happy with the result! This little device is happy serving as a simple voltage/current display (which is convenient in itself), but has the added benefit of continuously being available as a Bluetooth device. If I ever want to run an experiment to log/graph data, I just wirelessly connect to it and start recording the data. This build was a one-off device and is quite a hack (coding and construction wise). If I were interested in making a product out of this design, construction would greatly benefit from surface mount components and a PCB, and perhaps not necessitate super glue. For what it is, I’m happy how it came out, pleased to see it as a Bluetooth device I can connect to whenever I want, and I won’t tell anyone there’s super glue inside if you don’t.

Code used for this project is available at GitHub