The personal website of Scott W Harden
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
July 28th, 2016

Opto-Isolated Laser Controller Build

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

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

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

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

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

Design Notes

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

Potential Improvements

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Microcontroller Code

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

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

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

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

Final Design

Update: Improve Current Handling

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

Update: H11B1 minimum current and AC noise

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

This is the output of the unmodified H11B1:

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

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

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

# Opto-Isolated Laser Controller Build

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

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

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

</div>

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

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

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

</div>

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

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

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

<div class="text-center">

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

</div>

### Design Notes

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

### Potential Improvements

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

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

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

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

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

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

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

</div>

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

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

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

</div>

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

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

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

</div>

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

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

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

</div>

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

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

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

</div>

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

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

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

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

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

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

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

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

</div>

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

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

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

</div>

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

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

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

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

</div>

### Microcontroller Code

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

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

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

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

### Final Design

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

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

</div>

### Update: Improve Current Handling

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

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

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

</div>

### Update: H11B1 minimum current and AC noise

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

This is the output of the unmodified H11B1:

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

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

</div>

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

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

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

</div>

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

Controlling Bus Pirate with Python

After using the AVR-ISP mkII for years (actually the cheap eBay knock-offs) to program ATMEL AVR microcontrollers, today I gave the Bus Pirate a shot. Far more than just a microcontroller programmer, this little board is basically a serial interface to basic microcontroller peripherals. In a nutshell, you plug it in via USB and it looks like a serial port which has a command-line interface that lets you do things like turn pins on and off, perform voltage measurements, and it naively supports bidirectional use of common protocols like I2C, SPI, UART, and even HD44780 series LCDs. Note that although you could directly interface with the Bus Pirate using HyperTerminal, I recommend using TeraTerm. It can supply voltages (3.3V and 5V) to power small circuits, and if current draw is too high (indicating something is hooked-up wrong) it automatically turns the supply off. So clever! At <$30, it's a cool tool to have around. In addition, it's naively supported as an AVR programmer by AVRDUDE. Although I could write assembly to perform tasks, I almost always write in C for the convenience. For my reference (and that of anyone who may want to do something similar), I'm posting the simplest-case method I use to program AVR microcontrollers with the Bus Pirate on Windows (noting that Linux would be nearly identical). I also wrote a Python script to connect with the Bus Pirate and run simple commands (which turns the power supply on and report the voltage of the VCC line immediately after programming completes). Yes, there are fancy packages that allow you to interact with Bus Pirate from Python, but the advantage of my method is that it runs from native Python libraries! To get this all up and running for yourself, just install WinAVR (which supplies AVRDUDE and AVR-GCC) and Python 3. I assume this code will work just as well on Python 2, but haven't tried.

To ensure my Bus Pirate is working properly, I start off by running the Bus Pirate's built-in test routine. For full details read the guide. It just involves connecting two pairs of pins together as shown in the picture here, connecting to the Bus Pirate with the serial terminal, and running the command "~". It will output all sorts of useful information. Once I know my hardware is up and running, I'm good to continue.

Here's the code which runs on the microcontroller to twiddle all the pins (saved as main.c). Note that my MCU is an ATTiny85. I'm using standard clock settings (internal RC clock, 8MHz), but if I wanted to modify fuses to do things like use an external clock source or crystal, I'd calculate them with engbedded's handy dandy fuse calculator (which also shows AVRdude arguments needed to make the change!).

#define F_CPU (8000000UL)
#include <avr/io.h>
#include <util/delay.h>

int main (void)
{
    DDRB = 255;
    while(1)
    {
        PORTB ^= 255;
        _delay_ms(500);
    }
}

To compile the code and program the MCU with it, I always have a bash script in the same folder that I can double-click on to delete old compiled files (so we don't accidentally re-program our MCU with old code), compile main.c, and load it onto the MCU using the Bus Pirate. You may have to change COM3 to reflect the com port of your Bus Pirate. Note that it is required that you disconnect other terminals from the Bus Pirate before doing this, otherwise you'll get an "access denied" error.

@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny85 -Wall -Os -o main.elf main.c
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c buspirate -p attiny85 -P com3 -e -U flash:w:main.hex
python up.py

Although the programmer briefly supplies my MCU with power from the +5V pin, it's cut after programming completes. Rather than manually re-opening my terminal program, re-connecting with the bus pirate, re-setting the mode (command "m") to something random (DIO, command "9"), and re-enableing voltage output (command "W") just to see my LED blink, I want all that to be automated. Thanks python for making this easy. The last line calls "up.py". This fancy script even outputs the voltage of the VCC line after it's turned on!

Python3 Control of Bus Pirate

import serial

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

def send(ser,cmd):
    """send the command and listen to the response."""
    ser.write(str(cmd+'\n').encode('ascii')) # send our command
    for line in ser.readlines(): # while there's a response
        print(line.decode('utf-8').strip()) # show it

ser=serial.Serial(BUSPIRATE_PORT, 115200, timeout=1) # is com free?
assert ser.isOpen() #throw an exception if we aren't connected
send(ser,'#') # reset bus pirate (slow, maybe not needed)
send(ser,'m') # change mode (goal is to get away from HiZ)
send(ser,'9') # mode 9 is DIO
send(ser,'W') # turn power supply to ON. Lowercase w for OFF.
send(ser,'v') # show current voltages
ser.close() # disconnect so we can access it from another app
print("disconnected!") # let the user know we're done.

When "burn.cmd" is run, the code is compiled and loaded, the power supply is turned on (and killed if too much current is drawn!), and the voltage on VCC is reported. The output is:

C:\Users\scott\Documents\important\AVR\2016-07-13 ATTiny85 LEDblink>burn.cmd

Detecting BusPirate...
**
**  Bus Pirate v3a
**  Firmware v5.10 (r559)  Bootloader v4.4
**  DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
**  http://dangerousprototypes.com
**
BusPirate: using BINARY mode
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.12s

avrdude: Device signature = 0x1e930b
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (84 bytes):

Writing | ################################################## | 100% 3.12s

avrdude: 84 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex auto detected as Intel Hex
avrdude: input file main.hex contains 84 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 2.72s

avrdude: verifying ...
avrdude: 84 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.
#
RESET

Bus Pirate v3a
Firmware v5.10 (r559)  Bootloader v4.4
DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
http://dangerousprototypes.com

HiZ>
m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>
9
Ready
DIO>
W
Power supplies ON
DIO>
v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     CLK     MOSI    CS      MISO
P       P       P       I       I       I       I       I       I       I
GND     3.17V   5.00V   0.00V   0.00V   L       L       L       H       L
DIO>
disconnected!

This is a minimal-case scenario, but can be obviously expanded to perform some complicated tasks! For example, all commands could be run from a single python program. Considering the Bus Pirate's ability to communicate with so many different protocols (I2C, 2-write, etc.), being able to naively control it from Python without having to install special additional libraries will certainly prove to be convenient.

PS: I noted there is a surprising delay when initializing programming the AVR with the bus pirate. The process hangs for about 10 seconds after the bus pirate introduces itself with the welcome message, then seems to resume at full speed writing to the flash of the microchip. After a bit of Googling, I believe the delay is due to the Bus Pirate slowly bit-banging SPI to initialize the programming sequence. The AVR has rich SPI functionality, some of which involves its own programming. Satisfied with this answer for now, I'm not going to try to speed it up. It's a little annoying, but not too bad that I won't use this to program my AVRs.

Markdown source code last modified on January 18th, 2021
---
title: Controlling Bus Pirate with Python
date: 2016-07-14 00:15:31
tags: microcontroller, circuit, old
---

# Controlling Bus Pirate with Python

__After using the [AVR-ISP mkII](http://www.atmel.com/Images/Atmel-42093-AVR-ISP-mkII_UserGuide.pdf) for years (actually the [cheap eBay knock-offs](http://www.ebay.com/sch/i.html?_nkw=avr+isp+mkii)) to program [ATMEL AVR microcontrollers](https://en.wikipedia.org/wiki/Atmel_AVR), today I gave the [Bus Pirate ](http://dangerousprototypes.com/docs/Bus_Pirate)a shot.__ Far more than just a microcontroller programmer, this little board is basically a serial interface to basic microcontroller peripherals. In a nutshell, you plug it in via USB and it looks like a serial port which has a command-line interface that lets you do things like turn pins on and off, perform voltage measurements, and it naively supports bidirectional use of common protocols like I2C, SPI, UART, and even HD44780 series LCDs. Note that although you could directly interface with the Bus Pirate using HyperTerminal, I recommend using [TeraTerm](https://ttssh2.osdn.jp/index.html.en). It can supply voltages (3.3V and 5V) to power small circuits, and if current draw is too high (indicating something is hooked-up wrong) it automatically turns the supply off. So clever! At <$30, it's a cool tool to have around. In addition, it's naively supported as an AVR programmer by [AVRDUDE](http://www.nongnu.org/avrdude/). Although I could write assembly to perform tasks, I almost always write in C for the convenience. For my reference (and that of anyone who may want to do something similar), I'm posting the simplest-case method I use to program AVR microcontrollers with the Bus Pirate on Windows (noting that Linux would be nearly identical). ___I also wrote a Python script to connect with the Bus Pirate and run simple commands___ (which turns the power supply on and report the voltage of the VCC line immediately after programming completes).  Yes, there are [fancy packages](http://dangerousprototypes.com/docs/Bus_Pirate_Scripting_in_Python) that allow you to interact with Bus Pirate from Python, but ___the advantage of my method is that it runs from native Python libraries! ___To get this all up and running for yourself, just install [WinAVR ](http://winavr.sourceforge.net/)(which supplies AVRDUDE and AVR-GCC) and Python 3. I assume this code will work just as well on Python 2, but haven't tried.

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

[![](IMG_7092-1_thumb.jpg)](IMG_7092-1.jpg)

</div>

__To ensure my Bus Pirate is working properly, I start off by running the Bus Pirate's built-in test routine.__ For full details read the [guide](http://dangerousprototypes.com/docs/Bus_Pirate_self-test_guide). It just involves connecting two pairs of pins together as shown in the picture here, connecting to the Bus Pirate with the serial terminal, and running the command "~". It will output all sorts of useful information. Once I know my hardware is up and running, I'm good to continue.

<div class="text-center">

[![](Bpv3v2go-pinout_thumb.jpg)](Bpv3v2go-pinout.png)

</div>

__Here's the code which runs on the microcontroller to twiddle all the pins (saved as main.c).__ Note that my MCU is an [ATTiny85](http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf). I'm using standard clock settings (internal RC clock, 8MHz), but if I wanted to modify fuses to do things like use an external clock source or crystal, I'd calculate them with [engbedded's handy dandy fuse calculator](http://www.engbedded.com/fusecalc/) (which also shows AVRdude arguments needed to make the change!).

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

int main (void)
{
    DDRB = 255;
    while(1)
    {
        PORTB ^= 255;
        _delay_ms(500);
    }
}
```

__To compile the code and program the MCU with it__, I always have a bash script in the same folder that I can double-click on to delete old compiled files (so we don't accidentally re-program our MCU with old code), compile main.c, and load it onto the MCU using the Bus Pirate. You may have to change COM3 to reflect the com port of your Bus Pirate. Note that it is required that you disconnect other terminals from the Bus Pirate before doing this, otherwise you'll get an "access denied" error.

```bash
@echo off
del *.elf
del *.hex
avr-gcc -mmcu=attiny85 -Wall -Os -o main.elf main.c
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c buspirate -p attiny85 -P com3 -e -U flash:w:main.hex
python up.py
```

Although the programmer briefly supplies my MCU with power from the +5V pin, it's cut after programming completes. Rather than manually re-opening my terminal program, re-connecting with the bus pirate, re-setting the mode (command "m") to something random (DIO, command "9"), and re-enableing voltage output (command "W") just to see my LED blink, I want all that to be automated. Thanks python for making this easy. The last line calls "up.py". This fancy script even outputs the voltage of the VCC line after it's turned on!

### Python3 Control of Bus Pirate

```python
import serial

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

def send(ser,cmd):
    """send the command and listen to the response."""
    ser.write(str(cmd+'\n').encode('ascii')) # send our command
    for line in ser.readlines(): # while there's a response
        print(line.decode('utf-8').strip()) # show it

ser=serial.Serial(BUSPIRATE_PORT, 115200, timeout=1) # is com free?
assert ser.isOpen() #throw an exception if we aren't connected
send(ser,'#') # reset bus pirate (slow, maybe not needed)
send(ser,'m') # change mode (goal is to get away from HiZ)
send(ser,'9') # mode 9 is DIO
send(ser,'W') # turn power supply to ON. Lowercase w for OFF.
send(ser,'v') # show current voltages
ser.close() # disconnect so we can access it from another app
print("disconnected!") # let the user know we're done.

```

When "burn.cmd" is run, the code is compiled and loaded, the power supply is turned on (and killed if too much current is drawn!), and the voltage on VCC is reported. The output is:

```
C:\Users\scott\Documents\important\AVR\2016-07-13 ATTiny85 LEDblink>burn.cmd

Detecting BusPirate...
**
**  Bus Pirate v3a
**  Firmware v5.10 (r559)  Bootloader v4.4
**  DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
**  http://dangerousprototypes.com
**
BusPirate: using BINARY mode
avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.12s

avrdude: Device signature = 0x1e930b
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: input file main.hex auto detected as Intel Hex
avrdude: writing flash (84 bytes):

Writing | ################################################## | 100% 3.12s

avrdude: 84 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex auto detected as Intel Hex
avrdude: input file main.hex contains 84 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 2.72s

avrdude: verifying ...
avrdude: 84 bytes of flash verified

avrdude: safemode: Fuses OK

avrdude done.  Thank you.
```

```
#
RESET

Bus Pirate v3a
Firmware v5.10 (r559)  Bootloader v4.4
DEVID:0x0447 REVID:0x3046 (24FJ64GA002 B8)
http://dangerousprototypes.com

HiZ>
m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. LCD
9. DIO
x. exit(without change)

(1)>
9
Ready
DIO>
W
Power supplies ON
DIO>
v
Pinstates:
1.(BR)  2.(RD)  3.(OR)  4.(YW)  5.(GN)  6.(BL)  7.(PU)  8.(GR)  9.(WT)  0.(Blk)
GND     3.3V    5.0V    ADC     VPU     AUX     CLK     MOSI    CS      MISO
P       P       P       I       I       I       I       I       I       I
GND     3.17V   5.00V   0.00V   0.00V   L       L       L       H       L
DIO>
disconnected!
```

__This is a minimal-case scenario, but can be obviously expanded to perform some complicated tasks!__ For example, all commands could be run from a single python program. Considering the Bus Pirate's ability to communicate with so many different protocols (I2C, 2-write, etc.), being able to naively control it from Python without having to install special additional libraries will certainly prove to be convenient.

_PS: I noted there is a surprising delay when initializing programming the AVR with the bus pirate. The process hangs for about 10 seconds after the bus pirate introduces itself with the welcome message, then seems to resume at full speed writing to the flash of the microchip. After a bit of Googling, I believe the delay is due to the Bus Pirate slowly bit-banging SPI to initialize the programming sequence. The AVR has [rich SPI functionality](http://maxembedded.com/2013/11/the-spi-of-the-avr/), some of which involves its own programming. Satisfied with this answer for now, I'm not going to try to speed it up. It's a little annoying, but not too bad that I won't use this to program my AVRs._
Pages