SWHarden.com

The personal website of Scott W Harden

TTL Triggered Stimulus Generator

I was presented with a need to rapidly develop a pulse generator to take a TTL input and output a programmable output (for now 0.1 ms pulses at 20 Hz for as long as the input is high). I achieved this with a one-afternoon turnaround and the result looks great! This post documents the design and fabrication of this prototype device, with emphasis placed on design considerations and construction technique.

By stocking large quantities of frequently-used items, inventors can build beautiful and functional prototypes for new ideas at the drop of a hat. While it’s easy to inexpensively accumulate tens of thousands of passive components (resistors, capacitors, etc.), it’s the slightly more expensive components that people tend to order only when they need it for a project. However, paying high shipping rates or waiting months for items to arrive from overseas dramatically increases the barrier for initiating new projects. In my own workshop I have noticed that stocking large volumes of slightly more costly items (inductors, microcontrollers, connectors, enclosures, LED bezels, etc.) lowers the barrier for me to start new projects, and has proved to be a good investment! Now I can build a product on the same day that I have the idea! Today’s idea takes the form of a TTL-controlled pulse generator for physiology applications.

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

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

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

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

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

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

Configure the ATTiny2313 to use an external crystal clock source

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

The core program: main.c

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

volatile char state=0;

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

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

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

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

Compile and load the ATTiny2313

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

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

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

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

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


Controlling Speakers with RADAR

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

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

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

Design

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

Construction

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

Alternative Designs

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

Radar Module Teardown

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

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


Precision Pressure Meter Project

I just completed building a device capable of measuring temperature to one hundredth of a degree Celsius and pressure to one ten-thousandth of a PSI! This project is centered around 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:

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

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:

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 monitoring 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


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:

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


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