The personal website of Scott W Harden
June 28th, 2017

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.

The RADAR antenna is built into the front PCB. The back side of the module reveals the simplistic connections: VCC (5V), OUT, and GND.

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

I 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!

Markdown source code last modified on January 18th, 2021
---
title: Controlling Speakers with RADAR
date: 2017-06-28 21:56:24
tags: circuit
---

# 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!

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

__This project centers around a 5.8 GHz microwave radar sensor module__ ([HFS-DC06](http://www.icstation.com/microwave-radar-sensor-module-58ghz-waveband-392211mm-p-9551.html), $5.22 from [icstation.com](http://www.icstation.com/microwave-radar-sensor-module-58ghz-waveband-392211mm-p-9551.html), + 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](http://www.electronics-tutorials.ws/counter/count_1.html) (essentially a [flip-flop](https://en.wikipedia.org/wiki/Flip-flop_(electronics))) 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](http://www.ti.com/lit/ds/symlink/cd74hct574.pdf) comes to mind, [$0.54 on Mouser](http://www.mouser.com/ProductDetail/Texas-Instruments/SN74HC374N/)) but I did have a [74HC590 8-bit binary counter](https://assets.nexperia.com/documents/data-sheet/74HC590.pdf) on hand ([$0.61 on Mouser](http://www.mouser.com/ProductDetail/Texas-Instruments/SN74HC590AN/)) 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](https://en.wikipedia.org/wiki/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.

The RADAR antenna is built into the front PCB. The back side of the module reveals the simplistic connections: VCC (5V), OUT, and GND.

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

[![](03module_thumb.jpg)](03module.jpg)
[![](04module_thumb.jpg)](04module.jpg)

</div>

## __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](https://www.sparkfun.com/datasheets/Components/LM7805.pdf). 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](https://assets.nexperia.com/documents/data-sheet/74HC590.pdf) taking output of the div/2 pin. That output is fed into an [IRF510 N-channel MOSFET](http://www.vishay.com/docs/91015/sihf510.pdf) to switch current flow on the "DC output" on and off. A [Darlington transistor](https://en.wikipedia.org/wiki/Darlington_transistor) (i.e., [TIP122](http://www.mouser.com/ds/2/149/TIP122-890116.pdf)) 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](http://www.mouser.com/ProductDetail/Vishay-Semiconductors/IRF510PBF/)). Not shown is a status LED which is also on the output of the divide-by-2 chip (with a current limiting series resistor).

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

[![](schematic3_thumb.jpg)](schematic3.png)
[![](75hc590_divide_by_2_thumb.jpg)](75hc590_divide_by_2.png)
[![](75hc590_pinout_thumb.jpg)](75hc590_pinout.png)

</div>

## __Construction__

__I _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.

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

[![](1083_thumb.jpg)](1083.jpg)

</div>

## __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](https://www.adafruit.com/product/164) 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](https://www.sparkfun.com/products/13959) 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](http://www.ti.com/lit/ds/symlink/lm2904.pdf) dual op-amp and an [ATTiny13](http://www.atmel.com/Images/2535S.pdf) 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](https://en.wikipedia.org/wiki/555_timer_IC) or similar. A single-quantity ATTiny13 is [$0.58 on Mouser](http://www.mouser.com/ProductDetail/Microchip-Technology-Atmel/ATTINY13A-SSU) (as compared to [$0.36 for a 555](http://www.mouser.com/Semiconductors/Integrated-Circuits-ICs/_/N-6j73k?Keyword=555&FS=True&Ns=Pricing|0)) 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.

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

[![](teardown-3_thumb.jpg)](teardown-3.jpg)

</div>

__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!
April 29th, 2017

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 a 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. The 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 tool to have for electrical engineering hobbyists 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 16x2 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.

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 potential for 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.

Resources

Components

Tools

Software

Additional Resources

Markdown source code last modified on January 18th, 2021
---
title: Precision Pressure Meter Project
date: 2017-04-29 13:15:07
tags: microcontroller, circuit
---

# 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 a [MS5611 temperature sensor breakout board](http://www.icstation.com/ms5611-pressure-altitude-sensor-module-24bit-converter-p-10426.html) which was small enough to fit inside of a [plastic syringe](#links). The result is a small and inexpensive pressure sensor in a convenient form factor with a twist connector (a [Luer-Lok](https://en.wikipedia.org/wiki/Luer_taper) 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](http://www.ebay.com/sch/i.html?_nkw=CO2+aquarium+tubing). The MS5611 breakout board is small enough to fit inside a 10 mL syringe!

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

[![](20170423_171551_thumb.jpg)](20170423_171551.jpg)

</div>

__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](http://dangerousprototypes.com/docs/Bus_Pirate) - a _really_ convenient tool to have for electrical engineering hobbyists 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](#tools) 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](#tools) 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 [16x2 HD44780 character LCD display](#components) beautifully framed with a [laser-cut LCD bezel](#components) (from Tindie user [widgeneering](https://www.tindie.com/stores/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](#tools), 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.

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

## 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](http://www.hpinfotech.ro/MS5611-01BA03.pdf) 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!

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

![](10426_1_4531.jpg)
![](10426_3_5984.jpg)
[![](WIN_20170312_21_36_07_Pro_thumb.jpg)](WIN_20170312_21_36_07_Pro.jpg)
[![](WIN_20170312_21_32_56_Pro_thumb.jpg)](WIN_20170312_21_32_56_Pro.jpg)

</div>

__These are some photos of the break-out board__ from the [company's product page](http://icstation.com/ms5611-pressure-altitude-sensor-module-24bit-converter-p-10426.html) 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](http://www.hpinfotech.ro/MS5611-01BA03.pdf). 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](http://dangerousprototypes.com/docs/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](http://dangerousprototypes.com/docs/Bus_Pirate_I/O_Pin_Descriptions) 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.

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

[![](270_thumb.jpg)](270.jpg)

</div>

__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](http://dangerousprototypes.com/blog/bus-pirate-manual/i2c-guide/).

*   Open [RealTerm](#software) 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_


<div class="text-center">

[![](realterm3_thumb.jpg)](realterm3.png)

</div>

__For the most accurate readings__, use the algorithms on [page 7/20 of the datasheet](http://www.hpinfotech.ro/MS5611-01BA03.pdf) 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.

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

[![](310_thumb.jpg)](310.jpg)
[![](316_thumb.jpg)](316.jpg)
[![](327_thumb.jpg)](327.jpg)

</div>

## 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](https://en.wikipedia.org/wiki/JTAG)) that my first go-to was my [logic analyzer](#tools). 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.

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

[![](20170423_210831_thumb.jpg)](20170423_210831.jpg)

</div>

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

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

[![](saleae_thumb.jpg)](saleae.jpg)

</div>

## 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](https://www.tindie.com/stores/WIDGENEERING/)! Mounting character LCD displays on metal or plastic enclosures is a chore and usually looks awful. I cringe at [some of my old projects](https://www.swharden.com/wp/2011-03-14-frequency-counter-finished/) 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!

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

[![](20170408_120421_thumb.jpg)](20170408_120421.jpg)
[![](20170408_120633_thumb.jpg)](20170408_120633.jpg)
[![](342_thumb.jpg)](342.jpg)
[![](20170408_124012_thumb.jpg)](20170408_124012.jpg)

</div>

__I then proceeded to epoxy the connectors__ I wanted (USB and Ethernet) and drill holes for the PCB mount. I added the microcontroller ([ATMega328](http://www.microchip.com/wwwproducts/en/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.

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

[![](20170409_132514_HDR_thumb.jpg)](20170409_132514_HDR.jpg)

</div>

__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](http://hackaday.com/2017/02/08/taking-the-leap-off-board-an-introduction-to-i2c-over-long-wires/) 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.

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

[![](20170420_212832_thumb.jpg)](20170420_212832.jpg)

</div>

__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!

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

[![](20170422_172610_thumb.jpg)](20170422_172610.jpg)

</div>

## 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](#links), 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](https://electronics.stackexchange.com/questions/16400/excessive-avr-static-ram-usage-with-mixed-type-math-operations) 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](http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT232R.pdf) or its [breakout board](https://www.sparkfun.com/products/12731). 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:__

```c
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:__

```c
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. 

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

[![](20170329_232145_thumb.jpg)](20170329_232145.jpg)

</div>

**There's also potential for 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.

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

[![](20170423_214443_thumb.jpg)](20170423_214443.jpg)

</div>

## Resources

### Components

*   __Pressure sensor__ \[$7.79\] [MS5611 break-out board](http://www.icstation.com/ms5611-pressure-altitude-sensor-module-24bit-converter-p-10426.html) from [ICStation](http://www.ICStation.com) (15% discount code: haics)
*   __LCD Bezel__ \[$4.99\] [16x2 LCD Bezel](https://www.tindie.com/products/WIDGENEERING/16x2-lcd-bezel/) from Tindie user [Widgeneering](https://www.tindie.com/stores/WIDGENEERING/)
*   __LCD Display__ \[$1.50 in bulk\] 16x2 hd44780 character LCD with backlight [via eBay search](http://www.ebay.com/sch/i.html?_nkw=hd44780)
*   __LCD I2C adapter__ \[$3\] 1602-based [via eBay search](http://www.ebay.com/sch/i.html?_nkw=hd44780) (not really needed)
*   __Aluminum enclosure__ \[$11.50\] 4.32" x 3.14" x 1.43" size found [via eBay search](http://www.ebay.com/sch/i.html?_nkw=split+body+aluminum+enclosure)
*   __ATMega328 8-bit microcontroller__ \[~$1.50 each in bulk\] [via eBay search](http://www.ebay.com/sch/i.html?_nkw=atmega328)
*   __Plastic syringes__ and blunt needles \[cheap\] from [Amazon](https://www.amazon.com/Pack-Refilling-Measuring-E-Liquids-Adhesives/dp/B01CFJ51X4/)
*   __USB and Ethernet sockets and cables__ \[salvaged\]

### Tools

*   __Bus Pirate__ (v3) \[$27.15\] from [DangerousPrototypes.com](http://dangerousprototypes.com/docs/Bus_Pirate)
*   __AVR programmer__ (USBtinyISP) \[$4.99\] [via eBay search](http://www.ebay.com/sch/i.html?_nkw=usbtinyisp)\]
*   __Saleae Logic Analyzer__

    *   Saleae software (free) at [saleae.com/downloads](https://www.saleae.com/downloads)
    *   Official Logic Pro 8 \[$109\] or Logic Pro 16 \[$599\] from [saleae.com](https://www.saleae.com/)
    *   Knock-off 8 channel logic analyzer \[$6.93\] [via eBay search](http://www.ebay.com/sch/i.html?_nkw=saleae+logic+8)
    *   Knock-off 16 channel logic analyzer \[$39.50\] [via eBay search](http://www.ebay.com/sch/i.html?_nkw=saleae+logic+16)
    *   [Review of Knock-off hardware](https://iamzxlee.wordpress.com/2015/09/15/usb-logic-analyzer-review/) by iamzxlee
    *   [Review of Official hardware](http://hackaday.com/2015/05/26/review-dslogic-logic-analyzer/) on Hack-A-Day
    *   [Saleae's comments on counterfeit devices](https://www.saleae.com/counterfeit)

*   __Labels__ made with a [LetraTag LT-100T](https://www.amazon.com/DYMO-LetraTag-Personal-Hand-Held-1733013/dp/B001B1FIW2/) and [clear tape](https://www.amazon.com/DYMO-Labeling-LetraTag-Labelers-Black/dp/B00006B8FA) (tricks [learned from Onno](http://www.qsl.net/pa2ohh/tlabels.htm))

### Software

*   All code used for this project is on my [AVR-Projects GitHub page](https://github.com/swharden/AVR-projects)

    *   [Python code](https://github.com/swharden/AVR-projects/tree/master/BusPirate%202017-02-04%20i2c%20ms5661%20pressure) to interact with Bus Pirate
    *   [ATMega328 code](https://github.com/swharden/AVR-projects/tree/master/ATMega328%202017-03-19%20i2c%20LCD%20pressure%20sensor) to control the sensor and LCD with the microcontroller

*   [RealTerm on SourceForge](https://sourceforge.net/projects/realterm/) (serial console program I use to communicate with Bus Pirate)
*   [Saleae software](https://www.saleae.com/downloads) is free and works (for now) on clone hardware

### Additional Resources

*   [Bus Pirate I2C guide](http://dangerousprototypes.com/blog/bus-pirate-manual/i2c-guide/)
*   Hackaday article: [Sending I2C over long wires](http://hackaday.com/2017/02/08/taking-the-leap-off-board-an-introduction-to-i2c-over-long-wires/)
*   Application note: [Driving I2C bus signals over twisted pair cables with PCA9605](http://www.nxp.com/documents/application_note/AN11075.pdf)
*   My [AVR-Projects GitHub page](https://github.com/swharden/AVR-projects) has notes about AVR programmers and software
February 4th, 2017

Logging I2C Data with Bus Pirate and Python

I'm working on a project which requires I measure temperature via a computer, and I accomplished this with minimal complexity using a BusPirate and LM75A I2C temperature sensor. I already had some LM75A breakout boards I got on eBay (from China) a while back. A current eBay search reveals these boards are a couple dollars with free shipping. The IC itself is available on Mouser for $0.61 each. The LM75A datasheet reveals it can be powered from 2.8V-5.5V and has a resolution of 1/8 ºC (about 1/4 ºF). I attached the device to the Bus Pirate according to the Bus Pirate I/O Pin Descriptions page (SCL->CLOCK and SDA->MOSI) and started interacting with it according to the Bus Pirate I2C page. Since Phillips developed the I2C protocol, a lot of manufacturers avoid legal trouble and call it TWI (two-wire interface).

Here I show how to pull data from this I2C device directly via a serial terminal, then show my way of automating the process with Python. Note that there are multiple python packages out there that claim to make this easy, but in my experience they are either OS-specific or no longer supported or too confusing to figure out rapidly. For these reasons, I ended up just writing a script that uses common Python libraries so nothing special has to be installed.

Read data directly from a serial terminal

Before automating anything, I figured out what I2C address this chip was using and got some sample temperature readings directly from the serial terminal. I used RealTerm to connect to the Bus Pirate. The sequence of keystrokes I used are:

  • # - to reset the device

  • m - to enter the mode selection screen

    • 4 - to select I2C mode
    • 3 - to select 100KHz
  • W - to turn the power on

  • P - to enable pull-up resistors

  • (1) - to scan I2C devices

    • this showed the device listening on 0x91
  • [0x91 r:2] - to read 2 bytes from I2C address 0x91

    • this showed bytes like 0x1D and 0x20
    • 0x1D20 in decimal is 7456
    • according to datasheet, must divide by 2^8 (256)
    • 7456/256 = 29.125 C = 84.425 F

Automating Temperature Reads with Python

There should be an easy way to capture this data from Python. The Bus Pirate website even has a page showing how to read data from LM75, but it uses a pyBusPirateLite python package which has to be manually installed (it doesn't seem to be listed in pypi). Furthermore, they only have a screenshot of a partial code example (nothing I can copy or paste) and their link to the original article is broken. I found a cool pypy-indexed python module pyElectronics which should allow easy reading/writing from I2C devices via BusPirate and Raspberry Pi. However, it crashed immediately on my windows system due to attempting to load Linux-only python modules. I improved the code and issued a pull request, but I can't encourage use of this package at this time if you intend to log data in Windows. Therefore, I'm keeping it simple and using a self-contained script to interact with the Bus Pirate, make temperature reads, and graph the data over time. You can code-in more advanced features as needed. The graphical output of my script shows what happens when I breathe on the sensor (raising the temperature), then what happens when I cool it (by placing a TV dinner on top of it for a minute).

Below is the code used to set up the Bus Pirate to log and graph temperature data. It's not fast, but for temperature readings it doesn't have to be! It captures about 10 reads a second, and the rate-limiting step is the timeout value which is currently set to 0.1 sec.

NOTE: The Bus Pirate has a convenient binary scripting mode which can speed all this up. I'm not using that mode in this script, simply because I'm trying to closely mirror the functionality of directly typing things into the serial console.

import serial
import matplotlib.pyplot as plt

BUSPIRATE_PORT = 'com3' #customize this! Find it in device manager.

def send(ser,cmd,silent=False):
    """
    send the command and listen to the response.
    returns a list of the returned lines.
    The first item is always the command sent.
    """
    ser.write(str(cmd+'\n').encode('ascii')) # send our command
    lines=[]
    for line in ser.readlines(): # while there's a response
        lines.append(line.decode('utf-8').strip())
    if not silent:
        print("\n".join(lines))
        print('-'*60)
    return lines

def getTemp(ser,address='0x91',silent=True,fahrenheit=False):
    """return the temperature read from an LM75"""
    unit=" F" if fahrenheit else " C"
    lines=send(ser,'[%s r:2]'%address,silent=silent) # read two bytes
    for line in lines:
        if line.startswith("READ:"):
            line=line.split(" ",1)[1].replace("ACK",'')
            while "  " in line:
                line=" "+line.strip().replace("  "," ")
            line=line.split(" 0x")
            val=int("".join(line),16)
            # conversion to C according to the datasheet
            if val < 2**15:
                val = val/2**8
            else:
                val =  (val-2**16)/2**8
            if fahrenheit:
                val=val*9/5+32
            print("%.03f"%val+unit)
            return val

# the speed of sequential commands is determined by this timeout
ser=serial.Serial(BUSPIRATE_PORT, 115200, timeout=.1)

# have a clean starting point
send(ser,'#',silent=True) # reset bus pirate (slow, maybe not needed)
#send(ser,'v') # show current voltages

# set mode to I2C
send(ser,'m',silent=True) # change mode (goal is to get away from HiZ)
send(ser,'4',silent=True) # mode 4 is I2C
send(ser,'3',silent=True) # 100KHz
send(ser,'W',silent=True) # turn power supply to ON. Lowercase w for OFF.
send(ser,'P',silent=True) # enable pull-up resistors
send(ser,'(1)') # scan I2C devices. Returns "0x90(0x48 W) 0x91(0x48 R)"

data=[]
try:
    print("reading data until CTRL+C is pressed...")
    while True:
        data.append(getTemp(ser,fahrenheit=True))
except:
    print("exception broke continuous reading.")
    print("read %d data points"%len(data))

ser.close() # disconnect so we can access it from another app

plt.figure(figsize=(6,4))
plt.grid()
plt.plot(data,'.-',alpha=.5)
plt.title("LM75 data from Bus Pirate")
plt.ylabel("temperature")
plt.xlabel("number of reads")
plt.show()

print("disconnected!") # let the user know we're done.

Experiment: Measuring Heater Efficacy

This project now now ready for an actual application test. I made a simple heater circuit which could be driven by an analog input, PWM, or digital ON/OFF. Powered from 12V it can pass 80 mA to produce up to 1W of heat. This may dissipate up to 250 mW of heat in the transistor if partially driven, so keep this in mind if an analog signal drive is used (i.e., thermistor / op-amp circuit). Anyhow, I soldered this up with SMT components on a copper-clad PCB with slots drilled on it and decided to give it a go. It's screwed tightly to the temperature sensor board, but nothing special was done to ensure additional thermal conductivity. This is a pretty crude test.

Drilled Slots Facilitate SMT Placement

All Components Soldered

Heater Mates with Sensor Board

Headers are Easily Accessible

LED Indicates Heater Current

Styrofoam Igloo

Temperature Control Circuit

I ran an experiment to compare open-air heating/cooling vs. igloo conditions, as well as low vs. high heater drive conditions. The graph below shows these results. The "heating" ranges are indicated by shaded areas. The exposed condition is when the device is sitting on the desk without any insulation. A 47k resistor is used to drive the base of the transistor (producing less than maximal heating). I then repeated the same thing after the device was moved inside the igloo. I killed the heater power when it reached the same peak temperature as the first time, noticing that it took less time to reach this temperature. Finally, I used a 1k resistor on the base of the transistor and got near-peak heating power (about 1W). This resulted in faster heating and a higher maximum temperature. If I clean this enclosure up a bit, this will be a nice way to test software-based PID temperature control with slow PWM driving the base of the transistor.

Code to create file logging (csv data with timestamps and temperatures) and produce plots lives in the 'file logging' folder of the Bus Pirate LM75A project on the GitHub page.

Experiment: Challenging LM7805 Thermal Shutdown

The ubiquitous LM7805 linear voltage regulator offers internal current limiting (1.5A) and thermal shutdown. I've wondered for a long time if I could use this element as a heater. It's TO-220 package is quite convenient to mount onto enclosures. To see how quickly it heats up and what temperature it rests at, screwed a LM7805 directly to the LM75A breakout board (with a dab of thermal compound). I soldered the output pin to ground (!!!) and recorded temperature while it was plugged in.

[gallery size="medium" link="file" ids="6449,6450,6451"]

Power (12V) was applied to the LM7805 over the red-shaded region. It looks like it took about 2 minutes to reach maximum temperature, and settled around 225F. After disconnecting power, it settled back to room temperature after about 5 minutes. I'm curious if this type of power dissipation is sustainable long term...

Update: Reading LM75A values directly into an AVR

This topic probably doesn't belong inside this post, but it didn't fit anywhere else and I don't want to make it its own post. Now that I have this I2C sensor mounted where I want it, I want a microcontroller to read its value and send it (along with some other data) via serial USART to an FT232 (USB serial adapter). Ultimately I want to take advantage of its comparator thermostat function so I can have a USB-interfaced PC-controllable heater with multiple LM75A ICs providing temperature readings at different sites in my project. To do this, I had to write code to interface my microcontroller to the LM75A. I am using an ATMega328 (ATMega328P) with AVR-GCC (not Arduino). Although there are multiple LM75A senor libraries for Arduino [link] [link] [link] I couldn't find any examples which didn't rely on Arduino libraries. I ended up writing functions around g4lvanix's L2C-master-lib.

Here's a relevant code snippit. See the full code (with compile notes) on this GitHub page:

uint8_t data[2]; // prepare variable to hold sensor data
uint8_t address=0x91; // this is the i2c address of the sensor
i2c_receive(address,data,2); // read and store two bytes
temperature=(data[0]*256+data[1])/32; // convert two bytes to temperature

This project is on GitHub: https://github.com/swharden/AVR-projects

Markdown source code last modified on January 18th, 2021
---
title: Logging I2C Data with Bus Pirate and Python
date: 2017-02-04 13:13:44
tags: microcontroller, circuit
---

# Logging I2C Data with Bus Pirate and Python

__I'm working on a project which requires I measure temperature via a computer, and I accomplished this with minimal complexity using a BusPirate and LM75A I2C temperature sensor.__ I already had some LM75A breakout boards I got on eBay (from China) a while back. A [current eBay search reveals](http://www.ebay.com/sch/?_nkw=LM75A) these boards are a couple dollars with free shipping. The IC itself is [available on Mouser](http://www.mouser.com/ProductDetail/NXP-Semiconductors/LM75AD118) for $0.61 each. The [LM75A datasheet](http://www.mouser.com/ds/2/302/LM75A-841329.pdf) reveals it can be powered from 2.8V-5.5V and has a resolution of 1/8 ºC (about 1/4 ºF). I attached the device to the Bus Pirate according to the [Bus Pirate I/O Pin Descriptions](http://dangerousprototypes.com/docs/Bus_Pirate_I/O_Pin_Descriptions) page (SCL->CLOCK and SDA->MOSI) and started interacting with it according to the [Bus Pirate I2C page](http://dangerousprototypes.com/docs/I2C). Since Phillips developed the [I2C protocol,](https://en.wikipedia.org/wiki/I%C2%B2C) a lot of manufacturers avoid legal trouble and call it TWI (two-wire interface).

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

[![](busPirate_LM75A_thumb.jpg)](busPirate_LM75A.jpeg)

</div>

__Here I show how to pull data from this I2C device directly via a serial terminal, then show my way of automating the process with Python.__ Note that there are multiple python packages out there that claim to make this easy, but in my experience they are either OS-specific or no longer supported or too confusing to figure out rapidly. For these reasons, I ended up just writing a script that uses common Python libraries so nothing special has to be installed.

### Read data directly from a serial terminal

Before automating anything, I figured out what I2C address this chip was using and got some sample temperature readings directly from the serial terminal. I used [RealTerm ](https://sourceforge.net/projects/realterm/)to connect to the Bus Pirate. The sequence of keystrokes I used are:

*   __\#__ - to reset the device
*   __m__ - to enter the mode selection screen

    *   __4__ - to select I2C mode
    *   __3__ - to select 100KHz

*   __W__ - to turn the power on
*   __P__ - to enable pull-up resistors
*   __(1)__ - to scan I2C devices

    *   _this showed the device listening on 0x91_

*   __\[0x91 r:2\]__ - to read 2 bytes from I2C address 0x91

    *   _this showed bytes like 0x1D and 0x20_
    *   _0x1D20 in decimal is 7456_
    *   _according to datasheet, must divide by 2^8 (256)_
    *   _7456/256 = 29.125 C = 84.425 F_

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

[![](BusPirate_i2c_read_thumb.jpg)](BusPirate_i2c_read.png)
[![](BusPirate_i2c_scan_thumb.jpg)](BusPirate_i2c_scan.png)

</div>

### __Automating Temperature Reads with Python__

__There should be an easy way to capture this data from Python.__ The Bus Pirate website even [has a page](http://dangerousprototypes.com/blog/2014/11/05/bus-pirate-v3-and-lm75-temperature-sensors/) showing how to read data from LM75, but it uses a pyBusPirateLite python package which has to be manually installed (it doesn't seem to be listed in pypi). Furthermore, they only have a screenshot of a partial code example (nothing I can copy or paste) and their link to the original article is broken. I found a cool pypy-indexed python module _[pyElectronics](https://github.com/MartijnBraam/pyElectronics)_ which should allow easy reading/writing from I2C devices via BusPirate and Raspberry Pi. However, it crashed immediately on my windows system due to attempting to load Linux-only python modules. I improved the code and issued a pull request, but I can't encourage use of this package at this time if you intend to log data in Windows. Therefore, I'm keeping it simple and using a self-contained script to interact with the Bus Pirate, make temperature reads, and graph the data over time. You can code-in more advanced features as needed.__ The graphical output__ of my script shows what happens when I breathe on the sensor (raising the temperature), then what happens when I cool it (by placing a TV dinner on top of it for a minute). 

<div class="text-center">

[![](demo_thumb.jpg)](demo.png)

</div>

__Below is the code__ used to set up the Bus Pirate to log and graph temperature data. It's not fast, but for temperature readings it doesn't have to be! It captures about 10 reads a second, and the rate-limiting step is the timeout value which is currently set to 0.1 sec.

__NOTE:__ The Bus Pirate has a convenient [_binary scripting mode_](http://dangerousprototypes.com/docs/Bus_Pirate#Binary_scripting_mode) which can speed all this up. I'm not using that mode in this script, simply because I'm trying to closely mirror the functionality of directly typing things into the serial console.

```python
import serial
import matplotlib.pyplot as plt

BUSPIRATE_PORT = 'com3' #customize this! Find it in device manager.

def send(ser,cmd,silent=False):
    """
    send the command and listen to the response.
    returns a list of the returned lines.
    The first item is always the command sent.
    """
    ser.write(str(cmd+'\n').encode('ascii')) # send our command
    lines=[]
    for line in ser.readlines(): # while there's a response
        lines.append(line.decode('utf-8').strip())
    if not silent:
        print("\n".join(lines))
        print('-'*60)
    return lines

def getTemp(ser,address='0x91',silent=True,fahrenheit=False):
    """return the temperature read from an LM75"""
    unit=" F" if fahrenheit else " C"
    lines=send(ser,'[%s r:2]'%address,silent=silent) # read two bytes
    for line in lines:
        if line.startswith("READ:"):
            line=line.split(" ",1)[1].replace("ACK",'')
            while "  " in line:
                line=" "+line.strip().replace("  "," ")
            line=line.split(" 0x")
            val=int("".join(line),16)
            # conversion to C according to the datasheet
            if val < 2**15:
                val = val/2**8
            else:
                val =  (val-2**16)/2**8
            if fahrenheit:
                val=val*9/5+32
            print("%.03f"%val+unit)
            return val

# the speed of sequential commands is determined by this timeout
ser=serial.Serial(BUSPIRATE_PORT, 115200, timeout=.1)

# have a clean starting point
send(ser,'#',silent=True) # reset bus pirate (slow, maybe not needed)
#send(ser,'v') # show current voltages

# set mode to I2C
send(ser,'m',silent=True) # change mode (goal is to get away from HiZ)
send(ser,'4',silent=True) # mode 4 is I2C
send(ser,'3',silent=True) # 100KHz
send(ser,'W',silent=True) # turn power supply to ON. Lowercase w for OFF.
send(ser,'P',silent=True) # enable pull-up resistors
send(ser,'(1)') # scan I2C devices. Returns "0x90(0x48 W) 0x91(0x48 R)"

data=[]
try:
    print("reading data until CTRL+C is pressed...")
    while True:
        data.append(getTemp(ser,fahrenheit=True))
except:
    print("exception broke continuous reading.")
    print("read %d data points"%len(data))

ser.close() # disconnect so we can access it from another app

plt.figure(figsize=(6,4))
plt.grid()
plt.plot(data,'.-',alpha=.5)
plt.title("LM75 data from Bus Pirate")
plt.ylabel("temperature")
plt.xlabel("number of reads")
plt.show()

print("disconnected!") # let the user know we're done.

```

### __Experiment: Measuring Heater Efficacy__

This project now now ready for an actual application test. I made a simple heater circuit which could be driven by an analog input, PWM, or digital ON/OFF. Powered from 12V it can pass 80 mA to produce up to 1W of heat. This may dissipate up to 250 mW of heat in the transistor if partially driven, so keep this in mind if an analog signal drive is used (i.e., thermistor / op-amp circuit). Anyhow, I soldered this up with SMT components on a copper-clad PCB with slots drilled on it and decided to give it a go. It's screwed tightly to the temperature sensor board, but nothing special was done to ensure additional thermal conductivity. This is a pretty crude test.

### Drilled Slots Facilitate SMT Placement
<div class="text-center img-border">

[![](File_000_thumb.jpg)](File_000.jpeg)

</div>

### All Components Soldered
<div class="text-center img-border">

[![](File_001_thumb.jpg)](File_001.jpeg)

</div>

### Heater Mates with Sensor Board
<div class="text-center img-border">

[![](File_003_thumb.jpg)](File_003.jpeg)

</div>

### Headers are Easily Accessible
<div class="text-center img-border">

[![](File_002_thumb.jpg)](File_002.jpeg)

</div>

### LED Indicates Heater Current
<div class="text-center img-border">

[![](File_004_thumb.jpg)](File_004.jpeg)

</div>

### Styrofoam Igloo
<div class="text-center img-border">

[![](File_005_thumb.jpg)](File_005.jpeg)
[![](File_006_thumb.jpg)](File_006.jpeg)

</div>

### Temperature Control Circuit
<div class="text-center img-border">

[![](File_007_thumb.jpg)](File_007.jpeg)

</div>

__I ran an experiment to compare open-air heating/cooling vs. igloo conditions, as well as low vs. high heater drive conditions.__ The graph below shows these results. The "heating" ranges are indicated by shaded areas. The exposed condition is when the device is sitting on the desk without any insulation. A 47k resistor is used to drive the base of the transistor (producing less than maximal heating). I then repeated the same thing after the device was moved inside the igloo. I killed the heater power when it reached the same peak temperature as the first time, noticing that it took less time to reach this temperature. Finally, I used a 1k resistor on the base of the transistor and got near-peak heating power (about 1W). This resulted in faster heating and a higher maximum temperature. If I clean this enclosure up a bit, this will be a nice way to test software-based [PID temperature control](https://en.wikipedia.org/wiki/PID_controller) with slow PWM driving the base of the transistor.

<div class="text-center">

[![](experiment2_thumb.jpg)](experiment2.png)

</div>

Code to create file logging (csv data with timestamps and temperatures) and produce plots lives in the 'file logging' folder of the Bus Pirate LM75A project on the GitHub page.

### __Experiment: Challenging LM7805 Thermal Shutdown__

The ubiquitous LM7805 linear voltage regulator offers internal current limiting (1.5A) and thermal shutdown. I've wondered for a long time if I could use this element as a heater. It's TO-220 package is quite convenient to mount onto enclosures. To see how quickly it heats up and what temperature it rests at, screwed a LM7805 directly to the LM75A breakout board (with a dab of thermal compound). I soldered the output pin to ground (!!!) and recorded temperature while it was plugged in.

[gallery size="medium" link="file" ids="6449,6450,6451"]

Power (12V) was applied to the LM7805 over the red-shaded region. It looks like it took about 2 minutes to reach maximum temperature, and settled around 225F. After disconnecting power, it settled back to room temperature after about 5 minutes. I'm curious if this type of power dissipation is sustainable long term...

<div class="text-center">

[![](data_thumb.jpg)](data.png)

</div>

### Update: Reading LM75A values directly into an AVR

This topic probably doesn't belong inside this post, but it didn't fit anywhere else and I don't want to make it its own post. Now that I have this I2C sensor mounted where I want it, I want a microcontroller to read its value and send it (along with some other data) via serial USART to an FT232 (USB serial adapter). Ultimately I want to take advantage of its comparator thermostat function so I can have a USB-interfaced PC-controllable heater with multiple LM75A ICs providing temperature readings at different sites in my project. To do this, I had to write code to interface my microcontroller to the LM75A. I am using an ATMega328 (ATMega328P) with AVR-GCC (_not_ Arduino). Although there are multiple LM75A senor libraries for Arduino [[link](https://github.com/QuentinCG/Arduino-LM75A-Temperature-Sensor-Library)] [[link](https://github.com/thefekete/LM75)] [[link](https://piandmore.wordpress.com/2016/09/21/arduino-and-lm75a/)] I couldn't find any examples which didn't rely on Arduino libraries. I ended up writing functions around [g4lvanix's L2C-master-lib](https://github.com/g4lvanix/I2C-master-lib).

<div class="text-center">

![](circuit2.jpg)

</div>

__Here's a relevant code snippit.__ See the full code (with compile notes) on [this GitHub page](https://github.com/swharden/AVR-projects/tree/master/ATMega328%202017-02-08%20i2c%20LM75A%20thermometer):

```c
uint8_t data[2]; // prepare variable to hold sensor data
uint8_t address=0x91; // this is the i2c address of the sensor
i2c_receive(address,data,2); // read and store two bytes
temperature=(data[0]*256+data[1])/32; // convert two bytes to temperature

```

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

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

[![](demo-1_thumb.jpg)](demo-1.png)

</div>

This project is on GitHub: https://github.com/swharden/AVR-projects
February 2nd, 2017

1 Rotary Encoder, 3 Pins, 6 Inputs

Rotary encoders are a convenient way to add complex input functionality to small hardware projects with a single component. Rotary encoders (sometimes called shaft encoders, or rotary shaft encoders) can spin infinitely in both directions and many of them can be pressed like a button. The volume knob on your car radio is probably a rotary encoder.

With a single component and 3 microcontroller pins I can get six types of user input: turn right, turn left, press-and-turn right, press-and-turn left, press and release, and press and hold.

A few years ago I made a video discussing how rotary shaft encoders work and how to interface them with microcontrollers. Although I'm happy it has over 13,000 views, I'm disappointed I never posted the code or schematics on my website (despite the fact I said on the video I would). A few years later I couldn't find the original code anymore, and now that I'm working on a project using these devices I decided to document a simple case usage of this component.

This post is intended to be a resource for future me just as much as it is anyone who finds it via Google or YouTube. This project will permanently live in a "rotary encoder" folder of my AVR projects GitHub page: AVR-projects. For good measure, I made a follow-up YouTube video which describes a more simple rotary encoder example and that has working links to this code.

At about $.50 each, rotary encoders are certainly more expensive than other switches (such as momentary switches). A quick eBay search reveals these components can be purchased from china in packs of 10 for $3.99 with free shipping. On Mouser similar components are about $0.80 individually, cut below $0.50 in quantities of 200. The depressible kind have two pins which are shorted when the button is pressed. The rotary part has 3 pins, which are all open in the normal state. Assuming the center pin is grounded, spinning the knob in one direction or the other will temporarily short both of the other pins to ground, but slightly staggered from each other. The order of this stagger indicates which direction the encoder was rotated.

I typically pull these all high through 10k series resistors (debounced with a 0.1uF capacitor to ground to reduce accidental readings) and sense their state directly with a microcontroller. Although capacitors were placed where they are to facilitate a rapid fall time and slower rise time, their ultimate goal is high-speed integration of voltage on the line as a decoupling capacitor for potential RF noise which may otherwise get into the line. Extra hardware debouching could be achieved by adding an additional series resistor immediately before the rotary encoder switch. For my simple application, I feel okay omitting these. If you want to be really thorough, you may benefit from adding a Schmidt trigger between the output and the microcontroller as well. Note that I can easily applying time-dependent debouncing via software as well.

Single Click Left and Right

Spin Left and Right

Code Notes

Setting-up PWM on ATTiny2313

I chose to use the 16-bit Timer/Counter to generate the PWM. 16-bits of duty control feels excessive for controlling an LED brightness, but my ultimate application will use a rotary encoder to finely and coarsely adjust a radio frequency, so there is some advantage to having this fine level of control. To round things out to a simple value, I'm capping the duty at 10,000 rather than the full 65,535. This way I can set the duty to 50% easily by setting OCR1A to 5,000. Similarly, spinning left/right can adjust duty by 100, and push-and-turn can adjust by 1,000.

void setupPWM_16bit(){
    DDRB|=(1<<PB3); // enable 16-bit PWM output on PB3
    TCCR1A|=(1<<COM1A1); // Clear OC1A/OC1B on Compare Match
    TCCR1B|=(1<<WGM13); // enable "PWM, phase and frequency correct"
    TCCR1B|=(1<<CS10); // enable output with the fastest clock (no prescaling)
    ICR1=10000; // set the top value (could be up to 2^16)
    OCR1A=5000; // set PWM pulse width (starts at 50% duty)
}

Simple (spin only) Rotary Encoder Polling

void poll_encoder_v1(){
    // polls for turns only
    if (~PINB&(1<<PB2)) {
        if (~PINB&(1<<PB1)){
            // left turn
            duty_decrease(100);
        } else {
            // right turn
            duty_increase(100);
        }
        _delay_ms(2); // force a little down time before continuing
        while (~PINB&(1<<PB2)){} // wait until R1 comes back high
    }
}

Simple (spin only) Rotary Encoder Polling

void poll_encoder_v2(){
    // polls for turns as well as push+turns
    if (~PINB&(1<<PB2)) {
        if (~PINB&(1<<PB1)){
            if (PINB&(1<<PB0)){
                // left turn
                duty_decrease(100);
            } else {
                // left press and turn
                duty_decrease(1000);
            }
        } else {
            if (PINB&(1<<PB0)){
                // right turn
                duty_increase(100);
            } else {
                // right press and turn
                duty_increase(1000);
            }
        }
        _delay_ms(2); // force a little down time before continuing
        while (~PINB&(1<<PB2)){} // wait until R1 comes back high
    }
}

What about an interrupt-based method?

A good compromise between continuous polling and reading pins only when we need to is to take advantage of the pin change interrupts. Briefly, we import avr/interrupt.h, set GIMSK, EIFR, and PCMSK (definitely read the datasheet) to trigger a hardware interrupt when a pin state change is detected on any of the 3 inputs. Then we run sei(); to enable global interrupts, and our functionality is identical without having to continuously call our polling function!

// run this only when pin state changes
ISR(PCINT_vect){poll_encoder_v2();}

int main(void){
    setupPWM_16bit();

    // set up pin change interrupts
    GIMSK=(1<<PCIE); // Pin Change Interrupt Enable
    EIFR=(1<<PCIF); // Pin Change Interrupt Flag
    PCMSK=(1<<PCINT1)|(1<<PCINT2)|(1<<PCINT3); // watch these pins
    sei(); // enable global interrupts

    for(;;){} //forever
}

Code for this project is available on the GitHub: https://github.com/swharden/AVR-projects

Markdown source code last modified on January 18th, 2021
---
title: 1 Rotary Encoder, 3 Pins, 6 Inputs
date: 2017-02-02 22:48:53
tags: microcontroller, circuit
---

# 1 Rotary Encoder, 3 Pins, 6 Inputs

__Rotary encoders are a convenient way to add complex input functionality to small hardware projects with a single component. __Rotary encoders (sometimes called shaft encoders, or rotary shaft encoders) can spin infinitely in both directions and many of them can be pressed like a button. The volume knob on your car radio is probably a rotary encoder.

> With a single component and 3 microcontroller pins I can get six types of user input: turn right, turn left, press-and-turn right, press-and-turn left, press and release, and press and hold.

__A few years ago I [made a video](https://www.youtube.com/watch?v=DREGVc00FY8) discussing how rotary shaft encoders work and how to interface them with microcontrollers.__ Although I'm happy it has over 13,000 views, I'm disappointed I never posted the code or schematics on my website (despite the fact I said on the video I would). A few years later I couldn't find the original code anymore, and now that I'm working on a project using these devices I decided to document a simple case usage of this component.

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

This post is intended to be a resource for future me just as much as it is anyone who finds it via Google or YouTube. This project will permanently live in a "rotary encoder" folder of my AVR projects GitHub page: [AVR-projects](https://github.com/swharden/AVR-projects). For good measure, I made a follow-up YouTube video which describes a more simple rotary encoder example and that has working links to this code.

__At about $.50 each,__ rotary encoders are certainly more expensive than other switches (such as momentary switches). A [quick eBay search](http://www.ebay.com/sch/?_nkw=rotary+encoder+10pcs) reveals these components can be purchased from china in packs of 10 for $3.99 with free shipping. [On Mouser similar components](http://www.mouser.com/ProductDetail/BI-Technologies-TT-Electronics/EN12-HN22AF25) are about $0.80 individually, cut below $0.50 in quantities of 200. The depressible kind have two pins which are shorted when the button is pressed. The rotary part has 3 pins, which are all open in the normal state. Assuming the center pin is grounded, spinning the knob in one direction or the other will temporarily short both of the other pins to ground, but slightly staggered from each other. The order of this stagger indicates which direction the encoder was rotated.

<div class="text-center">

[![](schematic_thumb.jpg)](schematic.png)

</div>

I typically pull these all high through 10k series resistors ([debounced](http://www.labbookpages.co.uk/electronics/debounce.html) with a 0.1uF capacitor to ground to reduce accidental readings) and sense their state directly with a microcontroller. Although capacitors were placed where they are to facilitate a rapid fall time and slower rise time, their ultimate goal is high-speed integration of voltage on the line as a decoupling capacitor for potential RF noise which may otherwise get into the line. Extra hardware debouching could be achieved by adding an additional series resistor immediately before the rotary encoder switch. For my simple application, I feel okay omitting these. If you want to be really thorough, you may benefit from adding a Schmidt trigger between the output and the microcontroller as well. Note that I can easily applying time-dependent debouncing via software as well.

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

[![](scope_thumb.jpg)](scope.jpeg)
[![](704_thumb.jpg)](704.jpg)

</div>

### Single Click Left and Right

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

[![](left_thumb.jpg)](left.png)
[![](right_thumb.jpg)](right.png)

</div>

### Spin Left and Right

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

[![](fastLeft_thumb.jpg)](fastLeft.png)
[![](fastRight_thumb.jpg)](fastRight.png)

</div>

## Code Notes

### Setting-up PWM on ATTiny2313

I chose to use the 16-bit Timer/Counter to generate the PWM. 16-bits of duty control feels excessive for controlling an LED brightness, but my ultimate application will use a rotary encoder to finely and coarsely adjust a radio frequency, so there is some advantage to having this fine level of control. To round things out to a simple value, I'm capping the duty at 10,000 rather than the full 65,535. This way I can set the duty to 50% easily by setting OCR1A to 5,000. Similarly, spinning left/right can adjust duty by 100, and push-and-turn can adjust by 1,000.

```c
void setupPWM_16bit(){
    DDRB|=(1<<PB3); // enable 16-bit PWM output on PB3
    TCCR1A|=(1<<COM1A1); // Clear OC1A/OC1B on Compare Match
    TCCR1B|=(1<<WGM13); // enable "PWM, phase and frequency correct"
    TCCR1B|=(1<<CS10); // enable output with the fastest clock (no prescaling)
    ICR1=10000; // set the top value (could be up to 2^16)
    OCR1A=5000; // set PWM pulse width (starts at 50% duty)
}
```

### Simple (spin only) Rotary Encoder Polling

```c
void poll_encoder_v1(){
    // polls for turns only
    if (~PINB&(1<<PB2)) {
        if (~PINB&(1<<PB1)){
            // left turn
            duty_decrease(100);
        } else {
            // right turn
            duty_increase(100);
        }
        _delay_ms(2); // force a little down time before continuing
        while (~PINB&(1<<PB2)){} // wait until R1 comes back high
    }
}
```

### Simple (spin only) Rotary Encoder Polling

```c
void poll_encoder_v2(){
    // polls for turns as well as push+turns
    if (~PINB&(1<<PB2)) {
        if (~PINB&(1<<PB1)){
            if (PINB&(1<<PB0)){
                // left turn
                duty_decrease(100);
            } else {
                // left press and turn
                duty_decrease(1000);
            }
        } else {
            if (PINB&(1<<PB0)){
                // right turn
                duty_increase(100);
            } else {
                // right press and turn
                duty_increase(1000);
            }
        }
        _delay_ms(2); // force a little down time before continuing
        while (~PINB&(1<<PB2)){} // wait until R1 comes back high
    }
}
```

### What about an interrupt-based method?

A good compromise between continuous polling and reading pins only when we need to is to take advantage of the pin change interrupts. Briefly, we import avr/interrupt.h, set GIMSK, EIFR, and PCMSK (definitely read the datasheet) to trigger a hardware interrupt when a pin state change is detected on any of the 3 inputs. Then we run sei(); to enable global interrupts, and our functionality is identical without having to continuously call our polling function!

```c
// run this only when pin state changes
ISR(PCINT_vect){poll_encoder_v2();}

int main(void){
    setupPWM_16bit();

    // set up pin change interrupts
    GIMSK=(1<<PCIE); // Pin Change Interrupt Enable
    EIFR=(1<<PCIF); // Pin Change Interrupt Flag
    PCMSK=(1<<PCINT1)|(1<<PCINT2)|(1<<PCINT3); // watch these pins
    sei(); // enable global interrupts

    for(;;){} //forever
}
```

Code for this project is available on the GitHub: https://github.com/swharden/AVR-projects
October 10th, 2016

Raspberry Pi RF Frequency Counter

I build a lot of RF circuits, and often it's convenient to measure and log frequency with a computer. Previously I've built standalone frequency counters, frequency counters with a PC interface, and even hacked a classic frequency counter to add USB interface (twice, actually). My latest device uses only 2 microchips to provide a Raspberry Pi with RF frequency measurement capabilities. The RF signal clocks a 32-bit counter SN74LV8154 ($1.04 on Mouser) connected to a 16-bit IO expander MCP23017 ($1.26 on Mouser) accessable to the Raspberry Pi (via I²C) to provide real-time frequency measurements from a python script for $2.30 in components! Well, plus the cost of the Raspberry Pi. All files for this project are on my GitHub page.

The entire circuit is only two microchips! I have a few passives to clean up the RF signal (the RF input is loaded with a 1k resistor to ground, decoupled through a series 100 nF capacitor, and balanced at VCC/2 through a voltage divider of two 47k resistors), but if the measured signal is already a strong square wave they could be omitted. The circuit requires a gate pulse which typically will be 1 pulse per second (1PPS) and can be generated by dividing-down a 32.768kHz oscillator, a spare pin on a microcontroller, a fancy 1PPS time reference, or like in my case a GPS module (Neo-6M) with 1PPS output to provide an extremely accurate gate.

The connections are intuitive! The I2C address is 0x20 when A0, A1, and A2 are grounded. GPB(1-4) control the register select of the counter, and GPA(0-7) reads each bit of the selected register. The whole thing is controlled from Python, but could be trivially written in any language.

Here's a quick summary describing how the code works: First I send bytes to address 0 and 1 to set all pins of GPIO A as inputs, and GPIO B as outputs. Note that only 4 of 8 pins are used for the output, so technically 4 extra pins could be used for things like blinking LEDs or controlling other devices. I then set the register select pins by sending a value to 0x13 (GPIO B), and read the entire GPIO A bus (INTCAPB, 0x18). For address details, consult the datasheet. I do this 4 times (1 for each byte of the 32-bit counter), do a little math to turn it into a frequency value, and compare the current value with the last value and take the difference to display as the measured frequency.

An advantage of this continuously running mode is that no clock cycles are lost, so a gate which accidentally fires a bit early due to jitter and cuts-off a cycle will compensate for it on a subsequent read. This is shown above, as a very stable 10MHz frequency reference is measured with this method. A "slow" 1PPS clock tick causes a reading slightly higher, compensated-for by the next reading being slightly lower. In this way, clock sources which are extremely accurate but suffer from low precision (like GPS time sources) are able to maximize the long-term measurement of frequency. Combining this frequency measurement technique with the ability to generate an analog voltage with a Raspberry Pi will allow me to perform some interesting experiments with a voltage controlled crystal oscillator.

Markdown source code last modified on January 18th, 2021
---
title: Raspberry Pi RF Frequency Counter
date: 2016-10-10 02:58:41
---

# Raspberry Pi RF Frequency Counter

__I build a lot of RF circuits, and often it's convenient to measure and log frequency with a computer.__ Previously I've built [standalone frequency counters](https://www.swharden.com/wp/2011-03-14-frequency-counter-finished/), [frequency counters with a PC interface](https://www.swharden.com/wp/2016-09-05-vhf-frequency-counter-with-pc-interface/), and even [hacked a classic frequency counter to add USB interface](https://www.swharden.com/wp/2011-07-11-aj4vd-arsenal-recently-expanded/) ([twice](https://www.swharden.com/wp/2013-06-22-adding-usb-to-a-cheap-frequency-counter-again/), actually). My latest device uses only 2 microchips to provide a [Raspberry Pi](https://www.raspberrypi.org/) with RF frequency measurement capabilities. The RF signal clocks a 32-bit counter [SN74LV8154](http://www.ti.com/lit/ds/symlink/sn74lv8154.pdf) ($1.04 [on Mouser](http://www.mouser.com/Search/Refine.aspx?Keyword=SN74LV8154)) connected to a 16-bit IO expander [MCP23017](http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf) ($1.26 [on Mouser](http://www.mouser.com/Search/Refine.aspx?Keyword=MCP23017)) accessable to the Raspberry Pi (via [I²C](https://en.wikipedia.org/wiki/I%C2%B2C)) to provide real-time frequency measurements from a python script for $2.30 in components! Well, plus the cost of the Raspberry Pi. All files for this project are on [my GitHub page](https://github.com/swharden/RasPi-Frequency-Counter).

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

[![](IMG_8773_thumb.jpg)](IMG_8773.jpg)

</div>

__The entire circuit is only two microchips!__ I have a few passives to clean up the RF signal (the RF input is loaded with a 1k resistor to ground, decoupled through a series 100 nF capacitor, and balanced at VCC/2 through a voltage divider of two 47k resistors), but if the measured signal is already a strong square wave they could be omitted. The circuit requires a gate pulse which typically will be 1 pulse per second (1PPS) and can be generated by [dividing-down a 32.768kHz oscillator](http://electronics.stackexchange.com/questions/177844/why-do-we-use-32-768-khz-crystals-in-most-circuits), a spare pin on a microcontroller, a fancy 1PPS time reference, or like in my case a GPS module ([Neo-6M](https://www.google.com/search?q=Neo-6M&rct=j)) with 1PPS output to provide an extremely accurate gate.

<div class="text-center">

[![](schem_thumb.jpg)](schem.jpg)

</div>

__The connections are intuitive!__ The I2C address is 0x20 when A0, A1, and A2 are grounded. GPB(1-4) control the register select of the counter, and GPA(0-7) reads each bit of the selected register. The whole thing is [controlled from Python](https://github.com/swharden/RasPi-Frequency-Counter/blob/master/count.py), but could be trivially written in any language.

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

[![](IMG_8777_thumb.jpg)](IMG_8777.jpg)

</div>

__Here's a quick summary describing how [the code](https://github.com/swharden/RasPi-Frequency-Counter/blob/master/count.py) works:__ First I send bytes to address 0 and 1 to set all pins of GPIO A as inputs, and GPIO B as outputs. _Note that only 4 of 8 pins are used for the output, so technically 4 extra pins could be used for things like blinking LEDs or controlling other devices._ I then set the register select pins by sending a value to [0x13](https://en.wikipedia.org/wiki/Hexadecimal) (GPIO B), and read the entire GPIO A bus (INTCAPB, [0x18](https://en.wikipedia.org/wiki/Hexadecimal)). For address details, consult [the datasheet](http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf). I do this 4 times (1 for each byte of the 32-bit counter), do a little math to turn it into a frequency value, and compare the current value with the last value and take the difference to display as the measured frequency.

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

[![](screenshot_thumb.jpg)](screenshot.png)

</div>

__An advantage of this continuously running mode__ is that no clock cycles are lost, so a gate which accidentally fires a bit early due to jitter and cuts-off a cycle will compensate for it on a subsequent read. This is shown above, as a very stable 10MHz frequency reference is measured with this method. A "slow" 1PPS clock tick causes a reading slightly higher, compensated-for by the next reading being slightly lower. In this way, clock sources which are extremely accurate but suffer from low precision (like GPS time sources) are able to maximize the long-term measurement of frequency. Combining this frequency measurement technique with the ability to [generate an analog voltage with a Raspberry Pi](https://www.swharden.com/wp/2016-09-28-generating-analog-voltages-with-the-raspberry-pi/) will allow me to perform some interesting experiments with a voltage controlled crystal oscillator.

## Useful Links:

*   [MathWorks / MatLab example controlling the MCP23017 with a RasPi](https://www.mathworks.com/help/supportpkg/raspberrypiio/examples/add-digital-i-o-pins-to-raspberry-pi-hardware-using-mcp23017.html?requestedDomain=www.mathworks.com)
*   [IBEX References for RasPi I2C](http://www.raspberry-projects.com/pi/programming-in-python/i2c-programming-in-python/using-the-i2c-interface-2) (lots of good console commands)
*   [The adafruit page is great](https://learn.adafruit.com/adafruits-raspberry-pi-lesson-4-gpio-setup/configuring-i2c) for configuring I2C is pretty good

Pages