The personal website of Scott W Harden
February 27th, 2014

Directly Driving 7-Segment Display with AVR IO Pins

I came across the need for a quick and dirty display to show a 4 digit number from a microcontroller. The right way to do this would be to use a microcontroller in combination with a collection of transistors and current limiting resistors, or even a dedicated 7-segment LED driver IC. The wrong way to do this is to wire LEDs directly to microcontroller IO pins to source and sink current way out of spec of the microcontroller... and that's exactly what I did! With no current limiting resistors, the AVR is sourcing and sinking current potentially far out of spec for the chip. But, heck, it works! With 2 components (just a microcontroller and a 4 digit, 7-segment LED display) and a piece of ribbon cable, I made something that used to be a nightmare to construct (check out this post from 3 years ago when I accomplished the same thing with a rats nest of wires - it was so much work that I never built one again!) The hacked-together method I show today might not be up to spec for medical grade equipment, but it sure works for my test rig application, and it's easy and cheap to accomplish... as long as you don't mind breaking some electrical engineering rules. Consider how important it is to know how to hack projects like this together: Although I needed this device, if it were any harder, more expensive, or less convenient to build, I simply wouldn't have built it! Sometimes hacking equipment together the wrong way is worth it.

Segments are both current sourced and sunk directly from AVR IO pins. Digits are multiplexed with 1ms illumination duration. I don't really have a part number for the component because it was a China eBay special. The display was $6.50 for 4 (free shipping). That's ~$1.65 each. The microcontroller is ~$1.[/caption]

SCHEMATIC? If you want it, read this. It's so simple I don't feel like making it. Refer to an ATMega48 pin diagram. The LCD is common anode (not common cathode), and here's the schematic on the right. I got it from eBay (link) for <$2. The connections are as follows:

  • Segments (-) A...H are directly wired to PD0...PD7

  • I call the decimal point (dp) segment "H" - I don't use current limiting resistors. I'm not making a consumer product. It works fine, especially multiplexed. Yeah I could use transistors and CLRs to drive the segments to have them bright and within current specifications, but I'm not building an airplane or designing a pacemaker, I'm making a test device at minimum cost! Direct LED wiring to my microcontroller is fine for my purposes.

  • I am multiplexing the characters of my display. I could have used a driver IC to simplify my code and eliminate the current / wiring issues described above.

  • A MAX7219 or MAX7221 would have been easy choices for this (note common anode vs. common cathode drivers / displays). It adds an extra $5 to my project cost though, so I didn't go with a driver. I drove the segments right out of my IO pins.

  • Characters (+) 1...4 are PC0...PC3

  • Obviously I apply +5V and GND to the appropriate AVR pins

Here it all is together in my microcontroller programming set up. I'll place this device in a little enclosure and an an appropriate BNC connector and either plan on giving it USB power or run it from 3xAA batteries. For now, it works pretty darn well on the breadboard.

Here is my entire programming setup. On the top left is my eBay special USB AVR programmer. On the right is a little adapter board I made to accomodate a 6 pin ISP cable and provide a small breadboard for adding programming jumpers into a bigger breadboard. The breadboard at the bottom houses the microcontroller and the display. No other components! Well, okay, a 0.1uF decoupling capacitor to provide mild debouncing for the TTL input.

Let's talk about the code. Briefly, I use an infinite loop which continuously displays the value of the volatile long integer "numba". In the display function, I set all of my segments to (+) then momentarily provide a current sink (-) on the appropriate digit anode for 1ms. I do this for each of the four characters, then repeat. How is the time (the value of "numba") incremented? Using a hardware timer and its overflow interrupt! It's all in the ATMega48 datasheet, but virtually every microcontroller has some type of timer you can use to an equivalent effect. See my earlier article "Using Timers and Counters to Clock Seconds" for details. I guess that's pretty much it! I document my code well enough below that anyone should be able to figure it out. The microcontroller is an ATMega48 (clocked 8MHz with an internal RC clock, close enough for my purposes).

#define F_CPU 8000000UL // 8mhz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

// for simplicity, define pins as segments
#define A (1<<PD0)
#define B (1<<PD1)
#define C (1<<PD2)
#define D (1<<PD3)
#define E (1<<PD4)
#define F (1<<PD5)
#define G (1<<PD6)
#define H (1<<PD7)

void setDigit(char dig){ // set the digit starting at 0
    PORTC=(1<<dig)|(1<<PC4); // always keep the PC4 pin high
}

void setChar(char c){
    // given a number, set the appropraite segments
    switch(c){
        case 0:    DDRD=A|B|C|D|E|F;    break;
        case 1:    DDRD=B|C;            break;
        case 2:    DDRD=A|B|G|E|D;        break;
        case 3: DDRD=A|B|G|C|D;        break;
        case 4: DDRD=F|G|B|C;        break;
        case 5: DDRD=A|F|G|C|D;        break;
        case 6: DDRD=A|F|G|E|C|D;    break;
        case 7: DDRD=A|B|C;            break;
        case 8: DDRD=A|B|C|D|E|F|G;    break;
        case 9: DDRD=A|F|G|B|C;        break;
        case 31: DDRD=H;            break;
        default: DDRD=0;             break;
    }
}

void flashNumber(long num){
    char i;

    for (i=0;i<4;i++){
        setChar(num%10);
        if (i==2){DDRD|=H;} // H is the decimal point
        setDigit(3-i);
        num=num/10;
        _delay_ms(1); // time to leave the letter illuminated
    }
}

volatile long numba = 0;
volatile long addBy = 1;

ISR(PCINT1_vect){ // state change on PC4
    if ((PINC&(1<<PC4))==0) {addBy=0;} // pause
    else {numba=0;addBy=1;} // reset to 0 and resume
}

ISR(TIMER1_OVF_vect){
    TCNT1=65536-1250; // the next overflow in 1/100th of a second
    numba+=addBy;      // add 1 to the secound counter
}

int main(void)
{
    DDRC=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3); // set all characters as outputs
    DDRD=255;PORTD=0;     // set all segments as outputs, but keep them low

    TCCR1B|=(1<<CS11)|(1<<CS10); // prescaler 64
    TIMSK1|=(1<<TOIE1); //Enable Overflow Interrupt Enable
    TCNT1=65536-1250;   // the next overflow in 1/100th of a second

    // note that PC4 (PCINT12) is an input, held high, and interrupts when grounded
    PCICR |= (1<<PCIE1); // enable interrupts on PCING13..8 -> PCI1 vector
    PCMSK1 |= (1<<PCINT12); // enable PCINT12 state change to be an interrupt
    sei(); // enable global interrupts

    for(;;){flashNumber(numba);} // just show the current number repeatedly forever
}

I edit my code in Notepad++ by the way. To program the chip, I use a bash script...

avr-gcc -mmcu=atmega48 -Wall -Os -o main.elf main.c -w
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m48 -F -U flash:w:"main.hex":a -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m

Nothing here is groundbreaking. It's simple, and convenient as heck. Hopefully someone will be inspired enough by this write-up that, even if they don't recreate this project, they'll jump at the opportunity to make something quick and dirty in the future. It's another example that goes to show that you don't have to draw schematics, run simulations, do calculations and etch boards to make quick projects. Just hack it together and use it.

Update a two days later... I found a similarly quick and dirty way to package this project in an enclosure. I had on hand some 85x50x21mm project boxes (eBay, 10 for $14.85, free shipping, about $1.50 each) so I used a nibbler to hack out a square to accomodate the display. After a little super glue, ribbon cable, and solder, we're good to go!

Related reading for the technically inclined:

Markdown source code last modified on January 18th, 2021
---
title: Directly Driving 7-Segment Display with AVR IO Pins
date: 2014-02-27 16:19:53
tags: circuit, old
---

# Directly Driving 7-Segment Display with AVR IO Pins

__I came across the need for a quick and dirty display to show a 4 digit number from a microcontroller.__ The _right_ way to do this would be to use a microcontroller in combination with a collection of transistors and current limiting resistors, or even a dedicated 7-segment LED driver IC. The _wrong_ way to do this is to wire LEDs directly to microcontroller IO pins to source and sink current way out of spec of the microcontroller... and that's <span style="text-decoration: underline;">exactly</span> what I did! With no current limiting resistors, the AVR is sourcing and sinking current potentially far out of spec for the chip. But, heck, it works! With 2 components (just a microcontroller and a 4 digit, 7-segment LED display) and a piece of ribbon cable, I made something that used to be a nightmare to construct (check out this post from 3 years ago when I accomplished the same thing [with a rats nest of wires](http://www.swharden.com/blog/2011-03-14-frequency-counter-finished/) - it was so much work that I never built one again!) The hacked-together method I show today might not be up to spec for medical grade equipment, but it sure works for my test rig application, and it's easy and cheap to accomplish... as long as you don't mind breaking some electrical engineering rules. Consider how important it is to know how to hack projects like this together: Although I needed this device, if it were any harder, more expensive, or less convenient to build, I simply wouldn't have built it! Sometimes hacking equipment together the wrong way is worth it.

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

[![](IMG_2316_thumb.jpg)](IMG_2316.jpg)

</div>

Segments are both current sourced and sunk directly from AVR IO pins. Digits are multiplexed with 1ms illumination duration. I don't really have a part number for the component because it was a China eBay special. The display was $6.50 for 4 (free shipping). That's ~$1.65 each. The microcontroller is ~$1.[/caption]

__SCHEMATIC?__ If you want it, read this. It's so simple I don't feel like making it. Refer to an [ATMega48 pin diagram](http://www.swharden.com/blog/images/atmega48pinout.png). The LCD is common anode (not common cathode), and here's the schematic on the right. I got it from eBay ([link](http://www.ebay.com/itm/4Pcs-7seg-4digit-LED-Display-work-with-arm7-MCU-Arduino-/280533977596?ssPageName=ADME:L:OC:US:3160)) for <$2.  The connections are as follows:


<div class="text-center">

[![](common-cathode-7-segment-display-lcd_thumb.jpg)](common-cathode-7-segment-display-lcd.jpg)

</div>

*   Segments (-) A...H are directly wired to PD0...PD7 
  * I call the decimal point (dp) segment "H" - I don't use current limiting resistors. I'm not making a consumer product. It works fine, especially multiplexed. Yeah I could use transistors and CLRs to drive the segments to have them bright and within current specifications, but I'm not building an airplane or designing a pacemaker, I'm making a test device at minimum cost! Direct LED wiring to my microcontroller is fine for my purposes.
  * I am multiplexing the characters of my display. I could have used a driver IC to simplify my code and eliminate the current / wiring issues described above. 
  * A [MAX7219 or MAX7221](http://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf) would have been easy choices for this (note common anode vs. common cathode drivers / displays). It adds an extra $5 to my project cost though, so I didn't go with a driver. I drove the segments right out of my IO pins.

*   Characters (+) 1...4 are PC0...PC3

*   Obviously I apply +5V and GND to the appropriate AVR pins

__Here it all is together in my microcontroller programming set up.__ I'll place this device in a little enclosure and an an appropriate BNC connector and either plan on giving it USB power or run it from 3xAA batteries. For now, it works pretty darn well on the breadboard.

<div class="text-center">

[![](IMG_2320_thumb.jpg)](IMG_2320.jpg)

</div>

Here is my entire programming setup. On the top left is my eBay special USB AVR programmer. On the right is a little adapter board I made to accomodate a 6 pin ISP cable and provide a small breadboard for adding programming jumpers into a bigger breadboard. The breadboard at the bottom houses the microcontroller and the display. No other components! Well, okay, a 0.1uF decoupling capacitor to provide mild debouncing for the TTL input.

__Let's talk about the code.__ Briefly, I use an infinite loop which continuously displays the value of the volatile long integer "numba". In the display function, I set all of my segments to (+) then momentarily provide a current sink (-) on the appropriate digit anode for 1ms. I do this for each of the four characters, then repeat. How is the time (the value of "numba") incremented? Using a hardware timer and its overflow interrupt! It's all in the ATMega48 datasheet, but virtually every microcontroller has some type of timer you can use to an equivalent effect. See my earlier article "[_Using Timers and Counters to Clock Seconds_](http://www.swharden.com/blog/2011-06-19-using-timers-and-counters-to-clock-seconds/)" for details. I guess that's pretty much it! I document my code well enough below that anyone should be able to figure it out. The microcontroller is an [ATMega48](http://www.atmel.com/images/doc2545.pdf) (clocked 8MHz with an internal RC clock, close enough for my purposes).

```c
#define F_CPU 8000000UL // 8mhz
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

// for simplicity, define pins as segments
#define A (1<<PD0)
#define B (1<<PD1)
#define C (1<<PD2)
#define D (1<<PD3)
#define E (1<<PD4)
#define F (1<<PD5)
#define G (1<<PD6)
#define H (1<<PD7)

void setDigit(char dig){ // set the digit starting at 0
    PORTC=(1<<dig)|(1<<PC4); // always keep the PC4 pin high
}

void setChar(char c){
    // given a number, set the appropraite segments
    switch(c){
        case 0:    DDRD=A|B|C|D|E|F;    break;
        case 1:    DDRD=B|C;            break;
        case 2:    DDRD=A|B|G|E|D;        break;
        case 3: DDRD=A|B|G|C|D;        break;
        case 4: DDRD=F|G|B|C;        break;
        case 5: DDRD=A|F|G|C|D;        break;
        case 6: DDRD=A|F|G|E|C|D;    break;
        case 7: DDRD=A|B|C;            break;
        case 8: DDRD=A|B|C|D|E|F|G;    break;
        case 9: DDRD=A|F|G|B|C;        break;
        case 31: DDRD=H;            break;
        default: DDRD=0;             break;
    }
}

void flashNumber(long num){
    char i;

    for (i=0;i<4;i++){
        setChar(num%10);
        if (i==2){DDRD|=H;} // H is the decimal point
        setDigit(3-i);
        num=num/10;
        _delay_ms(1); // time to leave the letter illuminated
    }
}

volatile long numba = 0;
volatile long addBy = 1;

ISR(PCINT1_vect){ // state change on PC4
    if ((PINC&(1<<PC4))==0) {addBy=0;} // pause
    else {numba=0;addBy=1;} // reset to 0 and resume
}

ISR(TIMER1_OVF_vect){
    TCNT1=65536-1250; // the next overflow in 1/100th of a second
    numba+=addBy;      // add 1 to the secound counter
}

int main(void)
{
    DDRC=(1<<PC0)|(1<<PC1)|(1<<PC2)|(1<<PC3); // set all characters as outputs
    DDRD=255;PORTD=0;     // set all segments as outputs, but keep them low

    TCCR1B|=(1<<CS11)|(1<<CS10); // prescaler 64
    TIMSK1|=(1<<TOIE1); //Enable Overflow Interrupt Enable
    TCNT1=65536-1250;   // the next overflow in 1/100th of a second

    // note that PC4 (PCINT12) is an input, held high, and interrupts when grounded
    PCICR |= (1<<PCIE1); // enable interrupts on PCING13..8 -> PCI1 vector
    PCMSK1 |= (1<<PCINT12); // enable PCINT12 state change to be an interrupt
    sei(); // enable global interrupts

    for(;;){flashNumber(numba);} // just show the current number repeatedly forever
}
```

I edit my code in [Notepad++](http://notepad-plus-plus.org/) by the way. To program the chip, I use a bash script...

```bash
avr-gcc -mmcu=atmega48 -Wall -Os -o main.elf main.c -w
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m48 -F -U flash:w:"main.hex":a -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m
```

__Nothing here is groundbreaking.__ It's simple, and convenient as heck. Hopefully someone will be inspired enough by this write-up that, even if they don't recreate _this_ project, they'll jump at the opportunity to make something quick and dirty in the future. It's another example that goes to show that you don't have to draw schematics, run simulations, do calculations and etch boards to make quick projects. Just hack it together and use it.

__Update a two days later... __I found a similarly quick and dirty way to package this project in an enclosure. I had on hand some 85x50x21mm project boxes (eBay, 10 for $14.85, free shipping, about $1.50 each) so I used a [nibbler](http://www.amazon.com/Hand-Sheet-Metal-Nibbler-Cutter/dp/B000T5FV4Q) to hack out a square to accomodate the display. After a little super glue, ribbon cable, and solder, we're good to go!

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

[![](IMG_2336_thumb.jpg)](IMG_2336.jpg)
[![](IMG_2351_thumb.jpg)](IMG_2351.jpg)
[![](IMG_2355_thumb.jpg)](IMG_2355.jpg)
[![](IMG_2356_thumb.jpg)](IMG_2356.jpg)
[![](IMG_2362_thumb.jpg)](IMG_2362.jpg)
[![](IMG_2380_thumb.jpg)](IMG_2380.jpg)

</div>

__Related reading for the technically inclined:__

*   [Interfacing LEDs with the AVR microcontroller](http://www.avr-tutorials.com/interfacing/interfacing-leds-avr-microcontroller) (the right way)
*   [Driving LEDS with vs. without a current limiting resistor](http://tinkerlog.com/2009/04/05/driving-an-led-with-or-without-a-resistor/) (nice write-up!)
*   Official datasheet for [driving LCDs with AVR](http://www.atmel.com/Images/doc2569.pdf) (not LEDs)
*   Official datasheet for [using AVR timers](http://www.atmel.com/Images/doc2505.pdf)

June 22nd, 2013

Crystal Oven Testing

To maintain high frequency stability, RF oscillator circuits are sometimes "ovenized" where their temperature is raised slightly above ambient room temperature and held precisely at one temperature. Sometimes just the crystal is heated (with a "crystal oven"), and other times the entire oscillator circuit is heated. The advantage of heating the circuit is that other components (especially metal core instructors) are temperature sensitive. Googling for the phrase "crystal oven", you'll find no shortage of recommended circuits. Although a more complicated PID (proportional-integral-derivative) controller may seem enticing for these situations, the fact that the enclosure is so well insulated and drifts so little over vast periods of time suggests that it might not be the best application of a PID controller. One of my favorite write-ups is from M0AYF's site which describes how to build a crystal oven for QRSS purposes. He demonstrates the MK1 and then the next design the MK2 crystal oven controller.

Briefly, desired temperature is set with a potentiometer. An operational amplifier (op-amp) compares the target temperature with measured temperature (using a thermistor - a resistor which varies resistance by tempearture). If the measured temperature is below the target, the op-amp output goes high, and current flows through heating resistors. There are a few differences between the two circuits, but one of the things that struck me as different was the use of negative feedback with the operational amplifier. This means that rather than being on or off (like the air conditioning in your house), it can be on a little bit. I wondered if this would greatly affect frequency stability. In the original circuit, he mentions

The oven then cycles on and off roughly every thirty or forty seconds and hovers around 40 degrees-C thereafter to within better than one degree-C. I wondered how much this on/off heater cycle affected temperature. Is it negligible, or could it affect frequency of an oscillator circuit? Indeed his application heats an entire enclosure so small variations get averaged-out by the large thermal mass. However in crystal oven designs where only the crystal is heated, such as described by Bill (W4HBK), I'll bet the effect is much greater. Compare the thermal mass of these two concepts.

How does the amount of thermal mass relate to how well it can be controlled? How important is negative feedback for partial-on heater operation? Can simple ON/OFF heater regulation adequately stabalize a crystal or enclosure? I'd like to design my own heater, pulling the best elements from the rest I see on the internet. My goals are:

  1. use inexpensive thermistors instead of linear temperature sensors (like LM335)
  2. use inexpensive quarter-watt resistors as heaters instead of power resistors
  3. be able to set temperature with a knob
  4. be able to monitor temperature of the heater
  5. be able to monitor power delivered to the heater
  6. maximum long-term temperature stability

Right off the bat, I realized that this requires a PC interface. Even if it's not used to adjust temperature (an ultimate goal), it will be used to log temperature and power for analysis. I won't go into the details about how I did it, other than to say that I'm using an ATMEL ATMega8 AVR microcontroller and ten times I second I sample voltage on each of it's six 10-bit ADC pins (PC0-PC5), and send that data to the computer with USART using an eBay special serial/USB adapter based on FTDI. They're <$7 (shipped) and come with the USB cable. Obviously in a consumer application I'd etch boards and use the SMT-only FTDI chips, but for messing around at home I a few a few of these little adapters. They're convenient as heck because I can just add a heater to my prototype boards and it even supplies power and ground. Convenient, right? Power is messier than it could be because it's being supplied by the PC, but for now it gets the job done. On the software side, Python with PySerial listens to the serial port and copies data to a large numpy array, saving it every once and a while. Occasionally a bit is sent wrong and a number is received incorrectly (maybe one an hour), but the error is recognized and eliminated by the checksum (just the sum of all transmitted numbers). Plotting is done with numpy and matpltolib. Code for all of that is at the bottom of this post.

That's the data logger circuit I came up with. Reading six channels ten times a second, it's more than sufficient for voltage measurement. I went ahead and added an op-amp to the board too, since I knew I'd be using one. I dedicated one of the channels to serve as ambient temperature measurement. See the little red thermistor by the blue resistor? I also dedicated another channel to the output of the op-amp. This way I can measure drive to whatever temperature controller circuity I choose to use down the road. For my first test, I'm using a small thermal mass like one would in a crystal oven. Here's how I made that:

I then build the temperature controller part of the circuit. It's pretty similar to that previously published. it uses a thermistor in a voltage divider configuration to sense temperature. It uses a trimmer potentiometer to set temperature. An LED indicator light gives some indication of on/off, but keep in mind that a fraction of a volt will turn the Darlington transistor (TIP122) on slightly although it doesn't reach a level high enough to drive the LED. The amplifier by default is set to high gain (55x), but can be greatly lowered (negative gain actually) with a jumper. This lets me test how important gain is for the circuitry.

When using a crystal oven configuration, I concluded high high gain (cycling the heater on/off) is a BAD idea. While average temperature is held around the same, the crystal oscillates. This is what is occurring above when M0AYF indicates his MK1 heater turns on and off every 40 seconds. While you might be able to get away with it while heating a chassis or something, I think it's easy to see it's not a good option for crystal heaters. Instead, look at the low gain (negative gain) configuration. It reaches temperature surprisingly quickly and locks to it steadily. Excellent.

high gain configuration tends to oscillate every 30 seconds

low gain / negative gain configuration is extremely stable (fairly high temperature)

Here's a similar experiment with a lower target temperature. Noise is due to unregulated USB power supply / voltage reference. Undeniably, this circuit does not oscillate much if any

Clearly low (or negative) gain is best for crystal heaters. What about chassis / enclosure heaters? Let's give that a shot. I made an enclosure heater with the same 2 resistors. Again, I'm staying away from expensive components, and that includes power resistors. I used epoxy (gorilla glue) to cement them to the wall of one side of the enclosure.

I put a "heater sensor" thermistor near the resistors on the case so I could get an idea of the heat of the resistors, and a "case sensor" on the opposite side of the case. This will let me know how long it takes the case to reach temperature, and let me compare differences between using near vs. far sensors (with respect to the heating element) to control temperature. I ran the same experiments and this is what I came up with!

heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure

CLOSE SENSOR CONTROL, LOW/HIGH GAIN: TOP: heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure. BOTTOM: power to the heater (voltage off the op-amp output going into the base of the Darlington transistor). Although I didn't give the low-gain configuration time to equilibrate, I doubt it would have oscillated on a time scale I am patient enough to see. Future, days-long experimentation will be required to determine if it oscillates significantly.[/caption]

Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.

FAR SENSOR CONTROL, HIGH GAIN: Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Blue is the far sensor temperature. Green is the sensor near the heater temperature. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.

Right off the bat, we observe that even with the increased thermal mass of the entire enclosure (being heated with two dinky 100 ohm 1/4 watt resistors) the system is prone to temperature oscillation if gain is set too high. For me, this is the final nail in the coffin - I will never use a comparator-type high gain sensor/regulation loop to control heater current. With that out, the only thing to compare is which is better: placing the sensor near the heating element, or far from it. In reality, with a well-insulated device like I seem to have, it seems like it doesn't make much of a difference! The idea is that by placing it near the heater, it can stabilize quickly. However, placing it far from the heater will give it maximum sensation of "load" temperature. Anywhere in-between should be fine. As long as it's somewhat thermally coupled to the enclosure, enclosure temperature will pull it slightly away from heater temperature regardless of location. Therefore, I conclude it's not that critical where the sensor is placed, as long as it has good contact with the enclosure. Perhaps with long-term study (on the order of hours to days) slow oscillations may emerge, but I'll have to build it in a more permanent configuration to test it out. Lucky, that's exactly what I plan to do, so check back a few days from now!

Since the data speaks for itself, I'll be concise with my conclusions:

  • two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat an insulated enclosure with 12V
  • two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat a crystal with 5V
  • low gain or negative gain is preferred to prevent oscillating tempeartures
  • Sensor location on an enclosure is not critical as long as it's well-coupled to the enclosure and the entire enclosure is well-insulated.

I feel satisfied with today's work. Next step is to build this device on a larger scale and fix it in a more permanent configuration, then leave it to run for a few weeks and see how it does. On to making the oscillator! If you have any questions or comments, feel free to email me. If you recreate this project, email me! I'd love to hear about it.

Here's the code that went on the ATMega8 AVR (it continuously transmits voltage measurements on 6 channels).

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

/*
8MHZ: 300,600,1200,2400,4800,9600,14400,19200,38400
1MHZ: 300,600,1200,2400,4800
*/
#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

/*
ISR(ADC_vect)
{
    PORTD^=255;
}
*/

void USART_Init(void){
    UBRRL = BAUD_PRESCALE;
    UBRRH = (BAUD_PRESCALE >> 8);
    UCSRB = (1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
    while ( !( UCSRA & (1<<UDRE)) );
    UDR = data;
}

void sendNum(long unsigned int byte){
    if (byte==0){
        USART_Transmit(48);
    }
    while (byte){
        USART_Transmit(byte%10+48);
        byte-=byte%10;
        byte/=10;
    }
}

int readADC(char adcn){
    ADMUX = 0b0100000+adcn;
    ADCSRA |= (1<<ADSC); // reset value
    while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
    return ADC>>6;
}

int sendADC(char adcn){
    int val;
    val=readADC(adcn);
    sendNum(val);
    USART_Transmit(',');
    return val;
}

int main(void){
    ADCSRA = (1<<ADEN)  | 0b111;
    DDRB=255;
    USART_Init();
    int checksum;

    for(;;){
        PORTB=255;
        checksum=0;
        checksum+=sendADC(0);
        checksum+=sendADC(1);
        checksum+=sendADC(2);
        checksum+=sendADC(3);
        checksum+=sendADC(4);
        checksum+=sendADC(5);
        sendNum(checksum);
        USART_Transmit('n');
        PORTB=0;
        _delay_ms(200);
    }
}

Here's the command I used to compile the code, set the AVR fuse bits, and load it to the AVR.

del *.elf
del *.hex
avr-gcc -mmcu=atmega8 -Wall -Os -o main.elf main.c -w
pause
cls
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m8 -F -U flash:w:"main.hex":a -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m

Here's the code that runs on the PC to listen to the microchip, match the data to the checksum, and log it occasionally.

import serial, time
import numpy
ser = serial.Serial("COM16", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    lines+=1
    if "," in line:
        line=line.split(",")
        for i in range(len(line)):
            line[i]=int(line[i][::-1])

    if line[-1]==sum(line[:-1]):
        line=[time.time()]+line[:-1]
        print lines, line
        data.append(line)
    else:
        print  lines, line, "<-- FAIL"

    if lines%50==49:
        numpy.save("data.npy",data)
        print "nSAVINGn%d lines in %.02f sec (%.02f vals/sec)n"%(lines,
            time.time()-t1,lines/(time.time()-t1))

Here's the code that runs on the PC to graph data.

import matplotlib
matplotlib.use('TkAgg') # <-- THIS MAKES IT FAST!
import numpy
import pylab
import datetime
import time

def adc2F(adc):
    Vo=adc*5.0/1024.0
    K=Vo*100
    C=K-273
    F=C*(9.0/5)+32
    return F

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

def adc2V(adc):
    Vo=adc*5.0/1024.0
    return Vo

if True:
    print "LOADING DATA"
    data=numpy.load("data.npy")
    data=data
    print "LOADED"

    fig=pylab.figure()
    xs=data[:,0]
    tempAmbient=data[:,1]
    tempPower=data[:,2]
    tempHeater=data[:,3]
    tempCase=data[:,4]
    dates=(xs-xs[0])/60.0
    #dates=[]
    #for dt in xs: dates.append(datetime.datetime.fromtimestamp(dt))

    ax1=pylab.subplot(211)
    pylab.title("Temperature Controller - Low Gain")
    pylab.ylabel('Heater (ADC)')
    pylab.plot(dates,tempHeater,'b-')
    pylab.plot(dates,tempCase,'g-')
    #pylab.axhline(115.5,color="k",ls=":")

    #ax2=pylab.subplot(312,sharex=ax1)
    #pylab.ylabel('Case (ADC)')
    #pylab.plot(dates,tempCase,'r-')
    #pylab.plot(dates,tempAmbient,'g-')
    #pylab.axhline(0,color="k",ls=":")

    ax2=pylab.subplot(212,sharex=ax1)
    pylab.ylabel('Heater Power')
    pylab.plot(dates,tempPower)

    #fig.autofmt_xdate()
    pylab.xlabel('Elapsed Time (min)')

    pylab.show()

print "DONE"
Markdown source code last modified on January 18th, 2021
---
title: Crystal Oven Testing
date: 2013-06-22 15:14:37
tags: circuit, old
---

# Crystal Oven Testing

__To maintain high frequency stability, RF oscillator circuits are sometimes "ovenized" where their temperature is raised slightly above ambient room temperature and held precisely at one temperature.__ Sometimes just the crystal is heated (with a "crystal oven"), and other times the entire oscillator circuit is heated. The advantage of heating the circuit is that other components (especially metal core instructors) are temperature sensitive. Googling for the phrase "crystal oven", you'll find no shortage of recommended circuits. Although a more complicated[ PID (proportional-integral-derivative) controller](http://en.wikipedia.org/wiki/PID_controller) may seem enticing for these situations, the fact that the enclosure is so well insulated and drifts so little over vast periods of time suggests that it might not be the best application of a PID controller. One of my favorite write-ups is from [M0AYF's site](http://www.qsl.net/m0ayf/Crystal-Ovens.html) which describes how to build a crystal oven for QRSS purposes. He demonstrates the MK1 and then the next design the MK2 crystal oven controller.

__Briefly, desired temperature is set with a potentiometer.__ An operational amplifier (op-amp) compares the target temperature with measured temperature (using a thermistor - a resistor which varies resistance by tempearture). If the measured temperature is below the target, the op-amp output goes high, and current flows through heating resistors. There are a few differences between the two circuits, but one of the things that struck me as different was the use of negative feedback with the operational amplifier. This means that rather than being on or off (like the air conditioning in your house), it can be on a little bit. I wondered if this would greatly affect frequency stability. In the original circuit, he mentions

The oven then cycles on and off roughly every thirty or forty seconds and hovers around 40 degrees-C thereafter to within better than one degree-C.
__I wondered how much this on/off heater cycle affected temperature. Is it negligible, or could it affect frequency of an oscillator circuit?__ Indeed his application heats [an entire enclosure](http://www.qsl.net/m0ayf/Crystal-Ovens/OCXO.jpg) so small variations get averaged-out by the large thermal mass. However in crystal oven designs where only the crystal is heated, such as [described by Bill (W4HBK)](http://pensacolasnapper.blogspot.com/2011_03_01_archive.html), I'll bet the effect is much greater. Compare the thermal mass of these two concepts.

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

[![](m0ayf-enclosure-oven_thumb.jpg)](m0ayf-enclosure-oven.jpg)
[![](w4hbk-crystal-oven_thumb.jpg)](w4hbk-crystal-oven.jpg)

</div>

__How does the amount of thermal mass relate to how well it can be controlled?__ How important is negative feedback for partial-on heater operation? Can simple ON/OFF heater regulation adequately stabalize a crystal or enclosure? I'd like to design my own heater, pulling the best elements from the rest I see on the internet. My goals are:

1.   use inexpensive thermistors instead of linear temperature sensors (like [LM335](http://www.ti.com/lit/ds/symlink/lm335.pdf))
2.   use inexpensive quarter-watt resistors as heaters instead of power resistors
3.   be able to set temperature with a knob
4.   be able to monitor temperature of the heater
5.   be able to monitor power delivered to the heater
6.   maximum long-term temperature stability

__Right off the bat, I realized that this requires a PC interface.__ Even if it's not used to adjust temperature (an ultimate goal), it will be used to log temperature and power for analysis. I won't go into the details about how I did it, other than to say that I'm using an ATMEL ATMega8 AVR microcontroller and ten times I second I sample voltage on each of it's six 10-bit ADC pins (PC0-PC5), and send that data to the computer with USART using an eBay special serial/USB adapter based on FTDI. They're <$7 (shipped) and come with the USB cable. Obviously in a consumer application I'd etch boards and use the SMT-only FTDI chips, but for messing around at home I a few a few of these little adapters. They're convenient as heck because I can just add a heater to my prototype boards and it even supplies power and ground. Convenient, right? Power is messier than it could be because it's being supplied by the PC, but for now it gets the job done. On the software side, Python with PySerial listens to the serial port and copies data to a large numpy array, saving it every once and a while. Occasionally a bit is sent wrong and a number is received incorrectly (maybe one an hour), but the error is recognized and eliminated by the checksum (just the sum of all transmitted numbers). Plotting is done with numpy and matpltolib. Code for all of that is at the bottom of this post.

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

[![](IMG_0278_thumb.jpg)](IMG_0278.jpg)
[![](IMG_0279_thumb.jpg)](IMG_0279.jpg)
[![](IMG_0280_thumb.jpg)](IMG_0280.jpg)
[![](IMG_0282_thumb.jpg)](IMG_0282.jpg)

</div>

__That's the data logger circuit I came up with.__ Reading six channels ten times a second, it's more than sufficient for voltage measurement. I went ahead and added an op-amp to the board too, since I knew I'd be using one. I dedicated one of the channels to serve as ambient temperature measurement. See the little red thermistor by the blue resistor? I also dedicated another channel to the output of the op-amp. This way I can measure drive to whatever temperature controller circuity I choose to use down the road. For my first test, I'm using a small thermal mass like one would in a crystal oven. Here's how I made that:

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

[![](IMG_0265_thumb.jpg)](IMG_0265.jpg)
[![](IMG_0264_thumb.jpg)](IMG_0264.jpg)
[![](IMG_0263_thumb.jpg)](IMG_0263.jpg)
[![](IMG_0262_thumb.jpg)](IMG_0262.jpg)

</div>

__I then build the temperature controller part of the circuit.__ It's pretty similar to that previously published. it uses a thermistor in a voltage divider configuration to sense temperature. It uses a trimmer potentiometer to set temperature. An LED indicator light gives some indication of on/off, but keep in mind that a fraction of a volt will turn the Darlington transistor (TIP122) on slightly although it doesn't reach a level high enough to drive the LED. The amplifier by default is set to high gain (55x), but can be greatly lowered (negative gain actually) with a jumper. This lets me test how important gain is for the circuitry.

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

[![](controller_thumb.jpg)](controller.png)

</div>

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

[![](IMG_0261_thumb.jpg)](IMG_0261.jpg)

</div>

__When using a crystal oven configuration, I concluded high high gain (cycling the heater on/off) is a BAD idea.__ While average temperature is held around the same, the crystal oscillates. This is what is occurring above when M0AYF indicates his MK1 heater turns on and off every 40 seconds. While you might be able to get away with it while heating a chassis or something, I think it's easy to see it's not a good option for crystal heaters. Instead, look at the low gain (negative gain) configuration. It reaches temperature surprisingly quickly and locks to it steadily. Excellent.

<div class="text-center">

[![](high-gain_thumb.jpg)](high-gain.png)

</div>

high gain configuration tends to oscillate every 30 seconds

<div class="text-center">

[![](low-gain_thumb.jpg)](low-gain.png)

</div>

low gain / negative gain configuration is extremely stable (fairly high temperature)

<div class="text-center">

[![](low-gain1_thumb.jpg)](low-gain1.png)

</div>

Here's a similar experiment with a lower target temperature. Noise is due to unregulated USB power supply / voltage reference. Undeniably, this circuit does not oscillate much if any

__Clearly low (or negative) gain is best for crystal heaters.__ What about chassis / enclosure heaters? Let's give that a shot. I made an enclosure heater with the same 2 resistors. Again, I'm staying away from expensive components, and that includes power resistors. I used epoxy (gorilla glue) to cement them to the wall of one side of the enclosure.

__I put a "heater sensor" thermistor near the resistors on the case so I could get an idea of the heat of the resistors, and a "case sensor" on the opposite side of the case.__ This will let me know how long it takes the case to reach temperature, and let me compare differences between using near vs. far sensors (with respect to the heating element) to control temperature. I ran the same experiments and this is what I came up with!

<div class="text-center">

[![](case_thumb.jpg)](case.png)

</div>

heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure

__CLOSE SENSOR CONTROL, LOW/HIGH GAIN:__ TOP: heater temperature (blue) and enclosure temperature (green) with low gain (first 20 minutes), then high gain (after) operation. High gain sensor/feedback loop is sufficient to induce oscillation, even with the large thermal mass of the enclosure. BOTTOM: power to the heater (voltage off the op-amp output going into the base of the Darlington transistor). Although I didn't give the low-gain configuration time to equilibrate, I doubt it would have oscillated on a time scale I am patient enough to see. Future, days-long experimentation will be required to determine if it oscillates significantly.[/caption]

<div class="text-center">

[![](case-far-in-control_thumb.jpg)](case-far-in-control.png)

</div>

Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.

__FAR SENSOR CONTROL, HIGH GAIN:__ Even with the far sensor (opposite side of the enclosure as the heater) driving the operational amplifier in high gain mode, oscillations occur. Blue is the far sensor temperature. Green is the sensor near the heater temperature. Due to the larger thermal mass and increased distance the heat must travel to be sensed they take much longer to occur, leading them to be slower and larger than oscillations seen earlier when the heater was very close to the sensor.

__Right off the bat, we observe that even with the increased thermal mass of the entire enclosure (being heated with two dinky 100 ohm 1/4 watt resistors) the system is prone to temperature oscillation if gain is set too high.__ For me, this is the final nail in the coffin - I will never use a comparator-type high gain sensor/regulation loop to control heater current. With that out, the only thing to compare is which is better: placing the sensor near the heating element, or far from it. In reality, with a well-insulated device like I seem to have, it seems like it doesn't make much of a difference! The idea is that by placing it near the heater, it can stabilize quickly. However, placing it far from the heater will give it maximum sensation of "load" temperature. Anywhere in-between should be fine. As long as it's somewhat thermally coupled to the enclosure, enclosure temperature will pull it slightly away from heater temperature regardless of location. Therefore, I conclude it's not that critical where the sensor is placed, as long as it has good contact with the enclosure. Perhaps with long-term study (on the order of hours to days) slow oscillations may emerge, but I'll have to build it in a more permanent configuration to test it out. Lucky, that's exactly what I plan to do, so check back a few days from now!

__Since the data speaks for itself, I'll be concise with my conclusions:__

*   two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat an insulated enclosure with 12V
*   two 1/4 watt 100 Ohm resistors in parallel (50 ohms) are suitable to heat a crystal with 5V
*   low gain or negative gain is preferred to prevent oscillating tempeartures
*   Sensor location on an enclosure is not critical as long as it's well-coupled to the enclosure and the entire enclosure is well-insulated.

I feel satisfied with today's work. Next step is to build this device on a larger scale and fix it in a more permanent configuration, then leave it to run for a few weeks and see how it does. On to making the oscillator! If you have any questions or comments, feel free to email me. If you recreate this project, email me! I'd love to hear about it.

__Here's the code that went on the ATMega8 AVR (it continuously transmits voltage measurements on 6 channels).__

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

/*
8MHZ: 300,600,1200,2400,4800,9600,14400,19200,38400
1MHZ: 300,600,1200,2400,4800
*/
#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

/*
ISR(ADC_vect)
{
    PORTD^=255;
}
*/

void USART_Init(void){
    UBRRL = BAUD_PRESCALE;
    UBRRH = (BAUD_PRESCALE >> 8);
    UCSRB = (1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
    while ( !( UCSRA & (1<<UDRE)) );
    UDR = data;
}

void sendNum(long unsigned int byte){
    if (byte==0){
        USART_Transmit(48);
    }
    while (byte){
        USART_Transmit(byte%10+48);
        byte-=byte%10;
        byte/=10;
    }
}

int readADC(char adcn){
    ADMUX = 0b0100000+adcn;
    ADCSRA |= (1<<ADSC); // reset value
    while (ADCSRA & (1<<ADSC)) {}; // wait for measurement
    return ADC>>6;
}

int sendADC(char adcn){
    int val;
    val=readADC(adcn);
    sendNum(val);
    USART_Transmit(',');
    return val;
}

int main(void){
    ADCSRA = (1<<ADEN)  | 0b111;
    DDRB=255;
    USART_Init();
    int checksum;

    for(;;){
        PORTB=255;
        checksum=0;
        checksum+=sendADC(0);
        checksum+=sendADC(1);
        checksum+=sendADC(2);
        checksum+=sendADC(3);
        checksum+=sendADC(4);
        checksum+=sendADC(5);
        sendNum(checksum);
        USART_Transmit('n');
        PORTB=0;
        _delay_ms(200);
    }
}
```

__Here's the command I used to compile the code, set the AVR fuse bits, and load it to the AVR.__

```bash
del *.elf
del *.hex
avr-gcc -mmcu=atmega8 -Wall -Os -o main.elf main.c -w
pause
cls
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avrdude -c usbtiny -p m8 -F -U flash:w:"main.hex":a -U lfuse:w:0xe4:m -U hfuse:w:0xd9:m
```

__Here's the code that runs on the PC to listen to the microchip, match the data to the checksum, and log it occasionally. __

```python
import serial, time
import numpy
ser = serial.Serial("COM16", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    lines+=1
    if "," in line:
        line=line.split(",")
        for i in range(len(line)):
            line[i]=int(line[i][::-1])

    if line[-1]==sum(line[:-1]):
        line=[time.time()]+line[:-1]
        print lines, line
        data.append(line)
    else:
        print  lines, line, "<-- FAIL"

    if lines%50==49:
        numpy.save("data.npy",data)
        print "nSAVINGn%d lines in %.02f sec (%.02f vals/sec)n"%(lines,
            time.time()-t1,lines/(time.time()-t1))
```

__Here's the code that runs on the PC to graph data.__

```python
import matplotlib
matplotlib.use('TkAgg') # <-- THIS MAKES IT FAST!
import numpy
import pylab
import datetime
import time

def adc2F(adc):
    Vo=adc*5.0/1024.0
    K=Vo*100
    C=K-273
    F=C*(9.0/5)+32
    return F

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

def adc2V(adc):
    Vo=adc*5.0/1024.0
    return Vo

if True:
    print "LOADING DATA"
    data=numpy.load("data.npy")
    data=data
    print "LOADED"

    fig=pylab.figure()
    xs=data[:,0]
    tempAmbient=data[:,1]
    tempPower=data[:,2]
    tempHeater=data[:,3]
    tempCase=data[:,4]
    dates=(xs-xs[0])/60.0
    #dates=[]
    #for dt in xs: dates.append(datetime.datetime.fromtimestamp(dt))

    ax1=pylab.subplot(211)
    pylab.title("Temperature Controller - Low Gain")
    pylab.ylabel('Heater (ADC)')
    pylab.plot(dates,tempHeater,'b-')
    pylab.plot(dates,tempCase,'g-')
    #pylab.axhline(115.5,color="k",ls=":")

    #ax2=pylab.subplot(312,sharex=ax1)
    #pylab.ylabel('Case (ADC)')
    #pylab.plot(dates,tempCase,'r-')
    #pylab.plot(dates,tempAmbient,'g-')
    #pylab.axhline(0,color="k",ls=":")

    ax2=pylab.subplot(212,sharex=ax1)
    pylab.ylabel('Heater Power')
    pylab.plot(dates,tempPower)

    #fig.autofmt_xdate()
    pylab.xlabel('Elapsed Time (min)')

    pylab.show()

print "DONE"
```
June 22nd, 2013

Adding USB to a Cheap Frequency Counter (Again)

Today I rigineered my frequency counter to output frequency to a computer via a USB interface. You might remember that I did this exact same thing two years ago, but unfortunately I fell victim to accidental closed source. When I rigged it the first time, I stupidly tried to get fancy and add USB interface with V-USB requiring special drivers and special software code to retrieve the data. The advantage was that the microcontroller spoke directly to the PC USB port via 2 pins requiring no extra hardware. The stinky part is that I've since lost the software I wrote necessary to decode the data. Reading my old post, I see I wrote "Although it’s hard for me, I really don’t think I can release this [microchip code] right now. I’m working on an idiot’s guide to USB connectivity with ATMEL microcontrollers, and it would cause quite a stir to post that code too early." Obviously I never got around to finishing it, and I've since lost the code. Crap! I have this fancy USB "enabled" frequency counter, but no ability to use it. NOTE TO SELF: NEVER POST PROJECTS ONLINE WITHOUT INCLUDING THE CODE! I guess I have to crack this open again and see if I can reprogram it...

My original intention was just to reprogram the IC and add serial USART support, then use a little FTDI adapter module to serve as a USB serial port. That will be supported by every OS on the planet out of the box. Upon closer inspection, I realized I previously used an ATMega48 which has trouble being programmed by AVRDUDE, so I whipped up a new perf-board based around an ATMega8. I copied the wires exactly (which was stupid, because I didn't have it written down which did what, and they were in random order), and started attacking the problem in software.

The way the microcontroller reads frequency is via the display itself. There are multiplexed digits, so some close watching should reveal the frequency. I noticed that there were fewer connections to the microcontroller than expected - a total of 12. How could that be possible? 8 seven-segment displays should be at least 7+8=15 wires. What the heck? I had to take apart the display to remind myself how it worked. It used a pair of ULN2006A darlington transistor arrays to do the multiplexing (as expected), but I also noticed it was using a CD4511BE BCD-to-7-segment driver to drive the digits. I guess that makes sense. That way 4 wires can drive 7 segments. 8+4=12 wires, which matches up. Now I feel stupid for not realizing it in the first place. Time to screw things back together.

Here's the board I made. 3 wires go to the FTDI USB module (GND, VCC 5V drawn from USB, and RX data), black wires go to the display, and the headers are to aid programming. I added an 11.0592MHz crystal to allow maximum serial transfer speed (230,400 baud), but stupidly forgot to enable it in code. It's all boxed up now, running at 8MHz and 38,400 baud with the internal RC clock. Oh well, no loss I guess.

I wasted literally all day on this. It was so stupid. The whole time I was kicking myself for not posting the code online. I couldn't figure out which wires were for the digit selection, and which were for the BCD control. I had to tease it apart by putting random numbers on the screen (by sticking my finger in the frequency input hole) and looking at the data flowing out on the oscilloscope to figure out what was what. I wish I still had my DIY logic analyzer. I guess this project was what I built it for in the first place! A few hours of frustrating brute force programming and adult beverages later, I had all the lines figured out and was sending data to the computer.

With everything back together, I put the frequency counter back in my workstation and I'm ready to begin my frequency measurement experiments. Now it's 9PM and I don't have the energy to start a whole line of experiments. Gotta save it for another day. At least I got the counter working again!

Here's the code that goes on the microcontroller (it sends the value on the screen as well as a crude checksum, which is just the sum of all the digits)

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

#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void USART_Init(void){
    UBRRL = BAUD_PRESCALE;
    UBRRH = (BAUD_PRESCALE >> 8);
    UCSRB = (1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
    while ( !( UCSRA & (1<<UDRE)) );
    UDR = data;
}

void sendNum(int byte){
    if (byte==0){
        USART_Transmit(48);
    }
    while (byte){
        USART_Transmit(byte%10+48);
        byte-=byte%10;
        byte/=10;
    }
}

void sendBin(int byte){
    char i;
    for (i=0;i<8;i++){
        USART_Transmit(48+((byte>>i)&1));
    }
}

volatile char digits[]={0,0,0,0,0,0,0,0};
volatile char freq=123;

char getDigit(){
    char digit=0;
    if (PINC&0b00000100) {digit+=1;}
    if (PINC&0b00001000) {digit+=8;}
    if (PINC&0b00010000) {digit+=4;}
    if (PINC&0b00100000) {digit+=2;}
    if (digit==15) {digit=0;} // blank
    return digit;
}

void updateNumbers(){
    while ((PINB&0b00000001)==0){} digits[7]=getDigit();
    while ((PINB&0b00001000)==0){} digits[6]=getDigit();
    while ((PINB&0b00010000)==0){} digits[5]=getDigit();
    while ((PINB&0b00000010)==0){} digits[4]=getDigit();
    while ((PINB&0b00000100)==0){} digits[3]=getDigit();
    while ((PINB&0b00100000)==0){} digits[2]=getDigit();
    while ((PINC&0b00000001)==0){} digits[1]=getDigit();
    while ((PINC&0b00000010)==0){} digits[0]=getDigit();
}

int main(void){
    USART_Init();
    char checksum;
    char i=0;
    char digit=0;

    for(;;){
        updateNumbers();
        checksum=0;
        for (i=0;i<8;i++){
            checksum+=digits[i];
            sendNum(digits[i]);
        }
        USART_Transmit(',');
        sendNum(checksum);
        USART_Transmit('n');
        _delay_ms(100);
    }
}

Here's the Python code to listen to the serial port, though you could use any program (note that the checksum is just shown and not verified):

import serial, time
import numpy
ser = serial.Serial("COM15", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    print line

This is super preliminary, but I've gone ahead and tested heating/cooling an oscillator (a microcontroller clocked with an external crystal and outputting its signal with CKOUT). By measuring temperature and frequency at the same time, I can start to plot their relationship...

Markdown source code last modified on January 18th, 2021
---
title: Adding USB to a Cheap Frequency Counter (Again)
date: 2013-06-22 20:07:22
tags: circuit
---

# Adding USB to a Cheap Frequency Counter (Again)

__Today I rigineered my frequency counter to output frequency to a computer via a USB interface.__ You might remember that[ I did this exact same thing two years ago](http://www.swharden.com/blog/2011-07-11-aj4vd-arsenal-recently-expanded/), but unfortunately I fell victim to accidental closed source. When I rigged it the first time, I stupidly tried to get fancy and add USB interface with [V-USB](http://www.obdev.at/products/vusb/index.html) requiring special drivers and special software code to retrieve the data. The advantage was that the microcontroller spoke directly to the PC USB port via 2 pins requiring no extra hardware. The stinky part is that I've since lost the software I wrote necessary to decode the data. Reading my old post, I see I wrote_ "Although it’s hard for me, I really don’t think I can release this \[microchip code\] right now. I’m working on an idiot’s guide to USB connectivity with ATMEL microcontrollers, and it would cause quite a stir to post that code too early."_  Obviously I never got around to finishing it, and I've since lost the code. Crap! I have this fancy USB "enabled" frequency counter, but no ability to use it. NOTE TO SELF: NEVER POST PROJECTS ONLINE WITHOUT INCLUDING THE CODE! I guess I have to crack this open again and see if I can reprogram it...

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

[![](IMG_0285_thumb.jpg)](IMG_0285.jpg)

</div>

__My original intention was just to reprogram the IC and add serial USART support, then use a little FTDI adapter module to serve as a USB serial port.__ That will be supported by every OS on the planet out of the box.  Upon closer inspection, I realized I previously used an ATMega48 which has trouble being programmed by AVRDUDE, so I whipped up a new perf-board based around an ATMega8. I copied the wires exactly (which was stupid, because I didn't have it written down which did what, and they were in random order), and started attacking the problem in software.

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

[![](IMG_0283_thumb.jpg)](IMG_0283.jpg)

</div>

__The way the microcontroller reads frequency is via the display itself.__ There are multiplexed digits, so some close watching should reveal the frequency. I noticed that there were fewer connections to the microcontroller than expected - a total of 12. How could that be possible? 8 seven-segment displays should be at least 7+8=15 wires. What the heck? I had to take apart the display to remind myself how it worked. It used a pair of[ ULN2006A darlington transistor arrays](http://www.ti.com/lit/ds/symlink/uln2003a.pdf) to do the multiplexing (as expected), but I also noticed it was using a [CD4511BE BCD-to-7-segment driver to drive the digits](http://www.play.com.br/datasheet/CD4511.pdf). I guess that makes sense. That way 4 wires can drive 7 segments. 8+4=12 wires, which matches up. Now I feel stupid for not realizing it in the first place. Time to screw things back together.

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

[![](IMG_0288_thumb.jpg)](IMG_0288.jpg)

</div>

__Here's the board I made. 3 wires go to the FTDI USB module (GND, VCC 5V drawn from USB, and RX data), black wires go to the display, and the headers are to aid programming.__ I added an 11.0592MHz crystal to [allow maximum serial transfer speed](http://www.wormfood.net/avrbaudcalc.php) (230,400 baud), but stupidly forgot to enable it in code. It's all boxed up now, running at 8MHz and 38,400 baud with the internal RC clock. Oh well, no loss I guess.

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

[![](IMG_0291_thumb.jpg)](IMG_0291.jpg)
[![](IMG_0293_thumb.jpg)](IMG_0293.jpg)

</div>

__I wasted literally all day on this.__ It was so stupid. The whole time I was kicking myself for not posting the code online. I couldn't figure out which wires were for the digit selection, and which were for the BCD control. I had to tease it apart by putting random numbers on the screen (by sticking my finger in the frequency input hole) and looking at the data flowing out on the oscilloscope to figure out what was what. I wish I still had my [DIY logic analyzer](http://www.swharden.com/blog/2011-07-16-half-hearted-diy-logic-analyzer-works-a-little/). I guess this project was what I built it for in the first place! A few hours of frustrating brute force programming and adult beverages later, I had all the lines figured out and was sending data to the computer.

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

[![](IMG_0289_thumb.jpg)](IMG_0289.jpg)
[![](IMG_0287_thumb.jpg)](IMG_0287.jpg)
[![](IMG_0290_thumb.jpg)](IMG_0290.jpg)
[![](IMG_0288_thumb.jpg)](IMG_0288.jpg)

</div>

__With everything back together,__ I put the frequency counter back in my workstation and I'm ready to begin my frequency measurement experiments. Now it's 9PM and I don't have the energy to start a whole line of experiments. Gotta save it for another day. At least I got the counter working again!

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

[![](IMG_0296_thumb.jpg)](IMG_0296.jpg)

</div>

__Here's the code that goes on the microcontroller __(it sends the value on the screen as well as a crude checksum, which is just the sum of all the digits)

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

#define USART_BAUDRATE 38400
#define BAUD_PRESCALE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)

void USART_Init(void){
    UBRRL = BAUD_PRESCALE;
    UBRRH = (BAUD_PRESCALE >> 8);
    UCSRB = (1<<TXEN);
    UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); // 9N1
}

void USART_Transmit( unsigned char data ){
    while ( !( UCSRA & (1<<UDRE)) );
    UDR = data;
}

void sendNum(int byte){
    if (byte==0){
        USART_Transmit(48);
    }
    while (byte){
        USART_Transmit(byte%10+48);
        byte-=byte%10;
        byte/=10;
    }
}

void sendBin(int byte){
    char i;
    for (i=0;i<8;i++){
        USART_Transmit(48+((byte>>i)&1));
    }
}

volatile char digits[]={0,0,0,0,0,0,0,0};
volatile char freq=123;

char getDigit(){
    char digit=0;
    if (PINC&0b00000100) {digit+=1;}
    if (PINC&0b00001000) {digit+=8;}
    if (PINC&0b00010000) {digit+=4;}
    if (PINC&0b00100000) {digit+=2;}
    if (digit==15) {digit=0;} // blank
    return digit;
}

void updateNumbers(){
    while ((PINB&0b00000001)==0){} digits[7]=getDigit();
    while ((PINB&0b00001000)==0){} digits[6]=getDigit();
    while ((PINB&0b00010000)==0){} digits[5]=getDigit();
    while ((PINB&0b00000010)==0){} digits[4]=getDigit();
    while ((PINB&0b00000100)==0){} digits[3]=getDigit();
    while ((PINB&0b00100000)==0){} digits[2]=getDigit();
    while ((PINC&0b00000001)==0){} digits[1]=getDigit();
    while ((PINC&0b00000010)==0){} digits[0]=getDigit();
}

int main(void){
    USART_Init();
    char checksum;
    char i=0;
    char digit=0;

    for(;;){
        updateNumbers();
        checksum=0;
        for (i=0;i<8;i++){
            checksum+=digits[i];
            sendNum(digits[i]);
        }
        USART_Transmit(',');
        sendNum(checksum);
        USART_Transmit('n');
        _delay_ms(100);
    }
}
```

__Here's the Python code to listen to the serial port, though you could use any program __(note that the checksum is just shown and not verified):

```python
import serial, time
import numpy
ser = serial.Serial("COM15", 38400, timeout=100)

line=ser.readline()[:-1]
t1=time.time()
lines=0

data=[]

def adc2R(adc):
    Vo=adc*5.0/1024.0
    Vi=5.0
    R2=10000.0
    R1=R2*(Vi-Vo)/Vo
    return R1

while True:
    line=ser.readline()[:-1]
    print line
```

__This is super preliminary,__ but I've gone ahead and tested heating/cooling an oscillator (a microcontroller clocked with an external crystal and outputting its signal with CKOUT). By measuring temperature and frequency at the same time, I can start to plot their relationship...

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

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

</div>

<div class="text-center">

[![](tf_thumb.jpg)](tf.png)

</div>
December 18th, 2012

My DIY Bench Power Supply

Another thing everybody needs (and has probably built) is a simple laboratory bench power supply. A lot of people use things like modified PC power supplies but I wasn't in favor of this because I wanted something smaller, lower current, and cleaner (from an RF perspective). My needs are nothing particularly high power, just something to provide a few common voltages for digital logic and small RF circuits. This is what I came up with!

In the image above you can see an ordinary LED being powered directly from the a 5V hook-up. There is no current limiting resistor, so a lot of current is travelling through the LED, burning it up as I photographed it. The ammeter (blue number) shows it's drawing 410 mA - whoa! The layout is pretty simple. Each red banana plug hook-up supplies a voltage (5, 5, 12, and variable respectively). Black hook-ups are ground. The black hook-up on the top left is a current-sensing ground, and current travelling through it will be displayed on the blue dial. The right dial shows the voltage of the variable voltage supply, and can go from about 3.5 - 30.5 V depending on where the potentiometer is set. All voltage outputs are designed to put-out approximately 1A of current.

I built this using a lot of (eBay) components I had on hand. I often save money where I can by stocking my workbench with components I buy in bulk. Here's what I used:

  • 4.5-3.0V DC volt meter - $2.08 (shipped) eBay
  • 0-9.99 A ampere meter - $4.44 (shipped) eBay
  • L7805 5V voltage regulator - 10 for $3.51 ($.35 ea) (shipped) eBay
  • L7812 12V voltage regulator - 20 for $3.87 ($.19 ea) (shipped) eBay
  • LM317 variable voltage regulator - 20 for $6.15 ($0.30 ea) (shipped) eBay
  • 10k linear potentiometer - 10 for 4.00 ($.40 ea) (shipped) eBay
  • banana plug hook-ups - 20 for $3.98 ($.20 ea) (shipped) eBay
  • aluminum enclosure - $3.49 (radioshack)

TOTAL: $13.60

Does the variable voltage actually work? Is the voltmeter accurate? Let's check it out.

I'd say it's working nicely! I now have a new took on my workbench.

A note about the yellow color: The enclosure I got was originally silver aluminum. I sanded it (to roughen the surface), then sprayed it with a yellow rustoleum spray paint. I figured it was intended to go on metal, so I might as well give it a shot. I sprayed it once, then gave it a second coat 20 minutes later, then let it dry overnight. In the future I think I would try a lacquer finish, because it's a bit easy to scratch off. However, it looks pretty cool, and I'm going to have to start spray-painting more of my enclosures in the future.

A note about smoothing capacitors. Virtually all diagrams of linear voltage regulators like the LM7805 show decoupling capacitors before and after the regulator. I added a few different values of capacitors on the input (you can see them in the circuit), but I intentionally did not include smoothing capacitors on the output. The reason was that I always put smoothing capacitors in my breadboards and in my projects, closer to the actual circuitry. If I included (and relied) on output capacitors at the level of the power supply, I would be picking-up 60Hz (and other garbage) RF noise in the cables coming from the power supply to my board. In short, no capacitors on the output, so good design must always be employed and decoupling capacitors added to whatever circuits are being built.

The input of this circuit is a 48V printer power supply from an archaic inkjet printer. It's been attached to an RCA jack to allow easy plugging and unplugging.

Markdown source code last modified on January 18th, 2021
---
title: My DIY Bench Power Supply
date: 2012-12-18 11:30:27
tags: circuit
---

# My DIY Bench Power Supply

__Another thing everybody needs (and has probably built) is a simple laboratory bench power supply.__ A lot of people use things like [modified PC power supplies](http://web2.murraystate.edu/andy.batts/ps/powersupply.htm) but I wasn't in favor of this because I wanted something smaller, lower current, and cleaner (from an RF perspective).  My needs are nothing particularly high power, just something to provide a few common voltages for digital logic and small RF circuits.  This is what I came up with!

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

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

</div>

In the image above you can see an ordinary LED being powered directly from the a 5V hook-up.  There is no current limiting resistor, so a lot of current is travelling through the LED, burning it up as I photographed it. The ammeter (blue number) shows it's drawing 410 mA - whoa!  The layout is pretty simple. Each red banana plug hook-up supplies a voltage (5, 5, 12, and variable respectively). Black hook-ups are ground. The black hook-up on the top left is a current-sensing ground, and current travelling through it will be displayed on the blue dial.  The right dial shows the voltage of the variable voltage supply, and can go from about 3.5 - 30.5 V depending on where the potentiometer is set. All voltage outputs are designed to put-out approximately 1A of current.

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

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

</div>

__I built this using a lot of (eBay) components I had on hand.__ I often save money where I can by stocking my workbench with components I buy in bulk. Here's what I used:

*   4.5-3.0V DC volt meter - $2.08 (shipped) eBay
*   0-9.99 A ampere meter - $4.44 (shipped) eBay
*   L7805 5V voltage regulator - 10 for $3.51 ($.35 ea) (shipped) eBay
*   L7812 12V voltage regulator - 20 for $3.87 ($.19 ea) (shipped) eBay
*   LM317 variable voltage regulator - 20 for $6.15 ($0.30 ea) (shipped) eBay
*   10k linear potentiometer - 10 for 4.00 ($.40 ea) (shipped) eBay
*   banana plug hook-ups - 20 for $3.98 ($.20 ea) (shipped) eBay
*   aluminum enclosure - $3.49 (radioshack)

TOTAL: $13.60

<div class="text-center">

![](LM317.gif)

</div>

Does the variable voltage actually work? Is the voltmeter accurate? Let's check it out.

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

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

</div>

I'd say it's working nicely!  I now have a new took on my workbench.

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

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

</div>

__A note about the yellow color:__ The enclosure I got was originally silver aluminum. I sanded it (to roughen the surface), then sprayed it with a yellow rustoleum spray paint. I figured it was intended to go on metal, so I might as well give it a shot. I sprayed it once, then gave it a second coat 20 minutes later, then let it dry overnight. In the future I think I would try a lacquer finish, because it's a bit easy to scratch off.  However, it looks pretty cool, and I'm going to have to start spray-painting more of my enclosures in the future.

<div class="text-center">

![](rust.jpg)

</div>

__A note about smoothing capacitors.__ Virtually all diagrams of linear voltage regulators like the LM7805 show decoupling capacitors before and after the regulator. I added a few different values of capacitors on the input (you can see them in the circuit), but I intentionally did _not_ include smoothing capacitors on the output. The reason was that I always put smoothing capacitors in my breadboards and in my projects, closer to the actual circuitry. If I included (and relied) on output capacitors at the level of the power supply, I would be picking-up 60Hz (and other garbage) RF noise in the cables coming from the power supply to my board. In short, no capacitors on the output, so good design must always be employed and decoupling capacitors added to whatever circuits are being built.

__The input__ of this circuit is a 48V printer power supply from an archaic inkjet printer. It's been attached to an RCA jack to allow easy plugging and unplugging.
December 6th, 2012

Single Wavelength Pulse Oximeter

⚠️ Check out my newer ECG designs:

I want to create a microcontroller application which will utilize information obtained from a home-brew pulse oximeter. Everybody and their cousin seems to have their own slant how to make DIY pulse detectors, but I might as well share my experience. Traditionally, pulse oximeters calculate blood oxygen saturation by comparing absorbance of blood to different wavelengths of light. In the graph below (from Dildy et al., 1996 that deoxygenated blood (dark line) absorbs light differently than oxygenated blood (thin line), especially at 660nm (red) and 920nm (infrared). Therefore, the ratio of the difference of absorption at 660nm vs 920nm is an indication of blood oxygenation. Fancy (or at least well-designed) pulse oximeters continuously look at the ratio of these two wavelengths. Analog devices has a nice pulse oximeter design using an ADuC7024 microconverter. A more hackerish version was made and described on this non-english forum. A fail-at-the-end page of a simpler project is also shown here, but not well documented IMO.

That's not how mine works. I only use a single illumination source (~660nm) and watch it change with respect to time. Variability is due to a recombination effect of blood volume changes and blood oxygen saturation changes as blood pulses through my finger. Although it's not quite as good, it's a bit simpler, and it definitely works. Embedded-lab has a similar project but the output is only a pulsing LED (not what I want) and a voltage output that only varies by a few mV (not what I want).

Here's what the device looks like assembled in a breadboard:

I made a sensor by drilling appropriately-sized holes in a clothespin for the emitter (LED) and sensor (phototransistor). I had to bend the metal spring to make it more comfortable to wear. Light pressure is better than firm pressure, not only because it doesn't hurt as much, but because a firm pinch restricts blood flow considerably.

An obvious next step is microcontroller + LCD (or computer) digitization, but for now all you can do is check it out on my old-school analog oscilloscope. Vertical squares represent 1V (nice!). You can see the pulse provides a solid 2V spike.

Here's some video of it in action:

I'm holding-back the circuit diagram until I work through it a little more. I don't want to mislead people by having them re-create ill-conceived ideas on how to create analog amplifiers. I'll post more as I develop it.

Markdown source code last modified on January 18th, 2021
---
title: Single Wavelength Pulse Oximeter
date: 2012-12-06 08:13:41
tags: circuit, diyECG, old
---

# Single Wavelength Pulse Oximeter

> **⚠️ Check out my newer ECG designs:** 
* [**Sound Card ECG with AD8232**](https://swharden.com/blog/2019-03-15-sound-card-ecg-with-ad8232/)
* [**Single op-amp ECG**](https://swharden.com/blog/2016-08-08-diy-ecg-with-1-op-amp/)

__I want to create a microcontroller application__ which will utilize information obtained from a home-brew pulse oximeter. Everybody and their cousin seems to have their own slant how to make DIY pulse detectors, but I might as well share my experience. Traditionally, pulse oximeters calculate blood oxygen saturation by comparing absorbance of blood to different wavelengths of light. In the graph below (from [Dildy et al., 1996](http://www.ncbi.nlm.nih.gov/pubmed/8694032) that deoxygenated blood (dark line) absorbs light differently than oxygenated blood (thin line), especially at 660nm (red) and 920nm (infrared). Therefore, the ratio of the difference of absorption at 660nm vs 920nm is an indication of blood oxygenation. Fancy (or at least well-designed) pulse oximeters continuously look at the ratio of these two wavelengths. Analog devices has a [nice pulse oximeter design using an ADuC7024 microconverter](http://www.analog.com/library/analogDialogue/archives/41-01/pulse_oximeter.html). A more hackerish version was made and described [on this non-english forum](http://www.elektroda.pl/rtvforum/viewtopic.php?p=8025042). A fail-at-the-end page of a simpler project is also shown [here](http://blog.energymicro.com/2012/11/21/create-a-simple-pulse-oximeter-with-tiny-gecko/), but not well documented IMO.

<div class="text-center">

![](pulse-oximeter-wavelength.jpg)

</div>

That's not how mine works. I only use a single illumination source (~660nm) and watch it change with respect to time. Variability is due to a recombination effect of blood volume changes and blood oxygen saturation changes as blood pulses through my finger. Although it's not quite as good, it's a bit simpler, and it definitely works. [Embedded-lab has a similar project](http://embedded-lab.com/blog/?p=5508) but the output is only a pulsing LED (not what I want) and a voltage output that only varies by a few mV (not what I want).

__Here's what the device looks like assembled in a breadboard:__


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

[![](IMG_5919_thumb.jpg)](IMG_5919.jpg)

</div>

__I made a sensor__ by drilling appropriately-sized holes in a clothespin for the emitter (LED) and sensor (phototransistor). I had to bend the metal spring to make it more comfortable to wear. Light pressure is better than firm pressure, not only because it doesn't hurt as much, but because a firm pinch restricts blood flow considerably.

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

[![](IMG_5920_thumb.jpg)](IMG_5920.jpg)
[![](IMG_5924_thumb.jpg)](IMG_5924.jpg)

</div>

__An obvious next step__ is microcontroller + LCD (or computer) digitization, but for now all you can do is check it out on my old-school analog oscilloscope. Vertical squares represent 1V (nice!). You can see the pulse provides a solid 2V spike.

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

![](pulse-scope.jpg)

</div>

__Here's some video of it in action:__

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

I'm holding-back the circuit diagram until I work through it a little more. I don't want to mislead people by having them re-create ill-conceived ideas on how to create analog amplifiers. I'll post more as I develop it.
Pages